@astral-mcp/projection 0.7.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
  *
@@ -11,16 +146,16 @@ import { Client } from "@modelcontextprotocol/client";
11
146
  * nothing inside the package.
12
147
  */
13
148
  /** Alias identifying a wrapped server. v1 instantiates exactly one. */
14
- type ServerAlias = string;
15
- declare const DEFAULT_SERVER_ALIAS: ServerAlias;
149
+ export type ServerAlias = string;
150
+ export declare const DEFAULT_SERVER_ALIAS: ServerAlias;
16
151
  /** MCP tool annotations we care about (untrusted hints — S5). */
17
- interface ToolAnnotations {
152
+ export interface ToolAnnotations {
18
153
  readOnlyHint?: boolean;
19
154
  destructiveHint?: boolean;
20
155
  idempotentHint?: boolean;
21
156
  title?: string;
22
157
  }
23
- interface ToolCatalogEntry {
158
+ export interface ToolCatalogEntry {
24
159
  /** Tool name as declared by the wrapped server. */
25
160
  name: string;
26
161
  description?: string;
@@ -30,11 +165,11 @@ interface ToolCatalogEntry {
30
165
  outputSchema?: Record<string, unknown>;
31
166
  annotations?: ToolAnnotations;
32
167
  }
33
- interface ToolCatalog {
168
+ export interface ToolCatalog {
34
169
  /** Keyed by server alias (R30). v1 has exactly one entry. */
35
170
  servers: Map<ServerAlias, ServerCatalog>;
36
171
  }
37
- interface ServerCatalog {
172
+ export interface ServerCatalog {
38
173
  alias: ServerAlias;
39
174
  /** Stable identity string recorded into blobs (R30). */
40
175
  identity: string;
@@ -50,7 +185,7 @@ interface ServerCatalog {
50
185
  * must not be able to make the policy gate see a different entry (e.g. a
51
186
  * later `readOnlyHint: true` copy) than the one typegen exposed.
52
187
  */
53
- declare function findTool(catalog: ServerCatalog, name: string): ToolCatalogEntry | undefined;
188
+ export declare function findTool(catalog: ServerCatalog, name: string): ToolCatalogEntry | undefined;
54
189
  /**
55
190
  * Drop tools whose name repeats an earlier one; the first occurrence wins.
56
191
  *
@@ -58,30 +193,173 @@ declare function findTool(catalog: ServerCatalog, name: string): ToolCatalogEntr
58
193
  * catalog projection), so they all agree with {@link findTool} on which
59
194
  * entry a name denotes (S5).
60
195
  */
61
- declare function dedupeTools(tools: ToolCatalogEntry[]): ToolCatalogEntry[];
196
+ export declare function dedupeTools(tools: ToolCatalogEntry[]): ToolCatalogEntry[];
62
197
  /** Narrow to a non-null, non-array object. */
63
- declare function isRecord(value: unknown): value is Record<string, unknown>;
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
- interface ToolExecutor {
66
- callTool(alias: ServerAlias, name: string, args: Record<string, unknown>): Promise<ToolCallOutcome>;
255
+ export interface ToolExecutor {
256
+ callTool(alias: ServerAlias, name: string, args: Record<string, unknown>, options?: UpstreamCallOptions): Promise<ToolCallOutcome>;
67
257
  }
68
- type ToolCallOutcome = {
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
- type PolicyDecision = 'auto' | 'ask' | 'deny';
79
- interface PolicyOverrides {
303
+ export type PolicyDecision = 'auto' | 'ask' | 'deny';
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
- interface PendingToolCall {
362
+ export interface PendingToolCall {
85
363
  /** Unique id for this call within the workflow (also the inputRequest key). */
86
364
  id: string;
87
365
  name: string;
@@ -90,13 +368,23 @@ interface PendingToolCall {
90
368
  stateName: string;
91
369
  }
92
370
  /** Events the host injects into the machine. The machine never performs I/O itself. */
93
- type HostEvent = {
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,18 +392,24 @@ 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
- type ErrorPhase = 'transpile' | 'validate' | 'runtime' | 'cap_exceeded' | 'blob_verification' | 'protocol' |
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';
112
- interface Diagnostic {
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';
406
+ export interface Diagnostic {
113
407
  message: string;
114
408
  /** Optional source location within the submitted code. */
115
409
  line?: number;
116
410
  column?: number;
117
411
  }
118
- interface StructuredError {
412
+ export interface StructuredError {
119
413
  phase: ErrorPhase;
120
414
  message: string;
121
415
  diagnostics: Diagnostic[];
@@ -123,8 +417,8 @@ interface StructuredError {
123
417
  hint: string;
124
418
  logs: LogEntry[];
125
419
  }
126
- type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
127
- interface LogEntry {
420
+ export type LogLevel = 'log' | 'info' | 'warn' | 'error' | 'debug';
421
+ export interface LogEntry {
128
422
  level: LogLevel;
129
423
  message: string;
130
424
  /** Milliseconds since segment start. */
@@ -132,7 +426,7 @@ interface LogEntry {
132
426
  /** Zero-based MRTR segment index this entry was captured in. */
133
427
  segment: number;
134
428
  }
135
- interface ResourceCaps {
429
+ export interface ResourceCaps {
136
430
  /** Wall-clock per execution segment, ms. */
137
431
  segmentWallClockMs: number;
138
432
  /** WASM memory, bytes. */
@@ -154,14 +448,19 @@ interface ResourceCaps {
154
448
  /** Captured console output per segment, bytes. */
155
449
  maxConsoleBytes: number;
156
450
  }
157
- declare const DEFAULT_CAPS: ResourceCaps;
158
- declare const MAX_CAPS: ResourceCaps;
451
+ export declare const DEFAULT_CAPS: ResourceCaps;
452
+ export declare const MAX_CAPS: ResourceCaps;
159
453
  /** Clamp requested caps into [MIN_CAPS, MAX_CAPS] (R12). */
160
- declare function clampCaps(requested: Partial<ResourceCaps>): ResourceCaps;
161
- declare const BLOB_SCHEMA_VERSION = 1;
162
- /** Which tool minted a blob (R36): a blob opened by the other tool is rejected. */
163
- type BlobKind = 'machine' | 'call';
164
- interface BlobPayload {
454
+ export declare function clampCaps(requested: Partial<ResourceCaps>): ResourceCaps;
455
+ export declare const BLOB_SCHEMA_VERSION = 1;
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';
463
+ export interface BlobPayload {
165
464
  /** Blob schema version; unknown versions are rejected. */
166
465
  v: number;
167
466
  /** Minting tool (R36). */
@@ -190,8 +489,27 @@ 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,7 +517,39 @@ interface BlobPayload {
199
517
  /** Expiry, Unix ms (default TTL 15 min). */
200
518
  exp: number;
201
519
  }
202
- interface BlobSealerConfig {
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
+ }
552
+ export interface BlobSealerConfig {
203
553
  /** Current encryption key (32 bytes). */
204
554
  key: Uint8Array;
205
555
  /** Previous keys accepted for decryption during rotation (R22). */
@@ -221,7 +571,7 @@ interface BlobSealerConfig {
221
571
  * blob kinds. No default ships: an in-memory one would silently break
222
572
  * horizontal scaling (G4), so the integrator supplies a shared store.
223
573
  */
224
- interface ReplayCache {
574
+ export interface ReplayCache {
225
575
  /**
226
576
  * Record `nonce` until `exp` (Unix ms) and resolve `true` when it had
227
577
  * already been recorded — i.e. this approval is being replayed.
@@ -237,24 +587,37 @@ interface ReplayCache {
237
587
  seen(nonce: string, exp: number): Promise<boolean>;
238
588
  }
239
589
  /** Verdict delivered on retry for a pending gated call. */
240
- type ApprovalVerdict = {
590
+ export type ApprovalVerdict = {
241
591
  kind: 'approved';
242
592
  } | {
243
593
  kind: 'declined';
244
594
  reason?: string;
245
595
  };
246
596
  /** Outcome of running one execution segment. */
247
- type SegmentOutcome = {
597
+ /** Most result notes a completed segment reports (D29). */
598
+ export declare const MAX_SEGMENT_NOTES = 10;
599
+ export type SegmentOutcome = {
248
600
  status: 'completed';
249
601
  /** Final machine output/context to return to the client. */
250
602
  output: unknown;
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,22 +626,36 @@ 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
  };
267
- interface AuditEntry {
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
+ }
642
+ export interface AuditEntry {
268
643
  callId: string;
269
644
  tool: string;
270
645
  args: Record<string, unknown>;
271
646
  decision: 'auto' | 'approved' | 'declined' | 'denied_by_policy' | 'error';
272
647
  reason?: string;
273
648
  }
274
- interface RedactionHook {
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
  }
281
- interface ProjectionConfig {
658
+ export interface ProjectionConfig {
282
659
  /** Resource cap overrides, clamped at maxima (R12). */
283
660
  caps?: Partial<ResourceCaps>;
284
661
  /** Per-tool policy overrides (R14). */
@@ -293,21 +670,111 @@ 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
- declare const ENVELOPE_PREFIX = "proj1";
761
+ export declare const ENVELOPE_PREFIX = "proj1";
300
762
  /** Default blob TTL for `machine` blobs: 15 minutes (R20). */
301
- declare const DEFAULT_TTL_MS: number;
763
+ export declare const DEFAULT_TTL_MS: number;
302
764
  /**
303
765
  * Default TTL for `call` blobs: 5 minutes (R38). An approved `call` blob has
304
766
  * no forward step, so it is a standing capability until it expires (D4, D9).
305
767
  */
306
- declare const DEFAULT_CALL_TTL_MS: number;
768
+ export declare const DEFAULT_CALL_TTL_MS: number;
307
769
  /** Default sealed-blob size ceiling: 128 KB (R23). */
308
- declare const DEFAULT_MAX_BLOB_BYTES: number;
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
- declare function hashCode(code: string): string;
777
+ export declare function hashCode(code: string): string;
311
778
  interface BuildBlobPayloadInput {
312
779
  /** Minting tool (R36). Default `machine`. */
313
780
  kind?: BlobKind | undefined;
@@ -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
  }
@@ -335,19 +812,23 @@ interface BuildBlobPayloadInput {
335
812
  * Construct a fully populated {@link BlobPayload}: stamps the schema and
336
813
  * library/XState versions, hashes the code, and fills nonce/iat/exp.
337
814
  */
338
- declare function buildBlobPayload(input: BuildBlobPayloadInput): BlobPayload;
815
+ export declare function buildBlobPayload(input: BuildBlobPayloadInput): BlobPayload;
339
816
  /**
340
817
  * Seal a payload into an opaque `proj1.` envelope string (R19, R23).
341
818
  *
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
- 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
  }
@@ -374,9 +860,7 @@ interface OpenBlobOptions {
374
860
  * failed check (never key material or plaintext). A payload is only
375
861
  * returned once every check has passed.
376
862
  */
377
- declare function openBlob(blob: string, options: OpenBlobOptions): BlobPayload;
378
- //#endregion
379
- //#region src/blob/versions.d.ts
863
+ export declare function openBlob(blob: string, options: OpenBlobOptions): BlobPayload;
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,11 +873,175 @@ declare function openBlob(blob: string, options: OpenBlobOptions): BlobPayload;
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
- declare const LIB_VERSION = "0.7.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
- declare const XSTATE_VERSION = "5.32.5";
395
- //#endregion
396
- //#region src/mrtr/protocol.d.ts
878
+ export declare const XSTATE_VERSION = "5.32.5";
879
+ /**
880
+ * Decide the policy for a catalog tool (R14, R49). Precedence, strongest
881
+ * first:
882
+ *
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.
902
+ */
903
+ export declare function decidePolicy(entry: ToolCatalogEntry, context?: PolicyContext): PolicyDecision;
904
+ /**
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'`.
910
+ */
911
+ export declare function policyContext(options?: PolicyOptions): PolicyContext;
912
+ /**
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.
919
+ */
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
+ };
934
+ /**
935
+ * Resolve a machine-requested tool call against the server catalog and
936
+ * decide its policy.
937
+ *
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.
942
+ */
943
+ export declare function resolveCallPolicy(call: PendingToolCall, catalog: ServerCatalog, context?: PolicyContext): ResolvedCallPolicy;
944
+ /**
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.
948
+ */
949
+ export declare const POLICY_DENIED_REASON = "denied by server policy";
950
+ /**
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.
954
+ */
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;
397
1045
  /**
398
1046
  * What a `tools/call` handler may return on the 2026-07-28 era: a finished
399
1047
  * result, or the input-required suspension. This is the SDK's own
@@ -427,7 +1075,7 @@ interface RetryEnvelope {
427
1075
  * an approval prompt whose only payload is an optional decline reason is our
428
1076
  * product decision (R16/R17), not a protocol shape.
429
1077
  */
430
- declare const APPROVAL_SCHEMA: ElicitRequestFormParams['requestedSchema'];
1078
+ export declare const APPROVAL_SCHEMA: ElicitRequestFormParams['requestedSchema'];
431
1079
  /**
432
1080
  * Protocol revision that introduced MRTR.
433
1081
  *
@@ -438,7 +1086,7 @@ declare const APPROVAL_SCHEMA: ElicitRequestFormParams['requestedSchema'];
438
1086
  * `'modern'` rather than as a version string. We keep the literal purely so
439
1087
  * error copy can name the revision a client must speak (R28).
440
1088
  */
441
- declare const MRTR_PROTOCOL_REVISION = "2026-07-28";
1089
+ export declare const MRTR_PROTOCOL_REVISION = "2026-07-28";
442
1090
  /**
443
1091
  * Whether the era a request is being served on can carry MRTR (R28).
444
1092
  *
@@ -449,9 +1097,13 @@ declare const MRTR_PROTOCOL_REVISION = "2026-07-28";
449
1097
  * no `extra.protocolVersion` to read, and reading one was the bug this
450
1098
  * migration fixes (docs/DECISIONS.md D1).
451
1099
  */
452
- declare function supportsMrtr(era: ProtocolEra$1 | undefined): boolean;
453
- //#endregion
454
- //#region src/mrtr/adapter.d.ts
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;
455
1107
  /**
456
1108
  * `execute_machine` arguments plus the MRTR retry envelope.
457
1109
  *
@@ -476,12 +1128,18 @@ interface ExecuteMachineArgs extends RetryEnvelope {
476
1128
  * protocol-era and replay settings. `execute_machine` adds the sandbox
477
1129
  * bridge; `call_tool` needs nothing more (R35, R38).
478
1130
  */
479
- interface ApprovalGateDeps {
1131
+ interface ApprovalGateDeps extends PolicyOptions {
480
1132
  catalog: ServerCatalog;
481
1133
  executor: ToolExecutor;
482
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;
483
1142
  caps?: Partial<ResourceCaps>;
484
- policy?: PolicyOverrides;
485
1143
  redact?: RedactionHook;
486
1144
  /**
487
1145
  * Protocol era this request is being served on, for down-negotiation
@@ -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). */
@@ -524,11 +1195,9 @@ interface ExecuteMachineDeps extends ApprovalGateDeps {
524
1195
  * Statelessness (R27): everything needed to resume rides in `requestState`,
525
1196
  * so any instance can serve any retry. Nothing is held between calls.
526
1197
  */
527
- declare function executeMachine(args: ExecuteMachineArgs, deps: ExecuteMachineDeps): Promise<ToolCallResult>;
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
- declare function errorResult(error: StructuredError): CallToolResult;
530
- //#endregion
531
- //#region src/catalog/call.d.ts
1200
+ export declare function errorResult(error: StructuredError): CallToolResult;
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;
@@ -572,11 +1253,9 @@ type CallToolOutcome = {
572
1253
  reason: string;
573
1254
  };
574
1255
  /** The single inputRequest / pending key of a `call` blob (R36). */
575
- declare const CALL_KEY = "call";
1256
+ export declare const CALL_KEY = "call";
576
1257
  /** Run `call_tool` for one `tools/call`, first attempt or MRTR retry. */
577
- declare function callTool(args: CallToolArgs, deps: CallToolDeps): Promise<ToolCallResult>;
578
- //#endregion
579
- //#region src/catalog/canonical.d.ts
1258
+ export declare function callTool(args: CallToolArgs, deps: CallToolDeps): Promise<ToolCallResult>;
580
1259
  /**
581
1260
  * Canonical JSON for the `call_tool` binding hash (PRD R36).
582
1261
  *
@@ -598,11 +1277,9 @@ declare function callTool(args: CallToolArgs, deps: CallToolDeps): Promise<ToolC
598
1277
  * by the blob's `pending` entry alone (R36, D9).
599
1278
  */
600
1279
  /** Canonical JSON text of a plain JSON value. Throws `validate` on non-JSON. */
601
- declare function canonicalJson(value: unknown): string;
1280
+ export declare function canonicalJson(value: unknown): string;
602
1281
  /** The text a `call` blob is bound to (R36): `{ kind: "call", name, args }`. */
603
- declare function canonicalRequest(name: string, args: Record<string, unknown>): string;
604
- //#endregion
605
- //#region src/catalog/validate-args.d.ts
1282
+ export declare function canonicalRequest(name: string, args: Record<string, unknown>): string;
606
1283
  /**
607
1284
  * Structural argument validation for `call_tool` (PRD R35.1).
608
1285
  *
@@ -610,7 +1287,8 @@ declare function canonicalRequest(name: string, args: Record<string, unknown>):
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,14 +1296,12 @@ declare function canonicalRequest(name: string, args: Record<string, unknown>):
618
1296
  * argument name is ordinary data (S5/S6).
619
1297
  */
620
1298
  /** Throws a `validate`-phase {@link ProjectionError} naming the offending property. */
621
- declare function validateArgsShape(toolName: string, args: Record<string, unknown>, schema: Record<string, unknown>): void;
622
- //#endregion
623
- //#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;
624
1300
  /**
625
1301
  * Module specifier authored machines import the tools surface from (R4).
626
1302
  * The sandbox component mounts {@link GeneratedToolTypes.bridgeSource} here.
627
1303
  */
628
- declare const TOOLS_MODULE_SPECIFIER = "@astral-mcp/tools";
1304
+ export declare const TOOLS_MODULE_SPECIFIER = "@astral-mcp/tools";
629
1305
  /**
630
1306
  * Name of the global the sandbox host MUST inject before authored code runs.
631
1307
  *
@@ -646,7 +1322,7 @@ declare const TOOLS_MODULE_SPECIFIER = "@astral-mcp/tools";
646
1322
  * - If the hook is missing, every tool function throws, keeping the bridge
647
1323
  * inert outside a properly initialized sandbox (S1).
648
1324
  */
649
- declare const TOOL_CALL_HOST_GLOBAL = "__projection_toolcall";
1325
+ export declare const TOOL_CALL_HOST_GLOBAL = "__projection_toolcall";
650
1326
  /** Output of {@link generateToolTypes}. */
651
1327
  interface GeneratedToolTypes {
652
1328
  /** `.d.ts` text for the `@astral-mcp/tools` surface (serve via discover_apis, R3). */
@@ -656,36 +1332,45 @@ interface GeneratedToolTypes {
656
1332
  /** Compact per-tool overview block for inline embedding by discover_apis (R3). */
657
1333
  overview: string;
658
1334
  /**
659
- * Per-tool slice of `dts`, keyed by tool name (R34): the alias
660
- * declarations and the `tools.<name>` member signature exactly as they
661
- * appear in the full file, so `describe_tool` output type-checks against
662
- * 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.
663
1340
  */
664
1341
  toolTypes: Map<string, ToolTypeText>;
665
1342
  }
666
1343
  /** The generated types for one tool, as served by `describe_tool` (R34). */
667
1344
  interface ToolTypeText {
668
- /** `<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
+ */
669
1349
  inputAlias: string;
670
1350
  /** `<Pascal>Output` alias name, when the tool declares an `outputSchema`. */
671
1351
  outputAlias?: string;
672
1352
  /**
673
1353
  * The alias declarations verbatim from the `.d.ts`, then the member
674
1354
  * signature verbatim modulo indentation: the `tools` member is re-indented
675
- * 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).
676
1357
  */
677
1358
  dts: string;
678
1359
  }
679
1360
  /**
680
1361
  * Generate the `@astral-mcp/tools` artifacts for a wrapped server.
681
1362
  *
682
- * Accepts either the full aliased {@link ToolCatalog} (which must contain
683
- * exactly one server in v1, per R30's single-instance instantiation) or a
684
- * {@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).
1367
+ */
1368
+ export declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog, options?: GenerateToolTypesOptions): GeneratedToolTypes;
1369
+ /**
1370
+ * The policy options (R14, R49) decide the posture wording in the doc
1371
+ * comments and the overview.
685
1372
  */
686
- declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog): GeneratedToolTypes;
687
- //#endregion
688
- //#region src/typegen/json-schema.d.ts
1373
+ type GenerateToolTypesOptions = PolicyOptions;
689
1374
  /**
690
1375
  * Convert a JSON Schema into TypeScript type text.
691
1376
  *
@@ -693,63 +1378,23 @@ declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog): Genera
693
1378
  * @param indent - Indentation prefix of the line the type is emitted on;
694
1379
  * nested object literals indent relative to it.
695
1380
  */
696
- declare function schemaToTsType(schema: unknown, indent?: string): string;
1381
+ export declare function schemaToTsType(schema: unknown, indent?: string): string;
697
1382
  /**
698
1383
  * True when the schema requires at least one argument property — used to
699
1384
  * decide whether the generated tool function's `args` parameter is optional.
700
1385
  */
701
- declare function schemaRequiresArgs(schema: unknown): boolean;
702
- //#endregion
703
- //#region src/catalog/search.d.ts
704
- interface SearchToolsArgs {
705
- /** Plain substring filter over name and description (case-insensitive). */
706
- query?: string;
707
- /** Opaque cursor from a previous page's `nextCursor`. */
708
- cursor?: string;
709
- }
710
- interface SearchToolsOptions {
711
- /** Per-tool policy overrides (R14); decides the rendered posture. */
712
- policy?: PolicyOverrides;
713
- /** Tools per page. Default {@link DEFAULT_SEARCH_PAGE_SIZE}, clamped to [1, 200]. */
714
- pageSize?: number;
715
- }
716
- /** Page size — a transport bound, independent of the R37 inline threshold. */
717
- declare const DEFAULT_SEARCH_PAGE_SIZE = 50;
718
- /** `query` is capped (R33): it is matched as a plain substring, never a regex. */
719
- declare const MAX_QUERY_CHARS = 200;
720
- /** The three normative posture strings (R33) — models match on these. */
721
- type ToolPosture = 'runs immediately' | 'asks for approval' | 'blocked by policy';
722
- /** One entry of a `search_tools` page (R33). */
723
- interface ToolSummary {
724
- name: string;
725
- purpose: string;
726
- posture: ToolPosture;
727
- destructive: boolean;
728
- }
729
- interface SearchToolsPage {
730
- tools: ToolSummary[];
731
- /** Matching tools across all pages. */
732
- total: number;
733
- nextCursor?: string;
734
- }
735
- /** Map a policy decision to its plain-words posture (R33). */
736
- declare function postureOf(decision: PolicyDecision): ToolPosture;
737
- /** Summarize one catalog entry (R33). */
738
- declare function summarizeTool(entry: ToolCatalogEntry, policy?: PolicyOverrides): ToolSummary;
739
- /**
740
- * Run `search_tools`. Throws a `validate`-phase {@link ProjectionError} for a
741
- * malformed cursor, one issued for a different query, or one pointing past
742
- * the end of the results (R33).
743
- */
744
- declare function searchTools(catalog: ServerCatalog, args: SearchToolsArgs, options?: SearchToolsOptions): CallToolResult;
745
- //#endregion
746
- //#region src/catalog/describe.d.ts
1386
+ export declare function schemaRequiresArgs(schema: unknown): boolean;
747
1387
  interface DescribeToolArgs {
748
1388
  name: string;
1389
+ /**
1390
+ * Include the generated TypeScript for the tool (R34). Off by default:
1391
+ * the JSON Schema already carries the shape, and the types double the
1392
+ * payload — ask for them when authoring a machine.
1393
+ */
1394
+ types?: boolean;
749
1395
  }
750
- interface DescribeToolOptions {
751
- /** Per-tool policy overrides (R14); decides the rendered posture. */
752
- policy?: PolicyOverrides;
1396
+ /** The policy options (R14, R49) decide the rendered posture. */
1397
+ interface DescribeToolOptions extends PolicyOptions {
753
1398
  /** Per-tool generated types from typegen (R34). */
754
1399
  toolTypes?: ReadonlyMap<string, ToolTypeText>;
755
1400
  }
@@ -773,9 +1418,7 @@ interface ToolDescription {
773
1418
  * Run `describe_tool`. Throws a `validate`-phase {@link ProjectionError}
774
1419
  * when the wrapped server declares no such tool (R34).
775
1420
  */
776
- declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, options?: DescribeToolOptions): CallToolResult;
777
- //#endregion
778
- //#region src/catalog/schema-safety.d.ts
1421
+ export declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, options?: DescribeToolOptions): CallToolResult;
779
1422
  /**
780
1423
  * JSON Schema sanitizer for `describe_tool` (PRD R34, S5).
781
1424
  *
@@ -786,8 +1429,11 @@ declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, op
786
1429
  * model it passes through:
787
1430
  *
788
1431
  * 1. a key allowlist — only the keywords a model needs to build arguments
789
- * survive; everything else (examples, defaults, comments, vendor keys)
790
- * 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);
791
1437
  * 2. string neutralization — every string VALUE has control and bidi
792
1438
  * characters replaced and is length-capped. Property NAMES and
793
1439
  * `required` entries are kept verbatim: they are the keys the model must
@@ -802,9 +1448,9 @@ declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, op
802
1448
  * Pure and total: never throws, never returns anything but plain JSON.
803
1449
  */
804
1450
  /** Keywords that survive sanitization (R34). */
805
- declare const SCHEMA_KEY_ALLOWLIST: ReadonlySet<string>;
1451
+ export declare const SCHEMA_KEY_ALLOWLIST: ReadonlySet<string>;
806
1452
  /** Rendered-schema cap per tool, in characters of its JSON text (R34). */
807
- declare const MAX_SCHEMA_CHARS: number;
1453
+ export declare const MAX_SCHEMA_CHARS: number;
808
1454
  interface SanitizedSchema {
809
1455
  /** The sanitized schema, always a plain JSON object. */
810
1456
  schema: Record<string, unknown>;
@@ -817,31 +1463,311 @@ interface SanitizedSchema {
817
1463
  * Non-object input (a server declaring `inputSchema: true`, or garbage)
818
1464
  * sanitizes to `{}`.
819
1465
  */
820
- declare function sanitizeSchema(schema: unknown, maxChars?: number): SanitizedSchema;
821
- //#endregion
822
- //#region src/errors.d.ts
1466
+ export declare function sanitizeSchema(schema: unknown, maxChars?: number): SanitizedSchema;
823
1467
  /**
824
- * Error carrying the structured `isError` body every failure returns (R25).
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.
825
1490
  */
826
- declare class ProjectionError extends Error {
827
- readonly phase: ErrorPhase;
828
- readonly diagnostics: Diagnostic[];
829
- readonly hint: string;
830
- logs: LogEntry[];
831
- constructor(phase: ErrorPhase, message: string, options?: {
832
- diagnostics?: Diagnostic[];
833
- hint?: string;
834
- logs?: LogEntry[];
835
- cause?: unknown;
836
- });
837
- toStructured(): StructuredError;
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;
838
1509
  }
839
- /** Convert any thrown value into a StructuredError, defaulting to `runtime` phase. */
840
- declare function toStructuredError(err: unknown, logs?: LogEntry[]): StructuredError;
841
- //#endregion
842
- //#region src/mrtr/discover.d.ts
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[];
1609
+ interface SearchToolsArgs {
1610
+ /**
1611
+ * Words to match against tool names, titles and descriptions; any word
1612
+ * matches, name hits weigh more (case-insensitive).
1613
+ */
1614
+ query?: string;
1615
+ /** Opaque cursor from a previous page's `nextCursor`. */
1616
+ cursor?: string;
1617
+ }
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
+ */
1625
+ pageSize?: number;
1626
+ }
1627
+ /** Page size of a listing — a transport bound, independent of the R37 inline threshold. */
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;
1654
+ /** `query` is capped (R33): its words are matched as plain substrings, never a regex. */
1655
+ export declare const MAX_QUERY_CHARS = 200;
1656
+ /** One entry of a `search_tools` page (R33). */
1657
+ interface ToolSummary {
1658
+ name: string;
1659
+ purpose: string;
1660
+ posture: ToolPosture;
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[];
1673
+ }
1674
+ interface SearchToolsPage {
1675
+ tools: ToolSummary[];
1676
+ /** Matching tools across all pages. */
1677
+ total: number;
1678
+ nextCursor?: string;
1679
+ }
1680
+ /**
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.
1765
+ */
1766
+ export declare function runSegment(options: RunSegmentOptions): Promise<SegmentOutcome>;
1767
+ /** The single-server authoring contract (R3). */
1768
+ export declare const AUTHORING_CONTRACT: string;
843
1769
  /** Stable URIs the server serves via `resources/read` (R3). */
844
- declare const RESOURCE_URIS: {
1770
+ export declare const RESOURCE_URIS: {
845
1771
  readonly toolTypes: "projection://types/tools.d.ts";
846
1772
  readonly contract: "projection://docs/authoring-contract.md";
847
1773
  readonly happyPath: "projection://examples/happy-path.ts";
@@ -857,12 +1783,18 @@ interface ProjectionResource {
857
1783
  /** Resource-level metadata (e.g. the MCP Apps `ui` object). */
858
1784
  _meta?: Record<string, unknown>;
859
1785
  }
860
- 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";
861
- 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";
862
- /** Build the resources Projection serves for a wrapped server (R3). */
863
- 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[];
864
1796
  /** R37: above this many tools, `discover_apis` defers the per-tool list to the catalog tools. */
865
- declare const DEFAULT_INLINE_TOOL_THRESHOLD = 40;
1797
+ export declare const DEFAULT_INLINE_TOOL_THRESHOLD = 40;
866
1798
  interface DiscoverApisOptions {
867
1799
  /** Whether the catalog projection (`search_tools` / `describe_tool`) is served (R37). */
868
1800
  catalog?: boolean;
@@ -872,6 +1804,32 @@ interface DiscoverApisOptions {
872
1804
  * clamped at 0, anything else falls back to the default.
873
1805
  */
874
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;
875
1833
  }
876
1834
  /**
877
1835
  * The `discover_apis` result: an inline overview sufficient to author a
@@ -881,37 +1839,79 @@ interface DiscoverApisOptions {
881
1839
  * per-tool overview is replaced by the tool count and a pointer to
882
1840
  * `search_tools` / `describe_tool` — the `.d.ts` resource stays complete.
883
1841
  */
884
- declare function discoverApis(catalog: ServerCatalog, overview: string, options?: DiscoverApisOptions): CallToolResult;
1842
+ export declare function discoverApis(catalog: ServerCatalog, overview: string, options?: DiscoverApisOptions): CallToolResult;
885
1843
  /** Which projections a facade serves (R1, R32). */
886
1844
  interface ProjectionSelection {
887
1845
  codeMode: boolean;
888
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[];
889
1858
  }
890
1859
  /**
891
1860
  * The wrapped server's `instructions`, followed by the preamble for the
892
1861
  * projections in service (R29). The preamble is library copy the
893
1862
  * integrating developer can replace wholesale.
894
1863
  */
895
- declare function buildInstructions(catalog: ServerCatalog, preamble?: string, projections?: ProjectionSelection): string;
1864
+ export declare function buildInstructions(catalog: ServerCatalog, preamble?: string, projections?: ProjectionSelection): string;
896
1865
  /** Preamble when only code mode is on (R29). */
897
- 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.";
898
1867
  /** Preamble when only the catalog projection is on (R29, R32). */
899
- 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.";
900
1869
  /** Preamble when both projections are on: the routing rule (R29). */
901
- 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.";
902
1871
  /** The R29 preamble variant for a projection selection. */
903
- declare function defaultPreamble(projections: ProjectionSelection): string;
904
- //#endregion
905
- //#region src/wrap.d.ts
1872
+ export declare function defaultPreamble(projections: ProjectionSelection): string;
906
1873
  interface WrapOptions {
907
1874
  /** Blob sealing configuration; required for any gated flow (R22). */
908
1875
  blob: BlobSealerConfig;
909
1876
  /** Resource cap overrides, clamped at the hard maxima (R12). */
910
1877
  caps?: Partial<ResourceCaps>;
911
- /** Per-tool policy overrides (R14). */
1878
+ /**
1879
+ * Per-tool policy overrides (R14). Read once, at wrap time: the facade
1880
+ * keeps its own frozen copy, so later changes to this object are not seen.
1881
+ */
912
1882
  policy?: PolicyOverrides;
913
- /** 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
+ */
914
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;
915
1915
  /**
916
1916
  * Replaces the whole R29 preamble, whichever projections are on (the
917
1917
  * code-mode, catalog or routing copy).
@@ -949,12 +1949,68 @@ interface WrapOptions {
949
1949
  * disabling both is an error at wrap time.
950
1950
  */
951
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;
952
1982
  }
953
1983
  /** Per-projection on/off switches (R1, R32). */
954
1984
  interface ProjectionsOptions {
955
1985
  codeMode?: boolean;
956
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;
957
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;
958
2014
  /** Options for registering the projections on a server the caller built. */
959
2015
  interface RegisterOptions {
960
2016
  /**
@@ -982,9 +2038,11 @@ interface ProjectionFacade {
982
2038
  /**
983
2039
  * Run `execute_machine` for one `tools/call`, first attempt or retry.
984
2040
  * `clientCanElicit` reports whether the connected client declared the
985
- * `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).
986
2044
  */
987
- executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
2045
+ executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra, clientCanElicit?: boolean, clientUrlElicitation?: boolean): Promise<ToolCallResult>;
988
2046
  /** Run `discover_apis`. */
989
2047
  discoverApis(): CallToolResult;
990
2048
  /** Run `search_tools` (R33). Failures come back as R25 structured error results. */
@@ -995,95 +2053,111 @@ interface ProjectionFacade {
995
2053
  /** Run `describe_tool` (R34). Failures come back as R25 structured error results. */
996
2054
  describeTool(args: {
997
2055
  name: string;
2056
+ types?: boolean;
998
2057
  }): CallToolResult;
999
2058
  /**
1000
2059
  * Run `call_tool` (R35) for one `tools/call`, first attempt or retry.
1001
- * `era` / `clientCanElicit` as for `executeMachine`.
2060
+ * `era`, `clientCanElicit` and `clientUrlElicitation` as for `executeMachine`.
1002
2061
  */
1003
- callTool(args: CallToolArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
2062
+ callTool(args: CallToolArgs, era?: ProtocolEra, clientCanElicit?: boolean, clientUrlElicitation?: boolean): Promise<ToolCallResult>;
1004
2063
  /** The projections this facade serves (R32). Frozen: fixed at wrap time. */
1005
2064
  readonly projections: Readonly<ProjectionSelection>;
1006
2065
  /** Build an `McpServer` carrying the instructions, the enabled projections' tools and the resources. */
1007
2066
  createServer(ctx?: RegisterOptions): McpServer;
1008
2067
  /** Register the enabled projections' tools and the resources on an `McpServer` the caller built. */
1009
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>;
1010
2077
  /** Close the connection to the wrapped server. */
1011
2078
  close(): Promise<void>;
1012
2079
  }
1013
2080
  /**
1014
- * Wrap an MCP server (an in-process instance or a connected client) into a
1015
- * 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.
1016
2084
  */
1017
- declare function wrapMcpServer(source: McpServer | Client, options: WrapOptions): Promise<ProjectionFacade>;
1018
- //#endregion
1019
- //#region src/apps/dashboard.d.ts
1020
- /**
1021
- * MCP Apps workflow dashboard (PRD §12.1, second fenced use case).
1022
- *
1023
- * A static, library-authored HTML app that renders an `execute_machine`
1024
- * result — output, approval audit trail, console logs, or the structured
1025
- * error — inside the host's sandboxed iframe, per the MCP Apps extension
1026
- * (SEP-1865, spec 2026-01-26).
1027
- *
1028
- * Security posture (the §12.1 design constraint, satisfied by construction):
1029
- * - The HTML is a compile-time constant. No model-authored code, no
1030
- * template interpolation, no VM: UI rendering can never reach the tools
1031
- * bridge because nothing dynamic executes server-side at all.
1032
- * - Model-controlled data (outputs, audit args, log lines, error text)
1033
- * enters the DOM exclusively through `textContent` — the app never uses
1034
- * innerHTML — so hostile strings render inert (S6 extended to the app).
1035
- * - The app requests no host capabilities: `appCapabilities: {}`. It cannot
1036
- * call tools, open links, or write context; it only listens.
1037
- *
1038
- * Wire contract implemented (app side):
1039
- * - `ui/initialize` request → await result → `ui/notifications/initialized`.
1040
- * - Listens for `ui/notifications/tool-input`, `ui/notifications/tool-result`
1041
- * and `ui/notifications/tool-cancelled`; ignores everything else.
1042
- * - The result payload is the standard `CallToolResult` from
1043
- * `execute_machine`: `structuredContent` is either
1044
- * `{ output, approvals, logs }` or (with `isError`) the structured error
1045
- * `{ phase, message, hint, logs }`.
1046
- */
1047
- /** URI the dashboard is served under. MCP Apps mandates the ui:// scheme. */
1048
- declare const DASHBOARD_RESOURCE_URI = "ui://projection/dashboard.html";
1049
- /** MIME type the MCP Apps spec mandates for app HTML resources. */
1050
- declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
1051
- 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";
1052
- //#endregion
1053
- //#region src/prompts/workflow-prompts.d.ts
1054
- interface WorkflowPromptDefinition {
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}`. */
1055
2091
  name: string;
1056
- title: 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;
1057
2097
  description: string;
1058
- /** Argument names → { description, required }. All MCP prompt args are strings. */
1059
- args: Record<string, {
1060
- description: string;
1061
- required: boolean;
1062
- }>;
1063
- build(args: Record<string, string | undefined>, catalog: ServerCatalog): 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>;
1064
2108
  }
1065
- declare const SAVE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
1066
- declare const VISUALIZE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
1067
- declare const WORKFLOW_PROMPTS: readonly WorkflowPromptDefinition[];
1068
- //#endregion
1069
- //#region src/introspector/introspector.d.ts
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;
1070
2126
  /** A wrapped-server source: an in-process instance or a connected client. */
1071
2127
  type ServerSource = McpServer | Client;
1072
2128
  /**
1073
- * Aliased server sources (R30). v1 instantiates exactly one entry, but the
1074
- * interface accepts a set so the multi-server stretch goal (PRD §12.2) needs
1075
- * no signature break.
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).
1076
2133
  */
1077
- type IntrospectorSources = Record<ServerAlias, ServerSource>;
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
+ }
1078
2152
  /**
1079
2153
  * Holds one MCP client per wrapped-server alias, exposes the aggregated
1080
2154
  * {@link ToolCatalog}, and executes tool calls (implements
1081
2155
  * {@link ToolExecutor}). Construct with {@link createIntrospector}.
1082
2156
  */
1083
- declare class Introspector implements ToolExecutor {
2157
+ export declare class Introspector implements ToolExecutor {
1084
2158
  #private;
1085
2159
  private constructor();
1086
- static create(sources: IntrospectorSources): Promise<Introspector>;
2160
+ static create(sources: IntrospectorSources, options?: IntrospectOptions): Promise<Introspector>;
1087
2161
  /** The aggregated tool catalog, keyed by server alias (R30). */
1088
2162
  get catalog(): ToolCatalog;
1089
2163
  /** Re-read `tools/list` from every wrapped server and rebuild the catalog. */
@@ -1098,8 +2172,12 @@ declare class Introspector implements ToolExecutor {
1098
2172
  * - `isError` result or transport/protocol failure → `{ ok: false, error }`.
1099
2173
  *
1100
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).
1101
2179
  */
1102
- callTool(alias: ServerAlias, name: string, args: Record<string, unknown>): Promise<ToolCallOutcome>;
2180
+ callTool(alias: ServerAlias, name: string, args: Record<string, unknown>, options?: UpstreamCallOptions): Promise<ToolCallOutcome>;
1103
2181
  /**
1104
2182
  * Close connections the introspector created (in-process sources).
1105
2183
  * Caller-provided remote clients are left untouched.
@@ -1111,128 +2189,133 @@ declare class Introspector implements ToolExecutor {
1111
2189
  *
1112
2190
  * v1 usage: `createIntrospector({ [DEFAULT_SERVER_ALIAS]: serverOrClient })`.
1113
2191
  */
1114
- declare function createIntrospector(sources: IntrospectorSources): Promise<Introspector>;
1115
- //#endregion
1116
- //#region src/text-safety.d.ts
1117
- /**
1118
- * Rendering untrusted strings inertly (S5, S6).
1119
- *
1120
- * Tool names, descriptions, the wrapped server's identity, and machine state
1121
- * names all flow from a possibly third-party server or a model-authored
1122
- * machine. Wherever they are rendered into human-facing prompt copy or into
1123
- * generated source, they must not be able to forge structure with control
1124
- * characters (newlines, ESC for ANSI sequences) or reorder text with Unicode
1125
- * bidi overrides. Centralizing the character class here keeps every call site
1126
- * on the same, complete definition — an earlier divergence let generated
1127
- * `.d.ts`/bridge comments miss the directional-isolate range that the
1128
- * elicitation copy already stripped.
1129
- */
1130
- /**
1131
- * Replace control and bidi characters with the replacement character.
1132
- * `allowNewline` keeps `\n` (for multi-line blocks whose formatting newlines
1133
- * are part of the rendering, e.g. a pretty-printed JSON block).
1134
- */
1135
- declare function neutralizeControlChars(text: string, options?: {
1136
- allowNewline?: boolean;
1137
- }): string;
1138
- /** Truncate to `maxChars`, appending an ellipsis when shortened. */
1139
- declare function capLength(text: string, maxChars: number): string;
1140
- //#endregion
1141
- //#region src/policy/decision.d.ts
1142
- /**
1143
- * Decide the policy for a catalog tool (R14).
1144
- *
1145
- * - A developer override (`auto` | `ask` | `deny`) always wins.
1146
- * - With no override, `annotations.readOnlyHint === true` yields `auto`;
1147
- * an absent or `false` hint yields `ask` (S5: unannotated defaults to ask).
1148
- * - An override entry with an unrecognized value (possible when the config
1149
- * arrives untyped) fails safe to `ask` — it never grants `auto`.
1150
- *
1151
- * Only own properties of `overrides` are consulted, so tools named like
1152
- * `Object.prototype` members (`toString`, `constructor`, ...) cannot pick up
1153
- * inherited junk as a decision.
1154
- */
1155
- declare function decidePolicy(entry: ToolCatalogEntry, overrides?: PolicyOverrides): PolicyDecision;
1156
- /** Result of resolving a pending call against a server catalog. */
1157
- type ResolvedCallPolicy = {
1158
- ok: true;
1159
- decision: PolicyDecision;
1160
- entry: ToolCatalogEntry;
1161
- } | {
1162
- ok: false;
1163
- error: ProjectionError;
1164
- };
1165
- /**
1166
- * Resolve a machine-requested tool call against the server catalog and
1167
- * decide its policy.
1168
- *
1169
- * Returns the decision together with the matching catalog entry (the entry
1170
- * is what elicitation copy must be built from — S6), or a structured
1171
- * `validate`-phase {@link ProjectionError} when the machine names a tool the
1172
- * wrapped server does not declare.
1173
- */
1174
- declare function resolveCallPolicy(call: PendingToolCall, catalog: ServerCatalog, overrides?: PolicyOverrides): ResolvedCallPolicy;
1175
- /**
1176
- * Canonical TOOL_DENIED reason for calls blocked by a `deny` policy
1177
- * decision. A `deny` decision never produces an elicitation — the machine
1178
- * host injects `TOOL_DENIED` with this reason instead.
1179
- */
1180
- declare const POLICY_DENIED_REASON = "denied by server policy";
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
+ }
1181
2235
  /**
1182
- * Build the TOOL_DENIED reason string for a policy-denied call. With a tool
1183
- * name it names the denied tool so the machine (and audit trail) can tell
1184
- * which call was blocked.
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.
1185
2241
  */
1186
- declare function policyDeniedReason(toolName?: string): string;
1187
- //#endregion
1188
- //#region src/policy/elicitation.d.ts
1189
- /** Structured approval copy for one gated call (input to the MRTR adapter). */
1190
- interface ElicitationCopy {
1191
- /** Short action title, e.g. `Approve: delete file`. */
1192
- title: string;
1193
- /** Full human-readable summary (plain text with an indented JSON block). */
1194
- message: string;
1195
- /** Tool name, taken from the catalog entry. */
1196
- toolName: string;
1197
- /** Machine state path that requested the call (raw; render with care). */
1198
- stateName: string;
1199
- /** The redacted arguments as rendered in `message` (JSON text, possibly truncated). */
1200
- args: string;
1201
- /** True when the catalog entry carries `destructiveHint: true`. */
1202
- destructive: boolean;
1203
- }
1204
- interface ElicitationCopyOptions {
1205
- /** Redaction hook applied to args before any rendering (R16). */
1206
- redact?: RedactionHook;
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;
1207
2245
  /**
1208
- * Who asked: a machine state (default) or a direct `call_tool` request
1209
- * (R35). Selects the footer; nothing else differs.
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.
1210
2249
  */
1211
- origin?: 'machine' | 'direct';
2250
+ readonly skipped: readonly {
2251
+ alias: string;
2252
+ error: StructuredError;
2253
+ }[];
1212
2254
  /**
1213
- * Backstop cap on the whole rendered args JSON, in characters. Default
1214
- * 4096. Each string leaf is independently capped at 512 characters first.
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.
1215
2259
  */
1216
- maxArgsChars?: number;
2260
+ searchTools(args: {
2261
+ query?: string;
2262
+ cursor?: string;
2263
+ }, alias?: string): CallToolResult;
1217
2264
  }
1218
2265
  /**
1219
- * Build the approval summary for a gated call.
1220
- *
1221
- * `entry` MUST be the catalog entry the call was resolved against (use
1222
- * `resolveCallPolicy`); the copy takes the tool name, title and description
1223
- * from it, never from the pending call or its arguments.
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).
1224
2271
  */
1225
- declare function buildElicitationCopy(call: PendingToolCall, entry: ToolCatalogEntry, options?: ElicitationCopyOptions): ElicitationCopy;
2272
+ export declare function wrapMcpServers(sources: Record<string, McpServer | Client | UpstreamSpec | ServerSourceConfig>, options: WrapServersOptions): Promise<MultiProjectionFacade>;
1226
2273
  /**
1227
- * The one-line human purpose: the first sentence of the server's
1228
- * description, neutralized and capped. Runs of replacement characters
1229
- * (neutralized control bytes) are collapsed so a hostile or malformed
1230
- * description does not render as a wall of `�`. Undefined when the server
1231
- * gave no usable description.
2274
+ * MCP Apps workflow dashboard (PRD §12.1, second fenced use case).
2275
+ *
2276
+ * A static, library-authored HTML app that renders an `execute_machine`
2277
+ * result — output, approval audit trail, console logs, or the structured
2278
+ * error — inside the host's sandboxed iframe, per the MCP Apps extension
2279
+ * (SEP-1865, spec 2026-01-26).
2280
+ *
2281
+ * Security posture (the §12.1 design constraint, satisfied by construction):
2282
+ * - The HTML is a compile-time constant. No model-authored code, no
2283
+ * template interpolation, no VM: UI rendering can never reach the tools
2284
+ * bridge because nothing dynamic executes server-side at all.
2285
+ * - Model-controlled data (outputs, audit args, log lines, error text)
2286
+ * enters the DOM exclusively through `textContent` — the app never uses
2287
+ * innerHTML — so hostile strings render inert (S6 extended to the app).
2288
+ * - The app requests no host capabilities: `appCapabilities: {}`. It cannot
2289
+ * call tools, open links, or write context; it only listens.
2290
+ *
2291
+ * Wire contract implemented (app side):
2292
+ * - `ui/initialize` request → await result → `ui/notifications/initialized`.
2293
+ * - Listens for `ui/notifications/tool-input`, `ui/notifications/tool-result`
2294
+ * and `ui/notifications/tool-cancelled`; ignores everything else.
2295
+ * - The result payload is the standard `CallToolResult` from
2296
+ * `execute_machine`: `structuredContent` is either
2297
+ * `{ output, approvals, logs }` or (with `isError`) the structured error
2298
+ * `{ phase, message, hint, logs }`.
1232
2299
  */
1233
- declare function purposeSentence(description: string | undefined): string | undefined;
1234
- //#endregion
1235
- //#region src/sandbox/session.d.ts
2300
+ /** URI the dashboard is served under. MCP Apps mandates the ui:// scheme. */
2301
+ export declare const DASHBOARD_RESOURCE_URI = "ui://projection/dashboard.html";
2302
+ /** MIME type the MCP Apps spec mandates for app HTML resources. */
2303
+ export declare const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
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";
2305
+ interface WorkflowPromptDefinition {
2306
+ name: string;
2307
+ title: string;
2308
+ description: string;
2309
+ /** Argument names → { description, required }. All MCP prompt args are strings. */
2310
+ args: Record<string, {
2311
+ description: string;
2312
+ required: boolean;
2313
+ }>;
2314
+ build(args: Record<string, string | undefined>, catalog: ServerCatalog): string;
2315
+ }
2316
+ export declare const SAVE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
2317
+ export declare const VISUALIZE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
2318
+ export declare const WORKFLOW_PROMPTS: readonly WorkflowPromptDefinition[];
1236
2319
  /** A tool call surfaced from the `@astral-mcp/tools` bridge inside the VM. */
1237
2320
  interface SandboxToolCall {
1238
2321
  name: string;
@@ -1293,64 +2376,6 @@ interface SandboxSession {
1293
2376
  * bridge, evaluate the authored module, and return a handle for driving it.
1294
2377
  * See ./index.ts for the full boundary contract.
1295
2378
  */
1296
- declare function createSandboxSession(options: SandboxSessionOptions): Promise<SandboxSession>;
1297
- //#endregion
1298
- //#region src/machine-host/host.d.ts
1299
- interface RunSegmentOptions {
1300
- /** Submitted module source (R2). */
1301
- code: string;
1302
- lang?: 'ts' | 'js';
1303
- /** Machine input, passed as the machine's `input` on a fresh run. */
1304
- input?: unknown;
1305
- /** Catalog of the wrapped server whose tools the machine may call. */
1306
- catalog: ServerCatalog;
1307
- /** Generated `@astral-mcp/tools` bridge source (from typegen). */
1308
- bridgeSource: string;
1309
- /** Executes approved/auto tool calls against the wrapped server. */
1310
- executor: ToolExecutor;
1311
- /** Resource caps; clamped at the hard maxima (R12). */
1312
- caps?: Partial<ResourceCaps>;
1313
- /** Per-tool policy overrides (R14). */
1314
- policy?: PolicyOverrides;
1315
- /** Zero-based MRTR segment index — stamps logs (R31). */
1316
- segment?: number;
1317
- /**
1318
- * Resume state. Present on every segment after the first: the verified
1319
- * snapshot and the pending calls sealed alongside it, plus the verdicts
1320
- * the client returned for those calls.
1321
- */
1322
- resume?: {
1323
- snapshot: unknown;
1324
- /** Gated calls as sealed into the blob — the approved name+args (S2). */
1325
- pending: Record<string, PendingToolCall>;
1326
- /** Verdict per pending key; a missing entry counts as declined (R17). */
1327
- verdicts: Record<string, ApprovalVerdict>;
1328
- /** Audit entries accumulated in earlier segments. */
1329
- audit?: AuditEntry[];
1330
- /** Round-trip counter carried in the blob (R18). */
1331
- trip: number;
1332
- };
1333
- }
1334
- /**
1335
- * Run one execution segment to completion, suspension, or error.
1336
- *
1337
- * A segment is one MRTR round trip: fresh sandbox, fresh wall-clock budget,
1338
- * fresh logs. On the first segment the module is validated (R24) before the
1339
- * actor starts; on a resume the module is re-evaluated from the re-sent code
1340
- * and the actor is restored from the sealed snapshot.
1341
- *
1342
- * Note: `runSegment` interpolates `input` into driver source without a size
1343
- * check of its own — `executeMachine` enforces `maxInputBytes` before calling
1344
- * it. Callers invoking `runSegment` directly must bound `input` themselves.
1345
- */
1346
- declare function runSegment(options: RunSegmentOptions): Promise<SegmentOutcome>;
1347
- //#endregion
1348
- //#region src/machine-host/contract.d.ts
1349
- /**
1350
- * Human-readable authoring contract. `discover_apis` publishes this inline
1351
- * (R3) so a model can author a valid machine without fetching resources.
1352
- */
1353
- 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";
1354
- //#endregion
1355
- export { APPROVAL_SCHEMA, AUTHORING_CONTRACT, type ApprovalGateDeps, ApprovalVerdict, AuditEntry, BLOB_SCHEMA_VERSION, BlobKind, BlobPayload, BlobSealerConfig, CALL_KEY, type CallToolArgs, type CallToolDeps, type CallToolOutcome, type CallToolResult, type ContentBlock, DASHBOARD_HTML, DASHBOARD_RESOURCE_URI, DEFAULT_CALL_TTL_MS, DEFAULT_CAPS, DEFAULT_CATALOG_PREAMBLE, DEFAULT_CODE_MODE_PREAMBLE, DEFAULT_INLINE_TOOL_THRESHOLD, DEFAULT_MAX_BLOB_BYTES, DEFAULT_ROUTING_PREAMBLE, DEFAULT_SEARCH_PAGE_SIZE, DEFAULT_SERVER_ALIAS, DEFAULT_TTL_MS, type DescribeToolArgs, type DescribeToolOptions, Diagnostic, type DiscoverApisOptions, ENVELOPE_PREFIX, EXAMPLE_DENIAL_FALLBACK, EXAMPLE_HAPPY_PATH, type ElicitationCopy, type ElicitationCopyOptions, ErrorPhase, type ExecuteMachineArgs, type ExecuteMachineDeps, type GeneratedToolTypes, HostEvent, type InputRequest, type InputRequests, type InputRequiredResult, type InputResponse, type InputResponseView, type InputResponses, Introspector, LIB_VERSION, LogEntry, LogLevel, MAX_CAPS, MAX_QUERY_CHARS, MAX_SCHEMA_CHARS, MCP_APP_MIME_TYPE, MRTR_PROTOCOL_REVISION, type OpenBlobOptions, POLICY_DENIED_REASON, PendingToolCall, PolicyDecision, PolicyOverrides, ProjectionConfig, ProjectionError, type ProjectionFacade, type ProjectionResource, type ProjectionSelection, type ProjectionsOptions, type ProtocolEra, RESOURCE_URIS, RedactionHook, type RegisterOptions, ReplayCache, type ResolvedCallPolicy, ResourceCaps, type RetryEnvelope, type RunSegmentOptions, SAVE_WORKFLOW_PROMPT, SCHEMA_KEY_ALLOWLIST, type SandboxSession, type SandboxSessionOptions, type SandboxToolCall, type SandboxToolCallHandler, type SanitizedSchema, type SearchToolsArgs, type SearchToolsOptions, type SearchToolsPage, SegmentOutcome, ServerAlias, ServerCatalog, StructuredError, TOOLS_MODULE_SPECIFIER, TOOL_CALL_HOST_GLOBAL, ToolAnnotations, ToolCallOutcome, type ToolCallResult, ToolCatalog, ToolCatalogEntry, type ToolDescription, ToolExecutor, type ToolPosture, type ToolSummary, type ToolTypeText, VISUALIZE_WORKFLOW_PROMPT, WORKFLOW_PROMPTS, type WorkflowPromptDefinition, type WrapOptions, XSTATE_VERSION, acceptedContent, buildBlobPayload, buildElicitationCopy, buildInstructions, buildResources, callTool, canonicalJson, canonicalRequest, capLength, clampCaps, createIntrospector, createSandboxSession, decidePolicy, dedupeTools, defaultPreamble, describeTool, discoverApis, errorResult, executeMachine, findTool, generateToolTypes, hashCode, inputRequired, inputResponse, isInputRequiredResult, isRecord, neutralizeControlChars, openBlob, policyDeniedReason, postureOf, purposeSentence, resolveCallPolicy, runSegment, sanitizeSchema, schemaRequiresArgs, schemaToTsType, sealBlob, searchTools, summarizeTool, supportsMrtr, toStructuredError, validateArgsShape, wrapMcpServer };
2379
+ export declare function createSandboxSession(options: SandboxSessionOptions): Promise<SandboxSession>;
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 };
1356
2381
  //# sourceMappingURL=index.d.mts.map