UNPKG

agentfootprint

Version:

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

184 lines (183 loc) • 9.35 kB
/** * options — what `mcpClientPool` is built from, read and checked once. * * Pattern: one options type + one reader that refuses by name. * Role: The pool's budgets and clocks (`docs/design/2026-10-mcp-client-pool.md` * § 3.1). Every number is checked when the pool is built, never at the * first call. The pool takes the `mcpClient` options that shape one * connection and hands them to every client it opens; it never takes * `connection`, because it must build the transport itself — to end * the session, to hear the tools-changed notice, and to place the send * gate. */ import type { MemoryIdentity } from '../../memory/identity/types.js'; import { type McpReplayOptions } from '../replay.js'; import type { RetryOnThrottle } from '../throttleRetry.js'; import type { McpJsonSchemaValidator, McpSdk, McpTransport } from '../types.js'; import type { McpPoolReport } from './record.js'; import type { PoolClientOpener } from './plan.js'; /** The identity a pool keys a client by: `{ tenant?, principal?, conversationId }`. */ export type McpPoolIdentity = MemoryIdentity; /** What a conversation's tool list may be shared under: two names the app vouches for. */ export interface McpPoolShareContext { /** * What the transport hides in its code — a signer's role, the credentials of * an AWS client, a custom `fetch`. Hashed into the context, never recorded. */ readonly configuration: string; /** * What the server's tool visibility depends on — a role and a policy version * (`'analyst@policy-7'`). A version in the name reaches a new entry without * `permissionsChanged`, for conversations that already have a client too: * their tools are listed again under the new name, and a tool bound under the * old one is refused before it is sent. Hashed into the context, never * recorded. */ readonly authorization: string; } /** * Share tool definitions between conversations — declared by the app, checked by * the pool. `share` is asked for every conversation before anything opens, and * again at every listing, every call and every send gate after that, so a * changed answer takes effect at the next one; it must be synchronous and * cheap. A listing's answer is kept only if the names still give the context it * was listed for when it arrives — otherwise it is dropped and listed again. Return both names, or `undefined` to keep that conversation's list to * itself. Conversations share a list only when the transport's comparable * data, both names and the tenant are all equal. A `share` that throws refuses * the open (`init-failed`) or, for a conversation that already has a client, * that listing or call (`binding-changed`) — its error as `cause`. Never share * a server whose list follows the session (one that unlocks tools as a * conversation goes on). */ export interface McpPoolDefinitionSharing { readonly share: (identity: McpPoolIdentity) => McpPoolShareContext | undefined; } /** Options for {@link mcpClientPool}. */ export interface McpClientPoolOptions { /** The server's name: every tool's `source`, the provider's id, and the record. */ readonly name: string; /** * The transport each client is opened over. A FUNCTION of the identity is * preferred: it is called before each client opens (and again for every * replacement) with the identity the client is for, so each client gets a * transport — and a session map — of its own, and a provider can vend THAT * user's credential. One shared object serves every client; a shared * `'reuse'` runtime transport then needs `maxClients + maxEnding` at or below * its 256 remembered connections. */ readonly transport: McpTransport | ((identity: McpPoolIdentity) => McpTransport); /** Open clients — opening, busy or idle. Default 64. */ readonly maxClients?: number; /** Open clients for one principal (one tenant's principal). Default 8. */ readonly maxClientsPerPrincipal?: number; /** Clients being ended at once, until each ending's work has settled. Default 8. */ readonly maxEnding?: number; /** * Opens, listings, calls and endings outstanding on any client, each from * its admission until its work has settled — not merely until its caller was * answered. Default 256. Size it above `maxEnding`. */ readonly maxOperations?: number; /** One open per conversation, under this ceiling. Default 30 000 ms. */ readonly initTimeoutMs?: number; /** * End a client idle this long, at the next pool interaction (no timer is * installed). Default 600 000 ms — below AgentCore Runtime's 900 s idle * timeout; set it lower for a runtime configured lower. */ readonly idleMs?: number; /** An app lease (`acquire`) is revoked past this. Default 900 000 ms (15 minutes). */ readonly maxLeaseMs?: number; /** A tool list is listed again after this, at the next list. Default 600 000 ms. */ readonly definitionTtlMs?: number; /** `'per-client'` (the default), or the app's declared sharing contract. */ readonly definitions?: 'per-client' | McpPoolDefinitionSharing; /** Refuse an identity without a `principal`. Default `true`. */ readonly requirePrincipal?: boolean; /** Refuse an identity without a `tenant` — for multitenant hosts. Default `false`. */ readonly requireTenant?: boolean; /** * What happens outside a run — endings, deferred endings, revoked leases, * work that settled after its deadline. Ids and outcomes only. A throwing * handler is swallowed and changes nothing. */ readonly onEvent?: (report: McpPoolReport) => void; /** Passed to every client: how a tool's result is decoded (`McpClientOptions.resultMode`). */ readonly resultMode?: 'text' | 'structured' | 'structured-or-json'; /** Passed to every client: the `initialize` identity. */ readonly clientInfo?: { readonly name: string; readonly version: string; }; /** Passed to every client: the 429 retry (`McpClientOptions.retryOnThrottle`). */ readonly retryOnThrottle?: RetryOnThrottle; /** * Passed to every client: who may send a tool call again * (`McpClientOptions.replay`). Read and checked when the pool is built. The * default stays `'compatibility'`. Whatever the policy, the pool's own send * gate refuses a pooled request it cannot check (`request-unmatched`). */ readonly replay?: McpReplayOptions; /** Passed to every client: the two SDK modules, imported by you (`McpClientOptions.sdk`). */ readonly sdk?: McpSdk; /** * Passed to every client: the JSON Schema validator a tool's own output checks * use (`McpClientOptions.jsonSchemaValidator`). With `sdk`, give it: a shared * definition always runs its own checks, and without a validator a tool that * declares an `outputSchema` is refused unsent. */ readonly jsonSchemaValidator?: McpJsonSchemaValidator; /** Clock seam — tests drive idle, lease and definition ages without waiting. */ readonly now?: () => number; /** @internal Test seam: how a client is opened (the real path is `openPooledClient`). */ readonly _open?: PoolClientOpener; } /** The defaults, named so a test and a docstring cannot drift from the code. */ export declare const MCP_POOL_DEFAULTS: Readonly<{ maxClients: 64; maxClientsPerPrincipal: 8; maxEnding: 8; maxOperations: 256; initTimeoutMs: 30000; idleMs: 600000; maxLeaseMs: 900000; definitionTtlMs: 600000; }>; /** At most this many shared definition entries are kept; the least recently used goes first. */ export declare const MAX_DEFINITION_ENTRIES = 256; /** A list longer than this is used by its own client and never shared. */ export declare const MAX_SHARED_TOOLS = 512; /** The pool's settings, read once. */ export interface PoolSettings { readonly name: string; readonly transport: (identity: McpPoolIdentity) => McpTransport; readonly maxClients: number; readonly maxClientsPerPrincipal: number; readonly maxEnding: number; readonly maxOperations: number; readonly initTimeoutMs: number; readonly idleMs: number; readonly maxLeaseMs: number; readonly definitionTtlMs: number; readonly share: McpPoolDefinitionSharing['share'] | undefined; readonly requirePrincipal: boolean; readonly requireTenant: boolean; readonly onEvent: ((report: McpPoolReport) => void) | undefined; readonly client: { readonly resultMode?: McpClientPoolOptions['resultMode']; readonly clientInfo?: McpClientPoolOptions['clientInfo']; readonly retryOnThrottle?: RetryOnThrottle; readonly replay?: McpReplayOptions; readonly sdk?: McpSdk; readonly jsonSchemaValidator?: McpJsonSchemaValidator; }; readonly now: () => number; readonly open: PoolClientOpener | undefined; } /** * Read `options` once. * * @throws TypeError naming the option, when one is missing, mistyped or out of * range, or when `connection` is given — here, when the pool is built. */ export declare function readPoolOptions(options: McpClientPoolOptions): PoolSettings;