UNPKG

agentfootprint

Version:

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

161 lines (160 loc) • 8.52 kB
/** * callChecks — a listing never makes a callable tool lose its checks. * * Pattern: the SDK's three per-tool call checks, carried by every tool the * library hands out, plus one typed refusal. * Role: The MCP SDK's `Client` runs three checks inside `callTool`, and it * keeps them only for the tools of its most recent `tools/list` * answer (1.32: `cacheToolMetadata` replaces them on every answer): * * 1. a tool with `execution.taskSupport: 'required'` is refused before a * plain `tools/call` is sent; * 2. a tool with an `outputSchema` must answer with `structuredContent`, * unless the answer is an error; * 3. that `structuredContent` must match the schema. * * So a tool that declares any of them carries them itself, as the listing it * came from declared them. The library leaves them to the SDK only for a tool * of a ONE-page list whose answer is still the latest, with no list request * out (`listingGenerations.ts`), both when the call starts and when its answer * comes in: the one-page normal case, which runs exactly as it always did. * Every other call runs the tool's own checks, at the SDK's moments — the first * before sending, the other two once the answer is in, before anything reads it * — and raises the SDK's error: its own `McpError` where this arm loaded the * SDK, otherwise an error with the same name, code and message. Those calls * are to a tool of a paged list, a tool whose list a later answer replaced (a * refresh that failed or succeeded; a reference held across it), and any call * made while a list is being read. * * All of this covers the lists the library reads (`tools()`, `refresh()`); a * listing it does not send — the SDK client's own automatic refresh, a * `listTools` call of the caller's — is not seen (`listingGenerations.ts`). * * The output checks need a JSON Schema validator equal to the client's: the * SDK's default (`AjvJsonSchemaValidator`, loaded with the SDK on the Node * loader arm), or the caller's `jsonSchemaValidator`. Where neither exists — an * `sdk` or `connection` arm given none — an answer under these rules is never * returned unchecked (`McpOutputUncheckedError`): * * - a call under the tool's own checks from the start, to a tool that * declares an `outputSchema`, is refused before it is sent (`sent: false`); * - a call that started under the SDK's checks is refused once its answer is * in, unread, when the library sent a `tools/list` request while the call * was out (`sent: true`): the SDK may have checked it against another list. * * A schema is compiled when a tool's own checks first need it, never before: * the one-page normal case compiles nothing twice. */ import type { McpCallToolResult, McpJsonSchemaValidator, McpListedTool } from './types.js'; import type { ListingGenerations } from './listingGenerations.js'; /** The SDK's `McpError` constructor, as `@modelcontextprotocol/sdk/types.js` exports it. */ export type McpErrorClass = new (code: number, message: string, data?: unknown) => Error; /** A compiled output check, as the SDK's validator returns one. */ type Validate = (input: unknown) => { readonly valid: boolean; readonly errorMessage?: string; }; /** * What one connection checks its tools' calls with: a validator equal to its * client's, the SDK's own error class where the library loaded the SDK, and * which `tools/list` answer the SDK holds its checks for. */ export interface CallCheckKit { /** The client's name, as its refusals name it. */ readonly server: string; /** The validator; created on first use. `undefined` when none is reachable. */ validator(): McpJsonSchemaValidator | undefined; /** The SDK's own `McpError`, when this arm loaded the SDK. */ readonly McpError?: McpErrorClass; /** This connection's list requests, counted (`listingGenerations.ts` · `listingsOf`). */ readonly listings: ListingGenerations; } /** * The kit for one connection, named `server`, whose list requests `listings` * counts (`listingsOf(connection)`, shared by every client over it). `given` is * the caller's `jsonSchemaValidator`; `loaded` holds what the library loaded * with the SDK on the Node loader arm — the SDK's default validator class and * its `McpError` — each `undefined` where it is not a constructor (another arm, * or an SDK without it). */ export declare function callCheckKit(server: string, listings: ListingGenerations, given: McpJsonSchemaValidator | undefined, loaded?: { readonly Validator?: unknown; readonly McpError?: unknown; }): CallCheckKit; /** * `value` as a validator, or `undefined` when it was not given. Refused by name * when it is something else, before anything connects. */ export declare function validatorOption(value: unknown, at: string): McpJsonSchemaValidator | undefined; /** The checks one listed tool carries, as the listing it came from declared them. */ export interface CallChecks { readonly server: string; readonly tool: string; /** `execution.taskSupport: 'required'`: only ever run as a task. */ readonly taskRequired: boolean; /** The tool declares an `outputSchema`. */ readonly outputDeclared: boolean; /** * True while the SDK's `Client` holds this tool's checks: the tool came from * a one-page list whose answer is still the latest, and no list request is * out. Then the SDK checks the call, and nothing here runs. */ heldBySdk(): boolean; /** * The compiled output check, compiled from the declared schema on first use. * `undefined` when no output is declared or no validator is reachable. */ validate(): Validate | undefined; /** Raise an SDK error, as this arm can: the SDK's class, or its exact shape. */ fail(code: number, message: string): Error; } /** * The checks `listed` carries — `undefined` when it declares none. `generation` * is the one answer its list was read in, when it was read in one * (`toolList.ts` · `ToolListing`); a paged list's tools have none, so the SDK * never holds all of them. */ export declare function checksFor(listed: McpListedTool, kit: CallCheckKit, generation: number | undefined): CallChecks | undefined; /** * True when this call's output cannot be checked here: it runs under the * tool's own checks, the tool declares an `outputSchema`, no validator is * reachable, and it is not refused as a required task anyway (that refusal is * the SDK's, and comes first). The call is then refused before it is sent. */ export declare function outputUnchecked(checks: CallChecks): boolean; /** The SDK's task refusal, word for word, before anything is sent. */ export declare function refuseRequiredTask(checks: CallChecks): void; /** * The SDK's two output checks, in its order and with its sentences, once the * answer is in and before anything reads it. An answer no validator here can * check is never returned: it is refused, unread, as sent. */ export declare function checkOutput(checks: CallChecks, result: McpCallToolResult): void; /** * A tool call whose output this connection cannot check, refused. The tool * declares an `outputSchema`, the MCP SDK no longer holds the checks of the * tool list it came from, and this connection has no JSON Schema validator to * check it with — so its answer is never returned: * * - `sent: false` — refused before it was sent: nothing reached the server. * The tool came from a paged list, from a list a later answer replaced, or * the call was made while a list was being read. * - `sent: true` — the call was sent, and a `tools/list` request went out * before its answer came in: the SDK may have checked that answer against * the other list, so it is refused unread. The tool may have run. * * Pass `jsonSchemaValidator` — the validator your client uses — to `mcpClient`. * Names the server and the tool, never an argument or an answer. */ export declare class McpOutputUncheckedError extends Error { readonly server: string; readonly tool: string; /** Whether the request left the client: `false` when the refusal came before it. */ readonly sent: boolean; readonly code: "ERR_MCP_OUTPUT_UNCHECKED"; readonly reason: "output-unchecked"; constructor(server: string, tool: string, /** Whether the request left the client: `false` when the refusal came before it. */ sent: boolean); } export {};