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