@orkestrel/mcp 0.0.30 → 0.0.32

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.
@@ -1,4 +1,4 @@
1
- import { arrayOf, attempt, cloneJSONRecord, cloneJSONValue, isArray, isBoolean, isFiniteNumber, isInteger, isJSONValue, isNumber, isRecord, isString, isUndefined, parseJSON, sanitizeBudget } from "@orkestrel/contract";
1
+ import { arrayOf, attempt, cloneJSONRecord, cloneJSONValue, isArray, isBoolean, isError, isFiniteNumber, isInstance, isInteger, isJSONValue, isNumber, isObject, isRecord, isString, isUndefined, objectOf, parseJSON, sanitizeBudget } from "@orkestrel/contract";
2
2
  import { decodeBase64, decodeUTF8, encodeBase64, encodeHex } from "@orkestrel/codec";
3
3
  import { createSSEParser } from "@orkestrel/sse";
4
4
  import { Emitter } from "@orkestrel/emitter";
@@ -274,9 +274,12 @@ var DEFAULT_MCP_SUBSCRIPTION_CAPACITY = 64;
274
274
  * optional structured context.
275
275
  *
276
276
  * @remarks
277
- * {@link MCPClient} throws this error for a remote JSON-RPC `error` response and for a
278
- * locally detected protocol incompatibility. Local lifecycle and transport conditions such
279
- * as disconnects and request timeouts remain plain `Error`s. For a remote response, `context`
277
+ * {@link MCPClient} throws this error for a remote JSON-RPC `error` response, for a locally
278
+ * detected protocol incompatibility, and for a session-bound request refused because the
279
+ * client holds no connection — that one carries `-32600`, so a caller separates "reconnect"
280
+ * from "retry" without reading a message. Local lifecycle and transport conditions that
281
+ * settle a request already in flight, such as a disconnect and a request timeout, remain
282
+ * plain `Error`s. For a remote response, `context`
280
283
  * carries the optional `error.data` unchanged and is `undefined` when the peer omitted it.
281
284
  * This includes the modern reserved paths: `-32020` carries no context, `-32021` may carry
282
285
  * `requiredCapabilities`, and `-32022` carries the peer's `supported` revisions and
@@ -322,11 +325,7 @@ var MCPError = class extends Error {
322
325
  * ```
323
326
  */
324
327
  function isMCPError(value) {
325
- try {
326
- return value instanceof MCPError;
327
- } catch {
328
- return false;
329
- }
328
+ return isInstance(value, MCPError);
330
329
  }
331
330
  //#endregion
332
331
  //#region src/core/cloners.ts
@@ -577,7 +576,7 @@ function parseMCPInputState(value) {
577
576
  const name = parsed["name"];
578
577
  const digest = parsed["digest"];
579
578
  const state = parsed["state"];
580
- if (!isString(principal) || principal.length === 0 || !isNumber(expiry) || !Number.isFinite(expiry)) return;
579
+ if (!isString(principal) || principal.length === 0 || !isFiniteNumber(expiry)) return;
581
580
  if (!isJSONRPCId(id)) return void 0;
582
581
  if (!isString(version) || !isString(method) || !isString(name)) return void 0;
583
582
  if (!isString(digest) || !isUndefined(state) && !isJSONValue(state)) return void 0;
@@ -740,9 +739,9 @@ function serializeJSON(value, limits) {
740
739
  const limit = limits.bytes;
741
740
  const depth = limits.depth;
742
741
  const breadth = limits.keys;
743
- if (!Number.isFinite(limit) || !Number.isInteger(limit) || limit < 0) return void 0;
744
- if (!Number.isFinite(depth) || !Number.isInteger(depth) || depth < 0) return void 0;
745
- if (breadth !== void 0 && (!Number.isFinite(breadth) || !Number.isInteger(breadth) || breadth < 0)) return void 0;
742
+ if (!isInteger(limit) || limit < 0) return void 0;
743
+ if (!isInteger(depth) || depth < 0) return void 0;
744
+ if (breadth !== void 0 && (!isInteger(breadth) || breadth < 0)) return void 0;
746
745
  let bytes = 0;
747
746
  let keys = 0;
748
747
  const chunks = [];
@@ -856,7 +855,7 @@ function serializeJSON(value, limits) {
856
855
  continue;
857
856
  }
858
857
  if (isNumber(entry)) {
859
- if (!Number.isFinite(entry)) return void 0;
858
+ if (!isFiniteNumber(entry)) return void 0;
860
859
  const text = JSON.stringify(entry);
861
860
  if (!isString(text)) return void 0;
862
861
  bytes += text.length;
@@ -872,10 +871,10 @@ function serializeJSON(value, limits) {
872
871
  });
873
872
  continue;
874
873
  }
875
- if (typeof entry !== "object" || ancestors.has(entry)) return void 0;
876
- if (Array.isArray(entry)) {
874
+ if (!isObject(entry) || ancestors.has(entry)) return void 0;
875
+ if (isArray(entry)) {
877
876
  const lengthDescriptor = Reflect.getOwnPropertyDescriptor(entry, "length");
878
- if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value") || !isNumber(lengthDescriptor.value) || !Number.isInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.value > 4294967295 || lengthDescriptor.enumerable === true || lengthDescriptor.configurable === true) return void 0;
877
+ if (lengthDescriptor === void 0 || !Object.hasOwn(lengthDescriptor, "value") || !isNumber(lengthDescriptor.value) || !isInteger(lengthDescriptor.value) || lengthDescriptor.value < 0 || lengthDescriptor.value > 4294967295 || lengthDescriptor.enumerable === true || lengthDescriptor.configurable === true) return void 0;
879
878
  const length = lengthDescriptor.value;
880
879
  keys += length;
881
880
  bytes += 2 + (length === 0 ? 0 : length - 1);
@@ -892,7 +891,7 @@ function serializeJSON(value, limits) {
892
891
  continue;
893
892
  }
894
893
  const index = Number(name);
895
- if (!Number.isInteger(index) || index < 0 || index >= length || String(index) !== name) return void 0;
894
+ if (!isInteger(index) || index < 0 || index >= length || String(index) !== name) return void 0;
896
895
  }
897
896
  if (!foundLength) return void 0;
898
897
  const values = [];
@@ -1113,10 +1112,9 @@ function buildCallOutcome(name, result) {
1113
1112
  if (isRecord(result) && result["isError"] === true) throw new Error(text.length > 0 ? text : `MCP tool '${name}' failed`);
1114
1113
  let value;
1115
1114
  if (isRecord(result) && Object.hasOwn(result, "structuredContent")) value = result["structuredContent"];
1116
- else if (text.length > 0) try {
1117
- value = JSON.parse(text);
1118
- } catch {
1119
- value = text;
1115
+ else if (text.length > 0) {
1116
+ const parsed = parseJSON(text);
1117
+ value = parsed === void 0 ? text : parsed;
1120
1118
  }
1121
1119
  return Object.freeze({
1122
1120
  resultType: "complete",
@@ -1127,19 +1125,17 @@ function buildCallOutcome(name, result) {
1127
1125
  * Builds the canonical Tool call for one validated MCP `tools/call` request.
1128
1126
  *
1129
1127
  * @param request - The original MCP request
1130
- * @param caller - Optional consumer-asserted caller context
1131
1128
  * @param args - Optional once-validated modern arguments; omission retains legacy normalization
1132
1129
  * @returns The canonical call, or `undefined` when the name is invalid
1133
1130
  */
1134
- function buildToolCall(request, caller, args) {
1131
+ function buildToolCall(request, args) {
1135
1132
  const name = request.params?.["name"];
1136
- if (typeof name !== "string") return void 0;
1133
+ if (!isString(name)) return void 0;
1137
1134
  const rawArguments = request.params?.["arguments"];
1138
1135
  return {
1139
1136
  id: String(request.id),
1140
1137
  name,
1141
- arguments: args ?? (isRecord(rawArguments) ? rawArguments : {}),
1142
- ...caller === void 0 ? {} : { caller }
1138
+ arguments: args ?? (isRecord(rawArguments) ? rawArguments : {})
1143
1139
  };
1144
1140
  }
1145
1141
  /**
@@ -1230,7 +1226,8 @@ function buildMethodOptions(options, lifetime) {
1230
1226
  *
1231
1227
  * @remarks
1232
1228
  * Each {@link import('@orkestrel/tool').ToolDefinition} carries through its
1233
- * `name` and (when present) `description`; its open JSON-Schema `parameters`
1229
+ * `name`, optional `title` and `description`, and mapped annotation hints;
1230
+ * its open JSON-Schema `parameters`
1234
1231
  * becomes `inputSchema`, defaulting to an empty object schema (`{ type: 'object' }`)
1235
1232
  * when a tool declares none (MCP requires an `inputSchema`).
1236
1233
  *
@@ -1239,14 +1236,50 @@ function buildMethodOptions(options, lifetime) {
1239
1236
  */
1240
1237
  function buildToolDescriptors(manager) {
1241
1238
  return manager.definitions().map((definition) => {
1242
- const descriptor = {
1239
+ const annotations = definition.annotations === void 0 ? {} : toolAnnotationsToMCP(definition.annotations);
1240
+ return {
1243
1241
  name: definition.name,
1244
- inputSchema: definition.parameters ?? { type: "object" }
1242
+ inputSchema: definition.parameters ?? { type: "object" },
1243
+ ...definition.title === void 0 ? {} : { title: definition.title },
1244
+ ...definition.description === void 0 ? {} : { description: definition.description },
1245
+ ...Object.keys(annotations).length === 0 ? {} : { annotations }
1245
1246
  };
1246
- if (definition.description !== void 0) descriptor.description = definition.description;
1247
- return descriptor;
1248
1247
  });
1249
1248
  }
1249
+ /**
1250
+ * Projects domain tool annotations onto MCP wire hints without inventing defaults.
1251
+ *
1252
+ * @param annotations - The authored domain annotations
1253
+ * @returns The mapped hints; `untrusted` has no MCP counterpart
1254
+ *
1255
+ * @example
1256
+ * ```ts
1257
+ * toolAnnotationsToMCP({ pure: true, consequential: false }) // { readOnlyHint: true, destructiveHint: false }
1258
+ * ```
1259
+ */
1260
+ function toolAnnotationsToMCP(annotations) {
1261
+ return {
1262
+ ...annotations.pure === void 0 ? {} : { readOnlyHint: annotations.pure },
1263
+ ...annotations.consequential === void 0 ? {} : { destructiveHint: annotations.consequential }
1264
+ };
1265
+ }
1266
+ /**
1267
+ * Projects MCP wire hints onto domain tool annotations without inventing defaults.
1268
+ *
1269
+ * @param annotations - The validated MCP wire annotations
1270
+ * @returns The mapped annotations; unmapped wire hints are omitted
1271
+ *
1272
+ * @example
1273
+ * ```ts
1274
+ * mcpAnnotationsToTool({ readOnlyHint: false, destructiveHint: true }) // { pure: false, consequential: true }
1275
+ * ```
1276
+ */
1277
+ function mcpAnnotationsToTool(annotations) {
1278
+ return {
1279
+ ...annotations.readOnlyHint === void 0 ? {} : { pure: annotations.readOnlyHint },
1280
+ ...annotations.destructiveHint === void 0 ? {} : { consequential: annotations.destructiveHint }
1281
+ };
1282
+ }
1250
1283
  function buildModernResult(result, identity, ttl, scope) {
1251
1284
  const currentMetadata = isRecord(result) ? result["_meta"] : void 0;
1252
1285
  if (!isJSONValue(identity)) throw new TypeError("MCP identity must be exact JSON");
@@ -1282,7 +1315,7 @@ function modernResultToLegacy(result) {
1282
1315
  const projected = {};
1283
1316
  for (const [key, value] of Object.entries(result)) {
1284
1317
  if (key === "resultType" || key === "ttlMs" || key === "cacheScope") continue;
1285
- if (key === "content" && Array.isArray(value)) {
1318
+ if (key === "content" && isArray(value)) {
1286
1319
  projected[key] = value.map((entry) => isRecord(entry) && entry["type"] === "text" && isString(entry["text"]) ? {
1287
1320
  type: "text",
1288
1321
  text: entry["text"]
@@ -1395,7 +1428,7 @@ function matchesSubscriptionNotification(notification, filter) {
1395
1428
  if (notification.method === "notifications/resources/list_changed") return filter.resourcesListChanged === true;
1396
1429
  if (notification.method === "notifications/resources/updated") {
1397
1430
  const uri = notification.params?.["uri"];
1398
- return typeof uri === "string" && filter.resourceSubscriptions?.includes(uri) === true;
1431
+ return isString(uri) && filter.resourceSubscriptions?.includes(uri) === true;
1399
1432
  }
1400
1433
  if (notification.method === "notifications/tasks") return isMCPTaskNotification(notification) && filter.taskIds?.includes(notification.params.taskId) === true;
1401
1434
  return false;
@@ -1451,10 +1484,11 @@ function buildSubscriptionResult(id, identity) {
1451
1484
  * @remarks
1452
1485
  * `capabilities.resources` and `capabilities.prompts` appear only for servers with their
1453
1486
  * respective managers and derive notification flags from the configured subscription filter.
1454
- * `capabilities.completions` is independent and appears only with a completion provider.
1455
- * `capabilities.extensions` appears only for a server that configured the extension it
1456
- * would name. An advertisement is a promise a client is entitled to act on, so a server
1457
- * with no `task` policy omits the member entirely rather than advertising an empty
1487
+ * `tools.listChanged` is always advertised because the server produces the family from its
1488
+ * own registry. `capabilities.completions` is independent and appears only with a completion
1489
+ * provider. `capabilities.extensions` appears only for a server that configured the
1490
+ * extension it would name. An advertisement is a promise a client is entitled to act on, so
1491
+ * a server with no `task` policy omits the member entirely rather than advertising an empty
1458
1492
  * record — and its discovery answer stays byte-for-byte what it was before the extension
1459
1493
  * existed.
1460
1494
  *
@@ -1465,7 +1499,7 @@ function buildDiscoverResult(options) {
1465
1499
  return buildModernResult({
1466
1500
  supportedVersions: SUPPORTED_MODERN_PROTOCOL_VERSIONS,
1467
1501
  capabilities: {
1468
- tools: {},
1502
+ tools: { listChanged: true },
1469
1503
  ...options.resources === void 0 ? {} : { resources: {
1470
1504
  ...options.subscription?.notifications.resourceSubscriptions === void 0 ? {} : { subscribe: true },
1471
1505
  ...options.subscription?.notifications.resourcesListChanged === true ? { listChanged: true } : {}
@@ -2108,7 +2142,7 @@ function bindServer(server, transport) {
2108
2142
  try {
2109
2143
  const answer = await server.handle(message, { signal: request.signal });
2110
2144
  if (answer === void 0) return;
2111
- if (typeof answer === "string") {
2145
+ if (isString(answer)) {
2112
2146
  if (!request.signal.aborted) await transport.send(answer);
2113
2147
  } else if (request.signal.aborted) await answer[Symbol.asyncDispose]();
2114
2148
  else await sendStream(answer, transport);
@@ -2509,7 +2543,7 @@ function isMCPAnnotations(value) {
2509
2543
  const audience = annotations["audience"];
2510
2544
  const priority = annotations["priority"];
2511
2545
  const lastModified = annotations["lastModified"];
2512
- if (!isUndefined(audience) && (!Array.isArray(audience) || !audience.every((role) => role === "user" || role === "assistant"))) return false;
2546
+ if (!isUndefined(audience) && (!isArray(audience) || !audience.every((role) => role === "user" || role === "assistant"))) return false;
2513
2547
  if (!isUndefined(priority) && (!isFiniteNumber(priority) || priority < 0 || priority > 1)) return false;
2514
2548
  return isUndefined(lastModified) || isString(lastModified);
2515
2549
  } catch {
@@ -2531,7 +2565,7 @@ function isMCPIcon(value) {
2531
2565
  const mimeType = icon["mimeType"];
2532
2566
  const sizes = icon["sizes"];
2533
2567
  const theme = icon["theme"];
2534
- return (isUndefined(mimeType) || isString(mimeType)) && (isUndefined(sizes) || Array.isArray(sizes) && sizes.every((size) => isString(size))) && (isUndefined(theme) || theme === "light" || theme === "dark");
2568
+ return (isUndefined(mimeType) || isString(mimeType)) && (isUndefined(sizes) || isArray(sizes) && sizes.every((size) => isString(size))) && (isUndefined(theme) || theme === "light" || theme === "dark");
2535
2569
  } catch {
2536
2570
  return false;
2537
2571
  }
@@ -2547,7 +2581,7 @@ function isMCPIdentity(value) {
2547
2581
  const description = identity["description"];
2548
2582
  const website = identity["websiteUrl"];
2549
2583
  const icons = identity["icons"];
2550
- return (isUndefined(title) || isString(title)) && (isUndefined(description) || isString(description)) && (isUndefined(website) || isAbsoluteURI(website)) && (isUndefined(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon)));
2584
+ return (isUndefined(title) || isString(title)) && (isUndefined(description) || isString(description)) && (isUndefined(website) || isAbsoluteURI(website)) && (isUndefined(icons) || isArray(icons) && icons.every((icon) => isMCPIcon(icon)));
2551
2585
  } catch {
2552
2586
  return false;
2553
2587
  }
@@ -2658,12 +2692,34 @@ function isMCPResource(value) {
2658
2692
  const resource = owned.value;
2659
2693
  const icons = resource["icons"];
2660
2694
  const size = resource["size"];
2661
- return isAbsoluteURI(resource["uri"]) && isString(resource["name"]) && (isUndefined(resource["title"]) || isString(resource["title"])) && (isUndefined(resource["description"]) || isString(resource["description"])) && (isUndefined(resource["mimeType"]) || isString(resource["mimeType"])) && (isUndefined(resource["annotations"]) || isMCPAnnotations(resource["annotations"])) && (isUndefined(size) || isInteger(size) && size >= 0) && (isUndefined(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(resource["_meta"]) || isMCPMetaObject(resource["_meta"]));
2695
+ return isAbsoluteURI(resource["uri"]) && isString(resource["name"]) && (isUndefined(resource["title"]) || isString(resource["title"])) && (isUndefined(resource["description"]) || isString(resource["description"])) && (isUndefined(resource["mimeType"]) || isString(resource["mimeType"])) && (isUndefined(resource["annotations"]) || isMCPAnnotations(resource["annotations"])) && (isUndefined(size) || isInteger(size) && size >= 0) && (isUndefined(icons) || isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(resource["_meta"]) || isMCPMetaObject(resource["_meta"]));
2662
2696
  } catch {
2663
2697
  return false;
2664
2698
  }
2665
2699
  }
2666
2700
  /**
2701
+ * Checks whether a value carries valid consumed MCP 2026-07-28 tool annotation hints.
2702
+ *
2703
+ * @remarks
2704
+ * Validates only `readOnlyHint` and `destructiveHint`; other wire fields are not consumed, so
2705
+ * the open `objectOf` combinator carries them through unread. Every declared member is
2706
+ * optional, a hostile read answers `false` rather than throwing, and an array is refused.
2707
+ *
2708
+ * @param value - The unknown wire annotations
2709
+ * @returns True if consumed annotation fields are optional booleans; false otherwise
2710
+ *
2711
+ * @example
2712
+ * ```ts
2713
+ * isMCPToolAnnotations({ readOnlyHint: false }) // true
2714
+ * ```
2715
+ */
2716
+ function isMCPToolAnnotations(value) {
2717
+ return objectOf({
2718
+ readOnlyHint: isBoolean,
2719
+ destructiveHint: isBoolean
2720
+ }, true)(value);
2721
+ }
2722
+ /**
2667
2723
  * Determines whether a value is one resource-template descriptor.
2668
2724
  *
2669
2725
  * @remarks
@@ -2679,7 +2735,7 @@ function isMCPResourceTemplate(value) {
2679
2735
  try {
2680
2736
  const template = owned.value;
2681
2737
  const icons = template["icons"];
2682
- return isString(template["uriTemplate"]) && template["uriTemplate"].length > 0 && isString(template["name"]) && (isUndefined(template["title"]) || isString(template["title"])) && (isUndefined(template["description"]) || isString(template["description"])) && (isUndefined(template["mimeType"]) || isString(template["mimeType"])) && (isUndefined(template["annotations"]) || isMCPAnnotations(template["annotations"])) && (isUndefined(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(template["_meta"]) || isMCPMetaObject(template["_meta"]));
2738
+ return isString(template["uriTemplate"]) && template["uriTemplate"].length > 0 && isString(template["name"]) && (isUndefined(template["title"]) || isString(template["title"])) && (isUndefined(template["description"]) || isString(template["description"])) && (isUndefined(template["mimeType"]) || isString(template["mimeType"])) && (isUndefined(template["annotations"]) || isMCPAnnotations(template["annotations"])) && (isUndefined(icons) || isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(template["_meta"]) || isMCPMetaObject(template["_meta"]));
2683
2739
  } catch {
2684
2740
  return false;
2685
2741
  }
@@ -2719,7 +2775,7 @@ function isMCPResourcePage(value) {
2719
2775
  if (!owned.success) return false;
2720
2776
  const resources = owned.value["resources"];
2721
2777
  const cursor = owned.value["nextCursor"];
2722
- return Array.isArray(resources) && resources.every((resource) => isMCPResource(resource)) && (isUndefined(cursor) || isString(cursor));
2778
+ return isArray(resources) && resources.every((resource) => isMCPResource(resource)) && (isUndefined(cursor) || isString(cursor));
2723
2779
  }
2724
2780
  /**
2725
2781
  * Determines whether a value is one consumer-owned resource-template page.
@@ -2732,7 +2788,7 @@ function isMCPResourceTemplatePage(value) {
2732
2788
  if (!owned.success) return false;
2733
2789
  const templates = owned.value["resourceTemplates"];
2734
2790
  const cursor = owned.value["nextCursor"];
2735
- return Array.isArray(templates) && templates.every((template) => isMCPResourceTemplate(template)) && (isUndefined(cursor) || isString(cursor));
2791
+ return isArray(templates) && templates.every((template) => isMCPResourceTemplate(template)) && (isUndefined(cursor) || isString(cursor));
2736
2792
  }
2737
2793
  /**
2738
2794
  * Determines whether a value is a string-valued MCP argument record.
@@ -2768,7 +2824,7 @@ function isMCPPrompt(value) {
2768
2824
  const prompt = owned.value;
2769
2825
  const argumentsValue = prompt["arguments"];
2770
2826
  const icons = prompt["icons"];
2771
- return isString(prompt["name"]) && (isUndefined(prompt["title"]) || isString(prompt["title"])) && (isUndefined(prompt["description"]) || isString(prompt["description"])) && (isUndefined(argumentsValue) || Array.isArray(argumentsValue) && argumentsValue.every((argument) => isMCPPromptArgument(argument))) && (isUndefined(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(prompt["_meta"]) || isMCPMetaObject(prompt["_meta"]));
2827
+ return isString(prompt["name"]) && (isUndefined(prompt["title"]) || isString(prompt["title"])) && (isUndefined(prompt["description"]) || isString(prompt["description"])) && (isUndefined(argumentsValue) || isArray(argumentsValue) && argumentsValue.every((argument) => isMCPPromptArgument(argument))) && (isUndefined(icons) || isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(prompt["_meta"]) || isMCPMetaObject(prompt["_meta"]));
2772
2828
  }
2773
2829
  /**
2774
2830
  * Determines whether a value is one prompt message with existing rich content.
@@ -2791,7 +2847,7 @@ function isMCPPromptPage(value) {
2791
2847
  if (!owned.success) return false;
2792
2848
  const prompts = owned.value["prompts"];
2793
2849
  const cursor = owned.value["nextCursor"];
2794
- return Array.isArray(prompts) && prompts.every((prompt) => isMCPPrompt(prompt)) && (isUndefined(cursor) || isString(cursor));
2850
+ return isArray(prompts) && prompts.every((prompt) => isMCPPrompt(prompt)) && (isUndefined(cursor) || isString(cursor));
2795
2851
  }
2796
2852
  /**
2797
2853
  * Determines whether a value is one complete `prompts/get` result.
@@ -2804,7 +2860,7 @@ function isMCPPromptGetResult(value) {
2804
2860
  if (!owned.success) return false;
2805
2861
  const result = owned.value;
2806
2862
  const messages = result["messages"];
2807
- return result["resultType"] === "complete" && (isUndefined(result["description"]) || isString(result["description"])) && Array.isArray(messages) && messages.every((message) => isMCPPromptMessage(message)) && (isUndefined(result["_meta"]) || isMCPResultMetaObject(result["_meta"]));
2863
+ return result["resultType"] === "complete" && (isUndefined(result["description"]) || isString(result["description"])) && isArray(messages) && messages.every((message) => isMCPPromptMessage(message)) && (isUndefined(result["_meta"]) || isMCPResultMetaObject(result["_meta"]));
2808
2864
  }
2809
2865
  /**
2810
2866
  * Determines whether a value is a prompt or resource-template completion reference.
@@ -2849,7 +2905,7 @@ function isMCPCompletion(value) {
2849
2905
  const completion = owned.value;
2850
2906
  const values = completion["values"];
2851
2907
  const total = completion["total"];
2852
- return Array.isArray(values) && values.every((candidate) => isString(candidate)) && (isUndefined(total) || isInteger(total) && total >= 0) && (isUndefined(completion["hasMore"]) || isBoolean(completion["hasMore"]));
2908
+ return isArray(values) && values.every((candidate) => isString(candidate)) && (isUndefined(total) || isInteger(total) && total >= 0) && (isUndefined(completion["hasMore"]) || isBoolean(completion["hasMore"]));
2853
2909
  }
2854
2910
  /**
2855
2911
  * Determines whether a value is one complete, capped `completion/complete` result.
@@ -2887,7 +2943,7 @@ function isMCPContent(value) {
2887
2943
  case "resource_link": {
2888
2944
  const icons = content["icons"];
2889
2945
  const size = content["size"];
2890
- return isString(content["name"]) && isAbsoluteURI(content["uri"]) && (isUndefined(content["title"]) || isString(content["title"])) && (isUndefined(content["description"]) || isString(content["description"])) && (isUndefined(content["mimeType"]) || isString(content["mimeType"])) && (isUndefined(icons) || Array.isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(size) || isInteger(size) && size >= 0);
2946
+ return isString(content["name"]) && isAbsoluteURI(content["uri"]) && (isUndefined(content["title"]) || isString(content["title"])) && (isUndefined(content["description"]) || isString(content["description"])) && (isUndefined(content["mimeType"]) || isString(content["mimeType"])) && (isUndefined(icons) || isArray(icons) && icons.every((icon) => isMCPIcon(icon))) && (isUndefined(size) || isInteger(size) && size >= 0);
2891
2947
  }
2892
2948
  default: return false;
2893
2949
  }
@@ -2961,7 +3017,7 @@ function isMCPCallResult(value) {
2961
3017
  const content = result["content"];
2962
3018
  const error = result["isError"];
2963
3019
  const metadata = result["_meta"];
2964
- return Array.isArray(content) && content.every((entry) => isMCPContent(entry)) && (isUndefined(error) || isBoolean(error)) && (isUndefined(metadata) || isMCPResultMetaObject(metadata));
3020
+ return isArray(content) && content.every((entry) => isMCPContent(entry)) && (isUndefined(error) || isBoolean(error)) && (isUndefined(metadata) || isMCPResultMetaObject(metadata));
2965
3021
  } catch {
2966
3022
  return false;
2967
3023
  }
@@ -3150,7 +3206,7 @@ function isMCPTaskNotification(value) {
3150
3206
  * ```
3151
3207
  */
3152
3208
  function isBoundedString(value, bytes) {
3153
- if (!isString(value) || !Number.isFinite(bytes) || !Number.isInteger(bytes) || bytes < 0) return false;
3209
+ if (!isString(value) || !isInteger(bytes) || bytes < 0) return false;
3154
3210
  let measured = 0;
3155
3211
  for (let index = 0; index < value.length; index += 1) {
3156
3212
  const code = value.charCodeAt(index);
@@ -3272,6 +3328,22 @@ function isMCPSubscriptionFilter(value) {
3272
3328
  return isUndefined(tasks) || arrayOf(isString)(tasks);
3273
3329
  }
3274
3330
  /**
3331
+ * Checks whether a subscription filter leaves the built-in tools family to the server.
3332
+ *
3333
+ * @param value - The unknown value to inspect
3334
+ * @returns True if the filter is valid and does not claim tools changes; false otherwise
3335
+ *
3336
+ * @example
3337
+ * ```ts
3338
+ * isMCPConsumerFilter({ promptsListChanged: true }) // true
3339
+ * isMCPConsumerFilter({ toolsListChanged: true }) // false
3340
+ * ```
3341
+ */
3342
+ function isMCPConsumerFilter(value) {
3343
+ const owned = attempt(() => cloneJSONRecord(value));
3344
+ return owned.success && isMCPSubscriptionFilter(owned.value) && owned.value["toolsListChanged"] !== true;
3345
+ }
3346
+ /**
3275
3347
  * Determines whether a value is a graceful `subscriptions/listen` result.
3276
3348
  *
3277
3349
  * @param value - The unknown value to inspect
@@ -3319,21 +3391,21 @@ function isMCPElicitFieldSchema(value) {
3319
3391
  const names = schema["enumNames"];
3320
3392
  const titled = schema["oneOf"];
3321
3393
  if (!isUndefined(minimum) && (!isInteger(minimum) || minimum < 0) || !isUndefined(maximum) && (!isInteger(maximum) || maximum < 0) || !isUndefined(fallback) && !isString(fallback) || !isUndefined(format) && format !== "uri" && format !== "email" && format !== "date" && format !== "date-time") return false;
3322
- if (!isUndefined(choices) && (!Array.isArray(choices) || !choices.every(isString))) return false;
3323
- if (!isUndefined(names) && (isUndefined(choices) || !Array.isArray(names) || !names.every(isString))) return false;
3324
- return !(!isUndefined(titled) && (!Array.isArray(titled) || !titled.every((choice) => isJSONObject(choice) && isString(choice["const"]) && isString(choice["title"]))));
3394
+ if (!isUndefined(choices) && (!isArray(choices) || !choices.every(isString))) return false;
3395
+ if (!isUndefined(names) && (isUndefined(choices) || !isArray(names) || !names.every(isString))) return false;
3396
+ return !(!isUndefined(titled) && (!isArray(titled) || !titled.every((choice) => isJSONObject(choice) && isString(choice["const"]) && isString(choice["title"]))));
3325
3397
  }
3326
3398
  if (schema["type"] !== "array") return false;
3327
3399
  const minimum = schema["minItems"];
3328
3400
  const maximum = schema["maxItems"];
3329
3401
  const items = schema["items"];
3330
- if (!isUndefined(minimum) && (!isInteger(minimum) || minimum < 0) || !isUndefined(maximum) && (!isInteger(maximum) || maximum < 0) || !isUndefined(fallback) && (!Array.isArray(fallback) || !fallback.every((item) => isString(item))) || !isJSONObject(items)) return false;
3402
+ if (!isUndefined(minimum) && (!isInteger(minimum) || minimum < 0) || !isUndefined(maximum) && (!isInteger(maximum) || maximum < 0) || !isUndefined(fallback) && (!isArray(fallback) || !fallback.every((item) => isString(item))) || !isJSONObject(items)) return false;
3331
3403
  const itemType = items["type"];
3332
3404
  const choices = items["enum"];
3333
3405
  const titled = items["anyOf"];
3334
3406
  if (!isUndefined(itemType) && itemType !== "string") return false;
3335
- if (!isUndefined(choices) && (!Array.isArray(choices) || !choices.every(isString))) return false;
3336
- if (!isUndefined(titled) && (!Array.isArray(titled) || !titled.every((choice) => isJSONObject(choice) && isString(choice["const"]) && isString(choice["title"])))) return false;
3407
+ if (!isUndefined(choices) && (!isArray(choices) || !choices.every(isString))) return false;
3408
+ if (!isUndefined(titled) && (!isArray(titled) || !titled.every((choice) => isJSONObject(choice) && isString(choice["const"]) && isString(choice["title"])))) return false;
3337
3409
  return itemType === "string" && !isUndefined(choices) || !isUndefined(titled);
3338
3410
  } catch {
3339
3411
  return false;
@@ -3368,7 +3440,7 @@ function isMCPElicitSchema(value) {
3368
3440
  const dialect = schema["$schema"];
3369
3441
  if (!isUndefined(dialect) && !isString(dialect)) return false;
3370
3442
  const required = schema["required"];
3371
- return (isUndefined(required) || Array.isArray(required) && required.every((name) => isString(name))) && Object.values(properties).every((property) => isMCPElicitFieldSchema(property));
3443
+ return (isUndefined(required) || isArray(required) && required.every((name) => isString(name))) && Object.values(properties).every((property) => isMCPElicitFieldSchema(property));
3372
3444
  } catch {
3373
3445
  return false;
3374
3446
  }
@@ -3511,7 +3583,7 @@ function isMCPElicitResult(value) {
3511
3583
  if (action !== "accept") return isUndefined(content);
3512
3584
  if (isUndefined(content)) return true;
3513
3585
  if (!isJSONObject(content)) return false;
3514
- return Object.values(content).every((item) => isString(item) || isFiniteNumber(item) || isBoolean(item) || Array.isArray(item) && item.every((entry) => isString(entry)));
3586
+ return Object.values(content).every((item) => isString(item) || isFiniteNumber(item) || isBoolean(item) || isArray(item) && item.every((entry) => isString(entry)));
3515
3587
  } catch {
3516
3588
  return false;
3517
3589
  }
@@ -3566,11 +3638,11 @@ function isElicitContent(value, schema) {
3566
3638
  const properties = declared.value["properties"];
3567
3639
  const required = declared.value["required"];
3568
3640
  if (!isJSONObject(properties)) return false;
3569
- if (Array.isArray(required)) {
3641
+ if (isArray(required)) {
3570
3642
  for (const name of required) if (!isString(name) || !Object.hasOwn(content, name)) return false;
3571
3643
  }
3572
3644
  for (const [name, item] of Object.entries(content)) {
3573
- if (!isString(item) && !isFiniteNumber(item) && !isBoolean(item) && !(Array.isArray(item) && item.every((entry) => isString(entry)))) return false;
3645
+ if (!isString(item) && !isFiniteNumber(item) && !isBoolean(item) && !(isArray(item) && item.every((entry) => isString(entry)))) return false;
3574
3646
  if (!Object.hasOwn(properties, name)) continue;
3575
3647
  const field = properties[name];
3576
3648
  if (!isJSONObject(field)) return false;
@@ -3583,7 +3655,7 @@ function isElicitContent(value, schema) {
3583
3655
  const minimum = field["minimum"];
3584
3656
  const maximum = field["maximum"];
3585
3657
  if (!isFiniteNumber(item)) return false;
3586
- if (declaredType === "integer" && !Number.isInteger(item)) return false;
3658
+ if (declaredType === "integer" && !isInteger(item)) return false;
3587
3659
  if (isFiniteNumber(minimum) && item < minimum) return false;
3588
3660
  if (isFiniteNumber(maximum) && item > maximum) return false;
3589
3661
  continue;
@@ -3595,8 +3667,8 @@ function isElicitContent(value, schema) {
3595
3667
  const maximum = field["maxLength"];
3596
3668
  const format = field["format"];
3597
3669
  if (!isString(item)) return false;
3598
- if (Array.isArray(choices) && !choices.includes(item)) return false;
3599
- if (Array.isArray(titled) && !titled.some((choice) => isJSONObject(choice) && choice["const"] === item)) return false;
3670
+ if (isArray(choices) && !choices.includes(item)) return false;
3671
+ if (isArray(titled) && !titled.some((choice) => isJSONObject(choice) && choice["const"] === item)) return false;
3600
3672
  const length = Array.from(item).length;
3601
3673
  if (isInteger(minimum) && length < minimum) return false;
3602
3674
  if (isInteger(maximum) && length > maximum) return false;
@@ -3609,14 +3681,14 @@ function isElicitContent(value, schema) {
3609
3681
  const items = field["items"];
3610
3682
  const minimum = field["minItems"];
3611
3683
  const maximum = field["maxItems"];
3612
- if (!Array.isArray(item) || !item.every((entry) => isString(entry))) return false;
3684
+ if (!isArray(item) || !item.every((entry) => isString(entry))) return false;
3613
3685
  if (isInteger(minimum) && item.length < minimum) return false;
3614
3686
  if (isInteger(maximum) && item.length > maximum) return false;
3615
3687
  if (!isJSONObject(items)) return false;
3616
3688
  const choices = items["enum"];
3617
3689
  const titled = items["anyOf"];
3618
- if (Array.isArray(choices) && !item.every((entry) => choices.includes(entry))) return false;
3619
- if (Array.isArray(titled) && !item.every((entry) => titled.some((choice) => isJSONObject(choice) && choice["const"] === entry))) return false;
3690
+ if (isArray(choices) && !item.every((entry) => choices.includes(entry))) return false;
3691
+ if (isArray(titled) && !item.every((entry) => titled.some((choice) => isJSONObject(choice) && choice["const"] === entry))) return false;
3620
3692
  }
3621
3693
  return true;
3622
3694
  } catch {
@@ -3677,7 +3749,7 @@ function isMCPRootResult(value) {
3677
3749
  const result = owned.value;
3678
3750
  const roots = result["roots"];
3679
3751
  const metadata = result["_meta"];
3680
- if (!Array.isArray(roots) || !roots.every((root) => isMCPRoot(root))) return false;
3752
+ if (!isArray(roots) || !roots.every((root) => isMCPRoot(root))) return false;
3681
3753
  return isUndefined(metadata) || isMCPMetaObject(metadata);
3682
3754
  } catch {
3683
3755
  return false;
@@ -3715,7 +3787,7 @@ function isMCPSampleContent(value) {
3715
3787
  if (block["type"] === "tool_result") {
3716
3788
  const carried = block["content"];
3717
3789
  const failed = block["isError"];
3718
- if (!Array.isArray(carried) || !carried.every((entry) => isMCPContent(entry))) return false;
3790
+ if (!isArray(carried) || !carried.every((entry) => isMCPContent(entry))) return false;
3719
3791
  if (!isUndefined(failed) && !isBoolean(failed)) return false;
3720
3792
  return isString(block["toolUseId"]);
3721
3793
  }
@@ -3764,7 +3836,7 @@ function isMCPSampleResult(value) {
3764
3836
  const metadata = result["_meta"];
3765
3837
  if (role !== "user" && role !== "assistant") return false;
3766
3838
  if (!isString(result["model"])) return false;
3767
- if (!(Array.isArray(content) ? content : [content]).every((block) => isMCPSampleContent(block))) return false;
3839
+ if (!(isArray(content) ? content : [content]).every((block) => isMCPSampleContent(block))) return false;
3768
3840
  if (!isUndefined(reason) && !isString(reason)) return false;
3769
3841
  return isUndefined(metadata) || isMCPMetaObject(metadata);
3770
3842
  } catch {
@@ -5014,6 +5086,7 @@ var MCPServer = class {
5014
5086
  #limits;
5015
5087
  #subscriptions = 0;
5016
5088
  constructor(options) {
5089
+ if (options.subscription !== void 0 && !isMCPConsumerFilter(options.subscription.notifications)) throw new MCPError("The consumer filter must be a valid filter that omits tools changes, because the server produces that family from its registry", JSONRPC_INVALID_PARAMS);
5017
5090
  this.#emitter = new Emitter({
5018
5091
  ...options.on !== void 0 ? { on: options.on } : {},
5019
5092
  ...options.error !== void 0 ? { error: options.error } : {}
@@ -5176,7 +5249,7 @@ var MCPServer = class {
5176
5249
  const input = captured[0];
5177
5250
  return this.#forward(input, request);
5178
5251
  }
5179
- if (!Array.isArray(captured[0]) || !captured[0].every((entry) => isMCPResourceContents(entry))) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
5252
+ if (!isArray(captured[0]) || !captured[0].every((entry) => isMCPResourceContents(entry))) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server resource manager returned invalid or oversized contents");
5180
5253
  const result = buildModernResult({ contents: captured[0] }, this.#options.identity, this.#options.cache?.ttl ?? 6e4, this.#options.cache?.scope);
5181
5254
  return buildJSONRPCResult(request.id, result);
5182
5255
  }
@@ -5259,7 +5332,7 @@ var MCPServer = class {
5259
5332
  const generated = await manager.complete(params, options);
5260
5333
  if (generated === void 0) return buildJSONRPCError(request.id, JSONRPC_INVALID_PARAMS, "Completion reference not found");
5261
5334
  const candidate = generated;
5262
- if (!isRecord(candidate) || !Array.isArray(candidate["values"])) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server completion manager returned invalid or oversized candidates");
5335
+ if (!isRecord(candidate) || !isArray(candidate["values"])) return buildJSONRPCError(request.id, JSONRPC_INTERNAL_ERROR, "Server completion manager returned invalid or oversized candidates");
5263
5336
  const values = candidate["values"];
5264
5337
  const truncated = values.length > 100;
5265
5338
  const captured = snapshotJSON({
@@ -5286,14 +5359,14 @@ var MCPServer = class {
5286
5359
  const args = isRecord(rawArguments) ? rawArguments : EMPTY_MCP_ARGUMENTS;
5287
5360
  const input = await this.#input(request, args, options);
5288
5361
  if (input !== void 0) return input;
5289
- const call = buildToolCall(request, options.caller, args);
5362
+ const call = buildToolCall(request, args);
5290
5363
  if (call === void 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: a string `name` is required");
5291
5364
  const deferred = await this.#defer(request, call, options);
5292
5365
  if (deferred !== void 0) return deferred;
5293
5366
  const token = params?.["_meta"];
5294
5367
  const progressToken = isRecord(token) ? token["progressToken"] : void 0;
5295
- if (this.#options.execution !== void 0 && (isString(progressToken) || typeof progressToken === "number" && Number.isInteger(progressToken))) return this.#progress(request, call, progressToken, options);
5296
- const result = await this.#execute(request, call, options.signal);
5368
+ if (this.#options.execution !== void 0 && (isString(progressToken) || isInteger(progressToken))) return this.#progress(request, call, progressToken, options);
5369
+ const result = await this.#execute(request, call, options);
5297
5370
  return "jsonrpc" in result ? result : buildJSONRPCResult(id, result);
5298
5371
  }
5299
5372
  async #defer(request, call, options) {
@@ -5337,7 +5410,7 @@ var MCPServer = class {
5337
5410
  keys: this.#limits.keys,
5338
5411
  depth: this.#limits.depth
5339
5412
  }, signal);
5340
- const execution = this.#execute(request, call, signal, reporter);
5413
+ const execution = this.#execute(request, call, options, reporter);
5341
5414
  try {
5342
5415
  while (true) {
5343
5416
  const outcome = await Promise.race([reporter.take(), execution]);
@@ -5355,14 +5428,19 @@ var MCPServer = class {
5355
5428
  reporter.stop();
5356
5429
  }
5357
5430
  }
5358
- async #execute(request, call, signal, progress) {
5431
+ async #execute(request, call, options, progress) {
5432
+ const { signal, caller } = options;
5359
5433
  let result;
5360
5434
  try {
5361
- result = this.#options.execution === void 0 ? await this.#options.tools.execute(call) : await this.#options.execution({
5435
+ result = this.#options.execution === void 0 ? await this.#options.tools.execute(call, {
5436
+ signal,
5437
+ ...caller === void 0 ? {} : { caller }
5438
+ }) : await this.#options.execution({
5362
5439
  request,
5363
5440
  call,
5364
5441
  tools: this.#options.tools,
5365
5442
  signal,
5443
+ ...caller === void 0 ? {} : { caller },
5366
5444
  ...progress === void 0 ? {} : { progress }
5367
5445
  });
5368
5446
  } catch (error) {
@@ -5500,7 +5578,7 @@ var MCPServer = class {
5500
5578
  bytes: this.#limits.message,
5501
5579
  depth: this.#limits.depth
5502
5580
  });
5503
- if (configured === void 0 || context === void 0 || !isString(principal) || principal.length === 0 || !Number.isFinite(configured.ttl) || configured.ttl <= 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5581
+ if (configured === void 0 || context === void 0 || !isString(principal) || principal.length === 0 || !isFiniteNumber(configured.ttl) || configured.ttl <= 0) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: input policy returned an invalid round or continuation context");
5504
5582
  if (previous !== void 0 && previous <= Date.now()) return buildJSONRPCError(id, JSONRPC_INVALID_PARAMS, "Invalid params: request state could not be verified for this retry");
5505
5583
  const expiry = Date.now() + configured.ttl;
5506
5584
  const protectedState = {
@@ -5550,7 +5628,10 @@ var MCPServer = class {
5550
5628
  const task = this.#options.task;
5551
5629
  const configured = this.#options.subscription;
5552
5630
  const tasks = task !== void 0 && configured !== void 0;
5553
- let notifications = buildSubscriptionFilter(requested, configured?.notifications ?? {}, tasks);
5631
+ let notifications = buildSubscriptionFilter(requested, {
5632
+ ...configured?.notifications,
5633
+ toolsListChanged: true
5634
+ }, tasks);
5554
5635
  const requestedTaskIds = notifications.taskIds;
5555
5636
  if (requestedTaskIds !== void 0) {
5556
5637
  const resolved = [];
@@ -5563,19 +5644,22 @@ var MCPServer = class {
5563
5644
  taskIds: resolved
5564
5645
  } : rest;
5565
5646
  }
5647
+ const state = {
5648
+ frame: void 0,
5649
+ failure: void 0,
5650
+ iterator: void 0
5651
+ };
5652
+ const source = new ReadableStream({
5653
+ start: this.#startSubscription.bind(this, notifications, options, configured, state),
5654
+ pull: this.#pullSubscription.bind(this, notifications, options, state)
5655
+ });
5566
5656
  yield buildSubscriptionAcknowledgement(notifications, id);
5567
- if (configured !== void 0) {
5568
- const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
5569
- options.signal.addEventListener("abort", () => void iterator.return?.(void 0)?.catch(() => void 0), { once: true });
5570
- for (let next = await iterator.next(); next.done !== true; next = await iterator.next()) {
5571
- const owned = parseJSONRPCMessage(next.value, {
5572
- bytes: this.#limits.message,
5573
- depth: this.#limits.depth
5574
- });
5575
- if (owned === void 0 || !isJSONRPCNotification(owned)) continue;
5576
- if (matchesSubscriptionNotification(owned, notifications)) yield stampSubscriptionNotification(owned, id);
5577
- }
5657
+ for await (const notification of source) {
5658
+ if (notification === void 0) continue;
5659
+ if (notification === state.frame) state.frame = void 0;
5660
+ yield stampSubscriptionNotification(notification, id);
5578
5661
  }
5662
+ if (state.failure !== void 0) throw state.failure.error;
5579
5663
  return buildSubscriptionResult(id, this.#options.identity);
5580
5664
  } catch (error) {
5581
5665
  if (options.signal.aborted) throw error;
@@ -5584,6 +5668,72 @@ var MCPServer = class {
5584
5668
  slot.abort();
5585
5669
  }
5586
5670
  }
5671
+ async #startSubscription(notifications, options, configured, state, controller) {
5672
+ const emitter = this.#options.tools.emitter;
5673
+ const changed = this.#change.bind(this, controller, options.signal, state);
5674
+ if (options.signal.aborted) {
5675
+ controller.error(options.signal.reason);
5676
+ return;
5677
+ }
5678
+ if (notifications.toolsListChanged === true && !emitter.destroyed) {
5679
+ emitter.on("add", changed);
5680
+ emitter.on("remove", changed);
5681
+ emitter.on("clear", changed);
5682
+ }
5683
+ options.signal.addEventListener("abort", () => {
5684
+ emitter.off("add", changed);
5685
+ emitter.off("remove", changed);
5686
+ emitter.off("clear", changed);
5687
+ const iterator = state.iterator;
5688
+ state.iterator = void 0;
5689
+ if (iterator !== void 0) this.#releaseProducer(iterator, options.signal.reason);
5690
+ controller.error(options.signal.reason);
5691
+ }, { once: true });
5692
+ try {
5693
+ if (configured === void 0) return;
5694
+ const iterator = (await configured.producer(notifications, options))[Symbol.asyncIterator]();
5695
+ if (options.signal.aborted) this.#releaseProducer(iterator, options.signal.reason);
5696
+ else state.iterator = iterator;
5697
+ } catch (error) {
5698
+ this.#failSubscription(state, options.signal, controller, error);
5699
+ }
5700
+ }
5701
+ #change(controller, signal, state) {
5702
+ if (signal.aborted || state.failure !== void 0 || state.frame !== void 0) return;
5703
+ const notification = {
5704
+ jsonrpc: "2.0",
5705
+ method: "notifications/tools/list_changed"
5706
+ };
5707
+ state.frame = notification;
5708
+ controller.enqueue(notification);
5709
+ }
5710
+ async #pullSubscription(notifications, options, state, controller) {
5711
+ try {
5712
+ if (options.signal.aborted) return;
5713
+ const next = await state.iterator?.next();
5714
+ if (options.signal.aborted) return;
5715
+ if (next === void 0 || next.done === true) {
5716
+ state.iterator = void 0;
5717
+ if (notifications.toolsListChanged !== true) controller.close();
5718
+ return;
5719
+ }
5720
+ const owned = parseJSONRPCMessage(next.value, {
5721
+ bytes: this.#limits.message,
5722
+ depth: this.#limits.depth
5723
+ });
5724
+ controller.enqueue(owned !== void 0 && isJSONRPCNotification(owned) && owned.method !== "notifications/tools/list_changed" && matchesSubscriptionNotification(owned, notifications) ? owned : void 0);
5725
+ } catch (error) {
5726
+ this.#failSubscription(state, options.signal, controller, error);
5727
+ }
5728
+ }
5729
+ #failSubscription(state, signal, controller, error) {
5730
+ if (signal.aborted) return;
5731
+ state.failure = { error };
5732
+ controller.close();
5733
+ }
5734
+ #releaseProducer(iterator, reason) {
5735
+ iterator.return?.(reason)?.catch(() => void 0);
5736
+ }
5587
5737
  #readTaskId(request) {
5588
5738
  const id = request.id;
5589
5739
  const context = parseRequestContext(request, {
@@ -5951,9 +6101,11 @@ var MCPClient = class {
5951
6101
  async *#openSubscription(notifications, signal, capacity) {
5952
6102
  signal.throwIfAborted();
5953
6103
  if (!isInteger(capacity) || capacity < 1) throw new MCPError("MCP subscription capacity must be a positive integer", JSONRPC_INVALID_PARAMS);
6104
+ const method = "subscriptions/listen";
6105
+ const opening = this.#refuse(method);
6106
+ if (opening !== void 0) throw opening;
5954
6107
  this.#nextId += 1;
5955
6108
  const id = this.#nextId;
5956
- const method = "subscriptions/listen";
5957
6109
  const modern = this.#version;
5958
6110
  const request = {
5959
6111
  jsonrpc: "2.0",
@@ -6001,7 +6153,16 @@ var MCPClient = class {
6001
6153
  this.#cancelSubscription(id, /* @__PURE__ */ new Error("MCP subscription closed by its consumer"));
6002
6154
  }
6003
6155
  }
6156
+ #refuse(method) {
6157
+ if (method === "server/discover") return void 0;
6158
+ if (this.#connected) return void 0;
6159
+ const inflight = this.#connecting;
6160
+ if (inflight !== void 0 && inflight.generation === this.#generation) return void 0;
6161
+ return new MCPError(`MCP client is not connected, so '${method}' was not issued`, JSONRPC_INVALID_REQUEST);
6162
+ }
6004
6163
  #request(method, params, deadline, version, options) {
6164
+ const refusal = this.#refuse(method);
6165
+ if (refusal !== void 0) return Promise.reject(refusal);
6005
6166
  this.#nextId += 1;
6006
6167
  const id = this.#nextId;
6007
6168
  const timeout = deadline;
@@ -6055,7 +6216,7 @@ var MCPClient = class {
6055
6216
  ...report === void 0 ? {} : { progress: report }
6056
6217
  });
6057
6218
  this.#transport.send(request).catch((error) => {
6058
- this.#settle(id, error instanceof Error ? error : new Error(String(error)), true);
6219
+ this.#settle(id, isError(error) ? error : new Error(String(error)), true);
6059
6220
  });
6060
6221
  });
6061
6222
  }
@@ -6143,16 +6304,20 @@ var MCPClient = class {
6143
6304
  #tool(name, descriptor) {
6144
6305
  const inputSchema = descriptor["inputSchema"];
6145
6306
  const description = descriptor["description"];
6146
- const options = {
6307
+ const title = descriptor["title"];
6308
+ const annotations = descriptor["annotations"];
6309
+ const projected = isMCPToolAnnotations(annotations) ? mcpAnnotationsToTool(annotations) : {};
6310
+ return new Tool({
6147
6311
  name,
6312
+ ...isString(title) ? { title } : {},
6313
+ ...isString(description) ? { description } : {},
6314
+ ...isRecord(inputSchema) ? { parameters: inputSchema } : {},
6315
+ ...Object.keys(projected).length === 0 ? {} : { annotations: projected },
6148
6316
  execute: this.#execute.bind(this, name)
6149
- };
6150
- if (isString(description)) options.description = description;
6151
- if (isRecord(inputSchema)) options.parameters = inputSchema;
6152
- return new Tool(options);
6317
+ });
6153
6318
  }
6154
- async #execute(name, args) {
6155
- const outcome = await this.call(name, args);
6319
+ async #execute(name, args, context) {
6320
+ const outcome = await this.call(name, args, { signal: context.signal });
6156
6321
  if (outcome.resultType !== "complete") throw new Error(`MCP tool '${name}' answered '${outcome.resultType}' and has no inline value`);
6157
6322
  return outcome.value;
6158
6323
  }
@@ -6696,6 +6861,6 @@ function createDuplexClientTransport(transport) {
6696
6861
  };
6697
6862
  }
6698
6863
  //#endregion
6699
- 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, HTTPClientTransport, 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_ANNOTATION, MCP_HEADER_MISMATCH, MCP_LOOKUP_PAGES, MCP_META_CAPABILITIES, MCP_META_CLIENT, MCP_META_SERVER, MCP_META_SUBSCRIPTION, MCP_META_VERSION, MCP_METHOD_HEADER, MCP_MISSING_CAPABILITY, MCP_MODERN_VERSION, MCP_NAME_HEADER, MCP_PARAM_PREFIX, MCP_PROTOCOL_VERSION_HEADER, MCP_SENTINEL_PREFIX, MCP_SENTINEL_SUFFIX, MCP_SESSION_HEADER, MCP_UNSUPPORTED_VERSION, MCP_WEBSOCKET_SUBPROTOCOL, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, SUPPORTED_MCP_VERSIONS, SUPPORTED_MODERN_PROTOCOL_VERSIONS, bindClient, bindServer, buildCallOutcome, buildCancelledNotification, buildDiscoverResult, buildHeaderParameters, buildHeaderProjection, buildInitializeResult, buildJSONRPCError, buildJSONRPCResult, buildMethodOptions, buildModernResult, buildProgressNotification, buildResponseError, buildSubscriptionAcknowledgement, buildSubscriptionFilter, buildSubscriptionResult, buildToolCall, buildToolDescriptors, computeMissingCapabilities, countHeaderAnnotations, createDuplexClientTransport, createMCPClient, createMCPLegacy, createMCPLegacyClientTransport, createMCPServer, decodeBoundedMessage, decodeEvent, decodeSentinel, deliverMessage, digestJSON, encodeSentinel, extractContentText, extractHeaderAnnotations, extractToolSchema, inferEra, inferRequestEra, inferRequestVersion, inferVersion, isAbsoluteURI, isBoundedJSON, isBoundedString, isElicitContent, isFieldToken, 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, isMCPHeaderPrimitive, isMCPIcon, isMCPIdentity, isMCPInputRequest, isMCPInputRequestMap, isMCPInputResponse, isMCPInputResult, isMCPLegacyResult, isMCPLegacyVersion, isMCPLoggingLevel, isMCPMetaKey, isMCPMetaObject, isMCPModernVersion, isMCPNotificationMetaObject, isMCPPaginationParams, isMCPProgress, isMCPPrompt, isMCPPromptArgument, isMCPPromptGetResult, isMCPPromptMessage, isMCPPromptPage, isMCPResource, isMCPResourceContents, isMCPResourcePage, isMCPResourceTemplate, isMCPResourceTemplatePage, isMCPResult, isMCPResultMetaObject, isMCPRoot, isMCPRootResult, isMCPSampleContent, isMCPSampleResult, isMCPServerCapabilities, isMCPStringArguments, isMCPSubscriptionFilter, isMCPSubscriptionResult, isMCPTaskDetail, isMCPTaskDetailResult, isMCPTaskNotification, isMCPTaskResult, isMCPTaskStatus, isMCPTextResource, isMCPVersion, isModernRequest, isRFC3339Date, isRFC3339DateTime, isStandardBase64, legacyInvocationToModern, legacyResultToModern, matchesResultType, matchesSubscriptionNotification, modernInvocationToLegacy, modernResultToLegacy, parseJSONRPCMessage, parseMCPInputState, parseRequestContext, readCancelledId, readEventStream, renderHeaderValue, sendStream, serializeJSON, snapshotJSON, snapshotToolResult, stampSubscriptionNotification, supportsFormElicitation, supportsTask };
6864
+ 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, HTTPClientTransport, 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_ANNOTATION, MCP_HEADER_MISMATCH, MCP_LOOKUP_PAGES, MCP_META_CAPABILITIES, MCP_META_CLIENT, MCP_META_SERVER, MCP_META_SUBSCRIPTION, MCP_META_VERSION, MCP_METHOD_HEADER, MCP_MISSING_CAPABILITY, MCP_MODERN_VERSION, MCP_NAME_HEADER, MCP_PARAM_PREFIX, MCP_PROTOCOL_VERSION_HEADER, MCP_SENTINEL_PREFIX, MCP_SENTINEL_SUFFIX, MCP_SESSION_HEADER, MCP_UNSUPPORTED_VERSION, MCP_WEBSOCKET_SUBPROTOCOL, SUPPORTED_LEGACY_PROTOCOL_VERSIONS, SUPPORTED_MCP_VERSIONS, SUPPORTED_MODERN_PROTOCOL_VERSIONS, bindClient, bindServer, buildCallOutcome, buildCancelledNotification, buildDiscoverResult, buildHeaderParameters, buildHeaderProjection, buildInitializeResult, buildJSONRPCError, buildJSONRPCResult, buildMethodOptions, buildModernResult, buildProgressNotification, buildResponseError, buildSubscriptionAcknowledgement, buildSubscriptionFilter, buildSubscriptionResult, buildToolCall, buildToolDescriptors, computeMissingCapabilities, countHeaderAnnotations, createDuplexClientTransport, createMCPClient, createMCPLegacy, createMCPLegacyClientTransport, createMCPServer, decodeBoundedMessage, decodeEvent, decodeSentinel, deliverMessage, digestJSON, encodeSentinel, extractContentText, extractHeaderAnnotations, extractToolSchema, inferEra, inferRequestEra, inferRequestVersion, inferVersion, isAbsoluteURI, isBoundedJSON, isBoundedString, isElicitContent, isFieldToken, isInitializeRequest, isJSONObject, isJSONRPCError, isJSONRPCErrorResponse, isJSONRPCId, isJSONRPCInvocation, isJSONRPCMessage, isJSONRPCNotification, isJSONRPCRequest, isJSONRPCResponse, isJSONRPCResultResponse, isMCPAnnotations, isMCPBlobResource, isMCPCallResult, isMCPClientCapabilities, isMCPCompletion, isMCPCompletionParams, isMCPCompletionReference, isMCPCompletionResult, isMCPConsumerFilter, isMCPContent, isMCPElicitFieldSchema, isMCPElicitForm, isMCPElicitRequest, isMCPElicitResult, isMCPElicitSchema, isMCPElicitURL, isMCPError, isMCPHeaderPrimitive, isMCPIcon, isMCPIdentity, isMCPInputRequest, isMCPInputRequestMap, isMCPInputResponse, isMCPInputResult, isMCPLegacyResult, isMCPLegacyVersion, isMCPLoggingLevel, isMCPMetaKey, isMCPMetaObject, isMCPModernVersion, isMCPNotificationMetaObject, isMCPPaginationParams, isMCPProgress, isMCPPrompt, isMCPPromptArgument, isMCPPromptGetResult, isMCPPromptMessage, isMCPPromptPage, isMCPResource, isMCPResourceContents, isMCPResourcePage, isMCPResourceTemplate, isMCPResourceTemplatePage, isMCPResult, isMCPResultMetaObject, isMCPRoot, isMCPRootResult, isMCPSampleContent, isMCPSampleResult, isMCPServerCapabilities, isMCPStringArguments, isMCPSubscriptionFilter, isMCPSubscriptionResult, isMCPTaskDetail, isMCPTaskDetailResult, isMCPTaskNotification, isMCPTaskResult, isMCPTaskStatus, isMCPTextResource, isMCPToolAnnotations, isMCPVersion, isModernRequest, isRFC3339Date, isRFC3339DateTime, isStandardBase64, legacyInvocationToModern, legacyResultToModern, matchesResultType, matchesSubscriptionNotification, mcpAnnotationsToTool, modernInvocationToLegacy, modernResultToLegacy, parseJSONRPCMessage, parseMCPInputState, parseRequestContext, readCancelledId, readEventStream, renderHeaderValue, sendStream, serializeJSON, snapshotJSON, snapshotToolResult, stampSubscriptionNotification, supportsFormElicitation, supportsTask, toolAnnotationsToMCP };
6700
6865
 
6701
6866
  //# sourceMappingURL=index.js.map