@mcp-index/sdk 0.18.1 → 0.19.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.
- package/CHANGELOG.md +10 -0
- package/README.md +178 -0
- package/dist/src/ai.d.ts +96 -0
- package/dist/src/ai.d.ts.map +1 -0
- package/dist/src/ai.js +151 -0
- package/dist/src/ai.js.map +1 -0
- package/dist/src/lock/check.d.ts +143 -0
- package/dist/src/lock/check.d.ts.map +1 -0
- package/dist/src/lock/check.js +274 -0
- package/dist/src/lock/check.js.map +1 -0
- package/dist/src/lock/digest.d.ts +48 -0
- package/dist/src/lock/digest.d.ts.map +1 -0
- package/dist/src/lock/digest.js +234 -0
- package/dist/src/lock/digest.js.map +1 -0
- package/dist/src/lock/events.d.ts +82 -0
- package/dist/src/lock/events.d.ts.map +1 -0
- package/dist/src/lock/events.js +80 -0
- package/dist/src/lock/events.js.map +1 -0
- package/dist/src/lock/index.d.ts +20 -0
- package/dist/src/lock/index.d.ts.map +1 -0
- package/dist/src/lock/index.js +14 -0
- package/dist/src/lock/index.js.map +1 -0
- package/dist/src/lock/lock.d.ts +195 -0
- package/dist/src/lock/lock.d.ts.map +1 -0
- package/dist/src/lock/lock.js +1075 -0
- package/dist/src/lock/lock.js.map +1 -0
- package/dist/src/lock/pg.d.ts +150 -0
- package/dist/src/lock/pg.d.ts.map +1 -0
- package/dist/src/lock/pg.js +539 -0
- package/dist/src/lock/pg.js.map +1 -0
- package/dist/src/lock/rawList.d.ts +127 -0
- package/dist/src/lock/rawList.d.ts.map +1 -0
- package/dist/src/lock/rawList.js +561 -0
- package/dist/src/lock/rawList.js.map +1 -0
- package/dist/src/lock/signing.d.ts +35 -0
- package/dist/src/lock/signing.d.ts.map +1 -0
- package/dist/src/lock/signing.js +85 -0
- package/dist/src/lock/signing.js.map +1 -0
- package/dist/src/lock/store.d.ts +149 -0
- package/dist/src/lock/store.d.ts.map +1 -0
- package/dist/src/lock/store.js +459 -0
- package/dist/src/lock/store.js.map +1 -0
- package/package.json +31 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ additive feature, patch = fix). Backward-compatible additions only in 0.2.0.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
Version 0.19.0 in package.json. Not published yet.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Lock mode, in three new subpaths. It follows mcp.lock draft 1, revision 1d, which is not stable: files carry `lockVersion: "1-draft"` and a later reader will refuse them. It is opt-in, and the root `@mcp-index/sdk` import does not load any of it.
|
|
13
|
+
- `@mcp-index/sdk/lock`: reads and writes a lock in `.mcp/lock/` and `.mcp/contracts/`, checks each `tools/list` against it, and refuses calls to tools that are not approved with error -32011. `lockClient` and `LockedTransport` put it in front of the official MCP client. `FileLockStore` approves and revokes, verifies a signed manifest when a signer is configured, and refuses a `sequencePath` or `trustedRootPath` inside the lock's repository. Listing and refusal events go to a sink you pass.
|
|
14
|
+
- `@mcp-index/sdk/lock/pg`: `PgLockStore`, a Postgres store with append-only hash-chained records, row-level security, `app-scoped` and `role-bound` tenancy, two roles, and an optional signed head that needs a sequence store.
|
|
15
|
+
- `@mcp-index/sdk/ai`: `lockTransport` for `createMCPClient` from the Vercel AI SDK.
|
|
16
|
+
- Optional peer dependencies: `@ai-sdk/mcp` for `/ai`, `pg` for `/lock/pg`, `sigstore` for signed locks. The package still has no runtime dependencies.
|
|
17
|
+
|
|
8
18
|
## 0.18.1 - 2026-10-13
|
|
9
19
|
|
|
10
20
|
### Changed
|
package/README.md
CHANGED
|
@@ -174,6 +174,184 @@ When enabled, each tool you pin and each contract drift sends **one one-way sign
|
|
|
174
174
|
opt-in richer tier (it behaves identically today). The full notice is exported as
|
|
175
175
|
`DRIFT_TELEMETRY_NOTICE`. Unset the variable any time to stop.
|
|
176
176
|
|
|
177
|
+
## Lock mode (`@mcp-index/sdk/lock`)
|
|
178
|
+
|
|
179
|
+
Lock mode checks every `tools/list` against an mcp.lock kept in your repository
|
|
180
|
+
(`.mcp/lock/` and `.mcp/contracts/`, format: mcp.lock draft 1, revision 1e). The model sees
|
|
181
|
+
only tools whose definition digest matches an approval, and a call to anything else is
|
|
182
|
+
refused with JSON-RPC error -32011 before it reaches the server. Lock mode is opt-in and
|
|
183
|
+
draft 1 is not stable. Nothing here runs unless you construct it, and the root
|
|
184
|
+
`@mcp-index/sdk` import does not load it.
|
|
185
|
+
|
|
186
|
+
Connect the official MCP client through `lockClient`:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
190
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
191
|
+
import { FileLockStore, lockClient } from "@mcp-index/sdk/lock";
|
|
192
|
+
import type { Identity } from "@mcp-index/sdk/lock";
|
|
193
|
+
|
|
194
|
+
const identity: Identity = { kind: "stdio", package: "npm:@example/docs-server" };
|
|
195
|
+
const store = new FileLockStore({ root: process.cwd() }); // the directory that holds .mcp/
|
|
196
|
+
const client = new Client({ name: "my-host", version: "1.0.0" });
|
|
197
|
+
const transport = new StdioClientTransport({ command: "npx", args: ["-y", "@example/docs-server"] });
|
|
198
|
+
const locked = await lockClient(client, transport, { store, identity, posture: "guard" });
|
|
199
|
+
|
|
200
|
+
const { tools } = await client.listTools(); // approved tools only
|
|
201
|
+
console.log(tools.map((t) => t.name), locked.decision?.withheld);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`posture` is `monitor` (show everything, report what does not match), `guard` (withhold it)
|
|
205
|
+
or `strict` (same as guard in draft 1). Lock mode never approves a tool on first sight and
|
|
206
|
+
never re-pins. The lock changes only through `store.approve` and `store.revoke`, or a
|
|
207
|
+
reviewed edit to the lock files:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { readFileSync } from "node:fs";
|
|
211
|
+
|
|
212
|
+
// The Tool object exactly as the server sent it, saved for review. Not an SDK-parsed copy:
|
|
213
|
+
// the SDK drops members it does not know, and the digest covers every member.
|
|
214
|
+
const tool: unknown = JSON.parse(readFileSync("review/search.json", "utf8"));
|
|
215
|
+
await store.approve({ identity, tool, subject: "github:you", via: "api", ref: "pr-42" });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The identity names the server, in one of three forms:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const remote: Identity = { kind: "remote", url: "https://mcp.example.com/v1/mcp" };
|
|
222
|
+
const pkg: Identity = { kind: "stdio", package: "npm:@example/docs-server" }; // npm, pypi or oci
|
|
223
|
+
const cmd: Identity = { kind: "stdio", command: "uvx", args: ["git+https://github.com/example/server"] };
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`remoteIdentity`, `packageIdentity` and `commandIdentity` build them in normalized form.
|
|
227
|
+
`commandIdentity` throws when an argument looks like a credential.
|
|
228
|
+
|
|
229
|
+
A signed lock verifies `.mcp/lock/MANIFEST.json.sigstore` against a signer from your host
|
|
230
|
+
configuration. It needs the optional `sigstore` peer, a trust root file, and a sequence file
|
|
231
|
+
where the host keeps the highest manifest sequence it accepted, so an older signed manifest
|
|
232
|
+
is refused after a newer one:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
const signed = new FileLockStore({
|
|
236
|
+
root: "/srv/app",
|
|
237
|
+
signer: {
|
|
238
|
+
issuer: "https://token.actions.githubusercontent.com",
|
|
239
|
+
san: "https://github.com/acme/app/.github/workflows/lock.yml@refs/heads/main",
|
|
240
|
+
},
|
|
241
|
+
trustedRootPath: "/etc/mcplock/trusted_root.json",
|
|
242
|
+
sequencePath: "/var/lib/mcplock/sequence",
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Both paths must lie outside the repository that holds the lock, or the constructor throws
|
|
247
|
+
`HostConfigError`. The repository is the nearest directory at or above `root` with a `.git`
|
|
248
|
+
directory or a linked worktree's `.git` file; a submodule's `.git` file sends the search on to
|
|
249
|
+
the superproject. The check compares directories by device and inode, so a case-folded
|
|
250
|
+
spelling on macOS and a symlink followed by `..` are judged where they land, and a `..` in the
|
|
251
|
+
part of a path that does not exist yet is refused. When no `.git` entry exists at or above
|
|
252
|
+
`root`, only `root` itself is protected: a path beside it in the same untracked directory is
|
|
253
|
+
accepted, so keep host files somewhere the lock's writers cannot reach. A signer without a sequence store makes every load
|
|
254
|
+
`lock-signature-invalid`.
|
|
255
|
+
|
|
256
|
+
## Postgres lock store (`@mcp-index/sdk/lock/pg`)
|
|
257
|
+
|
|
258
|
+
`PgLockStore` keeps approvals in Postgres instead of files: append-only, hash-chained
|
|
259
|
+
records per tenant, with row-level security on both tables. `pg` is an optional peer.
|
|
260
|
+
|
|
261
|
+
The schema owner runs the migrations once. They are exported as SQL text:
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { writeFileSync } from "node:fs";
|
|
265
|
+
import { MIGRATION_SQL, ROLE_BOUND_SQL, ROLE_SQL } from "@mcp-index/sdk/lock/pg";
|
|
266
|
+
|
|
267
|
+
writeFileSync("mcplock-schema.sql", MIGRATION_SQL + ROLE_SQL);
|
|
268
|
+
writeFileSync("mcplock-role-bound.sql", ROLE_BOUND_SQL); // only for role-bound tenancy
|
|
269
|
+
// psql -v ON_ERROR_STOP=1 -f mcplock-schema.sql
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`ROLE_SQL` creates two roles without login. `mcplock_runtime` may only read, and is what hosts
|
|
273
|
+
in lock mode connect as. `mcplock_approver` may also append records and move the head, and
|
|
274
|
+
belongs to the approval path only. Neither may update or delete a record. Grant each to its
|
|
275
|
+
own login, and never connect as a superuser, since a superuser bypasses row-level security:
|
|
276
|
+
|
|
277
|
+
```sql
|
|
278
|
+
GRANT mcplock_runtime TO host_login;
|
|
279
|
+
GRANT mcplock_approver TO approver_login;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Two tenancy modes:
|
|
283
|
+
|
|
284
|
+
- `app-scoped` (the default, `MIGRATION_SQL` alone). The store sets the tenant from the
|
|
285
|
+
principal at the start of each transaction. This stops a query bug from reaching another
|
|
286
|
+
tenant. It does not stop someone holding a shared login, who can set any tenant.
|
|
287
|
+
- `role-bound` (`ROLE_BOUND_SQL` after the other two). Each tenant gets its own logins, and
|
|
288
|
+
the administrator maps each login to its tenant in `mcplock.tenant_logins`. The policies
|
|
289
|
+
read the tenant from the login, so a login reaches only its own tenant. Use this before
|
|
290
|
+
sharing one database between tenants that do not trust each other.
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
import { PgLockStore, createPgPool } from "@mcp-index/sdk/lock/pg";
|
|
294
|
+
|
|
295
|
+
const pool = await createPgPool(process.env.MCPLOCK_PG_URL ?? "postgres://host_login@127.0.0.1/app");
|
|
296
|
+
const store = new PgLockStore({
|
|
297
|
+
pool,
|
|
298
|
+
principal: { tenantId: "acme", subject: "svc:docs-host" }, // from your own auth, never a header
|
|
299
|
+
tenancy: "app-scoped",
|
|
300
|
+
});
|
|
301
|
+
const snapshot = await store.loadSnapshot();
|
|
302
|
+
console.log(snapshot.snapshotId);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
With `headVerifier` set, each load checks a signature over the head row, and the store must
|
|
306
|
+
also get a `sequenceStore`, for example `fileSequenceStore("/var/lib/mcplock/pg-sequence")`
|
|
307
|
+
from `@mcp-index/sdk/lock`, or every load is `lock-signature-invalid`. Without a head
|
|
308
|
+
signature, whoever can write the tables can rewrite the chain. Errors never carry the
|
|
309
|
+
connection string unredacted.
|
|
310
|
+
|
|
311
|
+
## Lock mode for the Vercel AI SDK (`@mcp-index/sdk/ai`)
|
|
312
|
+
|
|
313
|
+
Wrap the transport you hand to `createMCPClient` from `@ai-sdk/mcp` (an optional peer). The
|
|
314
|
+
wrapper reads the raw `tools/list` exchange, checks every tool against your mcp.lock, and the
|
|
315
|
+
client only ever sees approved tools, through `tools()` or `listTools()` alike:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
319
|
+
import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
|
|
320
|
+
import { lockTransport } from "@mcp-index/sdk/ai";
|
|
321
|
+
import { FileLockStore } from "@mcp-index/sdk/lock";
|
|
322
|
+
import type { Identity } from "@mcp-index/sdk/lock";
|
|
323
|
+
|
|
324
|
+
const identity: Identity = { kind: "stdio", package: "npm:@example/docs-server" };
|
|
325
|
+
const store = new FileLockStore({ root: process.cwd() });
|
|
326
|
+
const inner = new Experimental_StdioMCPTransport({ command: "npx", args: ["-y", "@example/docs-server"] });
|
|
327
|
+
const transport = lockTransport(inner, { store, identity, posture: "guard" });
|
|
328
|
+
const client = await createMCPClient({ transport });
|
|
329
|
+
const tools = await client.tools(); // approved tools only
|
|
330
|
+
console.log(Object.keys(tools));
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
A call to a withheld or unlisted tool never reaches the server. `callTool()` and the tool's
|
|
334
|
+
`execute()` reject with an `MCPClientError` whose `code` is -32011 and `data.reason` says why;
|
|
335
|
+
inside `generateText` that shows up as a `tool-error` part.
|
|
336
|
+
|
|
337
|
+
Limits:
|
|
338
|
+
|
|
339
|
+
- A digest says whether the contract changed. It says nothing about what the server does
|
|
340
|
+
when a tool runs.
|
|
341
|
+
- The AI SDK client does not refetch on `notifications/tools/list_changed`. After an approval
|
|
342
|
+
or a server change, call `tools()` again; until then the previous allow-list holds. While a
|
|
343
|
+
listing is being collected, every call is refused with `no-listing`.
|
|
344
|
+
- A tool's top-level `_meta` is not part of its digest. It reaches the client only when it
|
|
345
|
+
matches the `metaDigest` recorded at approval; otherwise, under guard, the tool is shown
|
|
346
|
+
without it and the decision reports `meta-changed` or `meta-unapproved`.
|
|
347
|
+
- `@mcp-index/sdk/ai` imports only types from `@ai-sdk/mcp`, so loading it needs nothing at
|
|
348
|
+
runtime. The `@ai-sdk/mcp` peer itself needs Node >= 22; that is the only part with that
|
|
349
|
+
requirement, and the rest of the package runs on Node 20.
|
|
350
|
+
- The AI SDK's own transports reject any message carrying a `__proto__` key before the wrapper
|
|
351
|
+
sees it, so such a listing never arrives and nothing is callable. The official SDK transports
|
|
352
|
+
pass it through and the lock withholds the tool. Instructions given to the client as
|
|
353
|
+
`initialInitializeResult` never cross the transport and are not checked.
|
|
354
|
+
|
|
177
355
|
## Related packages
|
|
178
356
|
|
|
179
357
|
Three ways to bring mcpindex into an agent, for different surfaces:
|
package/dist/src/ai.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mcp-index/sdk/ai`: lock mode for the Vercel AI SDK MCP client
|
|
3
|
+
* (`createMCPClient` from `@ai-sdk/mcp`).
|
|
4
|
+
*
|
|
5
|
+
* The AI SDK client parses `tools/list` with its own zod schema and builds
|
|
6
|
+
* tools from what is left, so a digest over `client.listTools()` would not be
|
|
7
|
+
* a digest over what the server sent. lockTransport wraps the transport the
|
|
8
|
+
* client is given and works on JSON-RPC messages before the client sees them.
|
|
9
|
+
* The message work is LockedTransport from `../lock/rawList.ts`: every page of
|
|
10
|
+
* a listing is collected under private ids and digested raw, withheld tools
|
|
11
|
+
* are dropped, and the client gets one result with no cursor. `client.tools()`
|
|
12
|
+
* and `client.listTools()` + `toolsFromDefinitions()` both only ever see
|
|
13
|
+
* approved tools (under guard).
|
|
14
|
+
*
|
|
15
|
+
* Every `tools/call` is checked against the allow-list from the latest
|
|
16
|
+
* complete listing. A refused call is never sent; the client gets a JSON-RPC
|
|
17
|
+
* -32011 error with data.reason. In @ai-sdk/mcp 2.0.74 that rejects
|
|
18
|
+
* `callTool()` and the tool's `execute()` with an MCPClientError carrying
|
|
19
|
+
* code -32011 and data { reason, tool }; it is not retried (the client never
|
|
20
|
+
* retries an error with a JSON-RPC code). Inside generateText the throw
|
|
21
|
+
* becomes a `tool-error` part, so the model sees the refusal as a tool error.
|
|
22
|
+
*
|
|
23
|
+
* Limits, all from the AI SDK client as of 2.0.74:
|
|
24
|
+
* - The client treats any server notification as "Unsupported message
|
|
25
|
+
* type" and reports it to onUncaughtError, so it never refetches on
|
|
26
|
+
* `notifications/tools/list_changed`. The wrapper still marks a listing
|
|
27
|
+
* as due (`listingDue`) and keeps the old allow-list until a new complete
|
|
28
|
+
* listing. The app has to call `tools()` again to see newly approved
|
|
29
|
+
* tools, and to drop ones the server removed.
|
|
30
|
+
* - The wrapper reports `supportsProtocolVersionDiscovery = false`, so the
|
|
31
|
+
* client uses the `initialize` handshake. Server instructions then arrive
|
|
32
|
+
* in the initialize result, where they are digested and, under guard,
|
|
33
|
+
* stripped when they differ from the lock. Instructions passed to the
|
|
34
|
+
* client as `initialInitializeResult` never cross the transport and are
|
|
35
|
+
* not checked.
|
|
36
|
+
* - The AI SDK's own stdio, SSE and HTTP transports parse with a secure
|
|
37
|
+
* JSON parser that throws on a `__proto__` key. Such a message never
|
|
38
|
+
* reaches the wrapper, so there is no listing and no tool is callable;
|
|
39
|
+
* the official SDK transports keep the key and the lock withholds the tool.
|
|
40
|
+
* - `@ai-sdk/mcp` needs Node >= 22. This module itself only imports types
|
|
41
|
+
* from it.
|
|
42
|
+
*
|
|
43
|
+
* The check covers the tool contract the model reads, and nothing about what
|
|
44
|
+
* the server does when a tool runs.
|
|
45
|
+
*/
|
|
46
|
+
import type { JSONRPCMessage, MCPTransport, MCPTransportCloseOptions, MCPTransportSendOptions } from "@ai-sdk/mcp";
|
|
47
|
+
import type { CallGate, Decision } from "./lock/check.js";
|
|
48
|
+
import type { LockEventSink } from "./lock/events.js";
|
|
49
|
+
import type { Identity, Posture } from "./lock/lock.js";
|
|
50
|
+
import type { LockStore } from "./lock/store.js";
|
|
51
|
+
/** Host settings that are not about which lock or which server. */
|
|
52
|
+
export interface AiLockHostConfig {
|
|
53
|
+
/** The host name written into events. Default "mcpindex-sdk". */
|
|
54
|
+
readonly host?: string;
|
|
55
|
+
/** Age after which a call reloads the snapshot. Default 60 s. */
|
|
56
|
+
readonly maxStalenessMs?: number;
|
|
57
|
+
readonly now?: () => number;
|
|
58
|
+
}
|
|
59
|
+
export interface AiLockOptions {
|
|
60
|
+
readonly store: LockStore;
|
|
61
|
+
/** This server's identity (section 3), from host configuration. */
|
|
62
|
+
readonly identity: Identity;
|
|
63
|
+
/** monitor, guard, or strict (behaves as guard in draft 1). */
|
|
64
|
+
readonly posture: Posture;
|
|
65
|
+
readonly sink?: LockEventSink;
|
|
66
|
+
readonly hostConfig?: AiLockHostConfig;
|
|
67
|
+
}
|
|
68
|
+
export declare class AiLockTransport implements MCPTransport {
|
|
69
|
+
#private;
|
|
70
|
+
/** Forces the initialize handshake, where instructions are checked. */
|
|
71
|
+
readonly supportsProtocolVersionDiscovery = false;
|
|
72
|
+
onclose?: () => void;
|
|
73
|
+
onerror?: (error: Error) => void;
|
|
74
|
+
onmessage?: (message: JSONRPCMessage) => void;
|
|
75
|
+
constructor(inner: MCPTransport, options: AiLockOptions);
|
|
76
|
+
get supportsMcpToolParameterHeaders(): boolean | undefined;
|
|
77
|
+
get protocolVersion(): string | undefined;
|
|
78
|
+
set protocolVersion(version: string | undefined);
|
|
79
|
+
setProtocolVersion(version: string): void;
|
|
80
|
+
/** The per-connection allow-list. */
|
|
81
|
+
get gate(): CallGate;
|
|
82
|
+
/** The decision for the latest complete listing, or null before one. */
|
|
83
|
+
get decision(): Decision | null;
|
|
84
|
+
/** True after list_changed until the next complete listing (call tools() again). */
|
|
85
|
+
get listingDue(): boolean;
|
|
86
|
+
start(): Promise<void>;
|
|
87
|
+
send(message: JSONRPCMessage, options?: MCPTransportSendOptions): Promise<void>;
|
|
88
|
+
close(options?: MCPTransportCloseOptions): Promise<void>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Wrap `inner` in lock mode for `createMCPClient({ transport })`.
|
|
92
|
+
* Lock mode has no auto-accept, first-use pin or re-pin; the lock changes
|
|
93
|
+
* only through LockStore.approve and revoke.
|
|
94
|
+
*/
|
|
95
|
+
export declare function lockTransport(inner: MCPTransport, options: AiLockOptions): AiLockTransport;
|
|
96
|
+
//# sourceMappingURL=ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAIrB,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAGxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACxC;AAuCD,qBAAa,eAAgB,YAAW,YAAY;;IAClD,uEAAuE;IACvE,QAAQ,CAAC,gCAAgC,SAAS;IAClD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;gBAKlC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa;IAUvD,IAAI,+BAA+B,IAAI,OAAO,GAAG,SAAS,CAEzD;IAED,IAAI,eAAe,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAE9C;IAED,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAIzC,qCAAqC;IACrC,IAAI,IAAI,IAAI,QAAQ,CAEnB;IAED,wEAAwE;IACxE,IAAI,QAAQ,IAAI,QAAQ,GAAG,IAAI,CAE9B;IAED,oFAAoF;IACpF,IAAI,UAAU,IAAI,OAAO,CAExB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E,KAAK,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;CAI/D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,eAAe,CAE1F"}
|
package/dist/src/ai.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mcp-index/sdk/ai`: lock mode for the Vercel AI SDK MCP client
|
|
3
|
+
* (`createMCPClient` from `@ai-sdk/mcp`).
|
|
4
|
+
*
|
|
5
|
+
* The AI SDK client parses `tools/list` with its own zod schema and builds
|
|
6
|
+
* tools from what is left, so a digest over `client.listTools()` would not be
|
|
7
|
+
* a digest over what the server sent. lockTransport wraps the transport the
|
|
8
|
+
* client is given and works on JSON-RPC messages before the client sees them.
|
|
9
|
+
* The message work is LockedTransport from `../lock/rawList.ts`: every page of
|
|
10
|
+
* a listing is collected under private ids and digested raw, withheld tools
|
|
11
|
+
* are dropped, and the client gets one result with no cursor. `client.tools()`
|
|
12
|
+
* and `client.listTools()` + `toolsFromDefinitions()` both only ever see
|
|
13
|
+
* approved tools (under guard).
|
|
14
|
+
*
|
|
15
|
+
* Every `tools/call` is checked against the allow-list from the latest
|
|
16
|
+
* complete listing. A refused call is never sent; the client gets a JSON-RPC
|
|
17
|
+
* -32011 error with data.reason. In @ai-sdk/mcp 2.0.74 that rejects
|
|
18
|
+
* `callTool()` and the tool's `execute()` with an MCPClientError carrying
|
|
19
|
+
* code -32011 and data { reason, tool }; it is not retried (the client never
|
|
20
|
+
* retries an error with a JSON-RPC code). Inside generateText the throw
|
|
21
|
+
* becomes a `tool-error` part, so the model sees the refusal as a tool error.
|
|
22
|
+
*
|
|
23
|
+
* Limits, all from the AI SDK client as of 2.0.74:
|
|
24
|
+
* - The client treats any server notification as "Unsupported message
|
|
25
|
+
* type" and reports it to onUncaughtError, so it never refetches on
|
|
26
|
+
* `notifications/tools/list_changed`. The wrapper still marks a listing
|
|
27
|
+
* as due (`listingDue`) and keeps the old allow-list until a new complete
|
|
28
|
+
* listing. The app has to call `tools()` again to see newly approved
|
|
29
|
+
* tools, and to drop ones the server removed.
|
|
30
|
+
* - The wrapper reports `supportsProtocolVersionDiscovery = false`, so the
|
|
31
|
+
* client uses the `initialize` handshake. Server instructions then arrive
|
|
32
|
+
* in the initialize result, where they are digested and, under guard,
|
|
33
|
+
* stripped when they differ from the lock. Instructions passed to the
|
|
34
|
+
* client as `initialInitializeResult` never cross the transport and are
|
|
35
|
+
* not checked.
|
|
36
|
+
* - The AI SDK's own stdio, SSE and HTTP transports parse with a secure
|
|
37
|
+
* JSON parser that throws on a `__proto__` key. Such a message never
|
|
38
|
+
* reaches the wrapper, so there is no listing and no tool is callable;
|
|
39
|
+
* the official SDK transports keep the key and the lock withholds the tool.
|
|
40
|
+
* - `@ai-sdk/mcp` needs Node >= 22. This module itself only imports types
|
|
41
|
+
* from it.
|
|
42
|
+
*
|
|
43
|
+
* The check covers the tool contract the model reads, and nothing about what
|
|
44
|
+
* the server does when a tool runs.
|
|
45
|
+
*/
|
|
46
|
+
import { LockedTransport } from "./lock/rawList.js";
|
|
47
|
+
/**
|
|
48
|
+
* Adapts an AI SDK MCPTransport (or an official SDK transport, which has the
|
|
49
|
+
* same shape) to the Transport that LockedTransport drives.
|
|
50
|
+
*/
|
|
51
|
+
class InnerAdapter {
|
|
52
|
+
inner;
|
|
53
|
+
onclose;
|
|
54
|
+
onerror;
|
|
55
|
+
onmessage;
|
|
56
|
+
closeOptions;
|
|
57
|
+
constructor(inner) {
|
|
58
|
+
this.inner = inner;
|
|
59
|
+
}
|
|
60
|
+
async start() {
|
|
61
|
+
this.inner.onmessage = (message) => this.onmessage?.(message);
|
|
62
|
+
this.inner.onerror = (error) => this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
63
|
+
this.inner.onclose = () => this.onclose?.();
|
|
64
|
+
await this.inner.start();
|
|
65
|
+
}
|
|
66
|
+
async send(message, options) {
|
|
67
|
+
const m = message;
|
|
68
|
+
if (options === undefined)
|
|
69
|
+
await this.inner.send(m);
|
|
70
|
+
else
|
|
71
|
+
await this.inner.send(m, options);
|
|
72
|
+
}
|
|
73
|
+
async close() {
|
|
74
|
+
const options = this.closeOptions;
|
|
75
|
+
if (options === undefined)
|
|
76
|
+
await this.inner.close();
|
|
77
|
+
else
|
|
78
|
+
await this.inner.close(options);
|
|
79
|
+
}
|
|
80
|
+
setProtocolVersion(version) {
|
|
81
|
+
if (typeof this.inner.setProtocolVersion === "function")
|
|
82
|
+
this.inner.setProtocolVersion(version);
|
|
83
|
+
else
|
|
84
|
+
this.inner.protocolVersion = version;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export class AiLockTransport {
|
|
88
|
+
/** Forces the initialize handshake, where instructions are checked. */
|
|
89
|
+
supportsProtocolVersionDiscovery = false;
|
|
90
|
+
onclose;
|
|
91
|
+
onerror;
|
|
92
|
+
onmessage;
|
|
93
|
+
#adapter;
|
|
94
|
+
#locked;
|
|
95
|
+
constructor(inner, options) {
|
|
96
|
+
const { hostConfig, ...rest } = options;
|
|
97
|
+
// hostConfig goes first so it cannot replace store, identity or posture.
|
|
98
|
+
// Any key the core refuses (autoAccept, tofu, repin, ...) at either level
|
|
99
|
+
// reaches the LockedTransport constructor and throws there.
|
|
100
|
+
const lockOptions = { ...(hostConfig ?? {}), ...rest };
|
|
101
|
+
this.#adapter = new InnerAdapter(inner);
|
|
102
|
+
this.#locked = new LockedTransport(this.#adapter, lockOptions);
|
|
103
|
+
}
|
|
104
|
+
get supportsMcpToolParameterHeaders() {
|
|
105
|
+
return this.#adapter.inner.supportsMcpToolParameterHeaders;
|
|
106
|
+
}
|
|
107
|
+
get protocolVersion() {
|
|
108
|
+
return this.#adapter.inner.protocolVersion;
|
|
109
|
+
}
|
|
110
|
+
set protocolVersion(version) {
|
|
111
|
+
if (version !== undefined)
|
|
112
|
+
this.#adapter.setProtocolVersion(version);
|
|
113
|
+
}
|
|
114
|
+
setProtocolVersion(version) {
|
|
115
|
+
this.#adapter.setProtocolVersion(version);
|
|
116
|
+
}
|
|
117
|
+
/** The per-connection allow-list. */
|
|
118
|
+
get gate() {
|
|
119
|
+
return this.#locked.gate;
|
|
120
|
+
}
|
|
121
|
+
/** The decision for the latest complete listing, or null before one. */
|
|
122
|
+
get decision() {
|
|
123
|
+
return this.#locked.decision;
|
|
124
|
+
}
|
|
125
|
+
/** True after list_changed until the next complete listing (call tools() again). */
|
|
126
|
+
get listingDue() {
|
|
127
|
+
return this.#locked.listingDue;
|
|
128
|
+
}
|
|
129
|
+
async start() {
|
|
130
|
+
this.#locked.onmessage = (message) => this.onmessage?.(message);
|
|
131
|
+
this.#locked.onerror = (error) => this.onerror?.(error);
|
|
132
|
+
this.#locked.onclose = () => this.onclose?.();
|
|
133
|
+
await this.#locked.start();
|
|
134
|
+
}
|
|
135
|
+
async send(message, options) {
|
|
136
|
+
await this.#locked.send(message, options);
|
|
137
|
+
}
|
|
138
|
+
async close(options) {
|
|
139
|
+
this.#adapter.closeOptions = options;
|
|
140
|
+
await this.#locked.close();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Wrap `inner` in lock mode for `createMCPClient({ transport })`.
|
|
145
|
+
* Lock mode has no auto-accept, first-use pin or re-pin; the lock changes
|
|
146
|
+
* only through LockStore.approve and revoke.
|
|
147
|
+
*/
|
|
148
|
+
export function lockTransport(inner, options) {
|
|
149
|
+
return new AiLockTransport(inner, options);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAcH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAuBpD;;;GAGG;AACH,MAAM,YAAY;IAMK;IALrB,OAAO,CAAc;IACrB,OAAO,CAA0B;IACjC,SAAS,CAA8C;IACvD,YAAY,CAAuC;IAEnD,YAAqB,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAE5C,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,OAAuB,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,OAAgC,CAAC,CAAC;QACvG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACnH,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAmB,EAAE,OAA8B;QAC5D,MAAM,CAAC,GAAG,OAAoC,CAAC;QAC/C,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;YAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,OAAkC,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC;QAClC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;;YAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,kBAAkB,CAAC,OAAe;QAChC,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,kBAAkB,KAAK,UAAU;YAAE,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;;YAC3F,IAAI,CAAC,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC;IAC5C,CAAC;CACF;AAED,MAAM,OAAO,eAAe;IAC1B,uEAAuE;IAC9D,gCAAgC,GAAG,KAAK,CAAC;IAClD,OAAO,CAAc;IACrB,OAAO,CAA0B;IACjC,SAAS,CAAqC;IAErC,QAAQ,CAAe;IACvB,OAAO,CAAkB;IAElC,YAAY,KAAmB,EAAE,OAAsB;QACrD,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QACxC,yEAAyE;QACzE,0EAA0E;QAC1E,4DAA4D;QAC5D,MAAM,WAAW,GAAG,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,GAAG,IAAI,EAAiB,CAAC;QACtE,IAAI,CAAC,QAAQ,GAAG,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,OAAO,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,+BAA+B;QACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,+BAA+B,CAAC;IAC7D,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC;IAC7C,CAAC;IAED,IAAI,eAAe,CAAC,OAA2B;QAC7C,IAAI,OAAO,KAAK,SAAS;YAAE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACvE,CAAC;IAED,kBAAkB,CAAC,OAAe;QAChC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,qCAAqC;IACrC,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,wEAAwE;IACxE,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/B,CAAC;IAED,oFAAoF;IACpF,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,OAAoC,CAAC,CAAC;QAC7F,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QACxD,IAAI,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAuB,EAAE,OAAiC;QACnE,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAgC,EAAE,OAA2C,CAAC,CAAC;IACzG,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,OAAkC;QAC5C,IAAI,CAAC,QAAQ,CAAC,YAAY,GAAG,OAAO,CAAC;QACrC,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IAC7B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAmB,EAAE,OAAsB;IACvE,OAAO,IAAI,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lock check (section 9): a pure decision over one complete listing, and a
|
|
3
|
+
* per-connection CallGate that allows `tools/call` only for names shown on the
|
|
4
|
+
* latest checked listing.
|
|
5
|
+
*
|
|
6
|
+
* Lock mode has no first-use pin, no auto-accept and no re-pin. Nothing here
|
|
7
|
+
* writes to a lock; the lock changes only through LockStore.approve/revoke.
|
|
8
|
+
*/
|
|
9
|
+
import { LockError } from "./lock.js";
|
|
10
|
+
import type { Identity, Lock, LockErrorReason, Posture } from "./lock.js";
|
|
11
|
+
export declare const LOCK_REFUSAL_CODE = -32011;
|
|
12
|
+
export declare const LOCK_REFUSAL_MESSAGE = "Tool withheld by lock";
|
|
13
|
+
export type ToolReason = "unlocked-server" | "duplicate-name" | "no-digest" | "new-tool" | "upstream-changed";
|
|
14
|
+
/** Why a shown tool's top-level `_meta` was not relayed as approved (draft 1c). */
|
|
15
|
+
export type MetaReason = "meta-changed" | "meta-unapproved";
|
|
16
|
+
export type CallReason = ToolReason | LockErrorReason | "no-listing" | "not-listed" | "classifier-error" | "unreachable";
|
|
17
|
+
export interface ToolOutcome {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly served: string | null;
|
|
20
|
+
readonly approved: string | null;
|
|
21
|
+
readonly decision: "shown" | "withheld";
|
|
22
|
+
readonly reason: ToolReason | LockErrorReason | null;
|
|
23
|
+
/** Section 9.2.3 flag for a shown tool's top-level `_meta`, else null. */
|
|
24
|
+
readonly meta: MetaReason | null;
|
|
25
|
+
}
|
|
26
|
+
/** Section 9.2.3 decision for one shown tool that has a top-level `_meta`. */
|
|
27
|
+
export interface MetaDecision {
|
|
28
|
+
/** True when `_meta` reaches the client as served. */
|
|
29
|
+
readonly relay: boolean;
|
|
30
|
+
readonly flag: MetaReason | null;
|
|
31
|
+
}
|
|
32
|
+
export interface Decision {
|
|
33
|
+
readonly posture: Posture;
|
|
34
|
+
/** Names shown to the model, in listing order (a duplicate name under monitor repeats). */
|
|
35
|
+
readonly shown: readonly string[];
|
|
36
|
+
/** Name to reason, under guard (or for any lock failure). */
|
|
37
|
+
readonly withheld: ReadonlyMap<string, ToolReason | LockErrorReason>;
|
|
38
|
+
/** Name to reason, under monitor: shown but reported. */
|
|
39
|
+
readonly flagged: ReadonlyMap<string, ToolReason>;
|
|
40
|
+
readonly removed: readonly string[];
|
|
41
|
+
readonly instructions: "instructions-changed" | null;
|
|
42
|
+
readonly stripInstructions: boolean;
|
|
43
|
+
readonly lockError: LockErrorReason | null;
|
|
44
|
+
/** The allow-list for calls on this connection. */
|
|
45
|
+
readonly callable: ReadonlySet<string>;
|
|
46
|
+
/** Indexes into the listing's tools array of the entries the model may see. */
|
|
47
|
+
readonly shownIndexes: readonly number[];
|
|
48
|
+
/**
|
|
49
|
+
* Every shown tool with a top-level `_meta`: name to whether it is relayed
|
|
50
|
+
* and its flag. A served `_meta` that does not match the approved
|
|
51
|
+
* metaDigest is flagged; under guard it is removed before the tool is
|
|
52
|
+
* relayed (stripMeta), under monitor it is relayed and reported.
|
|
53
|
+
*/
|
|
54
|
+
readonly meta: ReadonlyMap<string, MetaDecision>;
|
|
55
|
+
/** Indexes into the listing's tools array whose `_meta` is removed before relay. */
|
|
56
|
+
readonly stripMeta: ReadonlySet<number>;
|
|
57
|
+
/** One row per named listing entry, for the listing event. */
|
|
58
|
+
readonly tools: readonly ToolOutcome[];
|
|
59
|
+
readonly served: number;
|
|
60
|
+
}
|
|
61
|
+
export interface Listing {
|
|
62
|
+
readonly tools: readonly unknown[];
|
|
63
|
+
readonly instructions?: unknown;
|
|
64
|
+
}
|
|
65
|
+
export declare function isPosture(value: unknown): value is Posture;
|
|
66
|
+
/**
|
|
67
|
+
* Check one complete listing against a lock, or against a whole-lock failure.
|
|
68
|
+
* `listing.tools` is the raw array as received; nothing is fetched again.
|
|
69
|
+
*/
|
|
70
|
+
export declare function checkListing(lock: Lock | LockError, identity: Identity, listing: Listing, posture: Posture): Decision;
|
|
71
|
+
/** Decide instructions alone, for a host that sees them before any listing (initialize). */
|
|
72
|
+
export declare function checkInstructions(lock: Lock | LockError, identity: Identity, instructions: unknown, posture: Posture): {
|
|
73
|
+
readonly reason: "instructions-changed" | LockErrorReason | null;
|
|
74
|
+
readonly strip: boolean;
|
|
75
|
+
};
|
|
76
|
+
/** 'allow', or the reason sent in data.reason of a -32011 error. */
|
|
77
|
+
export declare function checkCall(decision: Decision | null, name: string): "allow" | CallReason;
|
|
78
|
+
export interface RefusalError {
|
|
79
|
+
readonly code: typeof LOCK_REFUSAL_CODE;
|
|
80
|
+
readonly message: string;
|
|
81
|
+
/** `tool` is left out when the call's name was not a string, as in the Python host. */
|
|
82
|
+
readonly data: {
|
|
83
|
+
readonly reason: CallReason;
|
|
84
|
+
readonly tool?: string;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export declare function refusalError(name: string | null, reason: CallReason): RefusalError;
|
|
88
|
+
/** Error thrown to callers who check through CallGate.enforce. */
|
|
89
|
+
export declare class LockRefusal extends Error {
|
|
90
|
+
readonly code = -32011;
|
|
91
|
+
readonly reason: CallReason;
|
|
92
|
+
readonly tool: string;
|
|
93
|
+
constructor(tool: string, reason: CallReason);
|
|
94
|
+
toJsonRpcError(): RefusalError;
|
|
95
|
+
}
|
|
96
|
+
/** Default age after which a call first reloads the snapshot (section 9.8), as in the Python host. */
|
|
97
|
+
export declare const DEFAULT_MAX_STALENESS_MS = 60000;
|
|
98
|
+
export interface CallGateOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Section 9.8: once the snapshot behind the decision is older than this, a
|
|
101
|
+
* call reloads it first (LockedTransport) and is refused with `lock-stale`
|
|
102
|
+
* when the reload fails. Default 60 s. Pass Infinity to never go stale.
|
|
103
|
+
*/
|
|
104
|
+
readonly maxStalenessMs?: number;
|
|
105
|
+
readonly now?: () => number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Per-connection allow-list. `record` replaces the decision only when a
|
|
109
|
+
* complete listing has been checked. A list_changed notification does not
|
|
110
|
+
* touch it. While a listing is in progress (section 9.2.1: `beginListing`
|
|
111
|
+
* until `record` or `markUnreachable`) every call is refused with
|
|
112
|
+
* `no-listing`, so nothing is called on the strength of a listing that is
|
|
113
|
+
* being replaced.
|
|
114
|
+
*
|
|
115
|
+
* Order of reasons (section 9.5): `no-listing` while a listing is in
|
|
116
|
+
* progress or before any listing has ended; then the whole-lock error of the
|
|
117
|
+
* latest completed listing, which a later failed listing does not clear;
|
|
118
|
+
* then `unreachable` after a failed listing; then `lock-stale`; then the
|
|
119
|
+
* per-name answer.
|
|
120
|
+
*/
|
|
121
|
+
export declare class CallGate {
|
|
122
|
+
#private;
|
|
123
|
+
constructor(options?: CallGateOptions);
|
|
124
|
+
get decision(): Decision | null;
|
|
125
|
+
get snapshotId(): string | null;
|
|
126
|
+
/** A listing started: the host asked for its first page. */
|
|
127
|
+
beginListing(): void;
|
|
128
|
+
/** The listing in progress ended without a decision and without failing. */
|
|
129
|
+
endListing(): void;
|
|
130
|
+
get listingInProgress(): boolean;
|
|
131
|
+
/** Install the decision for the latest complete listing. */
|
|
132
|
+
record(decision: Decision, snapshot: {
|
|
133
|
+
readonly snapshotId: string;
|
|
134
|
+
readonly loadedAt: number;
|
|
135
|
+
}): void;
|
|
136
|
+
/** The latest listing failed; refuse every call with `unreachable` until one completes. */
|
|
137
|
+
markUnreachable(): void;
|
|
138
|
+
get unreachable(): boolean;
|
|
139
|
+
isStale(): boolean;
|
|
140
|
+
check(name: string): "allow" | CallReason;
|
|
141
|
+
enforce(name: string): void;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../src/lock/check.ts"],"names":[],"mappings":"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"}
|