@astral-mcp/projection 0.8.0 → 0.9.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/README.md +58 -7
- package/dist/index.d.mts +1430 -426
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +8558 -2134
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,142 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
1
2
|
import "@sebastianwessel/quickjs";
|
|
2
3
|
import { CallToolResult, ContentBlock, ElicitRequestFormParams, HandlerResultTypeMap, Implementation, InputRequest, InputRequests, InputRequiredResult, InputResponse, InputResponseView, InputResponses, InputResponses as InputResponses$1, McpServer, McpServerFactory, ProtocolEra, ProtocolEra as ProtocolEra$1, acceptedContent, inputRequired, inputResponse, isInputRequiredResult } from "@modelcontextprotocol/server";
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Token residence (PRD R59, S1): the {@link TokenStore} contract, its keys,
|
|
6
|
+
* the in-memory store, and the default store — the OS credential store
|
|
7
|
+
* through the optional native library `@napi-rs/keyring` (macOS Keychain,
|
|
8
|
+
* Linux Secret Service, Windows Credential Manager), with an in-memory
|
|
9
|
+
* fallback.
|
|
10
|
+
*
|
|
11
|
+
* A store holds opaque strings. What Projection stores under a `tokens:`
|
|
12
|
+
* key is its own serialization of a token set (`token-record.ts`); a store
|
|
13
|
+
* never parses it.
|
|
14
|
+
*
|
|
15
|
+
* Nothing in this module logs, rethrows or embeds a stored value or an
|
|
16
|
+
* error from the keyring library: a store failure is reported in fixed
|
|
17
|
+
* library copy.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Where upstream credentials live (R59). `WrapOptions.tokenStore` replaces
|
|
21
|
+
* the default OS-credential-store one; a horizontally scaled deployment
|
|
22
|
+
* supplies a shared store (R27).
|
|
23
|
+
*
|
|
24
|
+
* Keys and values are opaque strings. A key is `<kind>:<JSON array>`; the
|
|
25
|
+
* kind before the first `:` says what the value is, so a store can route
|
|
26
|
+
* or label entries without parsing anything else:
|
|
27
|
+
*
|
|
28
|
+
* - `tokens:["<canonical resource URL>","<client id>","<principal>"]` — a
|
|
29
|
+
* token set ({@link tokenStoreKey});
|
|
30
|
+
* - `registration:…` — reserved for dynamic client registrations (phase 1b).
|
|
31
|
+
*
|
|
32
|
+
* Values are Projection's serialization (ASCII JSON) and must be returned
|
|
33
|
+
* byte for byte.
|
|
34
|
+
*
|
|
35
|
+
* - `get` returns the value stored under `key`, or `undefined`.
|
|
36
|
+
* - `set` replaces it.
|
|
37
|
+
* - `delete` removes it and reports whether it removed anything. With
|
|
38
|
+
* `expected`, it is a compare-and-delete (R58): it deletes only when the
|
|
39
|
+
* stored value is still exactly `expected`, so a value another process
|
|
40
|
+
* has just stored survives.
|
|
41
|
+
*
|
|
42
|
+
* Implementations must never log or surface a value.
|
|
43
|
+
*/
|
|
44
|
+
interface TokenStore {
|
|
45
|
+
get(key: string): Promise<string | undefined>;
|
|
46
|
+
set(key: string, value: string): Promise<void>;
|
|
47
|
+
delete(key: string, expected?: string): Promise<boolean>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The process-local store: the fallback, and what tests and single-process
|
|
51
|
+
* deployments that want no persistence can pass as `tokenStore`.
|
|
52
|
+
*/
|
|
53
|
+
export declare class MemoryTokenStore implements TokenStore {
|
|
54
|
+
#private;
|
|
55
|
+
get(key: string): Promise<string | undefined>;
|
|
56
|
+
set(key: string, value: string): Promise<void>;
|
|
57
|
+
delete(key: string, expected?: string): Promise<boolean>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Upstream sources given by URL (PRD §6.10, R51, R60): the `UpstreamSpec`
|
|
61
|
+
* shape, how it is told apart from the other source kinds, and its
|
|
62
|
+
* validation and canonical form.
|
|
63
|
+
*
|
|
64
|
+
* Every mistake here is a `validate` {@link ProjectionError} raised at wrap
|
|
65
|
+
* time, before anything connects (R39's rule). Messages never echo a value
|
|
66
|
+
* that could be a secret: the client secret is never quoted, and a URL is
|
|
67
|
+
* quoted only after it parsed without userinfo.
|
|
68
|
+
*/
|
|
69
|
+
/**
|
|
70
|
+
* OAuth settings of an {@link UpstreamSpec} (R51, R52, R53, R61). Phase 1
|
|
71
|
+
* needs a client the integrator registered beforehand: `clientId`, and a
|
|
72
|
+
* discouraged `clientSecret`. `clientMetadataUrl` and
|
|
73
|
+
* `authorizationEndpoint` arrive with phase 1b and are `validate` errors
|
|
74
|
+
* until then.
|
|
75
|
+
*/
|
|
76
|
+
interface UpstreamOAuthOptions {
|
|
77
|
+
/** The OAuth client id the integrator registered with the server's authorization server (R52). */
|
|
78
|
+
clientId: string;
|
|
79
|
+
/** Client secret for a confidential client. Discouraged: the device flow needs none (R52). */
|
|
80
|
+
clientSecret?: string;
|
|
81
|
+
/** Scopes to request, replacing the SDK's selection from metadata (R53). */
|
|
82
|
+
scopes?: string[];
|
|
83
|
+
/**
|
|
84
|
+
* Extra hosts the user may be sent to for sign-in, beyond the
|
|
85
|
+
* authorization server's own (R61). Bare lowercase ASCII hostnames, not IP addresses.
|
|
86
|
+
*/
|
|
87
|
+
signInHosts?: string[];
|
|
88
|
+
/** Token endpoint override for servers with incomplete metadata (R53). `https:` only. */
|
|
89
|
+
tokenEndpoint?: string;
|
|
90
|
+
/** Device authorization endpoint override (R53). `https:` only. */
|
|
91
|
+
deviceAuthorizationEndpoint?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A remote MCP server given by its URL (R51). Projection builds the
|
|
95
|
+
* Streamable HTTP transport itself, installs its own OAuth client provider
|
|
96
|
+
* on it, owns the resulting connection and closes it in `close()`.
|
|
97
|
+
*/
|
|
98
|
+
interface UpstreamSpec {
|
|
99
|
+
/** The server's Streamable HTTP endpoint. `https:`, or `http:` on a loopback host. */
|
|
100
|
+
url: string | URL;
|
|
101
|
+
oauth?: UpstreamOAuthOptions;
|
|
102
|
+
}
|
|
103
|
+
/** A validated {@link UpstreamOAuthOptions}, frozen at wrap time. */
|
|
104
|
+
interface ResolvedOAuth {
|
|
105
|
+
readonly clientId: string;
|
|
106
|
+
readonly clientSecret?: string;
|
|
107
|
+
readonly scopes?: readonly string[];
|
|
108
|
+
readonly signInHosts?: readonly string[];
|
|
109
|
+
readonly tokenEndpoint?: string;
|
|
110
|
+
readonly deviceAuthorizationEndpoint?: string;
|
|
111
|
+
}
|
|
112
|
+
/** A validated {@link UpstreamSpec}. */
|
|
113
|
+
interface ResolvedUpstream {
|
|
114
|
+
/** The parsed URL the transport connects to. */
|
|
115
|
+
readonly url: URL;
|
|
116
|
+
/** The canonical URL (R51): the resource's identity, token-store key and blob binding. */
|
|
117
|
+
readonly canonicalUrl: string;
|
|
118
|
+
/** The R60 per-server identity, `url:<canonical url>`. */
|
|
119
|
+
readonly identity: string;
|
|
120
|
+
/** The URL's hostname as the parser gives it (ASCII, punycode for IDNs). */
|
|
121
|
+
readonly host: string;
|
|
122
|
+
readonly oauth?: ResolvedOAuth;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Why an upstream auth step failed, in library terms:
|
|
126
|
+
*
|
|
127
|
+
* - `sign_in_required`: no usable token (none stored, none that refreshes).
|
|
128
|
+
* - `no_client`: the server requires sign-in and no `oauth.clientId` is configured (R52).
|
|
129
|
+
* - `insufficient_scope`: the granted scopes do not cover the request (R58).
|
|
130
|
+
* - `refresh_failed`: a refresh could not complete for a transient reason
|
|
131
|
+
* (a 5xx, a malformed response, a network failure); the stored record is kept.
|
|
132
|
+
* - `client_rejected`: the authorization server refused the OAuth client
|
|
133
|
+
* (`invalid_client` / `unauthorized_client`); the stored record is kept.
|
|
134
|
+
* - `insecure_endpoint`: an authorization-server endpoint is not `https:` (R61).
|
|
135
|
+
* - `oauth_error`: the authorization server refused (its `error` code, when valid, rides along).
|
|
136
|
+
* - `token_store`: the token store failed.
|
|
137
|
+
* - `failed`: anything else in the auth machinery (invalid metadata, for example).
|
|
138
|
+
*/
|
|
139
|
+
type UpstreamAuthReason = 'sign_in_required' | 'no_client' | 'insufficient_scope' | 'refresh_failed' | 'client_rejected' | 'insecure_endpoint' | 'oauth_error' | 'token_store' | 'failed';
|
|
5
140
|
/**
|
|
6
141
|
* Shared core types for Projection.
|
|
7
142
|
*
|
|
@@ -61,25 +196,168 @@ export declare function findTool(catalog: ServerCatalog, name: string): ToolCata
|
|
|
61
196
|
export declare function dedupeTools(tools: ToolCatalogEntry[]): ToolCatalogEntry[];
|
|
62
197
|
/** Narrow to a non-null, non-array object. */
|
|
63
198
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
199
|
+
/** A plain object (prototype `Object.prototype` or `null`): what a configuration value must be. */
|
|
200
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
201
|
+
/**
|
|
202
|
+
* One wrapped-server progress report (MCP `notifications/progress`), as the
|
|
203
|
+
* upstream sent it: `message` is server-authored text (S5), not yet
|
|
204
|
+
* neutralized.
|
|
205
|
+
*/
|
|
206
|
+
export interface UpstreamProgress {
|
|
207
|
+
progress: number;
|
|
208
|
+
total?: number;
|
|
209
|
+
message?: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Host-side options for one upstream call: never model-authored. `signal`
|
|
213
|
+
* cancels the upstream request (MCP `notifications/cancelled`) when the
|
|
214
|
+
* facade's own request is cancelled; `onProgress` receives the upstream's
|
|
215
|
+
* progress reports, and its presence asks the upstream for them.
|
|
216
|
+
*/
|
|
217
|
+
export interface UpstreamCallOptions {
|
|
218
|
+
signal?: AbortSignal;
|
|
219
|
+
onProgress?: (progress: UpstreamProgress) => void;
|
|
220
|
+
/**
|
|
221
|
+
* Re-issue a call whose upstream asked for input (G1, DECISIONS D24): the
|
|
222
|
+
* answers keyed by the upstream's own keys, and the upstream's own
|
|
223
|
+
* `requestState` echoed verbatim. Read only from a verified blob and the
|
|
224
|
+
* human's responses, never from tool arguments.
|
|
225
|
+
*/
|
|
226
|
+
input?: UpstreamInputResume;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* What a wrapped server asked for when its tool answered `input_required`
|
|
230
|
+
* (MCP 2026-07-28 MRTR), as received: untrusted server data (S5), not yet
|
|
231
|
+
* sanitized. Present only for a box that forwards upstream elicitation.
|
|
232
|
+
*/
|
|
233
|
+
export interface UpstreamInputRequest {
|
|
234
|
+
/** The upstream's embedded requests, keyed by its own keys. */
|
|
235
|
+
requests: Record<string, unknown>;
|
|
236
|
+
/** The upstream's opaque state, to be echoed back verbatim. */
|
|
237
|
+
requestState?: string;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Whether a box passes its server's own questions (MCP elicitation, form or
|
|
241
|
+
* URL mode) on to the human (G1, DECISIONS D24). `'refuse'` (the default)
|
|
242
|
+
* declares no elicitation capability to the server, and a question fails the
|
|
243
|
+
* call with a result that says so. `'forward'` declares the capability and
|
|
244
|
+
* relays each question over MRTR, attributed to the box and never shaped
|
|
245
|
+
* like an approval. Sampling and roots are never forwarded.
|
|
246
|
+
*/
|
|
247
|
+
export type UpstreamElicitationMode = 'forward' | 'refuse';
|
|
248
|
+
/** The answers and state that re-issue a call whose upstream asked for input. */
|
|
249
|
+
export interface UpstreamInputResume {
|
|
250
|
+
/** Bare `ElicitResult`s keyed by the upstream's own request keys. */
|
|
251
|
+
responses: Record<string, unknown>;
|
|
252
|
+
requestState?: string;
|
|
253
|
+
}
|
|
64
254
|
/** Executes a tool call against the wrapped server. Implemented by the introspector. */
|
|
65
255
|
export interface ToolExecutor {
|
|
66
|
-
callTool(alias: ServerAlias, name: string, args: Record<string, unknown
|
|
256
|
+
callTool(alias: ServerAlias, name: string, args: Record<string, unknown>, options?: UpstreamCallOptions): Promise<ToolCallOutcome>;
|
|
67
257
|
}
|
|
68
258
|
export type ToolCallOutcome = {
|
|
69
259
|
ok: true;
|
|
70
260
|
result: unknown;
|
|
261
|
+
/**
|
|
262
|
+
* Set when `result` is the upstream's raw `content` array (no
|
|
263
|
+
* `structuredContent`, not a single text block), so a renderer can
|
|
264
|
+
* tell content blocks from structured data that happens to be an
|
|
265
|
+
* array.
|
|
266
|
+
*/
|
|
267
|
+
rawContent?: true;
|
|
268
|
+
/**
|
|
269
|
+
* Short text the server sent beside its `structuredContent` that is
|
|
270
|
+
* not a JSON mirror of it (a warning such as "returning 10 of 40"),
|
|
271
|
+
* raw but redacted like the result (S1). Shown to the model only as
|
|
272
|
+
* a neutralized, quoted result note (D29, `catalog/result-notes.ts`).
|
|
273
|
+
*/
|
|
274
|
+
serverNote?: string;
|
|
71
275
|
} | {
|
|
72
276
|
ok: false;
|
|
73
277
|
error: {
|
|
74
278
|
message: string;
|
|
75
279
|
detail?: unknown;
|
|
76
280
|
};
|
|
281
|
+
/**
|
|
282
|
+
* Set when the call failed because upstream authorization failed (a
|
|
283
|
+
* source given by URL, R51): `call_tool` reports it in the R25 `auth`
|
|
284
|
+
* phase with this library-copy hint, not as an `upstream` failure.
|
|
285
|
+
*/
|
|
286
|
+
auth?: {
|
|
287
|
+
hint: string;
|
|
288
|
+
};
|
|
289
|
+
/**
|
|
290
|
+
* The upstream asked the user for input and its box forwards upstream
|
|
291
|
+
* elicitation (G1): the gated entry points turn this into their own
|
|
292
|
+
* `input_required`. A consumer that does not handle it sees an
|
|
293
|
+
* ordinary failure, so nothing is ever forwarded by accident.
|
|
294
|
+
*/
|
|
295
|
+
upstreamInput?: UpstreamInputRequest;
|
|
296
|
+
/**
|
|
297
|
+
* The upstream asked the user for input that is not forwarded (its
|
|
298
|
+
* box refuses upstream elicitation, or it asked for sampling or
|
|
299
|
+
* roots). `error.message` says so; the arguments were not the problem.
|
|
300
|
+
*/
|
|
301
|
+
inputRefused?: true;
|
|
77
302
|
};
|
|
78
303
|
export type PolicyDecision = 'auto' | 'ask' | 'deny';
|
|
79
304
|
export interface PolicyOverrides {
|
|
80
305
|
/** Per-tool decision override, keyed by tool name (v1: single server). */
|
|
81
306
|
[toolName: string]: PolicyDecision;
|
|
82
307
|
}
|
|
308
|
+
/**
|
|
309
|
+
* Approvals mode (R49). `'every-write'` (the default) asks before every tool
|
|
310
|
+
* that is not read-only; `'destructive-only'` also lets a non-read-only tool
|
|
311
|
+
* run without approval when its annotations declare `destructiveHint: false`
|
|
312
|
+
* exactly. That extends the trust placed in `readOnlyHint` to
|
|
313
|
+
* `destructiveHint: false` (S5): enable it only for servers you trust.
|
|
314
|
+
*/
|
|
315
|
+
export type ApprovalsMode = 'every-write' | 'destructive-only';
|
|
316
|
+
/**
|
|
317
|
+
* Everything a policy decision reads besides the tool itself (R14, R49):
|
|
318
|
+
* the per-tool overrides and the resolved approvals mode, both frozen at
|
|
319
|
+
* wrap time. Every surface decides through `decidePolicy` with one of these
|
|
320
|
+
* (build it with `policyContext`), so the posture a model is shown is the
|
|
321
|
+
* decision the gate makes.
|
|
322
|
+
*
|
|
323
|
+
* `approvals` is required — the context is *resolved* — so a bare
|
|
324
|
+
* {@link PolicyOverrides} object cannot be passed where a context is
|
|
325
|
+
* expected and silently lose its overrides. A multi-server facade adds its
|
|
326
|
+
* per-source modes as `toolApprovals`, resolved per qualified tool (they
|
|
327
|
+
* take precedence over `approvals`, R49), without changing `decidePolicy`'s
|
|
328
|
+
* signature.
|
|
329
|
+
*/
|
|
330
|
+
export interface PolicyContext {
|
|
331
|
+
/** Per-tool overrides (R14); an override always wins. */
|
|
332
|
+
overrides?: PolicyOverrides | undefined;
|
|
333
|
+
/** The approvals mode (R49) that applies when no override does. */
|
|
334
|
+
approvals: ApprovalsMode;
|
|
335
|
+
/**
|
|
336
|
+
* A multi-server facade's per-source approvals modes (R49), resolved at
|
|
337
|
+
* wrap time to one entry per qualified tool name (`fs.write_file`) from
|
|
338
|
+
* the boxed view's members — so the box is never re-derived by splitting
|
|
339
|
+
* a name. A tool with an entry decides with that mode instead of
|
|
340
|
+
* `approvals`. Absent on single-server facades.
|
|
341
|
+
*/
|
|
342
|
+
toolApprovals?: ReadonlyMap<string, ApprovalsMode> | undefined;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* The policy options every deciding surface takes (R14, R49) — the gate
|
|
346
|
+
* deps, `searchTools`, `describeTool`, the machine host and typegen — in one
|
|
347
|
+
* shape, turned into a {@link PolicyContext} by `policyContext`. Absent
|
|
348
|
+
* options mean no overrides and `'every-write'`.
|
|
349
|
+
*/
|
|
350
|
+
export interface PolicyOptions {
|
|
351
|
+
/** Per-tool policy overrides (R14). */
|
|
352
|
+
policy?: PolicyOverrides | undefined;
|
|
353
|
+
/** Approvals mode (R49), decided with `policy`. */
|
|
354
|
+
approvals?: ApprovalsMode | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* Per-source approvals modes of a multi-server facade, resolved per
|
|
357
|
+
* qualified tool name (R49; see {@link PolicyContext.toolApprovals}).
|
|
358
|
+
*/
|
|
359
|
+
toolApprovals?: ReadonlyMap<string, ApprovalsMode> | undefined;
|
|
360
|
+
}
|
|
83
361
|
/** A tool call requested by the machine via the `toolCall` action creator. */
|
|
84
362
|
export interface PendingToolCall {
|
|
85
363
|
/** Unique id for this call within the workflow (also the inputRequest key). */
|
|
@@ -93,10 +371,20 @@ export interface PendingToolCall {
|
|
|
93
371
|
export type HostEvent = {
|
|
94
372
|
type: 'TOOL_RESULT';
|
|
95
373
|
callId: string;
|
|
374
|
+
/** The call's own arguments — as sealed, for an approved call (S2). */
|
|
375
|
+
args: Record<string, unknown>;
|
|
96
376
|
result: unknown;
|
|
377
|
+
/**
|
|
378
|
+
* The upstream's text, when `result` is that text parsed: a single
|
|
379
|
+
* text block holding JSON for an object or an array (D29).
|
|
380
|
+
*/
|
|
381
|
+
text?: string;
|
|
382
|
+
/** The facade's notes on the result (R35.6, D29); absent when there are none. */
|
|
383
|
+
notes?: string[];
|
|
97
384
|
} | {
|
|
98
385
|
type: 'TOOL_ERROR';
|
|
99
386
|
callId: string;
|
|
387
|
+
args: Record<string, unknown>;
|
|
100
388
|
error: {
|
|
101
389
|
message: string;
|
|
102
390
|
detail?: unknown;
|
|
@@ -104,11 +392,17 @@ export type HostEvent = {
|
|
|
104
392
|
} | {
|
|
105
393
|
type: 'TOOL_DENIED';
|
|
106
394
|
callId: string;
|
|
395
|
+
args: Record<string, unknown>;
|
|
107
396
|
reason?: string;
|
|
108
397
|
};
|
|
109
398
|
export type ErrorPhase = 'transpile' | 'validate' | 'runtime' | 'cap_exceeded' | 'blob_verification' | 'protocol' |
|
|
110
399
|
/** An `isError` result or transport failure from the wrapped tool, surfaced by `call_tool` (R25). */
|
|
111
|
-
'upstream' |
|
|
400
|
+
'upstream' |
|
|
401
|
+
/**
|
|
402
|
+
* An upstream sign-in is required or could not complete (R25, R57):
|
|
403
|
+
* library copy only, plus at most the OAuth `error` code.
|
|
404
|
+
*/
|
|
405
|
+
'auth' | 'terminal';
|
|
112
406
|
export interface Diagnostic {
|
|
113
407
|
message: string;
|
|
114
408
|
/** Optional source location within the submitted code. */
|
|
@@ -159,8 +453,13 @@ export declare const MAX_CAPS: ResourceCaps;
|
|
|
159
453
|
/** Clamp requested caps into [MIN_CAPS, MAX_CAPS] (R12). */
|
|
160
454
|
export declare function clampCaps(requested: Partial<ResourceCaps>): ResourceCaps;
|
|
161
455
|
export declare const BLOB_SCHEMA_VERSION = 1;
|
|
162
|
-
/**
|
|
163
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Which tool minted a blob (R36): a blob opened by the other tool is
|
|
458
|
+
* rejected. `upstream_input` is minted by `call_tool` when the wrapped
|
|
459
|
+
* tool's own server asked the human a question (G1, D24); `call_tool`
|
|
460
|
+
* opens both of its kinds.
|
|
461
|
+
*/
|
|
462
|
+
export type BlobKind = 'machine' | 'call' | 'upstream_input';
|
|
164
463
|
export interface BlobPayload {
|
|
165
464
|
/** Blob schema version; unknown versions are rejected. */
|
|
166
465
|
v: number;
|
|
@@ -190,8 +489,27 @@ export interface BlobPayload {
|
|
|
190
489
|
* of its final segment.
|
|
191
490
|
*/
|
|
192
491
|
audit: AuditEntry[];
|
|
492
|
+
/**
|
|
493
|
+
* How this suspension was put to the person (R15): present, and
|
|
494
|
+
* `'batched'`, only when it was one batched elicitation; absent means
|
|
495
|
+
* one elicitation per pending call. Sealed so the resume answers the
|
|
496
|
+
* prompt that was actually shown, whatever the resuming instance's
|
|
497
|
+
* option (D25). Only on `machine` blobs with two or more pending calls;
|
|
498
|
+
* never on `call` or `upstream_input` blobs (both minted by `call_tool`,
|
|
499
|
+
* which never batches).
|
|
500
|
+
*/
|
|
501
|
+
prompts?: 'batched';
|
|
193
502
|
/** Round-trip counter (R18). */
|
|
194
503
|
trip: number;
|
|
504
|
+
/**
|
|
505
|
+
* Calls that already ran under the gate and are waiting on their own
|
|
506
|
+
* server's question (G1, D24), keyed by call id: the call, how it was
|
|
507
|
+
* authorized, the server's sealed `requestState`, and the questions.
|
|
508
|
+
* Never part of `pending` (S2): an answer re-issues the recorded call
|
|
509
|
+
* and approves nothing. Exactly one entry keyed `call` for an
|
|
510
|
+
* `upstream_input` blob; absent from `call` blobs.
|
|
511
|
+
*/
|
|
512
|
+
upstreamInput?: Record<string, UpstreamHold>;
|
|
195
513
|
/** Random nonce for replay scoping. */
|
|
196
514
|
nonce: string;
|
|
197
515
|
/** Issued-at, Unix ms. */
|
|
@@ -199,6 +517,38 @@ export interface BlobPayload {
|
|
|
199
517
|
/** Expiry, Unix ms (default TTL 15 min). */
|
|
200
518
|
exp: number;
|
|
201
519
|
}
|
|
520
|
+
/** A sanitized MCP elicitation schema: a flat object of primitive fields (G1). */
|
|
521
|
+
export interface ElicitationSchema {
|
|
522
|
+
type: 'object';
|
|
523
|
+
properties: Record<string, Record<string, unknown>>;
|
|
524
|
+
required?: string[];
|
|
525
|
+
}
|
|
526
|
+
/** One forwarded question as sealed: enough to map and check the answer (G1). */
|
|
527
|
+
export interface ForwardedQuestion {
|
|
528
|
+
/** The server's own key for this request. */
|
|
529
|
+
key: string;
|
|
530
|
+
/**
|
|
531
|
+
* `form`: the human filled the sanitized form in `schema`. `url`: the
|
|
532
|
+
* human was shown a link (as URL mode, or as a form with the link as inert
|
|
533
|
+
* text when the client cannot open links); accepting sends no content.
|
|
534
|
+
*/
|
|
535
|
+
mode: 'form' | 'url';
|
|
536
|
+
/** The sanitized schema the human answered (`form` only). */
|
|
537
|
+
schema?: ElicitationSchema;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* A call that ran under the gate and is waiting on its server's question
|
|
541
|
+
* (G1, D24), sealed in the blob's `upstreamInput` — never in the S2
|
|
542
|
+
* `pending` map: an answer re-issues the recorded call, it approves nothing.
|
|
543
|
+
*/
|
|
544
|
+
export interface UpstreamHold extends PendingToolCall {
|
|
545
|
+
/** How the call was authorized when it first ran. */
|
|
546
|
+
decision: 'auto' | 'approved';
|
|
547
|
+
/** The server's own opaque state, echoed verbatim on the re-issue. */
|
|
548
|
+
requestState?: string;
|
|
549
|
+
/** The questions, keyed by the facade's keys. */
|
|
550
|
+
questions: Record<string, ForwardedQuestion>;
|
|
551
|
+
}
|
|
202
552
|
export interface BlobSealerConfig {
|
|
203
553
|
/** Current encryption key (32 bytes). */
|
|
204
554
|
key: Uint8Array;
|
|
@@ -244,6 +594,8 @@ export type ApprovalVerdict = {
|
|
|
244
594
|
reason?: string;
|
|
245
595
|
};
|
|
246
596
|
/** Outcome of running one execution segment. */
|
|
597
|
+
/** Most result notes a completed segment reports (D29). */
|
|
598
|
+
export declare const MAX_SEGMENT_NOTES = 10;
|
|
247
599
|
export type SegmentOutcome = {
|
|
248
600
|
status: 'completed';
|
|
249
601
|
/** Final machine output/context to return to the client. */
|
|
@@ -251,10 +603,21 @@ export type SegmentOutcome = {
|
|
|
251
603
|
/** Per-call approval audit trail. */
|
|
252
604
|
audit: AuditEntry[];
|
|
253
605
|
logs: LogEntry[];
|
|
606
|
+
/**
|
|
607
|
+
* The result notes of this segment's tool calls (R35.6, D29), each
|
|
608
|
+
* prefixed with its tool's name, at most {@link MAX_SEGMENT_NOTES};
|
|
609
|
+
* absent when there are none.
|
|
610
|
+
*/
|
|
611
|
+
notes?: string[];
|
|
254
612
|
} | {
|
|
255
613
|
status: 'suspended';
|
|
256
614
|
/** Gated calls awaiting approval, keyed by inputRequest key. */
|
|
257
615
|
pending: Record<string, PendingToolCall>;
|
|
616
|
+
/**
|
|
617
|
+
* Calls that ran and whose server asked the human a question (G1):
|
|
618
|
+
* the adapter forwards each question and seals the hold.
|
|
619
|
+
*/
|
|
620
|
+
held: HeldCall[];
|
|
258
621
|
/** Actor snapshot to seal into the blob. */
|
|
259
622
|
snapshot: unknown;
|
|
260
623
|
trip: number;
|
|
@@ -263,7 +626,19 @@ export type SegmentOutcome = {
|
|
|
263
626
|
} | {
|
|
264
627
|
status: 'error';
|
|
265
628
|
error: StructuredError;
|
|
629
|
+
/**
|
|
630
|
+
* The workflow's audit trail up to the failure (earlier segments'
|
|
631
|
+
* included), so the result can say which approved calls already ran
|
|
632
|
+
* and which were refused. Absent when nothing was audited.
|
|
633
|
+
*/
|
|
634
|
+
audit?: AuditEntry[];
|
|
266
635
|
};
|
|
636
|
+
/** A call whose server answered `input_required` on a box that forwards (G1). */
|
|
637
|
+
export interface HeldCall {
|
|
638
|
+
call: PendingToolCall;
|
|
639
|
+
decision: 'auto' | 'approved';
|
|
640
|
+
input: UpstreamInputRequest;
|
|
641
|
+
}
|
|
267
642
|
export interface AuditEntry {
|
|
268
643
|
callId: string;
|
|
269
644
|
tool: string;
|
|
@@ -274,7 +649,9 @@ export interface AuditEntry {
|
|
|
274
649
|
export interface RedactionHook {
|
|
275
650
|
/**
|
|
276
651
|
* Mask sensitive argument fields before they appear in elicitation copy
|
|
277
|
-
* (R16). Receives the tool name
|
|
652
|
+
* (R16). Receives the tool name — qualified (`<alias>.<tool>`) on a
|
|
653
|
+
* multi-server facade — and a copy of the resolved args; returns args to
|
|
654
|
+
* display.
|
|
278
655
|
*/
|
|
279
656
|
(toolName: string, args: Record<string, unknown>): Record<string, unknown>;
|
|
280
657
|
}
|
|
@@ -293,8 +670,93 @@ export interface ProjectionConfig {
|
|
|
293
670
|
*/
|
|
294
671
|
codeModePreamble?: string;
|
|
295
672
|
}
|
|
296
|
-
|
|
297
|
-
|
|
673
|
+
/**
|
|
674
|
+
* Error carrying the structured `isError` body every failure returns (R25).
|
|
675
|
+
*/
|
|
676
|
+
export declare class ProjectionError extends Error {
|
|
677
|
+
readonly phase: ErrorPhase;
|
|
678
|
+
readonly diagnostics: Diagnostic[];
|
|
679
|
+
readonly hint: string;
|
|
680
|
+
logs: LogEntry[];
|
|
681
|
+
constructor(phase: ErrorPhase, message: string, options?: {
|
|
682
|
+
diagnostics?: Diagnostic[];
|
|
683
|
+
hint?: string;
|
|
684
|
+
logs?: LogEntry[];
|
|
685
|
+
cause?: unknown;
|
|
686
|
+
});
|
|
687
|
+
toStructured(): StructuredError;
|
|
688
|
+
}
|
|
689
|
+
/** Convert any thrown value into a StructuredError, defaulting to `runtime` phase. */
|
|
690
|
+
export declare function toStructuredError(err: unknown, logs?: LogEntry[]): StructuredError;
|
|
691
|
+
/**
|
|
692
|
+
* An upstream auth failure of a source given by URL (R25 `auth`, R57):
|
|
693
|
+
* library copy naming the source, plus at most the OAuth `error` code; the
|
|
694
|
+
* typed {@link reason} is what callers branch on (just-in-time sign-in
|
|
695
|
+
* re-locks a box on it).
|
|
696
|
+
*/
|
|
697
|
+
declare class UpstreamAuthProjectionError extends ProjectionError {
|
|
698
|
+
readonly reason: UpstreamAuthReason;
|
|
699
|
+
readonly oauthCode?: string;
|
|
700
|
+
constructor(reason: UpstreamAuthReason, oauthCode: string | undefined, message: string, hint: string);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* One `UpstreamSpec` source, validated and bound to its token store. The
|
|
704
|
+
* introspector opens it like any other source; Projection owns the client.
|
|
705
|
+
*/
|
|
706
|
+
declare class UpstreamConnector {
|
|
707
|
+
#private;
|
|
708
|
+
readonly upstream: ResolvedUpstream;
|
|
709
|
+
/** How messages name the source: `source "github"`, or `the wrapped server`. */
|
|
710
|
+
readonly label: string;
|
|
711
|
+
constructor(upstream: ResolvedUpstream, store: TokenStore, label: string);
|
|
712
|
+
/** The R60 identity, `url:<canonical url>`. */
|
|
713
|
+
get identity(): string;
|
|
714
|
+
/** The source and its host, for messages: `source "github" (api.github.com)`. */
|
|
715
|
+
get where(): string;
|
|
716
|
+
/**
|
|
717
|
+
* Connect (R51): Streamable HTTP with Projection's auth provider and
|
|
718
|
+
* guarded fetch, `onInsufficientScope: 'throw'` and the SDK's default
|
|
719
|
+
* `redirectPolicy` (never `'follow'`, R58, R61). Aborting `signal` closes
|
|
720
|
+
* the half-open connection. Any failure is {@link toError}'s.
|
|
721
|
+
*/
|
|
722
|
+
open(signal?: AbortSignal, options?: {
|
|
723
|
+
forwardElicitation?: boolean;
|
|
724
|
+
}): Promise<Client>;
|
|
725
|
+
/** `text` from this source, inert, with every credential it presented redacted, and bounded. */
|
|
726
|
+
redactText(text: string): string;
|
|
727
|
+
/**
|
|
728
|
+
* `value` from this source with every credential it presented redacted.
|
|
729
|
+
* Throws a {@link ProjectionError} when the value cannot be checked (fail
|
|
730
|
+
* closed, `SecretSet.scrubValue`).
|
|
731
|
+
*/
|
|
732
|
+
redact<T>(value: T): T;
|
|
733
|
+
/**
|
|
734
|
+
* Abort every auth request this connection's runs have in flight (a
|
|
735
|
+
* refresh, its discovery), so a facade that closes leaves no refresh
|
|
736
|
+
* behind for another facade over the same store to wait on.
|
|
737
|
+
*/
|
|
738
|
+
close(): void;
|
|
739
|
+
/** The typed `auth` error for `err`, when it is an upstream auth failure. */
|
|
740
|
+
authFailure(err: unknown): UpstreamAuthProjectionError | undefined;
|
|
741
|
+
/**
|
|
742
|
+
* Any failure to connect to or introspect this source, as a
|
|
743
|
+
* {@link ProjectionError}: an auth failure as the typed `auth` error; a
|
|
744
|
+
* refused request in fixed copy; anything else a `protocol` error (R47)
|
|
745
|
+
* whose text is redacted and bounded — never the raw error, whose
|
|
746
|
+
* message, data or cause may quote a server body that echoes the token.
|
|
747
|
+
*/
|
|
748
|
+
toError(err: unknown): ProjectionError;
|
|
749
|
+
/** A tool call to this source that threw, as the outcome `call_tool` and a machine see (R25). */
|
|
750
|
+
callFailure(name: string, err: unknown): Extract<ToolCallOutcome, {
|
|
751
|
+
ok: false;
|
|
752
|
+
}>;
|
|
753
|
+
/**
|
|
754
|
+
* Delete the stored tokens of this source (R59). No revocation yet (phase
|
|
755
|
+
* 1b). A store that cannot delete is an `auth` error: a sign-out must not
|
|
756
|
+
* silently leave the token behind.
|
|
757
|
+
*/
|
|
758
|
+
signOut(): Promise<void>;
|
|
759
|
+
}
|
|
298
760
|
/** Envelope format version prefix. Bump (proj2, …) on any wire-format change. */
|
|
299
761
|
export declare const ENVELOPE_PREFIX = "proj1";
|
|
300
762
|
/** Default blob TTL for `machine` blobs: 15 minutes (R20). */
|
|
@@ -306,6 +768,11 @@ export declare const DEFAULT_TTL_MS: number;
|
|
|
306
768
|
export declare const DEFAULT_CALL_TTL_MS: number;
|
|
307
769
|
/** Default sealed-blob size ceiling: 128 KB (R23). */
|
|
308
770
|
export declare const DEFAULT_MAX_BLOB_BYTES: number;
|
|
771
|
+
/**
|
|
772
|
+
* Which facade kind a blob belongs to (R41): `single` for `wrapMcpServer`,
|
|
773
|
+
* `multi` for `wrapMcpServers`. Selects the HKDF info string; nothing else.
|
|
774
|
+
*/
|
|
775
|
+
type BlobKeyDomain = 'single' | 'multi';
|
|
309
776
|
/** SHA-256 hex digest of a code argument (S4 binding — R20 `codeHash`). */
|
|
310
777
|
export declare function hashCode(code: string): string;
|
|
311
778
|
interface BuildBlobPayloadInput {
|
|
@@ -324,10 +791,20 @@ interface BuildBlobPayloadInput {
|
|
|
324
791
|
pending: Record<string, PendingToolCall>;
|
|
325
792
|
/** Approval audit accumulated so far in this workflow (PRD §8). */
|
|
326
793
|
audit?: AuditEntry[] | undefined;
|
|
794
|
+
/** `'batched'` when the suspension was one batched prompt (R15, D25). */
|
|
795
|
+
prompts?: 'batched' | undefined;
|
|
327
796
|
/** Round-trip counter (R18). */
|
|
328
797
|
trip: number;
|
|
798
|
+
/** Calls waiting on their own server's question (G1); see `BlobPayload.upstreamInput`. */
|
|
799
|
+
upstreamInput?: Record<string, UpstreamHold> | undefined;
|
|
329
800
|
/** Blob TTL in ms. Default {@link DEFAULT_TTL_MS} (15 minutes). */
|
|
330
801
|
ttlMs?: number | undefined;
|
|
802
|
+
/**
|
|
803
|
+
* Latest allowed `exp`, Unix ms: the blob expires at `iat + ttlMs` or this,
|
|
804
|
+
* whichever is earlier. A blob minted to continue an earlier one carries
|
|
805
|
+
* that blob's `exp` here, so a chain of round trips never extends it (R38).
|
|
806
|
+
*/
|
|
807
|
+
notAfter?: number | undefined;
|
|
331
808
|
/** Clock override for testability, Unix ms. Default `Date.now()`. */
|
|
332
809
|
now?: number | undefined;
|
|
333
810
|
}
|
|
@@ -342,12 +819,16 @@ export declare function buildBlobPayload(input: BuildBlobPayloadInput): BlobPayl
|
|
|
342
819
|
* JSON → deflateRaw → AES-256-GCM under an HKDF-SHA256 per-blob derived key,
|
|
343
820
|
* with the envelope header as AAD. Throws ProjectionError phase `terminal` if
|
|
344
821
|
* the sealed envelope exceeds `maxBlobBytes` (default 128 KB) or the config
|
|
345
|
-
* keys are malformed.
|
|
822
|
+
* keys are malformed. `domain` selects the facade kind's key domain (R41);
|
|
823
|
+
* it defaults to `single`, the only domain before multi-server facades.
|
|
346
824
|
*/
|
|
347
|
-
export declare function sealBlob(payload: BlobPayload, config: BlobSealerConfig): string;
|
|
825
|
+
export declare function sealBlob(payload: BlobPayload, config: BlobSealerConfig, domain?: BlobKeyDomain): string;
|
|
348
826
|
interface OpenBlobOptions {
|
|
349
|
-
/**
|
|
350
|
-
|
|
827
|
+
/**
|
|
828
|
+
* The kind(s) the opening tool accepts (R36). Default `machine`.
|
|
829
|
+
* `call_tool` opens `call` and `upstream_input` blobs.
|
|
830
|
+
*/
|
|
831
|
+
expectedKind?: BlobKind | readonly BlobKind[] | undefined;
|
|
351
832
|
/**
|
|
352
833
|
* The text re-sent with the retry (S4): the `code` argument for
|
|
353
834
|
* `execute_machine`, the canonical request for `call_tool`.
|
|
@@ -356,6 +837,11 @@ interface OpenBlobOptions {
|
|
|
356
837
|
/** Identity of the wrapped server handling the retry (R30). */
|
|
357
838
|
serverIdentity: string;
|
|
358
839
|
config: BlobSealerConfig;
|
|
840
|
+
/**
|
|
841
|
+
* Key domain of the facade opening the blob (R41). Default `single`. A
|
|
842
|
+
* blob sealed in the other domain fails authentication.
|
|
843
|
+
*/
|
|
844
|
+
domain?: BlobKeyDomain | undefined;
|
|
359
845
|
/** Clock override for testability, Unix ms. Default `Date.now()`. */
|
|
360
846
|
now?: number | undefined;
|
|
361
847
|
}
|
|
@@ -375,8 +861,6 @@ interface OpenBlobOptions {
|
|
|
375
861
|
* returned once every check has passed.
|
|
376
862
|
*/
|
|
377
863
|
export declare function openBlob(blob: string, options: OpenBlobOptions): BlobPayload;
|
|
378
|
-
//#endregion
|
|
379
|
-
//#region src/blob/versions.d.ts
|
|
380
864
|
/**
|
|
381
865
|
* Version constants recorded into every blob (R20) and strict-matched on
|
|
382
866
|
* open (R21, Q3: no migration — mismatches are rejected).
|
|
@@ -389,108 +873,282 @@ export declare function openBlob(blob: string, options: OpenBlobOptions): BlobPa
|
|
|
389
873
|
* CI instead of silently minting blobs with a stale version stamp.
|
|
390
874
|
*/
|
|
391
875
|
/** `@astral-mcp/projection` package version (must match packages/projection/package.json). */
|
|
392
|
-
export declare const LIB_VERSION = "0.
|
|
876
|
+
export declare const LIB_VERSION = "0.9.0";
|
|
393
877
|
/** Pinned XState version (must match the exact pin in package.json — D2). */
|
|
394
878
|
export declare const XSTATE_VERSION = "5.32.5";
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/mrtr/protocol.d.ts
|
|
397
|
-
/**
|
|
398
|
-
* What a `tools/call` handler may return on the 2026-07-28 era: a finished
|
|
399
|
-
* result, or the input-required suspension. This is the SDK's own
|
|
400
|
-
* handler-return type for the method, not a shape of ours.
|
|
401
|
-
*/
|
|
402
|
-
type ToolCallResult = HandlerResultTypeMap['tools/call'];
|
|
403
879
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* handler never reads them from `args`; Projection still names them because
|
|
407
|
-
* `executeMachine` is callable outside an SDK handler (R27 — the facade is
|
|
408
|
-
* plain functions).
|
|
880
|
+
* Decide the policy for a catalog tool (R14, R49). Precedence, strongest
|
|
881
|
+
* first:
|
|
409
882
|
*
|
|
410
|
-
* `
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
883
|
+
* 1. A developer override (`auto` | `ask` | `deny`) always wins. An override
|
|
884
|
+
* entry with an unrecognized value (possible when the config arrives
|
|
885
|
+
* untyped) fails safe to `ask` — it never grants `auto`.
|
|
886
|
+
* 2. `annotations.readOnlyHint === true` yields `auto` in every mode.
|
|
887
|
+
* 3. Under `approvals: 'destructive-only'`, a tool whose annotations declare
|
|
888
|
+
* `destructiveHint === false` exactly yields `auto`; a missing hint or any
|
|
889
|
+
* other value still asks (the MCP default reads a non-read-only tool as
|
|
890
|
+
* destructive).
|
|
891
|
+
* 4. Otherwise `ask` (S5: unannotated defaults to ask).
|
|
892
|
+
*
|
|
893
|
+
* A context whose `approvals` is not one of the two modes throws a
|
|
894
|
+
* `TypeError` — fail closed, never decide on a malformed context (the wrap
|
|
895
|
+
* rejects malformed modes up front with a `validate` error — see
|
|
896
|
+
* {@link assertApprovalsMode}).
|
|
897
|
+
*
|
|
898
|
+
* Only own properties of `context.overrides` and of `entry.annotations` are
|
|
899
|
+
* consulted, so tools named like `Object.prototype` members (`toString`,
|
|
900
|
+
* `constructor`, ...) cannot pick up inherited junk as a decision, and an
|
|
901
|
+
* annotations object cannot inherit a relaxing hint from its prototype.
|
|
414
902
|
*/
|
|
415
|
-
|
|
416
|
-
/** The `requestState` echoed back unchanged. */
|
|
417
|
-
requestState?: string;
|
|
418
|
-
/** Answers keyed by the same keys as `inputRequests`. */
|
|
419
|
-
inputResponses?: InputResponses$1 | Record<string, unknown>;
|
|
420
|
-
}
|
|
903
|
+
export declare function decidePolicy(entry: ToolCatalogEntry, context?: PolicyContext): PolicyDecision;
|
|
421
904
|
/**
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* an approval prompt whose only payload is an optional decline reason is our
|
|
428
|
-
* product decision (R16/R17), not a protocol shape.
|
|
905
|
+
* Build the {@link PolicyContext} for a set of {@link PolicyOptions} — the
|
|
906
|
+
* per-tool `policy` overrides (R14), the `approvals` mode and a multi-server
|
|
907
|
+
* facade's per-tool `toolApprovals` (R49), the shape the gate deps, the
|
|
908
|
+
* catalog tools, the machine host and typegen all take. An absent mode
|
|
909
|
+
* resolves to `'every-write'`.
|
|
429
910
|
*/
|
|
430
|
-
export declare
|
|
911
|
+
export declare function policyContext(options?: PolicyOptions): PolicyContext;
|
|
431
912
|
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
* `
|
|
438
|
-
* `'modern'` rather than as a version string. We keep the literal purely so
|
|
439
|
-
* error copy can name the revision a client must speak (R28).
|
|
913
|
+
* Validate an `approvals` option (R49): absent, or one of the two modes. A
|
|
914
|
+
* malformed value is a `validate`-phase {@link ProjectionError} — the mode
|
|
915
|
+
* is one deploy-time switch, so a typo fails the wrap instead of silently
|
|
916
|
+
* changing who gets asked (DECISIONS D17). `wrapMcpServer` calls it; call
|
|
917
|
+
* it yourself when passing `approvals` to `executeMachine` or
|
|
918
|
+
* `generateToolTypes` directly.
|
|
440
919
|
*/
|
|
441
|
-
export declare
|
|
920
|
+
export declare function assertApprovalsMode(value: unknown): ApprovalsMode | undefined;
|
|
921
|
+
/** The three normative posture strings (R33) — models match on these. */
|
|
922
|
+
type ToolPosture = 'runs immediately' | 'asks for approval' | 'blocked by policy';
|
|
923
|
+
/** Map a policy decision to its plain-words posture (R33). Shared by every surface that names one. */
|
|
924
|
+
export declare function postureOf(decision: PolicyDecision): ToolPosture;
|
|
925
|
+
/** Result of resolving a pending call against a server catalog. */
|
|
926
|
+
type ResolvedCallPolicy = {
|
|
927
|
+
ok: true;
|
|
928
|
+
decision: PolicyDecision;
|
|
929
|
+
entry: ToolCatalogEntry;
|
|
930
|
+
} | {
|
|
931
|
+
ok: false;
|
|
932
|
+
error: ProjectionError;
|
|
933
|
+
};
|
|
442
934
|
/**
|
|
443
|
-
*
|
|
935
|
+
* Resolve a machine-requested tool call against the server catalog and
|
|
936
|
+
* decide its policy.
|
|
444
937
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* no `extra.protocolVersion` to read, and reading one was the bug this
|
|
450
|
-
* migration fixes (docs/DECISIONS.md D1).
|
|
938
|
+
* Returns the decision together with the matching catalog entry (the entry
|
|
939
|
+
* is what elicitation copy must be built from — S6), or a structured
|
|
940
|
+
* `validate`-phase {@link ProjectionError} when the machine names a tool the
|
|
941
|
+
* wrapped server does not declare.
|
|
451
942
|
*/
|
|
452
|
-
export declare function
|
|
453
|
-
//#endregion
|
|
454
|
-
//#region src/mrtr/adapter.d.ts
|
|
943
|
+
export declare function resolveCallPolicy(call: PendingToolCall, catalog: ServerCatalog, context?: PolicyContext): ResolvedCallPolicy;
|
|
455
944
|
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
* envelope — siblings of `arguments` in a `tools/call` that the SDK lifts off
|
|
460
|
-
* the request params onto the handler context. They are NOT tool arguments.
|
|
461
|
-
* A direct caller of `executeMachine` MUST populate them only from the
|
|
462
|
-
* transport's retry fields, never from model-authored tool arguments: a
|
|
463
|
-
* model that has seen a `requestState` could otherwise echo it back with a
|
|
464
|
-
* fabricated `accept` and approve its own gated call.
|
|
945
|
+
* Canonical TOOL_DENIED reason for calls blocked by a `deny` policy
|
|
946
|
+
* decision. A `deny` decision never produces an elicitation — the machine
|
|
947
|
+
* host injects `TOOL_DENIED` with this reason instead.
|
|
465
948
|
*/
|
|
466
|
-
|
|
467
|
-
/** The authored module source (R2). */
|
|
468
|
-
code: string;
|
|
469
|
-
lang?: 'ts' | 'js';
|
|
470
|
-
/** Passed to the machine as its input. */
|
|
471
|
-
input?: unknown;
|
|
472
|
-
}
|
|
949
|
+
export declare const POLICY_DENIED_REASON = "denied by server policy";
|
|
473
950
|
/**
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* bridge; `call_tool` needs nothing more (R35, R38).
|
|
951
|
+
* Build the TOOL_DENIED reason string for a policy-denied call. With a tool
|
|
952
|
+
* name it names the denied tool so the machine (and audit trail) can tell
|
|
953
|
+
* which call was blocked.
|
|
478
954
|
*/
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
|
|
955
|
+
export declare function policyDeniedReason(toolName?: string): string;
|
|
956
|
+
/** Structured approval copy for one gated call (input to the MRTR adapter). */
|
|
957
|
+
interface ElicitationCopy {
|
|
958
|
+
/** Short action title, e.g. `Approve: delete file`. */
|
|
959
|
+
title: string;
|
|
960
|
+
/** Full human-readable summary (plain text with an indented JSON block). */
|
|
961
|
+
message: string;
|
|
962
|
+
/** Tool name, taken from the catalog entry; qualified with `box` when given. */
|
|
963
|
+
toolName: string;
|
|
964
|
+
/** Machine state path that requested the call (raw; render with care). */
|
|
965
|
+
stateName: string;
|
|
966
|
+
/** The redacted arguments as rendered in `message` (JSON text, possibly truncated). */
|
|
967
|
+
args: string;
|
|
968
|
+
/** True when the catalog entry carries `destructiveHint: true`. */
|
|
969
|
+
destructive: boolean;
|
|
970
|
+
}
|
|
971
|
+
interface ElicitationCopyOptions {
|
|
972
|
+
/** Redaction hook applied to args before any rendering (R16). */
|
|
973
|
+
redact?: RedactionHook;
|
|
974
|
+
/**
|
|
975
|
+
* Who asked: a machine state (default) or a direct `call_tool` request
|
|
976
|
+
* (R35). Selects the footer; nothing else differs.
|
|
977
|
+
*/
|
|
978
|
+
origin?: 'machine' | 'direct';
|
|
979
|
+
/**
|
|
980
|
+
* Multi-server facades (R40, R46): the alias of the box the tool lives
|
|
981
|
+
* in. `entry` is then the box's own catalog entry (bare name) and the
|
|
982
|
+
* call's name is qualified. The copy names the box (R46): the title reads
|
|
983
|
+
* `Approve (fs): …` (`⚠ Approve (fs): …` when destructive), the purpose
|
|
984
|
+
* line `What fs says it does: "…"` — always rendered, `(no description)`
|
|
985
|
+
* when the server gives none — and the footer `Tool: fs.write_file`, or
|
|
986
|
+
* `Tool: fs › "github.delete_repo"` when the bare name has a dot, so it
|
|
987
|
+
* never reads as another box's tool. The redaction hook receives the
|
|
988
|
+
* qualified name. The alias is the host's, never a server-reported name
|
|
989
|
+
* (S6).
|
|
990
|
+
*/
|
|
991
|
+
box?: string;
|
|
992
|
+
/**
|
|
993
|
+
* Backstop cap on the whole rendered args JSON, in characters. Default
|
|
994
|
+
* 4096. Each string leaf is independently capped at 512 characters first.
|
|
995
|
+
*/
|
|
996
|
+
maxArgsChars?: number;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Build the approval summary for a gated call.
|
|
1000
|
+
*
|
|
1001
|
+
* `entry` MUST be the catalog entry the call was resolved against (use
|
|
1002
|
+
* `resolveCallPolicy`); the copy takes the tool name, title and description
|
|
1003
|
+
* from it, never from the pending call or its arguments.
|
|
1004
|
+
*/
|
|
1005
|
+
export declare function buildElicitationCopy(call: PendingToolCall, entry: ToolCatalogEntry, options?: ElicitationCopyOptions): ElicitationCopy;
|
|
1006
|
+
/**
|
|
1007
|
+
* Rendering untrusted strings inertly (S5, S6).
|
|
1008
|
+
*
|
|
1009
|
+
* Tool names, descriptions, the wrapped server's identity, and machine state
|
|
1010
|
+
* names all flow from a possibly third-party server or a model-authored
|
|
1011
|
+
* machine. Wherever they are rendered into human-facing prompt copy or into
|
|
1012
|
+
* generated source, they must not be able to forge structure with control
|
|
1013
|
+
* characters (newlines, ESC for ANSI sequences) or reorder text with Unicode
|
|
1014
|
+
* bidi overrides. Centralizing the character class here keeps every call site
|
|
1015
|
+
* on the same, complete definition — an earlier divergence let generated
|
|
1016
|
+
* `.d.ts`/bridge comments miss the directional-isolate range that the
|
|
1017
|
+
* elicitation copy already stripped.
|
|
1018
|
+
*/
|
|
1019
|
+
/**
|
|
1020
|
+
* Replace control and bidi characters with the replacement character.
|
|
1021
|
+
* `allowNewline` keeps `\n` (for multi-line blocks whose formatting newlines
|
|
1022
|
+
* are part of the rendering, e.g. a pretty-printed JSON block); `allowTab`
|
|
1023
|
+
* also keeps `\t`, for text printed verbatim between fences (D35), where a
|
|
1024
|
+
* tab is the text's own (a Makefile recipe, a TSV row) and cannot start a
|
|
1025
|
+
* line. `allowTab` takes effect only with `allowNewline`.
|
|
1026
|
+
*/
|
|
1027
|
+
export declare function neutralizeControlChars(text: string, options?: {
|
|
1028
|
+
allowNewline?: boolean;
|
|
1029
|
+
allowTab?: boolean;
|
|
1030
|
+
}): string;
|
|
1031
|
+
/** Truncate to `maxChars`, appending an ellipsis when shortened. */
|
|
1032
|
+
export declare function capLength(text: string, maxChars: number): string;
|
|
1033
|
+
/**
|
|
1034
|
+
* The one-line human purpose: the first sentence of the server's
|
|
1035
|
+
* description, neutralized and capped. Runs of replacement characters
|
|
1036
|
+
* (neutralized control bytes) are collapsed so a hostile or malformed
|
|
1037
|
+
* description does not render as a wall of `�`. Undefined when the server
|
|
1038
|
+
* gave no usable description.
|
|
1039
|
+
*
|
|
1040
|
+
* One rule, three consumers — the approval copy (R16), the `discover_apis`
|
|
1041
|
+
* overview and `search_tools` (R33) — so every projection describes a tool
|
|
1042
|
+
* identically.
|
|
1043
|
+
*/
|
|
1044
|
+
export declare function purposeSentence(description: string | undefined): string | undefined;
|
|
1045
|
+
/**
|
|
1046
|
+
* What a `tools/call` handler may return on the 2026-07-28 era: a finished
|
|
1047
|
+
* result, or the input-required suspension. This is the SDK's own
|
|
1048
|
+
* handler-return type for the method, not a shape of ours.
|
|
1049
|
+
*/
|
|
1050
|
+
type ToolCallResult = HandlerResultTypeMap['tools/call'];
|
|
1051
|
+
/**
|
|
1052
|
+
* The MRTR retry fields the client re-sends alongside the original
|
|
1053
|
+
* arguments. The SDK lifts them off the params into `ctx.mcpReq`, so a
|
|
1054
|
+
* handler never reads them from `args`; Projection still names them because
|
|
1055
|
+
* `executeMachine` is callable outside an SDK handler (R27 — the facade is
|
|
1056
|
+
* plain functions).
|
|
1057
|
+
*
|
|
1058
|
+
* `inputResponses` is deliberately widened the same way the SDK's own
|
|
1059
|
+
* `inputResponse()` / `acceptedContent()` readers widen it: the values come
|
|
1060
|
+
* from the client and are never validated by the SDK, so a caller may hand
|
|
1061
|
+
* over whatever the wire carried.
|
|
1062
|
+
*/
|
|
1063
|
+
interface RetryEnvelope {
|
|
1064
|
+
/** The `requestState` echoed back unchanged. */
|
|
1065
|
+
requestState?: string;
|
|
1066
|
+
/** Answers keyed by the same keys as `inputRequests`. */
|
|
1067
|
+
inputResponses?: InputResponses$1 | Record<string, unknown>;
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* The JSON Schema an approval elicitation asks the client to collect, typed
|
|
1071
|
+
* as the SDK's restricted elicitation schema so `inputRequired.elicit()`
|
|
1072
|
+
* accepts it without a cast.
|
|
1073
|
+
*
|
|
1074
|
+
* Projection-specific: MRTR carries whatever schema the server asks for, and
|
|
1075
|
+
* an approval prompt whose only payload is an optional decline reason is our
|
|
1076
|
+
* product decision (R16/R17), not a protocol shape.
|
|
1077
|
+
*/
|
|
1078
|
+
export declare const APPROVAL_SCHEMA: ElicitRequestFormParams['requestedSchema'];
|
|
1079
|
+
/**
|
|
1080
|
+
* Protocol revision that introduced MRTR.
|
|
1081
|
+
*
|
|
1082
|
+
* The SDK has no exported constant for it: `LATEST_PROTOCOL_VERSION` and
|
|
1083
|
+
* `SUPPORTED_PROTOCOL_VERSIONS` cover the *legacy* (`initialize`-negotiated)
|
|
1084
|
+
* family only — `2025-11-25` and earlier. The 2026-07-28 era is selected by
|
|
1085
|
+
* `server/discover`, and the SDK models it as the `ProtocolEra` value
|
|
1086
|
+
* `'modern'` rather than as a version string. We keep the literal purely so
|
|
1087
|
+
* error copy can name the revision a client must speak (R28).
|
|
1088
|
+
*/
|
|
1089
|
+
export declare const MRTR_PROTOCOL_REVISION = "2026-07-28";
|
|
1090
|
+
/**
|
|
1091
|
+
* Whether the era a request is being served on can carry MRTR (R28).
|
|
1092
|
+
*
|
|
1093
|
+
* The SDK binds an era to the *serving instance*, not to a per-request
|
|
1094
|
+
* header: `createMcpHandler` / `serveStdio` hand the era to the server
|
|
1095
|
+
* factory as `McpRequestContext.era`, and a hand-connected `McpServer` is
|
|
1096
|
+
* always `'legacy'`. So this takes the era, not a version string — there is
|
|
1097
|
+
* no `extra.protocolVersion` to read, and reading one was the bug this
|
|
1098
|
+
* migration fixes (docs/DECISIONS.md D1).
|
|
1099
|
+
*/
|
|
1100
|
+
export declare function supportsMrtr(era: ProtocolEra$1 | undefined): boolean;
|
|
1101
|
+
/** How a suspension with several gated calls is put to the person. */
|
|
1102
|
+
type ApprovalPrompts = 'per-call' | 'batched';
|
|
1103
|
+
/** The single inputRequests key of a batched prompt (reserved namespace). */
|
|
1104
|
+
export declare const BATCH_KEY = "projection:approvals";
|
|
1105
|
+
/** The boolean field that carries the verdict of the call at 0-based position `i`. */
|
|
1106
|
+
export declare function batchField(i: number): string;
|
|
1107
|
+
/**
|
|
1108
|
+
* `execute_machine` arguments plus the MRTR retry envelope.
|
|
1109
|
+
*
|
|
1110
|
+
* **Security (S2):** `requestState` and `inputResponses` are the retry
|
|
1111
|
+
* envelope — siblings of `arguments` in a `tools/call` that the SDK lifts off
|
|
1112
|
+
* the request params onto the handler context. They are NOT tool arguments.
|
|
1113
|
+
* A direct caller of `executeMachine` MUST populate them only from the
|
|
1114
|
+
* transport's retry fields, never from model-authored tool arguments: a
|
|
1115
|
+
* model that has seen a `requestState` could otherwise echo it back with a
|
|
1116
|
+
* fabricated `accept` and approve its own gated call.
|
|
1117
|
+
*/
|
|
1118
|
+
interface ExecuteMachineArgs extends RetryEnvelope {
|
|
1119
|
+
/** The authored module source (R2). */
|
|
1120
|
+
code: string;
|
|
1121
|
+
lang?: 'ts' | 'js';
|
|
1122
|
+
/** Passed to the machine as its input. */
|
|
1123
|
+
input?: unknown;
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* What every gated entry point needs: the catalog and executor of the wrapped
|
|
1127
|
+
* server, the blob sealer, and the per-facade policy, redaction, caps,
|
|
1128
|
+
* protocol-era and replay settings. `execute_machine` adds the sandbox
|
|
1129
|
+
* bridge; `call_tool` needs nothing more (R35, R38).
|
|
1130
|
+
*/
|
|
1131
|
+
interface ApprovalGateDeps extends PolicyOptions {
|
|
1132
|
+
catalog: ServerCatalog;
|
|
1133
|
+
executor: ToolExecutor;
|
|
1134
|
+
blob: BlobSealerConfig;
|
|
1135
|
+
/**
|
|
1136
|
+
* The key domain of the facade kind serving this gate (R41): `single` for
|
|
1137
|
+
* `wrapMcpServer`, `multi` for `wrapMcpServers`. Checked against the
|
|
1138
|
+
* catalog, failing closed on a mismatch (`blobKeyDomain`). Omitted, it is
|
|
1139
|
+
* derived from the catalog alone.
|
|
1140
|
+
*/
|
|
1141
|
+
blobDomain?: BlobKeyDomain;
|
|
1142
|
+
caps?: Partial<ResourceCaps>;
|
|
1143
|
+
redact?: RedactionHook;
|
|
1144
|
+
/**
|
|
1145
|
+
* Protocol era this request is being served on, for down-negotiation
|
|
1146
|
+
* (R28). The SDK hands it to the server factory as
|
|
1147
|
+
* `McpRequestContext.era`; `undefined` is treated as legacy.
|
|
1148
|
+
*/
|
|
1149
|
+
protocolEra?: ProtocolEra;
|
|
1150
|
+
/**
|
|
1151
|
+
* Opt-in legacy-client compatibility (R28 amendment): when true, a
|
|
494
1152
|
* suspension on a pre-2026-07-28 connection is returned as a normal
|
|
495
1153
|
* `input_required` result and the SDK's serving layer fulfils it through
|
|
496
1154
|
* old-style server→client `elicitation/create` requests, re-invoking this
|
|
@@ -510,8 +1168,21 @@ interface ApprovalGateDeps {
|
|
|
510
1168
|
* not know) leaves the shim to decide.
|
|
511
1169
|
*/
|
|
512
1170
|
clientCanElicit?: boolean;
|
|
1171
|
+
/**
|
|
1172
|
+
* Whether the connected client declared URL-mode elicitation. A forwarded
|
|
1173
|
+
* server question that is a link (G1) is sent in URL mode only then;
|
|
1174
|
+
* otherwise the link is shown as inert text in a form.
|
|
1175
|
+
*/
|
|
1176
|
+
clientUrlElicitation?: boolean;
|
|
513
1177
|
/** Optional shared replay cache (R38); consulted after verification, before execution. */
|
|
514
1178
|
replayCache?: ReplayCache;
|
|
1179
|
+
/**
|
|
1180
|
+
* How a suspension holding several gated calls is put to the person
|
|
1181
|
+
* (`WrapOptions.approvalPrompts`, batch.ts): one elicitation per call
|
|
1182
|
+
* (default) or one batched elicitation for all of them. Only machine
|
|
1183
|
+
* suspensions can hold several; a `call_tool` suspension holds one.
|
|
1184
|
+
*/
|
|
1185
|
+
approvalPrompts?: ApprovalPrompts;
|
|
515
1186
|
}
|
|
516
1187
|
interface ExecuteMachineDeps extends ApprovalGateDeps {
|
|
517
1188
|
/** The `@astral-mcp/tools` bridge module source the sandbox loads (R2). */
|
|
@@ -527,8 +1198,6 @@ interface ExecuteMachineDeps extends ApprovalGateDeps {
|
|
|
527
1198
|
export declare function executeMachine(args: ExecuteMachineArgs, deps: ExecuteMachineDeps): Promise<ToolCallResult>;
|
|
528
1199
|
/** Every failure is a normal result with `isError: true` and a structured body (R25). */
|
|
529
1200
|
export declare function errorResult(error: StructuredError): CallToolResult;
|
|
530
|
-
//#endregion
|
|
531
|
-
//#region src/catalog/call.d.ts
|
|
532
1201
|
/**
|
|
533
1202
|
* `call_tool` arguments plus the MRTR retry envelope.
|
|
534
1203
|
*
|
|
@@ -545,6 +1214,12 @@ interface CallToolArgs extends RetryEnvelope {
|
|
|
545
1214
|
name: string;
|
|
546
1215
|
/** Arguments for the tool; absent means `{}`. */
|
|
547
1216
|
args?: unknown;
|
|
1217
|
+
/**
|
|
1218
|
+
* Host-side options for the upstream call this request runs, if it runs
|
|
1219
|
+
* one: the facade request's cancellation signal and a progress sink.
|
|
1220
|
+
* Never populated from tool arguments.
|
|
1221
|
+
*/
|
|
1222
|
+
upstream?: UpstreamCallOptions;
|
|
548
1223
|
}
|
|
549
1224
|
/** `call_tool` needs exactly the dependencies every gated tool shares (R35, R38). */
|
|
550
1225
|
type CallToolDeps = ApprovalGateDeps;
|
|
@@ -562,6 +1237,12 @@ type CallToolOutcome = {
|
|
|
562
1237
|
truncated?: true;
|
|
563
1238
|
/** Byte length of the full result's JSON, when truncated. */
|
|
564
1239
|
outputBytes?: number;
|
|
1240
|
+
/**
|
|
1241
|
+
* The facade's remarks on the result, when it looks partial or the
|
|
1242
|
+
* server sent a note beside its structured result (R35.6, D29):
|
|
1243
|
+
* single neutralized lines, at most three, printed under the head.
|
|
1244
|
+
*/
|
|
1245
|
+
notes?: string[];
|
|
565
1246
|
} | {
|
|
566
1247
|
status: 'declined';
|
|
567
1248
|
tool: string;
|
|
@@ -575,8 +1256,6 @@ type CallToolOutcome = {
|
|
|
575
1256
|
export declare const CALL_KEY = "call";
|
|
576
1257
|
/** Run `call_tool` for one `tools/call`, first attempt or MRTR retry. */
|
|
577
1258
|
export declare function callTool(args: CallToolArgs, deps: CallToolDeps): Promise<ToolCallResult>;
|
|
578
|
-
//#endregion
|
|
579
|
-
//#region src/catalog/canonical.d.ts
|
|
580
1259
|
/**
|
|
581
1260
|
* Canonical JSON for the `call_tool` binding hash (PRD R36).
|
|
582
1261
|
*
|
|
@@ -601,8 +1280,6 @@ export declare function callTool(args: CallToolArgs, deps: CallToolDeps): Promis
|
|
|
601
1280
|
export declare function canonicalJson(value: unknown): string;
|
|
602
1281
|
/** The text a `call` blob is bound to (R36): `{ kind: "call", name, args }`. */
|
|
603
1282
|
export declare function canonicalRequest(name: string, args: Record<string, unknown>): string;
|
|
604
|
-
//#endregion
|
|
605
|
-
//#region src/catalog/validate-args.d.ts
|
|
606
1283
|
/**
|
|
607
1284
|
* Structural argument validation for `call_tool` (PRD R35.1).
|
|
608
1285
|
*
|
|
@@ -610,7 +1287,8 @@ export declare function canonicalRequest(name: string, args: Record<string, unkn
|
|
|
610
1287
|
* types match, enum membership for top-level primitives, and no unknown
|
|
611
1288
|
* top-level properties when the schema forbids them. It exists so a human
|
|
612
1289
|
* is never asked to approve a call the wrapped server would reject on
|
|
613
|
-
* shape, and so the model gets a `validate` hint naming the property
|
|
1290
|
+
* shape, and so the model gets a `validate` hint naming the property and
|
|
1291
|
+
* showing the tool's call signature (D32). Full
|
|
614
1292
|
* JSON Schema validation would run twice — the wrapped server is the
|
|
615
1293
|
* authority — for nothing but a nicer error message (D9).
|
|
616
1294
|
*
|
|
@@ -618,156 +1296,7 @@ export declare function canonicalRequest(name: string, args: Record<string, unkn
|
|
|
618
1296
|
* argument name is ordinary data (S5/S6).
|
|
619
1297
|
*/
|
|
620
1298
|
/** Throws a `validate`-phase {@link ProjectionError} naming the offending property. */
|
|
621
|
-
export declare function validateArgsShape(toolName: string, args: Record<string, unknown>, schema: Record<string, unknown
|
|
622
|
-
//#endregion
|
|
623
|
-
//#region src/errors.d.ts
|
|
624
|
-
/**
|
|
625
|
-
* Error carrying the structured `isError` body every failure returns (R25).
|
|
626
|
-
*/
|
|
627
|
-
export declare class ProjectionError extends Error {
|
|
628
|
-
readonly phase: ErrorPhase;
|
|
629
|
-
readonly diagnostics: Diagnostic[];
|
|
630
|
-
readonly hint: string;
|
|
631
|
-
logs: LogEntry[];
|
|
632
|
-
constructor(phase: ErrorPhase, message: string, options?: {
|
|
633
|
-
diagnostics?: Diagnostic[];
|
|
634
|
-
hint?: string;
|
|
635
|
-
logs?: LogEntry[];
|
|
636
|
-
cause?: unknown;
|
|
637
|
-
});
|
|
638
|
-
toStructured(): StructuredError;
|
|
639
|
-
}
|
|
640
|
-
/** Convert any thrown value into a StructuredError, defaulting to `runtime` phase. */
|
|
641
|
-
export declare function toStructuredError(err: unknown, logs?: LogEntry[]): StructuredError;
|
|
642
|
-
//#endregion
|
|
643
|
-
//#region src/policy/decision.d.ts
|
|
644
|
-
/**
|
|
645
|
-
* Decide the policy for a catalog tool (R14).
|
|
646
|
-
*
|
|
647
|
-
* - A developer override (`auto` | `ask` | `deny`) always wins.
|
|
648
|
-
* - With no override, `annotations.readOnlyHint === true` yields `auto`;
|
|
649
|
-
* an absent or `false` hint yields `ask` (S5: unannotated defaults to ask).
|
|
650
|
-
* - An override entry with an unrecognized value (possible when the config
|
|
651
|
-
* arrives untyped) fails safe to `ask` — it never grants `auto`.
|
|
652
|
-
*
|
|
653
|
-
* Only own properties of `overrides` are consulted, so tools named like
|
|
654
|
-
* `Object.prototype` members (`toString`, `constructor`, ...) cannot pick up
|
|
655
|
-
* inherited junk as a decision.
|
|
656
|
-
*/
|
|
657
|
-
export declare function decidePolicy(entry: ToolCatalogEntry, overrides?: PolicyOverrides): PolicyDecision;
|
|
658
|
-
/** The three normative posture strings (R33) — models match on these. */
|
|
659
|
-
type ToolPosture = 'runs immediately' | 'asks for approval' | 'blocked by policy';
|
|
660
|
-
/** Map a policy decision to its plain-words posture (R33). Shared by every surface that names one. */
|
|
661
|
-
export declare function postureOf(decision: PolicyDecision): ToolPosture;
|
|
662
|
-
/** Result of resolving a pending call against a server catalog. */
|
|
663
|
-
type ResolvedCallPolicy = {
|
|
664
|
-
ok: true;
|
|
665
|
-
decision: PolicyDecision;
|
|
666
|
-
entry: ToolCatalogEntry;
|
|
667
|
-
} | {
|
|
668
|
-
ok: false;
|
|
669
|
-
error: ProjectionError;
|
|
670
|
-
};
|
|
671
|
-
/**
|
|
672
|
-
* Resolve a machine-requested tool call against the server catalog and
|
|
673
|
-
* decide its policy.
|
|
674
|
-
*
|
|
675
|
-
* Returns the decision together with the matching catalog entry (the entry
|
|
676
|
-
* is what elicitation copy must be built from — S6), or a structured
|
|
677
|
-
* `validate`-phase {@link ProjectionError} when the machine names a tool the
|
|
678
|
-
* wrapped server does not declare.
|
|
679
|
-
*/
|
|
680
|
-
export declare function resolveCallPolicy(call: PendingToolCall, catalog: ServerCatalog, overrides?: PolicyOverrides): ResolvedCallPolicy;
|
|
681
|
-
/**
|
|
682
|
-
* Canonical TOOL_DENIED reason for calls blocked by a `deny` policy
|
|
683
|
-
* decision. A `deny` decision never produces an elicitation — the machine
|
|
684
|
-
* host injects `TOOL_DENIED` with this reason instead.
|
|
685
|
-
*/
|
|
686
|
-
export declare const POLICY_DENIED_REASON = "denied by server policy";
|
|
687
|
-
/**
|
|
688
|
-
* Build the TOOL_DENIED reason string for a policy-denied call. With a tool
|
|
689
|
-
* name it names the denied tool so the machine (and audit trail) can tell
|
|
690
|
-
* which call was blocked.
|
|
691
|
-
*/
|
|
692
|
-
export declare function policyDeniedReason(toolName?: string): string;
|
|
693
|
-
//#endregion
|
|
694
|
-
//#region src/policy/elicitation.d.ts
|
|
695
|
-
/** Structured approval copy for one gated call (input to the MRTR adapter). */
|
|
696
|
-
interface ElicitationCopy {
|
|
697
|
-
/** Short action title, e.g. `Approve: delete file`. */
|
|
698
|
-
title: string;
|
|
699
|
-
/** Full human-readable summary (plain text with an indented JSON block). */
|
|
700
|
-
message: string;
|
|
701
|
-
/** Tool name, taken from the catalog entry. */
|
|
702
|
-
toolName: string;
|
|
703
|
-
/** Machine state path that requested the call (raw; render with care). */
|
|
704
|
-
stateName: string;
|
|
705
|
-
/** The redacted arguments as rendered in `message` (JSON text, possibly truncated). */
|
|
706
|
-
args: string;
|
|
707
|
-
/** True when the catalog entry carries `destructiveHint: true`. */
|
|
708
|
-
destructive: boolean;
|
|
709
|
-
}
|
|
710
|
-
interface ElicitationCopyOptions {
|
|
711
|
-
/** Redaction hook applied to args before any rendering (R16). */
|
|
712
|
-
redact?: RedactionHook;
|
|
713
|
-
/**
|
|
714
|
-
* Who asked: a machine state (default) or a direct `call_tool` request
|
|
715
|
-
* (R35). Selects the footer; nothing else differs.
|
|
716
|
-
*/
|
|
717
|
-
origin?: 'machine' | 'direct';
|
|
718
|
-
/**
|
|
719
|
-
* Backstop cap on the whole rendered args JSON, in characters. Default
|
|
720
|
-
* 4096. Each string leaf is independently capped at 512 characters first.
|
|
721
|
-
*/
|
|
722
|
-
maxArgsChars?: number;
|
|
723
|
-
}
|
|
724
|
-
/**
|
|
725
|
-
* Build the approval summary for a gated call.
|
|
726
|
-
*
|
|
727
|
-
* `entry` MUST be the catalog entry the call was resolved against (use
|
|
728
|
-
* `resolveCallPolicy`); the copy takes the tool name, title and description
|
|
729
|
-
* from it, never from the pending call or its arguments.
|
|
730
|
-
*/
|
|
731
|
-
export declare function buildElicitationCopy(call: PendingToolCall, entry: ToolCatalogEntry, options?: ElicitationCopyOptions): ElicitationCopy;
|
|
732
|
-
//#endregion
|
|
733
|
-
//#region src/text-safety.d.ts
|
|
734
|
-
/**
|
|
735
|
-
* Rendering untrusted strings inertly (S5, S6).
|
|
736
|
-
*
|
|
737
|
-
* Tool names, descriptions, the wrapped server's identity, and machine state
|
|
738
|
-
* names all flow from a possibly third-party server or a model-authored
|
|
739
|
-
* machine. Wherever they are rendered into human-facing prompt copy or into
|
|
740
|
-
* generated source, they must not be able to forge structure with control
|
|
741
|
-
* characters (newlines, ESC for ANSI sequences) or reorder text with Unicode
|
|
742
|
-
* bidi overrides. Centralizing the character class here keeps every call site
|
|
743
|
-
* on the same, complete definition — an earlier divergence let generated
|
|
744
|
-
* `.d.ts`/bridge comments miss the directional-isolate range that the
|
|
745
|
-
* elicitation copy already stripped.
|
|
746
|
-
*/
|
|
747
|
-
/**
|
|
748
|
-
* Replace control and bidi characters with the replacement character.
|
|
749
|
-
* `allowNewline` keeps `\n` (for multi-line blocks whose formatting newlines
|
|
750
|
-
* are part of the rendering, e.g. a pretty-printed JSON block).
|
|
751
|
-
*/
|
|
752
|
-
export declare function neutralizeControlChars(text: string, options?: {
|
|
753
|
-
allowNewline?: boolean;
|
|
754
|
-
}): string;
|
|
755
|
-
/** Truncate to `maxChars`, appending an ellipsis when shortened. */
|
|
756
|
-
export declare function capLength(text: string, maxChars: number): string;
|
|
757
|
-
/**
|
|
758
|
-
* The one-line human purpose: the first sentence of the server's
|
|
759
|
-
* description, neutralized and capped. Runs of replacement characters
|
|
760
|
-
* (neutralized control bytes) are collapsed so a hostile or malformed
|
|
761
|
-
* description does not render as a wall of `�`. Undefined when the server
|
|
762
|
-
* gave no usable description.
|
|
763
|
-
*
|
|
764
|
-
* One rule, three consumers — the approval copy (R16), the `discover_apis`
|
|
765
|
-
* overview and `search_tools` (R33) — so every projection describes a tool
|
|
766
|
-
* identically.
|
|
767
|
-
*/
|
|
768
|
-
export declare function purposeSentence(description: string | undefined): string | undefined;
|
|
769
|
-
//#endregion
|
|
770
|
-
//#region src/typegen/generate.d.ts
|
|
1299
|
+
export declare function validateArgsShape(toolName: string, args: Record<string, unknown>, schema: Record<string, unknown>, caller?: 'call_tool' | 'toolCall'): void;
|
|
771
1300
|
/**
|
|
772
1301
|
* Module specifier authored machines import the tools surface from (R4).
|
|
773
1302
|
* The sandbox component mounts {@link GeneratedToolTypes.bridgeSource} here.
|
|
@@ -803,40 +1332,45 @@ interface GeneratedToolTypes {
|
|
|
803
1332
|
/** Compact per-tool overview block for inline embedding by discover_apis (R3). */
|
|
804
1333
|
overview: string;
|
|
805
1334
|
/**
|
|
806
|
-
* Per-tool slice of `dts`, keyed by tool name (R34)
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
* the `
|
|
1335
|
+
* Per-tool slice of `dts`, keyed by tool name (R34) — the qualified
|
|
1336
|
+
* `<alias>.<name>` on a multi-server view (R45): the alias declarations
|
|
1337
|
+
* and the `tools.<name>` (`tools.<alias>.<name>`) member signature exactly
|
|
1338
|
+
* as they appear in the full file, so `describe_tool` output type-checks
|
|
1339
|
+
* against the `projection://types/tools.d.ts` resource.
|
|
810
1340
|
*/
|
|
811
1341
|
toolTypes: Map<string, ToolTypeText>;
|
|
812
1342
|
}
|
|
813
1343
|
/** The generated types for one tool, as served by `describe_tool` (R34). */
|
|
814
1344
|
interface ToolTypeText {
|
|
815
|
-
/**
|
|
1345
|
+
/**
|
|
1346
|
+
* `<Pascal>Input` alias name, unique within the `.d.ts` —
|
|
1347
|
+
* `<alias>_<Pascal>Input` on a multi-server view (R45).
|
|
1348
|
+
*/
|
|
816
1349
|
inputAlias: string;
|
|
817
1350
|
/** `<Pascal>Output` alias name, when the tool declares an `outputSchema`. */
|
|
818
1351
|
outputAlias?: string;
|
|
819
1352
|
/**
|
|
820
1353
|
* The alias declarations verbatim from the `.d.ts`, then the member
|
|
821
1354
|
* signature verbatim modulo indentation: the `tools` member is re-indented
|
|
822
|
-
* to column 0 under a `// Member of the \`tools\` surface:` line
|
|
1355
|
+
* to column 0 under a `// Member of the \`tools\` surface:` line
|
|
1356
|
+
* (`// Member of tools.<alias>:` on a multi-server view, R45).
|
|
823
1357
|
*/
|
|
824
1358
|
dts: string;
|
|
825
1359
|
}
|
|
826
1360
|
/**
|
|
827
1361
|
* Generate the `@astral-mcp/tools` artifacts for a wrapped server.
|
|
828
1362
|
*
|
|
829
|
-
* Accepts
|
|
830
|
-
*
|
|
831
|
-
*
|
|
1363
|
+
* Accepts the full aliased {@link ToolCatalog} (exactly one server: R30's
|
|
1364
|
+
* single-instance instantiation), a {@link ServerCatalog} directly, or the
|
|
1365
|
+
* multi-server view of a `wrapMcpServers` facade, which gets one
|
|
1366
|
+
* `tools.<alias>` object type per box and qualified names throughout (R45).
|
|
832
1367
|
*/
|
|
833
1368
|
export declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog, options?: GenerateToolTypesOptions): GeneratedToolTypes;
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
//#region src/typegen/json-schema.d.ts
|
|
1369
|
+
/**
|
|
1370
|
+
* The policy options (R14, R49) decide the posture wording in the doc
|
|
1371
|
+
* comments and the overview.
|
|
1372
|
+
*/
|
|
1373
|
+
type GenerateToolTypesOptions = PolicyOptions;
|
|
840
1374
|
/**
|
|
841
1375
|
* Convert a JSON Schema into TypeScript type text.
|
|
842
1376
|
*
|
|
@@ -850,8 +1384,6 @@ export declare function schemaToTsType(schema: unknown, indent?: string): string
|
|
|
850
1384
|
* decide whether the generated tool function's `args` parameter is optional.
|
|
851
1385
|
*/
|
|
852
1386
|
export declare function schemaRequiresArgs(schema: unknown): boolean;
|
|
853
|
-
//#endregion
|
|
854
|
-
//#region src/catalog/describe.d.ts
|
|
855
1387
|
interface DescribeToolArgs {
|
|
856
1388
|
name: string;
|
|
857
1389
|
/**
|
|
@@ -861,9 +1393,8 @@ interface DescribeToolArgs {
|
|
|
861
1393
|
*/
|
|
862
1394
|
types?: boolean;
|
|
863
1395
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
policy?: PolicyOverrides;
|
|
1396
|
+
/** The policy options (R14, R49) decide the rendered posture. */
|
|
1397
|
+
interface DescribeToolOptions extends PolicyOptions {
|
|
867
1398
|
/** Per-tool generated types from typegen (R34). */
|
|
868
1399
|
toolTypes?: ReadonlyMap<string, ToolTypeText>;
|
|
869
1400
|
}
|
|
@@ -888,8 +1419,6 @@ interface ToolDescription {
|
|
|
888
1419
|
* when the wrapped server declares no such tool (R34).
|
|
889
1420
|
*/
|
|
890
1421
|
export declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, options?: DescribeToolOptions): CallToolResult;
|
|
891
|
-
//#endregion
|
|
892
|
-
//#region src/catalog/schema-safety.d.ts
|
|
893
1422
|
/**
|
|
894
1423
|
* JSON Schema sanitizer for `describe_tool` (PRD R34, S5).
|
|
895
1424
|
*
|
|
@@ -900,8 +1429,11 @@ export declare function describeTool(catalog: ServerCatalog, args: DescribeToolA
|
|
|
900
1429
|
* model it passes through:
|
|
901
1430
|
*
|
|
902
1431
|
* 1. a key allowlist — only the keywords a model needs to build arguments
|
|
903
|
-
* survive; everything else (examples,
|
|
904
|
-
*
|
|
1432
|
+
* survive; everything else (examples, comments, vendor keys) is dropped.
|
|
1433
|
+
* `default` survives only as a scalar the model can act on (a number, a
|
|
1434
|
+
* boolean, `null`, or a short plain string — {@link safeDefault}): an
|
|
1435
|
+
* omitted argument silently takes it, and a model that cannot see a
|
|
1436
|
+
* default page size reads a page as the whole set (D29);
|
|
905
1437
|
* 2. string neutralization — every string VALUE has control and bidi
|
|
906
1438
|
* characters replaced and is length-capped. Property NAMES and
|
|
907
1439
|
* `required` entries are kept verbatim: they are the keys the model must
|
|
@@ -932,8 +1464,148 @@ interface SanitizedSchema {
|
|
|
932
1464
|
* sanitizes to `{}`.
|
|
933
1465
|
*/
|
|
934
1466
|
export declare function sanitizeSchema(schema: unknown, maxChars?: number): SanitizedSchema;
|
|
935
|
-
|
|
936
|
-
|
|
1467
|
+
/**
|
|
1468
|
+
* Compact call signatures (DECISIONS D32): a tool's top-level arguments on
|
|
1469
|
+
* one line, so a model can `call_tool` straight from a search hit or a
|
|
1470
|
+
* `validate` error instead of spending a turn on `describe_tool`.
|
|
1471
|
+
*
|
|
1472
|
+
* {owner: string, repo: string, state?: "open"|"closed", perPage?: number = 30}
|
|
1473
|
+
*
|
|
1474
|
+
* Required arguments first (in declaration order), then optional ones
|
|
1475
|
+
* (`?`), each with its type: JSON Schema types, `T[]` for arrays, the
|
|
1476
|
+
* literal members of an `enum` or `const`, and a scalar default (`= 30`)
|
|
1477
|
+
* when the sanitizer kept one (D29). Only the top level is rendered: an
|
|
1478
|
+
* object or array-of-objects argument shows as `object` / `object[]` and
|
|
1479
|
+
* the signature says `describe_tool` has the full schema; so does one cut
|
|
1480
|
+
* to fit {@link MAX_SIGNATURE_CHARS}.
|
|
1481
|
+
*
|
|
1482
|
+
* S5: built from the SANITIZED schema only ({@link sanitizeSchema}), so
|
|
1483
|
+
* every enum string and default already passed the allowlist and the
|
|
1484
|
+
* default rule. Property names stay server data: a name that is a plain
|
|
1485
|
+
* identifier prints bare, any other is JSON-quoted after neutralization,
|
|
1486
|
+
* and a name too long to print whole is left out (and the signature marked
|
|
1487
|
+
* incomplete) rather than shown cut — a cut name is a wrong key. Type words
|
|
1488
|
+
* outside the JSON Schema vocabulary print as `any`. The result is one
|
|
1489
|
+
* line: nothing a server writes can open a line of its own.
|
|
1490
|
+
*/
|
|
1491
|
+
/** A tool's compact call signature. */
|
|
1492
|
+
interface CallSignature {
|
|
1493
|
+
/** One line: `{a: string, b?: number = 1}`; `{}` for a tool that takes no arguments. */
|
|
1494
|
+
text: string;
|
|
1495
|
+
/**
|
|
1496
|
+
* True when the line shows the whole input schema's top level: no
|
|
1497
|
+
* nested object fields, no argument cut for length, no enum cut short,
|
|
1498
|
+
* no schema the sanitizer truncated. Copy otherwise says `describe_tool`
|
|
1499
|
+
* has the full schema.
|
|
1500
|
+
*/
|
|
1501
|
+
complete: boolean;
|
|
1502
|
+
/**
|
|
1503
|
+
* True when a call can be built from the line alone: the top level is a
|
|
1504
|
+
* plain object of properties and every required argument is shown with
|
|
1505
|
+
* a flat type. What is missing, if anything, is optional. Copy otherwise
|
|
1506
|
+
* says to call `describe_tool` first.
|
|
1507
|
+
*/
|
|
1508
|
+
callable: boolean;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Where optional arguments stop, in characters (required ones go on to
|
|
1512
|
+
* twice this). Sized on the eval
|
|
1513
|
+
* fixtures (D32): it keeps every argument of 93 of the 95 GitHub tools,
|
|
1514
|
+
* all 21 MongoDB and all 14 filesystem tools; the cut ones say so.
|
|
1515
|
+
*/
|
|
1516
|
+
export declare const MAX_SIGNATURE_CHARS = 480;
|
|
1517
|
+
/**
|
|
1518
|
+
* The compact signature of a tool's input schema; never throws. With
|
|
1519
|
+
* `shared` (D34), a required argument the box's shared-args line already
|
|
1520
|
+
* shows with the same type, and that has no default, is written by name
|
|
1521
|
+
* alone (`{connectionId, database, newName: string}`): copy that renders
|
|
1522
|
+
* this text must show that line on the same page. `complete` and
|
|
1523
|
+
* `callable` are unchanged.
|
|
1524
|
+
*/
|
|
1525
|
+
export declare function callSignature(inputSchema: unknown, options?: {
|
|
1526
|
+
shared?: readonly SharedArg[];
|
|
1527
|
+
}): CallSignature;
|
|
1528
|
+
/**
|
|
1529
|
+
* One argument's meaning, from its description (D33): what a signature's
|
|
1530
|
+
* `name: type` cannot say, such as where a `connectionId` comes from or
|
|
1531
|
+
* that a `sha` may also be a branch name.
|
|
1532
|
+
*/
|
|
1533
|
+
interface ArgGloss {
|
|
1534
|
+
/** The argument's name, as the schema declares it. */
|
|
1535
|
+
name: string;
|
|
1536
|
+
/** Its description on one line: whitespace collapsed, invisible characters gone, neutralized, capped. */
|
|
1537
|
+
text: string;
|
|
1538
|
+
}
|
|
1539
|
+
/**
|
|
1540
|
+
* Longest gloss on a search page, in characters, before `…`. Sized on the
|
|
1541
|
+
* eval fixtures (D33): it holds the median informative GitHub description
|
|
1542
|
+
* whole (48 characters) and keeps the instruction in the first sentences
|
|
1543
|
+
* of a longer one (Sentry's median is 124, MongoDB's `connectionId` 173);
|
|
1544
|
+
* past it are option lists and examples that `describe_tool` serves.
|
|
1545
|
+
*/
|
|
1546
|
+
export declare const MAX_GLOSS_CHARS = 120;
|
|
1547
|
+
/**
|
|
1548
|
+
* The glosses of a tool's arguments (D33): by default its required ones (a
|
|
1549
|
+
* call cannot go without them, and a model that cannot tell what one means
|
|
1550
|
+
* invents a value), or the `names` given (the arguments a rejected call
|
|
1551
|
+
* sent). An argument gets a gloss when the SANITIZED schema (S5) gives it a
|
|
1552
|
+
* description that says something its name does not: one made only of
|
|
1553
|
+
* filler words and words of the tool's or its arguments' names is skipped,
|
|
1554
|
+
* and so is an `enum` or `const` argument, whose values the signature
|
|
1555
|
+
* already lists (on the GitHub fixture its descriptions were a third of
|
|
1556
|
+
* the gloss text and said "The method to execute" or listed the options
|
|
1557
|
+
* again). Each gloss is one line cut at a word to `maxChars` (default
|
|
1558
|
+
* {@link MAX_GLOSS_CHARS}), and a tool's glosses stop at twice that.
|
|
1559
|
+
* Never throws.
|
|
1560
|
+
*/
|
|
1561
|
+
export declare function argGlosses(toolName: string, inputSchema: unknown, options?: {
|
|
1562
|
+
names?: readonly string[];
|
|
1563
|
+
maxChars?: number;
|
|
1564
|
+
}): ArgGloss[];
|
|
1565
|
+
/**
|
|
1566
|
+
* A required argument most of a box's tools share (D34): `connectionId` in
|
|
1567
|
+
* the MongoDB box, `owner` and `repo` in GitHub's.
|
|
1568
|
+
*/
|
|
1569
|
+
interface SharedArg {
|
|
1570
|
+
/** The argument's name, as the schemas declare it. */
|
|
1571
|
+
name: string;
|
|
1572
|
+
/** Its type as a signature prints it, the same in every tool counted. */
|
|
1573
|
+
type: string;
|
|
1574
|
+
/** How many of the box's tools require it with that type. */
|
|
1575
|
+
tools: number;
|
|
1576
|
+
/** Its gloss (D33), when most of those tools give it the same one. */
|
|
1577
|
+
gloss?: string;
|
|
1578
|
+
}
|
|
1579
|
+
/**
|
|
1580
|
+
* Share of a box's tools that must require an argument (same name, same
|
|
1581
|
+
* rendered type) for it to be shared (D34). Sized on the offline catalogs:
|
|
1582
|
+
* the arguments that recur are required by 58–95 % of their box's tools
|
|
1583
|
+
* (GitHub `repo` 58 %, `owner` 66 %; MongoDB `collection` 71 %, `database`
|
|
1584
|
+
* 90 %, `connectionId` 95 %; filesystem `path` 79 %; Sentry
|
|
1585
|
+
* `organizationSlug` 70 %; Slack `channel_id` 63 %), and the next ones by at
|
|
1586
|
+
* most 25 % (Notion `page_id`, Slack `thread_ts`, GitHub `method` 16 %).
|
|
1587
|
+
*/
|
|
1588
|
+
export declare const SHARED_ARG_SHARE = 0.5;
|
|
1589
|
+
/** Fewest tools an argument is shared by: two tools agreeing is not a pattern. */
|
|
1590
|
+
export declare const MIN_SHARED_ARG_TOOLS = 3;
|
|
1591
|
+
/** Most shared arguments a box's line shows (MongoDB has three). */
|
|
1592
|
+
export declare const MAX_SHARED_ARGS = 4;
|
|
1593
|
+
/**
|
|
1594
|
+
* The shared arguments of a box's tools (D34): required arguments, with a
|
|
1595
|
+
* flat type (no nested shape, no cut enum) and no default, that at least
|
|
1596
|
+
* {@link SHARED_ARG_SHARE} of `tools` (and {@link MIN_SHARED_ARG_TOOLS})
|
|
1597
|
+
* require under the same name and the same rendered type; most shared
|
|
1598
|
+
* first, then in order of first appearance, at most
|
|
1599
|
+
* {@link MAX_SHARED_ARGS}. An argument whose name differs in type across
|
|
1600
|
+
* tools counts only the tools of its most common type. Each carries the
|
|
1601
|
+
* D33 gloss that at least half of the tools counted give it, if one does.
|
|
1602
|
+
* Built from the sanitized schemas (S5). `tools` should be deduplicated by
|
|
1603
|
+
* name. Never throws.
|
|
1604
|
+
*/
|
|
1605
|
+
export declare function sharedArgs(tools: readonly {
|
|
1606
|
+
name: string;
|
|
1607
|
+
inputSchema: unknown;
|
|
1608
|
+
}[]): SharedArg[];
|
|
937
1609
|
interface SearchToolsArgs {
|
|
938
1610
|
/**
|
|
939
1611
|
* Words to match against tool names, titles and descriptions; any word
|
|
@@ -943,14 +1615,42 @@ interface SearchToolsArgs {
|
|
|
943
1615
|
/** Opaque cursor from a previous page's `nextCursor`. */
|
|
944
1616
|
cursor?: string;
|
|
945
1617
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1618
|
+
/** The policy options (R14, R49) decide the rendered posture. */
|
|
1619
|
+
interface SearchToolsOptions extends PolicyOptions {
|
|
1620
|
+
/**
|
|
1621
|
+
* Tools per page, for listings and queries alike. Default {@link DEFAULT_SEARCH_PAGE_SIZE}
|
|
1622
|
+
* for a listing (no query) and {@link DEFAULT_QUERY_PAGE_SIZE} for a
|
|
1623
|
+
* query; clamped to [1, 200].
|
|
1624
|
+
*/
|
|
950
1625
|
pageSize?: number;
|
|
951
1626
|
}
|
|
952
|
-
/** Page size — a transport bound, independent of the R37 inline threshold. */
|
|
1627
|
+
/** Page size of a listing — a transport bound, independent of the R37 inline threshold. */
|
|
953
1628
|
export declare const DEFAULT_SEARCH_PAGE_SIZE = 50;
|
|
1629
|
+
/**
|
|
1630
|
+
* Page size of a ranked (queried) search (D32). On the offline search
|
|
1631
|
+
* corpus (1,173 cases, D22) the gold tool is in the top 15 in 96.6 % of
|
|
1632
|
+
* cases and in the top 50 in 97.7 %, while every row past the 15th costs
|
|
1633
|
+
* about 30 tokens on every search that has it: "create issue" in the
|
|
1634
|
+
* GitHub box matched 25. The rest stay a cursor away.
|
|
1635
|
+
*/
|
|
1636
|
+
export declare const DEFAULT_QUERY_PAGE_SIZE = 15;
|
|
1637
|
+
/**
|
|
1638
|
+
* How many of a query's best hits carry their call signature (D32). On the
|
|
1639
|
+
* offline corpus the gold tool ranks 1st in 75.5 % of cases, top 3 in
|
|
1640
|
+
* 89.9 % (93.4 % of the queries models actually sent) and top 5 in 92.8 %:
|
|
1641
|
+
* the 4th and 5th signatures would buy 2.9 points for two more lines on
|
|
1642
|
+
* every search. A signature's optional arguments are cut at 480
|
|
1643
|
+
* characters (`MAX_SIGNATURE_CHARS`); over the GitHub tools the median is
|
|
1644
|
+
* 109 (~27 tokens), so the three typically cost ~80 tokens, against
|
|
1645
|
+
* ~800–1,400 for one `describe_tool` answer plus the turn that fetches it.
|
|
1646
|
+
*/
|
|
1647
|
+
export declare const SIGNATURE_HITS = 3;
|
|
1648
|
+
/**
|
|
1649
|
+
* How many of a query's best hits also gloss their required arguments
|
|
1650
|
+
* (D33): all of the signed ones. A page shows a gloss once; a later hit
|
|
1651
|
+
* with the same argument and the same words leaves it out.
|
|
1652
|
+
*/
|
|
1653
|
+
export declare const GLOSS_HITS = 3;
|
|
954
1654
|
/** `query` is capped (R33): its words are matched as plain substrings, never a regex. */
|
|
955
1655
|
export declare const MAX_QUERY_CHARS = 200;
|
|
956
1656
|
/** One entry of a `search_tools` page (R33). */
|
|
@@ -959,6 +1659,17 @@ interface ToolSummary {
|
|
|
959
1659
|
purpose: string;
|
|
960
1660
|
posture: ToolPosture;
|
|
961
1661
|
destructive: boolean;
|
|
1662
|
+
/**
|
|
1663
|
+
* The tool's compact call signature (D32): on the first
|
|
1664
|
+
* {@link SIGNATURE_HITS} hits of a query, unless the tool is blocked.
|
|
1665
|
+
*/
|
|
1666
|
+
signature?: CallSignature;
|
|
1667
|
+
/**
|
|
1668
|
+
* What the tool's required arguments mean, where the name alone does not
|
|
1669
|
+
* say (D33): on the first {@link GLOSS_HITS} signed hits, when any
|
|
1670
|
+
* required argument has such a description.
|
|
1671
|
+
*/
|
|
1672
|
+
glosses?: ArgGloss[];
|
|
962
1673
|
}
|
|
963
1674
|
interface SearchToolsPage {
|
|
964
1675
|
tools: ToolSummary[];
|
|
@@ -966,16 +1677,95 @@ interface SearchToolsPage {
|
|
|
966
1677
|
total: number;
|
|
967
1678
|
nextCursor?: string;
|
|
968
1679
|
}
|
|
969
|
-
/** Summarize one catalog entry (R33). */
|
|
970
|
-
export declare function summarizeTool(entry: ToolCatalogEntry, policy?: PolicyOverrides): ToolSummary;
|
|
971
1680
|
/**
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
*
|
|
1681
|
+
* Summarize one catalog entry (R33); `context` decides its posture (R14,
|
|
1682
|
+
* R49). With `withSignature`, a tool that is not blocked also carries its
|
|
1683
|
+
* call signature (D32): there is no point showing how to call a tool the
|
|
1684
|
+
* gate refuses. With `withGlosses` too, it carries its required
|
|
1685
|
+
* arguments' glosses (D33), when it has any.
|
|
1686
|
+
*/
|
|
1687
|
+
export declare function summarizeTool(entry: ToolCatalogEntry, context?: PolicyContext, withSignature?: boolean, withGlosses?: boolean): ToolSummary;
|
|
1688
|
+
/**
|
|
1689
|
+
* Run `search_tools`. Throws a `validate`-phase {@link ProjectionError} for a
|
|
1690
|
+
* malformed cursor, one issued for a different query, or one pointing past
|
|
1691
|
+
* the end of the results (R33).
|
|
1692
|
+
*/
|
|
1693
|
+
export declare function searchTools(catalog: ServerCatalog, args: SearchToolsArgs, options?: SearchToolsOptions): CallToolResult;
|
|
1694
|
+
/** One entry of a multi-server search page (R43): the R33 entry plus its box. */
|
|
1695
|
+
interface BoxedToolSummary extends ToolSummary {
|
|
1696
|
+
/** The box (alias) the tool lives in; `name` is qualified with it. */
|
|
1697
|
+
box: string;
|
|
1698
|
+
}
|
|
1699
|
+
interface BoxedSearchPage {
|
|
1700
|
+
/**
|
|
1701
|
+
* The box overview (R43): the server's full `instructions`, neutralized
|
|
1702
|
+
* and capped. Only on the first page of an empty-query `search_<alias>`
|
|
1703
|
+
* whose server sends instructions.
|
|
1704
|
+
*/
|
|
1705
|
+
overview?: string;
|
|
1706
|
+
tools: BoxedToolSummary[];
|
|
1707
|
+
/** Matching tools across all pages. */
|
|
1708
|
+
total: number;
|
|
1709
|
+
nextCursor?: string;
|
|
1710
|
+
}
|
|
1711
|
+
/** The policy options (R14, R49) are decided at the gate and on resume. */
|
|
1712
|
+
interface RunSegmentOptions extends PolicyOptions {
|
|
1713
|
+
/** Submitted module source (R2). */
|
|
1714
|
+
code: string;
|
|
1715
|
+
lang?: 'ts' | 'js';
|
|
1716
|
+
/** Machine input, passed as the machine's `input` on a fresh run. */
|
|
1717
|
+
input?: unknown;
|
|
1718
|
+
/** Catalog of the wrapped server whose tools the machine may call. */
|
|
1719
|
+
catalog: ServerCatalog;
|
|
1720
|
+
/** Generated `@astral-mcp/tools` bridge source (from typegen). */
|
|
1721
|
+
bridgeSource: string;
|
|
1722
|
+
/** Executes approved/auto tool calls against the wrapped server. */
|
|
1723
|
+
executor: ToolExecutor;
|
|
1724
|
+
/** Resource caps; clamped at the hard maxima (R12). */
|
|
1725
|
+
caps?: Partial<ResourceCaps>;
|
|
1726
|
+
/** Zero-based MRTR segment index — stamps logs (R31). */
|
|
1727
|
+
segment?: number;
|
|
1728
|
+
/**
|
|
1729
|
+
* Resume state. Present on every segment after the first: the verified
|
|
1730
|
+
* snapshot and the pending calls sealed alongside it, plus the verdicts
|
|
1731
|
+
* the client returned for those calls.
|
|
1732
|
+
*/
|
|
1733
|
+
resume?: {
|
|
1734
|
+
snapshot: unknown;
|
|
1735
|
+
/** Gated calls as sealed into the blob — the approved name+args (S2). */
|
|
1736
|
+
pending: Record<string, PendingToolCall>;
|
|
1737
|
+
/** Verdict per pending key; a missing entry counts as declined (R17). */
|
|
1738
|
+
verdicts: Record<string, ApprovalVerdict>;
|
|
1739
|
+
/** Audit entries accumulated in earlier segments. */
|
|
1740
|
+
audit?: AuditEntry[];
|
|
1741
|
+
/** Round-trip counter carried in the blob (R18). */
|
|
1742
|
+
trip: number;
|
|
1743
|
+
/**
|
|
1744
|
+
* Calls held on their server's question (G1), keyed by call id, with
|
|
1745
|
+
* the server's answers to send back, keyed like the hold's questions'
|
|
1746
|
+
* own keys. Re-issued as recorded; no verdict applies to them.
|
|
1747
|
+
*/
|
|
1748
|
+
held?: Record<string, {
|
|
1749
|
+
hold: UpstreamHold;
|
|
1750
|
+
responses: Record<string, unknown>;
|
|
1751
|
+
}>;
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Run one execution segment to completion, suspension, or error.
|
|
1756
|
+
*
|
|
1757
|
+
* A segment is one MRTR round trip: fresh sandbox, fresh wall-clock budget,
|
|
1758
|
+
* fresh logs. On the first segment the module is validated (R24) before the
|
|
1759
|
+
* actor starts; on a resume the module is re-evaluated from the re-sent code
|
|
1760
|
+
* and the actor is restored from the sealed snapshot.
|
|
1761
|
+
*
|
|
1762
|
+
* Note: `runSegment` interpolates `input` into driver source without a size
|
|
1763
|
+
* check of its own — `executeMachine` enforces `maxInputBytes` before calling
|
|
1764
|
+
* it. Callers invoking `runSegment` directly must bound `input` themselves.
|
|
975
1765
|
*/
|
|
976
|
-
export declare function
|
|
977
|
-
|
|
978
|
-
|
|
1766
|
+
export declare function runSegment(options: RunSegmentOptions): Promise<SegmentOutcome>;
|
|
1767
|
+
/** The single-server authoring contract (R3). */
|
|
1768
|
+
export declare const AUTHORING_CONTRACT: string;
|
|
979
1769
|
/** Stable URIs the server serves via `resources/read` (R3). */
|
|
980
1770
|
export declare const RESOURCE_URIS: {
|
|
981
1771
|
readonly toolTypes: "projection://types/tools.d.ts";
|
|
@@ -993,10 +1783,16 @@ interface ProjectionResource {
|
|
|
993
1783
|
/** Resource-level metadata (e.g. the MCP Apps `ui` object). */
|
|
994
1784
|
_meta?: Record<string, unknown>;
|
|
995
1785
|
}
|
|
996
|
-
export declare const EXAMPLE_HAPPY_PATH
|
|
997
|
-
export declare const EXAMPLE_DENIAL_FALLBACK
|
|
998
|
-
/**
|
|
999
|
-
|
|
1786
|
+
export declare const EXAMPLE_HAPPY_PATH: string;
|
|
1787
|
+
export declare const EXAMPLE_DENIAL_FALLBACK: string;
|
|
1788
|
+
/**
|
|
1789
|
+
* Build the code-mode resources Projection serves for a wrapped server (R3).
|
|
1790
|
+
* On a multi-server view the descriptions name the boxes and the contract
|
|
1791
|
+
* and the examples call qualified names from its first box, picked under
|
|
1792
|
+
* `policy` (R45, {@link toolSamples}); the box overview resources are the
|
|
1793
|
+
* multi-server facade's own (`catalog/box-guidance.ts`).
|
|
1794
|
+
*/
|
|
1795
|
+
export declare function buildResources(catalog: ServerCatalog, dts: string, policy?: PolicyOptions, maxRoundTrips?: number): ProjectionResource[];
|
|
1000
1796
|
/** R37: above this many tools, `discover_apis` defers the per-tool list to the catalog tools. */
|
|
1001
1797
|
export declare const DEFAULT_INLINE_TOOL_THRESHOLD = 40;
|
|
1002
1798
|
interface DiscoverApisOptions {
|
|
@@ -1008,6 +1804,32 @@ interface DiscoverApisOptions {
|
|
|
1008
1804
|
* clamped at 0, anything else falls back to the default.
|
|
1009
1805
|
*/
|
|
1010
1806
|
inlineThreshold?: number;
|
|
1807
|
+
/**
|
|
1808
|
+
* How the R37 pointer names the search tools a multi-server facade serves
|
|
1809
|
+
* (R43, R45): `searchToolsPhrase` of the view. Default `search_tools`.
|
|
1810
|
+
*/
|
|
1811
|
+
searchPhrase?: string;
|
|
1812
|
+
/**
|
|
1813
|
+
* Direct listing (`projections.direct`): the full sentence(s) telling the
|
|
1814
|
+
* model where tools are, replacing the R37 pointer's "Call <search tools>
|
|
1815
|
+
* to find tools …" — the served search tools and the boxes listed
|
|
1816
|
+
* directly, so the pointer never names a search tool the facade does not
|
|
1817
|
+
* serve.
|
|
1818
|
+
*/
|
|
1819
|
+
findTools?: string;
|
|
1820
|
+
/**
|
|
1821
|
+
* The resources the facade serves, when they go beyond
|
|
1822
|
+
* {@link buildResources} (a multi-server facade's box overviews, R45):
|
|
1823
|
+
* every one but the `ui://` dashboard is linked.
|
|
1824
|
+
*/
|
|
1825
|
+
resources?: readonly ProjectionResource[];
|
|
1826
|
+
/**
|
|
1827
|
+
* The facade's policy (R14, R49), which picks the sample tool names on a
|
|
1828
|
+
* multi-server view (R45, {@link toolSamples}).
|
|
1829
|
+
*/
|
|
1830
|
+
policy?: PolicyOptions;
|
|
1831
|
+
/** The facade's clamped `caps.maxRoundTrips` (R12), quoted by the contract's rule 5. */
|
|
1832
|
+
maxRoundTrips?: number;
|
|
1011
1833
|
}
|
|
1012
1834
|
/**
|
|
1013
1835
|
* The `discover_apis` result: an inline overview sufficient to author a
|
|
@@ -1022,6 +1844,17 @@ export declare function discoverApis(catalog: ServerCatalog, overview: string, o
|
|
|
1022
1844
|
interface ProjectionSelection {
|
|
1023
1845
|
codeMode: boolean;
|
|
1024
1846
|
catalog: boolean;
|
|
1847
|
+
/**
|
|
1848
|
+
* Whether the wrapped tools are also listed directly, as mediated aliases
|
|
1849
|
+
* of `call_tool` (`projections.direct`, resolved at wrap time). Absent
|
|
1850
|
+
* means not listed.
|
|
1851
|
+
*/
|
|
1852
|
+
direct?: boolean;
|
|
1853
|
+
/**
|
|
1854
|
+
* Multi-server facade under direct listing: the boxes listed directly, in
|
|
1855
|
+
* sorted alias order (the others keep the catalog path).
|
|
1856
|
+
*/
|
|
1857
|
+
directBoxes?: readonly string[];
|
|
1025
1858
|
}
|
|
1026
1859
|
/**
|
|
1027
1860
|
* The wrapped server's `instructions`, followed by the preamble for the
|
|
@@ -1030,15 +1863,13 @@ interface ProjectionSelection {
|
|
|
1030
1863
|
*/
|
|
1031
1864
|
export declare function buildInstructions(catalog: ServerCatalog, preamble?: string, projections?: ProjectionSelection): string;
|
|
1032
1865
|
/** Preamble when only code mode is on (R29). */
|
|
1033
|
-
export declare const DEFAULT_CODE_MODE_PREAMBLE = "This server runs in code mode: instead of calling its tools directly, author an XState v5 state machine and run it with execute_machine, which orchestrates the tool calls for you.
|
|
1866
|
+
export declare const DEFAULT_CODE_MODE_PREAMBLE = "This server runs in code mode: instead of calling its tools directly, author an XState v5 state machine and run it with execute_machine, which orchestrates the tool calls for you. Tools that change something may pause the machine and ask the user to approve that specific action before it runs; each tool's listing says whether it runs immediately or asks for approval. Call discover_apis first for the tool types, the authoring contract, and worked examples.";
|
|
1034
1867
|
/** Preamble when only the catalog projection is on (R29, R32). */
|
|
1035
|
-
export declare const DEFAULT_CATALOG_PREAMBLE = "This server exposes its tools through a catalog: call search_tools to find a tool,
|
|
1868
|
+
export declare const DEFAULT_CATALOG_PREAMBLE = "This server exposes its tools through a catalog: call search_tools to find a tool, then call_tool to run it, with the args its search hit shows (describe_tool gives any other tool's). Tools that change something may pause and ask the user to approve that specific action before it runs; each tool's listing says whether it runs immediately or asks for approval.";
|
|
1036
1869
|
/** Preamble when both projections are on: the routing rule (R29). */
|
|
1037
|
-
export declare const DEFAULT_ROUTING_PREAMBLE = "This server offers two ways to use its tools. For a single action, call search_tools to find the tool,
|
|
1870
|
+
export declare const DEFAULT_ROUTING_PREAMBLE = "This server offers two ways to use its tools. For a single action, call search_tools to find the tool, then call_tool to run it, with the args its search hit shows (describe_tool gives any other tool's). For several steps, branching on results, computing over a list (counting, filtering, summing, comparing or checking every item), or a workflow worth saving, author an XState v5 state machine and run it with execute_machine (call discover_apis first for the tool types, the authoring contract, and worked examples). Either way, tools that change something may pause and ask the user to approve that specific action before it runs; each tool's listing says whether it runs immediately or asks for approval.";
|
|
1038
1871
|
/** The R29 preamble variant for a projection selection. */
|
|
1039
1872
|
export declare function defaultPreamble(projections: ProjectionSelection): string;
|
|
1040
|
-
//#endregion
|
|
1041
|
-
//#region src/wrap.d.ts
|
|
1042
1873
|
interface WrapOptions {
|
|
1043
1874
|
/** Blob sealing configuration; required for any gated flow (R22). */
|
|
1044
1875
|
blob: BlobSealerConfig;
|
|
@@ -1049,8 +1880,38 @@ interface WrapOptions {
|
|
|
1049
1880
|
* keeps its own frozen copy, so later changes to this object are not seen.
|
|
1050
1881
|
*/
|
|
1051
1882
|
policy?: PolicyOverrides;
|
|
1052
|
-
/**
|
|
1883
|
+
/**
|
|
1884
|
+
* Approvals mode (R49). `'every-write'` (default) asks before every tool
|
|
1885
|
+
* that is not read-only. `'destructive-only'` also runs, without approval,
|
|
1886
|
+
* a non-read-only tool whose annotations declare `destructiveHint: false`
|
|
1887
|
+
* exactly; a missing or any other `destructiveHint` still asks. A per-tool
|
|
1888
|
+
* `policy` override wins over the mode. This extends the trust placed in
|
|
1889
|
+
* `readOnlyHint` to `destructiveHint: false` — a hostile server can
|
|
1890
|
+
* mislabel a damaging tool, so enable it only for servers you trust.
|
|
1891
|
+
* Read once, at wrap time; any other value fails the wrap (`validate`).
|
|
1892
|
+
*/
|
|
1893
|
+
approvals?: ApprovalsMode;
|
|
1894
|
+
/**
|
|
1895
|
+
* Masks sensitive argument fields before they reach an approval prompt
|
|
1896
|
+
* (R16). It receives the tool name and a copy of the arguments. On a
|
|
1897
|
+
* `wrapMcpServers` facade the name is the qualified `<alias>.<tool>`
|
|
1898
|
+
* (`fs.write_file`), so one hook can tell the boxes' same-named tools
|
|
1899
|
+
* apart; on `wrapMcpServer` it is the server's own tool name.
|
|
1900
|
+
*/
|
|
1053
1901
|
redact?: RedactionHook;
|
|
1902
|
+
/**
|
|
1903
|
+
* How a machine suspension holding several gated calls is put to the
|
|
1904
|
+
* person (R15 amendment; DECISIONS D25). `'per-call'` (default) sends one
|
|
1905
|
+
* elicitation per call. `'batched'` sends one elicitation listing every
|
|
1906
|
+
* call in its full approval copy, with one unticked boolean per call (no
|
|
1907
|
+
* default): each call still runs only if its own box comes back ticked,
|
|
1908
|
+
* on the arguments sealed for it (S2). A client that answers the form
|
|
1909
|
+
* without the booleans declines every call. The layout is sealed in the
|
|
1910
|
+
* blob; a batch is capped at 10 calls and 20 000 characters, falling back
|
|
1911
|
+
* to per-call prompts above that. Read once, at wrap time; any other
|
|
1912
|
+
* value fails the wrap (`validate`).
|
|
1913
|
+
*/
|
|
1914
|
+
approvalPrompts?: ApprovalPrompts;
|
|
1054
1915
|
/**
|
|
1055
1916
|
* Replaces the whole R29 preamble, whichever projections are on (the
|
|
1056
1917
|
* code-mode, catalog or routing copy).
|
|
@@ -1088,12 +1949,68 @@ interface WrapOptions {
|
|
|
1088
1949
|
* disabling both is an error at wrap time.
|
|
1089
1950
|
*/
|
|
1090
1951
|
projections?: ProjectionsOptions;
|
|
1952
|
+
/**
|
|
1953
|
+
* Where upstream tokens for sources given by URL live (R59). Default: the
|
|
1954
|
+
* OS credential store through the optional `@napi-rs/keyring` dependency,
|
|
1955
|
+
* falling back to memory (with one stderr warning) when it is absent or
|
|
1956
|
+
* the store is unavailable. A horizontally scaled deployment supplies a
|
|
1957
|
+
* shared store (R27). Unused by pre-built `McpServer` / `Client` sources.
|
|
1958
|
+
*/
|
|
1959
|
+
tokenStore?: TokenStore;
|
|
1960
|
+
/**
|
|
1961
|
+
* Whether the wrapped server's own questions reach the human (G1,
|
|
1962
|
+
* DECISIONS D24). `'refuse'` (default): Projection declares no
|
|
1963
|
+
* elicitation capability to the server, and a tool that asks anyway fails
|
|
1964
|
+
* with a result saying it needs the user's input. `'forward'`: the
|
|
1965
|
+
* capability is declared, and each question — a form, or a link to open —
|
|
1966
|
+
* is relayed to the human over MRTR in Projection's own prompt, headed
|
|
1967
|
+
* `Question from <box> (not an approval)` with the server's words quoted,
|
|
1968
|
+
* after the call itself passed the approval gate. Enable it only for
|
|
1969
|
+
* servers you trust to ask your users things: a hostile server can still
|
|
1970
|
+
* word a phishing question. On `wrapMcpServers` this is the default for
|
|
1971
|
+
* every box, and a source configuration can override it per box.
|
|
1972
|
+
* Read once, at wrap time; any other value fails the wrap (`validate`).
|
|
1973
|
+
*/
|
|
1974
|
+
upstreamElicitation?: UpstreamElicitationMode;
|
|
1975
|
+
/**
|
|
1976
|
+
* Bound, in milliseconds, on each wrapped source's connect and `tools/list`
|
|
1977
|
+
* introspection at wrap time (R47). A source that has not finished in time
|
|
1978
|
+
* fails the wrap with a `protocol` error, and every connection Projection
|
|
1979
|
+
* opened is closed (a `Client` you passed in stays open). Default 30 000.
|
|
1980
|
+
*/
|
|
1981
|
+
introspectTimeoutMs?: number;
|
|
1091
1982
|
}
|
|
1092
1983
|
/** Per-projection on/off switches (R1, R32). */
|
|
1093
1984
|
interface ProjectionsOptions {
|
|
1094
1985
|
codeMode?: boolean;
|
|
1095
1986
|
catalog?: boolean;
|
|
1987
|
+
/**
|
|
1988
|
+
* Direct listing (see `facade/direct.ts`): also register one
|
|
1989
|
+
* **mediated** facade tool per wrapped tool, a thin alias of
|
|
1990
|
+
* `call_tool({ name, args })` under exactly the same gate, so the model
|
|
1991
|
+
* sees every tool's name, description and schema without searching.
|
|
1992
|
+
* `true` always lists them; `'auto'` lists them when their estimated
|
|
1993
|
+
* definitions fit `directBudgetTokens` (on a multi-server facade box by
|
|
1994
|
+
* box, smallest box first; the other boxes keep the catalog path);
|
|
1995
|
+
* `false` never. Default `'auto'` (DECISIONS D28). Needs the
|
|
1996
|
+
* catalog projection: `true` with `catalog: false` fails the wrap
|
|
1997
|
+
* (`validate`); `'auto'` then stays off.
|
|
1998
|
+
*/
|
|
1999
|
+
direct?: 'auto' | boolean;
|
|
2000
|
+
/**
|
|
2001
|
+
* `direct: 'auto'`: the largest estimated size, in tokens, of all mediated
|
|
2002
|
+
* tool definitions (name, description, input schema; characters / 3.5)
|
|
2003
|
+
* that is still listed directly. Default {@link DEFAULT_DIRECT_BUDGET_TOKENS}.
|
|
2004
|
+
*/
|
|
2005
|
+
directBudgetTokens?: number;
|
|
1096
2006
|
}
|
|
2007
|
+
/**
|
|
2008
|
+
* Default `projections.directBudgetTokens`: about 8k estimated tokens of
|
|
2009
|
+
* tool definitions. Measured (explore/adaptive-surface.md): listing wins
|
|
2010
|
+
* clearly at 2k and 5.4k estimated tokens and breaks even on Sonnet 5.5 at
|
|
2011
|
+
* about 11k, whose tokenizer counts ~1.3–1.4× the estimate.
|
|
2012
|
+
*/
|
|
2013
|
+
export declare const DEFAULT_DIRECT_BUDGET_TOKENS = 8000;
|
|
1097
2014
|
/** Options for registering the projections on a server the caller built. */
|
|
1098
2015
|
interface RegisterOptions {
|
|
1099
2016
|
/**
|
|
@@ -1121,9 +2038,11 @@ interface ProjectionFacade {
|
|
|
1121
2038
|
/**
|
|
1122
2039
|
* Run `execute_machine` for one `tools/call`, first attempt or retry.
|
|
1123
2040
|
* `clientCanElicit` reports whether the connected client declared the
|
|
1124
|
-
* `elicitation` capability (legacy compat only; omit when unknown)
|
|
2041
|
+
* `elicitation` capability (legacy compat only; omit when unknown), and
|
|
2042
|
+
* `clientUrlElicitation` whether it declared URL mode (a forwarded
|
|
2043
|
+
* server link, G1, is otherwise shown as text in a form).
|
|
1125
2044
|
*/
|
|
1126
|
-
executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
|
|
2045
|
+
executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra, clientCanElicit?: boolean, clientUrlElicitation?: boolean): Promise<ToolCallResult>;
|
|
1127
2046
|
/** Run `discover_apis`. */
|
|
1128
2047
|
discoverApis(): CallToolResult;
|
|
1129
2048
|
/** Run `search_tools` (R33). Failures come back as R25 structured error results. */
|
|
@@ -1138,25 +2057,219 @@ interface ProjectionFacade {
|
|
|
1138
2057
|
}): CallToolResult;
|
|
1139
2058
|
/**
|
|
1140
2059
|
* Run `call_tool` (R35) for one `tools/call`, first attempt or retry.
|
|
1141
|
-
* `era`
|
|
2060
|
+
* `era`, `clientCanElicit` and `clientUrlElicitation` as for `executeMachine`.
|
|
1142
2061
|
*/
|
|
1143
|
-
callTool(args: CallToolArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
|
|
2062
|
+
callTool(args: CallToolArgs, era?: ProtocolEra, clientCanElicit?: boolean, clientUrlElicitation?: boolean): Promise<ToolCallResult>;
|
|
1144
2063
|
/** The projections this facade serves (R32). Frozen: fixed at wrap time. */
|
|
1145
2064
|
readonly projections: Readonly<ProjectionSelection>;
|
|
1146
2065
|
/** Build an `McpServer` carrying the instructions, the enabled projections' tools and the resources. */
|
|
1147
2066
|
createServer(ctx?: RegisterOptions): McpServer;
|
|
1148
2067
|
/** Register the enabled projections' tools and the resources on an `McpServer` the caller built. */
|
|
1149
2068
|
registerOn(server: McpServer, options?: RegisterOptions): void;
|
|
2069
|
+
/**
|
|
2070
|
+
* Delete the stored upstream tokens (R59) of the sources given by URL:
|
|
2071
|
+
* on a single-server facade, its source (call it without an alias); on a
|
|
2072
|
+
* multi-server facade, the box named by `alias`, or every such box
|
|
2073
|
+
* without one. No revocation yet. A facade or box not built from an
|
|
2074
|
+
* `UpstreamSpec`, or an unknown alias, is a `validate` error.
|
|
2075
|
+
*/
|
|
2076
|
+
signOut(alias?: string): Promise<void>;
|
|
1150
2077
|
/** Close the connection to the wrapped server. */
|
|
1151
2078
|
close(): Promise<void>;
|
|
1152
2079
|
}
|
|
1153
2080
|
/**
|
|
1154
|
-
* Wrap an MCP server
|
|
1155
|
-
* Projection
|
|
2081
|
+
* Wrap an MCP server — an in-process instance, a connected client, or a
|
|
2082
|
+
* remote server given by URL (`{ url, oauth? }`, R51) — into a Projection
|
|
2083
|
+
* facade.
|
|
2084
|
+
*/
|
|
2085
|
+
export declare function wrapMcpServer(source: McpServer | Client | UpstreamSpec, options: WrapOptions): Promise<ProjectionFacade>;
|
|
2086
|
+
/** Separator between a box alias and its tool in a mapped name (`fs__read_file`). */
|
|
2087
|
+
export declare const DIRECT_BOX_SEPARATOR = "__";
|
|
2088
|
+
/** One mediated tool, as listed. */
|
|
2089
|
+
interface DirectTool {
|
|
2090
|
+
/** The facade tool name, `[A-Za-z0-9_-]{1,64}`. */
|
|
2091
|
+
name: string;
|
|
2092
|
+
/** The catalog name `call_tool` is called with (qualified on a multi-server facade). */
|
|
2093
|
+
target: string;
|
|
2094
|
+
/** The box the tool lives in (multi-server facade only). */
|
|
2095
|
+
alias?: string;
|
|
2096
|
+
title?: string;
|
|
2097
|
+
description: string;
|
|
2098
|
+
/** The R34-sanitized input schema, always `type: "object"` at the root. */
|
|
2099
|
+
inputSchema: Record<string, unknown>;
|
|
2100
|
+
/** What the listing advertises: Projection's own, see {@link advertisedAnnotations}. */
|
|
2101
|
+
annotations: ToolAnnotations;
|
|
2102
|
+
posture: ToolPosture;
|
|
2103
|
+
}
|
|
2104
|
+
/** What {@link planDirectTools} needs besides the catalog. */
|
|
2105
|
+
interface DirectPlanOptions extends PolicyOptions {
|
|
2106
|
+
/** Names the facade already serves (Projection's own tools): never shadowed. */
|
|
2107
|
+
reserved: ReadonlySet<string>;
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* The mediated tools of a catalog, in catalog order. On a multi-server view
|
|
2111
|
+
* each is named `<alias>__<tool>`; on a single-server facade, the tool's own
|
|
2112
|
+
* name when it is legal and free. A name that is not legal is mapped
|
|
2113
|
+
* (illegal characters become `_`, an over-long name is cut and hashed); a
|
|
2114
|
+
* name that collides with a reserved one or an earlier mapped one gets
|
|
2115
|
+
* `_<8 hex of the catalog name>`. A tool whose name still collides is not
|
|
2116
|
+
* listed — it stays reachable through `call_tool`.
|
|
2117
|
+
*/
|
|
2118
|
+
export declare function planDirectTools(catalog: ServerCatalog, options: DirectPlanOptions): DirectTool[];
|
|
2119
|
+
/** Map `raw` onto the host name rule, deterministically. */
|
|
2120
|
+
export declare function directToolName(raw: string): string;
|
|
2121
|
+
/**
|
|
2122
|
+
* Estimated tokens of the listed definitions: name + title + description +
|
|
2123
|
+
* input schema JSON, at {@link CHARS_PER_TOKEN} characters per token.
|
|
2124
|
+
*/
|
|
2125
|
+
export declare function estimateDirectTokens(tools: readonly DirectTool[]): number;
|
|
2126
|
+
/** A wrapped-server source: an in-process instance or a connected client. */
|
|
2127
|
+
type ServerSource = McpServer | Client;
|
|
2128
|
+
/**
|
|
2129
|
+
* Aliased server sources (R30): one per alias. An {@link UpstreamConnector}
|
|
2130
|
+
* is a source given by URL (R51), validated by the facade: the introspector
|
|
2131
|
+
* opens it, owns the resulting client, and records its `url:` identity
|
|
2132
|
+
* (R60).
|
|
2133
|
+
*/
|
|
2134
|
+
type IntrospectorSources = Record<ServerAlias, ServerSource | UpstreamConnector>;
|
|
2135
|
+
/** How {@link Introspector.create} connects. */
|
|
2136
|
+
interface IntrospectOptions {
|
|
2137
|
+
/**
|
|
2138
|
+
* Bound on each source's connect and introspection (R47). A source that
|
|
2139
|
+
* has not finished in time fails the whole introspection with a
|
|
2140
|
+
* `protocol` error. Default {@link DEFAULT_INTROSPECT_TIMEOUT_MS}.
|
|
2141
|
+
*/
|
|
2142
|
+
timeoutMs?: number;
|
|
2143
|
+
/** How the timeout error names a source. Default: `wrapped server "<alias>"`. */
|
|
2144
|
+
describe?: (alias: ServerAlias) => string;
|
|
2145
|
+
/**
|
|
2146
|
+
* The aliases whose boxes forward their server's questions to the human
|
|
2147
|
+
* (`upstreamElicitation: 'forward'`, G1). Their owned clients declare the
|
|
2148
|
+
* elicitation capability; every other box declares none.
|
|
2149
|
+
*/
|
|
2150
|
+
forwardElicitation?: ReadonlySet<ServerAlias>;
|
|
2151
|
+
}
|
|
2152
|
+
/**
|
|
2153
|
+
* Holds one MCP client per wrapped-server alias, exposes the aggregated
|
|
2154
|
+
* {@link ToolCatalog}, and executes tool calls (implements
|
|
2155
|
+
* {@link ToolExecutor}). Construct with {@link createIntrospector}.
|
|
2156
|
+
*/
|
|
2157
|
+
export declare class Introspector implements ToolExecutor {
|
|
2158
|
+
#private;
|
|
2159
|
+
private constructor();
|
|
2160
|
+
static create(sources: IntrospectorSources, options?: IntrospectOptions): Promise<Introspector>;
|
|
2161
|
+
/** The aggregated tool catalog, keyed by server alias (R30). */
|
|
2162
|
+
get catalog(): ToolCatalog;
|
|
2163
|
+
/** Re-read `tools/list` from every wrapped server and rebuild the catalog. */
|
|
2164
|
+
refresh(): Promise<ToolCatalog>;
|
|
2165
|
+
/**
|
|
2166
|
+
* Execute a tool call against the wrapped server identified by `alias`,
|
|
2167
|
+
* mapping the MCP result to a {@link ToolCallOutcome}:
|
|
2168
|
+
*
|
|
2169
|
+
* - success → `{ ok: true, result }` where `result` is the tool's
|
|
2170
|
+
* `structuredContent` when present, else a single text block's text,
|
|
2171
|
+
* else the raw content array.
|
|
2172
|
+
* - `isError` result or transport/protocol failure → `{ ok: false, error }`.
|
|
2173
|
+
*
|
|
2174
|
+
* An unknown alias is a wiring bug, not a tool failure, and throws.
|
|
2175
|
+
*
|
|
2176
|
+
* `options` are host-side: `signal` cancels the upstream request, and
|
|
2177
|
+
* `onProgress` asks the upstream for progress and receives each report
|
|
2178
|
+
* (redacted first for a source given by URL, S1).
|
|
2179
|
+
*/
|
|
2180
|
+
callTool(alias: ServerAlias, name: string, args: Record<string, unknown>, options?: UpstreamCallOptions): Promise<ToolCallOutcome>;
|
|
2181
|
+
/**
|
|
2182
|
+
* Close connections the introspector created (in-process sources).
|
|
2183
|
+
* Caller-provided remote clients are left untouched.
|
|
2184
|
+
*/
|
|
2185
|
+
close(): Promise<void>;
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* Connect to the given wrapped servers and build their tool catalog.
|
|
2189
|
+
*
|
|
2190
|
+
* v1 usage: `createIntrospector({ [DEFAULT_SERVER_ALIAS]: serverOrClient })`.
|
|
2191
|
+
*/
|
|
2192
|
+
export declare function createIntrospector(sources: IntrospectorSources, options?: IntrospectOptions): Promise<Introspector>;
|
|
2193
|
+
/** One source with its per-box settings (R39). */
|
|
2194
|
+
interface ServerSourceConfig {
|
|
2195
|
+
/**
|
|
2196
|
+
* The server to wrap: an in-process `McpServer`, a connected `Client`, or
|
|
2197
|
+
* a remote server given by URL (`{ url, oauth? }`, R51).
|
|
2198
|
+
*/
|
|
2199
|
+
source: McpServer | Client | UpstreamSpec;
|
|
2200
|
+
/**
|
|
2201
|
+
* Host-trusted text naming the box's purpose, shown to the model on the
|
|
2202
|
+
* shelf (R44). Neutralized (control and bidi characters) and capped at 200
|
|
2203
|
+
* characters.
|
|
2204
|
+
*/
|
|
2205
|
+
description?: string;
|
|
2206
|
+
/**
|
|
2207
|
+
* Approvals mode for this box (R49); takes precedence over
|
|
2208
|
+
* `WrapServersOptions.approvals`. A per-tool `policy` override still wins.
|
|
2209
|
+
*/
|
|
2210
|
+
approvals?: ApprovalsMode;
|
|
2211
|
+
/**
|
|
2212
|
+
* Whether this box's server may ask the human questions (G1, DECISIONS
|
|
2213
|
+
* D24); takes precedence over `WrapServersOptions.upstreamElicitation`.
|
|
2214
|
+
* Default `'refuse'`.
|
|
2215
|
+
*/
|
|
2216
|
+
upstreamElicitation?: UpstreamElicitationMode;
|
|
2217
|
+
/**
|
|
2218
|
+
* R47 (skip this source instead of failing the wrap) is not supported yet:
|
|
2219
|
+
* `true` fails the wrap with a `validate` error. Wrapping is fail-closed.
|
|
2220
|
+
*/
|
|
2221
|
+
optional?: boolean;
|
|
2222
|
+
}
|
|
2223
|
+
/** Options of a multi-server facade (R39). */
|
|
2224
|
+
interface WrapServersOptions extends WrapOptions {
|
|
2225
|
+
catalog?: {
|
|
2226
|
+
/**
|
|
2227
|
+
* Serve the global `search_tools` across every box next to the per-box
|
|
2228
|
+
* `search_<alias>` tools (R43). Default `true`; `false` leaves only the
|
|
2229
|
+
* per-box tools, and every piece of copy that names a search tool names
|
|
2230
|
+
* those instead.
|
|
2231
|
+
*/
|
|
2232
|
+
globalSearch?: boolean;
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
/**
|
|
2236
|
+
* A multi-server facade (R39): every {@link ProjectionFacade} member, with
|
|
2237
|
+
* `catalog` the alias-keyed catalog of the wrapped boxes (bare names),
|
|
2238
|
+
* `skipped` the optional sources that were skipped (R47), `searchTools`
|
|
2239
|
+
* taking an optional alias, and `describeTool` / `callTool` taking
|
|
2240
|
+
* qualified names.
|
|
2241
|
+
*/
|
|
2242
|
+
interface MultiProjectionFacade extends Omit<ProjectionFacade, 'catalog' | 'searchTools'> {
|
|
2243
|
+
/** The wrapped boxes' catalogs, keyed by alias, with the servers' own tool names (R30). */
|
|
2244
|
+
readonly catalog: ToolCatalog;
|
|
2245
|
+
/**
|
|
2246
|
+
* Optional sources skipped at wrap time (R47). Always empty for now:
|
|
2247
|
+
* optional sources are not supported yet, so every source is wrapped or
|
|
2248
|
+
* the wrap fails.
|
|
2249
|
+
*/
|
|
2250
|
+
readonly skipped: readonly {
|
|
2251
|
+
alias: string;
|
|
2252
|
+
error: StructuredError;
|
|
2253
|
+
}[];
|
|
2254
|
+
/**
|
|
2255
|
+
* Run `search_<alias>` when `alias` is given, `search_tools` otherwise
|
|
2256
|
+
* (R43). A search tool the facade does not serve — an unknown alias, or
|
|
2257
|
+
* `search_tools` under `catalog.globalSearch: false` — comes back as an
|
|
2258
|
+
* R25 `validate` error result.
|
|
2259
|
+
*/
|
|
2260
|
+
searchTools(args: {
|
|
2261
|
+
query?: string;
|
|
2262
|
+
cursor?: string;
|
|
2263
|
+
}, alias?: string): CallToolResult;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Wrap several MCP servers (in-process instances, connected clients, or
|
|
2267
|
+
* `{ source, … }` configurations) into one Projection facade, each under
|
|
2268
|
+
* its alias (R39). Configuration mistakes are `validate` errors raised
|
|
2269
|
+
* before anything connects; a source that fails to connect or introspect
|
|
2270
|
+
* fails the wrap and closes every connection Projection opened (R47).
|
|
1156
2271
|
*/
|
|
1157
|
-
export declare function
|
|
1158
|
-
//#endregion
|
|
1159
|
-
//#region src/apps/dashboard.d.ts
|
|
2272
|
+
export declare function wrapMcpServers(sources: Record<string, McpServer | Client | UpstreamSpec | ServerSourceConfig>, options: WrapServersOptions): Promise<MultiProjectionFacade>;
|
|
1160
2273
|
/**
|
|
1161
2274
|
* MCP Apps workflow dashboard (PRD §12.1, second fenced use case).
|
|
1162
2275
|
*
|
|
@@ -1189,8 +2302,6 @@ export declare const DASHBOARD_RESOURCE_URI = "ui://projection/dashboard.html";
|
|
|
1189
2302
|
/** MIME type the MCP Apps spec mandates for app HTML resources. */
|
|
1190
2303
|
export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
|
|
1191
2304
|
export declare const DASHBOARD_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<title>Projection workflow dashboard</title>\n<style>\n :root {\n color-scheme: light dark;\n --bg: #ffffff; --fg: #1a1a2e; --muted: #6b7280; --line: #e5e7eb;\n --ok: #047857; --ok-bg: #ecfdf5; --err: #b91c1c; --err-bg: #fef2f2;\n --chip: #eef2ff; --mono: ui-monospace, SFMono-Regular, Menlo, monospace;\n }\n @media (prefers-color-scheme: dark) {\n :root {\n --bg: #111318; --fg: #e5e7eb; --muted: #9ca3af; --line: #2a2f3a;\n --ok: #34d399; --ok-bg: #06281f; --err: #f87171; --err-bg: #2d1215;\n --chip: #1e2433;\n }\n }\n html, body { margin: 0; padding: 0; background: var(--bg); color: var(--fg);\n font: 14px/1.5 system-ui, sans-serif; }\n main { padding: 16px; max-width: 720px; }\n h1 { font-size: 15px; margin: 0 0 12px; display: flex; gap: 8px; align-items: center; }\n .chip { font-size: 12px; padding: 2px 10px; border-radius: 999px; background: var(--chip); }\n .chip.ok { color: var(--ok); background: var(--ok-bg); }\n .chip.err { color: var(--err); background: var(--err-bg); }\n section { margin: 14px 0; }\n section > h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em;\n color: var(--muted); margin: 0 0 6px; }\n pre { margin: 0; padding: 10px; background: var(--chip); border-radius: 8px;\n font-family: var(--mono); font-size: 12px; overflow-x: auto; white-space: pre-wrap; }\n table { border-collapse: collapse; width: 100%; font-size: 13px; }\n th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line);\n vertical-align: top; }\n th { color: var(--muted); font-weight: 500; }\n td.args { font-family: var(--mono); font-size: 12px; }\n .log { font-family: var(--mono); font-size: 12px; padding: 2px 0; }\n .log .level { color: var(--muted); margin-right: 6px; }\n .hint { color: var(--muted); font-size: 13px; margin-top: 6px; }\n .hidden { display: none; }\n #waiting { color: var(--muted); }\n</style>\n</head>\n<body>\n<main>\n <h1>Projection workflow <span id=\"status\" class=\"chip\">waiting…</span></h1>\n <p id=\"waiting\">Waiting for the workflow result from the host…</p>\n <section id=\"output-section\" class=\"hidden\"><h2>Output</h2><pre id=\"output\"></pre></section>\n <section id=\"error-section\" class=\"hidden\"><h2>Error</h2>\n <pre id=\"error\"></pre><p id=\"error-hint\" class=\"hint\"></p></section>\n <section id=\"approvals-section\" class=\"hidden\"><h2>Approval audit trail</h2>\n <table><thead><tr><th>Tool</th><th>Decision</th><th>Arguments</th></tr></thead>\n <tbody id=\"approvals\"></tbody></table></section>\n <section id=\"logs-section\" class=\"hidden\"><h2>Console</h2><div id=\"logs\"></div></section>\n</main>\n<script>\n'use strict';\n(function () {\n var nextId = 1;\n var pending = new Map();\n\n function post(message) { window.parent.postMessage(message, '*'); }\n function request(method, params) {\n return new Promise(function (resolve, reject) {\n var id = nextId++;\n pending.set(id, { resolve: resolve, reject: reject });\n post({ jsonrpc: '2.0', id: id, method: method, params: params });\n });\n }\n function notify(method, params) {\n var message = { jsonrpc: '2.0', method: method };\n if (params !== undefined) message.params = params;\n post(message);\n }\n\n function byId(id) { return document.getElementById(id); }\n function show(id) { byId(id).classList.remove('hidden'); }\n function setStatus(text, cls) {\n var chip = byId('status');\n chip.textContent = text;\n chip.className = 'chip' + (cls ? ' ' + cls : '');\n byId('waiting').classList.add('hidden');\n }\n function pretty(value) {\n try { return JSON.stringify(value, null, 2); } catch (e) { return String(value); }\n }\n\n // All model-controlled data enters the DOM via textContent only.\n function renderResult(result) {\n var structured = (result && result.structuredContent) || {};\n if (result && result.isError) {\n setStatus('failed: ' + String(structured.phase || 'error'), 'err');\n byId('error').textContent =\n String(structured.message || 'unknown error');\n byId('error-hint').textContent = String(structured.hint || '');\n show('error-section');\n } else {\n setStatus('completed', 'ok');\n byId('output').textContent = pretty(\n structured.output === undefined ? null : structured.output,\n );\n show('output-section');\n }\n var approvals = Array.isArray(structured.approvals) ? structured.approvals : [];\n if (approvals.length > 0) {\n var tbody = byId('approvals');\n approvals.forEach(function (entry) {\n var row = document.createElement('tr');\n var tool = document.createElement('td');\n tool.textContent = String(entry.tool || '');\n var decision = document.createElement('td');\n decision.textContent = String(entry.decision || '');\n var args = document.createElement('td');\n args.className = 'args';\n args.textContent = pretty(entry.args || {});\n row.appendChild(tool); row.appendChild(decision); row.appendChild(args);\n tbody.appendChild(row);\n });\n show('approvals-section');\n }\n var logs = Array.isArray(structured.logs) ? structured.logs : [];\n if (logs.length > 0) {\n var list = byId('logs');\n logs.forEach(function (entry) {\n var line = document.createElement('div');\n line.className = 'log';\n var level = document.createElement('span');\n level.className = 'level';\n level.textContent = '[' + String(entry.level || 'log') + ']';\n line.appendChild(level);\n line.appendChild(document.createTextNode(String(entry.message || '')));\n list.appendChild(line);\n });\n show('logs-section');\n }\n }\n\n window.addEventListener('message', function (event) {\n // Only the embedding host may speak: forged tool-results from a hostile\n // embedder could otherwise spoof the displayed audit trail.\n if (event.source !== window.parent) return;\n var message = event.data;\n if (!message || message.jsonrpc !== '2.0') return;\n if (message.id !== undefined && message.method === undefined) {\n var waiter = pending.get(message.id);\n if (waiter) {\n pending.delete(message.id);\n if (message.error) waiter.reject(message.error);\n else waiter.resolve(message.result);\n }\n return;\n }\n if (message.method === 'ui/notifications/tool-input') {\n setStatus('running…');\n return;\n }\n if (message.method === 'ui/notifications/tool-result') {\n renderResult(message.params);\n return;\n }\n if (message.method === 'ui/notifications/tool-cancelled') {\n var reason = message.params && message.params.reason;\n setStatus('cancelled', 'err');\n byId('error').textContent = String(reason || 'cancelled by the host');\n show('error-section');\n return;\n }\n // Host -> app REQUESTS (id + method) get an empty result: answers ping\n // and ui/resource-teardown so hosts don't stall on their SHOULD-wait.\n if (message.id !== undefined && message.method !== undefined) {\n post({ jsonrpc: '2.0', id: message.id, result: {} });\n }\n });\n\n // Handshake: initialize, then signal readiness. The host MUST NOT push\n // tool data before ui/notifications/initialized.\n request('ui/initialize', { appCapabilities: {} }).then(function () {\n notify('ui/notifications/initialized');\n });\n})();\n</script>\n</body>\n</html>\n";
|
|
1192
|
-
//#endregion
|
|
1193
|
-
//#region src/prompts/workflow-prompts.d.ts
|
|
1194
2305
|
interface WorkflowPromptDefinition {
|
|
1195
2306
|
name: string;
|
|
1196
2307
|
title: string;
|
|
@@ -1205,55 +2316,6 @@ interface WorkflowPromptDefinition {
|
|
|
1205
2316
|
export declare const SAVE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
|
|
1206
2317
|
export declare const VISUALIZE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
|
|
1207
2318
|
export declare const WORKFLOW_PROMPTS: readonly WorkflowPromptDefinition[];
|
|
1208
|
-
//#endregion
|
|
1209
|
-
//#region src/introspector/introspector.d.ts
|
|
1210
|
-
/** A wrapped-server source: an in-process instance or a connected client. */
|
|
1211
|
-
type ServerSource = McpServer | Client;
|
|
1212
|
-
/**
|
|
1213
|
-
* Aliased server sources (R30). v1 instantiates exactly one entry, but the
|
|
1214
|
-
* interface accepts a set so the multi-server stretch goal (PRD §12.2) needs
|
|
1215
|
-
* no signature break.
|
|
1216
|
-
*/
|
|
1217
|
-
type IntrospectorSources = Record<ServerAlias, ServerSource>;
|
|
1218
|
-
/**
|
|
1219
|
-
* Holds one MCP client per wrapped-server alias, exposes the aggregated
|
|
1220
|
-
* {@link ToolCatalog}, and executes tool calls (implements
|
|
1221
|
-
* {@link ToolExecutor}). Construct with {@link createIntrospector}.
|
|
1222
|
-
*/
|
|
1223
|
-
export declare class Introspector implements ToolExecutor {
|
|
1224
|
-
#private;
|
|
1225
|
-
private constructor();
|
|
1226
|
-
static create(sources: IntrospectorSources): Promise<Introspector>;
|
|
1227
|
-
/** The aggregated tool catalog, keyed by server alias (R30). */
|
|
1228
|
-
get catalog(): ToolCatalog;
|
|
1229
|
-
/** Re-read `tools/list` from every wrapped server and rebuild the catalog. */
|
|
1230
|
-
refresh(): Promise<ToolCatalog>;
|
|
1231
|
-
/**
|
|
1232
|
-
* Execute a tool call against the wrapped server identified by `alias`,
|
|
1233
|
-
* mapping the MCP result to a {@link ToolCallOutcome}:
|
|
1234
|
-
*
|
|
1235
|
-
* - success → `{ ok: true, result }` where `result` is the tool's
|
|
1236
|
-
* `structuredContent` when present, else a single text block's text,
|
|
1237
|
-
* else the raw content array.
|
|
1238
|
-
* - `isError` result or transport/protocol failure → `{ ok: false, error }`.
|
|
1239
|
-
*
|
|
1240
|
-
* An unknown alias is a wiring bug, not a tool failure, and throws.
|
|
1241
|
-
*/
|
|
1242
|
-
callTool(alias: ServerAlias, name: string, args: Record<string, unknown>): Promise<ToolCallOutcome>;
|
|
1243
|
-
/**
|
|
1244
|
-
* Close connections the introspector created (in-process sources).
|
|
1245
|
-
* Caller-provided remote clients are left untouched.
|
|
1246
|
-
*/
|
|
1247
|
-
close(): Promise<void>;
|
|
1248
|
-
}
|
|
1249
|
-
/**
|
|
1250
|
-
* Connect to the given wrapped servers and build their tool catalog.
|
|
1251
|
-
*
|
|
1252
|
-
* v1 usage: `createIntrospector({ [DEFAULT_SERVER_ALIAS]: serverOrClient })`.
|
|
1253
|
-
*/
|
|
1254
|
-
export declare function createIntrospector(sources: IntrospectorSources): Promise<Introspector>;
|
|
1255
|
-
//#endregion
|
|
1256
|
-
//#region src/sandbox/session.d.ts
|
|
1257
2319
|
/** A tool call surfaced from the `@astral-mcp/tools` bridge inside the VM. */
|
|
1258
2320
|
interface SandboxToolCall {
|
|
1259
2321
|
name: string;
|
|
@@ -1315,63 +2377,5 @@ interface SandboxSession {
|
|
|
1315
2377
|
* See ./index.ts for the full boundary contract.
|
|
1316
2378
|
*/
|
|
1317
2379
|
export declare function createSandboxSession(options: SandboxSessionOptions): Promise<SandboxSession>;
|
|
1318
|
-
|
|
1319
|
-
//#region src/machine-host/host.d.ts
|
|
1320
|
-
interface RunSegmentOptions {
|
|
1321
|
-
/** Submitted module source (R2). */
|
|
1322
|
-
code: string;
|
|
1323
|
-
lang?: 'ts' | 'js';
|
|
1324
|
-
/** Machine input, passed as the machine's `input` on a fresh run. */
|
|
1325
|
-
input?: unknown;
|
|
1326
|
-
/** Catalog of the wrapped server whose tools the machine may call. */
|
|
1327
|
-
catalog: ServerCatalog;
|
|
1328
|
-
/** Generated `@astral-mcp/tools` bridge source (from typegen). */
|
|
1329
|
-
bridgeSource: string;
|
|
1330
|
-
/** Executes approved/auto tool calls against the wrapped server. */
|
|
1331
|
-
executor: ToolExecutor;
|
|
1332
|
-
/** Resource caps; clamped at the hard maxima (R12). */
|
|
1333
|
-
caps?: Partial<ResourceCaps>;
|
|
1334
|
-
/** Per-tool policy overrides (R14). */
|
|
1335
|
-
policy?: PolicyOverrides;
|
|
1336
|
-
/** Zero-based MRTR segment index — stamps logs (R31). */
|
|
1337
|
-
segment?: number;
|
|
1338
|
-
/**
|
|
1339
|
-
* Resume state. Present on every segment after the first: the verified
|
|
1340
|
-
* snapshot and the pending calls sealed alongside it, plus the verdicts
|
|
1341
|
-
* the client returned for those calls.
|
|
1342
|
-
*/
|
|
1343
|
-
resume?: {
|
|
1344
|
-
snapshot: unknown;
|
|
1345
|
-
/** Gated calls as sealed into the blob — the approved name+args (S2). */
|
|
1346
|
-
pending: Record<string, PendingToolCall>;
|
|
1347
|
-
/** Verdict per pending key; a missing entry counts as declined (R17). */
|
|
1348
|
-
verdicts: Record<string, ApprovalVerdict>;
|
|
1349
|
-
/** Audit entries accumulated in earlier segments. */
|
|
1350
|
-
audit?: AuditEntry[];
|
|
1351
|
-
/** Round-trip counter carried in the blob (R18). */
|
|
1352
|
-
trip: number;
|
|
1353
|
-
};
|
|
1354
|
-
}
|
|
1355
|
-
/**
|
|
1356
|
-
* Run one execution segment to completion, suspension, or error.
|
|
1357
|
-
*
|
|
1358
|
-
* A segment is one MRTR round trip: fresh sandbox, fresh wall-clock budget,
|
|
1359
|
-
* fresh logs. On the first segment the module is validated (R24) before the
|
|
1360
|
-
* actor starts; on a resume the module is re-evaluated from the re-sent code
|
|
1361
|
-
* and the actor is restored from the sealed snapshot.
|
|
1362
|
-
*
|
|
1363
|
-
* Note: `runSegment` interpolates `input` into driver source without a size
|
|
1364
|
-
* check of its own — `executeMachine` enforces `maxInputBytes` before calling
|
|
1365
|
-
* it. Callers invoking `runSegment` directly must bound `input` themselves.
|
|
1366
|
-
*/
|
|
1367
|
-
export declare function runSegment(options: RunSegmentOptions): Promise<SegmentOutcome>;
|
|
1368
|
-
//#endregion
|
|
1369
|
-
//#region src/machine-host/contract.d.ts
|
|
1370
|
-
/**
|
|
1371
|
-
* Human-readable authoring contract. `discover_apis` publishes this inline
|
|
1372
|
-
* (R3) so a model can author a valid machine without fetching resources.
|
|
1373
|
-
*/
|
|
1374
|
-
export declare const AUTHORING_CONTRACT = "Projection authoring contract (execute_machine)\n\nWrite ONE module whose only top-level statements are imports and a single\ndefault-exported XState v5 machine. The module is re-evaluated on every\napproval round trip, so top-level side effects are rejected.\n\n import { setup } from 'xstate';\n import { toolCall } from '@astral-mcp/tools';\n\n export default setup({}).createMachine({ /* ... */ });\n\nRules:\n1. Default-export a machine built with XState v5 setup(...).createMachine(...).\n XState v4 Machine(...) is rejected.\n2. Machine context must be JSON-serializable at all times. No Map, Set, Date,\n class instances, functions, undefined, bigint or symbols in context.\n3. Request tools with the toolCall action. NEVER wrap a tool call in\n fromPromise/invoke — restored snapshots restart invoked actors, which\n would re-execute an approved write. Machines using invoke are rejected.\n4. After a toolCall, wait in a state for one of these events:\n TOOL_RESULT { callId, result } the call succeeded\n TOOL_ERROR { callId, error } the call failed\n TOOL_DENIED { callId, reason } the user declined, or policy denies it\n Denial is not an abort: handle TOOL_DENIED and continue or finish.\n IMPORTANT: if your machine has more than one call outstanding at the same\n time (parallel regions), give each toolCall an explicit id and guard every\n waiting state with whenCall(id) — events reach every region that can\n handle them, so an unguarded region will consume another call's result:\n\n import { toolCall, whenCall } from '@astral-mcp/tools';\n entry: toolCall('write_file', () => ({ path: '/a' }), { id: 'a' }),\n on: { TOOL_RESULT: { guard: whenCall('a'), target: 'done' } },\n5. Read-only tools run immediately. Every other tool pauses the machine for\n user approval; the arguments captured at that moment are the arguments\n that execute.\n6. Only 'xstate' and '@astral-mcp/tools' resolve as imports. There is no\n filesystem, no network, no fetch. All working state lives in context.\n7. Finish by reaching a final state. Use the machine's output for the result.\n8. TOOL_RESULT.result is the tool's structuredContent when the server returns\n one (see its Output type in tools.d.ts); otherwise the text of its single\n text block, null for an empty result, or the raw content array for\n anything else. A tool that returns isError arrives as TOOL_ERROR. Tools\n without an Output type are typed unknown — usually a string.\n9. Tool inputs such as paths, ids and names are server-specific. Do not guess\n them: take them from the machine's input, or discover them first with a\n read-only call (a list/search tool) and keep what you learn in context.\n\nExample — happy path with a read-only call (run with input: { path }):\n\n import { setup, assign } from 'xstate';\n import { toolCall } from '@astral-mcp/tools';\n\n export default setup({}).createMachine({\n id: 'readOne',\n context: ({ input }) => ({ path: input.path, result: null }),\n initial: 'reading',\n states: {\n reading: {\n entry: toolCall('read_file', ({ context }) => ({ path: context.path })),\n on: {\n TOOL_RESULT: { target: 'done', actions: assign({ result: ({ event }) => event.result }) },\n TOOL_ERROR: 'failed',\n },\n },\n done: { type: 'final' },\n failed: { type: 'final' },\n },\n output: ({ context }) => ({ result: context.result }),\n });\n\nExample — handling a denied approval (run with input: { path }):\n\n import { setup, assign } from 'xstate';\n import { toolCall } from '@astral-mcp/tools';\n\n export default setup({}).createMachine({\n id: 'writeOne',\n context: ({ input }) => ({ path: input.path, status: 'pending' }),\n initial: 'writing',\n states: {\n writing: {\n entry: toolCall('write_file', ({ context }) => ({ path: context.path, content: 'hello' })),\n on: {\n TOOL_RESULT: { target: 'done', actions: assign({ status: 'written' }) },\n TOOL_DENIED: { target: 'done', actions: assign({ status: 'skipped' }) },\n TOOL_ERROR: { target: 'done', actions: assign({ status: 'failed' }) },\n },\n },\n done: { type: 'final' },\n },\n output: ({ context }) => ({ status: context.status }),\n });\n";
|
|
1375
|
-
//#endregion
|
|
1376
|
-
export { type ApprovalGateDeps, type CallToolArgs, type CallToolDeps, type CallToolOutcome, type CallToolResult, type ContentBlock, type DescribeToolArgs, type DescribeToolOptions, type DiscoverApisOptions, type ElicitationCopy, type ElicitationCopyOptions, type ExecuteMachineArgs, type ExecuteMachineDeps, type GeneratedToolTypes, type InputRequest, type InputRequests, type InputRequiredResult, type InputResponse, type InputResponseView, type InputResponses, type OpenBlobOptions, type ProjectionFacade, type ProjectionResource, type ProjectionSelection, type ProjectionsOptions, type ProtocolEra, type RegisterOptions, type ResolvedCallPolicy, type RetryEnvelope, type RunSegmentOptions, type SandboxSession, type SandboxSessionOptions, type SandboxToolCall, type SandboxToolCallHandler, type SanitizedSchema, type SearchToolsArgs, type SearchToolsOptions, type SearchToolsPage, type ToolCallResult, type ToolDescription, type ToolPosture, type ToolSummary, type ToolTypeText, type WorkflowPromptDefinition, type WrapOptions, acceptedContent, inputRequired, inputResponse, isInputRequiredResult };
|
|
2380
|
+
export { type ApprovalGateDeps, type ApprovalPrompts, type ArgGloss, type BlobKeyDomain, type BoxedSearchPage, type BoxedToolSummary, type CallSignature, type CallToolArgs, type CallToolDeps, type CallToolOutcome, type CallToolResult, type ContentBlock, type DescribeToolArgs, type DescribeToolOptions, type DirectTool, type DiscoverApisOptions, type ElicitationCopy, type ElicitationCopyOptions, type ExecuteMachineArgs, type ExecuteMachineDeps, type GeneratedToolTypes, type InputRequest, type InputRequests, type InputRequiredResult, type InputResponse, type InputResponseView, type InputResponses, type MultiProjectionFacade, type OpenBlobOptions, type ProjectionFacade, type ProjectionResource, type ProjectionSelection, type ProjectionsOptions, type ProtocolEra, type RegisterOptions, type ResolvedCallPolicy, type RetryEnvelope, type RunSegmentOptions, type SandboxSession, type SandboxSessionOptions, type SandboxToolCall, type SandboxToolCallHandler, type SanitizedSchema, type SearchToolsArgs, type SearchToolsOptions, type SearchToolsPage, type ServerSourceConfig, type SharedArg, type TokenStore, type ToolCallResult, type ToolDescription, type ToolPosture, type ToolSummary, type ToolTypeText, type UpstreamOAuthOptions, type UpstreamSpec, type WorkflowPromptDefinition, type WrapOptions, type WrapServersOptions, acceptedContent, inputRequired, inputResponse, isInputRequiredResult };
|
|
1377
2381
|
//# sourceMappingURL=index.d.mts.map
|