agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
69 lines (68 loc) • 3.84 kB
TypeScript
/**
* replayUnavailable — when a connection cannot keep the `'app-authorized'`
* contract, a tool call under it is refused BEFORE its request is sent, and the
* error says why.
*
* Pattern: a readiness rule + one typed refusal.
* Role: Under `'app-authorized'`, a call that genuinely lacked an answer and
* then fails is raised as "may have run" (`callLifetime.ts`). That
* rests on two things the library must ESTABLISH for every call,
* never assume:
*
* 1. ITS REQUEST IS MATCHED to the call, by the call's private marker, taken
* at the transport's `send` (`callMarker.ts`, `requestLedger.ts`).
* - `'requests-unwatched'`: the library cannot watch this transport's
* `send` at all (stdio, or a transport that refuses the wrap).
* - `'request-unmatched'`: a request reached `send` without its call's
* marker (an SDK that rebuilt the params and dropped `_meta`), so it
* cannot be tied to any call. Checked on every `tools/call`, so a match
* lost after the connection initialized is refused too.
* 2. ITS ANSWER IS SEEN: the JSON-RPC response to the request's id, read as
* it arrives. This is checked once the connection is initialized: the
* `initialize` answer must have passed the library's observer.
* - `'answers-unobserved'`: it did not. An MCP SDK whose `connect`
* REPLACES the transport's `onmessage` handler instead of keeping it
* leaves the observer out. SDK 1.10's `Protocol.connect` does, when
* run; reading the source finds the same through 1.13. The rule tests
* the behaviour, never a version number.
*
* Without either, a request the server accepted and never answered would reach
* the caller as a plain timeout: the strict contract, silently weakened. So the
* tool call is refused instead, and its request is not sent. Only tool
* invocations (`tools/call`) are refused: connecting (`initialize`) and listing
* tools ran before them and may already have reached the server (on `stdio`,
* the server process has started). The policy never falls back to
* `'compatibility'`, and a refused request is never sent again to recover its
* match. `'compatibility'` asks none of this and keeps its behaviour.
*/
/** Why a call under `'app-authorized'` was refused before it was sent. */
export type McpReplayUnavailableReason = 'requests-unwatched' | 'answers-unobserved' | 'request-unmatched';
/**
* A tool call refused before its request was sent, because this connection
* cannot keep the `'app-authorized'` replay contract (`reason`). The tool
* request never reached the server; connecting and listing tools may have.
* Names the server and the tool, never an argument.
*/
export declare class McpReplayUnavailableError extends Error {
readonly reason: McpReplayUnavailableReason;
readonly server: string;
readonly tool: string;
readonly code: "ERR_MCP_REPLAY_UNAVAILABLE";
/**
* This tool request was not sent: the refusal came before it left the
* client. It says nothing of the connection: `initialize` and `tools/list`
* ran before it, and on `stdio` the server process was started.
*/
readonly sent: false;
constructor(reason: McpReplayUnavailableReason, server: string, tool: string);
}
/**
* Why a connection cannot take a call under `'app-authorized'` before anything
* is sent — `undefined` when it can. `ledger` is the connection's request
* ledger: absent when the library could not watch the transport's requests.
* A request that reaches the transport without its marker is refused there, by
* the ledger (`'request-unmatched'`).
*/
export declare function unavailableBeforeSending(ledger: {
readonly answersObserved: boolean;
} | undefined): McpReplayUnavailableReason | undefined;