@orkestrel/mcp 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +797 -212
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +496 -111
- package/dist/src/core/index.d.ts +496 -111
- package/dist/src/core/index.js +780 -210
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +60 -19
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +44 -7
- package/dist/src/server/index.d.ts +44 -7
- package/dist/src/server/index.js +61 -21
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -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
|
|
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)
|
|
652
|
-
*
|
|
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
|
|
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):
|
|
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
|
|
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 {
|
|
@@ -1571,7 +1596,11 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
|
1571
1596
|
* event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
|
|
1572
1597
|
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1573
1598
|
* / `error` bridge to this transport's events.
|
|
1574
|
-
* - **Outbound (`send`).** `send(message)` writes one masked text frame.
|
|
1599
|
+
* - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
|
|
1600
|
+
* confirmed, so this transport answers a closed channel from its OWN state: a `send` with no
|
|
1601
|
+
* bound socket — before `start()`, after `close()`, or after the peer ended the socket —
|
|
1602
|
+
* REJECTS with `WebSocket transport is not connected`. It neither drops the message (the
|
|
1603
|
+
* browser face's posture) nor queues it for a connection this transport is not holding.
|
|
1575
1604
|
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1576
1605
|
* upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
|
|
1577
1606
|
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
@@ -1706,6 +1735,14 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
|
|
|
1706
1735
|
* The completion callback is the writable channel's backpressure boundary. A callback error and
|
|
1707
1736
|
* a synchronous `write` throw reject the returned promise with the original value.
|
|
1708
1737
|
*
|
|
1738
|
+
* That callback is the ONLY thing that settles the promise: this helper holds no timer and no
|
|
1739
|
+
* abort, so an output that neither confirms nor fails the write parks the promise for as long as
|
|
1740
|
+
* the caller-owned stream holds the callback. A caller wanting a bound races this promise against
|
|
1741
|
+
* one it owns — {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
1742
|
+
* registers such a bound per send and rejects it on `close()`, so closing the transport settles
|
|
1743
|
+
* the CALLER's `send` while the abandoned write stays with the stream that still holds its
|
|
1744
|
+
* callback, reachable from nothing the transport retains.
|
|
1745
|
+
*
|
|
1709
1746
|
* @param output - The writable stream that receives the line
|
|
1710
1747
|
* @param line - The complete line to write
|
|
1711
1748
|
* @returns Resolves when the stream confirms the write; rejects when the write fails
|
|
@@ -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
|
|
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)
|
|
652
|
-
*
|
|
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
|
|
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):
|
|
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
|
|
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 {
|
|
@@ -1571,7 +1596,11 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
|
1571
1596
|
* event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
|
|
1572
1597
|
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1573
1598
|
* / `error` bridge to this transport's events.
|
|
1574
|
-
* - **Outbound (`send`).** `send(message)` writes one masked text frame.
|
|
1599
|
+
* - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
|
|
1600
|
+
* confirmed, so this transport answers a closed channel from its OWN state: a `send` with no
|
|
1601
|
+
* bound socket — before `start()`, after `close()`, or after the peer ended the socket —
|
|
1602
|
+
* REJECTS with `WebSocket transport is not connected`. It neither drops the message (the
|
|
1603
|
+
* browser face's posture) nor queues it for a connection this transport is not holding.
|
|
1575
1604
|
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1576
1605
|
* upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
|
|
1577
1606
|
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
@@ -1706,6 +1735,14 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
|
|
|
1706
1735
|
* The completion callback is the writable channel's backpressure boundary. A callback error and
|
|
1707
1736
|
* a synchronous `write` throw reject the returned promise with the original value.
|
|
1708
1737
|
*
|
|
1738
|
+
* That callback is the ONLY thing that settles the promise: this helper holds no timer and no
|
|
1739
|
+
* abort, so an output that neither confirms nor fails the write parks the promise for as long as
|
|
1740
|
+
* the caller-owned stream holds the callback. A caller wanting a bound races this promise against
|
|
1741
|
+
* one it owns — {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
1742
|
+
* registers such a bound per send and rejects it on `close()`, so closing the transport settles
|
|
1743
|
+
* the CALLER's `send` while the abandoned write stays with the stream that still holds its
|
|
1744
|
+
* callback, reachable from nothing the transport retains.
|
|
1745
|
+
*
|
|
1709
1746
|
* @param output - The writable stream that receives the line
|
|
1710
1747
|
* @param line - The complete line to write
|
|
1711
1748
|
* @returns Resolves when the stream confirms the write; rejects when the write fails
|
package/dist/src/server/index.js
CHANGED
|
@@ -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,
|
|
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
|
|
@@ -370,6 +388,14 @@ function extractLines(buffer, chunk) {
|
|
|
370
388
|
* The completion callback is the writable channel's backpressure boundary. A callback error and
|
|
371
389
|
* a synchronous `write` throw reject the returned promise with the original value.
|
|
372
390
|
*
|
|
391
|
+
* That callback is the ONLY thing that settles the promise: this helper holds no timer and no
|
|
392
|
+
* abort, so an output that neither confirms nor fails the write parks the promise for as long as
|
|
393
|
+
* the caller-owned stream holds the callback. A caller wanting a bound races this promise against
|
|
394
|
+
* one it owns — {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
395
|
+
* registers such a bound per send and rejects it on `close()`, so closing the transport settles
|
|
396
|
+
* the CALLER's `send` while the abandoned write stays with the stream that still holds its
|
|
397
|
+
* callback, reachable from nothing the transport retains.
|
|
398
|
+
*
|
|
373
399
|
* @param output - The writable stream that receives the line
|
|
374
400
|
* @param line - The complete line to write
|
|
375
401
|
* @returns Resolves when the stream confirms the write; rejects when the write fails
|
|
@@ -538,7 +564,7 @@ function inferHeaderIssue(request, reference) {
|
|
|
538
564
|
return {
|
|
539
565
|
header: "MCP-Protocol-Version",
|
|
540
566
|
reason: "missing",
|
|
541
|
-
message: `Required MCP-Protocol-Version header is missing; this server offers '${
|
|
567
|
+
message: `Required MCP-Protocol-Version header is missing; this server offers '${MCP_HANDSHAKE_VERSION}'.`
|
|
542
568
|
};
|
|
543
569
|
}
|
|
544
570
|
const message = reference;
|
|
@@ -585,16 +611,20 @@ function inferHeaderIssue(request, reference) {
|
|
|
585
611
|
*
|
|
586
612
|
* @remarks
|
|
587
613
|
* A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
|
|
588
|
-
* request selects the newest supported legacy revision
|
|
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.
|
|
589
621
|
*
|
|
590
622
|
* @param request - The legacy initialize invocation
|
|
591
623
|
* @returns The negotiated legacy protocol revision
|
|
592
624
|
*/
|
|
593
625
|
function inferLegacyVersion(request) {
|
|
594
626
|
const requested = request.params?.["protocolVersion"];
|
|
595
|
-
|
|
596
|
-
if (version !== void 0 && inferEra(version) === "legacy") return version;
|
|
597
|
-
return MCP_PROTOCOL_VERSION;
|
|
627
|
+
return isMCPLegacyVersion(requested) ? requested : MCP_HANDSHAKE_VERSION;
|
|
598
628
|
}
|
|
599
629
|
/**
|
|
600
630
|
* Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
|
|
@@ -757,7 +787,9 @@ var HTTPDisconnect = class {
|
|
|
757
787
|
* Modern requests require matching protocol/method headers and a matching name header only
|
|
758
788
|
* for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
|
|
759
789
|
* accepted, while every other headerless request needs a live legacy session to supply its
|
|
760
|
-
* pinned version. A
|
|
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
|
|
761
793
|
* explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
|
|
762
794
|
* errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
|
|
763
795
|
* signal with response-body cancellation and supplies the result to every dispatched modern
|
|
@@ -812,8 +844,8 @@ function createMCPPostHandler(mcp, options) {
|
|
|
812
844
|
const issue = inferHeaderIssue(request, invocation);
|
|
813
845
|
if (issue !== void 0) return Response.json(buildJSONRPCError(id, MCP_HEADER_MISMATCH, issue.message), { status: 400 });
|
|
814
846
|
if (era === "legacy") {
|
|
815
|
-
if (protocol !== null && !
|
|
816
|
-
supported:
|
|
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,
|
|
817
849
|
requested: protocol
|
|
818
850
|
}), { status: 400 });
|
|
819
851
|
}
|
|
@@ -881,8 +913,10 @@ function createMCPPostHandler(mcp, options) {
|
|
|
881
913
|
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
882
914
|
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
883
915
|
* - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
884
|
-
* the SSE decoder)
|
|
885
|
-
*
|
|
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`.
|
|
886
920
|
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
|
|
887
921
|
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
888
922
|
*
|
|
@@ -977,18 +1011,20 @@ var HTTPClientTransport = class {
|
|
|
977
1011
|
async #deliver(response) {
|
|
978
1012
|
if (response.status === 202) return;
|
|
979
1013
|
const type = response.headers.get("content-type") ?? "";
|
|
1014
|
+
let messages = [];
|
|
1015
|
+
let failure;
|
|
980
1016
|
try {
|
|
981
|
-
if (type.includes("text/event-stream"))
|
|
982
|
-
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
if (type.includes("application/json")) {
|
|
1017
|
+
if (type.includes("text/event-stream")) messages = await readEventStream(response);
|
|
1018
|
+
else if (type.includes("application/json")) {
|
|
986
1019
|
const message = parseJSONRPCMessage(await response.json());
|
|
987
|
-
if (message !== void 0)
|
|
1020
|
+
if (message !== void 0) messages = [message];
|
|
988
1021
|
}
|
|
989
1022
|
} catch (error) {
|
|
990
|
-
|
|
1023
|
+
failure = { error };
|
|
991
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);
|
|
992
1028
|
}
|
|
993
1029
|
#capture(message) {
|
|
994
1030
|
if (isJSONRPCResponse(message) && isRecord(message.result) && isMCPVersion(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
|
|
@@ -1239,7 +1275,11 @@ var WebSocketServerTransport = class {
|
|
|
1239
1275
|
* event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
|
|
1240
1276
|
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1241
1277
|
* / `error` bridge to this transport's events.
|
|
1242
|
-
* - **Outbound (`send`).** `send(message)` writes one masked text frame.
|
|
1278
|
+
* - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
|
|
1279
|
+
* confirmed, so this transport answers a closed channel from its OWN state: a `send` with no
|
|
1280
|
+
* bound socket — before `start()`, after `close()`, or after the peer ended the socket —
|
|
1281
|
+
* REJECTS with `WebSocket transport is not connected`. It neither drops the message (the
|
|
1282
|
+
* browser face's posture) nor queues it for a connection this transport is not holding.
|
|
1243
1283
|
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1244
1284
|
* upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
|
|
1245
1285
|
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
@@ -1455,7 +1495,7 @@ var WebSocketClientTransport = class {
|
|
|
1455
1495
|
* ```ts
|
|
1456
1496
|
* const transport = new StdioClientTransport({ command: 'node', args: ['./server.js'] })
|
|
1457
1497
|
* const client = new MCPClient({ transport })
|
|
1458
|
-
* await client.connect() // start() spawns the child, then
|
|
1498
|
+
* await client.connect() // start() spawns the child, then modern discovery runs over stdio
|
|
1459
1499
|
* ```
|
|
1460
1500
|
*/
|
|
1461
1501
|
var StdioClientTransport = class {
|
|
@@ -2192,6 +2232,6 @@ function createMCPSession(options) {
|
|
|
2192
2232
|
};
|
|
2193
2233
|
}
|
|
2194
2234
|
//#endregion
|
|
2195
|
-
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 };
|
|
2196
2236
|
|
|
2197
2237
|
//# sourceMappingURL=index.js.map
|