UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

149 lines • 7.58 kB
/** * adapters/mcp/agentcore — reaching an AWS Bedrock **AgentCore Gateway**. * * ── What lives here, and why it is one file ────────────────────────────────── * A Gateway is an MCP server, and `mcpClient` + `gatewayTransport` already know * how to talk to one of those. Neither knows — and neither should learn — the * handful of facts that are AgentCore's alone: the hostname its endpoints take, * the name of the tool that searches its catalogue, the header that groups a * caller's requests into one policy session, and the service name a SigV4 * signature is computed against. Those four facts are this file, and this file * is the only place in the library that holds them. * * `gatewayTransport` says of itself: *"Nothing here is vendor-specific."* That * stays true precisely because this exists next to it. * * ── What it does NOT do ────────────────────────────────────────────────────── * It does not manage a Gateway. Creating one, adding targets, attaching a * policy engine, enabling semantic search — all control-plane operations on * `bedrock-agentcore-control`, all things an operator does once with the * console, the CLI or their IaC. This is the CLIENT side: an agent using a * gateway somebody already stood up. * * @example An agent whose tools come from a Gateway * import { mcpClient } from 'agentfootprint/providers'; * import { agentCoreGatewayTransport } from 'agentfootprint/providers'; * import { agentCoreIdentity } from 'agentfootprint/security'; * * const gateway = await mcpClient({ * name: 'gateway', * transport: agentCoreGatewayTransport({ * gatewayId: 'my-gateway-a1b2c3d4e5', * region: 'us-east-1', * credentials: agentCoreIdentity({ region: 'us-east-1' }), * }), * }); * const tools = await gateway.tools(); */ import { gatewayTransport } from '../../mcp/gatewayTransport.js'; /** * The Gateway's built-in semantic tool search, by its exact wire name. * * It is an ordinary MCP tool — `tools/call` with `{ query }` — that returns the * catalogue entries closest to a natural-language description. It matters at * the scale where listing every tool into a prompt stops being sensible. * * **It can only be enabled when the Gateway is CREATED**, never afterwards, so * its absence from a catalogue is a fact about that gateway rather than a * transient condition to retry. */ export const AGENTCORE_GATEWAY_SEARCH_TOOL = 'x_amz_bedrock_agentcore_search'; /** * The header that groups a caller's requests into ONE policy session. * * AgentCore's temporal policies decide on SEQUENCES of actions — "not after * three refunds", "only once this was approved" — and a sequence needs a * boundary. This header is that boundary, and without it every request is its * own history of one, which quietly makes every sequence rule unenforceable. */ export const AGENTCORE_POLICY_SESSION_HEADER = 'x-amzn-bedrock-agentcore-policy-session-id'; /** The service name a SigV4 signature for a Gateway is computed against. */ export const AGENTCORE_SIGV4_SERVICE = 'bedrock-agentcore'; /** * The MCP endpoint of a Gateway. * * `https://{gatewayId}.gateway.bedrock-agentcore.{region}.amazonaws.com/mcp` — * a shape nobody remembers correctly, which is the entire reason it is a * function and not a line in a README. */ export function agentCoreGatewayUrl(options) { const { gatewayId, region } = options; if (!gatewayId || !region) { throw new TypeError('agentCoreGatewayUrl: both `gatewayId` and `region` are required — the endpoint hostname ' + 'is built from the two.'); } return `https://${gatewayId}.gateway.bedrock-agentcore.${region}.amazonaws.com/mcp`; } /** * An MCP transport pointed at an AgentCore Gateway. * * A configuration of {@link gatewayTransport}: the endpoint built from your * gateway's id and region, and — when you name one — the policy session header * stamped on every request. Token vending, the once-and-dropped secrecy rule * and the rotation behaviour are all the neutral transport's, unchanged. */ export function agentCoreGatewayTransport(options) { const { policySessionId, fetch: innerFetch } = options; // The header is stamped in the `fetch` seam rather than in `headers` because // the seam runs per request. That is what lets the session id come from a // function, which is what keeps two people on one transport in two sessions. const stampPolicySession = policySessionId === undefined ? innerFetch : async (input, init) => { const id = typeof policySessionId === 'function' ? policySessionId() : policySessionId; const next = id === undefined || id === '' ? init : { ...init, headers: { ...init?.headers, [AGENTCORE_POLICY_SESSION_HEADER]: id, }, }; return innerFetch ? innerFetch(input, next) : globalThis.fetch(input, next); }; return gatewayTransport({ url: agentCoreGatewayUrl(options), credentials: options.credentials, ...(options.service !== undefined && { service: options.service }), ...(options.scopes !== undefined && { scopes: options.scopes }), ...(options.mode !== undefined && { mode: options.mode }), ...(options.headers !== undefined && { headers: options.headers }), ...(stampPolicySession !== undefined && { fetch: stampPolicySession }), }); } /** * The Gateway's semantic search tool, if this gateway has one. * * Returns `undefined` rather than throwing, because absence is a legitimate and * PERMANENT answer: semantic search is enabled when a Gateway is created and * cannot be turned on afterwards, so there is nothing to retry. * * ── Why this finds the tool instead of calling it ──────────────────────────── * The obvious convenience would be `search(gateway, 'refund an order')`, and it * is deliberately not here. Executing a tool needs a `ToolExecutionContext` — * the call id, the iteration, the credential seam, the artifact store — and * that object belongs to the agent loop. A helper would have to invent one, * and a call made on an invented context is a call that appears in no trace: * the model would be handed a shortlist nobody can later explain the origin of, * which is the opposite of what this library is for. * * So the search tool is registered like any other tool, the model calls it when * the catalogue is too large to reason about, and that call is an ordinary * tool call in the trace — visible, attributable, and replayable. * * @example Give the model the catalogue's own search * const tools = await gateway.tools(); * const search = gatewaySearchTool(tools); * Agent.create({ provider, model }) * .tools(search ? [search] : tools) // search it, or list it * .build(); */ export function gatewaySearchTool(tools) { return tools.find((t) => t.schema.name === AGENTCORE_GATEWAY_SEARCH_TOOL); } /** Whether this gateway's catalogue can be searched rather than listed. */ export function hasGatewaySearch(tools) { return gatewaySearchTool(tools) !== undefined; } //# sourceMappingURL=agentcore.js.map