@orkestrel/mcp 0.0.24 → 0.0.26

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.
@@ -11,6 +11,7 @@ import { MCPClientTransportInterface as MCPClientTransportInterface_2 } from '@o
11
11
  import { MCPContinuationInterface } from '@orkestrel/mcp';
12
12
  import { MCPDispatcherInterface } from '@orkestrel/mcp';
13
13
  import { MCPEra } from '@orkestrel/mcp';
14
+ import { MCPLegacyVersion } from '@orkestrel/mcp';
14
15
  import { MCPStreamControllerInterface } from '@orkestrel/mcp';
15
16
  import { MCPTransportInterface } from '@orkestrel/mcp';
16
17
  import { MCPVersion } from '@orkestrel/mcp';
@@ -108,6 +109,20 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
108
109
  */
109
110
  export declare function bridgeMessageTransport(transport: MCPClientTransportInterface): MCPTransportInterface;
110
111
 
112
+ /**
113
+ * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
114
+ *
115
+ * @param response - The response whose status is reported
116
+ * @param type - The response's content type, or an empty string when absent
117
+ * @returns An error naming the HTTP status and unsupported response shape
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const error = buildResponseError(new Response('', { status: 500 }), '')
122
+ * ```
123
+ */
124
+ export declare function buildResponseError(response: Response, type: string): Error;
125
+
111
126
  /**
112
127
  * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
113
128
  * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
@@ -160,7 +175,9 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
160
175
  * Modern requests require matching protocol/method headers and a matching name header only
161
176
  * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
162
177
  * accepted, while every other headerless request needs a live legacy session to supply its
163
- * pinned version. A present origin must occur in `origin.origins` unless validation is
178
+ * pinned version. A legacy-shaped request carrying a protocol header is admitted only for a
179
+ * legacy revision; any other value, the modern revision included, returns HTTP `400` + `-32022`
180
+ * whose `supported` names the legacy revisions this door accepts. A present origin must occur in `origin.origins` unless validation is
164
181
  * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
165
182
  * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
166
183
  * signal with response-body cancellation and supplies the result to every dispatched modern
@@ -648,8 +665,10 @@ export declare function extractLines(buffer: string, chunk: string): LineExtract
648
665
  * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
649
666
  * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
650
667
  * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
651
- * the SSE decoder) a non-message reply is dropped, never asserted; a `fetch` /
652
- * decode failure surfaces on the `error` event rather than escaping `send`.
668
+ * the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
669
+ * reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
670
+ * shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
671
+ * on a success response surfaces on the `error` event rather than escaping `send`.
653
672
  * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
654
673
  * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
655
674
  *
@@ -840,12 +859,18 @@ export declare function inferHeaderIssue(request: Request, reference: JSONRPCInv
840
859
  *
841
860
  * @remarks
842
861
  * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
843
- * request selects the newest supported legacy revision, matching the core initialize result.
862
+ * request selects the newest supported legacy revision. The read is deliberately the SAME one
863
+ * {@link import('@orkestrel/mcp').buildInitializeResult} performs — `isMCPLegacyVersion` over
864
+ * the requested revision — because the session version this pins and the version that result
865
+ * echoes must be the one value. Routing through `inferVersion` cannot do it: that inferer is
866
+ * modern-only, so it answers `undefined` for every legacy offer and the session would pin
867
+ * `2025-11-25` while the handshake echoed `2025-06-18`, which the client's own protocol
868
+ * header then contradicts.
844
869
  *
845
870
  * @param request - The legacy initialize invocation
846
871
  * @returns The negotiated legacy protocol revision
847
872
  */
848
- export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPVersion;
873
+ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegacyVersion;
849
874
 
850
875
  /**
851
876
  * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
@@ -1314,7 +1339,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1314
1339
  * ```ts
1315
1340
  * const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })
1316
1341
  * const client = new MCPClient({ transport })
1317
- * await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
1342
+ * await client.connect() // start() spawns the child, then modern discovery runs over stdio
1318
1343
  * ```
1319
1344
  */
1320
1345
  export declare class StdioClientTransport implements StdioClientTransportInterface {
@@ -11,6 +11,7 @@ import { MCPClientTransportInterface as MCPClientTransportInterface_2 } from '@o
11
11
  import { MCPContinuationInterface } from '@orkestrel/mcp';
12
12
  import { MCPDispatcherInterface } from '@orkestrel/mcp';
13
13
  import { MCPEra } from '@orkestrel/mcp';
14
+ import { MCPLegacyVersion } from '@orkestrel/mcp';
14
15
  import { MCPStreamControllerInterface } from '@orkestrel/mcp';
15
16
  import { MCPTransportInterface } from '@orkestrel/mcp';
16
17
  import { MCPVersion } from '@orkestrel/mcp';
@@ -108,6 +109,20 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
108
109
  */
109
110
  export declare function bridgeMessageTransport(transport: MCPClientTransportInterface): MCPTransportInterface;
110
111
 
112
+ /**
113
+ * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
114
+ *
115
+ * @param response - The response whose status is reported
116
+ * @param type - The response's content type, or an empty string when absent
117
+ * @returns An error naming the HTTP status and unsupported response shape
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * const error = buildResponseError(new Response('', { status: 500 }), '')
122
+ * ```
123
+ */
124
+ export declare function buildResponseError(response: Response, type: string): Error;
125
+
111
126
  /**
112
127
  * Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
113
128
  * — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
@@ -160,7 +175,9 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
160
175
  * Modern requests require matching protocol/method headers and a matching name header only
161
176
  * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
162
177
  * accepted, while every other headerless request needs a live legacy session to supply its
163
- * pinned version. A present origin must occur in `origin.origins` unless validation is
178
+ * pinned version. A legacy-shaped request carrying a protocol header is admitted only for a
179
+ * legacy revision; any other value, the modern revision included, returns HTTP `400` + `-32022`
180
+ * whose `supported` names the legacy revisions this door accepts. A present origin must occur in `origin.origins` unless validation is
164
181
  * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
165
182
  * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
166
183
  * signal with response-body cancellation and supplies the result to every dispatched modern
@@ -648,8 +665,10 @@ export declare function extractLines(buffer: string, chunk: string): LineExtract
648
665
  * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
649
666
  * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
650
667
  * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
651
- * the SSE decoder) a non-message reply is dropped, never asserted; a `fetch` /
652
- * decode failure surfaces on the `error` event rather than escaping `send`.
668
+ * the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
669
+ * reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
670
+ * shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
671
+ * on a success response surfaces on the `error` event rather than escaping `send`.
653
672
  * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
654
673
  * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
655
674
  *
@@ -840,12 +859,18 @@ export declare function inferHeaderIssue(request: Request, reference: JSONRPCInv
840
859
  *
841
860
  * @remarks
842
861
  * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
843
- * request selects the newest supported legacy revision, matching the core initialize result.
862
+ * request selects the newest supported legacy revision. The read is deliberately the SAME one
863
+ * {@link import('@orkestrel/mcp').buildInitializeResult} performs — `isMCPLegacyVersion` over
864
+ * the requested revision — because the session version this pins and the version that result
865
+ * echoes must be the one value. Routing through `inferVersion` cannot do it: that inferer is
866
+ * modern-only, so it answers `undefined` for every legacy offer and the session would pin
867
+ * `2025-11-25` while the handshake echoed `2025-06-18`, which the client's own protocol
868
+ * header then contradicts.
844
869
  *
845
870
  * @param request - The legacy initialize invocation
846
871
  * @returns The negotiated legacy protocol revision
847
872
  */
848
- export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPVersion;
873
+ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegacyVersion;
849
874
 
850
875
  /**
851
876
  * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
@@ -1314,7 +1339,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1314
1339
  * ```ts
1315
1340
  * const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })
1316
1341
  * const client = new MCPClient({ transport })
1317
- * await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
1342
+ * await client.connect() // start() spawns the child, then modern discovery runs over stdio
1318
1343
  * ```
1319
1344
  */
1320
1345
  export declare class StdioClientTransport implements StdioClientTransportInterface {
@@ -1,5 +1,5 @@
1
1
  import { createSSEParser } from "@orkestrel/sse";
2
- import { JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, MCP_HEADER_MISMATCH, MCP_META_VERSION, MCP_MISSING_CAPABILITY, MCP_PROTOCOL_VERSION, MCP_UNSUPPORTED_VERSION, SUPPORTED_PROTOCOL_VERSIONS, bindServer, buildJSONRPCError, inferEra, inferRequestVersion, inferVersion, isInitializeRequest, isJSONRPCInvocation, isJSONRPCResponse, isMCPVersion, isModernRequest, parseJSONRPCMessage, parseRequestContext } from "../core/index.js";
2
+ import { JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, MCP_HANDSHAKE_VERSION, MCP_HEADER_MISMATCH, MCP_META_VERSION, MCP_MISSING_CAPABILITY, MCP_UNSUPPORTED_VERSION, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, bindServer, buildJSONRPCError, inferRequestVersion, isInitializeRequest, isJSONRPCInvocation, isJSONRPCResponse, isMCPLegacyVersion, isMCPVersion, isModernRequest, parseJSONRPCMessage, parseRequestContext } from "../core/index.js";
3
3
  import { isRecord, isString, sanitizeBudget } from "@orkestrel/contract";
4
4
  import { openStream, signToken, verifyToken } from "@orkestrel/server";
5
5
  import { Emitter } from "@orkestrel/emitter";
@@ -99,6 +99,24 @@ var DEFAULT_MCP_DELIVERY = 1e4;
99
99
  //#endregion
100
100
  //#region src/server/helpers.ts
101
101
  /**
102
+ * Builds the error for a non-success HTTP response that carried no JSON-RPC message.
103
+ *
104
+ * @param response - The response whose status is reported
105
+ * @param type - The response's content type, or an empty string when absent
106
+ * @returns An error naming the HTTP status and unsupported response shape
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * const error = buildResponseError(new Response('', { status: 500 }), '')
111
+ * ```
112
+ */
113
+ function buildResponseError(response, type) {
114
+ if (type.includes("application/json")) return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained an application/json body that was not a JSON-RPC message`);
115
+ if (type.includes("text/event-stream")) return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained a text/event-stream body without a JSON-RPC message`);
116
+ const shape = type === "" ? "a body without a content type" : `an unsupported '${type}' body`;
117
+ return /* @__PURE__ */ new Error(`HTTP ${response.status} response contained ${shape}`);
118
+ }
119
+ /**
102
120
  * Creates a readable stream from its pull and cancellation behaviours.
103
121
  *
104
122
  * @param pull - The behaviour that supplies the stream's next chunk
@@ -546,7 +564,7 @@ function inferHeaderIssue(request, reference) {
546
564
  return {
547
565
  header: "MCP-Protocol-Version",
548
566
  reason: "missing",
549
- message: `Required MCP-Protocol-Version header is missing; this server offers '${MCP_PROTOCOL_VERSION}'.`
567
+ message: `Required MCP-Protocol-Version header is missing; this server offers '${MCP_HANDSHAKE_VERSION}'.`
550
568
  };
551
569
  }
552
570
  const message = reference;
@@ -593,16 +611,20 @@ function inferHeaderIssue(request, reference) {
593
611
  *
594
612
  * @remarks
595
613
  * A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
596
- * request selects the newest supported legacy revision, matching the core initialize result.
614
+ * request selects the newest supported legacy revision. The read is deliberately the SAME one
615
+ * {@link import('@orkestrel/mcp').buildInitializeResult} performs — `isMCPLegacyVersion` over
616
+ * the requested revision — because the session version this pins and the version that result
617
+ * echoes must be the one value. Routing through `inferVersion` cannot do it: that inferer is
618
+ * modern-only, so it answers `undefined` for every legacy offer and the session would pin
619
+ * `2025-11-25` while the handshake echoed `2025-06-18`, which the client's own protocol
620
+ * header then contradicts.
597
621
  *
598
622
  * @param request - The legacy initialize invocation
599
623
  * @returns The negotiated legacy protocol revision
600
624
  */
601
625
  function inferLegacyVersion(request) {
602
626
  const requested = request.params?.["protocolVersion"];
603
- const version = inferVersion(isString(requested) ? [requested] : []);
604
- if (version !== void 0 && inferEra(version) === "legacy") return version;
605
- return MCP_PROTOCOL_VERSION;
627
+ return isMCPLegacyVersion(requested) ? requested : MCP_HANDSHAKE_VERSION;
606
628
  }
607
629
  /**
608
630
  * Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
@@ -765,7 +787,9 @@ var HTTPDisconnect = class {
765
787
  * Modern requests require matching protocol/method headers and a matching name header only
766
788
  * for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
767
789
  * accepted, while every other headerless request needs a live legacy session to supply its
768
- * pinned version. A present origin must occur in `origin.origins` unless validation is
790
+ * pinned version. A legacy-shaped request carrying a protocol header is admitted only for a
791
+ * legacy revision; any other value, the modern revision included, returns HTTP `400` + `-32022`
792
+ * whose `supported` names the legacy revisions this door accepts. A present origin must occur in `origin.origins` unless validation is
769
793
  * explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
770
794
  * errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
771
795
  * signal with response-body cancellation and supplies the result to every dispatched modern
@@ -820,8 +844,8 @@ function createMCPPostHandler(mcp, options) {
820
844
  const issue = inferHeaderIssue(request, invocation);
821
845
  if (issue !== void 0) return Response.json(buildJSONRPCError(id, MCP_HEADER_MISMATCH, issue.message), { status: 400 });
822
846
  if (era === "legacy") {
823
- if (protocol !== null && !isMCPVersion(protocol)) return Response.json(buildJSONRPCError(id, MCP_UNSUPPORTED_VERSION, `Unsupported MCP protocol version '${protocol}'`, {
824
- supported: SUPPORTED_PROTOCOL_VERSIONS,
847
+ if (protocol !== null && !isMCPLegacyVersion(protocol)) return Response.json(buildJSONRPCError(id, MCP_UNSUPPORTED_VERSION, `Unsupported MCP protocol version '${protocol}'`, {
848
+ supported: SUPPORTED_LEGACY_PROTOCOL_VERSIONS,
825
849
  requested: protocol
826
850
  }), { status: 400 });
827
851
  }
@@ -889,8 +913,10 @@ function createMCPPostHandler(mcp, options) {
889
913
  * aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
890
914
  * idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
891
915
  * - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
892
- * the SSE decoder) a non-message reply is dropped, never asserted; a `fetch` /
893
- * decode failure surfaces on the `error` event rather than escaping `send`.
916
+ * the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
917
+ * reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
918
+ * shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
919
+ * on a success response surfaces on the `error` event rather than escaping `send`.
894
920
  * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
895
921
  * `message` per decoded reply, `error` on a fault, and `close` on `close()`.
896
922
  *
@@ -985,18 +1011,20 @@ var HTTPClientTransport = class {
985
1011
  async #deliver(response) {
986
1012
  if (response.status === 202) return;
987
1013
  const type = response.headers.get("content-type") ?? "";
1014
+ let messages = [];
1015
+ let failure;
988
1016
  try {
989
- if (type.includes("text/event-stream")) {
990
- for (const message of await readEventStream(response)) this.#capture(message);
991
- return;
992
- }
993
- if (type.includes("application/json")) {
1017
+ if (type.includes("text/event-stream")) messages = await readEventStream(response);
1018
+ else if (type.includes("application/json")) {
994
1019
  const message = parseJSONRPCMessage(await response.json());
995
- if (message !== void 0) this.#capture(message);
1020
+ if (message !== void 0) messages = [message];
996
1021
  }
997
1022
  } catch (error) {
998
- this.#emitter.emit("error", error);
1023
+ failure = { error };
999
1024
  }
1025
+ for (const message of messages) this.#capture(message);
1026
+ if (!response.ok && messages.length === 0) throw buildResponseError(response, type);
1027
+ if (failure !== void 0) this.#emitter.emit("error", failure.error);
1000
1028
  }
1001
1029
  #capture(message) {
1002
1030
  if (isJSONRPCResponse(message) && isRecord(message.result) && isMCPVersion(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
@@ -1467,7 +1495,7 @@ var WebSocketClientTransport = class {
1467
1495
  * ```ts
1468
1496
  * const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })
1469
1497
  * const client = new MCPClient({ transport })
1470
- * await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
1498
+ * await client.connect() // start() spawns the child, then modern discovery runs over stdio
1471
1499
  * ```
1472
1500
  */
1473
1501
  var StdioClientTransport = class {
@@ -2204,6 +2232,6 @@ function createMCPSession(options) {
2204
2232
  };
2205
2233
  }
2206
2234
  //#endregion
2207
- export { DEFAULT_MCP_DELIVERY, DEFAULT_MCP_KEEPALIVE_INTERVAL, DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, HTTPDisconnect, MCPSession, MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, SSE_BUFFERING_DISABLED, SSE_BUFFERING_HEADER, SSE_KEEPALIVE_COMMENT, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, allowsOrigin, bridgeMessageTransport, createHTTPClientTransport, createMCPContinuation, createMCPPostHandler, createMCPRoutes, createMCPSession, createReadableStream, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, inferHeaderIssue, inferLegacyVersion, inferStatus, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, sendEventStream, upgradeRequestPath, writeLine };
2235
+ export { DEFAULT_MCP_DELIVERY, DEFAULT_MCP_KEEPALIVE_INTERVAL, DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, HTTPDisconnect, MCPSession, MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, SSE_BUFFERING_DISABLED, SSE_BUFFERING_HEADER, SSE_KEEPALIVE_COMMENT, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, allowsOrigin, bridgeMessageTransport, buildResponseError, createHTTPClientTransport, createMCPContinuation, createMCPPostHandler, createMCPRoutes, createMCPSession, createReadableStream, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, inferHeaderIssue, inferLegacyVersion, inferStatus, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, sendEventStream, upgradeRequestPath, writeLine };
2208
2236
 
2209
2237
  //# sourceMappingURL=index.js.map