agentfootprint 12.0.1 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CAPABILITIES.md +1 -0
  2. package/dist/esm/mcp/callChecks.d.ts +160 -0
  3. package/dist/esm/mcp/callChecks.js +237 -0
  4. package/dist/esm/mcp/callChecks.js.map +1 -0
  5. package/dist/esm/mcp/index.d.ts +3 -1
  6. package/dist/esm/mcp/index.js +7 -0
  7. package/dist/esm/mcp/index.js.map +1 -1
  8. package/dist/esm/mcp/listingGenerations.d.ts +54 -0
  9. package/dist/esm/mcp/listingGenerations.js +75 -0
  10. package/dist/esm/mcp/listingGenerations.js.map +1 -0
  11. package/dist/esm/mcp/mcpClient.js +108 -23
  12. package/dist/esm/mcp/mcpClient.js.map +1 -1
  13. package/dist/esm/mcp/toolList.d.ts +90 -0
  14. package/dist/esm/mcp/toolList.js +123 -0
  15. package/dist/esm/mcp/toolList.js.map +1 -0
  16. package/dist/esm/mcp/types.d.ts +118 -2
  17. package/dist/esm/tool-providers/index.d.ts +2 -2
  18. package/dist/esm/tool-providers/index.js +8 -0
  19. package/dist/esm/tool-providers/index.js.map +1 -1
  20. package/dist/mcp/callChecks.js +247 -0
  21. package/dist/mcp/callChecks.js.map +1 -0
  22. package/dist/mcp/index.js +10 -1
  23. package/dist/mcp/index.js.map +1 -1
  24. package/dist/mcp/listingGenerations.js +80 -0
  25. package/dist/mcp/listingGenerations.js.map +1 -0
  26. package/dist/mcp/mcpClient.js +108 -23
  27. package/dist/mcp/mcpClient.js.map +1 -1
  28. package/dist/mcp/toolList.js +128 -0
  29. package/dist/mcp/toolList.js.map +1 -0
  30. package/dist/tool-providers/index.js +9 -1
  31. package/dist/tool-providers/index.js.map +1 -1
  32. package/dist/types/mcp/callChecks.d.ts +161 -0
  33. package/dist/types/mcp/callChecks.d.ts.map +1 -0
  34. package/dist/types/mcp/index.d.ts +3 -1
  35. package/dist/types/mcp/index.d.ts.map +1 -1
  36. package/dist/types/mcp/listingGenerations.d.ts +55 -0
  37. package/dist/types/mcp/listingGenerations.d.ts.map +1 -0
  38. package/dist/types/mcp/mcpClient.d.ts.map +1 -1
  39. package/dist/types/mcp/toolList.d.ts +91 -0
  40. package/dist/types/mcp/toolList.d.ts.map +1 -0
  41. package/dist/types/mcp/types.d.ts +118 -2
  42. package/dist/types/mcp/types.d.ts.map +1 -1
  43. package/dist/types/tool-providers/index.d.ts +2 -2
  44. package/dist/types/tool-providers/index.d.ts.map +1 -1
  45. package/package.json +1 -1
package/CAPABILITIES.md CHANGED
@@ -22,6 +22,7 @@ not in this table, search `src/index.ts` for the nearest noun before writing cod
22
22
  |---|---|---|---|
23
23
  | moving files in and out of a code sandbox exactly and bounded, and running shell commands in it — staging text as text and bytes as bytes (AgentCore: one `writeFiles` call, `blob` for bytes, refused over the 100 MB per-call limit, `AF_STAGED_INPUTS` filled as the local runner fills it); reading ONE file back whole or not at all (paired to its path by the returned URI, a missing or different one refused; text or bytes by the field the backend filled, never the extension; over `maxBytes` refused, never cut); a shell command with bounded output, the cut stated, a checkpoint read before the cut, and an exit status only when the backend reported one — off unless the runner opts in; one path rule for both runners; a gone AgentCore session as one typed error; an outcome nobody reported kept unknown (`CodeResult.ok` absent, never success), and a failed program (`exitCode`) told apart from an unconfirmed run (`serviceError`) and a stopped process (`terminatedBy`) in the result and in the `tools.code_run` record | `CodeSession.readFile` + `CodeSession.runCommand` (runner option `commands: true`) + `canReadCodeFiles` / `canRunCodeCommands` + `CodeSandboxError` + `SessionExpiredError` + `sandboxPath` (`agentfootprint`); `agentCoreCodeRunner` / `localCodeRunner` (`agentfootprint/providers`) | `src/adapters/code/` | 12.0.0 |
24
24
  | tuning how an MCP call waits before asking again — the first wait, how it grows, jitter, a cap on the scheduled wait, a deadline for the whole call — and keeping a 429 retry and AgentCore Runtime's session-busy retry from multiplying requests: each call is ONE footprintjs `attempt()` series, the 429 rule JOINS a transport's own series instead of wrapping it (one attempt count, one waiting total, one deadline), a `Retry-After` is waited whole (never cut by the cap), and `onRetry` reports the refusal's `kind` but never arguments or answers | `retryOnThrottle` / `agentCoreRuntimeMcpTransport({ retry })` / `retryingFetch(fetch, options)`: `baseDelayMs`, `multiplier`, `jitter` (none, equal or full), `maxDelayMs`, `maxElapsedMs` beside `maxAttempts`, `maxWaitMs`, `onRetry` (`agentfootprint/providers`); AgentCore default 500 ms doubling, 5 attempts = 3.75–7.5 s of waiting | `src/mcp/throttleRetry.ts` | 11.2.0 |
25
+ | reading an MCP server's whole tool list when it pages (`nextCursor`) — every page, at most 100 `tools/list` requests; a list that does not end (`'page-limit'`) or loops (`'cursor-repeated'`) is refused with what was listed on the error, never returned cut short; the signal checked before each request and after each answer; and every listed tool keeps the SDK `Client`'s call checks whatever `mcpClient` lists later (required task refused before sending, `structuredContent` required by and matched against `outputSchema`, the same error class or shape; left to the SDK only for a one-page list still its latest answer, so a paged list, a failed or successful `refresh()`, a held tool and a call during a listing all stay checked), with `McpOutputUncheckedError` (`sent: false`, or `sent: true` when a list was read while the call was out) only where no JSON Schema validator is reachable — ONLY while discovery runs through `tools()`/`refresh()` (any `mcpClient` over the client): a `listTools` call of your own, the SDK client's automatic refresh (`listChanged.tools`, `autoRefresh` on by default) or any other unseen cache replacement can bypass both the output and the required-task checks, validator or not, so on a `connection` set `autoRefresh: false` and route `onChanged` to `refresh()`, or give `mcpClient` its own connection | `mcpClient(...).tools()` / `.refresh()` + `McpToolListIncompleteError`, `jsonSchemaValidator` (`McpJsonSchemaValidator`) and `McpOutputUncheckedError` (`agentfootprint/providers`) | `src/mcp/` | 12.1.0 |
25
26
  | deciding which MCP tool calls are sent again after a 429 or a lost connection — permission declared by the APP per (server, tool) at registration, never by the model, a tool description or server annotations (`readOnlyHint`); a generic 429 is not proof nothing ran, so `'app-authorized'` (opt-in) sends a call again only on the kinds its entry names (`throttle`, `network`) and otherwise returns the refusal (`not-permitted`); today's default stays as the NAMED `'compatibility'` policy, every retry recording its assumption (`permission: { by: 'compatibility', assumes: 'refused-before-execution' }`); the adapter you chose retries its own refusal (AgentCore session-busy) as its POLICY, recording `permission: { by: 'adapter', assumes: 'refused-before-execution' }` — never proof: a busy-shaped reply can come from a server that ran the call, so every attempt sent that did not complete is `unknown` and `none` is only a request never sent; `retryOnThrottle: false` turns off the 429 rule only — a declared `network` repeat keeps its series, budget and record; the side-effect guard — a tool declared `changes: true` is never sent again unless the server is declared to deduplicate by a key the call carries (`changes-without-key`); each attempt records failure, effect (`none`/`unknown`/`applied`, footprintjs's law) and permission apart, plus `durationMs`; the end counts the requests sent and keeps uncertainty (a later refusal or success never resolves an earlier `unknown`; `applied` = the call completed, never proof of a write); under `'app-authorized'` a received refusal or error (429, 503, JSON-RPC or tool error — or a chain of them) keeps the server's error, effect `unknown`, while a call that genuinely lacked an answer (an attempt with no response, or a request accepted over HTTP — 202, a stream — whose JSON-RPC answer never came: the ledger watches it by id) and then fails, fails as "may have run" (footprintjs `UnknownEffectError`, its final error the cause, `mayHaveRun: true`); `'compatibility'` keeps today's errors; `'app-authorized'` never silently weakens: it needs each request matched to its call (a library-private `_meta` marker, stripped before sending, so a copying SDK still matches) and each answer seen (checked when the `initialize` answer passes the observer), or the call is refused BEFORE it is sent with `McpReplayUnavailableError` (`sent: false`, `reason`: `answers-unobserved` for an SDK whose `connect` replaces `onmessage`, as 1.10's does; `request-unmatched` for one that drops `_meta`; `requests-unwatched` for stdio); an answer's watch ends by request id (answer, SDK cancellation, call settled, client closed), never with the HTTP send alone; a repeat re-sends the same arguments, server, session and identity | `mcpClient({ replay: { policy, tools: { [name]: { on, changes } }, deduplicates: { argument } } })` + `McpReplayOptions` + `McpReplayUnavailableError` (`agentfootprint/providers`); `effect` / `permission` / `durationMs` on `agentfootprint.tools.request_attempts` rows; `attempts` / `effect` / `mayHaveRun` on its `end` | `src/mcp/replay.ts` | 11.5.0 |
26
27
  | saving what an MCP call's retries did into the run that made the call — every attempt, why the series went on or stopped after it, each wait and any stated `Retry-After`, and how the request ended, a cancellation DURING A WAIT included — so a saved and re-read recording gives it back; concurrent calls and runs on one connection never mix; past 100 attempts the rest are counted (`omitted`), never dropped; a 503 received is `answered` with its status, never success and never retried; allow-listed metadata only (a thrown error is a fixed sentence and its category; the endpoint is the declared `endpointLabel`, else its origin; a filing is read as plain data). Every request sent is a row or counted; one cut in flight is `cut`, `remote: 'unknown'` (never recorded as not run), and only a request never sent has no row. Filed ONCE per request by the call's own footprintjs `attempt()` series (no second loop) through an internal per-dispatch port (`REQUEST_EVIDENCE`, not public API) and a per-connection ledger: the call's private `_meta` marker (taken out before sending) → JSON-RPC id → series. An MCP tool call honours its `ctx.signal` (the SDK request is cancelled, `notifications/cancelled` sent), its series stops with it, a wait included, filed the moment it lands, and a call that settles first cuts its series inside the call — never filed after its run, nothing re-sent | `agentfootprint.tools.request_attempts` / `SeriesEvidence` + `RequestLedger` | `src/mcp/` | 11.3.0 |
27
28
  | refusing invalid memory list and search request sizes uniformly before backend reads or empty-result shortcuts, while keeping optional defaults, adapter upper caps and list continuation intact | `ListOptions` + `SearchOptions` | `src/memory/store/types.ts` | 10.8.1 |
@@ -0,0 +1,160 @@
1
+ /**
2
+ * callChecks — a listing never makes a callable tool lose its checks.
3
+ *
4
+ * Pattern: the SDK's three per-tool call checks, carried by every tool the
5
+ * library hands out, plus one typed refusal.
6
+ * Role: The MCP SDK's `Client` runs three checks inside `callTool`, and it
7
+ * keeps them only for the tools of its most recent `tools/list`
8
+ * answer (1.32: `cacheToolMetadata` replaces them on every answer):
9
+ *
10
+ * 1. a tool with `execution.taskSupport: 'required'` is refused before a
11
+ * plain `tools/call` is sent;
12
+ * 2. a tool with an `outputSchema` must answer with `structuredContent`,
13
+ * unless the answer is an error;
14
+ * 3. that `structuredContent` must match the schema.
15
+ *
16
+ * So a tool that declares any of them carries them itself, as the listing it
17
+ * came from declared them. The library leaves them to the SDK only for a tool
18
+ * of a ONE-page list whose answer is still the latest, with no list request
19
+ * out (`listingGenerations.ts`), both when the call starts and when its answer
20
+ * comes in: the one-page normal case, which runs exactly as it always did.
21
+ * Every other call runs the tool's own checks, at the SDK's moments — the first
22
+ * before sending, the other two once the answer is in, before anything reads it
23
+ * — and raises the SDK's error: its own `McpError` where this arm loaded the
24
+ * SDK, otherwise an error with the same name, code and message. Those calls
25
+ * are to a tool of a paged list, a tool whose list a later answer replaced (a
26
+ * refresh that failed or succeeded; a reference held across it), and any call
27
+ * made while a list is being read.
28
+ *
29
+ * All of this covers the lists the library reads (`tools()`, `refresh()`); a
30
+ * listing it does not send — the SDK client's own automatic refresh, a
31
+ * `listTools` call of the caller's — is not seen (`listingGenerations.ts`).
32
+ *
33
+ * The output checks need a JSON Schema validator equal to the client's: the
34
+ * SDK's default (`AjvJsonSchemaValidator`, loaded with the SDK on the Node
35
+ * loader arm), or the caller's `jsonSchemaValidator`. Where neither exists — an
36
+ * `sdk` or `connection` arm given none — an answer under these rules is never
37
+ * returned unchecked (`McpOutputUncheckedError`):
38
+ *
39
+ * - a call under the tool's own checks from the start, to a tool that
40
+ * declares an `outputSchema`, is refused before it is sent (`sent: false`);
41
+ * - a call that started under the SDK's checks is refused once its answer is
42
+ * in, unread, when the library sent a `tools/list` request while the call
43
+ * was out (`sent: true`): the SDK may have checked it against another list.
44
+ *
45
+ * A schema is compiled when a tool's own checks first need it, never before:
46
+ * the one-page normal case compiles nothing twice.
47
+ */
48
+ import type { McpCallToolResult, McpJsonSchemaValidator, McpListedTool } from './types.js';
49
+ import type { ListingGenerations } from './listingGenerations.js';
50
+ /** The SDK's `McpError` constructor, as `@modelcontextprotocol/sdk/types.js` exports it. */
51
+ export type McpErrorClass = new (code: number, message: string, data?: unknown) => Error;
52
+ /** A compiled output check, as the SDK's validator returns one. */
53
+ type Validate = (input: unknown) => {
54
+ readonly valid: boolean;
55
+ readonly errorMessage?: string;
56
+ };
57
+ /**
58
+ * What one connection checks its tools' calls with: a validator equal to its
59
+ * client's, the SDK's own error class where the library loaded the SDK, and
60
+ * which `tools/list` answer the SDK holds its checks for.
61
+ */
62
+ export interface CallCheckKit {
63
+ /** The client's name, as its refusals name it. */
64
+ readonly server: string;
65
+ /** The validator; created on first use. `undefined` when none is reachable. */
66
+ validator(): McpJsonSchemaValidator | undefined;
67
+ /** The SDK's own `McpError`, when this arm loaded the SDK. */
68
+ readonly McpError?: McpErrorClass;
69
+ /** This connection's list requests, counted (`listingGenerations.ts` · `listingsOf`). */
70
+ readonly listings: ListingGenerations;
71
+ }
72
+ /**
73
+ * The kit for one connection, named `server`, whose list requests `listings`
74
+ * counts (`listingsOf(connection)`, shared by every client over it). `given` is
75
+ * the caller's `jsonSchemaValidator`; `loaded` holds what the library loaded
76
+ * with the SDK on the Node loader arm — the SDK's default validator class and
77
+ * its `McpError` — each `undefined` where it is not a constructor (another arm,
78
+ * or an SDK without it).
79
+ */
80
+ export declare function callCheckKit(server: string, listings: ListingGenerations, given: McpJsonSchemaValidator | undefined, loaded?: {
81
+ readonly Validator?: unknown;
82
+ readonly McpError?: unknown;
83
+ }): CallCheckKit;
84
+ /**
85
+ * `value` as a validator, or `undefined` when it was not given. Refused by name
86
+ * when it is something else, before anything connects.
87
+ */
88
+ export declare function validatorOption(value: unknown, at: string): McpJsonSchemaValidator | undefined;
89
+ /** The checks one listed tool carries, as the listing it came from declared them. */
90
+ export interface CallChecks {
91
+ readonly server: string;
92
+ readonly tool: string;
93
+ /** `execution.taskSupport: 'required'`: only ever run as a task. */
94
+ readonly taskRequired: boolean;
95
+ /** The tool declares an `outputSchema`. */
96
+ readonly outputDeclared: boolean;
97
+ /**
98
+ * True while the SDK's `Client` holds this tool's checks: the tool came from
99
+ * a one-page list whose answer is still the latest, and no list request is
100
+ * out. Then the SDK checks the call, and nothing here runs.
101
+ */
102
+ heldBySdk(): boolean;
103
+ /**
104
+ * The compiled output check, compiled from the declared schema on first use.
105
+ * `undefined` when no output is declared or no validator is reachable.
106
+ */
107
+ validate(): Validate | undefined;
108
+ /** Raise an SDK error, as this arm can: the SDK's class, or its exact shape. */
109
+ fail(code: number, message: string): Error;
110
+ }
111
+ /**
112
+ * The checks `listed` carries — `undefined` when it declares none. `generation`
113
+ * is the one answer its list was read in, when it was read in one
114
+ * (`toolList.ts` · `ToolListing`); a paged list's tools have none, so the SDK
115
+ * never holds all of them.
116
+ */
117
+ export declare function checksFor(listed: McpListedTool, kit: CallCheckKit, generation: number | undefined): CallChecks | undefined;
118
+ /**
119
+ * True when this call's output cannot be checked here: it runs under the
120
+ * tool's own checks, the tool declares an `outputSchema`, no validator is
121
+ * reachable, and it is not refused as a required task anyway (that refusal is
122
+ * the SDK's, and comes first). The call is then refused before it is sent.
123
+ */
124
+ export declare function outputUnchecked(checks: CallChecks): boolean;
125
+ /** The SDK's task refusal, word for word, before anything is sent. */
126
+ export declare function refuseRequiredTask(checks: CallChecks): void;
127
+ /**
128
+ * The SDK's two output checks, in its order and with its sentences, once the
129
+ * answer is in and before anything reads it. An answer no validator here can
130
+ * check is never returned: it is refused, unread, as sent.
131
+ */
132
+ export declare function checkOutput(checks: CallChecks, result: McpCallToolResult): void;
133
+ /**
134
+ * A tool call whose output this connection cannot check, refused. The tool
135
+ * declares an `outputSchema`, the MCP SDK no longer holds the checks of the
136
+ * tool list it came from, and this connection has no JSON Schema validator to
137
+ * check it with — so its answer is never returned:
138
+ *
139
+ * - `sent: false` — refused before it was sent: nothing reached the server.
140
+ * The tool came from a paged list, from a list a later answer replaced, or
141
+ * the call was made while a list was being read.
142
+ * - `sent: true` — the call was sent, and a `tools/list` request went out
143
+ * before its answer came in: the SDK may have checked that answer against
144
+ * the other list, so it is refused unread. The tool may have run.
145
+ *
146
+ * Pass `jsonSchemaValidator` — the validator your client uses — to `mcpClient`.
147
+ * Names the server and the tool, never an argument or an answer.
148
+ */
149
+ export declare class McpOutputUncheckedError extends Error {
150
+ readonly server: string;
151
+ readonly tool: string;
152
+ /** Whether the request left the client: `false` when the refusal came before it. */
153
+ readonly sent: boolean;
154
+ readonly code: "ERR_MCP_OUTPUT_UNCHECKED";
155
+ readonly reason: "output-unchecked";
156
+ constructor(server: string, tool: string,
157
+ /** Whether the request left the client: `false` when the refusal came before it. */
158
+ sent: boolean);
159
+ }
160
+ export {};
@@ -0,0 +1,237 @@
1
+ /**
2
+ * callChecks — a listing never makes a callable tool lose its checks.
3
+ *
4
+ * Pattern: the SDK's three per-tool call checks, carried by every tool the
5
+ * library hands out, plus one typed refusal.
6
+ * Role: The MCP SDK's `Client` runs three checks inside `callTool`, and it
7
+ * keeps them only for the tools of its most recent `tools/list`
8
+ * answer (1.32: `cacheToolMetadata` replaces them on every answer):
9
+ *
10
+ * 1. a tool with `execution.taskSupport: 'required'` is refused before a
11
+ * plain `tools/call` is sent;
12
+ * 2. a tool with an `outputSchema` must answer with `structuredContent`,
13
+ * unless the answer is an error;
14
+ * 3. that `structuredContent` must match the schema.
15
+ *
16
+ * So a tool that declares any of them carries them itself, as the listing it
17
+ * came from declared them. The library leaves them to the SDK only for a tool
18
+ * of a ONE-page list whose answer is still the latest, with no list request
19
+ * out (`listingGenerations.ts`), both when the call starts and when its answer
20
+ * comes in: the one-page normal case, which runs exactly as it always did.
21
+ * Every other call runs the tool's own checks, at the SDK's moments — the first
22
+ * before sending, the other two once the answer is in, before anything reads it
23
+ * — and raises the SDK's error: its own `McpError` where this arm loaded the
24
+ * SDK, otherwise an error with the same name, code and message. Those calls
25
+ * are to a tool of a paged list, a tool whose list a later answer replaced (a
26
+ * refresh that failed or succeeded; a reference held across it), and any call
27
+ * made while a list is being read.
28
+ *
29
+ * All of this covers the lists the library reads (`tools()`, `refresh()`); a
30
+ * listing it does not send — the SDK client's own automatic refresh, a
31
+ * `listTools` call of the caller's — is not seen (`listingGenerations.ts`).
32
+ *
33
+ * The output checks need a JSON Schema validator equal to the client's: the
34
+ * SDK's default (`AjvJsonSchemaValidator`, loaded with the SDK on the Node
35
+ * loader arm), or the caller's `jsonSchemaValidator`. Where neither exists — an
36
+ * `sdk` or `connection` arm given none — an answer under these rules is never
37
+ * returned unchecked (`McpOutputUncheckedError`):
38
+ *
39
+ * - a call under the tool's own checks from the start, to a tool that
40
+ * declares an `outputSchema`, is refused before it is sent (`sent: false`);
41
+ * - a call that started under the SDK's checks is refused once its answer is
42
+ * in, unread, when the library sent a `tools/list` request while the call
43
+ * was out (`sent: true`): the SDK may have checked it against another list.
44
+ *
45
+ * A schema is compiled when a tool's own checks first need it, never before:
46
+ * the one-page normal case compiles nothing twice.
47
+ */
48
+ /** JSON-RPC's codes for the two errors the SDK raises here. */
49
+ const INVALID_REQUEST = -32600;
50
+ const INVALID_PARAMS = -32602;
51
+ /**
52
+ * The kit for one connection, named `server`, whose list requests `listings`
53
+ * counts (`listingsOf(connection)`, shared by every client over it). `given` is
54
+ * the caller's `jsonSchemaValidator`; `loaded` holds what the library loaded
55
+ * with the SDK on the Node loader arm — the SDK's default validator class and
56
+ * its `McpError` — each `undefined` where it is not a constructor (another arm,
57
+ * or an SDK without it).
58
+ */
59
+ export function callCheckKit(server, listings, given, loaded = {}) {
60
+ const Default = typeof loaded.Validator === 'function' ? loaded.Validator : undefined;
61
+ let created;
62
+ return {
63
+ server,
64
+ validator: () => {
65
+ if (given !== undefined)
66
+ return given;
67
+ if (Default === undefined)
68
+ return undefined;
69
+ created ??= new Default();
70
+ return created;
71
+ },
72
+ ...(typeof loaded.McpError === 'function' && { McpError: loaded.McpError }),
73
+ listings,
74
+ };
75
+ }
76
+ /**
77
+ * `value` as a validator, or `undefined` when it was not given. Refused by name
78
+ * when it is something else, before anything connects.
79
+ */
80
+ export function validatorOption(value, at) {
81
+ if (value === undefined)
82
+ return undefined;
83
+ const getValidator = value !== null && (typeof value === 'object' || typeof value === 'function')
84
+ ? value.getValidator
85
+ : undefined;
86
+ if (typeof getValidator === 'function')
87
+ return value;
88
+ throw new TypeError(`${at}: \`jsonSchemaValidator\` has no \`getValidator\` method. Pass a JSON Schema ` +
89
+ "validator as the MCP SDK's `Client` takes one: `new AjvJsonSchemaValidator()` from " +
90
+ "'@modelcontextprotocol/sdk/validation/ajv', or the one your client uses.");
91
+ }
92
+ /**
93
+ * The checks `listed` carries — `undefined` when it declares none. `generation`
94
+ * is the one answer its list was read in, when it was read in one
95
+ * (`toolList.ts` · `ToolListing`); a paged list's tools have none, so the SDK
96
+ * never holds all of them.
97
+ */
98
+ export function checksFor(listed, kit, generation) {
99
+ const schema = ownObject(listed, 'outputSchema');
100
+ const taskRequired = ownValue(ownObject(listed, 'execution'), 'taskSupport') === 'required';
101
+ if (schema === undefined && !taskRequired)
102
+ return undefined;
103
+ let compiled;
104
+ return {
105
+ server: kit.server,
106
+ tool: listed.name,
107
+ taskRequired,
108
+ outputDeclared: schema !== undefined,
109
+ heldBySdk: () => generation !== undefined && kit.listings.isLatest(generation),
110
+ validate: () => {
111
+ if (schema !== undefined)
112
+ compiled ??= kit.validator()?.getValidator(schema);
113
+ return compiled;
114
+ },
115
+ fail: (code, message) => kit.McpError !== undefined
116
+ ? new kit.McpError(code, message)
117
+ : new McpErrorShape(code, message),
118
+ };
119
+ }
120
+ /**
121
+ * True when this call's output cannot be checked here: it runs under the
122
+ * tool's own checks, the tool declares an `outputSchema`, no validator is
123
+ * reachable, and it is not refused as a required task anyway (that refusal is
124
+ * the SDK's, and comes first). The call is then refused before it is sent.
125
+ */
126
+ export function outputUnchecked(checks) {
127
+ return checks.outputDeclared && !checks.taskRequired && checks.validate() === undefined;
128
+ }
129
+ /** The SDK's task refusal, word for word, before anything is sent. */
130
+ export function refuseRequiredTask(checks) {
131
+ if (!checks.taskRequired)
132
+ return;
133
+ throw checks.fail(INVALID_REQUEST, `Tool "${checks.tool}" requires task-based execution. ` +
134
+ 'Use client.experimental.tasks.callToolStream() instead.');
135
+ }
136
+ /**
137
+ * The SDK's two output checks, in its order and with its sentences, once the
138
+ * answer is in and before anything reads it. An answer no validator here can
139
+ * check is never returned: it is refused, unread, as sent.
140
+ */
141
+ export function checkOutput(checks, result) {
142
+ if (!checks.outputDeclared)
143
+ return;
144
+ const validate = checks.validate();
145
+ if (validate === undefined)
146
+ throw new McpOutputUncheckedError(checks.server, checks.tool, true);
147
+ const structured = ownValue(result, 'structuredContent');
148
+ if (!structured && !ownValue(result, 'isError')) {
149
+ throw checks.fail(INVALID_REQUEST, `Tool ${checks.tool} has an output schema but did not return structured content`);
150
+ }
151
+ if (!structured)
152
+ return;
153
+ let mismatch;
154
+ try {
155
+ const verdict = validate(structured);
156
+ if (!verdict.valid) {
157
+ mismatch = `Structured content does not match the tool's output schema: ${verdict.errorMessage}`;
158
+ }
159
+ }
160
+ catch (error) {
161
+ throw checks.fail(INVALID_PARAMS, `Failed to validate structured content: ${error instanceof Error ? error.message : String(error)}`);
162
+ }
163
+ if (mismatch !== undefined)
164
+ throw checks.fail(INVALID_PARAMS, mismatch);
165
+ }
166
+ /**
167
+ * The SDK's `McpError`, for an arm that cannot reach the class itself (`sdk`,
168
+ * `connection`): the same name, `code`, `data` and message, so a caller that
169
+ * branches on those handles it as it handles the SDK's.
170
+ */
171
+ class McpErrorShape extends Error {
172
+ code;
173
+ data;
174
+ constructor(code, message) {
175
+ super(`MCP error ${code}: ${message}`);
176
+ this.code = code;
177
+ this.data = undefined;
178
+ this.name = 'McpError';
179
+ }
180
+ }
181
+ /** What the refusal says happened, refused before sending. */
182
+ const NOT_SENT = 'was not sent. The tool declares an outputSchema, the MCP SDK no longer holds the checks of ' +
183
+ 'the tool list it came from (that list was paged, a later list replaced it, or a list is ' +
184
+ 'being read)';
185
+ /** What the refusal says happened, refused once the answer was in. */
186
+ const SENT_UNREAD = 'was sent, and its answer is not returned. The tool declares an outputSchema, a tool list ' +
187
+ 'was read while the call was out (so the MCP SDK may not have checked the answer against ' +
188
+ "this tool's schema)";
189
+ /**
190
+ * A tool call whose output this connection cannot check, refused. The tool
191
+ * declares an `outputSchema`, the MCP SDK no longer holds the checks of the
192
+ * tool list it came from, and this connection has no JSON Schema validator to
193
+ * check it with — so its answer is never returned:
194
+ *
195
+ * - `sent: false` — refused before it was sent: nothing reached the server.
196
+ * The tool came from a paged list, from a list a later answer replaced, or
197
+ * the call was made while a list was being read.
198
+ * - `sent: true` — the call was sent, and a `tools/list` request went out
199
+ * before its answer came in: the SDK may have checked that answer against
200
+ * the other list, so it is refused unread. The tool may have run.
201
+ *
202
+ * Pass `jsonSchemaValidator` — the validator your client uses — to `mcpClient`.
203
+ * Names the server and the tool, never an argument or an answer.
204
+ */
205
+ export class McpOutputUncheckedError extends Error {
206
+ server;
207
+ tool;
208
+ sent;
209
+ code = 'ERR_MCP_OUTPUT_UNCHECKED';
210
+ reason = 'output-unchecked';
211
+ constructor(server, tool,
212
+ /** Whether the request left the client: `false` when the refusal came before it. */
213
+ sent) {
214
+ super(`mcpClient[${server}]: the call to '${tool}' ${sent ? SENT_UNREAD : NOT_SENT}, and this ` +
215
+ 'connection has no JSON Schema validator to check its output with. Pass ' +
216
+ '`jsonSchemaValidator` to mcpClient: the validator your client uses.');
217
+ this.server = server;
218
+ this.tool = tool;
219
+ this.sent = sent;
220
+ this.name = 'McpOutputUncheckedError';
221
+ }
222
+ }
223
+ /** An own data property, never an accessor's result; `undefined` otherwise. */
224
+ function ownValue(value, key) {
225
+ if (value === null || typeof value !== 'object')
226
+ return undefined;
227
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
228
+ return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;
229
+ }
230
+ /** An own data property that is a plain object (not an array); `undefined` otherwise. */
231
+ function ownObject(value, key) {
232
+ const found = ownValue(value, key);
233
+ return found !== null && typeof found === 'object' && !Array.isArray(found)
234
+ ? found
235
+ : undefined;
236
+ }
237
+ //# sourceMappingURL=callChecks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callChecks.js","sourceRoot":"","sources":["../../../src/mcp/callChecks.ts"],"names":[],"mappings":"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"}
@@ -13,4 +13,6 @@ export { retryingFetch } from './throttleRetry.js';
13
13
  export type { RetryOnThrottle, ThrottleFetch, ThrottleRetryInfo, ThrottleRetryOptions, } from './throttleRetry.js';
14
14
  export type { McpReplayDeduplication, McpReplayKind, McpReplayOptions, McpReplayPolicy, McpReplayTool, } from './replay.js';
15
15
  export { McpReplayUnavailableError, type McpReplayUnavailableReason } from './replayUnavailable.js';
16
- export type { McpCallToolResult, McpClient, McpClientOptions, McpConnection, McpConnectionOptions, McpSdk, McpGatewayTransport, McpHttpTransport, McpStdioTransport, McpTransport, McpSdkClient, McpServeOptions, McpServeHandle, McpServeTransport, McpHttpServeTransport, McpStdioServeTransport, McpSdkServer, } from './types.js';
16
+ export { McpToolListIncompleteError, type McpToolListIncompleteReason } from './toolList.js';
17
+ export { McpOutputUncheckedError } from './callChecks.js';
18
+ export type { McpCallToolResult, McpClient, McpClientOptions, McpConnection, McpConnectionOptions, McpJsonSchemaValidator, McpSdk, McpGatewayTransport, McpHttpTransport, McpStdioTransport, McpTransport, McpSdkClient, McpServeOptions, McpServeHandle, McpServeTransport, McpHttpServeTransport, McpStdioServeTransport, McpSdkServer, } from './types.js';
@@ -18,4 +18,11 @@ export { retryingFetch } from './throttleRetry.js';
18
18
  // A call under 'app-authorized' that a connection cannot honour is refused
19
19
  // before it is sent, with a typed reason.
20
20
  export { McpReplayUnavailableError } from './replayUnavailable.js';
21
+ // A tool list that could not be read to its end (past 100 requests, or a cursor
22
+ // sent twice) is refused with this error and a typed reason, never cut short.
23
+ export { McpToolListIncompleteError } from './toolList.js';
24
+ // A tool whose list the SDK no longer holds, and whose output this connection
25
+ // cannot check — no JSON Schema validator reachable — is refused, never answered
26
+ // unchecked.
27
+ export { McpOutputUncheckedError } from './callChecks.js';
21
28
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,8EAA8E;AAC9E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAsB,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAC;AAChG,OAAO,EACL,gBAAgB,EAChB,iCAAiC,GAElC,MAAM,uBAAuB,CAAC;AAC/B,6EAA6E;AAC7E,4EAA4E;AAC5E,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgBnD,2EAA2E;AAC3E,0CAA0C;AAC1C,OAAO,EAAE,yBAAyB,EAAmC,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,8EAA8E;AAC9E,0EAA0E;AAC1E,OAAO,EAAE,mBAAmB,EAAsB,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAA+C,MAAM,oBAAoB,CAAC;AAChG,OAAO,EACL,gBAAgB,EAChB,iCAAiC,GAElC,MAAM,uBAAuB,CAAC;AAC/B,6EAA6E;AAC7E,4EAA4E;AAC5E,sEAAsE;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAgBnD,2EAA2E;AAC3E,0CAA0C;AAC1C,OAAO,EAAE,yBAAyB,EAAmC,MAAM,wBAAwB,CAAC;AACpG,gFAAgF;AAChF,8EAA8E;AAC9E,OAAO,EAAE,0BAA0B,EAAoC,MAAM,eAAe,CAAC;AAC7F,8EAA8E;AAC9E,iFAAiF;AACjF,aAAa;AACb,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * listingGenerations — which `tools/list` answer the MCP SDK's `Client` holds
3
+ * its per-tool call checks for, as far as one connection can tell.
4
+ *
5
+ * Pattern: a count of the list requests one connection sent and settled.
6
+ * Role: The SDK's `Client` keeps its per-tool call checks (`callChecks.ts`)
7
+ * for the tools of the last `tools/list` answer it received: every
8
+ * answer replaces them. A request that FAILED can replace them too: the
9
+ * SDK caches an answer before it returns it, and caching can throw after
10
+ * the old checks are cleared (1.32: an `outputSchema` its validator
11
+ * cannot compile). The library cannot see inside the SDK, but it sends
12
+ * every list request it makes. So it counts them:
13
+ *
14
+ * - a request is OUT from the moment it is sent until it settles, because the
15
+ * SDK replaces its checks before the library sees the answer;
16
+ * - each request that settles, answered or failed, starts a new GENERATION.
17
+ *
18
+ * The SDK holds the checks of a listing's one answer only while the generation
19
+ * that answer settled at is the latest and no request is out. The SDK keeps one
20
+ * set of checks per client, so there is one count per connection object: every
21
+ * `mcpClient` over the same client shares it (`listingsOf`). A `tools/list`
22
+ * request the library did not send — your own code calling `listTools` on a
23
+ * client passed as `connection`, or that client's own automatic refresh
24
+ * (`listChanged.tools` with `autoRefresh`) — is not counted: the library cannot
25
+ * see it, and a tool it handed out goes on leaving its checks to an SDK that no
26
+ * longer holds them. That limit is documented, and pinned by
27
+ * `test/mcp/listingKeepsChecks.limits.test.ts`.
28
+ */
29
+ /** One settled `tools/list` request's answer, and the generation it settled at. */
30
+ export interface SettledListing<T> {
31
+ readonly answer: T;
32
+ readonly generation: number;
33
+ }
34
+ /** The `tools/list` requests one connection has out, and the generations they settled at. */
35
+ export declare class ListingGenerations {
36
+ private settled;
37
+ private out;
38
+ /**
39
+ * Send one `tools/list` request with `send`, counted as out from the moment it
40
+ * is sent until it settles. Settling starts a new generation, answered or not.
41
+ *
42
+ * @throws whatever `send` rejects with, as it rejected
43
+ */
44
+ ask<T>(send: () => Promise<T>): Promise<SettledListing<T>>;
45
+ /** True while `generation` is the latest settled one and no request is out. */
46
+ isLatest(generation: number): boolean;
47
+ private settle;
48
+ }
49
+ /**
50
+ * The count for `connection`: the same one for every `mcpClient` that adapts
51
+ * the same client, because the SDK keeps one set of checks per client and a
52
+ * listing by any of them replaces it.
53
+ */
54
+ export declare function listingsOf(connection: object): ListingGenerations;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * listingGenerations — which `tools/list` answer the MCP SDK's `Client` holds
3
+ * its per-tool call checks for, as far as one connection can tell.
4
+ *
5
+ * Pattern: a count of the list requests one connection sent and settled.
6
+ * Role: The SDK's `Client` keeps its per-tool call checks (`callChecks.ts`)
7
+ * for the tools of the last `tools/list` answer it received: every
8
+ * answer replaces them. A request that FAILED can replace them too: the
9
+ * SDK caches an answer before it returns it, and caching can throw after
10
+ * the old checks are cleared (1.32: an `outputSchema` its validator
11
+ * cannot compile). The library cannot see inside the SDK, but it sends
12
+ * every list request it makes. So it counts them:
13
+ *
14
+ * - a request is OUT from the moment it is sent until it settles, because the
15
+ * SDK replaces its checks before the library sees the answer;
16
+ * - each request that settles, answered or failed, starts a new GENERATION.
17
+ *
18
+ * The SDK holds the checks of a listing's one answer only while the generation
19
+ * that answer settled at is the latest and no request is out. The SDK keeps one
20
+ * set of checks per client, so there is one count per connection object: every
21
+ * `mcpClient` over the same client shares it (`listingsOf`). A `tools/list`
22
+ * request the library did not send — your own code calling `listTools` on a
23
+ * client passed as `connection`, or that client's own automatic refresh
24
+ * (`listChanged.tools` with `autoRefresh`) — is not counted: the library cannot
25
+ * see it, and a tool it handed out goes on leaving its checks to an SDK that no
26
+ * longer holds them. That limit is documented, and pinned by
27
+ * `test/mcp/listingKeepsChecks.limits.test.ts`.
28
+ */
29
+ /** The `tools/list` requests one connection has out, and the generations they settled at. */
30
+ export class ListingGenerations {
31
+ settled = 0;
32
+ out = 0;
33
+ /**
34
+ * Send one `tools/list` request with `send`, counted as out from the moment it
35
+ * is sent until it settles. Settling starts a new generation, answered or not.
36
+ *
37
+ * @throws whatever `send` rejects with, as it rejected
38
+ */
39
+ async ask(send) {
40
+ this.out += 1;
41
+ try {
42
+ const answer = await send();
43
+ return { answer, generation: this.settle() };
44
+ }
45
+ catch (error) {
46
+ this.settle();
47
+ throw error;
48
+ }
49
+ }
50
+ /** True while `generation` is the latest settled one and no request is out. */
51
+ isLatest(generation) {
52
+ return this.out === 0 && this.settled === generation;
53
+ }
54
+ settle() {
55
+ this.out -= 1;
56
+ this.settled += 1;
57
+ return this.settled;
58
+ }
59
+ }
60
+ /** One count per connection object, held only as long as the connection is. */
61
+ const BY_CONNECTION = new WeakMap();
62
+ /**
63
+ * The count for `connection`: the same one for every `mcpClient` that adapts
64
+ * the same client, because the SDK keeps one set of checks per client and a
65
+ * listing by any of them replaces it.
66
+ */
67
+ export function listingsOf(connection) {
68
+ let listings = BY_CONNECTION.get(connection);
69
+ if (listings === undefined) {
70
+ listings = new ListingGenerations();
71
+ BY_CONNECTION.set(connection, listings);
72
+ }
73
+ return listings;
74
+ }
75
+ //# sourceMappingURL=listingGenerations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listingGenerations.js","sourceRoot":"","sources":["../../../src/mcp/listingGenerations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAQH,6FAA6F;AAC7F,MAAM,OAAO,kBAAkB;IACrB,OAAO,GAAG,CAAC,CAAC;IACZ,GAAG,GAAG,CAAC,CAAC;IAEhB;;;;;OAKG;IACH,KAAK,CAAC,GAAG,CAAI,IAAsB;QACjC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACd,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;YAC5B,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,QAAQ,CAAC,UAAkB;QACzB,OAAO,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU,CAAC;IACvD,CAAC;IAEO,MAAM;QACZ,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACd,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;CACF;AAED,+EAA+E;AAC/E,MAAM,aAAa,GAAG,IAAI,OAAO,EAA8B,CAAC;AAEhE;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,QAAQ,GAAG,IAAI,kBAAkB,EAAE,CAAC;QACpC,aAAa,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}