@orkestrel/mcp 0.0.8 → 0.0.9

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
+ * ```
690
+ */
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
+ * ```
318
703
  */
319
- export declare function initializeResult(name: string, version: string, requested?: string): Readonly<Record<string, unknown>>;
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";
509
996
 
510
- /** The MCP protocol revision this server implements (the default negotiated version). */
511
- export declare const MCP_PROTOCOL_VERSION = "2025-06-18";
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;
1021
+
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,47 @@ 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
+ /** Per-request execution options every dispatched handler receives. */
1292
+ export declare interface MCPDispatchOptions {
1293
+ /** Aborts when the bound transport can observe that the caller's request has ended. */
1294
+ readonly signal?: AbortSignal;
1295
+ }
1296
+
1297
+ /** One consumer-requested form elicitation, before MCP assigns its map key and signs state. */
1298
+ export declare interface MCPElicitation {
1299
+ readonly request: ElicitRequestFormParams;
1300
+ readonly state?: string;
1301
+ }
1302
+
1303
+ /** The wire era selected by an MCP request's structure. */
1304
+ export declare type MCPEra = 'modern' | 'legacy';
1305
+
732
1306
  /**
733
1307
  * A remote Model Context Protocol JSON-RPC error, preserving its machine-readable
734
1308
  * numeric code and optional structured context.
@@ -737,13 +1311,19 @@ export declare interface MCPContent {
737
1311
  * {@link MCPClient} throws this error only for a remote JSON-RPC `error` response.
738
1312
  * Local lifecycle and transport conditions such as disconnects and request timeouts
739
1313
  * remain plain `Error`s. `context` carries the response's optional `error.data`
740
- * unchanged and is `undefined` when the peer omitted it.
1314
+ * unchanged and is `undefined` when the peer omitted it. This includes the modern
1315
+ * reserved paths: `-32020` carries no context, `-32021` may carry
1316
+ * `requiredCapabilities`, and `-32022` carries the peer's `supported` revisions and
1317
+ * `requested` revision for negotiation recovery.
741
1318
  *
742
1319
  * @example
743
1320
  * ```ts
744
- * const error = new MCPError('Method not found', -32601, { method: 'missing' })
745
- * error.code // -32601
746
- * error.context // { method: 'missing' }
1321
+ * const error = new MCPError('Unsupported protocol version', -32022, {
1322
+ * supported: ['2026-07-28'],
1323
+ * requested: '2024-11-05',
1324
+ * })
1325
+ * error.code // -32022
1326
+ * error.context // { supported: ['2026-07-28'], requested: '2024-11-05' }
747
1327
  * ```
748
1328
  */
749
1329
  export declare class MCPError extends Error {
@@ -760,6 +1340,188 @@ export declare class MCPError extends Error {
760
1340
  constructor(message: string, code: number, context?: unknown);
761
1341
  }
762
1342
 
1343
+ /** The identity (`name` / `version`) of an MCP server or client. */
1344
+ export declare interface MCPIdentity {
1345
+ readonly name: string;
1346
+ readonly version: string;
1347
+ }
1348
+
1349
+ /** The call-in-hand context supplied to an {@link MCPInputHandler}. */
1350
+ export declare interface MCPInputContext {
1351
+ readonly request: JSONRPCRequest;
1352
+ readonly name: string;
1353
+ readonly arguments: Readonly<Record<string, unknown>>;
1354
+ readonly response?: ElicitResult;
1355
+ readonly state?: string;
1356
+ }
1357
+
1358
+ /**
1359
+ * Decide whether the current `tools/call` needs operator input.
1360
+ *
1361
+ * @param context - The original call plus a verified response/state on a retry
1362
+ * @param options - The per-request execution options
1363
+ * @returns A form elicitation to send, or `undefined` to continue into the tool registry
1364
+ */
1365
+ export declare type MCPInputHandler = (context: MCPInputContext, options: MCPDispatchOptions) => MCPElicitation | undefined | Promise<MCPElicitation | undefined>;
1366
+
1367
+ /** Consumer policy for the server's multi-round-trip input mechanism. */
1368
+ export declare interface MCPInputOptions {
1369
+ /** HMAC secret or `[current, ...older]` rotation list used by `signToken` / `verifyToken`. */
1370
+ readonly secret: TokenSecret;
1371
+ /** Token lifetime in milliseconds; required so MCP never invents an expiry policy. */
1372
+ readonly ttl: number;
1373
+ /** Resolve the authenticated principal for the call in hand. */
1374
+ readonly principal: MCPPrincipalHandler;
1375
+ /** Decide whether the call needs a form elicitation, including on verified retries. */
1376
+ readonly elicit: MCPInputHandler;
1377
+ }
1378
+
1379
+ /** The integrity-protected payload carried inside an opaque `requestState` token. */
1380
+ export declare interface MCPInputState {
1381
+ readonly principal: string;
1382
+ readonly ttl: number;
1383
+ readonly origin: string | number;
1384
+ readonly key: string;
1385
+ readonly name: string;
1386
+ readonly state?: string;
1387
+ }
1388
+
1389
+ /** Limits applied by {@link isBoundedJSON} to one JSON value. */
1390
+ export declare interface MCPJSONLimitOptions {
1391
+ /** Maximum serialized UTF-8 bytes. */
1392
+ readonly bytes: number;
1393
+ /** Maximum total enumerable keys; omitted when bytes alone bound breadth. */
1394
+ readonly keys?: number;
1395
+ /** Maximum array/object nesting depth. */
1396
+ readonly depth: number;
1397
+ }
1398
+
1399
+ /** Configurable hostile-input and live-resource bounds for an MCP server. */
1400
+ export declare interface MCPLimitOptions {
1401
+ /** Maximum UTF-8 bytes accepted by the raw string boundary. */
1402
+ readonly message?: number;
1403
+ /** Maximum serialized UTF-8 bytes accepted in one `_meta` value. */
1404
+ readonly metadata?: number;
1405
+ /** Maximum total enumerable keys accepted across one `_meta` value. */
1406
+ readonly keys?: number;
1407
+ /** Maximum UTF-8 bytes accepted in one protected `requestState`. */
1408
+ readonly state?: number;
1409
+ /** Maximum serialized UTF-8 bytes accepted from one produced tool content value. */
1410
+ readonly content?: number;
1411
+ /** Maximum simultaneously live built-in subscription streams. */
1412
+ readonly subscriptions?: number;
1413
+ /** Maximum nesting depth accepted by bounded JSON values. */
1414
+ readonly depth?: number;
1415
+ }
1416
+
1417
+ /**
1418
+ * The MCP `tools/list` result — tool descriptors plus optional modern result
1419
+ * stamps.
1420
+ *
1421
+ * @remarks
1422
+ * The wire field names remain verbatim. A modern result requires `resultType`,
1423
+ * `ttlMs`, and `cacheScope`; they remain optional here because the same result
1424
+ * shape also models the unstamped legacy response.
1425
+ */
1426
+ export declare interface MCPListResult {
1427
+ readonly tools: readonly MCPToolDescriptor[];
1428
+ readonly resultType?: 'complete';
1429
+ readonly ttlMs?: number;
1430
+ readonly cacheScope?: 'public' | 'private';
1431
+ readonly _meta?: Readonly<Record<string, unknown>>;
1432
+ }
1433
+
1434
+ /**
1435
+ * One modern method, registered on the seam that dispatches it.
1436
+ *
1437
+ * @remarks
1438
+ * `undefined` answers nothing (the notification arm); an {@link MCPStream} holds the
1439
+ * request open. `options.signal` aborts when the caller's request ends — what a handler
1440
+ * does with it is the handler's decision, never this package's.
1441
+ *
1442
+ * @param request - The parsed modern request being dispatched
1443
+ * @param options - The per-request execution options (see {@link MCPDispatchOptions})
1444
+ * @returns The terminating response, a held-open {@link MCPStream}, or `undefined` for no answer
1445
+ */
1446
+ export declare type MCPMethodHandler = (request: JSONRPCRequest, options: MCPDispatchOptions) => Promise<JSONRPCResponse | MCPStream | undefined>;
1447
+
1448
+ /**
1449
+ * The modern method registry an {@link import('./types.js').MCPServerInterface}
1450
+ * dispatches through — a name-keyed store of {@link MCPMethodHandler}s that owns its
1451
+ * map rather than exposing one.
1452
+ *
1453
+ * @remarks
1454
+ * - **One seam.** The server registers its built-in modern methods here at construction
1455
+ * and resolves EVERY modern method from here, so a consumer's method and a built-in
1456
+ * are the same kind of thing on the same path.
1457
+ * - **Registration is a write, not a merge.** `add` under a name already present
1458
+ * REPLACES it, which is how a consumer overrides a built-in; there is no precedence
1459
+ * rule to remember.
1460
+ * - **A narrower contract than a `Map`.** Callers register and resolve; they cannot
1461
+ * iterate, clear, or otherwise reach the server's internal state through it.
1462
+ *
1463
+ * @example
1464
+ * ```ts
1465
+ * const methods = new MCPMethodManager()
1466
+ * methods.add('tools/list', async (request) => buildJSONRPCResult(request.id ?? null, { tools: [] }))
1467
+ * methods.method('tools/list') // the handler
1468
+ * methods.method('tools/nope') // undefined → the dispatch branch answers -32601
1469
+ * ```
1470
+ */
1471
+ export declare class MCPMethodManager implements MCPMethodManagerInterface {
1472
+ #private;
1473
+ add(name: string, handler: MCPMethodHandler): void;
1474
+ method(name: string): MCPMethodHandler | undefined;
1475
+ }
1476
+
1477
+ /**
1478
+ * The modern method registry an {@link MCPServerInterface} dispatches through — the ONE
1479
+ * seam carrying both the built-in methods and any method a consumer adds.
1480
+ *
1481
+ * @remarks
1482
+ * `server/discover`, `tools/list`, `tools/call`, and `subscriptions/listen` are registered here at construction,
1483
+ * so they travel the SAME path as every later method: there is no second dispatch route
1484
+ * and no precedence puzzle. `add` under an existing name REPLACES that method — a
1485
+ * consumer overriding a built-in is an ordinary registration, not a special case. A name
1486
+ * with no handler is not an error state to model: {@link method} answers `undefined` and
1487
+ * the dispatch branch turns that into `-32601`.
1488
+ */
1489
+ export declare interface MCPMethodManagerInterface {
1490
+ /**
1491
+ * Register one modern method — replacing any handler already under that name.
1492
+ *
1493
+ * @param name - The JSON-RPC method name to answer (e.g. `'tools/call'`)
1494
+ * @param handler - The handler dispatched for that method
1495
+ */
1496
+ add(name: string, handler: MCPMethodHandler): void;
1497
+ /**
1498
+ * Find the handler registered for one method name.
1499
+ *
1500
+ * @param name - The JSON-RPC method name to resolve
1501
+ * @returns The registered handler, or `undefined` when the method is unregistered
1502
+ */
1503
+ method(name: string): MCPMethodHandler | undefined;
1504
+ }
1505
+
1506
+ /** Derive the deployment-authenticated principal bound into signed request state. */
1507
+ export declare type MCPPrincipalHandler = (request: JSONRPCRequest) => string | Promise<string>;
1508
+
1509
+ /**
1510
+ * The validated per-request context projected from a modern request's reserved
1511
+ * `_meta` keys.
1512
+ *
1513
+ * @remarks
1514
+ * `version` remains a string so a syntactically valid but unsupported revision
1515
+ * reaches the dedicated unsupported-version path. `capabilities` is an open wire
1516
+ * record; `identity` is optional because client information is recommended but
1517
+ * not required.
1518
+ */
1519
+ export declare interface MCPRequestContext {
1520
+ readonly version: string;
1521
+ readonly capabilities: Readonly<Record<string, unknown>>;
1522
+ readonly identity?: MCPIdentity;
1523
+ }
1524
+
763
1525
  /**
764
1526
  * A transport-agnostic Model Context Protocol server — dispatches JSON-RPC 2.0
765
1527
  * requests over a live {@link ToolManagerInterface}, with NO transport coupling.
@@ -771,14 +1533,15 @@ export declare class MCPError extends Error {
771
1533
  * `JSON.parse`s the raw message (a failure → a `-32700` response), narrows it to
772
1534
  * a request (a non-request → a `-32600` response), dispatches, and serializes the
773
1535
  * 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`.
1536
+ * - **Dual-era dispatch.** A request carrying the reserved modern version key uses
1537
+ * modern metadata validation and the registered method seam. Every other request
1538
+ * uses the legacy `initialize` / `ping` / `tools/list` / `tools/call` switch. The
1539
+ * wire era is selected per request and never stored.
1540
+ * - **One modern seam.** `server/discover`, `tools/list`, `tools/call`, and
1541
+ * `subscriptions/listen` are
1542
+ * registered on `methods` at construction and resolved from it on every dispatch —
1543
+ * the same path a later method or a consumer's own takes, with an unregistered
1544
+ * method still answering `-32601`.
782
1545
  * - **Provider-agnostic.** Imports only core siblings — JSON-RPC + the tool registry,
783
1546
  * no HTTP, no model. Wire fields are narrowed via the contracts guards (no `as`).
784
1547
  * - **Observable (§13).** The owned `emitter` fires `request` at the top of every
@@ -789,7 +1552,7 @@ export declare class MCPError extends Error {
789
1552
  * ```ts
790
1553
  * const tools = createToolManager()
791
1554
  * 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 })
1555
+ * const server = new MCPServer({ identity: { name: 'demo', version: '1.0.0' }, tools })
793
1556
  * await server.handle('{"jsonrpc":"2.0","method":"ping","id":1}') // '{"jsonrpc":"2.0","id":1,"result":{}}'
794
1557
  * ```
795
1558
  */
@@ -797,10 +1560,10 @@ export declare class MCPServer implements MCPServerInterface {
797
1560
  #private;
798
1561
  constructor(options: MCPServerOptions);
799
1562
  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>;
1563
+ get identity(): MCPIdentity;
1564
+ get methods(): MCPMethodManagerInterface;
1565
+ dispatch(request: JSONRPCRequest, options?: MCPDispatchOptions): Promise<JSONRPCResponse | MCPStream | undefined>;
1566
+ handle(message: string, options?: MCPDispatchOptions): Promise<string | MCPTextStream | undefined>;
804
1567
  }
805
1568
 
806
1569
  /**
@@ -809,16 +1572,16 @@ export declare class MCPServer implements MCPServerInterface {
809
1572
  * via `server.emitter.on`.
810
1573
  *
811
1574
  * @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
1575
+ * `request` fires at the TOP of every `dispatch` with the method, correlating id
1576
+ * (`null` for a notification), and structurally selected wire era, BEFORE the
1577
+ * method runs — so an observer sees every inbound call. Listener isolation is the emitter's (§13): a
815
1578
  * listener throw is routed to the emitter's `error` handler (the `error` option),
816
1579
  * never onto this map, so a buggy observer can never corrupt a dispatch. Declared as
817
1580
  * a `type` alias (§4.5) so the type-literal satisfies `EventMap` structurally.
818
1581
  */
819
1582
  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];
1583
+ /** A request is being dispatched — its method, correlating id, and structural wire era. */
1584
+ readonly request: readonly [method: string, id: string | number | null, era: MCPEra];
822
1585
  /**
823
1586
  * A transport-level fault surfaced while a bound {@link MCPTransportInterface} was
824
1587
  * piping a reply out (a `send` throw or rejection from {@link bindServer}). A DOMAIN
@@ -827,26 +1590,26 @@ export declare type MCPServerEventMap = {
827
1590
  readonly error: readonly [error: unknown];
828
1591
  };
829
1592
 
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
1593
  /**
837
1594
  * A transport-agnostic Model Context Protocol server — dispatches JSON-RPC 2.0
838
- * requests (`initialize` / `ping` / `tools/list` / `tools/call`) over a live
1595
+ * requests (the fixed legacy methods plus the modern subscription method) over a live
839
1596
  * {@link ToolManagerInterface}, with NO transport coupling (a transport layer
840
1597
  * pumps strings through `handle`).
841
1598
  *
842
1599
  * @remarks
843
1600
  * - **Two entry points.** `dispatch(request)` is the TYPED core: it takes an
844
1601
  * 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.
1602
+ * {@link JSONRPCResponse} — or an {@link MCPStream} for a held-open modern method, or
1603
+ * `undefined` for a NOTIFICATION (a request with no `id`). `handle(message)` is the
1604
+ * STRING boundary: it `JSON.parse`s the raw message, narrows it to a request,
1605
+ * dispatches, and serializes the answer back to a string (or an {@link MCPTextStream},
1606
+ * the same sequence already serialized) turning a parse failure into a `-32700`
1607
+ * response and a non-request into a `-32600` response, and returning `undefined` for a
1608
+ * notification.
1609
+ * - **One method seam.** Every modern method — the built-in `server/discover` /
1610
+ * `tools/list` / `tools/call` / `subscriptions/listen` included — is registered on `methods` and dispatched
1611
+ * from it, so a method added later travels the identical path and an unregistered one
1612
+ * still answers `-32601`.
850
1613
  * - **Provider-agnostic.** Imports only core siblings; it speaks JSON-RPC + the
851
1614
  * tool registry, with no HTTP, no model, and no backend coupling.
852
1615
  * - **Observable (§13).** The owned `emitter` ({@link MCPServerEventMap}) fires
@@ -855,57 +1618,120 @@ export declare interface MCPServerInfo {
855
1618
  */
856
1619
  export declare interface MCPServerInterface {
857
1620
  readonly emitter: EmitterInterface<MCPServerEventMap>;
858
- readonly name: string;
859
- readonly version: string;
1621
+ readonly identity: MCPIdentity;
1622
+ /** The modern method registry this server dispatches through (built-ins included). */
1623
+ readonly methods: MCPMethodManagerInterface;
860
1624
  /**
861
- * Dispatch an already-parsed request — run its method and resolve the response,
862
- * or `undefined` for a notification (a request with no `id`).
1625
+ * Dispatch an already-parsed request — run its method and resolve the answer, or
1626
+ * `undefined` for a notification (a request with no `id`).
1627
+ *
1628
+ * @remarks
1629
+ * A held-open modern method answers with an {@link MCPStream} instead of a response:
1630
+ * narrow the two apart with `Symbol.asyncIterator in result`. `options` is optional,
1631
+ * so a caller that cannot abort simply never supplies one.
863
1632
  *
864
1633
  * @param request - The parsed JSON-RPC request to dispatch
865
- * @returns The response, or `undefined` when the request was a notification
1634
+ * @param options - Per-request execution options (see {@link MCPDispatchOptions})
1635
+ * @returns The response, a held-open {@link MCPStream}, or `undefined` for a notification
866
1636
  */
867
- dispatch(request: JSONRPCRequest): Promise<JSONRPCResponse | undefined>;
1637
+ dispatch(request: JSONRPCRequest, options?: MCPDispatchOptions): Promise<JSONRPCResponse | MCPStream | undefined>;
868
1638
  /**
869
- * Handle a raw message string — parse it, dispatch, and serialize the response.
1639
+ * Handle a raw message string — parse it, dispatch, and serialize the answer.
870
1640
  *
871
1641
  * @remarks
872
1642
  * A `JSON.parse` failure resolves a serialized `-32700` (Parse error) response;
873
1643
  * a parsed value that is not a valid request resolves a serialized `-32600`
874
- * (Invalid Request) response; a notification resolves `undefined` (no response).
1644
+ * (Invalid Request) response; a notification resolves `undefined` (no response). A
1645
+ * held-open method resolves an {@link MCPTextStream} — the typed stream's mirror,
1646
+ * already serialized — so a transport writes each message with no second parse.
875
1647
  *
876
1648
  * @param message - The raw JSON-RPC message string
877
- * @returns The serialized response string, or `undefined` for a notification
1649
+ * @param options - Per-request execution options (see {@link MCPDispatchOptions})
1650
+ * @returns The serialized response string, an {@link MCPTextStream}, or `undefined` for a notification
878
1651
  */
879
- handle(message: string): Promise<string | undefined>;
1652
+ handle(message: string, options?: MCPDispatchOptions): Promise<string | MCPTextStream | undefined>;
880
1653
  }
881
1654
 
882
1655
  /**
883
- * Options for `createMCPServer` — the server identity (`name` / `version`), the
884
- * live {@link ToolManagerInterface} it exposes, an optional `description`, and the
1656
+ * Options for `createMCPServer` — the server {@link MCPIdentity}, the live
1657
+ * {@link ToolManagerInterface} it exposes, optional `instructions`, and the
885
1658
  * reserved `on` hooks (§8).
886
1659
  *
887
1660
  * @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.
1661
+ * `identity` identifies the server in the `initialize` handshake (`serverInfo`).
1662
+ * `tools` is the live registry the server dispatches `tools/list` / `tools/call`
1663
+ * over — its `definitions()` advertise the tools and its `execute()` runs a call
1664
+ * (the manager already isolates a tool throw into a `success: false` result, so
1665
+ * the server adds none). `instructions` is the optional human guidance exposed
1666
+ * by `server/discover`. `cache` configures the modern cache stamps: `ttl` is the
1667
+ * freshness lifetime in milliseconds and `scope` defaults to `'private'`. `on`
1668
+ * is the §8 reserved key: initial listeners for the server's
1669
+ * {@link MCPServerEventMap}, wired at construction. `input` enables modern
1670
+ * `tools/call` multi-round trips: the consumer decides when input is needed and
1671
+ * supplies principal/signing/TTL policy, while MCP assigns the request key and
1672
+ * owns the protected wire round trip. `limit` configures the server's hostile-input
1673
+ * and live-subscription bounds; every omitted leaf uses {@link DEFAULT_MCP_LIMITS}.
896
1674
  */
897
1675
  export declare interface MCPServerOptions {
898
1676
  readonly on?: EmitterHooks<MCPServerEventMap>;
899
1677
  /** The emitter's listener-error handler (AGENTS §13) — a listener throw routes here, not to a domain event. */
900
1678
  readonly error?: EmitterErrorHandler;
901
- readonly name: string;
902
- readonly version: string;
1679
+ readonly identity: MCPIdentity;
903
1680
  /** The live tool registry the server exposes over `tools/list` / `tools/call`. */
904
1681
  readonly tools: ToolManagerInterface;
905
- /** A human label for the server (reserved for a future capability; unused by dispatch). */
906
- readonly description?: string;
1682
+ /** Optional human guidance exposed by `server/discover`. */
1683
+ readonly instructions?: string;
1684
+ /** Modern cache stamps; omitted values use the protocol-safe defaults. */
1685
+ readonly cache?: {
1686
+ readonly ttl?: number;
1687
+ readonly scope?: 'public' | 'private';
1688
+ };
1689
+ /** Optional multi-round-trip input mechanism; all signing and expiry policy is consumer-supplied. */
1690
+ readonly input?: MCPInputOptions;
1691
+ /** Optional event-driven producer for the modern `subscriptions/listen` method. */
1692
+ readonly subscription?: MCPSubscriptionOptions;
1693
+ /** Hostile-input and live-resource bounds; omitted leaves use secure defaults. */
1694
+ readonly limit?: MCPLimitOptions;
1695
+ }
1696
+
1697
+ /**
1698
+ * A held-open modern result: each `yield` is a notification (a {@link JSONRPCRequest}
1699
+ * with no `id`); the `return` value is the terminating response.
1700
+ *
1701
+ * @remarks
1702
+ * Held-open closure is a RESULT in the modern revision, not an out-of-band event, so it
1703
+ * arrives where a result arrives — the generator's `return`. Consuming a stream and
1704
+ * consuming a unary response therefore end the same way, and a transport narrows the two
1705
+ * apart at ONE point (`Symbol.asyncIterator in result`), at the place that already pumps
1706
+ * messages onto the wire.
1707
+ */
1708
+ export declare type MCPStream = AsyncGenerator<JSONRPCRequest, JSONRPCResponse>;
1709
+
1710
+ /**
1711
+ * Produce notifications for one honoured `subscriptions/listen` filter.
1712
+ *
1713
+ * @remarks
1714
+ * The producer parks on its own event source while idle and ends its iterable to close the
1715
+ * subscription gracefully. `options.signal` is the per-request cancellation signal; a
1716
+ * producer that needs cancellation observes it directly rather than polling.
1717
+ *
1718
+ * @param notifications - The requested filter intersected with the server's supported filter
1719
+ * @param options - The per-request execution options
1720
+ * @returns An event-driven source of server notifications
1721
+ */
1722
+ export declare type MCPSubscriptionHandler = (notifications: SubscriptionFilter, options: MCPDispatchOptions) => AsyncIterable<JSONRPCRequest> | Promise<AsyncIterable<JSONRPCRequest>>;
1723
+
1724
+ /** Configuration for the server's built-in `subscriptions/listen` method. */
1725
+ export declare interface MCPSubscriptionOptions {
1726
+ /** The notification filter this server can actually honour. */
1727
+ readonly notifications: SubscriptionFilter;
1728
+ /** Open the producer for one honoured filter. */
1729
+ readonly listen: MCPSubscriptionHandler;
907
1730
  }
908
1731
 
1732
+ /** The string-boundary mirror of {@link MCPStream} — the same sequence, already serialized. */
1733
+ export declare type MCPTextStream = AsyncGenerator<string, string>;
1734
+
909
1735
  /**
910
1736
  * One entry of the MCP `tools/list` result — a tool's `name`, optional
911
1737
  * `description`, and its JSON-Schema `inputSchema`.
@@ -921,22 +1747,6 @@ export declare interface MCPToolDescriptor {
921
1747
  readonly inputSchema: Readonly<Record<string, unknown>>;
922
1748
  }
923
1749
 
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
1750
  /**
941
1751
  * A duplex message channel an environment face provides to the pure engine — the
942
1752
  * one port `bindServer` and `bindClient` (`./helpers.js`) pipe an
@@ -962,6 +1772,9 @@ export declare interface MCPTransportInterface {
962
1772
  readonly close: () => void | Promise<void>;
963
1773
  }
964
1774
 
1775
+ /** A protocol revision supported by this MCP package. */
1776
+ export declare type MCPVersion = '2026-07-28' | '2025-11-25' | '2025-06-18';
1777
+
965
1778
  /**
966
1779
  * Narrow an already-parsed value to a {@link JSONRPCMessage}, or `undefined` when
967
1780
  * it is not one.
@@ -984,16 +1797,132 @@ export declare interface MCPTransportInterface {
984
1797
  */
985
1798
  export declare function parseJSONRPCMessage(value: unknown): JSONRPCMessage | undefined;
986
1799
 
1800
+ /**
1801
+ * Parse the verified value embedded in an opaque signed `requestState` token.
1802
+ *
1803
+ * @remarks
1804
+ * This parser does not verify the HMAC; {@link import('@orkestrel/server').verifyToken}
1805
+ * performs that boundary first and returns the JSON string parsed here. The protected
1806
+ * payload binds the authenticated principal, token lifetime, originating request id,
1807
+ * server-assigned input key, tool name, and optional consumer state. Total over malformed
1808
+ * or hostile input.
1809
+ *
1810
+ * @param value - The HMAC-verified token value to parse
1811
+ * @returns The protected input state, or `undefined` when malformed
1812
+ *
1813
+ * @example
1814
+ * ```ts
1815
+ * parseMCPInputState('{"principal":"user-1","ttl":1000,"origin":1,"key":"k","name":"reply"}')
1816
+ * // { principal: 'user-1', ttl: 1000, origin: 1, key: 'k', name: 'reply' }
1817
+ * ```
1818
+ */
1819
+ export declare function parseMCPInputState(value: unknown): MCPInputState | undefined;
1820
+
1821
+ /**
1822
+ * Parse the reserved modern request metadata into an {@link MCPRequestContext}.
1823
+ *
1824
+ * @remarks
1825
+ * This is the validity step after {@link isModernRequest}: a defined result can
1826
+ * only come from a guard-positive request, while a guard-positive request returns
1827
+ * `undefined` exactly when its required modern metadata is malformed. The version
1828
+ * must be a string but need not be supported; unsupported strings belong to the
1829
+ * dedicated protocol-version error path. Client identity is optional, but when
1830
+ * present it must carry string `name` and `version` members. Total over hostile and
1831
+ * malformed input.
1832
+ *
1833
+ * @param value - The already-parsed request candidate to coerce
1834
+ * @returns The validated modern request context, or `undefined`
1835
+ */
1836
+ export declare function parseRequestContext(value: unknown): MCPRequestContext | undefined;
1837
+
1838
+ /**
1839
+ * Pump an {@link MCPTextStream} onto a transport — every notification in order, then the
1840
+ * terminating response.
1841
+ *
1842
+ * @remarks
1843
+ * The generator's `return` value is a message like any other on the wire: it is sent
1844
+ * LAST and closes the exchange. Sends are awaited one at a time so the transport
1845
+ * receives the sequence in the order the method produced it.
1846
+ *
1847
+ * @param stream - The serialized held-open result to write out
1848
+ * @param transport - The duplex channel to write each message to
1849
+ * @returns Resolves once the terminating response has been sent
1850
+ *
1851
+ * @example
1852
+ * ```ts
1853
+ * const answer = await server.handle(message)
1854
+ * if (typeof answer !== 'string') await sendStream(answer, transport)
1855
+ * ```
1856
+ */
1857
+ export declare function sendStream(stream: MCPTextStream, transport: MCPTransportInterface): Promise<void>;
1858
+
1859
+ /**
1860
+ * Serialize a typed {@link MCPStream} into its string mirror — each yielded
1861
+ * notification and the terminating response, already `JSON.stringify`d.
1862
+ *
1863
+ * @remarks
1864
+ * The string-boundary half of the held-open arm: `handle` returns this so a transport
1865
+ * writes each message with no second parse, exactly as it writes a unary reply string.
1866
+ * The terminating response arrives as the returned generator's OWN `return` value, so a
1867
+ * consumer distinguishes "one more notification" from "this is the answer" without a
1868
+ * sentinel.
1869
+ *
1870
+ * @param stream - The typed held-open result to serialize
1871
+ * @returns The same sequence with every message serialized to a string
1872
+ *
1873
+ * @example
1874
+ * ```ts
1875
+ * const text = serializeStream(stream)
1876
+ * for (let next = await text.next(); ; next = await text.next()) {
1877
+ * if (next.done === true) return next.value // the terminating response, serialized
1878
+ * log(next.value) // one serialized notification
1879
+ * }
1880
+ * ```
1881
+ */
1882
+ export declare function serializeStream(stream: MCPStream): MCPTextStream;
1883
+
1884
+ /**
1885
+ * Stamp a subscription notification with the request id reserved for its held-open stream.
1886
+ *
1887
+ * @param notification - The notification to copy and stamp
1888
+ * @param id - The `subscriptions/listen` request id
1889
+ * @returns The stamped notification, preserving its other params and metadata
1890
+ */
1891
+ export declare function stampSubscriptionNotification(notification: JSONRPCRequest, id: string | number): JSONRPCRequest;
1892
+
1893
+ /** The notification families a client may opt in to on a `subscriptions/listen` stream. */
1894
+ export declare interface SubscriptionFilter {
1895
+ /** Receive `notifications/tools/list_changed` when the server produces it. */
1896
+ readonly toolsListChanged?: boolean;
1897
+ /** Receive `notifications/prompts/list_changed` when the server produces it. */
1898
+ readonly promptsListChanged?: boolean;
1899
+ /** Receive `notifications/resources/list_changed` when the server produces it. */
1900
+ readonly resourcesListChanged?: boolean;
1901
+ /** Receive `notifications/resources/updated` for these resource URIs. */
1902
+ readonly resourceSubscriptions?: readonly string[];
1903
+ }
1904
+
1905
+ /** The terminating result returned when a `subscriptions/listen` stream closes gracefully. */
1906
+ export declare interface SubscriptionsListenResult {
1907
+ readonly resultType: 'complete';
1908
+ readonly _meta: SubscriptionsListenResultMetaObject;
1909
+ }
1910
+
1911
+ /** The required metadata on a graceful `subscriptions/listen` result. */
1912
+ export declare interface SubscriptionsListenResultMetaObject extends Readonly<Record<string, unknown>> {
1913
+ /** The JSON-RPC id of the `subscriptions/listen` request whose stream is closing. */
1914
+ readonly 'io.modelcontextprotocol/subscriptionId': string | number;
1915
+ }
1916
+
987
1917
  /**
988
1918
  * The MCP protocol revisions this server can negotiate.
989
1919
  *
990
1920
  * @remarks
991
1921
  * `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.
1922
+ * this list. Frozen in client-preference and discovery-advertisement order. The
1923
+ * package does not advertise `2025-03-26` because that revision mandates JSON-RPC
1924
+ * batching, while this package accepts only individual JSON-RPC messages.
996
1925
  */
997
- export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
1926
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: readonly MCPVersion[];
998
1927
 
999
1928
  export { }