@orkestrel/mcp 0.0.26 → 0.0.27
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.d.ts +13 -4
- package/dist/src/browser/index.js +68 -15
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +754 -68
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +657 -44
- package/dist/src/core/index.d.ts +657 -44
- package/dist/src/core/index.js +734 -69
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +212 -33
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +123 -22
- package/dist/src/server/index.d.ts +123 -22
- package/dist/src/server/index.js +213 -36
- package/dist/src/server/index.js.map +1 -1
- package/package.json +8 -7
|
@@ -4,10 +4,10 @@ let _src_core = require("../core/index.cjs");
|
|
|
4
4
|
let _orkestrel_contract = require("@orkestrel/contract");
|
|
5
5
|
let _orkestrel_server = require("@orkestrel/server");
|
|
6
6
|
let _orkestrel_emitter = require("@orkestrel/emitter");
|
|
7
|
+
let _orkestrel_websocket = require("@orkestrel/websocket");
|
|
7
8
|
let node_crypto = require("node:crypto");
|
|
8
9
|
let node_http = require("node:http");
|
|
9
10
|
let node_https = require("node:https");
|
|
10
|
-
let _orkestrel_websocket = require("@orkestrel/websocket");
|
|
11
11
|
let _orkestrel_process_server = require("@orkestrel/process/server");
|
|
12
12
|
let _orkestrel_process = require("@orkestrel/process");
|
|
13
13
|
let node_stream = require("node:stream");
|
|
@@ -527,13 +527,51 @@ function bridgeMessageTransport(transport) {
|
|
|
527
527
|
//#endregion
|
|
528
528
|
//#region src/server/inferers.ts
|
|
529
529
|
/**
|
|
530
|
+
* Infers the target one modern request's `Mcp-Name` header must carry.
|
|
531
|
+
*
|
|
532
|
+
* @remarks
|
|
533
|
+
* The protocol scopes the header to the methods whose body carries a name-shaped field, and
|
|
534
|
+
* names the field per method: `tools/call` and `prompts/get` carry `params.name`, and
|
|
535
|
+
* `resources/read` carries `params.uri`. Every other method — `server/discover`, `tools/list`,
|
|
536
|
+
* `resources/list`, `prompts/list` — has nothing to derive a target from, so the header is not
|
|
537
|
+
* required there and a peer that sent one anyway is not held to it.
|
|
538
|
+
*
|
|
539
|
+
* A method within the scope whose named member is absent or is not a string reads as no
|
|
540
|
+
* target. There is nothing for a header to match, and refusing the request over a body member
|
|
541
|
+
* the header rule does not own would report a parameter fault as a header fault. Total.
|
|
542
|
+
*
|
|
543
|
+
* @param request - The parsed modern invocation to read the target from
|
|
544
|
+
* @returns The target the header must carry, or `undefined` when the method carries none
|
|
545
|
+
*
|
|
546
|
+
* @example
|
|
547
|
+
* ```ts
|
|
548
|
+
* inferHeaderTarget({ jsonrpc: '2.0', id: 1, method: 'resources/read', params: { uri: 'file:///a' } })
|
|
549
|
+
* // → 'file:///a'
|
|
550
|
+
* ```
|
|
551
|
+
*/
|
|
552
|
+
function inferHeaderTarget(request) {
|
|
553
|
+
if (request.method === "tools/call" || request.method === "prompts/get") {
|
|
554
|
+
const name = request.params?.["name"];
|
|
555
|
+
return (0, _orkestrel_contract.isString)(name) ? name : void 0;
|
|
556
|
+
}
|
|
557
|
+
if (request.method === "resources/read") {
|
|
558
|
+
const uri = request.params?.["uri"];
|
|
559
|
+
return (0, _orkestrel_contract.isString)(uri) ? uri : void 0;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
530
563
|
* Infers the first required MCP HTTP header that is missing or mismatched.
|
|
531
564
|
*
|
|
532
565
|
* @remarks
|
|
533
|
-
* A modern request derives its protocol, method, and
|
|
534
|
-
* the
|
|
535
|
-
*
|
|
536
|
-
*
|
|
566
|
+
* A modern request derives its protocol, method, and name expectations from the JSON-RPC body,
|
|
567
|
+
* the name expectation scoped to the methods {@link inferHeaderTarget} reads a target for. A
|
|
568
|
+
* name header carrying the Base64 sentinel is decoded through
|
|
569
|
+
* {@link import('@orkestrel/mcp').decodeSentinel} before the comparison, so a peer that had
|
|
570
|
+
* to encode its value still matches; a sentinel whose payload is invalid decodes to nothing
|
|
571
|
+
* and therefore mismatches, which is how an invalid header value is refused. A legacy request
|
|
572
|
+
* body requires a protocol header after initialization, while a supplied legacy session
|
|
573
|
+
* version additionally diagnoses a header that disagrees with the active session. Messages
|
|
574
|
+
* name the expected value but never echo the client-supplied one.
|
|
537
575
|
*
|
|
538
576
|
* @param request - The HTTP request carrying the headers
|
|
539
577
|
* @param reference - The parsed invocation body, or the active legacy session version
|
|
@@ -592,22 +630,73 @@ function inferHeaderIssue(request, reference) {
|
|
|
592
630
|
reason: "mismatched",
|
|
593
631
|
message: `Mcp-Method header does not match the request body method '${message.method}'.`
|
|
594
632
|
};
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (!(0, _orkestrel_contract.isString)(name)) return void 0;
|
|
633
|
+
const target = inferHeaderTarget(message);
|
|
634
|
+
if (target === void 0) return void 0;
|
|
598
635
|
const header = request.headers.get(MCP_NAME_HEADER);
|
|
599
636
|
if (header === null) return {
|
|
600
637
|
header: "Mcp-Name",
|
|
601
638
|
reason: "missing",
|
|
602
|
-
message: `Required Mcp-Name header is missing; the request body
|
|
639
|
+
message: `Required Mcp-Name header is missing; the request body target is '${target}'.`
|
|
603
640
|
};
|
|
604
|
-
if (header !==
|
|
641
|
+
if ((0, _src_core.decodeSentinel)(header) !== target) return {
|
|
605
642
|
header: "Mcp-Name",
|
|
606
643
|
reason: "mismatched",
|
|
607
|
-
message: `Mcp-Name header does not match the request body
|
|
644
|
+
message: `Mcp-Name header does not match the request body target '${target}'.`
|
|
608
645
|
};
|
|
609
646
|
}
|
|
610
647
|
/**
|
|
648
|
+
* Infers the refusal one `tools/call` earns for a `Mcp-Param-*` header the body contradicts.
|
|
649
|
+
*
|
|
650
|
+
* @remarks
|
|
651
|
+
* The custom-header half of the standard-header seam {@link inferHeaderIssue} owns, and it
|
|
652
|
+
* takes the SERVED definition's projections rather than a header issue: SEP-2243 scopes the
|
|
653
|
+
* rule to the `Mcp-Param-*` names the server's OWN tool definitions annotate, so a name no
|
|
654
|
+
* parameter claims is another party's header and travels through untouched.
|
|
655
|
+
*
|
|
656
|
+
* For each recognized parameter the body's value at the parameter's own property path fixes
|
|
657
|
+
* the expectation. A value the call omits or supplies as `null` requires no header, and a
|
|
658
|
+
* header sent anyway is refused because it asserts something the body never said. A value the
|
|
659
|
+
* call does supply requires its header: an absent one, a Base64 sentinel whose payload is
|
|
660
|
+
* invalid, and a decoded value that disagrees are each refused. An `integer` parameter
|
|
661
|
+
* compares numerically, so a peer that padded its decimal still matches. A supplied value
|
|
662
|
+
* whose runtime shape contradicts the declared type is left alone — the tool's own argument
|
|
663
|
+
* validation owns that disagreement, and refusing it here would report an argument fault as a
|
|
664
|
+
* header fault.
|
|
665
|
+
*
|
|
666
|
+
* Messages name the field and the body path the expectation came from, and never echo the
|
|
667
|
+
* value the peer supplied.
|
|
668
|
+
*
|
|
669
|
+
* @param request - The HTTP request carrying the headers
|
|
670
|
+
* @param parameters - The projections the served tool definition declares
|
|
671
|
+
* @param values - The call's `arguments` record
|
|
672
|
+
* @returns The refusal message for the first disagreeing parameter, or `undefined`
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```ts
|
|
676
|
+
* inferParameterRefusal(request, [{ name: 'Region', path: ['region'], primitive: 'string' }], {})
|
|
677
|
+
* // → undefined when the request carries no `Mcp-Param-Region` either
|
|
678
|
+
* ```
|
|
679
|
+
*/
|
|
680
|
+
function inferParameterRefusal(request, parameters, values) {
|
|
681
|
+
for (const parameter of parameters) {
|
|
682
|
+
let carried = values;
|
|
683
|
+
for (const key of parameter.path) carried = (0, _orkestrel_contract.isRecord)(carried) ? carried[key] : void 0;
|
|
684
|
+
const field = `${_src_core.MCP_PARAM_PREFIX}${parameter.name}`;
|
|
685
|
+
const path = parameter.path.join(".");
|
|
686
|
+
const header = request.headers.get(field);
|
|
687
|
+
if (carried === void 0 || carried === null) {
|
|
688
|
+
if (header === null) continue;
|
|
689
|
+
return `${field} header carries a value the request body omits at '${path}'.`;
|
|
690
|
+
}
|
|
691
|
+
const expected = (0, _src_core.renderHeaderValue)(carried, parameter.primitive);
|
|
692
|
+
if (expected === void 0) continue;
|
|
693
|
+
if (header === null) return `Required ${field} header is missing; the request body carries '${path}'.`;
|
|
694
|
+
const decoded = (0, _src_core.decodeSentinel)(header);
|
|
695
|
+
if (decoded === void 0) return `${field} header value is not a valid Base64 sentinel.`;
|
|
696
|
+
if (!(parameter.primitive === "integer" ? decoded.trim() !== "" && Number(decoded) === Number(expected) : decoded === expected)) return `${field} header does not match the request body value at '${path}'.`;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
611
700
|
* Infers the legacy revision an `initialize` request negotiates.
|
|
612
701
|
*
|
|
613
702
|
* @remarks
|
|
@@ -785,12 +874,17 @@ var HTTPDisconnect = class {
|
|
|
785
874
|
* Creates the Streamable-HTTP POST handler used by `createMCPRoutes`.
|
|
786
875
|
*
|
|
787
876
|
* @remarks
|
|
788
|
-
* Modern requests require matching protocol/method headers and a matching name header
|
|
789
|
-
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
877
|
+
* Modern requests require matching protocol/method headers and a matching name header on each
|
|
878
|
+
* method carrying a named target — `tools/call` and `prompts/get` against `params.name`,
|
|
879
|
+
* `resources/read` against `params.uri` — with a Base64-sentinel value decoded before the
|
|
880
|
+
* comparison; a missing, mismatched, or invalidly encoded value returns HTTP `400` + `-32020`.
|
|
881
|
+
* A protocol header naming a MODERN revision holds the request to that revision whatever shape
|
|
882
|
+
* its body arrived in, so a body with no parsable modern `_meta` returns HTTP `400` + `-32602`.
|
|
883
|
+
* Headerless `initialize` is accepted, while every other headerless request needs a live legacy
|
|
884
|
+
* session to supply its pinned version. A legacy-shaped request carrying a protocol header is
|
|
885
|
+
* otherwise admitted only for a legacy revision; a revision this server does not implement
|
|
886
|
+
* returns HTTP `400` + `-32022` whose `supported` names the legacy revisions this door accepts.
|
|
887
|
+
* A present origin must occur in `origin.origins` unless validation is
|
|
794
888
|
* explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
|
|
795
889
|
* errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
|
|
796
890
|
* signal with response-body cancellation and supplies the result to every dispatched modern
|
|
@@ -839,11 +933,42 @@ function createMCPPostHandler(mcp, options) {
|
|
|
839
933
|
const era = (0, _src_core.isModernRequest)(invocation) ? "modern" : "legacy";
|
|
840
934
|
const id = invocation.id;
|
|
841
935
|
const protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER);
|
|
842
|
-
if (era === "modern") {
|
|
936
|
+
if (era === "modern" || (0, _src_core.isMCPModernVersion)(protocol)) {
|
|
843
937
|
if ((0, _src_core.parseRequestContext)(invocation) === void 0) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata"), { status: 400 });
|
|
844
938
|
}
|
|
845
939
|
const issue = inferHeaderIssue(request, invocation);
|
|
846
940
|
if (issue !== void 0) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_HEADER_MISMATCH, issue.message), { status: 400 });
|
|
941
|
+
const called = invocation.params?.["name"];
|
|
942
|
+
if (era === "modern" && invocation.method === "tools/call" && (0, _orkestrel_contract.isString)(called)) {
|
|
943
|
+
let parameters = [];
|
|
944
|
+
let cursor = void 0;
|
|
945
|
+
for (let page = 0; page < _src_core.MCP_LOOKUP_PAGES; page += 1) {
|
|
946
|
+
const answer = await mcp.dispatch({
|
|
947
|
+
jsonrpc: "2.0",
|
|
948
|
+
id: 0,
|
|
949
|
+
method: "tools/list",
|
|
950
|
+
params: {
|
|
951
|
+
_meta: invocation.params?.["_meta"],
|
|
952
|
+
...cursor === void 0 ? {} : { cursor }
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
if (Symbol.asyncIterator in answer) {
|
|
956
|
+
answer.stop();
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
const schema = (0, _src_core.extractToolSchema)(answer, called);
|
|
960
|
+
if (schema !== void 0) {
|
|
961
|
+
parameters = (0, _src_core.buildHeaderParameters)(schema) ?? [];
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
const listing = answer.result;
|
|
965
|
+
const next = (0, _orkestrel_contract.isRecord)(listing) ? listing["nextCursor"] : void 0;
|
|
966
|
+
if (!(0, _orkestrel_contract.isString)(next)) break;
|
|
967
|
+
cursor = next;
|
|
968
|
+
}
|
|
969
|
+
const refusal = inferParameterRefusal(request, parameters, invocation.params?.["arguments"]);
|
|
970
|
+
if (refusal !== void 0) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_HEADER_MISMATCH, refusal), { status: 400 });
|
|
971
|
+
}
|
|
847
972
|
if (era === "legacy") {
|
|
848
973
|
if (protocol !== null && !(0, _src_core.isMCPLegacyVersion)(protocol)) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_UNSUPPORTED_VERSION, `Unsupported MCP protocol version '${protocol}'`, {
|
|
849
974
|
supported: _src_core.SUPPORTED_LEGACY_PROTOCOL_VERSIONS,
|
|
@@ -904,7 +1029,8 @@ function createMCPPostHandler(mcp, options) {
|
|
|
904
1029
|
* initialize result's `protocolVersion` is likewise captured, but only
|
|
905
1030
|
* when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
|
|
906
1031
|
* subsequent legacy requests. Modern requests instead derive protocol and method
|
|
907
|
-
* headers from the message, plus the name header only for `tools/call
|
|
1032
|
+
* headers from the message, plus the name header only for `tools/call` — carried in the
|
|
1033
|
+
* protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
|
|
908
1034
|
* Before initialize returns, neither captured legacy header is sent.
|
|
909
1035
|
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
910
1036
|
* POST is headerless; the captured `session` persists across `close()`.
|
|
@@ -935,8 +1061,11 @@ var HTTPClientTransport = class {
|
|
|
935
1061
|
#fetch;
|
|
936
1062
|
#timeout;
|
|
937
1063
|
#pending = /* @__PURE__ */ new Set();
|
|
1064
|
+
#parameters = /* @__PURE__ */ new Map();
|
|
1065
|
+
#stamps = /* @__PURE__ */ new WeakMap();
|
|
938
1066
|
#session = void 0;
|
|
939
1067
|
#protocol = void 0;
|
|
1068
|
+
#generation = 0;
|
|
940
1069
|
#closed = false;
|
|
941
1070
|
constructor(options) {
|
|
942
1071
|
this.#emitter = new _orkestrel_emitter.Emitter();
|
|
@@ -958,6 +1087,7 @@ var HTTPClientTransport = class {
|
|
|
958
1087
|
this.#closed = false;
|
|
959
1088
|
}
|
|
960
1089
|
async send(message) {
|
|
1090
|
+
this.#stamp(message);
|
|
961
1091
|
const request = new AbortController();
|
|
962
1092
|
this.#pending.add(request);
|
|
963
1093
|
try {
|
|
@@ -966,6 +1096,11 @@ var HTTPClientTransport = class {
|
|
|
966
1096
|
this.#pending.delete(request);
|
|
967
1097
|
}
|
|
968
1098
|
}
|
|
1099
|
+
#stamp(message) {
|
|
1100
|
+
if (!(0, _src_core.isModernRequest)(message) || message.method !== "tools/list") return;
|
|
1101
|
+
if (message.params?.["cursor"] === void 0) this.#generation += 1;
|
|
1102
|
+
this.#stamps.set(message, this.#generation);
|
|
1103
|
+
}
|
|
969
1104
|
async #exchange(message, signal) {
|
|
970
1105
|
let response;
|
|
971
1106
|
try {
|
|
@@ -987,7 +1122,7 @@ var HTTPClientTransport = class {
|
|
|
987
1122
|
}
|
|
988
1123
|
const session = response.headers.get(MCP_SESSION_HEADER);
|
|
989
1124
|
if (session !== null) this.#session = session;
|
|
990
|
-
await this.#deliver(response);
|
|
1125
|
+
await this.#deliver(response, message);
|
|
991
1126
|
}
|
|
992
1127
|
async close() {
|
|
993
1128
|
if (this.#closed) return;
|
|
@@ -1004,12 +1139,15 @@ var HTTPClientTransport = class {
|
|
|
1004
1139
|
return {
|
|
1005
1140
|
...version === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: version },
|
|
1006
1141
|
[MCP_METHOD_HEADER]: message.method,
|
|
1007
|
-
...message.method === "tools/call" && (0, _orkestrel_contract.isString)(name) ? {
|
|
1142
|
+
...message.method === "tools/call" && (0, _orkestrel_contract.isString)(name) ? {
|
|
1143
|
+
[MCP_NAME_HEADER]: (0, _src_core.encodeSentinel)(name),
|
|
1144
|
+
...(0, _src_core.buildHeaderProjection)(this.#parameters.get(name) ?? [], message.params?.["arguments"])
|
|
1145
|
+
} : {}
|
|
1008
1146
|
};
|
|
1009
1147
|
}
|
|
1010
1148
|
return this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol };
|
|
1011
1149
|
}
|
|
1012
|
-
async #deliver(response) {
|
|
1150
|
+
async #deliver(response, sent) {
|
|
1013
1151
|
if (response.status === 202) return;
|
|
1014
1152
|
const type = response.headers.get("content-type") ?? "";
|
|
1015
1153
|
let messages = [];
|
|
@@ -1023,13 +1161,43 @@ var HTTPClientTransport = class {
|
|
|
1023
1161
|
} catch (error) {
|
|
1024
1162
|
failure = { error };
|
|
1025
1163
|
}
|
|
1026
|
-
for (const message of messages) this.#capture(message);
|
|
1164
|
+
for (const message of messages) this.#capture(message, sent);
|
|
1027
1165
|
if (!response.ok && messages.length === 0) throw buildResponseError(response, type);
|
|
1028
1166
|
if (failure !== void 0) this.#emitter.emit("error", failure.error);
|
|
1029
1167
|
}
|
|
1030
|
-
#capture(message) {
|
|
1168
|
+
#capture(message, sent) {
|
|
1031
1169
|
if ((0, _src_core.isJSONRPCResponse)(message) && (0, _orkestrel_contract.isRecord)(message.result) && (0, _src_core.isMCPVersion)(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
|
|
1032
|
-
this.#emitter.emit("message", message);
|
|
1170
|
+
this.#emitter.emit("message", this.#select(message, sent));
|
|
1171
|
+
}
|
|
1172
|
+
#select(message, sent) {
|
|
1173
|
+
if (!(0, _src_core.isModernRequest)(sent) || sent.method !== "tools/list") return message;
|
|
1174
|
+
if (!(0, _src_core.isJSONRPCResponse)(message) || message.error !== void 0) return message;
|
|
1175
|
+
const result = message.result;
|
|
1176
|
+
const listed = (0, _orkestrel_contract.isRecord)(result) ? result["tools"] : void 0;
|
|
1177
|
+
if (!(0, _orkestrel_contract.isRecord)(result) || !(0, _orkestrel_contract.isArray)(listed)) return message;
|
|
1178
|
+
const current = this.#stamps.get(sent) === this.#generation;
|
|
1179
|
+
if (current && sent.params?.["cursor"] === void 0) this.#parameters.clear();
|
|
1180
|
+
const kept = [];
|
|
1181
|
+
for (const tool of listed) {
|
|
1182
|
+
if (!(0, _orkestrel_contract.isRecord)(tool) || !(0, _orkestrel_contract.isString)(tool["name"])) {
|
|
1183
|
+
kept.push(tool);
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
const parameters = (0, _src_core.buildHeaderParameters)(tool["inputSchema"]);
|
|
1187
|
+
if (parameters === void 0) {
|
|
1188
|
+
this.#emitter.emit("error", /* @__PURE__ */ new Error(`MCP tool '${tool["name"]}' is excluded from tools/list: its inputSchema carries an invalid x-mcp-header annotation`));
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
if (current) this.#parameters.set(tool["name"], parameters);
|
|
1192
|
+
kept.push(tool);
|
|
1193
|
+
}
|
|
1194
|
+
return {
|
|
1195
|
+
...message,
|
|
1196
|
+
result: {
|
|
1197
|
+
...result,
|
|
1198
|
+
tools: kept
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1033
1201
|
}
|
|
1034
1202
|
};
|
|
1035
1203
|
//#endregion
|
|
@@ -1173,8 +1341,13 @@ var MCPSession = class {
|
|
|
1173
1341
|
* a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It
|
|
1174
1342
|
* also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
|
|
1175
1343
|
* - **Outbound (`send`).** `send(message)` writes one text frame
|
|
1176
|
-
* (`nodeWs.send(JSON.stringify(message))`)
|
|
1177
|
-
* non-open socket, so a closed
|
|
1344
|
+
* (`nodeWs.send(JSON.stringify(message))`). The underlying wrapper no-ops a write on a
|
|
1345
|
+
* non-open socket and confirms nothing, so this bridge answers a closed channel from its own
|
|
1346
|
+
* state and the socket's `readyState`: a `send` after `close()`, after the peer's close, or on
|
|
1347
|
+
* a socket that is not `OPEN` REJECTS with `WebSocket transport is not connected` rather than
|
|
1348
|
+
* resolving on a frame nobody wrote. `bindServer` catches that rejection and routes it to the
|
|
1349
|
+
* dispatcher's `error` event, and it aborts every in-flight request the moment this transport's
|
|
1350
|
+
* `close` fires — so a peer that disconnects mid-request is answered by no write at all.
|
|
1178
1351
|
* - **`close()`** removes the subscriptions `start()` installed on the socket, closes the
|
|
1179
1352
|
* underlying socket (the RFC 6455 close handshake), and fires the transport's `close` event
|
|
1180
1353
|
* (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives
|
|
@@ -1211,6 +1384,7 @@ var WebSocketServerTransport = class {
|
|
|
1211
1384
|
this.#socket.emitter.on("error", this.#failure);
|
|
1212
1385
|
}
|
|
1213
1386
|
async send(message) {
|
|
1387
|
+
if (this.#closed || this.#socket.readyState !== _orkestrel_websocket.WEBSOCKET_READY_OPEN) throw new Error("WebSocket transport is not connected");
|
|
1214
1388
|
this.#socket.send(JSON.stringify(message));
|
|
1215
1389
|
}
|
|
1216
1390
|
async close() {
|
|
@@ -1277,10 +1451,12 @@ var WebSocketServerTransport = class {
|
|
|
1277
1451
|
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1278
1452
|
* / `error` bridge to this transport's events.
|
|
1279
1453
|
* - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
|
|
1280
|
-
* confirmed, so this transport answers a closed channel from its
|
|
1281
|
-
* bound socket — before `start()`, after `close()`, or after the
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1454
|
+
* confirmed, so this transport answers a closed channel from its own state AND the socket's
|
|
1455
|
+
* `readyState`: a `send` with no bound socket — before `start()`, after `close()`, or after the
|
|
1456
|
+
* peer ended the socket — and a `send` on a bound socket that is not `OPEN` both REJECT with
|
|
1457
|
+
* `WebSocket transport is not connected`. It neither drops the message nor queues it for a
|
|
1458
|
+
* connection this transport is not holding — the browser face queues a pre-open send, and this
|
|
1459
|
+
* one, holding no connection to flush it onto, rejects that too.
|
|
1284
1460
|
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1285
1461
|
* upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
|
|
1286
1462
|
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
@@ -1332,7 +1508,7 @@ var WebSocketClientTransport = class {
|
|
|
1332
1508
|
}
|
|
1333
1509
|
async send(message) {
|
|
1334
1510
|
const socket = this.#socket;
|
|
1335
|
-
if (socket === void 0) throw new Error("WebSocket transport is not connected");
|
|
1511
|
+
if (socket === void 0 || socket.readyState !== _orkestrel_websocket.WEBSOCKET_READY_OPEN) throw new Error("WebSocket transport is not connected");
|
|
1336
1512
|
socket.send(JSON.stringify(message));
|
|
1337
1513
|
}
|
|
1338
1514
|
async close() {
|
|
@@ -2032,7 +2208,8 @@ function createStdioClientTransport(options) {
|
|
|
2032
2208
|
* @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over stdio
|
|
2033
2209
|
* @param options - Optional injectable `input` / `output` streams; see
|
|
2034
2210
|
* {@link StdioServerOptions}
|
|
2035
|
-
* @returns A
|
|
2211
|
+
* @returns A {@link StdioServerInterface} handle to arm / tear down the pump; `stop()` ends
|
|
2212
|
+
* that handle's lifetime permanently
|
|
2036
2213
|
*
|
|
2037
2214
|
* @example
|
|
2038
2215
|
* ```ts
|
|
@@ -2271,7 +2448,9 @@ exports.decodeEvent = decodeEvent;
|
|
|
2271
2448
|
exports.dispatchLines = dispatchLines;
|
|
2272
2449
|
exports.extractLines = extractLines;
|
|
2273
2450
|
exports.inferHeaderIssue = inferHeaderIssue;
|
|
2451
|
+
exports.inferHeaderTarget = inferHeaderTarget;
|
|
2274
2452
|
exports.inferLegacyVersion = inferLegacyVersion;
|
|
2453
|
+
exports.inferParameterRefusal = inferParameterRefusal;
|
|
2275
2454
|
exports.inferStatus = inferStatus;
|
|
2276
2455
|
exports.readEventStream = readEventStream;
|
|
2277
2456
|
exports.readLastEventId = readLastEventId;
|