@orkestrel/mcp 0.0.8 → 0.0.10

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.
@@ -1,6 +1,8 @@
1
1
  import { EmitterErrorHandler } from '@orkestrel/emitter';
2
2
  import { EmitterHooks } from '@orkestrel/emitter';
3
3
  import { EmitterInterface } from '@orkestrel/emitter';
4
+ import { JSONValue } from '@orkestrel/contract';
5
+ import { TokenSecret } from '@orkestrel/server';
4
6
  import { ToolInterface } from '@orkestrel/tool';
5
7
  import { ToolManagerInterface } from '@orkestrel/tool';
6
8
  import { ToolResult } from '@orkestrel/tool';
@@ -54,7 +56,11 @@ export declare function bindClient(client: MCPClientInterface, transport: MCPTra
54
56
  * @remarks
55
57
  * `server.handle` already turns a malformed message into a serialized `-32700` /
56
58
  * `-32600` reply and a notification into `undefined` (no reply), so this binder adds
57
- * no parsing of its own. A `transport.send` throw or rejection is caught and routed
59
+ * no parsing of its own. A HELD-OPEN reply arrives as an
60
+ * {@link import('./types.js').MCPTextStream} instead of a string: this is the one place
61
+ * that pumps it, writing each notification in order and then the generator's returned
62
+ * terminating response ({@link sendStream}). A `transport.send` throw or rejection —
63
+ * mid-stream included — is caught and routed
58
64
  * to `server.emitter`'s `error` event (never rethrown, never an unhandled rejection);
59
65
  * a listener on that event that itself throws is swallowed (the end of the line —
60
66
  * the caller's own bug, never this binder's). The returned unbind DETACHES this
@@ -80,6 +86,128 @@ export declare function bindClient(client: MCPClientInterface, transport: MCPTra
80
86
  */
81
87
  export declare function bindServer(server: MCPServerInterface, transport: MCPTransportInterface): () => void;
82
88
 
89
+ /**
90
+ * Map an executed tool's {@link ToolResult} to an MCP {@link MCPCallResult} — the
91
+ * value as structured content plus a backwards-compatible `text` block, or the
92
+ * error as a `text` block.
93
+ *
94
+ * @remarks
95
+ * The {@link ToolManagerInterface} already isolates a thrown tool into a
96
+ * `success: false` result (so the server adds NO try/catch around `execute`):
97
+ * that branch builds an `isError: true` result carrying `result.error`, so the
98
+ * model sees the failure as a tool result it can react to rather than a protocol
99
+ * error; a valued `success: true` branch carries `result.value` unchanged as
100
+ * `structuredContent` and serializes it (via `JSON.stringify`) into one `text`
101
+ * block. A value-less success retains the required empty `content` block and
102
+ * omits `structuredContent`.
103
+ *
104
+ * @param result - The tool's execution outcome
105
+ * @returns The MCP tool-call result
106
+ */
107
+ export declare function buildCallResult(result: ToolResult): MCPCallResult;
108
+
109
+ /**
110
+ * Build the mandatory modern `server/discover` result.
111
+ *
112
+ * @param options - The server identity, instructions, and cache configuration
113
+ * @returns The supported revisions, tools capability, and required modern cache stamps
114
+ */
115
+ export declare function buildDiscoverResult(options: MCPServerOptions): MCPDiscoverResult;
116
+
117
+ /**
118
+ * Build the MCP `initialize` result — the negotiated protocol version, the
119
+ * advertised capabilities, and the server identity.
120
+ *
121
+ * @remarks
122
+ * Version negotiation echoes the client's `requested` version when it is one of the
123
+ * supported legacy revisions. A modern or unsupported request receives the newest
124
+ * supported legacy revision; the client decides whether to continue.
125
+ * `capabilities.tools` is an empty object — this server advertises the tools
126
+ * capability with no sub-options (no list-changed notification yet).
127
+ *
128
+ * @param name - The server name (echoed in `serverInfo`)
129
+ * @param version - The server version (echoed in `serverInfo`)
130
+ * @param requested - The client's requested protocol version (negotiated when supported)
131
+ * @returns The `initialize` result payload
132
+ */
133
+ export declare function buildInitializeResult(name: string, version: string, requested?: string): Readonly<Record<string, unknown>>;
134
+
135
+ /**
136
+ * Build a JSON-RPC error {@link JSONRPCResponse} — the `id` echoed, the failure as
137
+ * an `error` object.
138
+ *
139
+ * @param id - The request's id (`null` for a parse / invalid-request error)
140
+ * @param code - One of the reserved JSON-RPC codes (see `./constants.js`)
141
+ * @param message - A short human description of the failure
142
+ * @param data - An OPTIONAL machine-readable payload (omitted from the envelope when absent)
143
+ * @returns The error response envelope
144
+ */
145
+ export declare function buildJSONRPCError(id: string | number | null, code: number, message: string, data?: unknown): JSONRPCResponse;
146
+
147
+ /**
148
+ * Build a JSON-RPC success {@link JSONRPCResponse} — the `id` echoed, the method's
149
+ * value as `result`.
150
+ *
151
+ * @param id - The request's id (`null` only for a parse / invalid-request error)
152
+ * @param result - The method's return value
153
+ * @returns The success response envelope
154
+ */
155
+ export declare function buildJSONRPCResult(id: string | number | null, result: unknown): JSONRPCResponse;
156
+
157
+ /**
158
+ * Stamp a result with the modern complete-result discriminator and server
159
+ * metadata, plus cache fields when the result is cacheable.
160
+ *
161
+ * @remarks
162
+ * This is the single stamping site shared by modern result builders. Supplying
163
+ * `ttl` adds both schema-coupled fields (`ttlMs` and `cacheScope`); omitting it
164
+ * adds neither, which keeps `tools/call` distinct from cacheable results.
165
+ *
166
+ * @param result - The unstamped result payload
167
+ * @param identity - The server identity carried under the reserved `_meta` key
168
+ * @param ttl - Required freshness lifetime for a cacheable result; omit for a non-cacheable result
169
+ * @param scope - The cache visibility, defaulting to `'private'` when `ttl` is supplied
170
+ * @returns A copy of the payload with its modern stamps
171
+ */
172
+ export declare function buildModernResult<T extends object>(result: T, identity: MCPIdentity, ttl: number, scope?: 'public' | 'private'): T & {
173
+ readonly resultType: 'complete';
174
+ readonly _meta: Readonly<Record<string, unknown>>;
175
+ readonly ttlMs: number;
176
+ readonly cacheScope: 'public' | 'private';
177
+ };
178
+
179
+ export declare function buildModernResult<T extends object>(result: T, identity: MCPIdentity): T & {
180
+ readonly resultType: 'complete';
181
+ readonly _meta: Readonly<Record<string, unknown>>;
182
+ };
183
+
184
+ /**
185
+ * Build the first notification carrying a subscription id for a listen request.
186
+ *
187
+ * @param notifications - The exact notification filter the server will honour
188
+ * @param id - The `subscriptions/listen` request id
189
+ * @returns The stamped subscription acknowledgement notification
190
+ */
191
+ export declare function buildSubscriptionAcknowledgement(notifications: SubscriptionFilter, id: string | number): JSONRPCRequest;
192
+
193
+ /**
194
+ * Intersect a requested subscription filter with the notification families a server supports.
195
+ *
196
+ * @param requested - The notification families requested by the client
197
+ * @param supported - The notification families the server can actually produce
198
+ * @returns The exact subset the server will honour
199
+ */
200
+ export declare function buildSubscriptionFilter(requested: SubscriptionFilter, supported: SubscriptionFilter): SubscriptionFilter;
201
+
202
+ /**
203
+ * Build the terminating response for a subscription source that closes gracefully.
204
+ *
205
+ * @param id - The `subscriptions/listen` request id
206
+ * @param identity - The server identity included by the modern result stamping site
207
+ * @returns The complete modern result carrying the required subscription id metadata
208
+ */
209
+ export declare function buildSubscriptionResult(id: string | number, identity: MCPIdentity): JSONRPCResponse;
210
+
83
211
  /**
84
212
  * Map a {@link ToolManagerInterface}'s definitions to MCP `tools/list` descriptors
85
213
  * — renaming `parameters` to the wire's `inputSchema`.
@@ -95,22 +223,12 @@ export declare function bindServer(server: MCPServerInterface, transport: MCPTra
95
223
  */
96
224
  export declare function buildToolDescriptors(manager: ToolManagerInterface): readonly MCPToolDescriptor[];
97
225
 
98
- /**
99
- * Map an executed tool's {@link ToolResult} to an MCP {@link MCPToolResult} — the
100
- * value (or error) as a `text` content block.
101
- *
102
- * @remarks
103
- * The {@link ToolManagerInterface} already isolates a thrown tool into a
104
- * `success: false` result (so the server adds NO try/catch around `execute`):
105
- * that branch builds an `isError: true` result carrying `result.error`, so the
106
- * model sees the failure as a tool result it can react to rather than a protocol
107
- * error; the `success: true` branch serializes `result.value` (via
108
- * `JSON.stringify`) into one `text` block.
109
- *
110
- * @param result - The tool's execution outcome
111
- * @returns The MCP tool-call result
112
- */
113
- export declare function buildToolResult(result: ToolResult): MCPToolResult;
226
+ /** A successful JSON-RPC response to `tools/call`. */
227
+ export declare interface CallToolResultResponse {
228
+ readonly jsonrpc: '2.0';
229
+ readonly id: string | number;
230
+ readonly result: MCPCallResult | InputRequiredResult;
231
+ }
114
232
 
115
233
  /**
116
234
  * The observable events of a {@link ClientTransportInterface} (§13) — the moments the
@@ -233,8 +351,8 @@ export declare function createDuplexClientTransport(transport: MCPTransportInter
233
351
  * to `connect` / `disconnect` / `notification` via `client.on(...)` (or
234
352
  * `client.emitter.on(...)`).
235
353
  *
236
- * @param options - `transport` (the carrier; REQUIRED), `name` / `version` (the client
237
- * identity), `timeout` (the per-request deadline), and the reserved `on`
354
+ * @param options - `transport` (the carrier; REQUIRED), an optional `identity`
355
+ * (the client identity), `timeout` (the per-request deadline), and the reserved `on`
238
356
  * {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPClientOptions})
239
357
  * @returns A working {@link MCPClientInterface}
240
358
  *
@@ -267,8 +385,8 @@ export declare function createMCPClient(options: MCPClientOptions): MCPClientInt
267
385
  * `isError: true` tool result), so a misbehaving tool never crashes a dispatch. Subscribe to the
268
386
  * `request` event via `server.emitter.on('request', …)` for tracing.
269
387
  *
270
- * @param options - `name` / `version` (the server identity), `tools` (the live
271
- * registry to expose), an optional `description`, and the reserved `on`
388
+ * @param options - `identity` (the server identity), `tools` (the live
389
+ * registry to expose), optional `instructions`, and the reserved `on`
272
390
  * {@link import('@orkestrel/emitter').EmitterHooks} (see {@link MCPServerOptions})
273
391
  * @returns A working {@link MCPServerInterface}
274
392
  *
@@ -279,7 +397,7 @@ export declare function createMCPClient(options: MCPClientOptions): MCPClientInt
279
397
  * const tools = createToolManager()
280
398
  * tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
281
399
  *
282
- * const server = createMCPServer({ name: 'calculator', version: '1.0.0', tools })
400
+ * const server = createMCPServer({ identity: { name: 'calculator', version: '1.0.0' }, tools })
283
401
  * server.emitter.on('request', (method, id) => log(method, id))
284
402
  *
285
403
  * // A transport pumps message strings through `handle`:
@@ -289,34 +407,320 @@ export declare function createMCPClient(options: MCPClientOptions): MCPClientInt
289
407
  */
290
408
  export declare function createMCPServer(options: MCPServerOptions): MCPServerInterface;
291
409
 
410
+ /**
411
+ * Default modern result freshness lifetime in milliseconds.
412
+ *
413
+ * @remarks
414
+ * `ttlMs` is required on cacheable results, while zero means immediately stale
415
+ * rather than uncached, so the neutral usable default is one minute.
416
+ */
417
+ export declare const DEFAULT_MCP_CACHE_TTL = 60000;
418
+
292
419
  /** The default client name reported in the MCP `initialize` handshake (`clientInfo.name`). */
293
420
  export declare const DEFAULT_MCP_CLIENT_NAME = "taverna";
294
421
 
295
422
  /** The default client version reported in the MCP `initialize` handshake (`clientInfo.version`). */
296
423
  export declare const DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
297
424
 
425
+ /**
426
+ * Secure server bounds used when the matching `limit` option leaf is absent or malformed.
427
+ *
428
+ * @remarks
429
+ * One MiB admits ordinary JSON-RPC requests and substantial tool arguments; 16 KiB admits
430
+ * extension-rich modern metadata and signed multi-round state; four MiB admits substantial
431
+ * JSON tool output without allowing an unconfigured service to serialize arbitrary process
432
+ * memory; 64 metadata keys admits the reserved keys plus many extensions; 128 concurrent
433
+ * streams admits a busy service while bounding retained producers; depth 32 admits ordinary
434
+ * JSON documents while rejecting stack-hostile nesting. Frozen so callers cannot alter the
435
+ * defaults observed by later servers.
436
+ */
437
+ export declare const DEFAULT_MCP_LIMITS: Readonly<{
438
+ message: 1048576;
439
+ metadata: 16384;
440
+ keys: 64;
441
+ state: 16384;
442
+ content: 4194304;
443
+ subscriptions: 128;
444
+ depth: 32;
445
+ }>;
446
+
447
+ /** The maximum discovery-probe deadline used when a client deadline is configured. */
448
+ export declare const DEFAULT_MCP_PROBE_TIMEOUT = 50;
449
+
298
450
  /**
299
451
  * The default per-request deadline (ms) an `MCPClient` applies when `options.timeout`
300
452
  * is unset — a request the remote server does not answer within it rejects.
301
453
  */
302
454
  export declare const DEFAULT_MCP_REQUEST_TIMEOUT = 30000;
303
455
 
456
+ /** One titled value in a form elicitation's single- or multi-select schema. */
457
+ export declare interface ElicitChoice {
458
+ readonly const: string;
459
+ readonly title: string;
460
+ }
461
+
462
+ /** One restricted primitive schema accepted by MCP form-mode elicitation. */
463
+ export declare type ElicitPrimitiveSchema = {
464
+ readonly type: 'boolean';
465
+ readonly title?: string;
466
+ readonly description?: string;
467
+ readonly default?: boolean;
468
+ } | {
469
+ readonly type: 'number' | 'integer';
470
+ readonly title?: string;
471
+ readonly description?: string;
472
+ readonly minimum?: number;
473
+ readonly maximum?: number;
474
+ readonly default?: number;
475
+ } | {
476
+ readonly type: 'string';
477
+ readonly title?: string;
478
+ readonly description?: string;
479
+ readonly minLength?: number;
480
+ readonly maxLength?: number;
481
+ readonly format?: 'uri' | 'email' | 'date' | 'date-time';
482
+ readonly default?: string;
483
+ readonly enum?: readonly string[];
484
+ readonly enumNames?: readonly string[];
485
+ readonly oneOf?: readonly ElicitChoice[];
486
+ } | {
487
+ readonly type: 'array';
488
+ readonly title?: string;
489
+ readonly description?: string;
490
+ readonly minItems?: number;
491
+ readonly maxItems?: number;
492
+ readonly default?: readonly string[];
493
+ readonly items: {
494
+ readonly type: 'string';
495
+ readonly enum: readonly string[];
496
+ } | {
497
+ readonly anyOf: readonly ElicitChoice[];
498
+ };
499
+ };
500
+
501
+ /** An embedded MCP request asking the client to elicit input from its operator. */
502
+ export declare interface ElicitRequest {
503
+ readonly method: 'elicitation/create';
504
+ readonly params: ElicitRequestParams;
505
+ }
506
+
507
+ /** The restricted top-level object schema carried by a form-mode elicitation request. */
508
+ export declare interface ElicitRequestedSchema extends Readonly<Record<string, unknown>> {
509
+ readonly $schema?: string;
510
+ readonly type: 'object';
511
+ readonly properties: Readonly<Record<string, ElicitPrimitiveSchema>>;
512
+ readonly required?: readonly string[];
513
+ }
514
+
515
+ /** The parameters of a form-mode `elicitation/create` request. */
516
+ export declare interface ElicitRequestFormParams {
517
+ readonly mode?: 'form';
518
+ readonly message: string;
519
+ readonly requestedSchema: ElicitRequestedSchema;
520
+ }
521
+
522
+ /** The mode-discriminated parameters of an `elicitation/create` request. */
523
+ export declare type ElicitRequestParams = ElicitRequestFormParams | ElicitRequestURLParams;
524
+
525
+ /** The parameters of a URL-mode `elicitation/create` request. */
526
+ export declare interface ElicitRequestURLParams {
527
+ readonly mode: 'url';
528
+ readonly message: string;
529
+ readonly url: string;
530
+ }
531
+
532
+ /** The result supplied by a client for one embedded {@link ElicitRequest}. */
533
+ export declare interface ElicitResult {
534
+ readonly action: 'accept' | 'decline' | 'cancel';
535
+ readonly content?: Readonly<Record<string, ElicitValue>>;
536
+ }
537
+
538
+ /** The primitive value shapes accepted in an MCP form elicitation response. */
539
+ export declare type ElicitValue = string | number | boolean | readonly string[];
540
+
304
541
  /**
305
- * Build the MCP `initialize` result the negotiated protocol version, the
306
- * advertised capabilities, and the server identity.
542
+ * Infer the wire era for an MCP protocol revision.
543
+ *
544
+ * @param version - The protocol revision to classify
545
+ * @returns `'modern'` for `2026-07-28`, `'legacy'` for either supported legacy
546
+ * revision, or `undefined` when the revision is unsupported
547
+ */
548
+ export declare function inferEra(version: string): MCPEra | undefined;
549
+
550
+ /**
551
+ * Infer the newest supported protocol revision present in a peer's offer.
552
+ *
553
+ * @param offered - The protocol revisions offered by the peer
554
+ * @returns The newest locally supported offered revision, or `undefined`
555
+ */
556
+ export declare function inferVersion(offered: readonly string[]): MCPVersion | undefined;
557
+
558
+ /**
559
+ * One embedded multi-round-trip request.
307
560
  *
308
561
  * @remarks
309
- * Version negotiation echoes the client's `requested` version when it is one of the
310
- * {@link SUPPORTED_PROTOCOL_VERSIONS}, else falls back to {@link MCP_PROTOCOL_VERSION}.
311
- * `capabilities.tools` is an empty object — this server advertises the tools
312
- * capability with no sub-options (no list-changed notification yet).
562
+ * This package produces only {@link ElicitRequest}. The deprecated sampling and roots
563
+ * requests remain legal protocol union members and therefore retain their open parameter
564
+ * records here without gaining package-owned producers.
565
+ */
566
+ export declare type InputRequest = ElicitRequest | {
567
+ readonly method: 'sampling/createMessage';
568
+ readonly params: Readonly<Record<string, unknown>>;
569
+ } | {
570
+ readonly method: 'roots/list';
571
+ readonly params?: Readonly<Record<string, unknown>>;
572
+ };
573
+
574
+ /** A server-keyed map of embedded requests the client must fulfil. */
575
+ export declare type InputRequests = Readonly<Record<string, InputRequest>>;
576
+
577
+ /**
578
+ * An incomplete modern result carrying input requests, protected request state, or both.
313
579
  *
314
- * @param name - The server name (echoed in `serverInfo`)
315
- * @param version - The server version (echoed in `serverInfo`)
316
- * @param requested - The client's requested protocol version (negotiated when supported)
317
- * @returns The `initialize` result payload
580
+ * @remarks
581
+ * The two-arm union enforces the protocol's at-least-one-of rule at the type boundary:
582
+ * every value has `inputRequests`, `requestState`, or both.
583
+ */
584
+ export declare type InputRequiredResult = {
585
+ readonly resultType: 'input_required';
586
+ readonly inputRequests: InputRequests;
587
+ readonly requestState?: string;
588
+ readonly _meta?: Readonly<Record<string, unknown>>;
589
+ } | {
590
+ readonly resultType: 'input_required';
591
+ readonly inputRequests?: InputRequests;
592
+ readonly requestState: string;
593
+ readonly _meta?: Readonly<Record<string, unknown>>;
594
+ };
595
+
596
+ /** A map of client results keyed by the corresponding server-assigned input-request key. */
597
+ export declare type InputResponses = Readonly<Record<string, unknown>>;
598
+
599
+ /**
600
+ * Determine whether a value is bounded, cycle-free JSON with safe property names.
601
+ *
602
+ * @remarks
603
+ * Traversal is iterative, ancestor-aware, and contained by {@link attempt}; deep input,
604
+ * cycles, accessors, hostile proxies, `Map`/`Set`, and the prototype-pollution keys
605
+ * `__proto__`, `constructor`, and `prototype` return `false` rather than throwing.
606
+ * The byte count matches `JSON.stringify` without first allocating the serialization.
607
+ *
608
+ * @param value - The unknown value to inspect
609
+ * @param limits - Serialized byte, optional key, and nesting-depth bounds
610
+ * @returns `true` only for safe JSON satisfying every bound
611
+ *
612
+ * @example
613
+ * ```ts
614
+ * isBoundedJSON({ ok: true }, { bytes: 16, keys: 1, depth: 1 }) // true
615
+ * ```
616
+ */
617
+ export declare function isBoundedJSON<T>(value: T, limits: MCPJSONLimitOptions): value is T & JSONValue;
618
+
619
+ /**
620
+ * Determine whether a value is a string within a UTF-8 byte bound.
621
+ *
622
+ * @param value - The unknown value to inspect
623
+ * @param bytes - The maximum accepted encoded bytes
624
+ * @returns `true` only for a string whose UTF-8 representation fits the bound
625
+ *
626
+ * @example
627
+ * ```ts
628
+ * isBoundedString('€', 3) // true
629
+ * isBoundedString('€', 2) // false
630
+ * ```
631
+ */
632
+ export declare function isBoundedString(value: unknown, bytes: number): value is string;
633
+
634
+ /**
635
+ * Determine whether a value is one restricted primitive form-elicitation schema.
636
+ *
637
+ * @param value - The unknown value to inspect
638
+ * @returns `true` for a supported boolean, numeric, string, or string-array schema
639
+ *
640
+ * @example
641
+ * ```ts
642
+ * isElicitPrimitiveSchema({ type: 'boolean', default: true }) // true
643
+ * isElicitPrimitiveSchema({ type: 'object' }) // false
644
+ * ```
645
+ */
646
+ export declare function isElicitPrimitiveSchema(value: unknown): value is ElicitPrimitiveSchema;
647
+
648
+ /**
649
+ * Determine whether a value is an embedded `elicitation/create` request.
650
+ *
651
+ * @param value - The unknown value to inspect
652
+ * @returns `true` when `value` is a form- or URL-mode elicitation request
653
+ *
654
+ * @example
655
+ * ```ts
656
+ * isElicitRequest({
657
+ * method: 'elicitation/create',
658
+ * params: { message: 'Continue?', requestedSchema: { type: 'object', properties: {} } },
659
+ * }) // true
660
+ * ```
661
+ */
662
+ export declare function isElicitRequest(value: unknown): value is ElicitRequest;
663
+
664
+ /**
665
+ * Determine whether a value is a form-mode elicitation parameter object.
666
+ *
667
+ * @param value - The unknown value to inspect
668
+ * @returns `true` when `value` has the restricted form elicitation shape
669
+ *
670
+ * @example
671
+ * ```ts
672
+ * isElicitRequestFormParams({
673
+ * message: 'Continue?',
674
+ * requestedSchema: { type: 'object', properties: {} },
675
+ * }) // true
676
+ * ```
677
+ */
678
+ export declare function isElicitRequestFormParams(value: unknown): value is ElicitRequestFormParams;
679
+
680
+ /**
681
+ * Determine whether a value is a URL-mode elicitation parameter object.
682
+ *
683
+ * @param value - The unknown value to inspect
684
+ * @returns `true` when `value` has the URL elicitation shape
685
+ *
686
+ * @example
687
+ * ```ts
688
+ * isElicitRequestURLParams({ mode: 'url', message: 'Authenticate', url: 'https://example.test' })
689
+ * ```
318
690
  */
319
- export declare function initializeResult(name: string, version: string, requested?: string): Readonly<Record<string, unknown>>;
691
+ export declare function isElicitRequestURLParams(value: unknown): value is ElicitRequestURLParams;
692
+
693
+ /**
694
+ * Determine whether a value is one elicitation response.
695
+ *
696
+ * @param value - The unknown value to inspect
697
+ * @returns `true` when action/content have the protocol shape
698
+ *
699
+ * @example
700
+ * ```ts
701
+ * isElicitResult({ action: 'accept', content: { approved: true } }) // true
702
+ * ```
703
+ */
704
+ export declare function isElicitResult(value: unknown): value is ElicitResult;
705
+
706
+ /**
707
+ * Determine whether a client capability record declares form-mode elicitation.
708
+ *
709
+ * @remarks
710
+ * The protocol's empty `elicitation` object is the implicit form-only declaration.
711
+ * A non-empty declaration must carry a record-valued `form` member; URL-only support
712
+ * does not authorize a form request. Total over hostile input.
713
+ *
714
+ * @param value - The client capability record to inspect
715
+ * @returns `true` when form-mode elicitation is declared
716
+ *
717
+ * @example
718
+ * ```ts
719
+ * isFormElicitationSupported({ elicitation: {} }) // true — implicit form mode
720
+ * isFormElicitationSupported({ elicitation: { url: {} } }) // false
721
+ * ```
722
+ */
723
+ export declare function isFormElicitationSupported(value: unknown): boolean;
320
724
 
321
725
  /**
322
726
  * Determine whether a parsed value is an MCP `initialize` request — a
@@ -333,6 +737,50 @@ export declare function initializeResult(name: string, version: string, requeste
333
737
  */
334
738
  export declare function isInitializeRequest(value: unknown): value is JSONRPCRequest;
335
739
 
740
+ /**
741
+ * Determine whether a value is one legal embedded multi-round-trip request.
742
+ *
743
+ * @param value - The unknown value to inspect
744
+ * @returns `true` for elicitation, deprecated sampling, or deprecated roots requests
745
+ *
746
+ * @example
747
+ * ```ts
748
+ * isInputRequest({ method: 'roots/list' }) // true — legal but not produced by this package
749
+ * ```
750
+ */
751
+ export declare function isInputRequest(value: unknown): value is InputRequest;
752
+
753
+ /**
754
+ * Determine whether a value is a server-keyed map of embedded input requests.
755
+ *
756
+ * @param value - The unknown value to inspect
757
+ * @returns `true` when every own value is a legal {@link InputRequest}
758
+ *
759
+ * @example
760
+ * ```ts
761
+ * isInputRequests({ confirm: { method: 'roots/list' } }) // true; maps, never arrays
762
+ * ```
763
+ */
764
+ export declare function isInputRequests(value: unknown): value is InputRequests;
765
+
766
+ /**
767
+ * Determine whether a value is an MCP input-required result.
768
+ *
769
+ * @remarks
770
+ * Enforces the at-least-one-of rule at runtime: `inputRequests`, `requestState`, or
771
+ * both must be present and valid. Total over hostile input.
772
+ *
773
+ * @param value - The unknown value to inspect
774
+ * @returns `true` when `value` is a valid input-required result
775
+ *
776
+ * @example
777
+ * ```ts
778
+ * isInputRequiredResult({ resultType: 'input_required', requestState: 'opaque' }) // true
779
+ * isInputRequiredResult({ resultType: 'input_required' }) // false
780
+ * ```
781
+ */
782
+ export declare function isInputRequiredResult(value: unknown): value is InputRequiredResult;
783
+
336
784
  /**
337
785
  * Determine whether a parsed value is a {@link JSONRPCMessage} — a request or a
338
786
  * response.
@@ -394,6 +842,29 @@ export declare function isJSONRPCResponse(value: unknown): value is JSONRPCRespo
394
842
  */
395
843
  export declare function isMCPError(value: unknown): value is MCPError;
396
844
 
845
+ /**
846
+ * Determine whether a value is a supported {@link MCPVersion}.
847
+ *
848
+ * @param value - The unknown value to inspect
849
+ * @returns `true` when the value is one of {@link SUPPORTED_PROTOCOL_VERSIONS}
850
+ */
851
+ export declare function isMCPVersion(value: unknown): value is MCPVersion;
852
+
853
+ /**
854
+ * Determine whether a JSON-RPC request uses the modern per-request MCP wire shape.
855
+ *
856
+ * @remarks
857
+ * Presence routes and validity answers: this guard checks only that
858
+ * `params._meta` carries the reserved protocol-version key. The key's value is
859
+ * deliberately not narrowed here, so a present non-string version remains modern
860
+ * and is rejected later by `parseRequestContext` rather than falling through to
861
+ * legacy dispatch. Total over hostile and malformed input.
862
+ *
863
+ * @param value - The already-parsed value to inspect
864
+ * @returns `true` when the value is a request carrying the reserved version key
865
+ */
866
+ export declare function isModernRequest(value: unknown): value is JSONRPCRequest;
867
+
397
868
  /**
398
869
  * Determine whether a value is a valid JSON-RPC REQUEST `id` — a string, a number,
399
870
  * or absent.
@@ -416,6 +887,20 @@ export declare function isMCPError(value: unknown): value is MCPError;
416
887
  */
417
888
  export declare function isRequestId(value: unknown): value is string | number | undefined;
418
889
 
890
+ /**
891
+ * Determine whether a value is an MCP {@link SubscriptionFilter}.
892
+ *
893
+ * @remarks
894
+ * Every filter field is optional. Boolean notification families accept only booleans, and
895
+ * `resourceSubscriptions` accepts only an array of string URIs. Unknown fields remain open
896
+ * for protocol extensions and are ignored by the built-in subscription matcher. Total over
897
+ * hostile input.
898
+ *
899
+ * @param value - The unknown value to inspect
900
+ * @returns `true` when every recognized filter field has its protocol shape
901
+ */
902
+ export declare function isSubscriptionFilter(value: unknown): value is SubscriptionFilter;
903
+
419
904
  /** JSON-RPC 2.0 reserved error: the method's parameters were invalid. */
420
905
  export declare const JSONRPC_INVALID_PARAMS = -32602;
421
906
 
@@ -431,18 +916,6 @@ export declare const JSONRPC_PARSE_ERROR = -32700;
431
916
  /** JSON-RPC 2.0 implementation-defined server error (the `-32000` to `-32099` range). */
432
917
  export declare const JSONRPC_SERVER_ERROR = -32000;
433
918
 
434
- /**
435
- * Build a JSON-RPC error {@link JSONRPCResponse} — the `id` echoed, the failure as
436
- * an `error` object.
437
- *
438
- * @param id - The request's id (`null` for a parse / invalid-request error)
439
- * @param code - One of the reserved JSON-RPC codes (see `./constants.js`)
440
- * @param message - A short human description of the failure
441
- * @param data - An OPTIONAL machine-readable payload (omitted from the envelope when absent)
442
- * @returns The error response envelope
443
- */
444
- export declare function jsonRPCError(id: string | number | null, code: number, message: string, data?: unknown): JSONRPCResponse;
445
-
446
919
  /**
447
920
  * A JSON-RPC 2.0 error object — the `error` member of a failed
448
921
  * {@link JSONRPCResponse}.
@@ -498,28 +971,88 @@ export declare interface JSONRPCResponse {
498
971
  }
499
972
 
500
973
  /**
501
- * Build a JSON-RPC success {@link JSONRPCResponse} the `id` echoed, the method's
502
- * value as `result`.
974
+ * Determine whether a produced notification belongs to an honoured subscription filter.
503
975
  *
504
- * @param id - The request's id (`null` only for a parse / invalid-request error)
505
- * @param result - The method's return value
506
- * @returns The success response envelope
976
+ * @param notification - The server notification offered by the configured producer
977
+ * @param filter - The filter acknowledged to the client
978
+ * @returns `true` when the notification belongs on this subscription stream
507
979
  */
508
- export declare function jsonRPCResult(id: string | number | null, result: unknown): JSONRPCResponse;
980
+ export declare function matchesSubscriptionNotification(notification: JSONRPCRequest, filter: SubscriptionFilter): boolean;
981
+
982
+ /** MCP reserved error: required HTTP metadata does not match the request body. */
983
+ export declare const MCP_HEADER_MISMATCH = -32020;
984
+
985
+ /** The legacy fallback anchor used when an initialize request cannot be accepted as modern. */
986
+ export declare const MCP_LEGACY_VERSION: MCPVersion;
987
+
988
+ /** Reserved modern `_meta` key carrying the client's open capability record. */
989
+ export declare const MCP_META_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
990
+
991
+ /** Reserved modern `_meta` key carrying the optional client identity. */
992
+ export declare const MCP_META_CLIENT = "io.modelcontextprotocol/clientInfo";
993
+
994
+ /** Reserved modern `_meta` key carrying the server identity on results. */
995
+ export declare const MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
996
+
997
+ /** Reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
998
+ export declare const MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
999
+
1000
+ /** Reserved modern `_meta` key carrying the request's protocol revision. */
1001
+ export declare const MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
1002
+
1003
+ /** MCP reserved error: an operation needs a client capability that was not declared. */
1004
+ export declare const MCP_MISSING_CAPABILITY = -32021;
1005
+
1006
+ /** The modern revision offered by an unpinned client during discovery. */
1007
+ export declare const MCP_MODERN_VERSION: MCPVersion;
1008
+
1009
+ /**
1010
+ * The revision offered and defaulted to in the legacy `initialize` handshake.
1011
+ *
1012
+ * @remarks
1013
+ * This is deliberately a legacy revision, and the newest one supported. 2026-07-28 is stateless
1014
+ * and defines no `initialize`, so it can never be the handshake's version — a client that offers
1015
+ * it is asking to negotiate a revision with no negotiation.
1016
+ */
1017
+ export declare const MCP_PROTOCOL_VERSION: MCPVersion;
1018
+
1019
+ /** MCP reserved error: a request names an unsupported protocol revision. */
1020
+ export declare const MCP_UNSUPPORTED_VERSION = -32022;
509
1021
 
510
- /** The MCP protocol revision this server implements (the default negotiated version). */
511
- export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
1022
+ /**
1023
+ * The MCP `tools/call` result — the executed tool's output as `content` blocks,
1024
+ * with `isError` flagging a tool failure.
1025
+ *
1026
+ * @remarks
1027
+ * A success carries the tool's value unchanged as `structuredContent` alongside
1028
+ * its serialized form in one `text` content block. A value-less success omits
1029
+ * `structuredContent`. A tool FAILURE (the `success: false` branch the registry
1030
+ * isolated) carries its `error` text in `content` AND sets `isError: true`, so the
1031
+ * model sees the failure as a tool result it can react to rather than a protocol
1032
+ * error.
1033
+ */
1034
+ export declare interface MCPCallResult {
1035
+ readonly content: readonly MCPContent[];
1036
+ /** The successful tool value in its original structure; absent when no value was returned. */
1037
+ readonly structuredContent?: unknown;
1038
+ /** `true` when the tool failed — its error text is in `content`. */
1039
+ readonly isError?: boolean;
1040
+ /** The modern result discriminator; absent on a legacy result. */
1041
+ readonly resultType?: 'complete';
1042
+ /** Open modern protocol metadata, including reserved namespaced keys. */
1043
+ readonly _meta?: Readonly<Record<string, unknown>>;
1044
+ }
512
1045
 
513
1046
  /**
514
1047
  * A transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE MCP server
515
- * over an injected {@link ClientTransportInterface}, runs the `initialize` handshake,
516
- * and exposes the server's tools as local {@link ToolInterface}s an agent can run.
1048
+ * over an injected {@link ClientTransportInterface}, negotiates the modern or legacy
1049
+ * wire era, and exposes the server's tools as local {@link ToolInterface}s an agent can run.
517
1050
  *
518
1051
  * @remarks
519
1052
  * - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
520
- * this client ISSUES them over a transport. `connect` runs `initialize`, validates and
521
- * exposes the negotiated `protocol`, then sends `notifications/initialized`; `tools()`
522
- * lists the remote tools and wraps each as a
1053
+ * this client ISSUES them over a transport. `connect` probes `server/discover` unless
1054
+ * pinned legacy, falls back to `initialize` only for a legacy peer, and exposes the
1055
+ * negotiated `version`; `tools()` lists the remote tools and wraps each as a
523
1056
  * local {@link ToolInterface} whose `execute` calls back through `call`; `call` runs a
524
1057
  * remote `tools/call` and returns the tool's value (a remote `isError: true` throws
525
1058
  * locally, so an agent's {@link import('@orkestrel/tool').ToolManagerInterface}
@@ -528,9 +1061,9 @@ export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
528
1061
  * `id` ({@link #nextId}); a single transport `message` subscription resolves / rejects
529
1062
  * the matching {@link #pending} entry by `id`. A message that is NOT a response to a
530
1063
  * pending request is a server NOTIFICATION — re-surfaced on the `notification` event.
531
- * - **Per-request deadline.** `#request` races `AbortSignal.timeout(this.#timeout)` (the
532
- * taverna idiom never a raw `setTimeout`): a server that never replies REJECTS the
533
- * pending request once the deadline fires, never hanging.
1064
+ * - **Per-request deadline.** Each `#request` receives its own deadline: ordinary calls use
1065
+ * `this.#timeout`, while an explicitly bounded discovery uses the shorter probe deadline.
1066
+ * `AbortSignal.timeout` (never a raw `setTimeout`) rejects only that pending request.
534
1067
  * - **Transport-agnostic.** Imports only core siblings (JSON-RPC + the tool vocabulary);
535
1068
  * the concrete transport is injected. Wire fields are narrowed via the contracts
536
1069
  * guards (no `as`).
@@ -540,7 +1073,7 @@ export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
540
1073
  *
541
1074
  * @example
542
1075
  * ```ts
543
- * const client = new MCPClient({ transport, name: 'agent', version: '1.0.0' })
1076
+ * const client = new MCPClient({ transport, identity: { name: 'agent', version: '1.0.0' } })
544
1077
  * await client.connect()
545
1078
  * const tools = await client.tools()
546
1079
  * agent.context.tools.add(tools) // the remote tools are now the agent's
@@ -552,10 +1085,11 @@ export declare class MCPClient implements MCPClientInterface {
552
1085
  constructor(options: MCPClientOptions);
553
1086
  get emitter(): EmitterInterface<MCPClientEventMap>;
554
1087
  get connected(): boolean;
555
- get protocol(): string | undefined;
1088
+ get version(): MCPVersion | undefined;
556
1089
  get transport(): ClientTransportInterface;
557
1090
  on<K extends keyof MCPClientEventMap>(event: K, handler: (...args: MCPClientEventMap[K]) => void): void;
558
1091
  connect(): Promise<void>;
1092
+ discover(): Promise<MCPDiscoverResult>;
559
1093
  disconnect(): Promise<void>;
560
1094
  tools(): Promise<readonly ToolInterface[]>;
561
1095
  call(name: string, args: Readonly<Record<string, unknown>>): Promise<unknown>;
@@ -590,15 +1124,16 @@ export declare type MCPClientEventMap = {
590
1124
 
591
1125
  /**
592
1126
  * A transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE MCP
593
- * server over an injected {@link ClientTransportInterface}, performs the
594
- * `initialize` handshake, and exposes the server's tools as local
1127
+ * server over an injected {@link ClientTransportInterface}, negotiates the
1128
+ * modern or legacy wire era, and exposes the server's tools as local
595
1129
  * {@link ToolInterface}s an agent can run.
596
1130
  *
597
1131
  * @remarks
598
1132
  * - **The mirror of {@link MCPServerInterface}.** Where the server DISPATCHES requests
599
- * over a tool registry, the client ISSUES them over a transport: `connect` runs the
600
- * `initialize` handshake, validates and exposes the negotiated `protocol` (then sends
601
- * `notifications/initialized`); `tools()` lists
1133
+ * over a tool registry, the client ISSUES them over a transport: `connect` probes
1134
+ * `server/discover` first unless pinned to a legacy revision, falling back to the
1135
+ * legacy `initialize` handshake only when the peer does not speak the modern era.
1136
+ * The negotiated revision is exposed through `version`; `tools()` lists
602
1137
  * the remote tools and wraps each as a local {@link ToolInterface} whose `execute`
603
1138
  * calls back through `call`; `call(name, args)` runs a remote `tools/call` and
604
1139
  * returns the tool's value (a remote tool FAILURE — `isError: true` — throws locally,
@@ -620,13 +1155,10 @@ export declare type MCPClientEventMap = {
620
1155
  */
621
1156
  export declare interface MCPClientInterface {
622
1157
  readonly emitter: EmitterInterface<MCPClientEventMap>;
623
- /** Whether the `initialize` handshake has completed and the client is connected. */
1158
+ /** Whether era negotiation has completed and the client is connected. */
624
1159
  readonly connected: boolean;
625
- /**
626
- * The MCP protocol revision negotiated by {@link connect}, or `undefined` before
627
- * connecting and after {@link disconnect}.
628
- */
629
- readonly protocol: string | undefined;
1160
+ /** The negotiated protocol revision, or `undefined` while disconnected. */
1161
+ readonly version: MCPVersion | undefined;
630
1162
  /** The injected transport the client drives the remote server over. */
631
1163
  readonly transport: ClientTransportInterface;
632
1164
  /**
@@ -638,19 +1170,29 @@ export declare interface MCPClientInterface {
638
1170
  */
639
1171
  on<K extends keyof MCPClientEventMap>(event: K, handler: (...args: MCPClientEventMap[K]) => void): void;
640
1172
  /**
641
- * Connect to the remote server — open the transport and run the `initialize`
642
- * handshake, validate its negotiated protocol, then send
643
- * `notifications/initialized`.
1173
+ * Connect to the remote server — open the transport and negotiate the modern or
1174
+ * legacy wire era without exposing that choice to the caller.
644
1175
  *
645
1176
  * @remarks
646
- * Idempotent — a second `connect` while already connected is a no-op. On success
647
- * {@link protocol} contains a supported revision and the `connect` event fires. A
648
- * non-string or unsupported revision closes the transport and rejects without
649
- * connecting or sending the initialized notification.
1177
+ * Idempotent — a second `connect` while already connected is a no-op. An unpinned
1178
+ * client probes `server/discover`; a pinned legacy client and a legacy fallback run
1179
+ * `initialize` and send `notifications/initialized`. On success {@link version}
1180
+ * contains a supported revision and the `connect` event fires.
650
1181
  *
651
1182
  * @returns Resolves once the handshake completes and the client is connected
652
1183
  */
653
1184
  connect(): Promise<void>;
1185
+ /**
1186
+ * Discover a modern server's supported revisions and capabilities.
1187
+ *
1188
+ * @remarks
1189
+ * The request carries the modern per-request metadata stamp. Unknown revisions in
1190
+ * the peer's advertisement are ignored because {@link MCPDiscoverResult} exposes
1191
+ * only revisions this client can negotiate.
1192
+ *
1193
+ * @returns The validated modern discovery result
1194
+ */
1195
+ discover(): Promise<MCPDiscoverResult>;
654
1196
  /**
655
1197
  * Disconnect from the remote server — reject every pending request and close the
656
1198
  * transport.
@@ -680,7 +1222,7 @@ export declare interface MCPClientInterface {
680
1222
  * result's `text` content blocks, and either parses the JSON value or throws.
681
1223
  *
682
1224
  * @remarks
683
- * The inverse of the server's `buildToolResult`: a SUCCESS parses the concatenated
1225
+ * The inverse of the server's `buildCallResult`: a SUCCESS parses the concatenated
684
1226
  * `text` as JSON (falling back to the raw string when it is not JSON); a remote tool
685
1227
  * FAILURE (`isError: true`) THROWS an `Error` carrying the error text — so an agent's
686
1228
  * {@link ToolManagerInterface} isolates the remote failure into a `success: false`
@@ -695,15 +1237,18 @@ export declare interface MCPClientInterface {
695
1237
 
696
1238
  /**
697
1239
  * Options for `createMCPClient` — the {@link ClientTransportInterface} to drive, the
698
- * client identity (`name` / `version`), the per-request `timeout`, and the reserved
1240
+ * optional client {@link MCPIdentity}, the per-request `timeout`, and the reserved
699
1241
  * `on` hooks (§8).
700
1242
  *
701
1243
  * @remarks
702
1244
  * - `transport` — the carrier the client drives a remote MCP server over (REQUIRED;
703
1245
  * a concrete one from `src/server/mcp`, or an in-process loopback).
704
- * - `name` / `version` identify the client in the `initialize` handshake
705
- * (`clientInfo`); default to {@link import('./constants.js').DEFAULT_MCP_CLIENT_NAME}
706
- * / {@link import('./constants.js').DEFAULT_MCP_CLIENT_VERSION}.
1246
+ * - `identity` — identifies the client in the `initialize` handshake (`clientInfo`);
1247
+ * defaults to {@link import('./constants.js').DEFAULT_MCP_CLIENT_NAME} /
1248
+ * {@link import('./constants.js').DEFAULT_MCP_CLIENT_VERSION}.
1249
+ * - `capabilities` — the open client-capability record carried by every modern
1250
+ * request; defaults to an empty record when the modern client implementation lands.
1251
+ * - `version` — an optional protocol pin; absence lets the modern client negotiate.
707
1252
  * - `timeout` — the per-request deadline in milliseconds: a `tools/list` / `tools/call`
708
1253
  * / `initialize` that the server does not answer within it REJECTS (the pending
709
1254
  * request is settled by an `AbortSignal.timeout(timeout)` deadline — never a raw
@@ -717,18 +1262,57 @@ export declare interface MCPClientOptions {
717
1262
  /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
718
1263
  readonly error?: EmitterErrorHandler;
719
1264
  readonly transport: ClientTransportInterface;
720
- readonly name?: string;
721
- readonly version?: string;
1265
+ readonly identity?: MCPIdentity;
1266
+ /** The open client-capability record carried by modern requests. */
1267
+ readonly capabilities?: Readonly<Record<string, unknown>>;
1268
+ /** An optional protocol revision pin; absence permits negotiation. */
1269
+ readonly version?: MCPVersion;
722
1270
  /** The per-request deadline in milliseconds (default {@link import('./constants.js').DEFAULT_MCP_REQUEST_TIMEOUT}). */
723
1271
  readonly timeout?: number;
724
1272
  }
725
1273
 
726
- /** One content item of an MCP {@link MCPToolResult} — a `text` block carrying the tool's output. */
1274
+ /** One content item of an MCP {@link MCPCallResult} — a `text` block carrying the tool's output. */
727
1275
  export declare interface MCPContent {
728
1276
  readonly type: 'text';
729
1277
  readonly text: string;
730
1278
  }
731
1279
 
1280
+ /** The mandatory modern `server/discover` result. */
1281
+ export declare interface MCPDiscoverResult {
1282
+ readonly supportedVersions: readonly MCPVersion[];
1283
+ readonly capabilities: Readonly<Record<string, unknown>>;
1284
+ readonly resultType: 'complete';
1285
+ readonly ttlMs: number;
1286
+ readonly cacheScope: 'public' | 'private';
1287
+ readonly instructions?: string;
1288
+ readonly _meta?: Readonly<Record<string, unknown>>;
1289
+ }
1290
+
1291
+ /**
1292
+ * Per-request execution options every dispatched handler receives.
1293
+ *
1294
+ * @remarks
1295
+ * `caller` is consumer-ASSERTED and NEVER VERIFIED. Sessions mint transport identity, not
1296
+ * caller identity, and nothing in MCP authenticates this value. This package carries it
1297
+ * opaquely without inspecting, validating, or serializing it. A consumer must narrow it with
1298
+ * its own total guard and treat absence as unauthenticated.
1299
+ */
1300
+ export declare interface MCPDispatchOptions {
1301
+ /** Aborts when the bound transport can observe that the caller's request has ended. */
1302
+ readonly signal?: AbortSignal;
1303
+ /** Consumer-asserted caller context, forwarded opaquely and never protocol-verified. */
1304
+ readonly caller?: unknown;
1305
+ }
1306
+
1307
+ /** One consumer-requested form elicitation, before MCP assigns its map key and signs state. */
1308
+ export declare interface MCPElicitation {
1309
+ readonly request: ElicitRequestFormParams;
1310
+ readonly state?: string;
1311
+ }
1312
+
1313
+ /** The wire era selected by an MCP request's structure. */
1314
+ export declare type MCPEra = 'modern' | 'legacy';
1315
+
732
1316
  /**
733
1317
  * A remote Model Context Protocol JSON-RPC error, preserving its machine-readable
734
1318
  * numeric code and optional structured context.
@@ -737,13 +1321,19 @@ export declare interface MCPContent {
737
1321
  * {@link MCPClient} throws this error only for a remote JSON-RPC `error` response.
738
1322
  * Local lifecycle and transport conditions such as disconnects and request timeouts
739
1323
  * remain plain `Error`s. `context` carries the response's optional `error.data`
740
- * unchanged and is `undefined` when the peer omitted it.
1324
+ * unchanged and is `undefined` when the peer omitted it. This includes the modern
1325
+ * reserved paths: `-32020` carries no context, `-32021` may carry
1326
+ * `requiredCapabilities`, and `-32022` carries the peer's `supported` revisions and
1327
+ * `requested` revision for negotiation recovery.
741
1328
  *
742
1329
  * @example
743
1330
  * ```ts
744
- * const error = new MCPError('Method not found', -32601, { method: 'missing' })
745
- * error.code // -32601
746
- * error.context // { method: 'missing' }
1331
+ * const error = new MCPError('Unsupported protocol version', -32022, {
1332
+ * supported: ['2026-07-28'],
1333
+ * requested: '2024-11-05',
1334
+ * })
1335
+ * error.code // -32022
1336
+ * error.context // { supported: ['2026-07-28'], requested: '2024-11-05' }
747
1337
  * ```
748
1338
  */
749
1339
  export declare class MCPError extends Error {
@@ -760,6 +1350,195 @@ export declare class MCPError extends Error {
760
1350
  constructor(message: string, code: number, context?: unknown);
761
1351
  }
762
1352
 
1353
+ /** The identity (`name` / `version`) of an MCP server or client. */
1354
+ export declare interface MCPIdentity {
1355
+ readonly name: string;
1356
+ readonly version: string;
1357
+ }
1358
+
1359
+ /** The call-in-hand context supplied to an {@link MCPInputHandler}. */
1360
+ export declare interface MCPInputContext {
1361
+ readonly request: JSONRPCRequest;
1362
+ readonly name: string;
1363
+ readonly arguments: Readonly<Record<string, unknown>>;
1364
+ readonly response?: ElicitResult;
1365
+ readonly state?: string;
1366
+ }
1367
+
1368
+ /**
1369
+ * Decide whether the current `tools/call` needs operator input.
1370
+ *
1371
+ * @param context - The original call plus a verified response/state on a retry
1372
+ * @param options - The per-request execution options
1373
+ * @returns A form elicitation to send, or `undefined` to continue into the tool registry
1374
+ */
1375
+ export declare type MCPInputHandler = (context: MCPInputContext, options: MCPDispatchOptions) => MCPElicitation | undefined | Promise<MCPElicitation | undefined>;
1376
+
1377
+ /** Consumer policy for the server's multi-round-trip input mechanism. */
1378
+ export declare interface MCPInputOptions {
1379
+ /** HMAC secret or `[current, ...older]` rotation list used by `signToken` / `verifyToken`. */
1380
+ readonly secret: TokenSecret;
1381
+ /** Token lifetime in milliseconds; required so MCP never invents an expiry policy. */
1382
+ readonly ttl: number;
1383
+ /** Resolve the authenticated principal for the call in hand. */
1384
+ readonly principal: MCPPrincipalHandler;
1385
+ /** Decide whether the call needs a form elicitation, including on verified retries. */
1386
+ readonly elicit: MCPInputHandler;
1387
+ }
1388
+
1389
+ /** The integrity-protected payload carried inside an opaque `requestState` token. */
1390
+ export declare interface MCPInputState {
1391
+ readonly principal: string;
1392
+ readonly ttl: number;
1393
+ readonly origin: string | number;
1394
+ readonly key: string;
1395
+ readonly name: string;
1396
+ readonly state?: string;
1397
+ }
1398
+
1399
+ /** Limits applied by {@link isBoundedJSON} to one JSON value. */
1400
+ export declare interface MCPJSONLimitOptions {
1401
+ /** Maximum serialized UTF-8 bytes. */
1402
+ readonly bytes: number;
1403
+ /** Maximum total enumerable keys; omitted when bytes alone bound breadth. */
1404
+ readonly keys?: number;
1405
+ /** Maximum array/object nesting depth. */
1406
+ readonly depth: number;
1407
+ }
1408
+
1409
+ /** Configurable hostile-input and live-resource bounds for an MCP server. */
1410
+ export declare interface MCPLimitOptions {
1411
+ /** Maximum UTF-8 bytes accepted by the raw string boundary. */
1412
+ readonly message?: number;
1413
+ /** Maximum serialized UTF-8 bytes accepted in one `_meta` value. */
1414
+ readonly metadata?: number;
1415
+ /** Maximum total enumerable keys accepted across one `_meta` value. */
1416
+ readonly keys?: number;
1417
+ /** Maximum UTF-8 bytes accepted in one protected `requestState`. */
1418
+ readonly state?: number;
1419
+ /** Maximum serialized UTF-8 bytes accepted from one produced tool content value. */
1420
+ readonly content?: number;
1421
+ /** Maximum simultaneously live built-in subscription streams. */
1422
+ readonly subscriptions?: number;
1423
+ /** Maximum nesting depth accepted by bounded JSON values. */
1424
+ readonly depth?: number;
1425
+ }
1426
+
1427
+ /**
1428
+ * The MCP `tools/list` result — tool descriptors plus optional modern result
1429
+ * stamps.
1430
+ *
1431
+ * @remarks
1432
+ * The wire field names remain verbatim. A modern result requires `resultType`,
1433
+ * `ttlMs`, and `cacheScope`; they remain optional here because the same result
1434
+ * shape also models the unstamped legacy response.
1435
+ */
1436
+ export declare interface MCPListResult {
1437
+ readonly tools: readonly MCPToolDescriptor[];
1438
+ readonly resultType?: 'complete';
1439
+ readonly ttlMs?: number;
1440
+ readonly cacheScope?: 'public' | 'private';
1441
+ readonly _meta?: Readonly<Record<string, unknown>>;
1442
+ }
1443
+
1444
+ /**
1445
+ * One modern method, registered on the seam that dispatches it.
1446
+ *
1447
+ * @remarks
1448
+ * `undefined` answers nothing (the notification arm); an {@link MCPStream} holds the
1449
+ * request open. `options.signal` aborts when the caller's request ends — what a handler
1450
+ * does with it is the handler's decision, never this package's. `options.caller` is
1451
+ * consumer-asserted and never verified by this package.
1452
+ *
1453
+ * @param request - The parsed modern request being dispatched
1454
+ * @param options - The per-request execution options (see {@link MCPDispatchOptions})
1455
+ * @returns The terminating response, a held-open {@link MCPStream}, or `undefined` for no answer
1456
+ */
1457
+ export declare type MCPMethodHandler = (request: JSONRPCRequest, options: MCPDispatchOptions) => Promise<JSONRPCResponse | MCPStream | undefined>;
1458
+
1459
+ /**
1460
+ * The modern method registry an {@link import('./types.js').MCPServerInterface}
1461
+ * dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
1462
+ * map rather than exposing one.
1463
+ *
1464
+ * @remarks
1465
+ * - **One seam.** The server registers its built-in modern methods here at construction
1466
+ * and resolves EVERY modern method from here, so a consumer's method and a built-in
1467
+ * are the same kind of thing on the same path.
1468
+ * - **Registration is a write, not a merge.** `add` under a name already present
1469
+ * REPLACES it, which is how a consumer overrides a built-in; there is no precedence
1470
+ * rule to remember.
1471
+ * - **A narrower contract than a `Map`.** Callers register and resolve; they cannot
1472
+ * iterate, clear, or otherwise reach the server's internal state through it.
1473
+ *
1474
+ * @example
1475
+ * ```ts
1476
+ * const methods = new MCPMethodManager()
1477
+ * methods.add('tools/list', async (request) => buildJSONRPCResult(request.id ?? null, { tools: [] }))
1478
+ * methods.method('tools/list') // the handler
1479
+ * methods.method('tools/nope') // undefined → the dispatch branch answers -32601
1480
+ * ```
1481
+ */
1482
+ export declare class MCPMethodManager implements MCPMethodManagerInterface {
1483
+ #private;
1484
+ add(name: string, handler: MCPMethodHandler): void;
1485
+ method(name: string): MCPMethodHandler | undefined;
1486
+ }
1487
+
1488
+ /**
1489
+ * The modern method registry an {@link MCPServerInterface} dispatches through — the ONE
1490
+ * seam carrying both the built-in methods and any method a consumer adds.
1491
+ *
1492
+ * @remarks
1493
+ * `server/discover`, `tools/list`, `tools/call`, and `subscriptions/listen` are registered here at construction,
1494
+ * so they travel the SAME path as every later method: there is no second dispatch route
1495
+ * and no precedence puzzle. `add` under an existing name REPLACES that method — a
1496
+ * consumer overriding a built-in is an ordinary registration, not a special case. A name
1497
+ * with no handler is not an error state to model: {@link method} answers `undefined` and
1498
+ * the dispatch branch turns that into `-32601`.
1499
+ */
1500
+ export declare interface MCPMethodManagerInterface {
1501
+ /**
1502
+ * Register one modern method — replacing any handler already under that name.
1503
+ *
1504
+ * @param name - The JSON-RPC method name to answer (e.g. `'tools/call'`)
1505
+ * @param handler - The handler dispatched for that method
1506
+ */
1507
+ add(name: string, handler: MCPMethodHandler): void;
1508
+ /**
1509
+ * Find the handler registered for one method name.
1510
+ *
1511
+ * @param name - The JSON-RPC method name to resolve
1512
+ * @returns The registered handler, or `undefined` when the method is unregistered
1513
+ */
1514
+ method(name: string): MCPMethodHandler | undefined;
1515
+ }
1516
+
1517
+ /**
1518
+ * Derive the deployment-authenticated principal bound into signed request state.
1519
+ *
1520
+ * @param request - The parsed `tools/call` request
1521
+ * @param options - The per-request execution options
1522
+ * @returns The authenticated principal to bind into protected state
1523
+ */
1524
+ export declare type MCPPrincipalHandler = (request: JSONRPCRequest, options: MCPDispatchOptions) => string | Promise<string>;
1525
+
1526
+ /**
1527
+ * The validated per-request context projected from a modern request's reserved
1528
+ * `_meta` keys.
1529
+ *
1530
+ * @remarks
1531
+ * `version` remains a string so a syntactically valid but unsupported revision
1532
+ * reaches the dedicated unsupported-version path. `capabilities` is an open wire
1533
+ * record; `identity` is optional because client information is recommended but
1534
+ * not required.
1535
+ */
1536
+ export declare interface MCPRequestContext {
1537
+ readonly version: string;
1538
+ readonly capabilities: Readonly<Record<string, unknown>>;
1539
+ readonly identity?: MCPIdentity;
1540
+ }
1541
+
763
1542
  /**
764
1543
  * A transport-agnostic Model Context Protocol server — dispatches JSON-RPC 2.0
765
1544
  * requests over a live {@link ToolManagerInterface}, with NO transport coupling.
@@ -771,14 +1550,15 @@ export declare class MCPError extends Error {
771
1550
  * `JSON.parse`s the raw message (a failure → a `-32700` response), narrows it to
772
1551
  * a request (a non-request → a `-32600` response), dispatches, and serializes the
773
1552
  * response back to a string (`undefined` for a notification).
774
- * - **The method switch.** `initialize` negotiates the protocol version + advertises
775
- * the tools capability; `notifications/initialized` is a notification (no
776
- * response); `ping` returns `{}`; `tools/list` lists the registry's tools (its
777
- * `parameters` renamed to `inputSchema`); `tools/call` runs a tool by name (the
778
- * {@link ToolManagerInterface} isolates a tool throw into a `success: false`
779
- * result, which maps to an `isError: true` tool result — so the server adds NO
780
- * try/catch). An unknown method `-32601`; a `tools/call` with a missing /
781
- * non-string `name` `-32602`.
1553
+ * - **Dual-era dispatch.** A request carrying the reserved modern version key uses
1554
+ * modern metadata validation and the registered method seam. Every other request
1555
+ * uses the legacy `initialize` / `ping` / `tools/list` / `tools/call` switch. The
1556
+ * wire era is selected per request and never stored.
1557
+ * - **One modern seam.** `server/discover`, `tools/list`, `tools/call`, and
1558
+ * `subscriptions/listen` are
1559
+ * registered on `methods` at construction and resolved from it on every dispatch —
1560
+ * the same path a later method or a consumer's own takes, with an unregistered
1561
+ * method still answering `-32601`.
782
1562
  * - **Provider-agnostic.** Imports only core siblings — JSON-RPC + the tool registry,
783
1563
  * no HTTP, no model. Wire fields are narrowed via the contracts guards (no `as`).
784
1564
  * - **Observable (§13).** The owned `emitter` fires `request` at the top of every
@@ -789,7 +1569,7 @@ export declare class MCPError extends Error {
789
1569
  * ```ts
790
1570
  * const tools = createToolManager()
791
1571
  * tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
792
- * const server = new MCPServer({ name: 'demo', version: '1.0.0', tools })
1572
+ * const server = new MCPServer({ identity: { name: 'demo', version: '1.0.0' }, tools })
793
1573
  * await server.handle('{"jsonrpc":"2.0","method":"ping","id":1}') // '{"jsonrpc":"2.0","id":1,"result":{}}'
794
1574
  * ```
795
1575
  */
@@ -797,10 +1577,10 @@ export declare class MCPServer implements MCPServerInterface {
797
1577
  #private;
798
1578
  constructor(options: MCPServerOptions);
799
1579
  get emitter(): EmitterInterface<MCPServerEventMap>;
800
- get name(): string;
801
- get version(): string;
802
- dispatch(request: JSONRPCRequest): Promise<JSONRPCResponse | undefined>;
803
- handle(message: string): Promise<string | undefined>;
1580
+ get identity(): MCPIdentity;
1581
+ get methods(): MCPMethodManagerInterface;
1582
+ dispatch(request: JSONRPCRequest, options?: MCPDispatchOptions): Promise<JSONRPCResponse | MCPStream | undefined>;
1583
+ handle(message: string, options?: MCPDispatchOptions): Promise<string | MCPTextStream | undefined>;
804
1584
  }
805
1585
 
806
1586
  /**
@@ -809,16 +1589,16 @@ export declare class MCPServer implements MCPServerInterface {
809
1589
  * via `server.emitter.on`.
810
1590
  *
811
1591
  * @remarks
812
- * `request` fires at the TOP of every `dispatch` with the method and the
813
- * correlating id (`null` for a notification), BEFORE the method runs so an
814
- * observer sees every inbound call. Listener isolation is the emitter's (§13): a
1592
+ * `request` fires at the TOP of every `dispatch` with the method, correlating id
1593
+ * (`null` for a notification), and structurally selected wire era, BEFORE the
1594
+ * method runs — so an observer sees every inbound call. Listener isolation is the emitter's (§13): a
815
1595
  * listener throw is routed to the emitter's `error` handler (the `error` option),
816
1596
  * never onto this map, so a buggy observer can never corrupt a dispatch. Declared as
817
1597
  * a `type` alias (§4.5) so the type-literal satisfies `EventMap` structurally.
818
1598
  */
819
1599
  export declare type MCPServerEventMap = {
820
- /** A request is being dispatched — its `method` and correlating `id` (`null` for a notification). */
821
- readonly request: readonly [method: string, id: string | number | null];
1600
+ /** A request is being dispatched — its method, correlating id, and structural wire era. */
1601
+ readonly request: readonly [method: string, id: string | number | null, era: MCPEra];
822
1602
  /**
823
1603
  * A transport-level fault surfaced while a bound {@link MCPTransportInterface} was
824
1604
  * piping a reply out (a `send` throw or rejection from {@link bindServer}). A DOMAIN
@@ -827,26 +1607,26 @@ export declare type MCPServerEventMap = {
827
1607
  readonly error: readonly [error: unknown];
828
1608
  };
829
1609
 
830
- /** The server identity echoed in the MCP `initialize` result's `serverInfo`. */
831
- export declare interface MCPServerInfo {
832
- readonly name: string;
833
- readonly version: string;
834
- }
835
-
836
1610
  /**
837
1611
  * A transport-agnostic Model Context Protocol server — dispatches JSON-RPC 2.0
838
- * requests (`initialize` / `ping` / `tools/list` / `tools/call`) over a live
1612
+ * requests (the fixed legacy methods plus the modern subscription method) over a live
839
1613
  * {@link ToolManagerInterface}, with NO transport coupling (a transport layer
840
1614
  * pumps strings through `handle`).
841
1615
  *
842
1616
  * @remarks
843
1617
  * - **Two entry points.** `dispatch(request)` is the TYPED core: it takes an
844
1618
  * already-parsed {@link JSONRPCRequest}, runs the method, and resolves a
845
- * {@link JSONRPCResponse} — or `undefined` for a NOTIFICATION (a request with no
846
- * `id`). `handle(message)` is the STRING boundary: it `JSON.parse`s the raw
847
- * message, narrows it to a request, dispatches, and serializes the response back
848
- * to a string turning a parse failure into a `-32700` response and a non-request
849
- * into a `-32600` response, and returning `undefined` for a notification.
1619
+ * {@link JSONRPCResponse} — or an {@link MCPStream} for a held-open modern method, or
1620
+ * `undefined` for a NOTIFICATION (a request with no `id`). `handle(message)` is the
1621
+ * STRING boundary: it `JSON.parse`s the raw message, narrows it to a request,
1622
+ * dispatches, and serializes the answer back to a string (or an {@link MCPTextStream},
1623
+ * the same sequence already serialized) turning a parse failure into a `-32700`
1624
+ * response and a non-request into a `-32600` response, and returning `undefined` for a
1625
+ * notification.
1626
+ * - **One method seam.** Every modern method — the built-in `server/discover` /
1627
+ * `tools/list` / `tools/call` / `subscriptions/listen` included — is registered on `methods` and dispatched
1628
+ * from it, so a method added later travels the identical path and an unregistered one
1629
+ * still answers `-32601`.
850
1630
  * - **Provider-agnostic.** Imports only core siblings; it speaks JSON-RPC + the
851
1631
  * tool registry, with no HTTP, no model, and no backend coupling.
852
1632
  * - **Observable (§13).** The owned `emitter` ({@link MCPServerEventMap}) fires
@@ -855,57 +1635,120 @@ export declare interface MCPServerInfo {
855
1635
  */
856
1636
  export declare interface MCPServerInterface {
857
1637
  readonly emitter: EmitterInterface<MCPServerEventMap>;
858
- readonly name: string;
859
- readonly version: string;
1638
+ readonly identity: MCPIdentity;
1639
+ /** The modern method registry this server dispatches through (built-ins included). */
1640
+ readonly methods: MCPMethodManagerInterface;
860
1641
  /**
861
- * Dispatch an already-parsed request — run its method and resolve the response,
862
- * or `undefined` for a notification (a request with no `id`).
1642
+ * Dispatch an already-parsed request — run its method and resolve the answer, or
1643
+ * `undefined` for a notification (a request with no `id`).
1644
+ *
1645
+ * @remarks
1646
+ * A held-open modern method answers with an {@link MCPStream} instead of a response:
1647
+ * narrow the two apart with `Symbol.asyncIterator in result`. `options` is optional,
1648
+ * so a caller that cannot abort simply never supplies one.
863
1649
  *
864
1650
  * @param request - The parsed JSON-RPC request to dispatch
865
- * @returns The response, or `undefined` when the request was a notification
1651
+ * @param options - Per-request execution options (see {@link MCPDispatchOptions})
1652
+ * @returns The response, a held-open {@link MCPStream}, or `undefined` for a notification
866
1653
  */
867
- dispatch(request: JSONRPCRequest): Promise<JSONRPCResponse | undefined>;
1654
+ dispatch(request: JSONRPCRequest, options?: MCPDispatchOptions): Promise<JSONRPCResponse | MCPStream | undefined>;
868
1655
  /**
869
- * Handle a raw message string — parse it, dispatch, and serialize the response.
1656
+ * Handle a raw message string — parse it, dispatch, and serialize the answer.
870
1657
  *
871
1658
  * @remarks
872
1659
  * A `JSON.parse` failure resolves a serialized `-32700` (Parse error) response;
873
1660
  * a parsed value that is not a valid request resolves a serialized `-32600`
874
- * (Invalid Request) response; a notification resolves `undefined` (no response).
1661
+ * (Invalid Request) response; a notification resolves `undefined` (no response). A
1662
+ * held-open method resolves an {@link MCPTextStream} — the typed stream's mirror,
1663
+ * already serialized — so a transport writes each message with no second parse.
875
1664
  *
876
1665
  * @param message - The raw JSON-RPC message string
877
- * @returns The serialized response string, or `undefined` for a notification
1666
+ * @param options - Per-request execution options (see {@link MCPDispatchOptions})
1667
+ * @returns The serialized response string, an {@link MCPTextStream}, or `undefined` for a notification
878
1668
  */
879
- handle(message: string): Promise<string | undefined>;
1669
+ handle(message: string, options?: MCPDispatchOptions): Promise<string | MCPTextStream | undefined>;
880
1670
  }
881
1671
 
882
1672
  /**
883
- * Options for `createMCPServer` — the server identity (`name` / `version`), the
884
- * live {@link ToolManagerInterface} it exposes, an optional `description`, and the
1673
+ * Options for `createMCPServer` — the server {@link MCPIdentity}, the live
1674
+ * {@link ToolManagerInterface} it exposes, optional `instructions`, and the
885
1675
  * reserved `on` hooks (§8).
886
1676
  *
887
1677
  * @remarks
888
- * `name` / `version` identify the server in the `initialize` handshake
889
- * (`serverInfo`). `tools` is the live registry the server dispatches `tools/list`
890
- * / `tools/call` over — its `definitions()` advertise the tools and its
891
- * `execute()` runs a call (the manager already isolates a tool throw into a
892
- * `success: false` result, so the server adds none). `description` is a human label for
893
- * the server (reserved for a future `instructions` capability — unused by the
894
- * current dispatch). `on` is the §8 reserved key: initial listeners for the
895
- * server's {@link MCPServerEventMap}, wired at construction.
1678
+ * `identity` identifies the server in the `initialize` handshake (`serverInfo`).
1679
+ * `tools` is the live registry the server dispatches `tools/list` / `tools/call`
1680
+ * over — its `definitions()` advertise the tools and its `execute()` runs a call
1681
+ * (the manager already isolates a tool throw into a `success: false` result, so
1682
+ * the server adds none). `instructions` is the optional human guidance exposed
1683
+ * by `server/discover`. `cache` configures the modern cache stamps: `ttl` is the
1684
+ * freshness lifetime in milliseconds and `scope` defaults to `'private'`. `on`
1685
+ * is the §8 reserved key: initial listeners for the server's
1686
+ * {@link MCPServerEventMap}, wired at construction. `input` enables modern
1687
+ * `tools/call` multi-round trips: the consumer decides when input is needed and
1688
+ * supplies principal/signing/TTL policy, while MCP assigns the request key and
1689
+ * owns the protected wire round trip. `limit` configures the server's hostile-input
1690
+ * and live-subscription bounds; every omitted leaf uses {@link DEFAULT_MCP_LIMITS}.
896
1691
  */
897
1692
  export declare interface MCPServerOptions {
898
1693
  readonly on?: EmitterHooks<MCPServerEventMap>;
899
1694
  /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
900
1695
  readonly error?: EmitterErrorHandler;
901
- readonly name: string;
902
- readonly version: string;
1696
+ readonly identity: MCPIdentity;
903
1697
  /** The live tool registry the server exposes over `tools/list` / `tools/call`. */
904
1698
  readonly tools: ToolManagerInterface;
905
- /** A human label for the server (reserved for a future capability; unused by dispatch). */
906
- readonly description?: string;
1699
+ /** Optional human guidance exposed by `server/discover`. */
1700
+ readonly instructions?: string;
1701
+ /** Modern cache stamps; omitted values use the protocol-safe defaults. */
1702
+ readonly cache?: {
1703
+ readonly ttl?: number;
1704
+ readonly scope?: 'public' | 'private';
1705
+ };
1706
+ /** Optional multi-round-trip input mechanism; all signing and expiry policy is consumer-supplied. */
1707
+ readonly input?: MCPInputOptions;
1708
+ /** Optional event-driven producer for the modern `subscriptions/listen` method. */
1709
+ readonly subscription?: MCPSubscriptionOptions;
1710
+ /** Hostile-input and live-resource bounds; omitted leaves use secure defaults. */
1711
+ readonly limit?: MCPLimitOptions;
1712
+ }
1713
+
1714
+ /**
1715
+ * A held-open modern result: each `yield` is a notification (a {@link JSONRPCRequest}
1716
+ * with no `id`); the `return` value is the terminating response.
1717
+ *
1718
+ * @remarks
1719
+ * Held-open closure is a RESULT in the modern revision, not an out-of-band event, so it
1720
+ * arrives where a result arrives — the generator's `return`. Consuming a stream and
1721
+ * consuming a unary response therefore end the same way, and a transport narrows the two
1722
+ * apart at ONE point (`Symbol.asyncIterator in result`), at the place that already pumps
1723
+ * messages onto the wire.
1724
+ */
1725
+ export declare type MCPStream = AsyncGenerator<JSONRPCRequest, JSONRPCResponse>;
1726
+
1727
+ /**
1728
+ * Produce notifications for one honoured `subscriptions/listen` filter.
1729
+ *
1730
+ * @remarks
1731
+ * The producer parks on its own event source while idle and ends its iterable to close the
1732
+ * subscription gracefully. `options.signal` is the per-request cancellation signal; a
1733
+ * producer that needs cancellation observes it directly rather than polling.
1734
+ *
1735
+ * @param notifications - The requested filter intersected with the server's supported filter
1736
+ * @param options - The per-request execution options
1737
+ * @returns An event-driven source of server notifications
1738
+ */
1739
+ export declare type MCPSubscriptionHandler = (notifications: SubscriptionFilter, options: MCPDispatchOptions) => AsyncIterable<JSONRPCRequest> | Promise<AsyncIterable<JSONRPCRequest>>;
1740
+
1741
+ /** Configuration for the server's built-in `subscriptions/listen` method. */
1742
+ export declare interface MCPSubscriptionOptions {
1743
+ /** The notification filter this server can actually honour. */
1744
+ readonly notifications: SubscriptionFilter;
1745
+ /** Open the producer for one honoured filter. */
1746
+ readonly listen: MCPSubscriptionHandler;
907
1747
  }
908
1748
 
1749
+ /** The string-boundary mirror of {@link MCPStream} — the same sequence, already serialized. */
1750
+ export declare type MCPTextStream = AsyncGenerator<string, string>;
1751
+
909
1752
  /**
910
1753
  * One entry of the MCP `tools/list` result — a tool's `name`, optional
911
1754
  * `description`, and its JSON-Schema `inputSchema`.
@@ -921,22 +1764,6 @@ export declare interface MCPToolDescriptor {
921
1764
  readonly inputSchema: Readonly<Record<string, unknown>>;
922
1765
  }
923
1766
 
924
- /**
925
- * The MCP `tools/call` result — the executed tool's output as `content` blocks,
926
- * with `isError` flagging a tool failure.
927
- *
928
- * @remarks
929
- * A success carries the tool's value serialized into one `text` content block; a
930
- * tool FAILURE (the `success: false` branch the registry isolated) carries its
931
- * `error` text in `content` AND sets `isError: true`, so the model sees the
932
- * failure as a tool result it can react to rather than a protocol error.
933
- */
934
- export declare interface MCPToolResult {
935
- readonly content: readonly MCPContent[];
936
- /** `true` when the tool failed — its error text is in `content`. */
937
- readonly isError?: boolean;
938
- }
939
-
940
1767
  /**
941
1768
  * A duplex message channel an environment face provides to the pure engine — the
942
1769
  * one port `bindServer` and `bindClient` (`./helpers.js`) pipe an
@@ -962,6 +1789,9 @@ export declare interface MCPTransportInterface {
962
1789
  readonly close: () => void | Promise<void>;
963
1790
  }
964
1791
 
1792
+ /** A protocol revision supported by this MCP package. */
1793
+ export declare type MCPVersion = '2026-07-28' | '2025-11-25' | '2025-06-18';
1794
+
965
1795
  /**
966
1796
  * Narrow an already-parsed value to a {@link JSONRPCMessage}, or `undefined` when
967
1797
  * it is not one.
@@ -984,16 +1814,132 @@ export declare interface MCPTransportInterface {
984
1814
  */
985
1815
  export declare function parseJSONRPCMessage(value: unknown): JSONRPCMessage | undefined;
986
1816
 
1817
+ /**
1818
+ * Parse the verified value embedded in an opaque signed `requestState` token.
1819
+ *
1820
+ * @remarks
1821
+ * This parser does not verify the HMAC; {@link import('@orkestrel/server').verifyToken}
1822
+ * performs that boundary first and returns the JSON string parsed here. The protected
1823
+ * payload binds the authenticated principal, token lifetime, originating request id,
1824
+ * server-assigned input key, tool name, and optional consumer state. Total over malformed
1825
+ * or hostile input.
1826
+ *
1827
+ * @param value - The HMAC-verified token value to parse
1828
+ * @returns The protected input state, or `undefined` when malformed
1829
+ *
1830
+ * @example
1831
+ * ```ts
1832
+ * parseMCPInputState('{"principal":"user-1","ttl":1000,"origin":1,"key":"k","name":"reply"}')
1833
+ * // { principal: 'user-1', ttl: 1000, origin: 1, key: 'k', name: 'reply' }
1834
+ * ```
1835
+ */
1836
+ export declare function parseMCPInputState(value: unknown): MCPInputState | undefined;
1837
+
1838
+ /**
1839
+ * Parse the reserved modern request metadata into an {@link MCPRequestContext}.
1840
+ *
1841
+ * @remarks
1842
+ * This is the validity step after {@link isModernRequest}: a defined result can
1843
+ * only come from a guard-positive request, while a guard-positive request returns
1844
+ * `undefined` exactly when its required modern metadata is malformed. The version
1845
+ * must be a string but need not be supported; unsupported strings belong to the
1846
+ * dedicated protocol-version error path. Client identity is optional, but when
1847
+ * present it must carry string `name` and `version` members. Total over hostile and
1848
+ * malformed input.
1849
+ *
1850
+ * @param value - The already-parsed request candidate to coerce
1851
+ * @returns The validated modern request context, or `undefined`
1852
+ */
1853
+ export declare function parseRequestContext(value: unknown): MCPRequestContext | undefined;
1854
+
1855
+ /**
1856
+ * Pump an {@link MCPTextStream} onto a transport — every notification in order, then the
1857
+ * terminating response.
1858
+ *
1859
+ * @remarks
1860
+ * The generator's `return` value is a message like any other on the wire: it is sent
1861
+ * LAST and closes the exchange. Sends are awaited one at a time so the transport
1862
+ * receives the sequence in the order the method produced it.
1863
+ *
1864
+ * @param stream - The serialized held-open result to write out
1865
+ * @param transport - The duplex channel to write each message to
1866
+ * @returns Resolves once the terminating response has been sent
1867
+ *
1868
+ * @example
1869
+ * ```ts
1870
+ * const answer = await server.handle(message)
1871
+ * if (typeof answer !== 'string') await sendStream(answer, transport)
1872
+ * ```
1873
+ */
1874
+ export declare function sendStream(stream: MCPTextStream, transport: MCPTransportInterface): Promise<void>;
1875
+
1876
+ /**
1877
+ * Serialize a typed {@link MCPStream} into its string mirror — each yielded
1878
+ * notification and the terminating response, already `JSON.stringify`d.
1879
+ *
1880
+ * @remarks
1881
+ * The string-boundary half of the held-open arm: `handle` returns this so a transport
1882
+ * writes each message with no second parse, exactly as it writes a unary reply string.
1883
+ * The terminating response arrives as the returned generator's OWN `return` value, so a
1884
+ * consumer distinguishes "one more notification" from "this is the answer" without a
1885
+ * sentinel.
1886
+ *
1887
+ * @param stream - The typed held-open result to serialize
1888
+ * @returns The same sequence with every message serialized to a string
1889
+ *
1890
+ * @example
1891
+ * ```ts
1892
+ * const text = serializeStream(stream)
1893
+ * for (let next = await text.next(); ; next = await text.next()) {
1894
+ * if (next.done === true) return next.value // the terminating response, serialized
1895
+ * log(next.value) // one serialized notification
1896
+ * }
1897
+ * ```
1898
+ */
1899
+ export declare function serializeStream(stream: MCPStream): MCPTextStream;
1900
+
1901
+ /**
1902
+ * Stamp a subscription notification with the request id reserved for its held-open stream.
1903
+ *
1904
+ * @param notification - The notification to copy and stamp
1905
+ * @param id - The `subscriptions/listen` request id
1906
+ * @returns The stamped notification, preserving its other params and metadata
1907
+ */
1908
+ export declare function stampSubscriptionNotification(notification: JSONRPCRequest, id: string | number): JSONRPCRequest;
1909
+
1910
+ /** The notification families a client may opt in to on a `subscriptions/listen` stream. */
1911
+ export declare interface SubscriptionFilter {
1912
+ /** Receive `notifications/tools/list_changed` when the server produces it. */
1913
+ readonly toolsListChanged?: boolean;
1914
+ /** Receive `notifications/prompts/list_changed` when the server produces it. */
1915
+ readonly promptsListChanged?: boolean;
1916
+ /** Receive `notifications/resources/list_changed` when the server produces it. */
1917
+ readonly resourcesListChanged?: boolean;
1918
+ /** Receive `notifications/resources/updated` for these resource URIs. */
1919
+ readonly resourceSubscriptions?: readonly string[];
1920
+ }
1921
+
1922
+ /** The terminating result returned when a `subscriptions/listen` stream closes gracefully. */
1923
+ export declare interface SubscriptionsListenResult {
1924
+ readonly resultType: 'complete';
1925
+ readonly _meta: SubscriptionsListenResultMetaObject;
1926
+ }
1927
+
1928
+ /** The required metadata on a graceful `subscriptions/listen` result. */
1929
+ export declare interface SubscriptionsListenResultMetaObject extends Readonly<Record<string, unknown>> {
1930
+ /** The JSON-RPC id of the `subscriptions/listen` request whose stream is closing. */
1931
+ readonly 'io.modelcontextprotocol/subscriptionId': string | number;
1932
+ }
1933
+
987
1934
  /**
988
1935
  * The MCP protocol revisions this server can negotiate.
989
1936
  *
990
1937
  * @remarks
991
1938
  * `initialize` echoes the client's requested `protocolVersion` when it appears in
992
- * this list, else falls back to {@link MCP_PROTOCOL_VERSION}. Frozen so the list is
993
- * an immutable contract. The package does not advertise `2025-03-26` because that
994
- * revision mandates JSON-RPC batching, while this package accepts only individual
995
- * JSON-RPC messages.
1939
+ * this list. Frozen in client-preference and discovery-advertisement order. The
1940
+ * package does not advertise `2025-03-26` because that revision mandates JSON-RPC
1941
+ * batching, while this package accepts only individual JSON-RPC messages.
996
1942
  */
997
- export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
1943
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly MCPVersion[];
998
1944
 
999
1945
  export { }