UNPKG

agentfootprint

Version:

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

121 lines • 5.98 kB
"use strict"; /** * connectionRefusals — the two arms of `mcpClient` refuse to be mixed, at * CONSTRUCTION, in words that name where the behaviour went. * * ── Why refuse rather than ignore ──────────────────────────────────────────── * `mcpClient({ connection })` hands over a client somebody else built, so this * library builds no transport on that arm — and every option consumed INSIDE a * transport therefore has nothing to act on. `retryOnThrottle` is the sharp * one: it is ON by default, it is consumed by `retryingFetch` around the * transport's `fetch`, and accepting it here would leave a caller holding an * option that NAMES a behaviour which no longer happens. A knob that lies about * what it does is worse than one that is absent, so these throw. * * The type union refuses the same combinations at compile time * (`McpConnectionOptions` declares each of them `?: undefined`). This is the * runtime half, and it is not redundant: excess-property checking does not * survive a spread, and JavaScript callers have no compiler at all. * * Pattern: pure guard. Role: Layer-3 tool integration. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.refuseConflictingOptions = void 0; /** The three methods this library calls on a connection. Nothing else. */ const CONNECTION_METHODS = ['listTools', 'callTool', 'close']; /** * Refuse a mixed or malformed options object before anything connects. * * @param opts what the caller passed, before any defaulting * @param name the client's logical name, so a multi-server app knows which one * @throws naming the two options that cannot travel together, or the member the * connection is missing */ function refuseConflictingOptions(opts, name) { const at = `mcpClient[${name}]`; // Read through a permissive view on purpose. The union already narrows these // combinations away at compile time, so narrowing here would leave the guard // reasoning about a shape it exists precisely to disbelieve — an object built // by a spread, or by JavaScript. const given = opts; if (given['connection'] === undefined) { if (given['transport'] === undefined && given['_client'] === undefined) { throw new Error(`${at}: nothing to connect to. Pass \`transport\` (the library builds the connection) ` + 'or `connection` (a client you connected yourself).'); } return; } // From here on the caller chose the `connection` arm. for (const [option, moved] of BUILT_BY_THE_TRANSPORT) { if (given[option] !== undefined) { throw new Error(`${at}: \`connection\` and \`${option}\` cannot travel together. ` + `A connection you built yourself carries its own transport, and \`${option}\` is ` + `consumed inside the transport this library did not build. ${moved}`); } } assertConnection(given['connection'], at); } exports.refuseConflictingOptions = refuseConflictingOptions; /** * The options that only exist because the library builds the transport, each * paired with the sentence naming where that behaviour moved to. Ordered so the * most surprising loss — throttle retry, which is ON by default — is named * first when a caller passes several. */ const BUILT_BY_THE_TRANSPORT = [ [ 'retryOnThrottle', 'Wrap your own `fetch` with `retryingFetch(yourFetch, options)` (exported from ' + '`agentfootprint/providers`) and hand THAT to your transport — it is the same ' + 'implementation, applied where you build it.', ], [ 'replay', 'The replay declaration is asked by the retry series inside that transport, and a ' + 'connection you built has none of ours: `retryingFetch` keeps the compatibility ' + 'policy (a 429 retried for every tool, the assumption recorded nowhere). Pass ' + '`transport` instead to have tool calls sent again only as you declare.', ], [ 'clientInfo', 'Pass it to the SDK `Client` constructor instead: `new Client(clientInfo, { capabilities: {} })`.', ], [ 'transport', 'Drop one of the two: `transport` asks the library to connect, `connection` says it already is.', ], [ 'sdk', '`sdk` exists so the library can build the transport without its Node loader; ' + 'on this arm you have already built it.', ], [ '_client', '`_client` is the same idea as `connection` and predates it — pass `connection` alone.', ], ]; /** * A connection is only a connection if it can be called. * * This catches the near-miss people actually make: handing over the TRANSPORT * rather than the client. A transport has none of these three methods, and * without this check it fails on the first `tools()`, one stack frame deep * inside the SDK. * * The OTHER near-miss — a `Client` that was constructed but never `connect()`ed * — cannot be caught here, because it has all three methods. It is named in the * message anyway, since it produces the same "my connection does not work" and * the SDK's own "Not connected" is the thing to look for. */ function assertConnection(connection, at) { const members = (connection ?? {}); for (const method of CONNECTION_METHODS) { if (typeof members[method] !== 'function') { throw new Error(`${at}: \`connection\` has no \`${method}()\`. It must be an MCP client that is ` + 'already connected, and the likely mistake is passing the TRANSPORT instead of the ' + 'client. (A `new Client(...)` you never awaited `connect()` on passes this check ' + 'and fails later with the SDK\'s own "Not connected".)'); } } } //# sourceMappingURL=connectionRefusals.js.map