UNPKG

agentfootprint

Version:

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

92 lines • 4.81 kB
"use strict"; /** * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.unavailableBeforeSending = exports.McpReplayUnavailableError = void 0; /** The fixed sentence each reason reads as. Never an argument, a header or an answer. */ const BECAUSE = { 'requests-unwatched': "the library cannot watch this transport's requests, so it cannot match a request to its call", 'answers-unobserved': "this connection's answers do not pass the library's observer: the MCP SDK replaced the " + "transport's message handler when it connected", 'request-unmatched': "the request reached the transport without its call's marker: the MCP SDK rebuilt the " + "request's params", }; /** * 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. */ class McpReplayUnavailableError extends Error { reason; server; tool; 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. */ sent = false; constructor(reason, server, tool) { super(`mcpClient[${server}]: the call to '${tool}' was not sent. The 'app-authorized' replay ` + `policy needs each request matched to its call and each answer seen, and ` + `${BECAUSE[reason]}.`); this.reason = reason; this.server = server; this.tool = tool; this.name = 'McpReplayUnavailableError'; } } exports.McpReplayUnavailableError = McpReplayUnavailableError; /** * 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'`). */ function unavailableBeforeSending(ledger) { if (ledger === undefined) return 'requests-unwatched'; if (!ledger.answersObserved) return 'answers-unobserved'; return undefined; } exports.unavailableBeforeSending = unavailableBeforeSending; //# sourceMappingURL=replayUnavailable.js.map