@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/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
- import { Client } from "@modelcontextprotocol/client";
4
- //#region src/types.d.ts
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>): Promise<ToolCallOutcome>;
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' | 'terminal';
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
- /** Which tool minted a blob (R36): a blob opened by the other tool is rejected. */
163
- export type BlobKind = 'machine' | 'call';
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 and resolved args; returns args to display.
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
- //#endregion
297
- //#region src/blob/sealer.d.ts
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
- /** The tool opening the blob (R36). Default `machine`. */
350
- expectedKind?: BlobKind | undefined;
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.8.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
- * The MRTR retry fields the client re-sends alongside the original
405
- * arguments. The SDK lifts them off the params into `ctx.mcpReq`, so a
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
- * `inputResponses` is deliberately widened the same way the SDK's own
411
- * `inputResponse()` / `acceptedContent()` readers widen it: the values come
412
- * from the client and are never validated by the SDK, so a caller may hand
413
- * over whatever the wire carried.
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
- interface RetryEnvelope {
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
- * The JSON Schema an approval elicitation asks the client to collect, typed
423
- * as the SDK's restricted elicitation schema so `inputRequired.elicit()`
424
- * accepts it without a cast.
425
- *
426
- * Projection-specific: MRTR carries whatever schema the server asks for, and
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 const APPROVAL_SCHEMA: ElicitRequestFormParams['requestedSchema'];
911
+ export declare function policyContext(options?: PolicyOptions): PolicyContext;
431
912
  /**
432
- * Protocol revision that introduced MRTR.
433
- *
434
- * The SDK has no exported constant for it: `LATEST_PROTOCOL_VERSION` and
435
- * `SUPPORTED_PROTOCOL_VERSIONS` cover the *legacy* (`initialize`-negotiated)
436
- * family only — `2025-11-25` and earlier. The 2026-07-28 era is selected by
437
- * `server/discover`, and the SDK models it as the `ProtocolEra` value
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 const MRTR_PROTOCOL_REVISION = "2026-07-28";
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
- * Whether the era a request is being served on can carry MRTR (R28).
935
+ * Resolve a machine-requested tool call against the server catalog and
936
+ * decide its policy.
444
937
  *
445
- * The SDK binds an era to the *serving instance*, not to a per-request
446
- * header: `createMcpHandler` / `serveStdio` hand the era to the server
447
- * factory as `McpRequestContext.era`, and a hand-connected `McpServer` is
448
- * always `'legacy'`. So this takes the era, not a version string — there is
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 supportsMrtr(era: ProtocolEra$1 | undefined): boolean;
453
- //#endregion
454
- //#region src/mrtr/adapter.d.ts
943
+ export declare function resolveCallPolicy(call: PendingToolCall, catalog: ServerCatalog, context?: PolicyContext): ResolvedCallPolicy;
455
944
  /**
456
- * `execute_machine` arguments plus the MRTR retry envelope.
457
- *
458
- * **Security (S2):** `requestState` and `inputResponses` are the retry
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
- interface ExecuteMachineArgs extends RetryEnvelope {
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
- * What every gated entry point needs: the catalog and executor of the wrapped
475
- * server, the blob sealer, and the per-facade policy, redaction, caps,
476
- * protocol-era and replay settings. `execute_machine` adds the sandbox
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
- interface ApprovalGateDeps {
480
- catalog: ServerCatalog;
481
- executor: ToolExecutor;
482
- blob: BlobSealerConfig;
483
- caps?: Partial<ResourceCaps>;
484
- policy?: PolicyOverrides;
485
- redact?: RedactionHook;
486
- /**
487
- * Protocol era this request is being served on, for down-negotiation
488
- * (R28). The SDK hands it to the server factory as
489
- * `McpRequestContext.era`; `undefined` is treated as legacy.
490
- */
491
- protocolEra?: ProtocolEra;
492
- /**
493
- * Opt-in legacy-client compatibility (R28 amendment): when true, a
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. Full
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>): void;
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): the alias
807
- * declarations and the `tools.<name>` member signature exactly as they
808
- * appear in the full file, so `describe_tool` output type-checks against
809
- * the `projection://types/tools.d.ts` resource.
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
- /** `<Pascal>Input` alias name, unique within the `.d.ts`. */
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 either the full aliased {@link ToolCatalog} (which must contain
830
- * exactly one server in v1, per R30's single-instance instantiation) or a
831
- * {@link ServerCatalog} directly.
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
- interface GenerateToolTypesOptions {
835
- /** Per-tool policy overrides (R14): decide the posture wording in docs and overview. */
836
- policy?: PolicyOverrides | undefined;
837
- }
838
- //#endregion
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
- interface DescribeToolOptions {
865
- /** Per-tool policy overrides (R14); decides the rendered posture. */
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, defaults, comments, vendor keys)
904
- * is dropped;
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
- //#endregion
936
- //#region src/catalog/search.d.ts
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
- interface SearchToolsOptions {
947
- /** Per-tool policy overrides (R14); decides the rendered posture. */
948
- policy?: PolicyOverrides;
949
- /** Tools per page. Default {@link DEFAULT_SEARCH_PAGE_SIZE}, clamped to [1, 200]. */
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
- * Run `search_tools`. Throws a `validate`-phase {@link ProjectionError} for a
973
- * malformed cursor, one issued for a different query, or one pointing past
974
- * the end of the results (R33).
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 searchTools(catalog: ServerCatalog, args: SearchToolsArgs, options?: SearchToolsOptions): CallToolResult;
977
- //#endregion
978
- //#region src/mrtr/discover.d.ts
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 = "import { setup, assign } from 'xstate';\nimport { toolCall } from '@astral-mcp/tools';\n\n// Read a file and return the tool's result. Read-only calls run immediately,\n// so this whole machine completes in one round trip.\n//\n// Run it with input: { path } — paths are server-specific, so take them\n// from the input or from an earlier read-only listing call, never guess.\nexport default setup({}).createMachine({\n id: 'readOne',\n context: ({ input }) => ({ path: input.path, result: null, error: null }),\n initial: 'reading',\n states: {\n reading: {\n entry: toolCall('read_file', ({ context }) => ({ path: context.path })),\n on: {\n TOOL_RESULT: {\n target: 'done',\n // event.result is the tool's structuredContent when the server\n // returns one (see its Output type in tools.d.ts); otherwise the\n // text of its single text block, null when empty, or the raw\n // content array. isError results arrive as TOOL_ERROR instead.\n actions: assign({ result: ({ event }) => event.result }),\n },\n TOOL_ERROR: {\n target: 'failed',\n actions: assign({ error: ({ event }) => event.error.message }),\n },\n },\n },\n done: { type: 'final' },\n failed: { type: 'final' },\n },\n output: ({ context }) => ({ result: context.result, error: context.error }),\n});\n";
997
- export declare const EXAMPLE_DENIAL_FALLBACK = "import { setup, assign } from 'xstate';\nimport { toolCall } from '@astral-mcp/tools';\n\n// Write a file, but keep going if the user declines. TOOL_DENIED is an\n// ordinary transition — denial is never an automatic abort.\n//\n// Run it with input: { path, dir } — both are server-specific.\nexport default setup({}).createMachine({\n id: 'writeWithFallback',\n context: ({ input }) => ({ path: input.path, dir: input.dir, status: 'pending', note: null }),\n initial: 'writing',\n states: {\n writing: {\n entry: toolCall('write_file', ({ context }) => ({ path: context.path, content: 'hello' })),\n on: {\n TOOL_RESULT: {\n target: 'summarizing',\n actions: assign({ status: 'written' }),\n },\n TOOL_DENIED: {\n // The user said no: record it and carry on with the read-only\n // part of the work instead of failing the whole workflow.\n target: 'summarizing',\n actions: assign({\n status: 'skipped',\n note: ({ event }) => event.reason ?? 'declined by user',\n }),\n },\n TOOL_ERROR: {\n target: 'summarizing',\n actions: assign({ status: 'failed' }),\n },\n },\n },\n summarizing: {\n entry: toolCall('list_dir', ({ context }) => ({ path: context.dir })),\n on: {\n TOOL_RESULT: { target: 'done' },\n TOOL_ERROR: { target: 'done' },\n },\n },\n done: { type: 'final' },\n },\n output: ({ context }) => ({ status: context.status, note: context.note }),\n});\n";
998
- /** Build the resources Projection serves for a wrapped server (R3). */
999
- export declare function buildResources(catalog: ServerCatalog, dts: string): ProjectionResource[];
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. Any tool that is not read-only pauses the machine and asks the user to approve that specific action before it runs. Call discover_apis first for the tool types, the authoring contract, and worked examples.";
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, describe_tool to see its arguments, then call_tool to run it. Any tool that is not read-only pauses and asks the user to approve that specific action before it runs.";
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, describe_tool to see its arguments, then call_tool to run it. For several steps, branching on results, 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, any action that is not read-only pauses and asks the user to approve that specific action before it runs.";
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
- /** Masks sensitive argument fields before they reach an approval prompt (R16). */
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` / `clientCanElicit` as for `executeMachine`.
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 (an in-process instance or a connected client) into a
1155
- * Projection facade.
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 wrapMcpServer(source: McpServer | Client, options: WrapOptions): Promise<ProjectionFacade>;
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
- //#endregion
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