agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
144 lines (143 loc) • 7.73 kB
TypeScript
/**
* 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 { type FetchLike } from '../../mcp/gatewayTransport.js';
import type { McpGatewayTransport } from '../../mcp/types.js';
import type { CredentialProvider } from '../../identity/types.js';
import type { Tool } from '../../tools/tools.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 declare 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 declare 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 declare const AGENTCORE_SIGV4_SERVICE = "bedrock-agentcore";
export interface AgentCoreGatewayUrlOptions {
/** The gateway's id, as the console and `CreateGateway`'s response give it. */
readonly gatewayId: string;
/** The region it lives in, e.g. `'us-east-1'`. */
readonly region: string;
}
/**
* 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 declare function agentCoreGatewayUrl(options: AgentCoreGatewayUrlOptions): string;
export interface AgentCoreGatewayTransportOptions extends AgentCoreGatewayUrlOptions {
/** Who vends the token. `agentCoreIdentity()` is the usual answer. */
readonly credentials: CredentialProvider;
/** The downstream service id your provider keys on. Default `'gateway'`. */
readonly service?: string;
/** OAuth scopes, when the provider uses them. */
readonly scopes?: readonly string[];
/** `machine` (default) or `user`, for a gateway that acts on someone's behalf. */
readonly mode?: 'machine' | 'user';
/**
* The policy session this caller's requests belong to — a string, or a
* function consulted PER REQUEST.
*
* **Prefer the function on any transport more than one person shares.** A
* fixed string on a shared transport merges every caller's action history
* into a single policy session, which is not a small mistake: it makes one
* person's earlier actions count against another person's rule. Passing
* `() => currentSessionId` keeps the boundary where it belongs, and returning
* `undefined` sends no header at all.
*/
readonly policySessionId?: string | (() => string | undefined);
/** Extra static headers. **No secrets** — these live as long as the transport. */
readonly headers?: Readonly<Record<string, string>>;
/** Your own `fetch`, called underneath the per-request vending. */
readonly fetch?: FetchLike;
}
/**
* 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 declare function agentCoreGatewayTransport(options: AgentCoreGatewayTransportOptions): McpGatewayTransport;
/**
* 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 declare function gatewaySearchTool(tools: readonly Tool[]): Tool | undefined;
/** Whether this gateway's catalogue can be searched rather than listed. */
export declare function hasGatewaySearch(tools: readonly Tool[]): boolean;