@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.
- 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 +47 -18
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +31 -6
- package/dist/src/server/index.d.ts +31 -6
- package/dist/src/server/index.js +48 -20
- package/dist/src/server/index.js.map +1 -1
- package/package.json +10 -10
package/dist/src/core/index.js
CHANGED
|
@@ -10,25 +10,24 @@ import { Tool } from "@orkestrel/tool";
|
|
|
10
10
|
* and defines no `initialize`, so it can never be the handshake's version — a client that offers
|
|
11
11
|
* it is asking to negotiate a revision with no negotiation.
|
|
12
12
|
*/
|
|
13
|
-
var
|
|
14
|
-
/** The legacy
|
|
15
|
-
var
|
|
13
|
+
var MCP_HANDSHAKE_VERSION = "2025-11-25";
|
|
14
|
+
/** The older legacy revision the optional legacy decorator accepts and an adapter can pin. */
|
|
15
|
+
var MCP_FALLBACK_VERSION = "2025-06-18";
|
|
16
16
|
/** The modern revision offered by an unpinned client during discovery. */
|
|
17
17
|
var MCP_MODERN_VERSION = "2026-07-28";
|
|
18
18
|
/**
|
|
19
|
-
* The MCP protocol revisions
|
|
19
|
+
* The modern MCP protocol revisions a bare server accepts and advertises.
|
|
20
20
|
*
|
|
21
21
|
* @remarks
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* batching, while this package accepts only individual JSON-RPC messages.
|
|
22
|
+
* Frozen in discovery-advertisement order. Legacy revisions are absent because
|
|
23
|
+
* only {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS} and the optional legacy
|
|
24
|
+
* decorator own them.
|
|
26
25
|
*/
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
]);
|
|
26
|
+
var SUPPORTED_MODERN_PROTOCOL_VERSIONS = Object.freeze([MCP_MODERN_VERSION]);
|
|
27
|
+
/** The protocol revisions accepted by the optional legacy decorator. */
|
|
28
|
+
var SUPPORTED_LEGACY_PROTOCOL_VERSIONS = Object.freeze([MCP_HANDSHAKE_VERSION, MCP_FALLBACK_VERSION]);
|
|
29
|
+
/** The protocol revisions the `isMCPVersion` guard admits, spanning the modern and legacy eras. */
|
|
30
|
+
var SUPPORTED_MCP_VERSIONS = Object.freeze([...SUPPORTED_MODERN_PROTOCOL_VERSIONS, ...SUPPORTED_LEGACY_PROTOCOL_VERSIONS]);
|
|
32
31
|
/** Reserved modern `_meta` key carrying the request's protocol revision. */
|
|
33
32
|
var MCP_META_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
34
33
|
/** Reserved modern `_meta` key carrying the client's open capability record. */
|
|
@@ -40,10 +39,11 @@ var MCP_META_SERVER = "io.modelcontextprotocol/serverInfo";
|
|
|
40
39
|
/** Reserved modern `_meta` key carrying a `subscriptions/listen` request id. */
|
|
41
40
|
var MCP_META_SUBSCRIPTION = "io.modelcontextprotocol/subscriptionId";
|
|
42
41
|
/**
|
|
43
|
-
* The reserved extension key identifying the
|
|
42
|
+
* The reserved extension key identifying the stable Tasks extension.
|
|
44
43
|
*
|
|
45
44
|
* @remarks
|
|
46
|
-
* The ONE spelling of it in this package
|
|
45
|
+
* The ONE spelling of it in this package, and the identity of the immutable snapshot dated
|
|
46
|
+
* 2026-07-28 this package implements. A client declares it per REQUEST, under
|
|
47
47
|
* `_meta['io.modelcontextprotocol/clientCapabilities'].extensions`; a server advertises it
|
|
48
48
|
* under `server/discover`'s `capabilities.extensions`. Both sides carry an empty object —
|
|
49
49
|
* the extension defines no options, so presence is the entire declaration.
|
|
@@ -61,8 +61,8 @@ var MCP_HEADER_MISMATCH = -32020;
|
|
|
61
61
|
* `error.data.requiredCapabilities` alone (`{ elicitation: {} }` against
|
|
62
62
|
* `{ extensions: { 'io.modelcontextprotocol/tasks': {} } }`). They are instances of the same
|
|
63
63
|
* condition, so a separate numeral would describe the same fact twice. The Tasks extension's
|
|
64
|
-
* own
|
|
65
|
-
*
|
|
64
|
+
* own prose examples show `-32003`; the dated core schema fixes this code, and the dated
|
|
65
|
+
* schema is what a peer implements against.
|
|
66
66
|
*/
|
|
67
67
|
var MCP_MISSING_CAPABILITY = -32021;
|
|
68
68
|
/** MCP reserved error: a request names an unsupported protocol revision. */
|
|
@@ -147,6 +147,8 @@ var DEFAULT_MCP_CLIENT_VERSION = "1.0.0";
|
|
|
147
147
|
* is unset — a request the remote server does not answer within it rejects.
|
|
148
148
|
*/
|
|
149
149
|
var DEFAULT_MCP_REQUEST_TIMEOUT = 3e4;
|
|
150
|
+
/** The default number of subscription frames retained while no client read is parked. */
|
|
151
|
+
var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
|
|
150
152
|
//#endregion
|
|
151
153
|
//#region src/core/errors.ts
|
|
152
154
|
/**
|
|
@@ -209,66 +211,6 @@ function isMCPError(value) {
|
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
//#endregion
|
|
212
|
-
//#region src/core/inferers.ts
|
|
213
|
-
/**
|
|
214
|
-
* Infers the wire era for an MCP protocol revision.
|
|
215
|
-
*
|
|
216
|
-
* @param version - The protocol revision to classify
|
|
217
|
-
* @returns `'modern'` for `2026-07-28`, `'legacy'` for either supported legacy
|
|
218
|
-
* revision, or `undefined` when the revision is unsupported
|
|
219
|
-
*/
|
|
220
|
-
function inferEra(version) {
|
|
221
|
-
switch (version) {
|
|
222
|
-
case "2026-07-28": return "modern";
|
|
223
|
-
case "2025-11-25":
|
|
224
|
-
case "2025-06-18": return "legacy";
|
|
225
|
-
default: return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Infers the newest supported protocol revision present in a peer's offer.
|
|
230
|
-
*
|
|
231
|
-
* @param offered - The protocol revisions offered by the peer
|
|
232
|
-
* @returns The newest locally supported offered revision, or `undefined`
|
|
233
|
-
*/
|
|
234
|
-
function inferVersion(offered) {
|
|
235
|
-
for (const version of SUPPORTED_PROTOCOL_VERSIONS) if (offered.includes(version)) return version;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Infers the protocol version an outbound message announces itself with — the ONE
|
|
239
|
-
* projection every HTTP client transport stamps `mcp-protocol-version` from.
|
|
240
|
-
*
|
|
241
|
-
* @remarks
|
|
242
|
-
* This is deliberately the SAME read the server's own expectation performs
|
|
243
|
-
* ({@link import('@orkestrel/mcp/server').inferHeaderIssue}): a modern request's reserved
|
|
244
|
-
* `_meta` version, accepted whenever it is a string. It is NOT
|
|
245
|
-
* {@link import('./parsers.js').parseRequestContext}, and the difference is the whole
|
|
246
|
-
* point. That parser answers a different question — is the modern metadata WELL FORMED —
|
|
247
|
-
* and refuses a request whose capability declaration or logging level is malformed. Such a
|
|
248
|
-
* request is still modern (era is fixed by key presence) and the server still demands the
|
|
249
|
-
* header for it, so projecting through the parser withholds a header the peer requires and
|
|
250
|
-
* earns `-32602` instead of the `-32602` the malformed metadata itself deserves.
|
|
251
|
-
*
|
|
252
|
-
* A non-modern message projects nothing: a legacy request's version comes from the
|
|
253
|
-
* `initialize` handshake the transport captured, not from the message.
|
|
254
|
-
*
|
|
255
|
-
* Header NAMES stay with the transports that own the wire (see `constants.ts`); core owns
|
|
256
|
-
* the value this projection derives, which is the part the browser and Node faces disagreed about.
|
|
257
|
-
*
|
|
258
|
-
* @param message - The outbound message about to be written
|
|
259
|
-
* @returns The version to announce, or `undefined` when the message announces none
|
|
260
|
-
*
|
|
261
|
-
* @example
|
|
262
|
-
* ```ts
|
|
263
|
-
* inferRequestVersion({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
|
|
264
|
-
* ```
|
|
265
|
-
*/
|
|
266
|
-
function inferRequestVersion(message) {
|
|
267
|
-
if (!isModernRequest(message)) return void 0;
|
|
268
|
-
const version = (isRecord(message.params?.["_meta"]) ? message.params["_meta"] : void 0)?.[MCP_META_VERSION];
|
|
269
|
-
return isString(version) ? version : void 0;
|
|
270
|
-
}
|
|
271
|
-
//#endregion
|
|
272
214
|
//#region src/core/cloners.ts
|
|
273
215
|
/**
|
|
274
216
|
* Snapshots one bounded exact JSON value together with its canonical wire serialization.
|
|
@@ -570,26 +512,29 @@ function isFormElicitationSupported(value) {
|
|
|
570
512
|
}
|
|
571
513
|
}
|
|
572
514
|
/**
|
|
573
|
-
* Determines whether a client capability record declares the
|
|
515
|
+
* Determines whether a client capability record declares the stable Tasks extension.
|
|
574
516
|
*
|
|
575
517
|
* @remarks
|
|
576
|
-
* The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
518
|
+
* The declaration lives at `extensions['io.modelcontextprotocol/tasks']` and the schema
|
|
519
|
+
* types its value EXACTLY EMPTY — `Record<string, never>`, an object with no additional
|
|
520
|
+
* properties. So the key's presence is the whole declaration, and the value carries the
|
|
521
|
+
* whole of the check: a `true` or a string there is a client speaking a different protocol
|
|
522
|
+
* rather than a shorthand, and a member inside the object is a client declaring an option
|
|
523
|
+
* this extension does not define. Both are refused, because a server that accepted either
|
|
524
|
+
* would be reading a shape no peer can produce from the snapshot's own schema.
|
|
581
525
|
*
|
|
582
526
|
* A client declares this PER REQUEST. Nothing here consults a session, because the modern
|
|
583
527
|
* revision is stateless and a capability declared once at connect time says nothing about
|
|
584
528
|
* the request in hand. Total over hostile input.
|
|
585
529
|
*
|
|
586
530
|
* @param value - The client capability record to inspect
|
|
587
|
-
* @returns `true` when the tasks extension is declared
|
|
531
|
+
* @returns `true` when the tasks extension is declared as the schema's empty object
|
|
588
532
|
*
|
|
589
533
|
* @example
|
|
590
534
|
* ```ts
|
|
591
535
|
* isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': {} } }) // true
|
|
592
536
|
* isTaskSupported({ extensions: {} }) // false — the key is the declaration
|
|
537
|
+
* isTaskSupported({ extensions: { 'io.modelcontextprotocol/tasks': { on: true } } }) // false
|
|
593
538
|
* ```
|
|
594
539
|
*/
|
|
595
540
|
function isTaskSupported(value) {
|
|
@@ -597,7 +542,9 @@ function isTaskSupported(value) {
|
|
|
597
542
|
if (!owned.success) return false;
|
|
598
543
|
try {
|
|
599
544
|
const extensions = owned.value["extensions"];
|
|
600
|
-
|
|
545
|
+
if (!isRecord(extensions)) return false;
|
|
546
|
+
const declaration = extensions[MCP_EXTENSION_TASKS];
|
|
547
|
+
return isRecord(declaration) && Object.keys(declaration).length === 0;
|
|
601
548
|
} catch {
|
|
602
549
|
return false;
|
|
603
550
|
}
|
|
@@ -1142,23 +1089,118 @@ function buildModernResult(result, identity, ttl, scope) {
|
|
|
1142
1089
|
};
|
|
1143
1090
|
}
|
|
1144
1091
|
/**
|
|
1092
|
+
* Projects one complete modern result onto the legacy wire shape.
|
|
1093
|
+
*
|
|
1094
|
+
* @remarks
|
|
1095
|
+
* The projection removes the modern discriminator, cache fields, and reserved server identity.
|
|
1096
|
+
* A non-complete result has no legacy representation and returns `undefined`.
|
|
1097
|
+
*
|
|
1098
|
+
* @param result - The modern result to project
|
|
1099
|
+
* @returns The legacy result, or `undefined` when the modern arm cannot be represented
|
|
1100
|
+
*/
|
|
1101
|
+
function modernResultToLegacy(result) {
|
|
1102
|
+
if (result.resultType !== "complete") return void 0;
|
|
1103
|
+
const projected = {};
|
|
1104
|
+
for (const [key, value] of Object.entries(result)) {
|
|
1105
|
+
if (key === "resultType" || key === "ttlMs" || key === "cacheScope") continue;
|
|
1106
|
+
if (key === "content" && Array.isArray(value)) {
|
|
1107
|
+
projected[key] = value.map((entry) => isRecord(entry) && entry["type"] === "text" && isString(entry["text"]) ? {
|
|
1108
|
+
type: "text",
|
|
1109
|
+
text: entry["text"]
|
|
1110
|
+
} : entry);
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
if (key !== "_meta" || !isRecord(value)) {
|
|
1114
|
+
projected[key] = value;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const metadata = {};
|
|
1118
|
+
for (const [name, entry] of Object.entries(value)) if (name !== "io.modelcontextprotocol/serverInfo") metadata[name] = entry;
|
|
1119
|
+
if (Object.keys(metadata).length > 0) projected["_meta"] = metadata;
|
|
1120
|
+
}
|
|
1121
|
+
return projected;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Restores one legacy result to the modern complete-result shape.
|
|
1125
|
+
*
|
|
1126
|
+
* @remarks
|
|
1127
|
+
* Legacy `tools/list` results receive the required modern cache fields. Other legacy results are
|
|
1128
|
+
* non-cacheable. Every restored result receives the server identity learned during `initialize`.
|
|
1129
|
+
*
|
|
1130
|
+
* @param result - The unstamped legacy result
|
|
1131
|
+
* @param method - The request method whose result is being restored
|
|
1132
|
+
* @param identity - The server identity learned during the legacy handshake
|
|
1133
|
+
* @returns The modern complete result
|
|
1134
|
+
*/
|
|
1135
|
+
function legacyResultToModern(result, method, identity) {
|
|
1136
|
+
return method === "tools/list" ? buildModernResult(result, identity, DEFAULT_MCP_CACHE_TTL) : buildModernResult(result, identity);
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Stamps one legacy request for the modern dispatcher.
|
|
1140
|
+
*
|
|
1141
|
+
* @param request - The legacy request to translate
|
|
1142
|
+
* @returns A modern request carrying the package revision and an empty capability set
|
|
1143
|
+
*/
|
|
1144
|
+
function legacyInvocationToModern(request) {
|
|
1145
|
+
const params = request.params ?? {};
|
|
1146
|
+
const metadata = isRecord(params["_meta"]) ? params["_meta"] : {};
|
|
1147
|
+
return {
|
|
1148
|
+
...request,
|
|
1149
|
+
params: {
|
|
1150
|
+
...params,
|
|
1151
|
+
_meta: {
|
|
1152
|
+
...metadata,
|
|
1153
|
+
[MCP_META_VERSION]: MCP_MODERN_VERSION,
|
|
1154
|
+
[MCP_META_CAPABILITIES]: {}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
/**
|
|
1160
|
+
* Removes modern request metadata before an invocation reaches a legacy peer.
|
|
1161
|
+
*
|
|
1162
|
+
* @remarks
|
|
1163
|
+
* Non-reserved metadata such as `progressToken` remains on the legacy wire. When no metadata
|
|
1164
|
+
* remains, the translated parameters omit `_meta`.
|
|
1165
|
+
*
|
|
1166
|
+
* @param invocation - The modern invocation to translate
|
|
1167
|
+
* @returns The legacy invocation with reserved modern metadata removed
|
|
1168
|
+
*/
|
|
1169
|
+
function modernInvocationToLegacy(invocation) {
|
|
1170
|
+
const params = invocation.params;
|
|
1171
|
+
if (params === void 0 || !isRecord(params["_meta"])) return invocation;
|
|
1172
|
+
const translated = {};
|
|
1173
|
+
for (const [key, value] of Object.entries(params)) if (key !== "_meta") translated[key] = value;
|
|
1174
|
+
const metadata = {};
|
|
1175
|
+
for (const [key, value] of Object.entries(params["_meta"])) if (key !== "io.modelcontextprotocol/protocolVersion" && key !== "io.modelcontextprotocol/clientCapabilities" && key !== "io.modelcontextprotocol/clientInfo") metadata[key] = value;
|
|
1176
|
+
if (Object.keys(metadata).length > 0) translated["_meta"] = metadata;
|
|
1177
|
+
return {
|
|
1178
|
+
...invocation,
|
|
1179
|
+
params: translated
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1145
1183
|
* Intersects a requested subscription filter with the notification families a server supports.
|
|
1146
1184
|
*
|
|
1147
1185
|
* @param requested - The notification families requested by the client
|
|
1148
1186
|
* @param supported - The notification families the server can actually produce
|
|
1187
|
+
* @param enabled - If `true`, carries the requested task identifiers into the filter; if `false`,
|
|
1188
|
+
* omits them. Default: `false`
|
|
1149
1189
|
* @returns The exact subset the server will honour
|
|
1150
1190
|
*/
|
|
1151
|
-
function buildSubscriptionFilter(requested, supported) {
|
|
1191
|
+
function buildSubscriptionFilter(requested, supported, enabled = false) {
|
|
1152
1192
|
const toolsListChanged = requested.toolsListChanged === true && supported.toolsListChanged === true;
|
|
1153
1193
|
const promptsListChanged = requested.promptsListChanged === true && supported.promptsListChanged === true;
|
|
1154
1194
|
const resourcesListChanged = requested.resourcesListChanged === true && supported.resourcesListChanged === true;
|
|
1155
1195
|
const supportedResources = new Set(supported.resourceSubscriptions ?? []);
|
|
1156
1196
|
const resourceSubscriptions = requested.resourceSubscriptions?.filter((uri) => supportedResources.has(uri));
|
|
1197
|
+
const taskIds = enabled ? requested.taskIds : void 0;
|
|
1157
1198
|
return {
|
|
1158
1199
|
...toolsListChanged ? { toolsListChanged: true } : {},
|
|
1159
1200
|
...promptsListChanged ? { promptsListChanged: true } : {},
|
|
1160
1201
|
...resourcesListChanged ? { resourcesListChanged: true } : {},
|
|
1161
|
-
...resourceSubscriptions !== void 0 && resourceSubscriptions.length > 0 ? { resourceSubscriptions } : {}
|
|
1202
|
+
...resourceSubscriptions !== void 0 && resourceSubscriptions.length > 0 ? { resourceSubscriptions } : {},
|
|
1203
|
+
...taskIds !== void 0 && taskIds.length > 0 ? { taskIds } : {}
|
|
1162
1204
|
};
|
|
1163
1205
|
}
|
|
1164
1206
|
/**
|
|
@@ -1172,9 +1214,12 @@ function matchesSubscriptionNotification(notification, filter) {
|
|
|
1172
1214
|
if (notification.method === "notifications/tools/list_changed") return filter.toolsListChanged === true;
|
|
1173
1215
|
if (notification.method === "notifications/prompts/list_changed") return filter.promptsListChanged === true;
|
|
1174
1216
|
if (notification.method === "notifications/resources/list_changed") return filter.resourcesListChanged === true;
|
|
1175
|
-
if (notification.method
|
|
1176
|
-
|
|
1177
|
-
|
|
1217
|
+
if (notification.method === "notifications/resources/updated") {
|
|
1218
|
+
const uri = notification.params?.["uri"];
|
|
1219
|
+
return typeof uri === "string" && filter.resourceSubscriptions?.includes(uri) === true;
|
|
1220
|
+
}
|
|
1221
|
+
if (notification.method === "notifications/tasks") return isMCPTaskNotification(notification) && filter.taskIds?.includes(notification.params.taskId) === true;
|
|
1222
|
+
return false;
|
|
1178
1223
|
}
|
|
1179
1224
|
/**
|
|
1180
1225
|
* Stamps a subscription notification with the request id reserved for its held-open stream.
|
|
@@ -1239,7 +1284,7 @@ function buildSubscriptionResult(id, identity) {
|
|
|
1239
1284
|
*/
|
|
1240
1285
|
function buildDiscoverResult(options) {
|
|
1241
1286
|
return buildModernResult({
|
|
1242
|
-
supportedVersions:
|
|
1287
|
+
supportedVersions: SUPPORTED_MODERN_PROTOCOL_VERSIONS,
|
|
1243
1288
|
capabilities: {
|
|
1244
1289
|
tools: {},
|
|
1245
1290
|
...options.resources === void 0 ? {} : { resources: {
|
|
@@ -1270,9 +1315,8 @@ function buildDiscoverResult(options) {
|
|
|
1270
1315
|
* @returns The `initialize` result payload
|
|
1271
1316
|
*/
|
|
1272
1317
|
function buildInitializeResult(name, version, requested) {
|
|
1273
|
-
const newestLegacy = SUPPORTED_PROTOCOL_VERSIONS.find((candidate) => inferEra(candidate) === "legacy") ?? "2025-06-18";
|
|
1274
1318
|
return {
|
|
1275
|
-
protocolVersion:
|
|
1319
|
+
protocolVersion: isMCPLegacyVersion(requested) ? requested : MCP_HANDSHAKE_VERSION,
|
|
1276
1320
|
capabilities: { tools: {} },
|
|
1277
1321
|
serverInfo: {
|
|
1278
1322
|
name,
|
|
@@ -1586,6 +1630,31 @@ function isMCPResultMetaObject(value) {
|
|
|
1586
1630
|
const identity = owned.value[MCP_META_SERVER];
|
|
1587
1631
|
return isUndefined(identity) || isMCPIdentity(identity);
|
|
1588
1632
|
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Determines whether a value is exact notification metadata with a valid reserved
|
|
1635
|
+
* subscription id.
|
|
1636
|
+
*
|
|
1637
|
+
* @remarks
|
|
1638
|
+
* The reserved key is OPTIONAL, so a frame delivered outside a `subscriptions/listen`
|
|
1639
|
+
* stream passes with no stamp at all. When the key IS present its value must be a valid
|
|
1640
|
+
* {@link JSONRPCId}, because a stamp naming nothing addressable is worse than no stamp.
|
|
1641
|
+
*
|
|
1642
|
+
* @param value - The unknown value to inspect
|
|
1643
|
+
* @returns `true` when the value is exact metadata whose subscription stamp, if present, is valid
|
|
1644
|
+
*
|
|
1645
|
+
* @example
|
|
1646
|
+
* ```ts
|
|
1647
|
+
* isMCPNotificationMetaObject({}) // true — an unstamped frame carries no subscription
|
|
1648
|
+
* isMCPNotificationMetaObject({ 'io.modelcontextprotocol/subscriptionId': 7 }) // true
|
|
1649
|
+
* isMCPNotificationMetaObject({ 'io.modelcontextprotocol/subscriptionId': null }) // false
|
|
1650
|
+
* ```
|
|
1651
|
+
*/
|
|
1652
|
+
function isMCPNotificationMetaObject(value) {
|
|
1653
|
+
const owned = attempt(() => cloneJSONRecord(value));
|
|
1654
|
+
if (!owned.success || !Object.keys(owned.value).every((key) => isMCPMetaKey(key))) return false;
|
|
1655
|
+
const subscription = owned.value[MCP_META_SUBSCRIPTION];
|
|
1656
|
+
return isUndefined(subscription) || isJSONRPCId(subscription);
|
|
1657
|
+
}
|
|
1589
1658
|
/** Determines whether a value is one dated MCP logging level. */
|
|
1590
1659
|
function isMCPLoggingLevel(value) {
|
|
1591
1660
|
return value === "debug" || value === "info" || value === "notice" || value === "warning" || value === "error" || value === "critical" || value === "alert" || value === "emergency";
|
|
@@ -2248,7 +2317,8 @@ function isMCPCallResult(value) {
|
|
|
2248
2317
|
* shape. The manager is consumer-supplied, so its types are a promise rather than a
|
|
2249
2318
|
* proof: this is what stands between a manager that answers a numeric `taskId` and a
|
|
2250
2319
|
* client that would receive one. `ttlMs` accepts `null` because the schema uses it to
|
|
2251
|
-
* mean "no expiry", which is distinct from an absent field
|
|
2320
|
+
* mean "no expiry", which is distinct from an absent field, and both durations must be
|
|
2321
|
+
* INTEGER milliseconds because the schema formats them `int`.
|
|
2252
2322
|
*
|
|
2253
2323
|
* @param value - The unknown value to inspect
|
|
2254
2324
|
* @returns Whether the value is a well-formed `resultType: 'task'` result
|
|
@@ -2269,7 +2339,7 @@ function isMCPTaskResult(value) {
|
|
|
2269
2339
|
const interval = result["pollIntervalMs"];
|
|
2270
2340
|
const lifetime = result["ttlMs"];
|
|
2271
2341
|
const metadata = result["_meta"];
|
|
2272
|
-
return isString(result["taskId"]) && isMCPTaskStatus(result["status"]) && isString(result["createdAt"]) && isString(result["lastUpdatedAt"]) && (lifetime === null ||
|
|
2342
|
+
return isString(result["taskId"]) && isMCPTaskStatus(result["status"]) && isString(result["createdAt"]) && isString(result["lastUpdatedAt"]) && (lifetime === null || isInteger(lifetime)) && (isUndefined(message) || isString(message)) && (isUndefined(interval) || isInteger(interval)) && (isUndefined(metadata) || isMCPResultMetaObject(metadata));
|
|
2273
2343
|
} catch {
|
|
2274
2344
|
return false;
|
|
2275
2345
|
}
|
|
@@ -2299,9 +2369,14 @@ function isMCPTaskStatus(value) {
|
|
|
2299
2369
|
* the requests to answer, `completed` owns the deferred call's result, `failed` owns the
|
|
2300
2370
|
* JSON-RPC error that ended it, and `working` / `cancelled` own nothing further.
|
|
2301
2371
|
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
2304
|
-
*
|
|
2372
|
+
* A `completed` task's `result` is checked as an OBJECT and no further. The schema declares
|
|
2373
|
+
* it an open record, so its contents belong to whichever method was deferred; a guard that
|
|
2374
|
+
* demanded a protocol result here would refuse payloads the extension permits.
|
|
2375
|
+
* `ttlMs` and `pollIntervalMs` are integer milliseconds, per the schema's `int` formats.
|
|
2376
|
+
*
|
|
2377
|
+
* Unrecognized members stay valid, because this guard reads a value a consumer's manager
|
|
2378
|
+
* produced, and a guard over a foreign contract enforces the published contract and no more.
|
|
2379
|
+
* What is checked is what this package publishes as the contract.
|
|
2305
2380
|
*
|
|
2306
2381
|
* @param value - The unknown value to inspect
|
|
2307
2382
|
* @returns Whether the value is a well-formed {@link MCPTaskDetail}
|
|
@@ -2323,9 +2398,9 @@ function isMCPTaskDetail(value) {
|
|
|
2323
2398
|
const message = detail["statusMessage"];
|
|
2324
2399
|
const interval = detail["pollIntervalMs"];
|
|
2325
2400
|
const lifetime = detail["ttlMs"];
|
|
2326
|
-
if (!isString(detail["taskId"]) || !isMCPTaskStatus(status) || !isString(detail["createdAt"]) || !isString(detail["lastUpdatedAt"]) || lifetime !== null && !
|
|
2401
|
+
if (!isString(detail["taskId"]) || !isMCPTaskStatus(status) || !isString(detail["createdAt"]) || !isString(detail["lastUpdatedAt"]) || lifetime !== null && !isInteger(lifetime) || !isUndefined(message) && !isString(message) || !isUndefined(interval) && !isInteger(interval)) return false;
|
|
2327
2402
|
if (status === "input_required") return isMCPInputRequestMap(detail["inputRequests"]);
|
|
2328
|
-
if (status === "completed") return
|
|
2403
|
+
if (status === "completed") return isRecord(detail["result"]);
|
|
2329
2404
|
if (status === "failed") return isJSONRPCError(detail["error"]);
|
|
2330
2405
|
return true;
|
|
2331
2406
|
} catch {
|
|
@@ -2333,6 +2408,78 @@ function isMCPTaskDetail(value) {
|
|
|
2333
2408
|
}
|
|
2334
2409
|
}
|
|
2335
2410
|
/**
|
|
2411
|
+
* Determines whether a value is the wire answer to `tasks/get`.
|
|
2412
|
+
*
|
|
2413
|
+
* @remarks
|
|
2414
|
+
* {@link isMCPTaskDetail} plus the stamp the METHOD owes. The schema types a `tasks/get`
|
|
2415
|
+
* reply as the detail intersected with the standard result, so `resultType: 'complete'` is
|
|
2416
|
+
* part of the answer rather than decoration on it — and an unstamped payload, or one
|
|
2417
|
+
* carrying the creation answer's `resultType: 'task'`, is a peer answering some other
|
|
2418
|
+
* shape. Use this guard wherever a `tasks/get` REPLY is read; use
|
|
2419
|
+
* {@link isMCPTaskDetail} wherever a consumer's manager answers directly.
|
|
2420
|
+
*
|
|
2421
|
+
* `_meta` is checked only when present, and only as result metadata: the server identity a
|
|
2422
|
+
* peer stamps there is the peer's to write.
|
|
2423
|
+
*
|
|
2424
|
+
* @param value - The unknown value to inspect
|
|
2425
|
+
* @returns Whether the value is a well-formed {@link MCPTaskDetailResult}
|
|
2426
|
+
*
|
|
2427
|
+
* @example
|
|
2428
|
+
* ```ts
|
|
2429
|
+
* isMCPTaskDetailResult({ resultType: 'complete', taskId: 'a', status: 'working',
|
|
2430
|
+
* createdAt: '', lastUpdatedAt: '', ttlMs: null }) // true
|
|
2431
|
+
* isMCPTaskDetailResult({ taskId: 'a', status: 'working', createdAt: '',
|
|
2432
|
+
* lastUpdatedAt: '', ttlMs: null }) // false — the reply owes its `resultType`
|
|
2433
|
+
* ```
|
|
2434
|
+
*/
|
|
2435
|
+
function isMCPTaskDetailResult(value) {
|
|
2436
|
+
const owned = attempt(() => cloneJSONRecord(value));
|
|
2437
|
+
if (!owned.success) return false;
|
|
2438
|
+
const result = owned.value;
|
|
2439
|
+
if (result["resultType"] !== "complete") return false;
|
|
2440
|
+
const metadata = result["_meta"];
|
|
2441
|
+
if (!isUndefined(metadata) && !isMCPResultMetaObject(metadata)) return false;
|
|
2442
|
+
return isMCPTaskDetail(result);
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Determines whether a value is a `notifications/tasks` frame carrying a task snapshot.
|
|
2446
|
+
*
|
|
2447
|
+
* @remarks
|
|
2448
|
+
* The ADMISSION guard for a task transition: a subscription producer is consumer-written,
|
|
2449
|
+
* so the frame it hands over is foreign input, and this is what stands between a mutated
|
|
2450
|
+
* or half-built snapshot and a subscribed client. Both halves are checked — the method
|
|
2451
|
+
* literal the extension fixes, and params that hold together as an
|
|
2452
|
+
* {@link MCPTaskDetail} — because either alone admits a frame the other rejects.
|
|
2453
|
+
*
|
|
2454
|
+
* `_meta` is checked for SHAPE WHEN PRESENT and nothing more. The reserved subscription
|
|
2455
|
+
* stamp is the SERVER'S to write, after this guard admits the frame and the matcher agrees
|
|
2456
|
+
* to it, so a guard that demanded the stamp would refuse every frame a producer emits.
|
|
2457
|
+
*
|
|
2458
|
+
* @param value - The unknown value to inspect
|
|
2459
|
+
* @returns Whether the value is a well-formed `notifications/tasks` notification
|
|
2460
|
+
*
|
|
2461
|
+
* @example
|
|
2462
|
+
* ```ts
|
|
2463
|
+
* isMCPTaskNotification({ jsonrpc: '2.0', method: 'notifications/tasks',
|
|
2464
|
+
* params: { taskId: 'a', status: 'working', createdAt: '', lastUpdatedAt: '',
|
|
2465
|
+
* ttlMs: null } }) // true
|
|
2466
|
+
* isMCPTaskNotification({ jsonrpc: '2.0', method: 'notifications/tasks',
|
|
2467
|
+
* params: { taskId: 'a' } }) // false — the params owe a whole snapshot
|
|
2468
|
+
* ```
|
|
2469
|
+
*/
|
|
2470
|
+
function isMCPTaskNotification(value) {
|
|
2471
|
+
const owned = attempt(() => cloneJSONRecord(value));
|
|
2472
|
+
if (!owned.success) return false;
|
|
2473
|
+
const notification = owned.value;
|
|
2474
|
+
if (!isJSONRPCNotification(notification)) return false;
|
|
2475
|
+
if (notification["method"] !== "notifications/tasks") return false;
|
|
2476
|
+
const params = notification["params"];
|
|
2477
|
+
if (!isRecord(params)) return false;
|
|
2478
|
+
const metadata = params["_meta"];
|
|
2479
|
+
if (!isUndefined(metadata) && !isMCPNotificationMetaObject(metadata)) return false;
|
|
2480
|
+
return isMCPTaskDetail(params);
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2336
2483
|
* Determines whether a value is a string within a UTF-8 byte bound.
|
|
2337
2484
|
*
|
|
2338
2485
|
* @param value - The unknown value to inspect
|
|
@@ -2413,19 +2560,41 @@ function isJSONRPCId(value) {
|
|
|
2413
2560
|
* Determines whether a value is a supported {@link MCPVersion}.
|
|
2414
2561
|
*
|
|
2415
2562
|
* @param value - The unknown value to inspect
|
|
2416
|
-
* @returns `true` when the value is one of {@link
|
|
2563
|
+
* @returns `true` when the value is one of {@link SUPPORTED_MCP_VERSIONS}
|
|
2417
2564
|
*/
|
|
2418
2565
|
function isMCPVersion(value) {
|
|
2419
|
-
return isString(value) &&
|
|
2566
|
+
return isString(value) && SUPPORTED_MCP_VERSIONS.some((version) => version === value);
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Determines whether a value is a modern protocol revision accepted by a bare server.
|
|
2570
|
+
*
|
|
2571
|
+
* @param value - The unknown value to inspect
|
|
2572
|
+
* @returns `true` when the value is one of {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS}
|
|
2573
|
+
*/
|
|
2574
|
+
function isMCPModernVersion(value) {
|
|
2575
|
+
return isString(value) && SUPPORTED_MODERN_PROTOCOL_VERSIONS.some((version) => version === value);
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Determines whether a value is a revision accepted by the optional legacy decorator.
|
|
2579
|
+
*
|
|
2580
|
+
* @param value - The unknown value to inspect
|
|
2581
|
+
* @returns `true` when the value is one of {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
|
|
2582
|
+
*/
|
|
2583
|
+
function isMCPLegacyVersion(value) {
|
|
2584
|
+
return isString(value) && SUPPORTED_LEGACY_PROTOCOL_VERSIONS.some((version) => version === value);
|
|
2420
2585
|
}
|
|
2421
2586
|
/**
|
|
2422
2587
|
* Determines whether a value is an MCP {@link MCPSubscriptionFilter}.
|
|
2423
2588
|
*
|
|
2424
2589
|
* @remarks
|
|
2425
|
-
* Every filter field is optional. Boolean notification families accept only booleans,
|
|
2426
|
-
* `resourceSubscriptions` accepts only an array of string URIs
|
|
2427
|
-
*
|
|
2428
|
-
* hostile input.
|
|
2590
|
+
* Every filter field is optional. Boolean notification families accept only booleans,
|
|
2591
|
+
* `resourceSubscriptions` accepts only an array of string URIs, and `taskIds` accepts only
|
|
2592
|
+
* an array of string task identifiers. Unknown fields remain open for protocol extensions
|
|
2593
|
+
* and are ignored by the built-in subscription matcher. Total over hostile input.
|
|
2594
|
+
*
|
|
2595
|
+
* A malformed `taskIds` is refused here rather than dropped, so the listen request that
|
|
2596
|
+
* carried it fails outright instead of quietly agreeing to a narrower subscription than
|
|
2597
|
+
* the caller asked for.
|
|
2429
2598
|
*
|
|
2430
2599
|
* @param value - The unknown value to inspect
|
|
2431
2600
|
* @returns `true` when every recognized filter field has its protocol shape
|
|
@@ -2441,7 +2610,21 @@ function isMCPSubscriptionFilter(value) {
|
|
|
2441
2610
|
const resources = filter["resourcesListChanged"];
|
|
2442
2611
|
if (!isUndefined(resources) && !isBoolean(resources)) return false;
|
|
2443
2612
|
const subscriptions = filter["resourceSubscriptions"];
|
|
2444
|
-
|
|
2613
|
+
if (!isUndefined(subscriptions) && !arrayOf(isString)(subscriptions)) return false;
|
|
2614
|
+
const tasks = filter["taskIds"];
|
|
2615
|
+
return isUndefined(tasks) || arrayOf(isString)(tasks);
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Determines whether a value is a graceful `subscriptions/listen` result.
|
|
2619
|
+
*
|
|
2620
|
+
* @param value - The unknown value to inspect
|
|
2621
|
+
* @returns `true` when the result is complete and carries a valid subscription id
|
|
2622
|
+
*/
|
|
2623
|
+
function isMCPSubscriptionResult(value) {
|
|
2624
|
+
const owned = attempt(() => cloneJSONRecord(value));
|
|
2625
|
+
if (!owned.success || owned.value["resultType"] !== "complete") return false;
|
|
2626
|
+
const metadata = owned.value["_meta"];
|
|
2627
|
+
return isMCPResultMetaObject(metadata) && isRecord(metadata) && isJSONRPCId(metadata["io.modelcontextprotocol/subscriptionId"]);
|
|
2445
2628
|
}
|
|
2446
2629
|
/**
|
|
2447
2630
|
* Determines whether a value is one restricted primitive form-elicitation schema.
|
|
@@ -3040,6 +3223,67 @@ function isModernRequest(value) {
|
|
|
3040
3223
|
return isRecord(metadata) && Object.hasOwn(metadata, "io.modelcontextprotocol/protocolVersion");
|
|
3041
3224
|
}
|
|
3042
3225
|
//#endregion
|
|
3226
|
+
//#region src/core/inferers.ts
|
|
3227
|
+
/**
|
|
3228
|
+
* Infers the wire era for an MCP protocol revision.
|
|
3229
|
+
*
|
|
3230
|
+
* @remarks
|
|
3231
|
+
* The era is READ from the two era guards rather than restated here, so a revision added
|
|
3232
|
+
* to {@link SUPPORTED_MODERN_PROTOCOL_VERSIONS} or {@link SUPPORTED_LEGACY_PROTOCOL_VERSIONS}
|
|
3233
|
+
* carries its era with it and no third list can disagree with those two.
|
|
3234
|
+
*
|
|
3235
|
+
* @param version - The protocol revision to classify
|
|
3236
|
+
* @returns `'modern'` for a revision a bare server accepts, `'legacy'` for a revision the
|
|
3237
|
+
* optional decorator accepts, or `undefined` when the revision is unsupported
|
|
3238
|
+
*/
|
|
3239
|
+
function inferEra(version) {
|
|
3240
|
+
if (isMCPModernVersion(version)) return "modern";
|
|
3241
|
+
if (isMCPLegacyVersion(version)) return "legacy";
|
|
3242
|
+
}
|
|
3243
|
+
/**
|
|
3244
|
+
* Infers the newest supported modern protocol revision present in a peer's offer.
|
|
3245
|
+
*
|
|
3246
|
+
* @param offered - The protocol revisions offered by the peer
|
|
3247
|
+
* @returns The newest locally supported modern revision, or `undefined`
|
|
3248
|
+
*/
|
|
3249
|
+
function inferVersion(offered) {
|
|
3250
|
+
for (const version of SUPPORTED_MODERN_PROTOCOL_VERSIONS) if (offered.includes(version)) return version;
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Infers the protocol version an outbound message announces itself with — the ONE
|
|
3254
|
+
* projection every HTTP client transport stamps `mcp-protocol-version` from.
|
|
3255
|
+
*
|
|
3256
|
+
* @remarks
|
|
3257
|
+
* This is deliberately the SAME read the server's own expectation performs
|
|
3258
|
+
* ({@link import('@orkestrel/mcp/server').inferHeaderIssue}): a modern request's reserved
|
|
3259
|
+
* `_meta` version, accepted whenever it is a string. It is NOT
|
|
3260
|
+
* {@link import('./parsers.js').parseRequestContext}, and the difference is the whole
|
|
3261
|
+
* point. That parser answers a different question — is the modern metadata WELL FORMED —
|
|
3262
|
+
* and refuses a request whose capability declaration or logging level is malformed. Such a
|
|
3263
|
+
* request is still modern (era is fixed by key presence) and the server still demands the
|
|
3264
|
+
* header for it, so projecting through the parser withholds a header the peer requires and
|
|
3265
|
+
* earns `-32602` instead of the `-32602` the malformed metadata itself deserves.
|
|
3266
|
+
*
|
|
3267
|
+
* A non-modern message projects nothing: a legacy request's version comes from the
|
|
3268
|
+
* `initialize` handshake the transport captured, not from the message.
|
|
3269
|
+
*
|
|
3270
|
+
* Header NAMES stay with the transports that own the wire (see `constants.ts`); core owns
|
|
3271
|
+
* the value this projection derives, which is the part the browser and Node faces disagreed about.
|
|
3272
|
+
*
|
|
3273
|
+
* @param message - The outbound message about to be written
|
|
3274
|
+
* @returns The version to announce, or `undefined` when the message announces none
|
|
3275
|
+
*
|
|
3276
|
+
* @example
|
|
3277
|
+
* ```ts
|
|
3278
|
+
* inferRequestVersion({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: { _meta: meta } })
|
|
3279
|
+
* ```
|
|
3280
|
+
*/
|
|
3281
|
+
function inferRequestVersion(message) {
|
|
3282
|
+
if (!isModernRequest(message)) return void 0;
|
|
3283
|
+
const version = (isRecord(message.params?.["_meta"]) ? message.params["_meta"] : void 0)?.[MCP_META_VERSION];
|
|
3284
|
+
return isString(version) ? version : void 0;
|
|
3285
|
+
}
|
|
3286
|
+
//#endregion
|
|
3043
3287
|
//#region src/core/MCPMethodManager.ts
|
|
3044
3288
|
/**
|
|
3045
3289
|
* The modern method registry an {@link import('./types.js').MCPServerInterface}
|
|
@@ -3528,9 +3772,12 @@ var MCPTextStreamController = class {
|
|
|
3528
3772
|
* Translates the fixed legacy method set onto one modern dispatcher.
|
|
3529
3773
|
*
|
|
3530
3774
|
* @remarks
|
|
3531
|
-
* This decorator
|
|
3532
|
-
*
|
|
3533
|
-
*
|
|
3775
|
+
* This decorator answers `initialize` and `ping` itself, under the limits the configured
|
|
3776
|
+
* dispatcher advertises through {@link MCPLegacy.limit}: an invocation outside the message
|
|
3777
|
+
* bound earns the same id-less `-32600` refusal the dispatcher produces, whether this
|
|
3778
|
+
* decorator would have answered it or forwarded it. It owns no result normalizer. Modern
|
|
3779
|
+
* invocations pass through untouched. Legacy tool methods acquire modern request metadata,
|
|
3780
|
+
* run through the configured dispatcher, and lose only fields their dated result shape
|
|
3534
3781
|
* cannot represent.
|
|
3535
3782
|
*/
|
|
3536
3783
|
var MCPLegacy = class {
|
|
@@ -3555,6 +3802,7 @@ var MCPLegacy = class {
|
|
|
3555
3802
|
return this.#legacy(invocation, options);
|
|
3556
3803
|
}
|
|
3557
3804
|
async handle(message, options) {
|
|
3805
|
+
if (!isBoundedString(message, this.limit.message)) return JSON.stringify(buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request"));
|
|
3558
3806
|
let parsed;
|
|
3559
3807
|
try {
|
|
3560
3808
|
parsed = JSON.parse(message);
|
|
@@ -3568,12 +3816,16 @@ var MCPLegacy = class {
|
|
|
3568
3816
|
async #legacy(invocation, options) {
|
|
3569
3817
|
if (invocation.id === void 0) return void 0;
|
|
3570
3818
|
const id = invocation.id;
|
|
3819
|
+
if (parseJSONRPCMessage(invocation, {
|
|
3820
|
+
bytes: this.limit.message,
|
|
3821
|
+
depth: this.limit.depth
|
|
3822
|
+
}) === void 0) return buildJSONRPCError(void 0, JSONRPC_INVALID_REQUEST, "Invalid Request");
|
|
3571
3823
|
switch (invocation.method) {
|
|
3572
3824
|
case "initialize": {
|
|
3573
3825
|
const requested = invocation.params?.["protocolVersion"];
|
|
3574
3826
|
return buildJSONRPCResult(id, buildInitializeResult(this.#options.identity.name, this.#options.identity.version, isString(requested) ? requested : void 0));
|
|
3575
3827
|
}
|
|
3576
|
-
case "ping":
|
|
3828
|
+
case "ping": return buildJSONRPCResult(id, {});
|
|
3577
3829
|
case "tools/list": return this.#forward(invocation, options);
|
|
3578
3830
|
case "tools/call":
|
|
3579
3831
|
if (invocation.params !== void 0 && (Object.hasOwn(invocation.params, "requestState") || Object.hasOwn(invocation.params, "inputResponses"))) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: legacy requests cannot continue an input-required result");
|
|
@@ -3582,19 +3834,7 @@ var MCPLegacy = class {
|
|
|
3582
3834
|
}
|
|
3583
3835
|
}
|
|
3584
3836
|
async #forward(request, options) {
|
|
3585
|
-
const
|
|
3586
|
-
const metadata = isRecord(params["_meta"]) ? params["_meta"] : {};
|
|
3587
|
-
const translated = {
|
|
3588
|
-
...request,
|
|
3589
|
-
params: {
|
|
3590
|
-
...params,
|
|
3591
|
-
_meta: {
|
|
3592
|
-
...metadata,
|
|
3593
|
-
[MCP_META_VERSION]: MCP_MODERN_VERSION,
|
|
3594
|
-
[MCP_META_CAPABILITIES]: {}
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
};
|
|
3837
|
+
const translated = legacyInvocationToModern(request);
|
|
3598
3838
|
const answer = await this.#options.dispatcher.dispatch(translated, options);
|
|
3599
3839
|
if (Symbol.asyncIterator in answer) {
|
|
3600
3840
|
answer.stop();
|
|
@@ -3605,25 +3845,8 @@ var MCPLegacy = class {
|
|
|
3605
3845
|
}
|
|
3606
3846
|
#project(answer, id) {
|
|
3607
3847
|
if (answer.error !== void 0) return answer.error.code === -32021 ? this.#unsupported(id, this.#capability(answer)) : answer;
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
for (const [key, value] of Object.entries(answer.result)) {
|
|
3611
|
-
if (key === "resultType" || key === "ttlMs" || key === "cacheScope") continue;
|
|
3612
|
-
if (key === "content" && Array.isArray(value)) {
|
|
3613
|
-
projected[key] = value.map((entry) => isRecord(entry) && entry["type"] === "text" && isString(entry["text"]) ? {
|
|
3614
|
-
type: "text",
|
|
3615
|
-
text: entry["text"]
|
|
3616
|
-
} : entry);
|
|
3617
|
-
continue;
|
|
3618
|
-
}
|
|
3619
|
-
if (key !== "_meta" || !isRecord(value)) {
|
|
3620
|
-
projected[key] = value;
|
|
3621
|
-
continue;
|
|
3622
|
-
}
|
|
3623
|
-
const metadata = {};
|
|
3624
|
-
for (const [name, entry] of Object.entries(value)) if (name !== "io.modelcontextprotocol/serverInfo") metadata[name] = entry;
|
|
3625
|
-
if (Object.keys(metadata).length > 0) projected["_meta"] = metadata;
|
|
3626
|
-
}
|
|
3848
|
+
const projected = modernResultToLegacy(answer.result);
|
|
3849
|
+
if (projected === void 0) return this.#unsupported(id, answer.result.resultType ?? "unstamped");
|
|
3627
3850
|
return buildJSONRPCResult(id, projected);
|
|
3628
3851
|
}
|
|
3629
3852
|
#capability(answer) {
|
|
@@ -3635,7 +3858,230 @@ var MCPLegacy = class {
|
|
|
3635
3858
|
return isRecord(extensions) && Object.hasOwn(extensions, "io.modelcontextprotocol/tasks") ? "task" : "input-required";
|
|
3636
3859
|
}
|
|
3637
3860
|
#unsupported(id, result) {
|
|
3638
|
-
return buildJSONRPCError(id, JSONRPC_SERVER_ERROR, `Legacy protocol ${
|
|
3861
|
+
return buildJSONRPCError(id, JSONRPC_SERVER_ERROR, `Legacy protocol ${MCP_HANDSHAKE_VERSION} cannot represent ${result === "input-required" ? "an" : "a"} ${result} result`);
|
|
3862
|
+
}
|
|
3863
|
+
};
|
|
3864
|
+
//#endregion
|
|
3865
|
+
//#region src/core/MCPLegacyClientTransport.ts
|
|
3866
|
+
/**
|
|
3867
|
+
* Adapts a legacy MCP peer to the modern client transport boundary.
|
|
3868
|
+
*
|
|
3869
|
+
* @remarks
|
|
3870
|
+
* `start` performs the legacy `initialize` handshake. The adapter answers
|
|
3871
|
+
* `server/discover` locally from that handshake, removes modern request metadata before writes,
|
|
3872
|
+
* restores legacy results to modern complete-result shapes before delivery, and bounds retained
|
|
3873
|
+
* request correlations with the configured deadline.
|
|
3874
|
+
*/
|
|
3875
|
+
var MCPLegacyClientTransport = class {
|
|
3876
|
+
#emitter = new Emitter();
|
|
3877
|
+
#transport;
|
|
3878
|
+
#client;
|
|
3879
|
+
#capabilities;
|
|
3880
|
+
#pin;
|
|
3881
|
+
#timeout;
|
|
3882
|
+
#correlations = /* @__PURE__ */ new Map();
|
|
3883
|
+
#handshake = void 0;
|
|
3884
|
+
#instructions = void 0;
|
|
3885
|
+
#server = void 0;
|
|
3886
|
+
#supported = void 0;
|
|
3887
|
+
/**
|
|
3888
|
+
* Creates a legacy client transport adapter.
|
|
3889
|
+
*
|
|
3890
|
+
* @param transport - The legacy peer transport
|
|
3891
|
+
* @param options - The legacy handshake identity, capabilities, revision, and deadline
|
|
3892
|
+
*/
|
|
3893
|
+
constructor(transport, options) {
|
|
3894
|
+
const requested = options?.version;
|
|
3895
|
+
if (requested !== void 0 && !isMCPLegacyVersion(requested)) throw new MCPError("Unsupported legacy protocol version", MCP_UNSUPPORTED_VERSION, { requested });
|
|
3896
|
+
this.#transport = transport;
|
|
3897
|
+
this.#client = options?.identity ?? {
|
|
3898
|
+
name: "taverna",
|
|
3899
|
+
version: "1.0.0"
|
|
3900
|
+
};
|
|
3901
|
+
this.#capabilities = options?.capabilities ?? {};
|
|
3902
|
+
this.#pin = requested;
|
|
3903
|
+
this.#timeout = options?.timeout ?? 3e4;
|
|
3904
|
+
transport.emitter.on("message", (message) => this.#receive(message));
|
|
3905
|
+
transport.emitter.on("close", () => this.#emitter.emit("close"));
|
|
3906
|
+
transport.emitter.on("error", (error) => this.#emitter.emit("error", error));
|
|
3907
|
+
}
|
|
3908
|
+
get emitter() {
|
|
3909
|
+
return this.#emitter;
|
|
3910
|
+
}
|
|
3911
|
+
get session() {
|
|
3912
|
+
return this.#transport.session;
|
|
3913
|
+
}
|
|
3914
|
+
get duplex() {
|
|
3915
|
+
return this.#transport.duplex;
|
|
3916
|
+
}
|
|
3917
|
+
async start() {
|
|
3918
|
+
await this.#transport.start();
|
|
3919
|
+
try {
|
|
3920
|
+
await this.#initialize();
|
|
3921
|
+
} catch (error) {
|
|
3922
|
+
try {
|
|
3923
|
+
await this.#transport.close();
|
|
3924
|
+
} catch (fault) {
|
|
3925
|
+
this.#emitter.emit("error", fault);
|
|
3926
|
+
}
|
|
3927
|
+
throw error;
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
async send(message) {
|
|
3931
|
+
if (!("method" in message)) {
|
|
3932
|
+
await this.#transport.send(message);
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
if (message.method === "server/discover" && message.id !== void 0) {
|
|
3936
|
+
this.#discover(message.id);
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
const id = message.id;
|
|
3940
|
+
let correlation;
|
|
3941
|
+
if (id !== void 0) {
|
|
3942
|
+
correlation = { method: message.method };
|
|
3943
|
+
this.#correlations.set(id, correlation);
|
|
3944
|
+
AbortSignal.timeout(this.#timeout).addEventListener("abort", () => {
|
|
3945
|
+
if (this.#correlations.get(id) !== correlation) return;
|
|
3946
|
+
this.#reject(id, new MCPError(`Legacy MCP request timed out after ${this.#timeout}ms`, JSONRPC_INTERNAL_ERROR));
|
|
3947
|
+
}, { once: true });
|
|
3948
|
+
}
|
|
3949
|
+
try {
|
|
3950
|
+
await this.#transport.send(modernInvocationToLegacy(message));
|
|
3951
|
+
} catch (error) {
|
|
3952
|
+
if (id !== void 0 && this.#correlations.get(id) === correlation) this.#correlations.delete(id);
|
|
3953
|
+
throw error;
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
/**
|
|
3957
|
+
* Closes the wrapped transport and clears retained adapter state.
|
|
3958
|
+
*
|
|
3959
|
+
* @remarks
|
|
3960
|
+
* The cleared handshake state — the server identity, the supported reading, and the retained
|
|
3961
|
+
* `instructions` value — is unobservable between `close()` and the next accepted handshake.
|
|
3962
|
+
* Discovery answers the pre-handshake refusal in that window, and the accepted handshake
|
|
3963
|
+
* reassigns the state unconditionally.
|
|
3964
|
+
*
|
|
3965
|
+
* @returns Resolves after the wrapped transport closes
|
|
3966
|
+
*/
|
|
3967
|
+
async close() {
|
|
3968
|
+
this.#instructions = void 0;
|
|
3969
|
+
this.#server = void 0;
|
|
3970
|
+
this.#supported = void 0;
|
|
3971
|
+
this.#correlations.clear();
|
|
3972
|
+
await this.#transport.close();
|
|
3973
|
+
}
|
|
3974
|
+
async #initialize() {
|
|
3975
|
+
const handshake = Promise.withResolvers();
|
|
3976
|
+
this.#handshake = handshake;
|
|
3977
|
+
try {
|
|
3978
|
+
await this.#write({
|
|
3979
|
+
jsonrpc: "2.0",
|
|
3980
|
+
id: 0,
|
|
3981
|
+
method: "initialize",
|
|
3982
|
+
params: {
|
|
3983
|
+
protocolVersion: this.#pin ?? "2025-11-25",
|
|
3984
|
+
capabilities: this.#capabilities,
|
|
3985
|
+
clientInfo: this.#client
|
|
3986
|
+
}
|
|
3987
|
+
});
|
|
3988
|
+
AbortSignal.timeout(this.#timeout).addEventListener("abort", () => handshake.reject(new MCPError(`Legacy MCP handshake timed out after ${this.#timeout}ms`, MCP_UNSUPPORTED_VERSION)), { once: true });
|
|
3989
|
+
const response = await handshake.promise;
|
|
3990
|
+
this.#accept(response);
|
|
3991
|
+
await this.#write({
|
|
3992
|
+
jsonrpc: "2.0",
|
|
3993
|
+
method: "notifications/initialized"
|
|
3994
|
+
});
|
|
3995
|
+
} finally {
|
|
3996
|
+
if (this.#handshake === handshake) this.#handshake = void 0;
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
#accept(response) {
|
|
4000
|
+
if (response.error !== void 0) throw new MCPError(response.error.message, response.error.code, response.error.data);
|
|
4001
|
+
const owned = attempt(() => cloneJSONRecord(response.result));
|
|
4002
|
+
if (!owned.success || !isMCPLegacyResult(owned.value)) throw new MCPError("Legacy MCP handshake returned a malformed result", JSONRPC_INVALID_PARAMS);
|
|
4003
|
+
const result = owned.value;
|
|
4004
|
+
const protocol = result["protocolVersion"];
|
|
4005
|
+
const capabilities = result["capabilities"];
|
|
4006
|
+
const identity = result["serverInfo"];
|
|
4007
|
+
const instructions = result["instructions"];
|
|
4008
|
+
if (protocol === void 0) throw new MCPError("Legacy MCP handshake returned no protocol version", JSONRPC_INVALID_PARAMS, result);
|
|
4009
|
+
if (!isString(protocol)) throw new MCPError("Legacy MCP handshake returned a malformed protocol version", JSONRPC_INVALID_PARAMS, result);
|
|
4010
|
+
if (!isMCPLegacyVersion(protocol)) throw new MCPError(`Legacy MCP peer negotiated unsupported protocol version '${protocol}'`, MCP_UNSUPPORTED_VERSION, {
|
|
4011
|
+
supported: SUPPORTED_LEGACY_PROTOCOL_VERSIONS,
|
|
4012
|
+
negotiated: protocol
|
|
4013
|
+
});
|
|
4014
|
+
if (!isMCPServerCapabilities(capabilities) || !isMCPIdentity(identity) || instructions !== void 0 && !isString(instructions)) throw new MCPError("Legacy MCP handshake returned a malformed result", JSONRPC_INVALID_PARAMS, result);
|
|
4015
|
+
if (this.#pin !== void 0 && protocol !== this.#pin) throw new MCPError("Legacy MCP peer negotiated a different protocol version than the adapter requested", MCP_UNSUPPORTED_VERSION, {
|
|
4016
|
+
requested: this.#pin,
|
|
4017
|
+
negotiated: protocol
|
|
4018
|
+
});
|
|
4019
|
+
this.#instructions = instructions;
|
|
4020
|
+
this.#server = identity;
|
|
4021
|
+
this.#supported = capabilities;
|
|
4022
|
+
}
|
|
4023
|
+
async #write(message) {
|
|
4024
|
+
const deadline = AbortSignal.timeout(this.#timeout);
|
|
4025
|
+
await Promise.race([this.#transport.send(message), new Promise((_resolve, reject) => deadline.addEventListener("abort", () => reject(new MCPError(`Legacy MCP handshake write timed out after ${this.#timeout}ms`, MCP_UNSUPPORTED_VERSION)), { once: true }))]);
|
|
4026
|
+
}
|
|
4027
|
+
#discover(id) {
|
|
4028
|
+
const identity = this.#server;
|
|
4029
|
+
const capabilities = this.#supported;
|
|
4030
|
+
if (identity === void 0 || capabilities === void 0) {
|
|
4031
|
+
this.#reject(id, new MCPError("Legacy MCP transport has not completed its handshake", JSONRPC_INTERNAL_ERROR));
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
this.#emitter.emit("message", {
|
|
4035
|
+
jsonrpc: "2.0",
|
|
4036
|
+
id,
|
|
4037
|
+
result: buildModernResult({
|
|
4038
|
+
supportedVersions: [MCP_MODERN_VERSION],
|
|
4039
|
+
capabilities,
|
|
4040
|
+
...this.#instructions === void 0 ? {} : { instructions: this.#instructions }
|
|
4041
|
+
}, identity, 0)
|
|
4042
|
+
});
|
|
4043
|
+
}
|
|
4044
|
+
#receive(message) {
|
|
4045
|
+
const owned = parseJSONRPCMessage(message);
|
|
4046
|
+
if (owned === void 0) {
|
|
4047
|
+
this.#emitter.emit("error", new MCPError("Legacy MCP peer returned a malformed message", JSONRPC_INVALID_PARAMS));
|
|
4048
|
+
return;
|
|
4049
|
+
}
|
|
4050
|
+
const handshake = this.#handshake;
|
|
4051
|
+
if (handshake !== void 0 && isJSONRPCResponse(owned) && owned.id === 0) {
|
|
4052
|
+
handshake.resolve(owned);
|
|
4053
|
+
return;
|
|
4054
|
+
}
|
|
4055
|
+
if (!isJSONRPCResponse(owned) || owned.id === void 0) {
|
|
4056
|
+
this.#emitter.emit("message", owned);
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
const correlation = this.#correlations.get(owned.id);
|
|
4060
|
+
if (correlation === void 0) {
|
|
4061
|
+
this.#emitter.emit("message", owned);
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
this.#correlations.delete(owned.id);
|
|
4065
|
+
const method = correlation.method;
|
|
4066
|
+
if (owned.error !== void 0) {
|
|
4067
|
+
this.#emitter.emit("message", owned);
|
|
4068
|
+
return;
|
|
4069
|
+
}
|
|
4070
|
+
const identity = this.#server;
|
|
4071
|
+
if (identity === void 0 || !isMCPLegacyResult(owned.result)) {
|
|
4072
|
+
this.#reject(owned.id, new MCPError("Legacy MCP peer returned a malformed result", JSONRPC_INTERNAL_ERROR));
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
this.#emitter.emit("message", {
|
|
4076
|
+
jsonrpc: "2.0",
|
|
4077
|
+
id: owned.id,
|
|
4078
|
+
result: legacyResultToModern(owned.result, method, identity)
|
|
4079
|
+
});
|
|
4080
|
+
}
|
|
4081
|
+
#reject(id, error) {
|
|
4082
|
+
this.#correlations.delete(id);
|
|
4083
|
+
this.#emitter.emit("error", error);
|
|
4084
|
+
this.#emitter.emit("message", buildJSONRPCError(id, error.code, error.message));
|
|
3639
4085
|
}
|
|
3640
4086
|
};
|
|
3641
4087
|
//#endregion
|
|
@@ -3656,7 +4102,7 @@ var MCPLegacy = class {
|
|
|
3656
4102
|
* - **One modern seam.** `server/discover`, `tools/list`, `tools/call`, and
|
|
3657
4103
|
* `subscriptions/listen` are always registered; `resources/*`, `prompts/*`, and
|
|
3658
4104
|
* `completion/complete` register independently when their respective host ports are
|
|
3659
|
-
* configured — plus `tasks/get`, `tasks/update`, and `tasks/cancel` when the
|
|
4105
|
+
* configured — plus `tasks/get`, `tasks/update`, and `tasks/cancel` when the stable Tasks
|
|
3660
4106
|
* extension is configured — and every method is resolved from the registry on
|
|
3661
4107
|
* every dispatch: the same path a later method or a consumer's own takes, with an
|
|
3662
4108
|
* unregistered method still answering `-32601`.
|
|
@@ -3671,8 +4117,8 @@ var MCPLegacy = class {
|
|
|
3671
4117
|
* const tools = createToolManager()
|
|
3672
4118
|
* tools.add(createTool({ name: 'add', execute: (a) => Number(a.x) + Number(a.y) }))
|
|
3673
4119
|
* const server = new MCPServer({ identity: { name: 'demo', version: '1.0.0' }, tools })
|
|
3674
|
-
* await server.handle('{"jsonrpc":"2.0","method":"
|
|
3675
|
-
* //
|
|
4120
|
+
* await server.handle('{"jsonrpc":"2.0","method":"server/discover","id":1,"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}')
|
|
4121
|
+
* // The result advertises only `2026-07-28`; wrap with `createMCPLegacy` to serve initialize or ping.
|
|
3676
4122
|
* ```
|
|
3677
4123
|
*/
|
|
3678
4124
|
var MCPServer = class {
|
|
@@ -3756,7 +4202,6 @@ var MCPServer = class {
|
|
|
3756
4202
|
return Symbol.asyncIterator in answer ? new MCPTextStreamController(answer) : JSON.stringify(answer);
|
|
3757
4203
|
}
|
|
3758
4204
|
#register() {
|
|
3759
|
-
this.#methods.add("ping", async (request, _options) => buildJSONRPCResult(request.id, buildModernResult({}, this.#options.identity)));
|
|
3760
4205
|
this.#methods.add("server/discover", async (request, _options) => this.#discover(request));
|
|
3761
4206
|
this.#methods.add("tools/list", async (request, _options) => this.#list(request));
|
|
3762
4207
|
this.#methods.add("tools/call", async (request, options) => this.#call(request, options));
|
|
@@ -3793,8 +4238,8 @@ var MCPServer = class {
|
|
|
3793
4238
|
depth: this.#limits.depth
|
|
3794
4239
|
});
|
|
3795
4240
|
if (context === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata");
|
|
3796
|
-
if (
|
|
3797
|
-
supported:
|
|
4241
|
+
if (!isMCPModernVersion(context.version)) return buildJSONRPCError(id, MCP_UNSUPPORTED_VERSION, `Unsupported protocol version: ${context.version}`, {
|
|
4242
|
+
supported: SUPPORTED_MODERN_PROTOCOL_VERSIONS,
|
|
3798
4243
|
requested: context.version
|
|
3799
4244
|
});
|
|
3800
4245
|
const handler = this.#methods.method(request.method);
|
|
@@ -4203,8 +4648,22 @@ var MCPServer = class {
|
|
|
4203
4648
|
if (options.signal.aborted) slot.abort();
|
|
4204
4649
|
else options.signal.addEventListener("abort", () => slot.abort(), { once: true });
|
|
4205
4650
|
try {
|
|
4651
|
+
const task = this.#options.task;
|
|
4206
4652
|
const configured = this.#options.subscription;
|
|
4207
|
-
const
|
|
4653
|
+
const tasks = task !== void 0 && configured !== void 0;
|
|
4654
|
+
let notifications = buildSubscriptionFilter(requested, configured?.notifications ?? {}, tasks);
|
|
4655
|
+
const requestedTaskIds = notifications.taskIds;
|
|
4656
|
+
if (requestedTaskIds !== void 0) {
|
|
4657
|
+
const resolved = [];
|
|
4658
|
+
if (task !== void 0) {
|
|
4659
|
+
for (const taskId of requestedTaskIds) if (await task.tasks.task(taskId, options) !== void 0) resolved.push(taskId);
|
|
4660
|
+
}
|
|
4661
|
+
const { taskIds: _dropped, ...rest } = notifications;
|
|
4662
|
+
notifications = resolved.length > 0 ? {
|
|
4663
|
+
...rest,
|
|
4664
|
+
taskIds: resolved
|
|
4665
|
+
} : rest;
|
|
4666
|
+
}
|
|
4208
4667
|
yield buildSubscriptionAcknowledgement(notifications, id);
|
|
4209
4668
|
if (configured !== void 0) {
|
|
4210
4669
|
const iterator = (await configured.listen(notifications, options))[Symbol.asyncIterator]();
|
|
@@ -4316,7 +4775,7 @@ var MCPServer = class {
|
|
|
4316
4775
|
//#endregion
|
|
4317
4776
|
//#region src/core/MCPTaskClient.ts
|
|
4318
4777
|
/**
|
|
4319
|
-
* The CLIENT half of the
|
|
4778
|
+
* The CLIENT half of the stable Tasks extension — the `tasks/*` methods over one
|
|
4320
4779
|
* correlated-request door, exposed as an {@link import('./types.js').MCPClientInterface}'s
|
|
4321
4780
|
* `tasks`.
|
|
4322
4781
|
*
|
|
@@ -4360,7 +4819,7 @@ var MCPTaskClient = class {
|
|
|
4360
4819
|
}
|
|
4361
4820
|
async task(id) {
|
|
4362
4821
|
const result = await this.#request("tasks/get", { taskId: id }, this.#timeout);
|
|
4363
|
-
if (!
|
|
4822
|
+
if (!isMCPTaskDetailResult(result)) throw new MCPError("MCP server returned an invalid task", JSONRPC_INVALID_PARAMS);
|
|
4364
4823
|
return result;
|
|
4365
4824
|
}
|
|
4366
4825
|
async update(id, responses) {
|
|
@@ -4377,14 +4836,14 @@ var MCPTaskClient = class {
|
|
|
4377
4836
|
//#region src/core/MCPClient.ts
|
|
4378
4837
|
/**
|
|
4379
4838
|
* A transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE MCP server
|
|
4380
|
-
* over an injected {@link MCPClientTransportInterface}, negotiates the modern
|
|
4381
|
-
*
|
|
4839
|
+
* over an injected {@link MCPClientTransportInterface}, negotiates the modern revision, and
|
|
4840
|
+
* exposes the server's tools as local {@link ToolInterface}s an agent can run.
|
|
4382
4841
|
*
|
|
4383
4842
|
* @remarks
|
|
4384
4843
|
* - **The mirror of `MCPServer`.** The server DISPATCHES requests over a tool registry;
|
|
4385
|
-
* this client ISSUES them over a transport. `connect` probes `server/discover`
|
|
4386
|
-
*
|
|
4387
|
-
*
|
|
4844
|
+
* this client ISSUES them over a transport. `connect` probes `server/discover` and exposes
|
|
4845
|
+
* the negotiated `version`; a legacy peer requires an explicit transport adapter.
|
|
4846
|
+
* `tools()` lists the remote tools and wraps each as a
|
|
4388
4847
|
* local {@link ToolInterface} whose `execute` calls back through `call`; `call` runs a
|
|
4389
4848
|
* remote `tools/call` and reports the arm the peer answered with — a value, a durable
|
|
4390
4849
|
* task, or a request for more input (a remote `isError: true` throws locally, so an
|
|
@@ -4454,17 +4913,18 @@ var MCPClient = class {
|
|
|
4454
4913
|
#generation = 0;
|
|
4455
4914
|
#supersession = Promise.withResolvers();
|
|
4456
4915
|
#version = void 0;
|
|
4457
|
-
#era = void 0;
|
|
4458
4916
|
#offer;
|
|
4459
4917
|
constructor(options) {
|
|
4460
4918
|
const requested = options.version;
|
|
4461
|
-
|
|
4462
|
-
|
|
4919
|
+
const on = options.on;
|
|
4920
|
+
const error = options.error;
|
|
4921
|
+
if (requested !== void 0 && !isMCPModernVersion(requested)) throw new MCPError("Unsupported protocol version", MCP_UNSUPPORTED_VERSION, {
|
|
4922
|
+
supported: SUPPORTED_MODERN_PROTOCOL_VERSIONS,
|
|
4463
4923
|
requested
|
|
4464
4924
|
});
|
|
4465
4925
|
this.#emitter = new Emitter({
|
|
4466
|
-
...
|
|
4467
|
-
...
|
|
4926
|
+
...on === void 0 ? {} : { on },
|
|
4927
|
+
...error === void 0 ? {} : { error }
|
|
4468
4928
|
});
|
|
4469
4929
|
this.#transport = options.transport;
|
|
4470
4930
|
this.#identity = options.identity ?? {
|
|
@@ -4480,6 +4940,7 @@ var MCPClient = class {
|
|
|
4480
4940
|
timeout: this.#timeout
|
|
4481
4941
|
});
|
|
4482
4942
|
this.#transport.emitter.on("message", (message) => this.#receive(message));
|
|
4943
|
+
this.#transport.emitter.on("close", () => this.#loseTransport());
|
|
4483
4944
|
}
|
|
4484
4945
|
get emitter() {
|
|
4485
4946
|
return this.#emitter;
|
|
@@ -4528,7 +4989,9 @@ var MCPClient = class {
|
|
|
4528
4989
|
}
|
|
4529
4990
|
}
|
|
4530
4991
|
async discover() {
|
|
4531
|
-
const
|
|
4992
|
+
const negotiated = this.#version;
|
|
4993
|
+
const version = isMCPModernVersion(negotiated) ? negotiated : this.#offer;
|
|
4994
|
+
const received = await this.#request("server/discover", void 0, this.#timeout, version);
|
|
4532
4995
|
const owned = attempt(() => cloneJSONRecord(received));
|
|
4533
4996
|
if (!owned.success) throw new MCPError("MCP server returned a malformed discovery result", JSONRPC_INVALID_PARAMS);
|
|
4534
4997
|
const result = owned.value;
|
|
@@ -4541,7 +5004,7 @@ var MCPClient = class {
|
|
|
4541
5004
|
const resultType = result["resultType"];
|
|
4542
5005
|
if (!isArray(advertised) || !advertised.every(isString) || !isMCPServerCapabilities(capabilities) || !isInteger(ttl) || ttl < 0 || scope !== "public" && scope !== "private" || resultType !== "complete" || instructions !== void 0 && !isString(instructions) || metadata !== void 0 && !isMCPResultMetaObject(metadata)) throw new MCPError("MCP server returned a malformed discovery result", JSONRPC_INVALID_PARAMS, result);
|
|
4543
5006
|
const supportedVersions = [];
|
|
4544
|
-
for (const
|
|
5007
|
+
for (const candidate of advertised) if (isMCPModernVersion(candidate)) supportedVersions.push(candidate);
|
|
4545
5008
|
const retained = Object.freeze(supportedVersions);
|
|
4546
5009
|
return Object.freeze({
|
|
4547
5010
|
supportedVersions: retained,
|
|
@@ -4570,19 +5033,81 @@ var MCPClient = class {
|
|
|
4570
5033
|
}
|
|
4571
5034
|
return tools;
|
|
4572
5035
|
}
|
|
5036
|
+
listen(notifications, options) {
|
|
5037
|
+
const signal = options.signal;
|
|
5038
|
+
const capacity = options.capacity ?? 64;
|
|
5039
|
+
return this.#openSubscription(notifications, signal, capacity);
|
|
5040
|
+
}
|
|
4573
5041
|
async call(name, args, options) {
|
|
5042
|
+
const input = options?.input;
|
|
4574
5043
|
return buildCallOutcome(name, await this.#request("tools/call", {
|
|
4575
5044
|
name,
|
|
4576
|
-
arguments: args
|
|
5045
|
+
arguments: args,
|
|
5046
|
+
...input === void 0 ? {} : {
|
|
5047
|
+
requestState: input.state,
|
|
5048
|
+
inputResponses: input.responses
|
|
5049
|
+
}
|
|
4577
5050
|
}, this.#timeout, void 0, options));
|
|
4578
5051
|
}
|
|
5052
|
+
async *#openSubscription(notifications, signal, capacity) {
|
|
5053
|
+
signal.throwIfAborted();
|
|
5054
|
+
if (!isInteger(capacity) || capacity < 1) throw new MCPError("MCP subscription capacity must be a positive integer", JSONRPC_INVALID_PARAMS);
|
|
5055
|
+
this.#nextId += 1;
|
|
5056
|
+
const id = this.#nextId;
|
|
5057
|
+
const method = "subscriptions/listen";
|
|
5058
|
+
const modern = this.#version;
|
|
5059
|
+
const request = {
|
|
5060
|
+
jsonrpc: "2.0",
|
|
5061
|
+
id,
|
|
5062
|
+
method,
|
|
5063
|
+
params: {
|
|
5064
|
+
notifications: notifications ?? {},
|
|
5065
|
+
...modern === void 0 ? {} : { _meta: {
|
|
5066
|
+
[MCP_META_VERSION]: modern,
|
|
5067
|
+
[MCP_META_CAPABILITIES]: this.#capabilities,
|
|
5068
|
+
[MCP_META_CLIENT]: this.#identity
|
|
5069
|
+
} }
|
|
5070
|
+
}
|
|
5071
|
+
};
|
|
5072
|
+
const subscription = {
|
|
5073
|
+
queue: [],
|
|
5074
|
+
capacity
|
|
5075
|
+
};
|
|
5076
|
+
const abort = this.#abortSubscription.bind(this, id, signal);
|
|
5077
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
5078
|
+
this.#pending.set(id, {
|
|
5079
|
+
method,
|
|
5080
|
+
signal,
|
|
5081
|
+
abort,
|
|
5082
|
+
subscription
|
|
5083
|
+
});
|
|
5084
|
+
this.#transport.send(request).catch((error) => this.#settle(id, error, true));
|
|
5085
|
+
try {
|
|
5086
|
+
for (;;) {
|
|
5087
|
+
if (subscription.failure !== void 0) throw subscription.failure.reason;
|
|
5088
|
+
const queued = subscription.queue.shift();
|
|
5089
|
+
if (queued !== void 0) {
|
|
5090
|
+
yield queued;
|
|
5091
|
+
continue;
|
|
5092
|
+
}
|
|
5093
|
+
if (subscription.terminal !== void 0) return subscription.terminal;
|
|
5094
|
+
const waiter = Promise.withResolvers();
|
|
5095
|
+
subscription.waiter = waiter;
|
|
5096
|
+
const frame = await waiter.promise;
|
|
5097
|
+
if ("method" in frame) yield frame;
|
|
5098
|
+
else return frame;
|
|
5099
|
+
}
|
|
5100
|
+
} finally {
|
|
5101
|
+
this.#cancelSubscription(id, /* @__PURE__ */ new Error("MCP subscription closed by its consumer"));
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
4579
5104
|
#request(method, params, deadline, version, options) {
|
|
4580
5105
|
this.#nextId += 1;
|
|
4581
5106
|
const id = this.#nextId;
|
|
4582
5107
|
const timeout = deadline;
|
|
4583
5108
|
const caller = options?.signal;
|
|
4584
5109
|
const report = options?.progress;
|
|
4585
|
-
const modern = version ??
|
|
5110
|
+
const modern = version ?? this.#version;
|
|
4586
5111
|
const metadata = {
|
|
4587
5112
|
...modern === void 0 ? {} : {
|
|
4588
5113
|
[MCP_META_VERSION]: modern,
|
|
@@ -4646,7 +5171,6 @@ var MCPClient = class {
|
|
|
4646
5171
|
});
|
|
4647
5172
|
if (correlated.success && correlated.value !== void 0) {
|
|
4648
5173
|
if (this.#pending.get(correlated.value)?.method === "server/discover") {
|
|
4649
|
-
this.#era = "modern";
|
|
4650
5174
|
this.#settle(correlated.value, new MCPError("MCP server returned a malformed discovery result", JSONRPC_INVALID_PARAMS), true);
|
|
4651
5175
|
return;
|
|
4652
5176
|
}
|
|
@@ -4662,9 +5186,11 @@ var MCPClient = class {
|
|
|
4662
5186
|
const correlation = owned.id;
|
|
4663
5187
|
const pending = this.#pending.get(correlation);
|
|
4664
5188
|
if (pending !== void 0) {
|
|
4665
|
-
if (pending.method === "server/discover" && (owned.error === void 0 || owned.error.code !== -32601 && owned.error.code !== -32600)) this.#era = "modern";
|
|
4666
5189
|
if (owned.error !== void 0) this.#settle(correlation, new MCPError(owned.error.message, owned.error.code, owned.error.data), true);
|
|
4667
|
-
else {
|
|
5190
|
+
else if (pending.subscription !== void 0) {
|
|
5191
|
+
if (!isMCPSubscriptionResult(owned.result) || owned.result["_meta"]["io.modelcontextprotocol/subscriptionId"] !== correlation) this.#settle(correlation, new MCPError("MCP server returned a malformed subscription result", JSONRPC_INVALID_PARAMS, owned.result), true);
|
|
5192
|
+
else this.#settle(correlation, owned.result, false);
|
|
5193
|
+
} else {
|
|
4668
5194
|
const resultType = isRecord(owned.result) ? owned.result["resultType"] : void 0;
|
|
4669
5195
|
const metadata = isRecord(owned.result) ? owned.result["_meta"] : void 0;
|
|
4670
5196
|
if (metadata !== void 0 && !isMCPResultMetaObject(metadata)) {
|
|
@@ -4679,8 +5205,30 @@ var MCPClient = class {
|
|
|
4679
5205
|
return;
|
|
4680
5206
|
}
|
|
4681
5207
|
if ("method" in owned && this.#reportProgress(owned)) return;
|
|
5208
|
+
if ("method" in owned && this.#routeSubscription(owned)) return;
|
|
4682
5209
|
this.#emitter.emit("notification", owned);
|
|
4683
5210
|
}
|
|
5211
|
+
#routeSubscription(message) {
|
|
5212
|
+
if (!isJSONRPCNotification(message)) return false;
|
|
5213
|
+
const metadata = message.params?.["_meta"];
|
|
5214
|
+
if (!isRecord(metadata)) return false;
|
|
5215
|
+
const id = metadata[MCP_META_SUBSCRIPTION];
|
|
5216
|
+
if (!isJSONRPCId(id)) return false;
|
|
5217
|
+
const subscription = this.#pending.get(id)?.subscription;
|
|
5218
|
+
if (subscription === void 0) return true;
|
|
5219
|
+
const waiter = subscription.waiter;
|
|
5220
|
+
if (waiter !== void 0) {
|
|
5221
|
+
delete subscription.waiter;
|
|
5222
|
+
waiter.resolve(message);
|
|
5223
|
+
return true;
|
|
5224
|
+
}
|
|
5225
|
+
if (subscription.queue.length >= subscription.capacity) {
|
|
5226
|
+
this.#cancelSubscription(id, new MCPError("MCP subscription frame queue overflow", JSONRPC_INTERNAL_ERROR));
|
|
5227
|
+
return true;
|
|
5228
|
+
}
|
|
5229
|
+
subscription.queue.push(message);
|
|
5230
|
+
return true;
|
|
5231
|
+
}
|
|
4684
5232
|
#reportProgress(message) {
|
|
4685
5233
|
if (message.method !== "notifications/progress") return false;
|
|
4686
5234
|
const params = message.params;
|
|
@@ -4714,10 +5262,6 @@ var MCPClient = class {
|
|
|
4714
5262
|
this.#owner = generation;
|
|
4715
5263
|
try {
|
|
4716
5264
|
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4717
|
-
if (this.#era === "legacy" || this.#pin !== void 0 && inferEra(this.#pin) === "legacy") {
|
|
4718
|
-
await this.#initialize(generation, this.#pin ?? "2025-11-25");
|
|
4719
|
-
return;
|
|
4720
|
-
}
|
|
4721
5265
|
let discovery;
|
|
4722
5266
|
try {
|
|
4723
5267
|
try {
|
|
@@ -4734,13 +5278,12 @@ var MCPClient = class {
|
|
|
4734
5278
|
}
|
|
4735
5279
|
} catch (error) {
|
|
4736
5280
|
if (generation !== this.#generation) throw error;
|
|
4737
|
-
if (!
|
|
4738
|
-
|
|
4739
|
-
return;
|
|
5281
|
+
if (!isMCPError(error) || error.code !== -32601) throw error;
|
|
5282
|
+
throw new MCPError("MCP server does not support modern negotiation; wrap the transport with createMCPLegacyClientTransport to connect to a legacy peer", error.code, error.context);
|
|
4740
5283
|
}
|
|
4741
5284
|
let version;
|
|
4742
5285
|
if (this.#pin === void 0) version = inferVersion(discovery.supportedVersions);
|
|
4743
|
-
else if (discovery.supportedVersions.includes(this.#pin)) version = this.#pin;
|
|
5286
|
+
else if (isMCPModernVersion(this.#pin) && discovery.supportedVersions.includes(this.#pin)) version = this.#pin;
|
|
4744
5287
|
else throw new MCPError("MCP server does not support the pinned protocol version", MCP_UNSUPPORTED_VERSION, {
|
|
4745
5288
|
supported: discovery.supportedVersions,
|
|
4746
5289
|
requested: this.#pin
|
|
@@ -4748,7 +5291,6 @@ var MCPClient = class {
|
|
|
4748
5291
|
if (version === void 0) throw new MCPError("MCP server supports no compatible protocol version", MCP_UNSUPPORTED_VERSION, { supported: discovery.supportedVersions });
|
|
4749
5292
|
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4750
5293
|
this.#version = version;
|
|
4751
|
-
this.#era = "modern";
|
|
4752
5294
|
this.#connected = true;
|
|
4753
5295
|
this.#emitter.emit("connect");
|
|
4754
5296
|
} catch (error) {
|
|
@@ -4810,31 +5352,6 @@ var MCPClient = class {
|
|
|
4810
5352
|
this.#closing = void 0;
|
|
4811
5353
|
if (closed) this.#owner = void 0;
|
|
4812
5354
|
}
|
|
4813
|
-
async #initialize(generation, version) {
|
|
4814
|
-
const result = await this.#request("initialize", {
|
|
4815
|
-
protocolVersion: version,
|
|
4816
|
-
capabilities: {},
|
|
4817
|
-
clientInfo: this.#identity
|
|
4818
|
-
}, this.#timeout);
|
|
4819
|
-
const protocol = isRecord(result) ? result["protocolVersion"] : void 0;
|
|
4820
|
-
if (protocol === void 0) throw new Error("MCP server returned no protocol version");
|
|
4821
|
-
if (!isString(protocol)) throw new Error("MCP server returned a malformed protocol version");
|
|
4822
|
-
if (!isMCPVersion(protocol) || inferEra(protocol) !== "legacy") throw new Error(`MCP server negotiated unsupported protocol version '${protocol}'`);
|
|
4823
|
-
if (this.#pin !== void 0 && protocol !== this.#pin) throw new MCPError("MCP server negotiated a different protocol version than the client pinned", MCP_UNSUPPORTED_VERSION, {
|
|
4824
|
-
requested: this.#pin,
|
|
4825
|
-
negotiated: protocol
|
|
4826
|
-
});
|
|
4827
|
-
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4828
|
-
await Promise.race([this.#transport.send({
|
|
4829
|
-
jsonrpc: "2.0",
|
|
4830
|
-
method: "notifications/initialized"
|
|
4831
|
-
}), this.#supersession.promise]);
|
|
4832
|
-
if (generation !== this.#generation) throw new Error("MCP client disconnected");
|
|
4833
|
-
this.#version = protocol;
|
|
4834
|
-
this.#era = "legacy";
|
|
4835
|
-
this.#connected = true;
|
|
4836
|
-
this.#emitter.emit("connect");
|
|
4837
|
-
}
|
|
4838
5355
|
#timeoutRequest(id, method, timeout) {
|
|
4839
5356
|
this.#settle(id, /* @__PURE__ */ new Error(`MCP request '${method}' timed out after ${timeout}ms`), true);
|
|
4840
5357
|
}
|
|
@@ -4844,14 +5361,48 @@ var MCPClient = class {
|
|
|
4844
5361
|
if (this.#transport.duplex) this.#transport.send(buildCancelledNotification(id, isString(reason) ? reason : void 0)).catch((error) => this.#emitter.emit("error", error));
|
|
4845
5362
|
this.#settle(id, new Error(`MCP request '${method}' was aborted`, { cause: reason }), true);
|
|
4846
5363
|
}
|
|
5364
|
+
#abortSubscription(id, signal) {
|
|
5365
|
+
this.#cancelSubscription(id, signal.reason);
|
|
5366
|
+
}
|
|
5367
|
+
#cancelSubscription(id, reason) {
|
|
5368
|
+
if (this.#pending.get(id)?.subscription === void 0) return;
|
|
5369
|
+
if (this.#transport.duplex) this.#transport.send(buildCancelledNotification(id, isString(reason) ? reason : void 0)).catch((error) => this.#emitter.emit("error", error));
|
|
5370
|
+
this.#settle(id, reason, true);
|
|
5371
|
+
}
|
|
5372
|
+
#loseTransport() {
|
|
5373
|
+
const announced = this.#connected;
|
|
5374
|
+
this.#generation += 1;
|
|
5375
|
+
this.#connected = false;
|
|
5376
|
+
this.#version = void 0;
|
|
5377
|
+
this.#owner = void 0;
|
|
5378
|
+
const supersession = this.#supersession;
|
|
5379
|
+
this.#supersession = Promise.withResolvers();
|
|
5380
|
+
supersession.resolve();
|
|
5381
|
+
for (const id of this.#pending.keys()) this.#settle(id, /* @__PURE__ */ new Error("MCP transport closed"), true);
|
|
5382
|
+
if (announced) this.#emitter.emit("disconnect");
|
|
5383
|
+
}
|
|
4847
5384
|
#settle(id, value, failed) {
|
|
4848
5385
|
const pending = this.#pending.get(id);
|
|
4849
5386
|
if (pending === void 0) return;
|
|
4850
5387
|
this.#pending.delete(id);
|
|
4851
5388
|
if (pending.deadline !== void 0 && pending.timeout !== void 0) pending.deadline.removeEventListener("abort", pending.timeout);
|
|
4852
5389
|
if (pending.signal !== void 0 && pending.abort !== void 0) pending.signal.removeEventListener("abort", pending.abort);
|
|
4853
|
-
|
|
4854
|
-
|
|
5390
|
+
const subscription = pending.subscription;
|
|
5391
|
+
if (subscription !== void 0) {
|
|
5392
|
+
const waiter = subscription.waiter;
|
|
5393
|
+
delete subscription.waiter;
|
|
5394
|
+
if (failed) {
|
|
5395
|
+
subscription.queue.length = 0;
|
|
5396
|
+
if (waiter === void 0) subscription.failure = { reason: value };
|
|
5397
|
+
else waiter.reject(value);
|
|
5398
|
+
} else if (isMCPSubscriptionResult(value)) {
|
|
5399
|
+
if (waiter === void 0) subscription.terminal = value;
|
|
5400
|
+
else waiter.resolve(value);
|
|
5401
|
+
}
|
|
5402
|
+
return;
|
|
5403
|
+
}
|
|
5404
|
+
if (failed) pending.reject?.(value);
|
|
5405
|
+
else pending.resolve?.(value);
|
|
4855
5406
|
}
|
|
4856
5407
|
};
|
|
4857
5408
|
//#endregion
|
|
@@ -4914,13 +5465,14 @@ function createMCPLegacy(server) {
|
|
|
4914
5465
|
/**
|
|
4915
5466
|
* Creates a transport-agnostic Model Context Protocol CLIENT — connects to a REMOTE
|
|
4916
5467
|
* MCP server over an injected {@link import('./types.js').MCPClientTransportInterface},
|
|
4917
|
-
*
|
|
5468
|
+
* negotiates the modern revision through `server/discover`, and exposes the server's tools as local
|
|
4918
5469
|
* {@link import('@orkestrel/tool').ToolInterface}s an agent can run.
|
|
4919
5470
|
*
|
|
4920
5471
|
* @remarks
|
|
4921
5472
|
* The egress mirror of {@link createMCPServer}: where the server exposes a local tool
|
|
4922
|
-
* registry over MCP, the client USES a remote server's tools. `connect()`
|
|
4923
|
-
* validates and exposes the negotiated protocol
|
|
5473
|
+
* registry over MCP, the client USES a remote server's tools. `connect()` discovers,
|
|
5474
|
+
* validates, and exposes the negotiated modern protocol; a legacy peer requires
|
|
5475
|
+
* {@link createMCPLegacyClientTransport}. `tools()` lists + wraps the remote
|
|
4924
5476
|
* tools (each `execute` calls back over the wire),
|
|
4925
5477
|
* and `call(name, args)` runs a remote `tools/call` (a remote tool failure throws
|
|
4926
5478
|
* locally, so an agent's {@link import('@orkestrel/tool').ToolManagerInterface}
|
|
@@ -4950,6 +5502,24 @@ function createMCPClient(options) {
|
|
|
4950
5502
|
return new MCPClient(options);
|
|
4951
5503
|
}
|
|
4952
5504
|
/**
|
|
5505
|
+
* Decorates one client transport with explicit legacy handshake and era translation.
|
|
5506
|
+
*
|
|
5507
|
+
* @param transport - The transport connected to a legacy MCP peer
|
|
5508
|
+
* @param options - Optional legacy handshake identity, capabilities, revision, and deadline
|
|
5509
|
+
* @returns A modern-facing client transport over the legacy peer
|
|
5510
|
+
*
|
|
5511
|
+
* @example
|
|
5512
|
+
* ```ts
|
|
5513
|
+
* const transport = createMCPLegacyClientTransport(legacyTransport)
|
|
5514
|
+
* const client = createMCPClient({ transport })
|
|
5515
|
+
* await client.connect()
|
|
5516
|
+
* client.version // '2026-07-28'
|
|
5517
|
+
* ```
|
|
5518
|
+
*/
|
|
5519
|
+
function createMCPLegacyClientTransport(transport, options) {
|
|
5520
|
+
return new MCPLegacyClientTransport(transport, options);
|
|
5521
|
+
}
|
|
5522
|
+
/**
|
|
4953
5523
|
* Adapts an {@link MCPTransportInterface} (the environment-agnostic duplex message
|
|
4954
5524
|
* channel) into a {@link MCPClientTransportInterface} — the additive bridge that lets
|
|
4955
5525
|
* `createMCPClient` run over the new port without any change to `MCPClient`'s
|
|
@@ -4998,6 +5568,6 @@ function createDuplexClientTransport(transport) {
|
|
|
4998
5568
|
};
|
|
4999
5569
|
}
|
|
5000
5570
|
//#endregion
|
|
5001
|
-
export { DEFAULT_MCP_CACHE_TTL, DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_LIMITS, DEFAULT_MCP_REQUEST_TIMEOUT, EMPTY_MCP_ARGUMENTS, JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPError, MCPLegacy, MCPMethodManager, MCPProgressReporter, MCPServer, MCPStreamController, MCPTaskClient, MCPTextStreamController, MCP_EXTENSION_TASKS,
|
|
5571
|
+
export { DEFAULT_MCP_CACHE_TTL, DEFAULT_MCP_CLIENT_NAME, DEFAULT_MCP_CLIENT_VERSION, DEFAULT_MCP_LIMITS, DEFAULT_MCP_REQUEST_TIMEOUT, DEFAULT_MCP_SUBSCRIPTION_CAPACITY, EMPTY_MCP_ARGUMENTS, JSONRPC_INTERNAL_ERROR, JSONRPC_INVALID_PARAMS, JSONRPC_INVALID_REQUEST, JSONRPC_METHOD_NOT_FOUND, JSONRPC_PARSE_ERROR, JSONRPC_SERVER_ERROR, MCPClient, MCPError, MCPLegacy, MCPLegacyClientTransport, MCPMethodManager, MCPProgressReporter, MCPServer, MCPStreamController, MCPTaskClient, MCPTextStreamController, MCP_EXTENSION_TASKS, MCP_FALLBACK_VERSION, MCP_HANDSHAKE_VERSION, MCP_HEADER_MISMATCH, MCP_META_CAPABILITIES, MCP_META_CLIENT, MCP_META_SERVER, MCP_META_SUBSCRIPTION, MCP_META_VERSION, MCP_MISSING_CAPABILITY, MCP_MODERN_VERSION, MCP_UNSUPPORTED_VERSION, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, SUPPORTED_MCP_VERSIONS, SUPPORTED_MODERN_PROTOCOL_VERSIONS, bindClient, bindServer, buildCallOutcome, buildCancelledNotification, buildDiscoverResult, buildInitializeResult, buildJSONRPCError, buildJSONRPCResult, buildMethodOptions, buildModernResult, buildProgressNotification, buildSubscriptionAcknowledgement, buildSubscriptionFilter, buildSubscriptionResult, buildToolCall, buildToolDescriptors, createDuplexClientTransport, createMCPClient, createMCPLegacy, createMCPLegacyClientTransport, createMCPServer, decodeBoundedMessage, digestJSON, extractContentText, inferEra, inferRequestVersion, inferVersion, isAbsoluteURI, isBoundedJSON, isBoundedString, isElicitContent, isFormElicitationSupported, isInitializeRequest, isJSONObject, isJSONRPCError, isJSONRPCErrorResponse, isJSONRPCId, isJSONRPCInvocation, isJSONRPCMessage, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResponse, isJSONRPCResultResponse, isMCPAnnotations, isMCPBlobResource, isMCPCallResult, isMCPClientCapabilities, isMCPCompletion, isMCPCompletionParams, isMCPCompletionReference, isMCPCompletionResult, isMCPContent, isMCPElicitFieldSchema, isMCPElicitForm, isMCPElicitRequest, isMCPElicitResult, isMCPElicitSchema, isMCPElicitURL, isMCPError, isMCPIcon, isMCPIdentity, isMCPInputRequest, isMCPInputRequestMap, isMCPInputResult, isMCPLegacyResult, isMCPLegacyVersion, isMCPLoggingLevel, isMCPMetaKey, isMCPMetaObject, isMCPModernVersion, isMCPNotificationMetaObject, isMCPPaginationParams, isMCPProgress, isMCPPrompt, isMCPPromptArgument, isMCPPromptGetResult, isMCPPromptMessage, isMCPPromptPage, isMCPResource, isMCPResourceContents, isMCPResourcePage, isMCPResourceTemplate, isMCPResourceTemplatePage, isMCPResult, isMCPResultMetaObject, isMCPServerCapabilities, isMCPStringArguments, isMCPSubscriptionFilter, isMCPSubscriptionResult, isMCPTaskDetail, isMCPTaskDetailResult, isMCPTaskNotification, isMCPTaskResult, isMCPTaskStatus, isMCPTextResource, isMCPVersion, isModernRequest, isRFC3339Date, isRFC3339DateTime, isStandardBase64, isTaskSupported, legacyInvocationToModern, legacyResultToModern, matchesResultType, matchesSubscriptionNotification, modernInvocationToLegacy, modernResultToLegacy, parseJSONRPCMessage, parseMCPInputState, parseRequestContext, readCancelledId, sendStream, serializeJSON, snapshotJSON, snapshotToolResult, stampSubscriptionNotification };
|
|
5002
5572
|
|
|
5003
5573
|
//# sourceMappingURL=index.js.map
|