@astral-mcp/projection 0.5.1 → 0.7.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,5 +1,5 @@
1
- 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";
2
1
  import "@sebastianwessel/quickjs";
2
+ 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
3
  import { Client } from "@modelcontextprotocol/client";
4
4
  //#region src/types.d.ts
5
5
  /**
@@ -46,11 +46,19 @@ interface ServerCatalog {
46
46
  * Look up a tool by name in a catalog, O(1) after the first call.
47
47
  *
48
48
  * Duplicate names resolve to the FIRST occurrence, matching `Array#find` and
49
- * typegen's `dedupeToolNames` (S5): a hostile server declaring the same name
50
- * twice must not be able to make the policy gate see a different entry (e.g.
51
- * a later `readOnlyHint: true` copy) than the one typegen exposed.
49
+ * {@link dedupeTools} (S5): a hostile server declaring the same name twice
50
+ * must not be able to make the policy gate see a different entry (e.g. a
51
+ * later `readOnlyHint: true` copy) than the one typegen exposed.
52
52
  */
53
53
  declare function findTool(catalog: ServerCatalog, name: string): ToolCatalogEntry | undefined;
54
+ /**
55
+ * Drop tools whose name repeats an earlier one; the first occurrence wins.
56
+ *
57
+ * The single dedupe rule for every consumer of the catalog (typegen, the
58
+ * catalog projection), so they all agree with {@link findTool} on which
59
+ * entry a name denotes (S5).
60
+ */
61
+ declare function dedupeTools(tools: ToolCatalogEntry[]): ToolCatalogEntry[];
54
62
  /** Narrow to a non-null, non-array object. */
55
63
  declare function isRecord(value: unknown): value is Record<string, unknown>;
56
64
  /** Executes a tool call against the wrapped server. Implemented by the introspector. */
@@ -98,7 +106,9 @@ type HostEvent = {
98
106
  callId: string;
99
107
  reason?: string;
100
108
  };
101
- type ErrorPhase = 'transpile' | 'validate' | 'runtime' | 'cap_exceeded' | 'blob_verification' | 'protocol' | 'terminal';
109
+ type ErrorPhase = 'transpile' | 'validate' | 'runtime' | 'cap_exceeded' | 'blob_verification' | 'protocol' |
110
+ /** An `isError` result or transport failure from the wrapped tool, surfaced by `call_tool` (R25). */
111
+ 'upstream' | 'terminal';
102
112
  interface Diagnostic {
103
113
  message: string;
104
114
  /** Optional source location within the submitted code. */
@@ -149,14 +159,22 @@ declare const MAX_CAPS: ResourceCaps;
149
159
  /** Clamp requested caps into [MIN_CAPS, MAX_CAPS] (R12). */
150
160
  declare function clampCaps(requested: Partial<ResourceCaps>): ResourceCaps;
151
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';
152
164
  interface BlobPayload {
153
165
  /** Blob schema version; unknown versions are rejected. */
154
166
  v: number;
167
+ /** Minting tool (R36). */
168
+ kind: BlobKind;
155
169
  /** Library version that produced the snapshot. */
156
170
  libv: string;
157
171
  /** Exact XState version that produced the snapshot. */
158
172
  xstatev: string;
159
- /** SHA-256 (hex) of the original `code` argument. */
173
+ /**
174
+ * Binding hash (S4): SHA-256 (hex) of the original `code` for `machine`
175
+ * blobs, of the canonical `{ kind: "call", name, args }` request for
176
+ * `call` blobs (R36).
177
+ */
160
178
  codeHash: string;
161
179
  /** Server identity the blob was minted against (R30). */
162
180
  serverIdentity: string;
@@ -186,11 +204,38 @@ interface BlobSealerConfig {
186
204
  key: Uint8Array;
187
205
  /** Previous keys accepted for decryption during rotation (R22). */
188
206
  previousKeys?: Uint8Array[];
189
- /** Blob TTL in ms. Default 15 minutes. */
207
+ /** Blob TTL in ms for `machine` blobs. Default 15 minutes. */
190
208
  ttlMs?: number;
209
+ /**
210
+ * Blob TTL in ms for `call` blobs (R38). Default 5 minutes: an approved
211
+ * `call` blob has no forward step, so it is a standing capability until
212
+ * it expires (D4, D9).
213
+ */
214
+ callTtlMs?: number;
191
215
  /** Sealed+compressed size ceiling in bytes. Default 128 KB (R23). */
192
216
  maxBlobBytes?: number;
193
217
  }
218
+ /**
219
+ * Optional shared replay cache (R38). When configured, the adapter consults
220
+ * it after blob verification and before executing any gated call, for both
221
+ * blob kinds. No default ships: an in-memory one would silently break
222
+ * horizontal scaling (G4), so the integrator supplies a shared store.
223
+ */
224
+ interface ReplayCache {
225
+ /**
226
+ * Record `nonce` until `exp` (Unix ms) and resolve `true` when it had
227
+ * already been recorded — i.e. this approval is being replayed.
228
+ *
229
+ * Contract: this MUST be an atomic test-and-set across every instance that
230
+ * can serve a retry (a SETNX-style primitive, not a read followed by a
231
+ * write), or two concurrent replays of one approval both execute. The
232
+ * nonce is recorded on FIRST SIGHT, before the verdict is read and
233
+ * regardless of what follows — a decline, a policy denial, an upstream
234
+ * failure — so a blob is single-use whatever happens to the call it
235
+ * carried. A retry that fails verification never reaches this method.
236
+ */
237
+ seen(nonce: string, exp: number): Promise<boolean>;
238
+ }
194
239
  /** Verdict delivered on retry for a pending gated call. */
195
240
  type ApprovalVerdict = {
196
241
  kind: 'approved';
@@ -242,21 +287,34 @@ interface ProjectionConfig {
242
287
  blob: BlobSealerConfig;
243
288
  /** Redaction hook for elicitation copy (R16). */
244
289
  redact?: RedactionHook;
245
- /** Override the code-mode preamble appended to instructions (R29). */
290
+ /**
291
+ * Replaces the whole R29 preamble appended to `instructions`, whichever
292
+ * projections are on (code mode, catalog, or the routing copy for both).
293
+ */
246
294
  codeModePreamble?: string;
247
295
  }
248
296
  //#endregion
249
297
  //#region src/blob/sealer.d.ts
250
298
  /** Envelope format version prefix. Bump (proj2, …) on any wire-format change. */
251
299
  declare const ENVELOPE_PREFIX = "proj1";
252
- /** Default blob TTL: 15 minutes (R20). */
300
+ /** Default blob TTL for `machine` blobs: 15 minutes (R20). */
253
301
  declare const DEFAULT_TTL_MS: number;
302
+ /**
303
+ * Default TTL for `call` blobs: 5 minutes (R38). An approved `call` blob has
304
+ * no forward step, so it is a standing capability until it expires (D4, D9).
305
+ */
306
+ declare const DEFAULT_CALL_TTL_MS: number;
254
307
  /** Default sealed-blob size ceiling: 128 KB (R23). */
255
308
  declare const DEFAULT_MAX_BLOB_BYTES: number;
256
309
  /** SHA-256 hex digest of a code argument (S4 binding — R20 `codeHash`). */
257
310
  declare function hashCode(code: string): string;
258
311
  interface BuildBlobPayloadInput {
259
- /** The original `code` argument; hashed into `codeHash` (S4). */
312
+ /** Minting tool (R36). Default `machine`. */
313
+ kind?: BlobKind | undefined;
314
+ /**
315
+ * The text the blob is bound to (S4): the original `code` argument for a
316
+ * `machine` blob, the canonical request for a `call` blob (R36).
317
+ */
260
318
  code: string;
261
319
  /** Identity of the wrapped server this blob is minted against (R30). */
262
320
  serverIdentity: string;
@@ -288,7 +346,12 @@ declare function buildBlobPayload(input: BuildBlobPayloadInput): BlobPayload;
288
346
  */
289
347
  declare function sealBlob(payload: BlobPayload, config: BlobSealerConfig): string;
290
348
  interface OpenBlobOptions {
291
- /** The `code` argument re-sent with the retried call (S4). */
349
+ /** The tool opening the blob (R36). Default `machine`. */
350
+ expectedKind?: BlobKind | undefined;
351
+ /**
352
+ * The text re-sent with the retry (S4): the `code` argument for
353
+ * `execute_machine`, the canonical request for `call_tool`.
354
+ */
292
355
  code: string;
293
356
  /** Identity of the wrapped server handling the retry (R30). */
294
357
  serverIdentity: string;
@@ -326,7 +389,7 @@ declare function openBlob(blob: string, options: OpenBlobOptions): BlobPayload;
326
389
  * CI instead of silently minting blobs with a stale version stamp.
327
390
  */
328
391
  /** `@astral-mcp/projection` package version (must match packages/projection/package.json). */
329
- declare const LIB_VERSION = "0.5.1";
392
+ declare const LIB_VERSION = "0.7.0";
330
393
  /** Pinned XState version (must match the exact pin in package.json — D2). */
331
394
  declare const XSTATE_VERSION = "5.32.5";
332
395
  //#endregion
@@ -389,6 +452,17 @@ declare const MRTR_PROTOCOL_REVISION = "2026-07-28";
389
452
  declare function supportsMrtr(era: ProtocolEra$1 | undefined): boolean;
390
453
  //#endregion
391
454
  //#region src/mrtr/adapter.d.ts
455
+ /**
456
+ * `execute_machine` arguments plus the MRTR retry envelope.
457
+ *
458
+ * **Security (S2):** `requestState` and `inputResponses` are the retry
459
+ * envelope — siblings of `arguments` in a `tools/call` that the SDK lifts off
460
+ * the request params onto the handler context. They are NOT tool arguments.
461
+ * A direct caller of `executeMachine` MUST populate them only from the
462
+ * transport's retry fields, never from model-authored tool arguments: a
463
+ * model that has seen a `requestState` could otherwise echo it back with a
464
+ * fabricated `accept` and approve its own gated call.
465
+ */
392
466
  interface ExecuteMachineArgs extends RetryEnvelope {
393
467
  /** The authored module source (R2). */
394
468
  code: string;
@@ -396,9 +470,14 @@ interface ExecuteMachineArgs extends RetryEnvelope {
396
470
  /** Passed to the machine as its input. */
397
471
  input?: unknown;
398
472
  }
399
- interface ExecuteMachineDeps {
473
+ /**
474
+ * What every gated entry point needs: the catalog and executor of the wrapped
475
+ * server, the blob sealer, and the per-facade policy, redaction, caps,
476
+ * protocol-era and replay settings. `execute_machine` adds the sandbox
477
+ * bridge; `call_tool` needs nothing more (R35, R38).
478
+ */
479
+ interface ApprovalGateDeps {
400
480
  catalog: ServerCatalog;
401
- bridgeSource: string;
402
481
  executor: ToolExecutor;
403
482
  blob: BlobSealerConfig;
404
483
  caps?: Partial<ResourceCaps>;
@@ -423,6 +502,20 @@ interface ExecuteMachineDeps {
423
502
  * session affinity over legacy HTTP). Default false: fail fast per R28.
424
503
  */
425
504
  legacyElicitation?: boolean;
505
+ /**
506
+ * Whether the connected client declared the MCP `elicitation` capability.
507
+ * Only consulted on the legacy path with `legacyElicitation` on: the SDK
508
+ * shim cannot deliver an approval to a client without it, and its own
509
+ * failure surfaces as an unstructured error. `undefined` (the caller does
510
+ * not know) leaves the shim to decide.
511
+ */
512
+ clientCanElicit?: boolean;
513
+ /** Optional shared replay cache (R38); consulted after verification, before execution. */
514
+ replayCache?: ReplayCache;
515
+ }
516
+ interface ExecuteMachineDeps extends ApprovalGateDeps {
517
+ /** The `@astral-mcp/tools` bridge module source the sandbox loads (R2). */
518
+ bridgeSource: string;
426
519
  }
427
520
  /**
428
521
  * Run `execute_machine` for one `tools/call`, whether it is a first attempt
@@ -432,6 +525,319 @@ interface ExecuteMachineDeps {
432
525
  * so any instance can serve any retry. Nothing is held between calls.
433
526
  */
434
527
  declare function executeMachine(args: ExecuteMachineArgs, deps: ExecuteMachineDeps): Promise<ToolCallResult>;
528
+ /** 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
532
+ /**
533
+ * `call_tool` arguments plus the MRTR retry envelope.
534
+ *
535
+ * **Security (S2):** `requestState` and `inputResponses` are the retry
536
+ * envelope — siblings of `arguments` in a `tools/call` that the SDK lifts off
537
+ * the request params onto the handler context. They are NOT tool arguments.
538
+ * A direct caller of `callTool` MUST populate them only from the transport's
539
+ * retry fields, never from model-authored tool arguments: a model that has
540
+ * seen a `requestState` could otherwise echo it back with a fabricated
541
+ * `accept` and approve its own write.
542
+ */
543
+ interface CallToolArgs extends RetryEnvelope {
544
+ /** Tool name as listed by `search_tools`. */
545
+ name: string;
546
+ /** Arguments for the tool; absent means `{}`. */
547
+ args?: unknown;
548
+ }
549
+ /** `call_tool` needs exactly the dependencies every gated tool shares (R35, R38). */
550
+ type CallToolDeps = ApprovalGateDeps;
551
+ /** `structuredContent` of a completed `call_tool` (R35.5). */
552
+ type CallToolOutcome = {
553
+ status: 'ok';
554
+ tool: string;
555
+ /**
556
+ * The wrapped tool's result — or, when its JSON exceeded
557
+ * `caps.maxResultBytes`, a string preview of that JSON. The call ran
558
+ * either way; see `truncated`.
559
+ */
560
+ output: unknown;
561
+ /** Present and `true` when `output` is a preview, not the full result. */
562
+ truncated?: true;
563
+ /** Byte length of the full result's JSON, when truncated. */
564
+ outputBytes?: number;
565
+ } | {
566
+ status: 'declined';
567
+ tool: string;
568
+ reason?: string;
569
+ } | {
570
+ status: 'denied_by_policy';
571
+ tool: string;
572
+ reason: string;
573
+ };
574
+ /** The single inputRequest / pending key of a `call` blob (R36). */
575
+ declare const CALL_KEY = "call";
576
+ /** 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
580
+ /**
581
+ * Canonical JSON for the `call_tool` binding hash (PRD R36).
582
+ *
583
+ * A `call` blob is bound to the request that produced it by hashing the
584
+ * canonical text of `{ kind: "call", name, args }`: object keys sorted by
585
+ * UTF-16 code unit order (JavaScript's default sort), array order kept,
586
+ * no whitespace. Both sides canonicalize the *parsed* request, so `1` and
587
+ * `1.0`, `-0` and `0`, escaped and literal characters, and duplicate keys
588
+ * (last wins at parse time) all canonicalize identically. No Unicode
589
+ * normalization is applied — NFC and NFD differ, deliberately failing
590
+ * closed. Values JSON cannot represent (`undefined`, functions, symbols,
591
+ * bigint, non-finite numbers) throw a `validate` error rather than being
592
+ * dropped: the facade is callable outside the SDK (R27), so they can occur.
593
+ * Non-plain objects (`Date`, `Map`, class instances) canonicalize by their
594
+ * own enumerable keys, i.e. typically to `{}` — JSON over the wire never
595
+ * produces them, so this too is reachable only by a direct caller.
596
+ *
597
+ * The hash is a coherence check, not the approval binding — S2 is carried
598
+ * by the blob's `pending` entry alone (R36, D9).
599
+ */
600
+ /** Canonical JSON text of a plain JSON value. Throws `validate` on non-JSON. */
601
+ declare function canonicalJson(value: unknown): string;
602
+ /** 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
606
+ /**
607
+ * Structural argument validation for `call_tool` (PRD R35.1).
608
+ *
609
+ * Deliberately shallow: required properties present, top-level property
610
+ * types match, enum membership for top-level primitives, and no unknown
611
+ * top-level properties when the schema forbids them. It exists so a human
612
+ * 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
614
+ * JSON Schema validation would run twice — the wrapped server is the
615
+ * authority — for nothing but a nicer error message (D9).
616
+ *
617
+ * Own-property semantics throughout: `__proto__` or `constructor` as an
618
+ * argument name is ordinary data (S5/S6).
619
+ */
620
+ /** 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
624
+ /**
625
+ * Module specifier authored machines import the tools surface from (R4).
626
+ * The sandbox component mounts {@link GeneratedToolTypes.bridgeSource} here.
627
+ */
628
+ declare const TOOLS_MODULE_SPECIFIER = "@astral-mcp/tools";
629
+ /**
630
+ * Name of the global the sandbox host MUST inject before authored code runs.
631
+ *
632
+ * Contract (for the sandbox / machine-host components):
633
+ *
634
+ * ```js
635
+ * globalThis.__projection_toolcall = (name, args) => <host-defined value>
636
+ * ```
637
+ *
638
+ * - `name` is the wrapped server's tool name exactly as catalogued.
639
+ * - `args` is a plain JSON-serializable object (never `undefined`; the
640
+ * bridge substitutes `{}` for a missing argument).
641
+ * - The bridge performs no I/O itself and returns the hook's value
642
+ * verbatim. Per R7, authored machines never consume that value as a
643
+ * promise inside invoked actors — the library-supplied `toolCall` action
644
+ * is the intended caller, and results come back as TOOL_RESULT /
645
+ * TOOL_ERROR / TOOL_DENIED events injected by the host.
646
+ * - If the hook is missing, every tool function throws, keeping the bridge
647
+ * inert outside a properly initialized sandbox (S1).
648
+ */
649
+ declare const TOOL_CALL_HOST_GLOBAL = "__projection_toolcall";
650
+ /** Output of {@link generateToolTypes}. */
651
+ interface GeneratedToolTypes {
652
+ /** `.d.ts` text for the `@astral-mcp/tools` surface (serve via discover_apis, R3). */
653
+ dts: string;
654
+ /** ESM source of the runtime bridge to mount at `@astral-mcp/tools` in the sandbox. */
655
+ bridgeSource: string;
656
+ /** Compact per-tool overview block for inline embedding by discover_apis (R3). */
657
+ overview: string;
658
+ /**
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.
663
+ */
664
+ toolTypes: Map<string, ToolTypeText>;
665
+ }
666
+ /** The generated types for one tool, as served by `describe_tool` (R34). */
667
+ interface ToolTypeText {
668
+ /** `<Pascal>Input` alias name, unique within the `.d.ts`. */
669
+ inputAlias: string;
670
+ /** `<Pascal>Output` alias name, when the tool declares an `outputSchema`. */
671
+ outputAlias?: string;
672
+ /**
673
+ * The alias declarations verbatim from the `.d.ts`, then the member
674
+ * signature verbatim modulo indentation: the `tools` member is re-indented
675
+ * to column 0 under a `// Member of the \`tools\` surface:` line.
676
+ */
677
+ dts: string;
678
+ }
679
+ /**
680
+ * Generate the `@astral-mcp/tools` artifacts for a wrapped server.
681
+ *
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.
685
+ */
686
+ declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog): GeneratedToolTypes;
687
+ //#endregion
688
+ //#region src/typegen/json-schema.d.ts
689
+ /**
690
+ * Convert a JSON Schema into TypeScript type text.
691
+ *
692
+ * @param schema - The schema (also used as the $ref resolution root).
693
+ * @param indent - Indentation prefix of the line the type is emitted on;
694
+ * nested object literals indent relative to it.
695
+ */
696
+ declare function schemaToTsType(schema: unknown, indent?: string): string;
697
+ /**
698
+ * True when the schema requires at least one argument property — used to
699
+ * decide whether the generated tool function's `args` parameter is optional.
700
+ */
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
747
+ interface DescribeToolArgs {
748
+ name: string;
749
+ }
750
+ interface DescribeToolOptions {
751
+ /** Per-tool policy overrides (R14); decides the rendered posture. */
752
+ policy?: PolicyOverrides;
753
+ /** Per-tool generated types from typegen (R34). */
754
+ toolTypes?: ReadonlyMap<string, ToolTypeText>;
755
+ }
756
+ /** `structuredContent` of a `describe_tool` result (R34). */
757
+ interface ToolDescription {
758
+ name: string;
759
+ /** Neutralized, capped description; `undefined` when the server gave none. */
760
+ description?: string;
761
+ posture: ToolPosture;
762
+ destructive: boolean;
763
+ /** The server's annotations, as untrusted hints (S5); `title` neutralized. */
764
+ annotations: ToolAnnotations;
765
+ inputSchema: Record<string, unknown>;
766
+ outputSchema?: Record<string, unknown>;
767
+ /** True when either schema had to be reduced to fit the R34 cap. */
768
+ schemaTruncated: boolean;
769
+ /** Generated TypeScript for this tool, as in `projection://types/tools.d.ts`. */
770
+ types?: string;
771
+ }
772
+ /**
773
+ * Run `describe_tool`. Throws a `validate`-phase {@link ProjectionError}
774
+ * when the wrapped server declares no such tool (R34).
775
+ */
776
+ declare function describeTool(catalog: ServerCatalog, args: DescribeToolArgs, options?: DescribeToolOptions): CallToolResult;
777
+ //#endregion
778
+ //#region src/catalog/schema-safety.d.ts
779
+ /**
780
+ * JSON Schema sanitizer for `describe_tool` (PRD R34, S5).
781
+ *
782
+ * The wrapped server's schemas are the injection surface of the catalog
783
+ * projection: beyond `description`, a server controls free text in
784
+ * `title`, nested descriptions, `examples`, `default`, `$comment`, `x-*`
785
+ * vendor keys, `enum` members and `pattern`. Before a schema reaches the
786
+ * model it passes through:
787
+ *
788
+ * 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;
791
+ * 2. string neutralization — every string VALUE has control and bidi
792
+ * characters replaced and is length-capped. Property NAMES and
793
+ * `required` entries are kept verbatim: they are the keys the model must
794
+ * send back, and `JSON.stringify` already renders them inert. A name
795
+ * longer than the string cap is dropped outright (with its `required`
796
+ * entry) — unusable as a key, and otherwise a way around the size cap;
797
+ * 3. a size cap — the rendered schema is bounded per tool, degrading in
798
+ * deterministic stages (drop prose → prune deep subtrees → keep the
799
+ * top-level shape → the marker alone) and flagging truncation explicitly
800
+ * so the model knows the wrapped server enforces more than it can see.
801
+ *
802
+ * Pure and total: never throws, never returns anything but plain JSON.
803
+ */
804
+ /** Keywords that survive sanitization (R34). */
805
+ declare const SCHEMA_KEY_ALLOWLIST: ReadonlySet<string>;
806
+ /** Rendered-schema cap per tool, in characters of its JSON text (R34). */
807
+ declare const MAX_SCHEMA_CHARS: number;
808
+ interface SanitizedSchema {
809
+ /** The sanitized schema, always a plain JSON object. */
810
+ schema: Record<string, unknown>;
811
+ /** True when anything the server declared was cut: by the size cap, the depth cap or the name cap (R34). */
812
+ truncated: boolean;
813
+ }
814
+ /**
815
+ * Sanitize a tool schema for the model (R34).
816
+ *
817
+ * Non-object input (a server declaring `inputSchema: true`, or garbage)
818
+ * sanitizes to `{}`.
819
+ */
820
+ declare function sanitizeSchema(schema: unknown, maxChars?: number): SanitizedSchema;
821
+ //#endregion
822
+ //#region src/errors.d.ts
823
+ /**
824
+ * Error carrying the structured `isError` body every failure returns (R25).
825
+ */
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;
838
+ }
839
+ /** Convert any thrown value into a StructuredError, defaulting to `runtime` phase. */
840
+ declare function toStructuredError(err: unknown, logs?: LogEntry[]): StructuredError;
435
841
  //#endregion
436
842
  //#region src/mrtr/discover.d.ts
437
843
  /** Stable URIs the server serves via `resources/read` (R3). */
@@ -451,21 +857,50 @@ interface ProjectionResource {
451
857
  /** Resource-level metadata (e.g. the MCP Apps `ui` object). */
452
858
  _meta?: Record<string, unknown>;
453
859
  }
454
- declare const EXAMPLE_HAPPY_PATH = "import { setup, assign } from 'xstate';\nimport { toolCall } from '@astral-mcp/tools';\n\n// Read a file and return its contents. Read-only calls run immediately,\n// so this whole machine completes in one round trip.\nexport default setup({}).createMachine({\n id: 'readNotes',\n context: ({ input }) => ({ path: input?.path ?? '/notes.txt', text: 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 actions: assign({ text: ({ event }) => event.result }),\n },\n TOOL_ERROR: {\n target: 'failed',\n actions: assign({ text: ({ event }) => `error: ${event.error.message}` }),\n },\n },\n },\n done: { type: 'final' },\n failed: { type: 'final' },\n },\n output: ({ context }) => ({ text: context.text }),\n});\n";
455
- 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.\nexport default setup({}).createMachine({\n id: 'writeWithFallback',\n context: { status: 'pending', note: null },\n initial: 'writing',\n states: {\n writing: {\n entry: toolCall('write_file', () => ({ path: '/notes.txt', 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', () => ({ path: '/' })),\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";
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";
456
862
  /** Build the resources Projection serves for a wrapped server (R3). */
457
863
  declare function buildResources(catalog: ServerCatalog, dts: string): ProjectionResource[];
864
+ /** R37: above this many tools, `discover_apis` defers the per-tool list to the catalog tools. */
865
+ declare const DEFAULT_INLINE_TOOL_THRESHOLD = 40;
866
+ interface DiscoverApisOptions {
867
+ /** Whether the catalog projection (`search_tools` / `describe_tool`) is served (R37). */
868
+ catalog?: boolean;
869
+ /**
870
+ * Tool count above which the inline per-tool list is omitted. Default
871
+ * {@link DEFAULT_INLINE_TOOL_THRESHOLD}; a finite value is floored and
872
+ * clamped at 0, anything else falls back to the default.
873
+ */
874
+ inlineThreshold?: number;
875
+ }
458
876
  /**
459
877
  * The `discover_apis` result: an inline overview sufficient to author a
460
878
  * simple machine unaided, plus resource links for the full artifacts.
879
+ *
880
+ * R37: when the catalog projection is on and the catalog is large, the
881
+ * per-tool overview is replaced by the tool count and a pointer to
882
+ * `search_tools` / `describe_tool` — the `.d.ts` resource stays complete.
461
883
  */
462
- declare function discoverApis(catalog: ServerCatalog, overview: string): CallToolResult;
884
+ declare function discoverApis(catalog: ServerCatalog, overview: string, options?: DiscoverApisOptions): CallToolResult;
885
+ /** Which projections a facade serves (R1, R32). */
886
+ interface ProjectionSelection {
887
+ codeMode: boolean;
888
+ catalog: boolean;
889
+ }
463
890
  /**
464
- * The wrapped server's `instructions`, followed by the code-mode preamble
465
- * (R29). The preamble is library copy the integrating developer can replace.
891
+ * The wrapped server's `instructions`, followed by the preamble for the
892
+ * projections in service (R29). The preamble is library copy the
893
+ * integrating developer can replace wholesale.
466
894
  */
895
+ declare function buildInstructions(catalog: ServerCatalog, preamble?: string, projections?: ProjectionSelection): string;
896
+ /** Preamble when only code mode is on (R29). */
467
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.";
468
- declare function buildInstructions(catalog: ServerCatalog, preamble?: string): string;
898
+ /** 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.";
900
+ /** 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.";
902
+ /** The R29 preamble variant for a projection selection. */
903
+ declare function defaultPreamble(projections: ProjectionSelection): string;
469
904
  //#endregion
470
905
  //#region src/wrap.d.ts
471
906
  interface WrapOptions {
@@ -477,7 +912,10 @@ interface WrapOptions {
477
912
  policy?: PolicyOverrides;
478
913
  /** Masks sensitive argument fields before they reach an approval prompt (R16). */
479
914
  redact?: RedactionHook;
480
- /** Replaces the library's code-mode preamble (R29). */
915
+ /**
916
+ * Replaces the whole R29 preamble, whichever projections are on (the
917
+ * code-mode, catalog or routing copy).
918
+ */
481
919
  codeModePreamble?: string;
482
920
  /** Compatibility switches for pre-2026-07-28 clients. */
483
921
  compat?: {
@@ -492,8 +930,32 @@ interface WrapOptions {
492
930
  };
493
931
  /** Identity the servers built by `createServer` advertise. */
494
932
  serverInfo?: Implementation;
933
+ /**
934
+ * R37: above this many tools, `discover_apis` omits the per-tool list and
935
+ * points at `search_tools` / `describe_tool` (only when the catalog
936
+ * projection is on). Default 40.
937
+ */
938
+ catalogInlineThreshold?: number;
939
+ /**
940
+ * Shared replay cache (R38). Consulted after blob verification and before
941
+ * any gated execution, for both `execute_machine` and `call_tool`. None
942
+ * ships by default; supply a store shared across instances.
943
+ */
944
+ replayCache?: ReplayCache;
945
+ /**
946
+ * Which projections the facade serves (R1, R32): code mode
947
+ * (`execute_machine`, `discover_apis`) and the catalog projection
948
+ * (`search_tools`, `describe_tool`, `call_tool`). Both default on;
949
+ * disabling both is an error at wrap time.
950
+ */
951
+ projections?: ProjectionsOptions;
495
952
  }
496
- /** Options for registering the code-mode surface on a server the caller built. */
953
+ /** Per-projection on/off switches (R1, R32). */
954
+ interface ProjectionsOptions {
955
+ codeMode?: boolean;
956
+ catalog?: boolean;
957
+ }
958
+ /** Options for registering the projections on a server the caller built. */
497
959
  interface RegisterOptions {
498
960
  /**
499
961
  * The protocol era this server instance serves (R28). Omitted means
@@ -513,24 +975,44 @@ interface ProjectionFacade {
513
975
  readonly resources: ProjectionResource[];
514
976
  /**
515
977
  * Server factory to hand to `createMcpHandler` or `serveStdio` (R28, R29).
516
- * Every instance it builds carries the code-mode `instructions` and knows
517
- * the era it is serving.
978
+ * Every instance it builds carries the `instructions` and knows the era it
979
+ * is serving.
518
980
  */
519
981
  readonly serverFactory: McpServerFactory;
520
- /** Run `execute_machine` for one `tools/call`, first attempt or retry. */
521
- executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra): Promise<ToolCallResult>;
982
+ /**
983
+ * Run `execute_machine` for one `tools/call`, first attempt or retry.
984
+ * `clientCanElicit` reports whether the connected client declared the
985
+ * `elicitation` capability (legacy compat only; omit when unknown).
986
+ */
987
+ executeMachine(args: ExecuteMachineArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
522
988
  /** Run `discover_apis`. */
523
989
  discoverApis(): CallToolResult;
524
- /** Build an `McpServer` carrying the instructions, both tools and the resources. */
990
+ /** Run `search_tools` (R33). Failures come back as R25 structured error results. */
991
+ searchTools(args: {
992
+ query?: string;
993
+ cursor?: string;
994
+ }): CallToolResult;
995
+ /** Run `describe_tool` (R34). Failures come back as R25 structured error results. */
996
+ describeTool(args: {
997
+ name: string;
998
+ }): CallToolResult;
999
+ /**
1000
+ * Run `call_tool` (R35) for one `tools/call`, first attempt or retry.
1001
+ * `era` / `clientCanElicit` as for `executeMachine`.
1002
+ */
1003
+ callTool(args: CallToolArgs, era?: ProtocolEra, clientCanElicit?: boolean): Promise<ToolCallResult>;
1004
+ /** The projections this facade serves (R32). Frozen: fixed at wrap time. */
1005
+ readonly projections: Readonly<ProjectionSelection>;
1006
+ /** Build an `McpServer` carrying the instructions, the enabled projections' tools and the resources. */
525
1007
  createServer(ctx?: RegisterOptions): McpServer;
526
- /** Register both tools and the resources on an `McpServer` the caller built. */
1008
+ /** Register the enabled projections' tools and the resources on an `McpServer` the caller built. */
527
1009
  registerOn(server: McpServer, options?: RegisterOptions): void;
528
1010
  /** Close the connection to the wrapped server. */
529
1011
  close(): Promise<void>;
530
1012
  }
531
1013
  /**
532
1014
  * Wrap an MCP server (an in-process instance or a connected client) into a
533
- * code-mode facade.
1015
+ * Projection facade.
534
1016
  */
535
1017
  declare function wrapMcpServer(source: McpServer | Client, options: WrapOptions): Promise<ProjectionFacade>;
536
1018
  //#endregion
@@ -584,26 +1066,6 @@ declare const SAVE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
584
1066
  declare const VISUALIZE_WORKFLOW_PROMPT: WorkflowPromptDefinition;
585
1067
  declare const WORKFLOW_PROMPTS: readonly WorkflowPromptDefinition[];
586
1068
  //#endregion
587
- //#region src/errors.d.ts
588
- /**
589
- * Error carrying the structured `isError` body every failure returns (R25).
590
- */
591
- declare class ProjectionError extends Error {
592
- readonly phase: ErrorPhase;
593
- readonly diagnostics: Diagnostic[];
594
- readonly hint: string;
595
- logs: LogEntry[];
596
- constructor(phase: ErrorPhase, message: string, options?: {
597
- diagnostics?: Diagnostic[];
598
- hint?: string;
599
- logs?: LogEntry[];
600
- cause?: unknown;
601
- });
602
- toStructured(): StructuredError;
603
- }
604
- /** Convert any thrown value into a StructuredError, defaulting to `runtime` phase. */
605
- declare function toStructuredError(err: unknown, logs?: LogEntry[]): StructuredError;
606
- //#endregion
607
1069
  //#region src/introspector/introspector.d.ts
608
1070
  /** A wrapped-server source: an in-process instance or a connected client. */
609
1071
  type ServerSource = McpServer | Client;
@@ -651,66 +1113,6 @@ declare class Introspector implements ToolExecutor {
651
1113
  */
652
1114
  declare function createIntrospector(sources: IntrospectorSources): Promise<Introspector>;
653
1115
  //#endregion
654
- //#region src/typegen/generate.d.ts
655
- /**
656
- * Module specifier authored machines import the tools surface from (R4).
657
- * The sandbox component mounts {@link GeneratedToolTypes.bridgeSource} here.
658
- */
659
- declare const TOOLS_MODULE_SPECIFIER = "@astral-mcp/tools";
660
- /**
661
- * Name of the global the sandbox host MUST inject before authored code runs.
662
- *
663
- * Contract (for the sandbox / machine-host components):
664
- *
665
- * ```js
666
- * globalThis.__projection_toolcall = (name, args) => <host-defined value>
667
- * ```
668
- *
669
- * - `name` is the wrapped server's tool name exactly as catalogued.
670
- * - `args` is a plain JSON-serializable object (never `undefined`; the
671
- * bridge substitutes `{}` for a missing argument).
672
- * - The bridge performs no I/O itself and returns the hook's value
673
- * verbatim. Per R7, authored machines never consume that value as a
674
- * promise inside invoked actors — the library-supplied `toolCall` action
675
- * is the intended caller, and results come back as TOOL_RESULT /
676
- * TOOL_ERROR / TOOL_DENIED events injected by the host.
677
- * - If the hook is missing, every tool function throws, keeping the bridge
678
- * inert outside a properly initialized sandbox (S1).
679
- */
680
- declare const TOOL_CALL_HOST_GLOBAL = "__projection_toolcall";
681
- /** Output of {@link generateToolTypes}. */
682
- interface GeneratedToolTypes {
683
- /** `.d.ts` text for the `@astral-mcp/tools` surface (serve via discover_apis, R3). */
684
- dts: string;
685
- /** ESM source of the runtime bridge to mount at `@astral-mcp/tools` in the sandbox. */
686
- bridgeSource: string;
687
- /** Compact per-tool overview block for inline embedding by discover_apis (R3). */
688
- overview: string;
689
- }
690
- /**
691
- * Generate the `@astral-mcp/tools` artifacts for a wrapped server.
692
- *
693
- * Accepts either the full aliased {@link ToolCatalog} (which must contain
694
- * exactly one server in v1, per R30's single-instance instantiation) or a
695
- * {@link ServerCatalog} directly.
696
- */
697
- declare function generateToolTypes(catalog: ToolCatalog | ServerCatalog): GeneratedToolTypes;
698
- //#endregion
699
- //#region src/typegen/json-schema.d.ts
700
- /**
701
- * Convert a JSON Schema into TypeScript type text.
702
- *
703
- * @param schema - The schema (also used as the $ref resolution root).
704
- * @param indent - Indentation prefix of the line the type is emitted on;
705
- * nested object literals indent relative to it.
706
- */
707
- declare function schemaToTsType(schema: unknown, indent?: string): string;
708
- /**
709
- * True when the schema requires at least one argument property — used to
710
- * decide whether the generated tool function's `args` parameter is optional.
711
- */
712
- declare function schemaRequiresArgs(schema: unknown): boolean;
713
- //#endregion
714
1116
  //#region src/text-safety.d.ts
715
1117
  /**
716
1118
  * Rendering untrusted strings inertly (S5, S6).
@@ -786,7 +1188,7 @@ declare function policyDeniedReason(toolName?: string): string;
786
1188
  //#region src/policy/elicitation.d.ts
787
1189
  /** Structured approval copy for one gated call (input to the MRTR adapter). */
788
1190
  interface ElicitationCopy {
789
- /** Short title, e.g. `Approval required: delete_file`. */
1191
+ /** Short action title, e.g. `Approve: delete file`. */
790
1192
  title: string;
791
1193
  /** Full human-readable summary (plain text with an indented JSON block). */
792
1194
  message: string;
@@ -802,17 +1204,33 @@ interface ElicitationCopy {
802
1204
  interface ElicitationCopyOptions {
803
1205
  /** Redaction hook applied to args before any rendering (R16). */
804
1206
  redact?: RedactionHook;
805
- /** Cap on the rendered args JSON, in characters. Default 4096. */
1207
+ /**
1208
+ * Who asked: a machine state (default) or a direct `call_tool` request
1209
+ * (R35). Selects the footer; nothing else differs.
1210
+ */
1211
+ origin?: 'machine' | 'direct';
1212
+ /**
1213
+ * Backstop cap on the whole rendered args JSON, in characters. Default
1214
+ * 4096. Each string leaf is independently capped at 512 characters first.
1215
+ */
806
1216
  maxArgsChars?: number;
807
1217
  }
808
1218
  /**
809
1219
  * Build the approval summary for a gated call.
810
1220
  *
811
1221
  * `entry` MUST be the catalog entry the call was resolved against (use
812
- * `resolveCallPolicy`); the copy takes the tool name and description from
813
- * it, never from the pending call or its arguments.
1222
+ * `resolveCallPolicy`); the copy takes the tool name, title and description
1223
+ * from it, never from the pending call or its arguments.
814
1224
  */
815
1225
  declare function buildElicitationCopy(call: PendingToolCall, entry: ToolCatalogEntry, options?: ElicitationCopyOptions): ElicitationCopy;
1226
+ /**
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.
1232
+ */
1233
+ declare function purposeSentence(description: string | undefined): string | undefined;
816
1234
  //#endregion
817
1235
  //#region src/sandbox/session.d.ts
818
1236
  /** A tool call surfaced from the `@astral-mcp/tools` bridge inside the VM. */
@@ -932,7 +1350,7 @@ declare function runSegment(options: RunSegmentOptions): Promise<SegmentOutcome>
932
1350
  * Human-readable authoring contract. `discover_apis` publishes this inline
933
1351
  * (R3) so a model can author a valid machine without fetching resources.
934
1352
  */
935
- 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.\n\nExample — happy path with a read-only call:\n\n import { setup, assign } from 'xstate';\n import { toolCall } from '@astral-mcp/tools';\n\n export default setup({}).createMachine({\n id: 'readNotes',\n context: ({ input }) => ({ path: input?.path ?? '/notes.txt', text: 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({ text: ({ event }) => event.result }) },\n TOOL_ERROR: 'failed',\n },\n },\n done: { type: 'final' },\n failed: { type: 'final' },\n },\n output: ({ context }) => ({ text: context.text }),\n });\n\nExample — handling a denied approval:\n\n import { setup, assign } from 'xstate';\n import { toolCall } from '@astral-mcp/tools';\n\n export default setup({}).createMachine({\n id: 'writeNotes',\n context: { status: 'pending' },\n initial: 'writing',\n states: {\n writing: {\n entry: toolCall('write_file', () => ({ path: '/notes.txt', 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";
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";
936
1354
  //#endregion
937
- export { APPROVAL_SCHEMA, AUTHORING_CONTRACT, ApprovalVerdict, AuditEntry, BLOB_SCHEMA_VERSION, BlobPayload, BlobSealerConfig, type CallToolResult, type ContentBlock, DASHBOARD_HTML, DASHBOARD_RESOURCE_URI, DEFAULT_CAPS, DEFAULT_CODE_MODE_PREAMBLE, DEFAULT_MAX_BLOB_BYTES, DEFAULT_SERVER_ALIAS, DEFAULT_TTL_MS, Diagnostic, 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, MCP_APP_MIME_TYPE, MRTR_PROTOCOL_REVISION, type OpenBlobOptions, POLICY_DENIED_REASON, PendingToolCall, PolicyDecision, PolicyOverrides, ProjectionConfig, ProjectionError, type ProjectionFacade, type ProjectionResource, type ProtocolEra, RESOURCE_URIS, RedactionHook, type RegisterOptions, type ResolvedCallPolicy, ResourceCaps, type RetryEnvelope, type RunSegmentOptions, SAVE_WORKFLOW_PROMPT, type SandboxSession, type SandboxSessionOptions, type SandboxToolCall, type SandboxToolCallHandler, SegmentOutcome, ServerAlias, ServerCatalog, StructuredError, TOOLS_MODULE_SPECIFIER, TOOL_CALL_HOST_GLOBAL, ToolAnnotations, ToolCallOutcome, type ToolCallResult, ToolCatalog, ToolCatalogEntry, ToolExecutor, VISUALIZE_WORKFLOW_PROMPT, WORKFLOW_PROMPTS, type WorkflowPromptDefinition, type WrapOptions, XSTATE_VERSION, acceptedContent, buildBlobPayload, buildElicitationCopy, buildInstructions, buildResources, capLength, clampCaps, createIntrospector, createSandboxSession, decidePolicy, discoverApis, executeMachine, findTool, generateToolTypes, hashCode, inputRequired, inputResponse, isInputRequiredResult, isRecord, neutralizeControlChars, openBlob, policyDeniedReason, resolveCallPolicy, runSegment, schemaRequiresArgs, schemaToTsType, sealBlob, supportsMrtr, toStructuredError, wrapMcpServer };
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 };
938
1356
  //# sourceMappingURL=index.d.mts.map