@fruggr/zendesk-mcp-server 3.7.0 → 3.8.0

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/index.js CHANGED
@@ -7911,6 +7911,11 @@ const ConfigSchema = object({
7911
7911
  oauthMasterSecretFile: string().min(1).optional(),
7912
7912
  /** HTTP only: the grant store URI; defaults to a file in the config dir. */
7913
7913
  oauthStore: string().url().optional(),
7914
+ /**
7915
+ * HTTP only: where the admins' instructions, prompts and skills live
7916
+ * (`file://<dir>` or `memory://`); defaults to a folder in the config dir.
7917
+ */
7918
+ customizationStore: string().url().optional(),
7914
7919
  /** HTTP only: CIMD client ids added to the consent-skip allowlist. */
7915
7920
  oauthTrustedClients: array(string().url()).default([]),
7916
7921
  /** HTTP only: whether the built-in allowlist (claude.ai, ChatGPT) applies. */
@@ -8014,6 +8019,7 @@ const CLI_OPTIONS = {
8014
8019
  "callback-port": { type: "string" },
8015
8020
  "oauth-master-secret-file": { type: "string" },
8016
8021
  "oauth-store": { type: "string" },
8022
+ "customization-store": { type: "string" },
8017
8023
  "oauth-trusted-client": {
8018
8024
  type: "string",
8019
8025
  multiple: true
@@ -8045,6 +8051,7 @@ const FIELD_BY_FLAG = /* @__PURE__ */ new Map([
8045
8051
  ["cors-origin", "corsOrigins"],
8046
8052
  ["oauth-master-secret-file", "oauthMasterSecretFile"],
8047
8053
  ["oauth-store", "oauthStore"],
8054
+ ["customization-store", "customizationStore"],
8048
8055
  ["oauth-trusted-client", "oauthTrustedClients"],
8049
8056
  ["sla-reply-thresholds", "slaReplyThresholds"],
8050
8057
  ["sla-wait-thresholds", "slaWaitThresholds"],
@@ -8112,6 +8119,7 @@ const resolveOAuthServerSettings = (cli) => ({
8112
8119
  oauthMasterSecret: requireNonEmptyEnv("OAUTH_MASTER_SECRET"),
8113
8120
  oauthMasterSecretFile: cli.oauthMasterSecretFile ?? requireNonEmptyEnv("OAUTH_MASTER_SECRET_FILE"),
8114
8121
  oauthStore: cli.oauthStore ?? requireNonEmptyEnv("OAUTH_STORE"),
8122
+ customizationStore: cli.customizationStore ?? requireNonEmptyEnv("CUSTOMIZATION_STORE"),
8115
8123
  oauthTrustedClients: [...cli.oauthTrustedClients ?? [], ...splitList(readEnv("OAUTH_TRUSTED_CLIENTS").value)],
8116
8124
  defaultTrustedClients: cli.defaultTrustedClients
8117
8125
  });
@@ -8383,7 +8391,7 @@ const buildHelpCenterInstructions = (config) => {
8383
8391
  ].join("\n");
8384
8392
  };
8385
8393
  //#endregion
8386
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
8394
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/chunk-vYduTEsO.mjs
8387
8395
  var __create = Object.create;
8388
8396
  var __defProp = Object.defineProperty;
8389
8397
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -8415,7 +8423,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
8415
8423
  enumerable: true
8416
8424
  }) : target, mod));
8417
8425
  //#endregion
8418
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/dialects-DoSzNhcb.mjs
8426
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/dialects-CDpMbily.mjs
8419
8427
  /**
8420
8428
  * Canonical `$schema` URIs per supported dialect (http + https variants, trailing-`#` stripped).
8421
8429
  */
@@ -9875,6 +9883,8 @@ const ServerResultSchema = union([
9875
9883
  ListToolsResultSchema,
9876
9884
  SubscriptionsListenResultSchema
9877
9885
  ]);
9886
+ //#endregion
9887
+ //#region node_modules/.pnpm/@modelcontextprotocol+core@2.4.0/node_modules/@modelcontextprotocol/core/dist/auth-UMQCo-ad.mjs
9878
9888
  /**
9879
9889
  * Reusable URL validation that disallows `javascript:` scheme
9880
9890
  */
@@ -10078,7 +10088,7 @@ const OAuthTokenRevocationRequestSchema = object({
10078
10088
  token_type_hint: string().optional()
10079
10089
  }).strip();
10080
10090
  //#endregion
10081
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/src-Cqbh3MYc.mjs
10091
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/src-DdvbAHrR.mjs
10082
10092
  /**
10083
10093
  * Cross-bundle `instanceof` support for the SDK error classes.
10084
10094
  *
@@ -14457,25 +14467,22 @@ const X_MCP_HEADER_KEY = "x-mcp-header";
14457
14467
  */
14458
14468
  const RFC9110_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
14459
14469
  /**
14460
- * JSON Schema `type` values the spec admits on an `x-mcp-header` property.
14461
- *
14462
- * The spec text names `integer`, `string`, `boolean` and explicitly excludes
14463
- * `number`. The published conformance referee at the pinned release ships its
14464
- * `http-custom-headers` scenario with two `type: "number"` `x-mcp-header`
14465
- * parameters and expects the client to mirror them, so `number` is accepted
14466
- * here so that the conformance gate passes; the discrepancy is tracked
14467
- * upstream. Everything else (`object`, `array`, `null`, absent) is rejected.
14470
+ * JSON Schema `type` values the spec admits on an `x-mcp-header` property:
14471
+ * `integer`, `string` and `boolean`. `number` is explicitly not permitted
14472
+ * (2026-07-28 Streamable HTTP, "Schema Extension"), so a tool that annotates a
14473
+ * `number`-typed property is invalid (the server still checks its headers).
14474
+ * Everything else (`object`, `array`, `null`, absent) is rejected too.
14468
14475
  */
14469
14476
  const PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
14470
14477
  "string",
14471
14478
  "integer",
14472
- "boolean",
14473
- "number"
14479
+ "boolean"
14474
14480
  ]);
14475
14481
  /**
14476
14482
  * Scan a tool's JSON-serialized `inputSchema` for `x-mcp-header` declarations
14477
14483
  * and validate every constraint the spec places on them. Returns either the
14478
- * collected declarations (possibly empty) or the first violated constraint.
14484
+ * collected declarations (possibly empty) or the first violated constraint
14485
+ * (with the declarations too, when `number`-typed declarations are the only fault).
14479
14486
  *
14480
14487
  * The walk descends through `properties` at any depth (the spec's "any nesting
14481
14488
  * depth" clause). The static-reachability MUST is enforced as a structural
@@ -14488,6 +14495,7 @@ const PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
14488
14495
  function scanXMcpHeaderDeclarations(inputSchema) {
14489
14496
  const declarations = [];
14490
14497
  const seenLower = /* @__PURE__ */ new Map();
14498
+ let numberFault;
14491
14499
  const visit = (node, path, reachable) => {
14492
14500
  if (node === null || typeof node !== "object") return void 0;
14493
14501
  const schema = node;
@@ -14497,7 +14505,11 @@ function scanXMcpHeaderDeclarations(inputSchema) {
14497
14505
  if (typeof raw !== "string" || raw.length === 0) return `${pathName(path)}: x-mcp-header MUST be a non-empty string`;
14498
14506
  if (!RFC9110_TOKEN.test(raw)) return `${pathName(path)}: x-mcp-header '${raw}' is not a valid RFC 9110 token (no spaces, control characters or HTTP delimiters)`;
14499
14507
  const type = typeof schema.type === "string" ? schema.type : void 0;
14500
- if (type === void 0 || !PERMITTED_X_MCP_HEADER_TYPES.has(type)) return `${pathName(path)}: x-mcp-header is only permitted on primitive-typed properties (string, integer, boolean); got ${type ?? "<none>"}`;
14508
+ if (type === void 0 || !PERMITTED_X_MCP_HEADER_TYPES.has(type)) {
14509
+ const fault$1 = `${pathName(path)}: x-mcp-header is only permitted on primitive-typed properties (string, integer, boolean); got ${type ?? "<none>"}`;
14510
+ if (type !== "number") return fault$1;
14511
+ numberFault ??= fault$1;
14512
+ }
14501
14513
  const lower = raw.toLowerCase();
14502
14514
  const prior = seenLower.get(lower);
14503
14515
  if (prior !== void 0) return `x-mcp-header '${raw}' is not case-insensitively unique (also declared as '${prior}')`;
@@ -14524,12 +14536,17 @@ function scanXMcpHeaderDeclarations(inputSchema) {
14524
14536
  }
14525
14537
  };
14526
14538
  const fault = visit(inputSchema, [], true);
14527
- return fault === void 0 ? {
14539
+ if (fault !== void 0) return {
14540
+ valid: false,
14541
+ reason: numberFault ?? fault
14542
+ };
14543
+ return numberFault === void 0 ? {
14528
14544
  valid: true,
14529
14545
  declarations
14530
14546
  } : {
14531
14547
  valid: false,
14532
- reason: fault
14548
+ reason: numberFault,
14549
+ declarations
14533
14550
  };
14534
14551
  }
14535
14552
  /**
@@ -14632,7 +14649,7 @@ function valueAtPath(root, path) {
14632
14649
  * A sentinel-carrying header whose payload is not canonical Base64 / valid
14633
14650
  * UTF-8 is rejected as invalid characters.
14634
14651
  *
14635
- * Integer-typed declarations are compared numerically (the spec's SHOULD —
14652
+ * Integer- and number-typed declarations are compared numerically (the spec's SHOULD —
14636
14653
  * `42.0` and `42` are equal); everything else is compared as decoded strings.
14637
14654
  *
14638
14655
  * Returns `undefined` when every check passes, or an
@@ -15202,22 +15219,26 @@ function standardSchemaToJsonSchema(schema, io = "input") {
15202
15219
  ...result
15203
15220
  };
15204
15221
  }
15222
+ const LOCAL_DEFINITION_REF = /^#\/(\$defs|definitions)\/([^/~%]+)$/;
15205
15223
  /**
15206
15224
  * A typeless JSON Schema root is "provably object-shaped" when either it carries object keywords
15207
15225
  * directly (`properties`/`patternProperties`/`additionalProperties`/`required`), or it is a
15208
15226
  * composition (`oneOf`/`anyOf`/`allOf`) whose every member is itself `type:'object'` or recursively
15209
- * provably object-shaped (e.g. a nested `discriminatedUnion`). `$ref` is not followed. Used to
15227
+ * provably object-shaped (e.g. a nested `discriminatedUnion`). A `$ref` is followed one hop, to an
15228
+ * entry of the root's `$defs`/`definitions` that is itself `type:'object'`. Used to
15210
15229
  * decide whether stamping `type:'object'` is safe (redundant-but-valid) versus self-contradictory.
15211
15230
  */
15212
- function isProvablyObjectShapedRoot(schema) {
15231
+ function isProvablyObjectShapedRoot(schema, root = schema) {
15213
15232
  if ("properties" in schema || "patternProperties" in schema || "additionalProperties" in schema || "required" in schema) return true;
15233
+ const ref = typeof schema.$ref === "string" && LOCAL_DEFINITION_REF.exec(schema.$ref);
15234
+ if (ref && root[ref[1]]?.[ref[2]]?.type === "object") return true;
15214
15235
  for (const key of [
15215
15236
  "oneOf",
15216
15237
  "anyOf",
15217
15238
  "allOf"
15218
15239
  ]) {
15219
15240
  const members = schema[key];
15220
- if (Array.isArray(members) && members.length > 0) return members.every((m) => m !== null && typeof m === "object" && (m.type === "object" || isProvablyObjectShapedRoot(m)));
15241
+ if (Array.isArray(members) && members.length > 0) return members.every((m) => m !== null && typeof m === "object" && (m.type === "object" || isProvablyObjectShapedRoot(m, "$id" in m ? {} : root)));
15221
15242
  }
15222
15243
  return false;
15223
15244
  }
@@ -17187,7 +17208,7 @@ function normalizeRawShapeSchema(schema) {
17187
17208
  return schema;
17188
17209
  }
17189
17210
  //#endregion
17190
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/ajvProvider-CEoC__sr.mjs
17211
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/ajvProvider-DyjnscPj.mjs
17191
17212
  var require_code$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
17192
17213
  Object.defineProperty(exports, "__esModule", { value: true });
17193
17214
  exports.regexpCode = exports.getEsmExportName = exports.getProperty = exports.safeStringify = exports.stringify = exports.strConcat = exports.addCodeArg = exports.str = exports._ = exports.nil = exports._Code = exports.Name = exports.IDENTIFIER = exports._CodeOrName = void 0;
@@ -19865,6 +19886,28 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19865
19886
  const isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
19866
19887
  /** @type {(value: string) => boolean} */
19867
19888
  const isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
19889
+ /** @type {(value: string) => boolean} */
19890
+ const isPort = RegExp.prototype.test.bind(/^\d*$/u);
19891
+ /** @type {(value: string) => boolean} */
19892
+ const isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
19893
+ /** @type {(value: string) => boolean} */
19894
+ const isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
19895
+ /** @type {(value: string) => boolean} */
19896
+ const isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
19897
+ /** @type {(value: string) => boolean} */
19898
+ const isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
19899
+ /** @type {(value: string) => boolean} */
19900
+ const isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
19901
+ const BYTE_HEX = new Array(256);
19902
+ {
19903
+ const HEX_DIGITS = "0123456789ABCDEF";
19904
+ for (let i = 0; i < 256; i++) BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
19905
+ }
19906
+ function percentEncodeNonAscii(cp) {
19907
+ if (cp < 2048) return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
19908
+ if (cp < 65536) return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
19909
+ return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
19910
+ }
19868
19911
  /**
19869
19912
  * @param {Array<string>} input
19870
19913
  * @returns {string}
@@ -19887,119 +19930,162 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
19887
19930
  }
19888
19931
  return acc;
19889
19932
  }
19890
- /**
19891
- * @typedef {Object} GetIPV6Result
19892
- * @property {boolean} error - Indicates if there was an error parsing the IPv6 address.
19893
- * @property {string} address - The parsed IPv6 address.
19894
- * @property {string} [zone] - The zone identifier, if present.
19895
- */
19933
+ /** @type {(value: string) => boolean} */
19934
+ const isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
19935
+ /** @type {(value: string) => boolean} */
19936
+ const isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
19937
+ /** @type {(value: string) => boolean} */
19938
+ const isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
19896
19939
  /**
19897
19940
  * @param {string} value
19898
19941
  * @returns {boolean}
19899
19942
  */
19900
19943
  const nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
19901
19944
  /**
19902
- * @param {Array<string>} buffer
19945
+ * @param {string} zone
19903
19946
  * @returns {boolean}
19904
19947
  */
19905
- function consumeIsZone(buffer) {
19906
- buffer.length = 0;
19948
+ function isZoneIdentifier(zone) {
19949
+ if (zone.length === 0) return false;
19950
+ for (let i = 0; i < zone.length; i++) {
19951
+ if (isZoneCharacter(zone[i])) continue;
19952
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
19953
+ i += 2;
19954
+ continue;
19955
+ }
19956
+ return false;
19957
+ }
19907
19958
  return true;
19908
19959
  }
19909
19960
  /**
19910
- * @param {Array<string>} buffer
19911
- * @param {Array<string>} address
19912
- * @param {GetIPV6Result} output
19913
- * @returns {boolean}
19961
+ * Compresses the longest run of zero hextets to "::" per RFC 5952. A run of a
19962
+ * single zero hextet is left uncompressed. On ties the leftmost run wins.
19963
+ *
19964
+ * @param {string[]} hextets
19965
+ * @returns {string}
19914
19966
  */
19915
- function consumeHextets(buffer, address, output) {
19916
- if (buffer.length) {
19917
- const hex = stringArrayToHexStripped(buffer);
19918
- if (hex !== "") address.push(hex);
19919
- else {
19920
- output.error = true;
19921
- return false;
19967
+ function compressIPv6ZeroRun(hextets) {
19968
+ let bestStart = -1;
19969
+ let bestLength = 0;
19970
+ let runStart = -1;
19971
+ let runLength = 0;
19972
+ for (let i = 0; i < hextets.length; i++) if (hextets[i] === "0") {
19973
+ if (runStart === -1) runStart = i;
19974
+ runLength++;
19975
+ if (runLength > bestLength) {
19976
+ bestLength = runLength;
19977
+ bestStart = runStart;
19922
19978
  }
19923
- buffer.length = 0;
19979
+ } else {
19980
+ runStart = -1;
19981
+ runLength = 0;
19924
19982
  }
19925
- return true;
19983
+ if (bestLength < 2) return hextets.join(":");
19984
+ const head = hextets.slice(0, bestStart).join(":");
19985
+ const tail = hextets.slice(bestStart + bestLength).join(":");
19986
+ return head + "::" + tail;
19926
19987
  }
19927
19988
  /**
19989
+ * Validates an IPv6 address against the alternatives in RFC 3986 section
19990
+ * 3.2.2 and returns the same address with leading hextet zeroes removed.
19991
+ * An embedded IPv4 address counts as two hextets and is only valid at the end.
19992
+ *
19928
19993
  * @param {string} input
19929
- * @returns {GetIPV6Result}
19930
- */
19931
- function getIPV6(input) {
19932
- let tokenCount = 0;
19933
- const output = {
19934
- error: false,
19935
- address: "",
19936
- zone: ""
19937
- };
19938
- /** @type {Array<string>} */
19939
- const address = [];
19940
- /** @type {Array<string>} */
19941
- const buffer = [];
19942
- let endipv6Encountered = false;
19943
- let endIpv6 = false;
19944
- let consume = consumeHextets;
19945
- for (let i = 0; i < input.length; i++) {
19946
- const cursor = input[i];
19947
- if (cursor === "[" || cursor === "]") continue;
19948
- if (cursor === ":") {
19949
- if (endipv6Encountered === true) endIpv6 = true;
19950
- if (!consume(buffer, address, output)) break;
19951
- if (++tokenCount > 7) {
19952
- output.error = true;
19953
- break;
19954
- }
19955
- if (i > 0 && input[i - 1] === ":") endipv6Encountered = true;
19956
- address.push(":");
19957
- continue;
19958
- } else if (cursor === "%") {
19959
- if (!consume(buffer, address, output)) break;
19960
- consume = consumeIsZone;
19961
- } else {
19962
- buffer.push(cursor);
19994
+ * @returns {string|undefined}
19995
+ */
19996
+ function normalizeIPv6Address(input) {
19997
+ const compression = input.indexOf("::");
19998
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
19999
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
20000
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
20001
+ if (compression !== -1) {
20002
+ if (left.length === 1 && left[0] === "") left.length = 0;
20003
+ if (right.length === 1 && right[0] === "") right.length = 0;
20004
+ }
20005
+ const parts = left.concat(right);
20006
+ let hextetCount = 0;
20007
+ for (let i = 0; i < parts.length; i++) {
20008
+ const part = parts[i];
20009
+ if (part === "") return void 0;
20010
+ if (part.indexOf(".") !== -1) {
20011
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
20012
+ hextetCount += 2;
19963
20013
  continue;
19964
20014
  }
20015
+ if (!isHextet(part)) return void 0;
20016
+ parts[i] = parseInt(part, 16).toString(16);
20017
+ hextetCount++;
19965
20018
  }
19966
- if (buffer.length) if (consume === consumeIsZone) output.zone = buffer.join("");
19967
- else if (endIpv6) address.push(buffer.join(""));
19968
- else address.push(stringArrayToHexStripped(buffer));
19969
- output.address = address.join("");
19970
- return output;
20019
+ if (compression === -1) {
20020
+ if (hextetCount !== 8) return void 0;
20021
+ return compressIPv6ZeroRun(parts);
20022
+ }
20023
+ if (hextetCount >= 8) return void 0;
20024
+ const expanded = parts.slice(0, left.length);
20025
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
20026
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
20027
+ return compressIPv6ZeroRun(expanded);
19971
20028
  }
19972
20029
  /**
19973
20030
  * @typedef {Object} NormalizeIPv6Result
19974
20031
  * @property {string} host - The normalized host.
19975
20032
  * @property {string} [escapedHost] - The escaped host.
19976
20033
  * @property {boolean} isIPV6 - Indicates if the host is an IPv6 address.
20034
+ * @property {boolean} [isIPVFuture] - Indicates if the host is an IPvFuture literal.
20035
+ * @property {boolean} [error] - Indicates if a bracketed IP literal is malformed.
19977
20036
  */
19978
20037
  /**
20038
+ * Validates and normalizes a bracketed IP literal. Raw zone separators remain
20039
+ * accepted for backwards compatibility, while encoded separators and zone
20040
+ * contents follow RFC 6874.
20041
+ *
19979
20042
  * @param {string} host
19980
20043
  * @returns {NormalizeIPv6Result}
19981
20044
  */
19982
20045
  function normalizeIPv6(host) {
19983
- if (findToken(host, ":") < 2) return {
20046
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
20047
+ if ((host[0] === "[" || host[host.length - 1] === "]") && !bracketed) return {
19984
20048
  host,
19985
- isIPV6: false
20049
+ isIPV6: false,
20050
+ error: true
19986
20051
  };
19987
- const ipv6 = getIPV6(host);
19988
- if (!ipv6.error) {
19989
- let newHost = ipv6.address;
19990
- let escapedHost = ipv6.address;
19991
- if (ipv6.zone) {
19992
- newHost += "%" + ipv6.zone;
19993
- escapedHost += "%25" + ipv6.zone;
19994
- }
20052
+ let input = bracketed ? host.slice(1, -1) : host;
20053
+ if (bracketed && isIPvFuture(input)) {
20054
+ input = input.toLowerCase();
19995
20055
  return {
19996
- host: newHost,
19997
- isIPV6: true,
19998
- escapedHost
20056
+ host: `[${input}]`,
20057
+ escapedHost: input,
20058
+ isIPV6: false,
20059
+ isIPVFuture: true
20060
+ };
20061
+ }
20062
+ if (findToken(input, ":") < 2) return {
20063
+ host,
20064
+ isIPV6: false,
20065
+ error: bracketed
20066
+ };
20067
+ let zoneIdentifier = "";
20068
+ const zoneSeparator = input.indexOf("%");
20069
+ if (zoneSeparator !== -1) {
20070
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
20071
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
20072
+ if (!isZoneIdentifier(zoneIdentifier)) return {
20073
+ host,
20074
+ isIPV6: false,
20075
+ error: true
19999
20076
  };
20000
- } else return {
20077
+ input = input.slice(0, zoneSeparator);
20078
+ }
20079
+ const address = normalizeIPv6Address(input);
20080
+ if (address === void 0) return {
20001
20081
  host,
20002
- isIPV6: false
20082
+ isIPV6: false,
20083
+ error: true
20084
+ };
20085
+ return {
20086
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
20087
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
20088
+ isIPV6: true
20003
20089
  };
20004
20090
  }
20005
20091
  /**
@@ -20087,19 +20173,264 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20087
20173
  return output.join("");
20088
20174
  }
20089
20175
  /**
20090
- * @param {import('../types/index').URIComponent} component
20091
- * @param {boolean} esc
20092
- * @returns {import('../types/index').URIComponent}
20176
+ * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
20177
+ * After the URI regex parses, these characters cannot be literal in the host
20178
+ * field, so any that appear after decoding came from percent-encoding and
20179
+ * must be restored to prevent authority structure changes.
20180
+ *
20181
+ * @param {string} host
20182
+ * @param {boolean} isIP - true for IPv4/IPv6 hosts (skip colon re-escaping)
20183
+ * @returns {string}
20093
20184
  */
20094
- function normalizeComponentEncoding(component, esc) {
20095
- const func = esc !== true ? escape : unescape;
20096
- if (component.scheme !== void 0) component.scheme = func(component.scheme);
20097
- if (component.userinfo !== void 0) component.userinfo = func(component.userinfo);
20098
- if (component.host !== void 0) component.host = func(component.host);
20099
- if (component.path !== void 0) component.path = func(component.path);
20100
- if (component.query !== void 0) component.query = func(component.query);
20101
- if (component.fragment !== void 0) component.fragment = func(component.fragment);
20102
- return component;
20185
+ const HOST_DELIMS = {
20186
+ "@": "%40",
20187
+ "/": "%2F",
20188
+ "?": "%3F",
20189
+ "#": "%23",
20190
+ ":": "%3A"
20191
+ };
20192
+ const HOST_DELIM_RE = /[@/?#:]/g;
20193
+ const HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
20194
+ function reescapeHostDelimiters(host, isIP) {
20195
+ const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
20196
+ re.lastIndex = 0;
20197
+ return host.replace(re, (ch) => HOST_DELIMS[ch]);
20198
+ }
20199
+ /**
20200
+ * Normalizes percent escapes and optionally decodes only unreserved ASCII bytes.
20201
+ * Reserved delimiters such as `%2F` stay escaped; `%2E` is unreserved.
20202
+ *
20203
+ * @param {string} input
20204
+ * @param {boolean} [decodeUnreserved=false]
20205
+ * @returns {string}
20206
+ */
20207
+ function normalizePercentEncoding(input, decodeUnreserved = false) {
20208
+ if (input.indexOf("%") === -1) return input;
20209
+ let output = "";
20210
+ for (let i = 0; i < input.length; i++) {
20211
+ if (input[i] === "%" && i + 2 < input.length) {
20212
+ const hex = input.slice(i + 1, i + 3);
20213
+ if (isHexPair(hex)) {
20214
+ const normalizedHex = hex.toUpperCase();
20215
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
20216
+ if (decodeUnreserved && isUnreserved(decoded)) output += decoded;
20217
+ else output += "%" + normalizedHex;
20218
+ i += 2;
20219
+ continue;
20220
+ }
20221
+ }
20222
+ output += input[i];
20223
+ }
20224
+ return output;
20225
+ }
20226
+ /**
20227
+ * Normalizes path data without turning reserved escapes into live path syntax.
20228
+ * Valid escapes are uppercased, raw unsafe characters are escaped, and only
20229
+ * unreserved bytes that are not `.` are decoded.
20230
+ *
20231
+ * @param {string} input
20232
+ * @returns {string}
20233
+ */
20234
+ function normalizePathEncoding(input) {
20235
+ let output = "";
20236
+ for (let i = 0; i < input.length; i++) {
20237
+ const ch = input[i];
20238
+ if (ch === "%" && i + 2 < input.length) {
20239
+ const hex = input.slice(i + 1, i + 3);
20240
+ if (isHexPair(hex)) {
20241
+ const normalizedHex = hex.toUpperCase();
20242
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
20243
+ if (decoded !== "." && isUnreserved(decoded)) output += decoded;
20244
+ else output += "%" + normalizedHex;
20245
+ i += 2;
20246
+ continue;
20247
+ }
20248
+ }
20249
+ if (isPathCharacter(ch)) output += ch;
20250
+ else {
20251
+ const code = input.charCodeAt(i);
20252
+ if (code < 128) output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
20253
+ else if (code < 55296 || code > 57343) output += percentEncodeNonAscii(code);
20254
+ else if (code <= 56319 && i + 1 < input.length) {
20255
+ const low = input.charCodeAt(i + 1);
20256
+ if (low >= 56320 && low <= 57343) {
20257
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
20258
+ i++;
20259
+ } else output += percentEncodeNonAscii(65533);
20260
+ } else output += percentEncodeNonAscii(65533);
20261
+ }
20262
+ }
20263
+ return output;
20264
+ }
20265
+ /**
20266
+ * Serializes a path without rewriting reserved data. Raw RFC 3986 path
20267
+ * characters remain literal, valid escapes are preserved and uppercased, and
20268
+ * everything else is UTF-8 percent-encoded. In a path-noscheme, a colon in the
20269
+ * first segment must be escaped so the result cannot be parsed as a scheme.
20270
+ *
20271
+ * @param {string} input
20272
+ * @param {boolean} [pathNoScheme=false]
20273
+ * @returns {string}
20274
+ */
20275
+ function serializePathEncoding(input, pathNoScheme = false) {
20276
+ let output = "";
20277
+ let firstSegment = pathNoScheme && input[0] !== "/";
20278
+ for (let i = 0; i < input.length; i++) {
20279
+ const ch = input[i];
20280
+ if (ch === "%" && i + 2 < input.length) {
20281
+ const hex = input.slice(i + 1, i + 3);
20282
+ if (isHexPair(hex)) {
20283
+ output += "%" + hex.toUpperCase();
20284
+ i += 2;
20285
+ continue;
20286
+ }
20287
+ }
20288
+ if (ch === "/") firstSegment = false;
20289
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) output += ch;
20290
+ else {
20291
+ const code = input.charCodeAt(i);
20292
+ if (code < 128) output += BYTE_HEX[code];
20293
+ else if (code < 55296 || code > 57343) output += percentEncodeNonAscii(code);
20294
+ else if (code <= 56319 && i + 1 < input.length) {
20295
+ const low = input.charCodeAt(i + 1);
20296
+ if (low >= 56320 && low <= 57343) {
20297
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
20298
+ i++;
20299
+ } else output += percentEncodeNonAscii(65533);
20300
+ } else output += percentEncodeNonAscii(65533);
20301
+ }
20302
+ }
20303
+ return output;
20304
+ }
20305
+ /**
20306
+ * Percent-encodes a URI component using its RFC 3986 literal character set.
20307
+ * Existing valid escapes are preserved and normalized to uppercase hex.
20308
+ *
20309
+ * @param {string} input
20310
+ * @param {(value: string) => boolean} isAllowed
20311
+ * @returns {string}
20312
+ */
20313
+ function encodeComponent(input, isAllowed) {
20314
+ let output = "";
20315
+ for (let i = 0; i < input.length; i++) {
20316
+ const ch = input[i];
20317
+ if (ch === "%" && i + 2 < input.length) {
20318
+ const hex = input.slice(i + 1, i + 3);
20319
+ if (isHexPair(hex)) {
20320
+ output += "%" + hex.toUpperCase();
20321
+ i += 2;
20322
+ continue;
20323
+ }
20324
+ }
20325
+ if (isAllowed(ch)) output += ch;
20326
+ else {
20327
+ const code = input.charCodeAt(i);
20328
+ if (code < 128) output += BYTE_HEX[code];
20329
+ else if (code < 55296 || code > 57343) output += percentEncodeNonAscii(code);
20330
+ else if (code <= 56319 && i + 1 < input.length) {
20331
+ const low = input.charCodeAt(i + 1);
20332
+ if (low >= 56320 && low <= 57343) {
20333
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
20334
+ i++;
20335
+ } else output += percentEncodeNonAscii(65533);
20336
+ } else output += percentEncodeNonAscii(65533);
20337
+ }
20338
+ }
20339
+ return output;
20340
+ }
20341
+ /**
20342
+ * Encodes userinfo while preserving its RFC 3986 §3.2.1 literal characters.
20343
+ * In particular, authority delimiters such as `@`, `/`, `?`, and `#` are data.
20344
+ *
20345
+ * @param {string} input
20346
+ * @returns {string}
20347
+ */
20348
+ function encodeUserinfo(input) {
20349
+ return encodeComponent(input, isUserinfoCharacter);
20350
+ }
20351
+ /**
20352
+ * Encodes query data using the RFC 3986 §3.4 grammar. A literal `#` must be
20353
+ * escaped because it would otherwise begin the fragment component.
20354
+ *
20355
+ * @param {string} input
20356
+ * @returns {string}
20357
+ */
20358
+ function encodeQuery(input) {
20359
+ return encodeComponent(input, isQueryFragmentCharacter);
20360
+ }
20361
+ /**
20362
+ * Encodes fragment data using the RFC 3986 §3.5 grammar.
20363
+ *
20364
+ * @param {string} input
20365
+ * @returns {string}
20366
+ */
20367
+ function encodeFragment(input) {
20368
+ return encodeComponent(input, isQueryFragmentCharacter);
20369
+ }
20370
+ function isEscapeSafe(cp) {
20371
+ return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
20372
+ }
20373
+ /**
20374
+ * Normalizes the percent-encoding of a query or fragment component.
20375
+ *
20376
+ * Like `normalizePathEncoding`, but uses the query/fragment character set
20377
+ * (which additionally allows `?`) and decodes `.` since it has no dot-segment
20378
+ * meaning outside of a path.
20379
+ *
20380
+ * @param {string} input
20381
+ * @returns {string}
20382
+ */
20383
+ function normalizeQueryFragmentEncoding(input) {
20384
+ let output = "";
20385
+ for (let i = 0; i < input.length; i++) {
20386
+ const ch = input[i];
20387
+ if (ch === "%" && i + 2 < input.length) {
20388
+ const hex = input.slice(i + 1, i + 3);
20389
+ if (isHexPair(hex)) {
20390
+ const normalizedHex = hex.toUpperCase();
20391
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
20392
+ if (isUnreserved(decoded)) output += decoded;
20393
+ else output += "%" + normalizedHex;
20394
+ i += 2;
20395
+ continue;
20396
+ }
20397
+ }
20398
+ if (isQueryFragmentCharacter(ch)) output += ch;
20399
+ else {
20400
+ const code = input.charCodeAt(i);
20401
+ if (code < 128) output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
20402
+ else if (code < 55296 || code > 57343) output += percentEncodeNonAscii(code);
20403
+ else if (code <= 56319 && i + 1 < input.length) {
20404
+ const low = input.charCodeAt(i + 1);
20405
+ if (low >= 56320 && low <= 57343) {
20406
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
20407
+ i++;
20408
+ } else output += percentEncodeNonAscii(65533);
20409
+ } else output += percentEncodeNonAscii(65533);
20410
+ }
20411
+ }
20412
+ return output;
20413
+ }
20414
+ /**
20415
+ * Escapes a component while preserving existing valid percent escapes.
20416
+ *
20417
+ * @param {string} input
20418
+ * @returns {string}
20419
+ */
20420
+ function escapePreservingEscapes(input) {
20421
+ let output = "";
20422
+ for (let i = 0; i < input.length; i++) {
20423
+ if (input[i] === "%" && i + 2 < input.length) {
20424
+ const hex = input.slice(i + 1, i + 3);
20425
+ if (isHexPair(hex)) {
20426
+ output += "%" + hex.toUpperCase();
20427
+ i += 2;
20428
+ continue;
20429
+ }
20430
+ }
20431
+ output += escape(input[i]);
20432
+ }
20433
+ return output;
20103
20434
  }
20104
20435
  /**
20105
20436
  * @param {import('../types/index').URIComponent} component
@@ -20108,28 +20439,42 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20108
20439
  function recomposeAuthority(component) {
20109
20440
  const uriTokens = [];
20110
20441
  if (component.userinfo !== void 0) {
20111
- uriTokens.push(component.userinfo);
20442
+ uriTokens.push(encodeUserinfo(component.userinfo));
20112
20443
  uriTokens.push("@");
20113
20444
  }
20114
20445
  if (component.host !== void 0) {
20115
- let host = unescape(component.host);
20446
+ let host = component.host;
20116
20447
  if (!isIPv4(host)) {
20117
- const ipV6res = normalizeIPv6(host);
20118
- if (ipV6res.isIPV6 === true) host = `[${ipV6res.escapedHost}]`;
20119
- else host = component.host;
20448
+ let ipV6res = normalizeIPv6(host);
20449
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
20450
+ host = normalizePercentEncoding(host, true);
20451
+ ipV6res = normalizeIPv6(host);
20452
+ }
20453
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) host = `[${ipV6res.escapedHost}]`;
20454
+ else host = reescapeHostDelimiters(host, false);
20120
20455
  }
20121
20456
  uriTokens.push(host);
20122
20457
  }
20123
20458
  if (typeof component.port === "number" || typeof component.port === "string") {
20459
+ const port = String(component.port);
20460
+ if (!isPort(port)) throw new TypeError("URI port is malformed.");
20124
20461
  uriTokens.push(":");
20125
- uriTokens.push(String(component.port));
20462
+ uriTokens.push(port);
20126
20463
  }
20127
20464
  return uriTokens.length ? uriTokens.join("") : void 0;
20128
20465
  }
20129
20466
  module.exports = {
20130
20467
  nonSimpleDomain,
20131
20468
  recomposeAuthority,
20132
- normalizeComponentEncoding,
20469
+ reescapeHostDelimiters,
20470
+ normalizePercentEncoding,
20471
+ normalizePathEncoding,
20472
+ serializePathEncoding,
20473
+ normalizeQueryFragmentEncoding,
20474
+ encodeUserinfo,
20475
+ encodeQuery,
20476
+ encodeFragment,
20477
+ escapePreservingEscapes,
20133
20478
  removeDotSegments,
20134
20479
  isIPv4,
20135
20480
  isUUID,
@@ -20139,7 +20484,7 @@ var require_utils = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20139
20484
  }));
20140
20485
  var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20141
20486
  const { isUUID } = require_utils();
20142
- const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
20487
+ const URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
20143
20488
  const supportedSchemeNames = [
20144
20489
  "http",
20145
20490
  "https",
@@ -20210,9 +20555,10 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20210
20555
  wsComponent.secure = void 0;
20211
20556
  }
20212
20557
  if (wsComponent.resourceName) {
20213
- const [path, query] = wsComponent.resourceName.split("?");
20558
+ const queryIndex = wsComponent.resourceName.indexOf("?");
20559
+ const path = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
20214
20560
  wsComponent.path = path && path !== "/" ? path : void 0;
20215
- wsComponent.query = query;
20561
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
20216
20562
  wsComponent.resourceName = void 0;
20217
20563
  }
20218
20564
  wsComponent.fragment = void 0;
@@ -20225,7 +20571,7 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20225
20571
  return urnComponent;
20226
20572
  }
20227
20573
  const matches = urnComponent.path.match(URN_REG);
20228
- if (matches) {
20574
+ if (matches && matches[0] === urnComponent.path) {
20229
20575
  const scheme = options.scheme || urnComponent.scheme || "urn";
20230
20576
  urnComponent.nid = matches[1].toLowerCase();
20231
20577
  urnComponent.nss = matches[2];
@@ -20319,8 +20665,19 @@ var require_schemes = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20319
20665
  };
20320
20666
  }));
20321
20667
  var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20322
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
20668
+ const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
20323
20669
  const { SCHEMES, getSchemeHandler } = require_schemes();
20670
+ const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
20671
+ const MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
20672
+ /**
20673
+ * @param {string} scheme
20674
+ * @returns {string}
20675
+ */
20676
+ function decodeValidScheme(scheme) {
20677
+ const decodedScheme = unescape(String(scheme));
20678
+ if (!VALID_SCHEME.test(decodedScheme)) throw new TypeError(MALFORMED_SCHEME_ERROR);
20679
+ return decodedScheme;
20680
+ }
20324
20681
  /**
20325
20682
  * @template {import('./types/index').URIComponent|string} T
20326
20683
  * @param {T} uri
@@ -20328,7 +20685,7 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20328
20685
  * @returns {T}
20329
20686
  */
20330
20687
  function normalize(uri, options) {
20331
- if (typeof uri === "string") uri = serialize(parse(uri, options), options);
20688
+ if (typeof uri === "string") uri = normalizeString(uri, options);
20332
20689
  else if (typeof uri === "object") uri = parse(serialize(uri, options), options);
20333
20690
  return uri;
20334
20691
  }
@@ -20340,7 +20697,16 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20340
20697
  */
20341
20698
  function resolve(baseURI, relativeURI, options) {
20342
20699
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
20343
- const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
20700
+ const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed, malformedPercentEncoding: baseMalformedPercentEncoding, malformedSchemeSpecific: baseMalformedSchemeSpecific, malformedHost: baseMalformedHost, malformedScheme: baseMalformedScheme } = parseWithStatus(baseURI, schemelessOptions);
20701
+ const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed, malformedPercentEncoding: relativeMalformedPercentEncoding, malformedSchemeSpecific: relativeMalformedSchemeSpecific, malformedHost: relativeMalformedHost, malformedScheme: relativeMalformedScheme } = parseWithStatus(relativeURI, schemelessOptions);
20702
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
20703
+ const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
20704
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
20705
+ const resolvedHost = resolved.host;
20706
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
20707
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
20708
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !/\P{ASCII}/u.test(resolvedHost);
20709
+ if (resolved.error && !encodedASCIIHost) throw new Error(resolved.error);
20344
20710
  schemelessOptions.skipEscape = true;
20345
20711
  return serialize(resolved, schemelessOptions);
20346
20712
  }
@@ -20404,27 +20770,9 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20404
20770
  * @returns {boolean}
20405
20771
  */
20406
20772
  function equal(uriA, uriB, options) {
20407
- if (typeof uriA === "string") {
20408
- uriA = unescape(uriA);
20409
- uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), {
20410
- ...options,
20411
- skipEscape: true
20412
- });
20413
- } else if (typeof uriA === "object") uriA = serialize(normalizeComponentEncoding(uriA, true), {
20414
- ...options,
20415
- skipEscape: true
20416
- });
20417
- if (typeof uriB === "string") {
20418
- uriB = unescape(uriB);
20419
- uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), {
20420
- ...options,
20421
- skipEscape: true
20422
- });
20423
- } else if (typeof uriB === "object") uriB = serialize(normalizeComponentEncoding(uriB, true), {
20424
- ...options,
20425
- skipEscape: true
20426
- });
20427
- return uriA.toLowerCase() === uriB.toLowerCase();
20773
+ const normalizedA = normalizeComparableURI(uriA, options);
20774
+ const normalizedB = normalizeComparableURI(uriB, options);
20775
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
20428
20776
  }
20429
20777
  /**
20430
20778
  * @param {Readonly<import('./types/index').URIComponent>} cmpts
@@ -20450,13 +20798,17 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20450
20798
  };
20451
20799
  const options = Object.assign({}, opts);
20452
20800
  const uriTokens = [];
20801
+ if (component.scheme) component.scheme = decodeValidScheme(component.scheme);
20453
20802
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
20454
20803
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
20455
- if (component.path !== void 0) if (!options.skipEscape) {
20456
- component.path = escape(component.path);
20457
- if (component.scheme !== void 0) component.path = component.path.split("%3A").join(":");
20458
- } else component.path = unescape(component.path);
20459
- if (options.reference !== "suffix" && component.scheme) uriTokens.push(component.scheme, ":");
20804
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
20805
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
20806
+ if (component.path !== void 0) if (!options.skipEscape) component.path = serializePathEncoding(component.path, pathNoScheme);
20807
+ else component.path = normalizePercentEncoding(component.path);
20808
+ if (options.reference !== "suffix" && component.scheme) {
20809
+ component.scheme = decodeValidScheme(component.scheme);
20810
+ uriTokens.push(component.scheme, ":");
20811
+ }
20460
20812
  const authority = recomposeAuthority(component);
20461
20813
  if (authority !== void 0) {
20462
20814
  if (options.reference !== "suffix") uriTokens.push("//");
@@ -20466,20 +20818,84 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20466
20818
  if (component.path !== void 0) {
20467
20819
  let s = component.path;
20468
20820
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) s = removeDotSegments(s);
20821
+ if (pathNoScheme) s = serializePathEncoding(s, true);
20469
20822
  if (authority === void 0 && s[0] === "/" && s[1] === "/") s = "/%2F" + s.slice(2);
20470
20823
  uriTokens.push(s);
20471
20824
  }
20472
- if (component.query !== void 0) uriTokens.push("?", component.query);
20473
- if (component.fragment !== void 0) uriTokens.push("#", component.fragment);
20825
+ if (component.query !== void 0) uriTokens.push("?", encodeQuery(component.query));
20826
+ if (component.fragment !== void 0) uriTokens.push("#", encodeFragment(component.fragment));
20474
20827
  return uriTokens.join("");
20475
20828
  }
20476
20829
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
20830
+ const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
20831
+ const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
20832
+ /**
20833
+ * @param {import('./types/index').URIComponent} parsed
20834
+ * @param {RegExpMatchArray} matches
20835
+ * @returns {string|undefined}
20836
+ */
20837
+ function getParseError(parsed, matches) {
20838
+ if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") return "URI path must start with \"/\" when authority is present.";
20839
+ if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) return "URI port is malformed.";
20840
+ }
20841
+ /**
20842
+ * Checks percent syntax without decoding the represented octets. RFC 3986
20843
+ * percent-encoding is byte-oriented, so sequences such as `%FF` are valid even
20844
+ * though they are not independently valid UTF-8.
20845
+ *
20846
+ * @param {string|undefined} component
20847
+ * @returns {boolean}
20848
+ */
20849
+ function hasMalformedPercentEncoding(component) {
20850
+ if (component === void 0) return false;
20851
+ let percent = component.indexOf("%");
20852
+ while (percent !== -1) {
20853
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) return true;
20854
+ percent = component.indexOf("%", percent + 3);
20855
+ }
20856
+ return false;
20857
+ }
20858
+ /**
20859
+ * Whether the host is a bracketed IP literal (RFC 3986 `IP-literal`).
20860
+ * An unterminated `[` is not a literal, so it must still be validated as a
20861
+ * reg-name instead of being waved through as an IP.
20862
+ *
20863
+ * @param {string} host
20864
+ * @returns {boolean}
20865
+ */
20866
+ function isIPLiteral(host) {
20867
+ return host[0] === "[" && host[host.length - 1] === "]";
20868
+ }
20869
+ /**
20870
+ * @param {RegExpMatchArray} matches
20871
+ * @returns {boolean}
20872
+ */
20873
+ function hasMalformedComponentPercentEncoding(matches) {
20874
+ const host = matches[4];
20875
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
20876
+ }
20877
+ /**
20878
+ * @param {import('./types/index').URIComponent} parsed
20879
+ * @param {import('./types/index').Options} options
20880
+ * @param {{ domainHost?: boolean, unicodeSupport?: boolean }|undefined} schemeHandler
20881
+ * @param {boolean} isIP
20882
+ * @returns {boolean} whether host conversion failed
20883
+ */
20884
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
20885
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) try {
20886
+ parsed.host = new URL("http://" + parsed.host).hostname;
20887
+ } catch (e) {
20888
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
20889
+ return true;
20890
+ }
20891
+ return false;
20892
+ }
20477
20893
  /**
20478
20894
  * @param {string} uri
20479
20895
  * @param {import('./types/index').Options} [opts]
20480
- * @returns
20896
+ * @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
20481
20897
  */
20482
- function parse(uri, opts) {
20898
+ function parseWithStatus(uri, opts) {
20483
20899
  const options = Object.assign({}, opts);
20484
20900
  /** @type {import('./types/index').URIComponent} */
20485
20901
  const parsed = {
@@ -20491,9 +20907,34 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20491
20907
  query: void 0,
20492
20908
  fragment: void 0
20493
20909
  };
20910
+ let malformedAuthorityOrPort = false;
20911
+ let malformedPercentEncoding = false;
20912
+ let malformedSchemeSpecific = false;
20913
+ let malformedHost = false;
20914
+ let malformedIPLiteral = false;
20915
+ let malformedScheme = false;
20494
20916
  let isIP = false;
20495
20917
  if (options.reference === "suffix") if (options.scheme) uri = options.scheme + ":" + uri;
20496
20918
  else uri = "//" + uri;
20919
+ const authorityMatch = uri.match(AUTHORITY_PREFIX);
20920
+ if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
20921
+ parsed.error = "URI authority must not contain a literal backslash.";
20922
+ malformedAuthorityOrPort = true;
20923
+ }
20924
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
20925
+ if (introducerMatch !== null) {
20926
+ const region = introducerMatch[1];
20927
+ const normalizedRegion = region.replace(/[\t\n\r]/g, "");
20928
+ if (normalizedRegion.length >= 2) {
20929
+ if (normalizedRegion.slice(0, 2) !== "//") {
20930
+ parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
20931
+ malformedAuthorityOrPort = true;
20932
+ } else if (region.length !== normalizedRegion.length) {
20933
+ parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
20934
+ malformedAuthorityOrPort = true;
20935
+ }
20936
+ }
20937
+ }
20497
20938
  const matches = uri.match(URI_PARSE);
20498
20939
  if (matches) {
20499
20940
  parsed.scheme = matches[1];
@@ -20503,11 +20944,33 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20503
20944
  parsed.path = matches[6] || "";
20504
20945
  parsed.query = matches[7];
20505
20946
  parsed.fragment = matches[8];
20947
+ if (parsed.scheme !== void 0) {
20948
+ const decodedScheme = unescape(parsed.scheme);
20949
+ if (VALID_SCHEME.test(decodedScheme)) parsed.scheme = decodedScheme.toLowerCase();
20950
+ else {
20951
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
20952
+ malformedScheme = true;
20953
+ }
20954
+ }
20955
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
20956
+ if (malformedPercentEncoding) parsed.error = parsed.error || "URI contains malformed percent-encoding.";
20506
20957
  if (isNaN(parsed.port)) parsed.port = matches[5];
20958
+ const parseError = getParseError(parsed, matches);
20959
+ if (parseError !== void 0) {
20960
+ parsed.error = parsed.error || parseError;
20961
+ malformedAuthorityOrPort = true;
20962
+ }
20507
20963
  if (parsed.host) if (isIPv4(parsed.host) === false) {
20964
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
20965
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
20508
20966
  const ipv6result = normalizeIPv6(parsed.host);
20509
- parsed.host = ipv6result.host.toLowerCase();
20510
- isIP = ipv6result.isIPV6;
20967
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
20968
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
20969
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
20970
+ if (malformedIPLiteral) {
20971
+ parsed.error = parsed.error || "URI host is malformed.";
20972
+ malformedAuthorityOrPort = true;
20973
+ }
20511
20974
  } else isIP = true;
20512
20975
  if (parsed.scheme === void 0 && parsed.userinfo === void 0 && parsed.host === void 0 && parsed.port === void 0 && parsed.query === void 0 && !parsed.path) parsed.reference = "same-document";
20513
20976
  else if (parsed.scheme === void 0) parsed.reference = "relative";
@@ -20515,24 +20978,78 @@ var require_fast_uri = /* @__PURE__ */ __commonJSMin(((exports, module) => {
20515
20978
  else parsed.reference = "uri";
20516
20979
  if (options.reference && options.reference !== "suffix" && options.reference !== parsed.reference) parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
20517
20980
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
20518
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
20519
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) try {
20520
- parsed.host = URL.domainToASCII(parsed.host.toLowerCase());
20521
- } catch (e) {
20522
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
20523
- }
20981
+ if (!malformedIPLiteral) malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
20982
+ if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
20983
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
20984
+ if (!isIP) host = normalizePercentEncoding(host.toLowerCase());
20985
+ parsed.host = reescapeHostDelimiters(host, isIP);
20524
20986
  }
20525
20987
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
20526
- if (uri.indexOf("%") !== -1) {
20527
- if (parsed.scheme !== void 0) parsed.scheme = unescape(parsed.scheme);
20528
- if (parsed.host !== void 0) parsed.host = unescape(parsed.host);
20529
- }
20530
- if (parsed.path) parsed.path = escape(unescape(parsed.path));
20531
- if (parsed.fragment) parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
20988
+ if (parsed.path) parsed.path = normalizePathEncoding(parsed.path);
20989
+ if (parsed.query) parsed.query = normalizeQueryFragmentEncoding(parsed.query);
20990
+ if (parsed.fragment) parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
20991
+ }
20992
+ if (schemeHandler && schemeHandler.parse) {
20993
+ schemeHandler.parse(parsed, options);
20994
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) malformedSchemeSpecific = true;
20532
20995
  }
20533
- if (schemeHandler && schemeHandler.parse) schemeHandler.parse(parsed, options);
20534
20996
  } else parsed.error = parsed.error || "URI can not be parsed.";
20535
- return parsed;
20997
+ return {
20998
+ parsed,
20999
+ malformedAuthorityOrPort,
21000
+ malformedPercentEncoding,
21001
+ malformedSchemeSpecific,
21002
+ malformedHost,
21003
+ malformedScheme
21004
+ };
21005
+ }
21006
+ /**
21007
+ * @param {string} uri
21008
+ * @param {import('./types/index').Options} [opts]
21009
+ * @returns
21010
+ */
21011
+ function parse(uri, opts) {
21012
+ return parseWithStatus(uri, opts).parsed;
21013
+ }
21014
+ /**
21015
+ * @param {string} uri
21016
+ * @param {import('./types/index').Options} [opts]
21017
+ * @returns {string}
21018
+ */
21019
+ function normalizeString(uri, opts) {
21020
+ return normalizeStringWithStatus(uri, opts).normalized;
21021
+ }
21022
+ /**
21023
+ * @param {string} uri
21024
+ * @param {import('./types/index').Options} [opts]
21025
+ * @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
21026
+ */
21027
+ function normalizeStringWithStatus(uri, opts) {
21028
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
21029
+ return {
21030
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
21031
+ malformedAuthorityOrPort,
21032
+ malformedPercentEncoding,
21033
+ malformedSchemeSpecific,
21034
+ malformedHost,
21035
+ malformedScheme
21036
+ };
21037
+ }
21038
+ /**
21039
+ * @param {import ('./types/index').URIComponent|string} uri
21040
+ * @param {import('./types/index').Options} [opts]
21041
+ * @returns {string|undefined}
21042
+ */
21043
+ function normalizeComparableURI(uri, opts) {
21044
+ if (typeof uri !== "string" && typeof uri !== "object") return;
21045
+ let value;
21046
+ try {
21047
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
21048
+ } catch {
21049
+ return;
21050
+ }
21051
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
21052
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
20536
21053
  }
20537
21054
  const fastUri = {
20538
21055
  SCHEMES,
@@ -24322,7 +24839,7 @@ var AjvJsonSchemaValidator = class {
24322
24839
  };
24323
24840
  import_ajv.Ajv;
24324
24841
  //#endregion
24325
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/mcp-DIH4cS6P.mjs
24842
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/mcp-oHn3ureC.mjs
24326
24843
  const COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
24327
24844
  /**
24328
24845
  * Checks if a schema is completable (has completion metadata).
@@ -24344,604 +24861,425 @@ function armSseKeepAlive(intervalMs, onTick) {
24344
24861
  timer.unref?.();
24345
24862
  return timer;
24346
24863
  }
24864
+ let readClassification;
24347
24865
  /**
24348
- * A `ServerEventBus` backed by an in-process listener set.
24866
+ * The per-request micro-transport: a real, connected `Transport` whose whole
24867
+ * lifetime is one HTTP exchange. See the module documentation for the
24868
+ * response shapes it produces.
24349
24869
  *
24350
- * `publish()` delivers synchronously to the live listener set (a listener
24351
- * unsubscribing itself mid-dispatch is safe; the entry's listen-router
24352
- * listeners never unsubscribe peers). A throwing listener does not stop
24353
- * delivery to the others.
24870
+ * Connecting a `Server` binds the instance to the classified modern revision
24871
+ * when its `supportedProtocolVersions` lists it and the SDK implements it.
24354
24872
  */
24355
- var InMemoryServerEventBus = class {
24356
- _listeners = /* @__PURE__ */ new Set();
24873
+ var PerRequestHTTPServerTransport = class {
24874
+ static {
24875
+ readClassification = (transport) => transport._classification;
24876
+ }
24877
+ onclose;
24878
+ onerror;
24879
+ onmessage;
24880
+ _classification;
24881
+ _responseMode;
24882
+ _started = false;
24883
+ _used = false;
24884
+ _closed = false;
24885
+ _terminalDelivered = false;
24357
24886
  /**
24358
- * @param onerror - Optional callback for errors thrown by listeners
24359
- * during dispatch.
24887
+ * `true` only while the inbound message is being delivered synchronously
24888
+ * to the connected protocol layer. The pre-handler gates (the era
24889
+ * registry gate, the edge→instance handoff check, the missing-handler
24890
+ * rejection) answer inside this window; request handlers always run
24891
+ * after it (the protocol layer defers them to a microtask). An error
24892
+ * sent inside the window is therefore ladder-originated, and an error
24893
+ * sent after it is handler-produced.
24360
24894
  */
24361
- constructor(onerror) {
24362
- this.onerror = onerror;
24895
+ _dispatchWindowOpen = false;
24896
+ _requestId;
24897
+ _deferredResponse;
24898
+ _sse;
24899
+ _abortCleanup;
24900
+ _keepAliveMs;
24901
+ constructor(options) {
24902
+ this._classification = options.classification;
24903
+ this._responseMode = options.responseMode ?? "auto";
24904
+ this._keepAliveMs = options.keepAliveMs ?? 15e3;
24363
24905
  }
24364
- publish(event) {
24365
- for (const listener of this._listeners) try {
24366
- listener(event);
24367
- } catch (error) {
24368
- this.onerror?.(error instanceof Error ? error : new Error(String(error)));
24906
+ async start() {
24907
+ if (this._started) throw new Error("PerRequestHTTPServerTransport is already started");
24908
+ this._started = true;
24909
+ }
24910
+ /**
24911
+ * Serves the single exchange: delivers the classified message to the
24912
+ * connected server instance and resolves with the HTTP response.
24913
+ *
24914
+ * Throws when called a second time (the transport is strictly
24915
+ * single-use), or before a server has been connected to the transport.
24916
+ * The returned promise rejects with a connection-closed error when the
24917
+ * transport is closed before a response was produced (for example because
24918
+ * the client disconnected).
24919
+ */
24920
+ async handleMessage(message, extra) {
24921
+ if (this._used) throw new Error("PerRequestHTTPServerTransport serves exactly one exchange; construct a new transport per request");
24922
+ if (!this._started || this.onmessage === void 0) throw new Error("PerRequestHTTPServerTransport is not connected: connect a server to this transport before handling a message");
24923
+ if (this._closed) throw new Error("PerRequestHTTPServerTransport is closed");
24924
+ this._used = true;
24925
+ const signal = extra?.request?.signal;
24926
+ if (signal?.aborted) {
24927
+ await this.close();
24928
+ throw new SdkError(SdkErrorCode.ConnectionClosed, "The request was aborted before it could be handled");
24929
+ }
24930
+ const messageExtra = {
24931
+ classification: this._classification,
24932
+ ...extra?.request !== void 0 && { request: extra.request },
24933
+ ...extra?.authInfo !== void 0 && { authInfo: extra.authInfo }
24934
+ };
24935
+ if (isJSONRPCRequest(message)) {
24936
+ this._requestId = message.id;
24937
+ let resolve;
24938
+ let reject;
24939
+ const promise = new Promise((promiseResolve, promiseReject) => {
24940
+ resolve = promiseResolve;
24941
+ reject = promiseReject;
24942
+ });
24943
+ this._deferredResponse = {
24944
+ promise,
24945
+ resolve,
24946
+ reject,
24947
+ settled: false
24948
+ };
24949
+ if (signal !== void 0) {
24950
+ const onAbort = () => void this.close();
24951
+ signal.addEventListener("abort", onAbort, { once: true });
24952
+ this._abortCleanup = () => signal.removeEventListener("abort", onAbort);
24953
+ }
24954
+ this._dispatchWindowOpen = true;
24955
+ try {
24956
+ this.onmessage(message, messageExtra);
24957
+ } finally {
24958
+ this._dispatchWindowOpen = false;
24959
+ }
24960
+ if (this._responseMode === "sse" && !this._closed && !this._deferredResponse.settled) this.upgradeToSse();
24961
+ return promise;
24369
24962
  }
24963
+ this.onmessage(message, messageExtra);
24964
+ return new Response(null, { status: 202 });
24370
24965
  }
24371
- subscribe(listener) {
24372
- this._listeners.add(listener);
24373
- let live = true;
24374
- return () => {
24375
- if (!live) return;
24376
- live = false;
24377
- this._listeners.delete(listener);
24966
+ async send(message, options) {
24967
+ if (this._closed) return;
24968
+ const isResponse = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message);
24969
+ const relatedId = isResponse ? message.id : options?.relatedRequestId;
24970
+ if (this._requestId === void 0 || relatedId === void 0 || relatedId !== this._requestId) {
24971
+ if (isResponse) this.onerror?.(/* @__PURE__ */ new Error(`Received a response for an unknown request id: ${String(message.id)}`));
24972
+ return;
24973
+ }
24974
+ if (isResponse) {
24975
+ if (this._terminalDelivered) return;
24976
+ this._terminalDelivered = true;
24977
+ const errorCode = isJSONRPCErrorResponse(message) ? message.error.code : void 0;
24978
+ const ladderStatus = errorCode !== void 0 && (this._dispatchWindowOpen || errorCode === ProtocolErrorCode.MissingRequiredClientCapability) ? LADDER_ERROR_HTTP_STATUS[errorCode] : void 0;
24979
+ if (ladderStatus !== void 0 && this._sse === void 0) {
24980
+ this.settleResponse(Response.json(message, {
24981
+ status: ladderStatus,
24982
+ headers: { "Content-Type": "application/json" }
24983
+ }));
24984
+ queueMicrotask(() => void this.close());
24985
+ return;
24986
+ }
24987
+ if (this._sse !== void 0 || this._responseMode === "sse") {
24988
+ if (this._sse === void 0) this.upgradeToSse();
24989
+ this.writeMessageFrame(message);
24990
+ this.finalizeStream();
24991
+ return;
24992
+ }
24993
+ this.settleResponse(Response.json(message, {
24994
+ status: 200,
24995
+ headers: { "Content-Type": "application/json" }
24996
+ }));
24997
+ queueMicrotask(() => void this.close());
24998
+ return;
24999
+ }
25000
+ if (this._responseMode === "json") return;
25001
+ if (this._sse === void 0) this.upgradeToSse();
25002
+ this.writeMessageFrame(message);
25003
+ }
25004
+ /**
25005
+ * Writes an SSE comment frame (a keep-alive heartbeat). Dropped when the
25006
+ * exchange is not currently streaming.
25007
+ */
25008
+ writeCommentFrame(comment) {
25009
+ if (this._closed || this._sse === void 0 || this._sse.closed) return;
25010
+ const frame = comment.split("\n").map((line) => `: ${line}`).join("\n");
25011
+ this.writeFrame(`${frame}\n\n`);
25012
+ }
25013
+ async close() {
25014
+ if (this._closed) return;
25015
+ this._closed = true;
25016
+ this._abortCleanup?.();
25017
+ this._abortCleanup = void 0;
25018
+ if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
25019
+ if (this._sse !== void 0 && !this._sse.closed) {
25020
+ this._sse.closed = true;
25021
+ try {
25022
+ this._sse.controller.close();
25023
+ } catch {}
25024
+ }
25025
+ if (this._deferredResponse !== void 0 && !this._deferredResponse.settled) {
25026
+ this._deferredResponse.settled = true;
25027
+ this._deferredResponse.reject(new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed before a response was produced"));
25028
+ }
25029
+ this.onclose?.();
25030
+ }
25031
+ settleResponse(response) {
25032
+ if (this._deferredResponse === void 0 || this._deferredResponse.settled) return;
25033
+ this._deferredResponse.settled = true;
25034
+ this._deferredResponse.resolve(response);
25035
+ }
25036
+ upgradeToSse() {
25037
+ let controller;
25038
+ const readable = new ReadableStream({
25039
+ start: (streamController) => {
25040
+ controller = streamController;
25041
+ },
25042
+ cancel: () => {
25043
+ this.close();
25044
+ }
25045
+ });
25046
+ this._sse = {
25047
+ controller,
25048
+ encoder: new TextEncoder(),
25049
+ closed: false
24378
25050
  };
25051
+ this._sse.keepAliveTimer = armSseKeepAlive(this._keepAliveMs, () => this.writeCommentFrame("keepalive"));
25052
+ this.settleResponse(new Response(readable, {
25053
+ status: 200,
25054
+ headers: {
25055
+ "Content-Type": "text/event-stream",
25056
+ "Cache-Control": "no-cache, no-transform",
25057
+ Connection: "keep-alive",
25058
+ "X-Accel-Buffering": "no"
25059
+ }
25060
+ }));
24379
25061
  }
24380
- /** The number of currently registered listeners (test/introspection only — the routers track capacity via their own open-subscription set). */
24381
- get listenerCount() {
24382
- return this._listeners.size;
25062
+ finalizeStream() {
25063
+ if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
25064
+ if (this._sse !== void 0 && !this._sse.closed) {
25065
+ this._sse.closed = true;
25066
+ try {
25067
+ this._sse.controller.close();
25068
+ } catch {}
25069
+ }
25070
+ queueMicrotask(() => void this.close());
25071
+ }
25072
+ writeMessageFrame(message) {
25073
+ this.writeFrame(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
25074
+ }
25075
+ writeFrame(frame) {
25076
+ if (this._sse === void 0 || this._sse.closed) return;
25077
+ try {
25078
+ this._sse.controller.enqueue(this._sse.encoder.encode(frame));
25079
+ } catch (error) {
25080
+ this.onerror?.(/* @__PURE__ */ new Error(`Failed to write to the response stream: ${error}`));
25081
+ }
24383
25082
  }
24384
25083
  };
24385
- /** Build a {@linkcode ServerNotifier} over a bus. */
24386
- function createServerNotifier(bus) {
24387
- return {
24388
- toolsChanged: () => bus.publish({ kind: "tools_list_changed" }),
24389
- promptsChanged: () => bus.publish({ kind: "prompts_list_changed" }),
24390
- resourcesChanged: () => bus.publish({ kind: "resources_list_changed" }),
24391
- resourceUpdated: (uri) => bus.publish({
24392
- kind: "resource_updated",
24393
- uri
24394
- })
24395
- };
25084
+ /** Package-internal: the classification a per-request transport serves; `undefined` for any other transport. Not public API. */
25085
+ function perRequestClassificationOf(transport) {
25086
+ return transport instanceof PerRequestHTTPServerTransport ? readClassification(transport) : void 0;
25087
+ }
25088
+ function comparableResource(value) {
25089
+ const text = String(value);
25090
+ const hash = text.indexOf("#");
25091
+ return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, "");
25092
+ }
25093
+ function sameResource(reported, expected) {
25094
+ if (!reported) return false;
25095
+ return comparableResource(reported) === comparableResource(expected);
25096
+ }
25097
+ function headerQuotedValue(value) {
25098
+ return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
24396
25099
  }
24397
25100
  /**
24398
- * Whether a `subscriptions/listen` filter accepts a given change event.
24399
- *
24400
- * Pure: no I/O, no mutation. The filter governs ONLY the four
24401
- * subscription-gated change types — non-gated notifications never reach the
24402
- * bus and are not modeled here.
25101
+ * Build a `WWW-Authenticate: Bearer …` challenge header value (RFC 6750).
24403
25102
  *
24404
- * `resource_updated` matches only when `resourceSubscriptions` is present and
24405
- * contains the event's URI exactly (per the spec: "for these resource URIs").
24406
- */
24407
- function listenFilterAccepts(filter, event) {
24408
- switch (event.kind) {
24409
- case "tools_list_changed": return filter.toolsListChanged === true;
24410
- case "prompts_list_changed": return filter.promptsListChanged === true;
24411
- case "resources_list_changed": return filter.resourcesListChanged === true;
24412
- case "resource_updated": return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
25103
+ * The single formatter behind every challenge this package emits — the
25104
+ * bearer-auth 401/403 answers and the per-operation scope-challenge 403 — so
25105
+ * all challenges from one server agree on parameter order and quoting. Every
25106
+ * parameter value is emitted as an HTTP quoted-string with `\` and `"`
25107
+ * escaped and non-printable characters replaced.
25108
+ */
25109
+ function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, resourceMetadataUrl) {
25110
+ let header = `Bearer error="${headerQuotedValue(errorCode)}", error_description="${headerQuotedValue(description)}"`;
25111
+ if (requiredScopes.length > 0) header += `, scope="${headerQuotedValue(requiredScopes.join(" "))}"`;
25112
+ if (resourceMetadataUrl) header += `, resource_metadata="${headerQuotedValue(resourceMetadataUrl)}"`;
25113
+ return header;
25114
+ }
25115
+ /**
25116
+ * Validate a raw `Authorization` header value as a Bearer token and return
25117
+ * the verified {@link AuthInfo}.
25118
+ *
25119
+ * The runtime-neutral core of Bearer authentication: it parses the header,
25120
+ * runs the verifier, compares the token's resource with `expectedResource`,
25121
+ * enforces `requiredScopes`, and rejects tokens without an
25122
+ * expiration or past it. On any failure it throws an {@link OAuthError} —
25123
+ * pass that to {@link bearerAuthChallengeResponse} for the matching HTTP
25124
+ * answer, or use {@link requireBearerAuth} to get both steps as one call.
25125
+ *
25126
+ * Framework adapters build on this: `requireBearerAuth` from
25127
+ * `@modelcontextprotocol/express` feeds it `req.headers.authorization`.
25128
+ */
25129
+ async function verifyBearerToken(authorizationHeader, options) {
25130
+ const { verifier, requiredScopes = [], expectedResource } = options;
25131
+ if (!authorizationHeader) throw new OAuthError(OAuthErrorCode.InvalidToken, "Missing Authorization header");
25132
+ const [type, token] = authorizationHeader.split(" ");
25133
+ if (type?.toLowerCase() !== "bearer" || !token) throw new OAuthError(OAuthErrorCode.InvalidToken, "Invalid Authorization header format, expected 'Bearer TOKEN'");
25134
+ const authInfo = await verifier.verifyAccessToken(token);
25135
+ if (expectedResource !== void 0 && !sameResource(authInfo.resource, expectedResource)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token was not issued for this resource");
25136
+ if (requiredScopes.length > 0) {
25137
+ if (!requiredScopes.every((scope) => authInfo.scopes.includes(scope))) throw new OAuthError(OAuthErrorCode.InsufficientScope, "Insufficient scope");
24413
25138
  }
25139
+ if (typeof authInfo.expiresAt !== "number" || Number.isNaN(authInfo.expiresAt)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has no expiration time");
25140
+ else if (authInfo.expiresAt < Date.now() / 1e3) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has expired");
25141
+ if (options.resourceMetadataUrl !== void 0 && authInfo.resourceMetadataUrl === void 0) return {
25142
+ ...authInfo,
25143
+ resourceMetadataUrl: options.resourceMetadataUrl
25144
+ };
25145
+ return authInfo;
24414
25146
  }
24415
25147
  /**
24416
- * The honored subset of a requested filter: keeps only the fields the client
24417
- * explicitly opted in to (drops `false` and absent fields), narrowed against
24418
- * the server's declared capabilities when supplied. The serving entry sends
24419
- * this back in `notifications/subscriptions/acknowledged` so the ack reflects
24420
- * what the server can actually deliver.
25148
+ * Build the HTTP answer for a Bearer authentication failure.
24421
25149
  *
24422
- * - `toolsListChanged` is honored only when `capabilities.tools.listChanged`
24423
- * is advertised; likewise `promptsListChanged` / `resourcesListChanged`.
24424
- * - `resourceSubscriptions` is honored only when
24425
- * `capabilities.resources.subscribe` is advertised.
25150
+ * Maps an {@link OAuthError} to its status — `401` for `invalid_token` and
25151
+ * `403` for `insufficient_scope` (both carrying the `WWW-Authenticate: Bearer …`
25152
+ * challenge, with `resource_metadata` when configured so clients can discover
25153
+ * the Authorization Server), `500` for `server_error`, `400` for anything
25154
+ * else. A non-`OAuthError` value answers `500 server_error`. The body is the
25155
+ * OAuth error JSON.
25156
+ */
25157
+ function bearerAuthChallengeResponse(error, options) {
25158
+ const { requiredScopes = [], resourceMetadataUrl } = options ?? {};
25159
+ if (!(error instanceof OAuthError)) {
25160
+ const serverError = new OAuthError(OAuthErrorCode.ServerError, "Internal Server Error");
25161
+ return Response.json(serverError.toResponseObject(), { status: 500 });
25162
+ }
25163
+ switch (error.code) {
25164
+ case OAuthErrorCode.InvalidToken: {
25165
+ const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
25166
+ return Response.json(error.toResponseObject(), {
25167
+ status: 401,
25168
+ headers: { "WWW-Authenticate": challenge }
25169
+ });
25170
+ }
25171
+ case OAuthErrorCode.InsufficientScope: {
25172
+ const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
25173
+ return Response.json(error.toResponseObject(), {
25174
+ status: 403,
25175
+ headers: { "WWW-Authenticate": challenge }
25176
+ });
25177
+ }
25178
+ case OAuthErrorCode.ServerError: return Response.json(error.toResponseObject(), { status: 500 });
25179
+ default: return Response.json(error.toResponseObject(), { status: 400 });
25180
+ }
25181
+ }
25182
+ /**
25183
+ * Builds the RFC 9728 Protected Resource Metadata URL for a given MCP server
25184
+ * URL by inserting `/.well-known/oauth-protected-resource` ahead of the path.
24426
25185
  *
24427
- * `capabilities` is optional on this pure helper for test convenience only —
24428
- * both wired routers REQUIRE capabilities at the call site (the HTTP router's
24429
- * `serve()` takes a required parameter; `StdioListenRouter.serve()` throws
24430
- * before `setServerCapabilities()` was called), so the fail-open
24431
- * `undefined → honor everything` branch is never reachable on a wired entry.
25186
+ * @example
25187
+ * ```ts
25188
+ * getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
25189
+ * // → 'https://api.example.com/.well-known/oauth-protected-resource/mcp'
25190
+ * ```
24432
25191
  */
24433
- function honoredSubset(requested, capabilities) {
24434
- const honored = {};
24435
- const allow = (bit) => capabilities === void 0 || bit === true;
24436
- if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
24437
- if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
24438
- if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
24439
- if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
24440
- return honored;
25192
+ function getOAuthProtectedResourceMetadataUrl(serverUrl) {
25193
+ const metadataUrl = new URL(serverUrl);
25194
+ metadataUrl.pathname = protectedResourceMetadataPath(serverUrl);
25195
+ metadataUrl.hash = "";
25196
+ return metadataUrl.href;
24441
25197
  }
24442
- /** Map a {@linkcode ServerEvent} onto its wire notification `{method, params}`. */
24443
- function serverEventToNotification(event) {
24444
- switch (event.kind) {
24445
- case "tools_list_changed": return { method: "notifications/tools/list_changed" };
24446
- case "prompts_list_changed": return { method: "notifications/prompts/list_changed" };
24447
- case "resources_list_changed": return { method: "notifications/resources/list_changed" };
24448
- case "resource_updated": return {
24449
- method: "notifications/resources/updated",
24450
- params: { uri: event.uri }
24451
- };
25198
+ /** The RFC 9728 path-aware well-known path for a resource URL. */
25199
+ function protectedResourceMetadataPath(resourceServerUrl) {
25200
+ const rsPath = stripTrailingSlash(resourceServerUrl.pathname);
25201
+ return `/.well-known/oauth-protected-resource${rsPath === "/" ? "" : rsPath}`;
25202
+ }
25203
+ function stripTrailingSlash(path) {
25204
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
25205
+ }
25206
+ /** @internal */
25207
+ function supportsScopeChallengeResolver(transport) {
25208
+ return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
25209
+ }
25210
+ function assertScope(scope, location) {
25211
+ if (typeof scope !== "string" || !/^[\u0021\u0023-\u005B\u005D-\u007E]+$/.test(scope)) throw new TypeError(`${location} must satisfy the OAuth scope-token grammar`);
25212
+ }
25213
+ function validateScopeChallenge(challenge) {
25214
+ if (challenge === null || typeof challenge !== "object" || !Array.isArray(challenge.scopes) || challenge.scopes.length === 0) throw new TypeError("scope challenge must contain at least one scope");
25215
+ for (const [index, scope] of challenge.scopes.entries()) assertScope(scope, `scope challenge scopes[${index}]`);
25216
+ if (challenge.errorDescription !== void 0 && typeof challenge.errorDescription !== "string") throw new TypeError("scope challenge errorDescription must be a string");
25217
+ if (challenge.errorDescription !== void 0 && !/^[\u0020-\u0021\u0023-\u005B\u005D-\u007E]+$/.test(challenge.errorDescription)) throw new TypeError("scope challenge errorDescription must satisfy the RFC 6750 error-description grammar");
25218
+ return challenge;
25219
+ }
25220
+ /** @internal */
25221
+ async function findScopeChallenge(requests, authInfo, resolve) {
25222
+ for (const request of requests) {
25223
+ const challenge = await resolve({
25224
+ request,
25225
+ ...authInfo !== void 0 && { authInfo }
25226
+ });
25227
+ if (challenge !== void 0) return validateScopeChallenge(challenge);
24452
25228
  }
24453
25229
  }
24454
- /** Default capacity guard: refuse a new subscription when this many are already open. */
24455
- const DEFAULT_MAX_SUBSCRIPTIONS = 1024;
24456
- function jsonRpcError(id, code, message) {
24457
- return Response.json({
24458
- jsonrpc: "2.0",
24459
- error: {
24460
- code,
24461
- message
24462
- },
24463
- id
24464
- }, { status: 200 });
25230
+ /**
25231
+ * The RFC 9728 Protected Resource Metadata URL to advertise on a scope
25232
+ * challenge, derived from the verified {@link AuthInfo}: the URL the
25233
+ * authentication gate stamped (`authInfo.resourceMetadataUrl`, set by the
25234
+ * bearer-auth helpers from their `resourceMetadataUrl` option), falling back
25235
+ * to the well-known location for an HTTP(S) RFC 8707 `resource` identifier, or
25236
+ * `undefined` when neither is available (the `resource_metadata` parameter is
25237
+ * then omitted, matching the bearer-auth challenges).
25238
+ *
25239
+ * @internal
25240
+ */
25241
+ function scopeChallengeResourceMetadataUrl(authInfo) {
25242
+ if (authInfo?.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
25243
+ if (authInfo?.resource?.protocol === "https:" || authInfo?.resource?.protocol === "http:") return getOAuthProtectedResourceMetadataUrl(authInfo.resource);
24465
25244
  }
24466
- /** Stamp the subscription id onto a notification's `_meta`. Non-mutating. */
24467
- function stampSubscriptionId(notification, subscriptionId) {
25245
+ /** @internal */
25246
+ function createScopeChallengeResponse(challenge, resourceMetadataUrl) {
25247
+ return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InsufficientScope, challenge.errorDescription ?? "Insufficient scope"), {
25248
+ requiredScopes: [...challenge.scopes],
25249
+ resourceMetadataUrl
25250
+ });
25251
+ }
25252
+ /**
25253
+ * Default handler re-entries per originating request — tighter than the
25254
+ * client driver's 10 because the shim holds a live wire request open.
25255
+ */
25256
+ const DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
25257
+ /** Default per-leg timeout: legs are human-paced, so the 60s protocol default is wrong. */
25258
+ const DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
25259
+ /** Resolves and validates `ServerOptions.inputRequired`, failing loudly at construction time. */
25260
+ function resolveLegacyShimOptions(options) {
25261
+ if (options?.maxRounds !== void 0 && (!Number.isInteger(options.maxRounds) || options.maxRounds < 1)) throw new RangeError(`inputRequired.maxRounds must be a positive integer (got ${options.maxRounds})`);
25262
+ if (options?.roundTimeoutMs !== void 0 && (!Number.isFinite(options.roundTimeoutMs) || options.roundTimeoutMs <= 0)) throw new RangeError(`inputRequired.roundTimeoutMs must be a positive number (got ${options.roundTimeoutMs})`);
24468
25263
  return {
24469
- method: notification.method,
24470
- params: {
24471
- ...notification.params,
24472
- _meta: {
24473
- ...notification.params?._meta,
24474
- [SUBSCRIPTION_ID_META_KEY]: subscriptionId
24475
- }
24476
- }
25264
+ maxRounds: options?.maxRounds ?? DEFAULT_LEGACY_SHIM_MAX_ROUNDS,
25265
+ roundTimeoutMs: options?.roundTimeoutMs ?? DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS,
25266
+ legacyShim: options?.legacyShim ?? true
24477
25267
  };
24478
25268
  }
24479
25269
  /**
24480
- * Read the requested filter off a `subscriptions/listen` request body.
24481
- * Returns the validated filter, or `undefined` when `params.notifications`
24482
- * is absent or fails the schema (the caller answers `-32602` — the spec
24483
- * marks `notifications` REQUIRED on the listen request).
25270
+ * Validates one `inputRequests` entry: malformed or unknown kinds are server
25271
+ * bugs and fail loudly on both eras. Shared by the modern seam's capability
25272
+ * check and the shim's gate.
24484
25273
  */
24485
- function parseListenFilter(message) {
24486
- const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
24487
- return outcome.ok ? outcome.value.params?.notifications : void 0;
24488
- }
24489
- function createListenRouter(options) {
24490
- const { bus, onerror } = options;
24491
- const maxSubscriptions = options.maxSubscriptions ?? 1024;
24492
- const keepAliveMs = options.keepAliveMs ?? 15e3;
24493
- const open = /* @__PURE__ */ new Set();
24494
- function serve(message, signal, capabilities, serverInfo) {
24495
- if (open.size >= maxSubscriptions) {
24496
- onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
24497
- return jsonRpcError(message.id, -32603, "Subscription limit reached");
24498
- }
24499
- const filter = parseListenFilter(message);
24500
- if (filter === void 0) return jsonRpcError(message.id, -32602, "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter");
24501
- const honored = honoredSubset(filter, capabilities);
24502
- const subscriptionId = message.id;
24503
- const encoder = new TextEncoder();
24504
- let controller;
24505
- let closed = false;
24506
- let unsubscribe;
24507
- let keepAliveTimer;
24508
- let abortCleanup;
24509
- const writeFrame = (frame) => {
24510
- if (closed) return;
24511
- try {
24512
- controller.enqueue(encoder.encode(frame));
24513
- } catch (error) {
24514
- onerror?.(error instanceof Error ? error : new Error(String(error)));
24515
- }
24516
- };
24517
- const writeNotification = (method, params) => {
24518
- writeFrame(`event: message\ndata: ${JSON.stringify({
24519
- jsonrpc: "2.0",
24520
- method,
24521
- params
24522
- })}\n\n`);
24523
- };
24524
- const teardown = (graceful) => {
24525
- if (closed) return;
24526
- if (graceful) writeFrame(`event: message\ndata: ${JSON.stringify({
24527
- jsonrpc: "2.0",
24528
- id: subscriptionId,
24529
- result: {
24530
- resultType: "complete",
24531
- _meta: {
24532
- [SUBSCRIPTION_ID_META_KEY]: subscriptionId,
24533
- [SERVER_INFO_META_KEY]: serverInfo
24534
- }
24535
- }
24536
- })}\n\n`);
24537
- closed = true;
24538
- try {
24539
- unsubscribe?.();
24540
- } catch (error) {
24541
- onerror?.(error instanceof Error ? error : new Error(String(error)));
24542
- }
24543
- if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
24544
- abortCleanup?.();
24545
- open.delete(teardown);
24546
- try {
24547
- controller.close();
24548
- } catch {}
24549
- };
24550
- const readable = new ReadableStream({
24551
- start(streamController) {
24552
- controller = streamController;
24553
- const ack = stampSubscriptionId({
24554
- method: "notifications/subscriptions/acknowledged",
24555
- params: { notifications: honored }
24556
- }, subscriptionId);
24557
- writeNotification(ack.method, ack.params);
24558
- if (Object.keys(honored).length === 0) {
24559
- teardown(true);
24560
- return;
24561
- }
24562
- unsubscribe = bus.subscribe((event) => {
24563
- if (closed || !listenFilterAccepts(honored, event)) return;
24564
- const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
24565
- writeNotification(note.method, note.params);
24566
- });
24567
- keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
24568
- open.add(teardown);
24569
- },
24570
- cancel() {
24571
- teardown(false);
24572
- }
24573
- });
24574
- if (signal !== void 0) if (signal.aborted) teardown(false);
24575
- else {
24576
- const onAbort = () => teardown(false);
24577
- signal.addEventListener("abort", onAbort, { once: true });
24578
- abortCleanup = () => signal.removeEventListener("abort", onAbort);
24579
- }
24580
- return new Response(readable, {
24581
- status: 200,
24582
- headers: {
24583
- "Content-Type": "text/event-stream",
24584
- "Cache-Control": "no-cache, no-transform",
24585
- Connection: "keep-alive",
24586
- "X-Accel-Buffering": "no"
24587
- }
24588
- });
24589
- }
24590
- return {
24591
- serve,
24592
- closeAll() {
24593
- for (const teardown of open) teardown(true);
24594
- },
24595
- get openCount() {
24596
- return open.size;
24597
- }
24598
- };
24599
- }
24600
- const CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
24601
- "notifications/tools/list_changed",
24602
- "notifications/prompts/list_changed",
24603
- "notifications/resources/list_changed",
24604
- "notifications/resources/updated"
24605
- ]);
24606
- /**
24607
- * Per-connection listen state for the stdio entry. One instance is held by
24608
- * `serveStdio` for the connection lifetime; it routes inbound
24609
- * `subscriptions/listen` / `notifications/cancelled` and rewrites outbound
24610
- * change notifications onto the active subscriptions. No bus — the long-lived
24611
- * pinned instance's existing `send*ListChanged()` calls feed straight into
24612
- * `routeOutbound()`.
24613
- */
24614
- var StdioListenRouter = class {
24615
- /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
24616
- _subs = /* @__PURE__ */ new Map();
24617
- /**
24618
- * The serving instance's declared capabilities. Filled in by the entry
24619
- * once the modern instance is constructed (the router is created before
24620
- * the instance exists), so the acknowledged filter is narrowed against
24621
- * what the server can actually deliver.
24622
- */
24623
- _serverCapabilities;
24624
- /**
24625
- * The serving instance's identity, stamped onto the graceful-close
24626
- * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
24627
- * `ResultMetaObject`). Handed over together with the capabilities.
24628
- */
24629
- _serverInfo;
24630
- constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
24631
- this._maxSubscriptions = _maxSubscriptions;
24632
- this._serverCapabilities = serverCapabilities;
24633
- this._serverInfo = serverInfo;
24634
- }
24635
- /**
24636
- * Record the serving instance's declared capabilities and identity once
24637
- * it has been constructed. Called by `serveStdio`'s connect path;
24638
- * subsequent `serve()` calls narrow the honored filter against the
24639
- * capabilities, and `teardownAll()` stamps the identity.
24640
- */
24641
- setServerCapabilities(capabilities, serverInfo) {
24642
- this._serverCapabilities = capabilities;
24643
- if (serverInfo !== void 0) this._serverInfo = serverInfo;
24644
- }
24645
- /** Whether `id` is an active listen subscription on this connection. */
24646
- has(id) {
24647
- return this._subs.has(id);
24648
- }
24649
- /**
24650
- * Serve one inbound `subscriptions/listen` request: registers the
24651
- * subscription and returns the stamped acknowledged notification (or, on
24652
- * capacity / params rejection, the in-band JSON-RPC error response).
24653
- *
24654
- * @throws when called before {@linkcode setServerCapabilities} (or the
24655
- * constructor) has supplied the serving instance's capabilities. Honoring a
24656
- * filter without knowing the server's advertised capabilities would fail
24657
- * open (deliver unadvertised types); the entry guarantees capabilities are
24658
- * set before any listen request is routed here.
24659
- */
24660
- serve(message) {
24661
- if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
24662
- if (this._subs.size >= this._maxSubscriptions) return {
24663
- jsonrpc: "2.0",
24664
- id: message.id,
24665
- error: {
24666
- code: -32603,
24667
- message: "Subscription limit reached"
24668
- }
24669
- };
24670
- const filter = parseListenFilter(message);
24671
- if (filter === void 0) return {
24672
- jsonrpc: "2.0",
24673
- id: message.id,
24674
- error: {
24675
- code: -32602,
24676
- message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
24677
- }
24678
- };
24679
- const honored = honoredSubset(filter, this._serverCapabilities);
24680
- this._subs.set(message.id, honored);
24681
- return stampSubscriptionId({
24682
- method: "notifications/subscriptions/acknowledged",
24683
- params: { notifications: honored }
24684
- }, message.id);
24685
- }
24686
- /**
24687
- * Tear down one subscription (inbound `notifications/cancelled`). Returns
24688
- * `true` when a subscription was removed. After this call NOTHING further
24689
- * is delivered for that subscription id (the post-cancel hardening).
24690
- */
24691
- cancel(id) {
24692
- return this._subs.delete(id);
24693
- }
24694
- /**
24695
- * Route an outbound notification through the active subscriptions.
24696
- *
24697
- * - For a subscription-gated change notification, returns one stamped copy
24698
- * per subscription that opted in to it (an empty array means it is
24699
- * dropped — the modern era never delivers an un-requested change type).
24700
- * - For any other outbound message, returns `'passthrough'` (the entry
24701
- * forwards it as-is).
24702
- */
24703
- routeOutbound(message) {
24704
- if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
24705
- const uriParam = message.params?.["uri"];
24706
- const uri = typeof uriParam === "string" ? uriParam : void 0;
24707
- const event = notificationToServerEvent(message.method, uri);
24708
- const out = [];
24709
- for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
24710
- method: message.method,
24711
- params: message.params ?? {}
24712
- }, subscriptionId));
24713
- return out;
24714
- }
24715
- /**
24716
- * Server-side graceful teardown of every active subscription: returns the
24717
- * empty `subscriptions/listen` JSON-RPC result for each subscription id —
24718
- * the spec's graceful-close signal, `_meta` carrying the subscription id
24719
- * and the serving instance's identity — for the entry to emit before
24720
- * closing the wire. Clears the set so nothing further is delivered.
24721
- */
24722
- teardownAll() {
24723
- const out = [];
24724
- for (const id of this._subs.keys()) out.push({
24725
- jsonrpc: "2.0",
24726
- id,
24727
- result: {
24728
- resultType: "complete",
24729
- _meta: {
24730
- [SUBSCRIPTION_ID_META_KEY]: id,
24731
- ...this._serverInfo !== void 0 && { ["io.modelcontextprotocol/serverInfo"]: this._serverInfo }
24732
- }
24733
- }
24734
- });
24735
- this._subs.clear();
24736
- return out;
24737
- }
24738
- };
24739
- function notificationToServerEvent(method, uri) {
24740
- switch (method) {
24741
- case "notifications/tools/list_changed": return { kind: "tools_list_changed" };
24742
- case "notifications/prompts/list_changed": return { kind: "prompts_list_changed" };
24743
- case "notifications/resources/list_changed": return { kind: "resources_list_changed" };
24744
- default: return {
24745
- kind: "resource_updated",
24746
- uri: uri ?? ""
24747
- };
24748
- }
24749
- }
24750
- function comparableResource(value) {
24751
- const text = String(value);
24752
- const hash = text.indexOf("#");
24753
- return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, "");
24754
- }
24755
- function sameResource(reported, expected) {
24756
- if (!reported) return false;
24757
- return comparableResource(reported) === comparableResource(expected);
24758
- }
24759
- function headerQuotedValue(value) {
24760
- return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
24761
- }
24762
- /**
24763
- * Build a `WWW-Authenticate: Bearer …` challenge header value (RFC 6750).
24764
- *
24765
- * The single formatter behind every challenge this package emits — the
24766
- * bearer-auth 401/403 answers and the per-operation scope-challenge 403 — so
24767
- * all challenges from one server agree on parameter order and quoting. Every
24768
- * parameter value is emitted as an HTTP quoted-string with `\` and `"`
24769
- * escaped and non-printable characters replaced.
24770
- */
24771
- function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, resourceMetadataUrl) {
24772
- let header = `Bearer error="${headerQuotedValue(errorCode)}", error_description="${headerQuotedValue(description)}"`;
24773
- if (requiredScopes.length > 0) header += `, scope="${headerQuotedValue(requiredScopes.join(" "))}"`;
24774
- if (resourceMetadataUrl) header += `, resource_metadata="${headerQuotedValue(resourceMetadataUrl)}"`;
24775
- return header;
24776
- }
24777
- /**
24778
- * Validate a raw `Authorization` header value as a Bearer token and return
24779
- * the verified {@link AuthInfo}.
24780
- *
24781
- * The runtime-neutral core of Bearer authentication: it parses the header,
24782
- * runs the verifier, compares the token's resource with `expectedResource`,
24783
- * enforces `requiredScopes`, and rejects tokens without an
24784
- * expiration or past it. On any failure it throws an {@link OAuthError} —
24785
- * pass that to {@link bearerAuthChallengeResponse} for the matching HTTP
24786
- * answer, or use {@link requireBearerAuth} to get both steps as one call.
24787
- *
24788
- * Framework adapters build on this: `requireBearerAuth` from
24789
- * `@modelcontextprotocol/express` feeds it `req.headers.authorization`.
24790
- */
24791
- async function verifyBearerToken(authorizationHeader, options) {
24792
- const { verifier, requiredScopes = [], expectedResource } = options;
24793
- if (!authorizationHeader) throw new OAuthError(OAuthErrorCode.InvalidToken, "Missing Authorization header");
24794
- const [type, token] = authorizationHeader.split(" ");
24795
- if (type?.toLowerCase() !== "bearer" || !token) throw new OAuthError(OAuthErrorCode.InvalidToken, "Invalid Authorization header format, expected 'Bearer TOKEN'");
24796
- const authInfo = await verifier.verifyAccessToken(token);
24797
- if (expectedResource !== void 0 && !sameResource(authInfo.resource, expectedResource)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token was not issued for this resource");
24798
- if (requiredScopes.length > 0) {
24799
- if (!requiredScopes.every((scope) => authInfo.scopes.includes(scope))) throw new OAuthError(OAuthErrorCode.InsufficientScope, "Insufficient scope");
24800
- }
24801
- if (typeof authInfo.expiresAt !== "number" || Number.isNaN(authInfo.expiresAt)) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has no expiration time");
24802
- else if (authInfo.expiresAt < Date.now() / 1e3) throw new OAuthError(OAuthErrorCode.InvalidToken, "Token has expired");
24803
- if (options.resourceMetadataUrl !== void 0 && authInfo.resourceMetadataUrl === void 0) return {
24804
- ...authInfo,
24805
- resourceMetadataUrl: options.resourceMetadataUrl
24806
- };
24807
- return authInfo;
24808
- }
24809
- /**
24810
- * Build the HTTP answer for a Bearer authentication failure.
24811
- *
24812
- * Maps an {@link OAuthError} to its status — `401` for `invalid_token` and
24813
- * `403` for `insufficient_scope` (both carrying the `WWW-Authenticate: Bearer …`
24814
- * challenge, with `resource_metadata` when configured so clients can discover
24815
- * the Authorization Server), `500` for `server_error`, `400` for anything
24816
- * else. A non-`OAuthError` value answers `500 server_error`. The body is the
24817
- * OAuth error JSON.
24818
- */
24819
- function bearerAuthChallengeResponse(error, options) {
24820
- const { requiredScopes = [], resourceMetadataUrl } = options ?? {};
24821
- if (!(error instanceof OAuthError)) {
24822
- const serverError = new OAuthError(OAuthErrorCode.ServerError, "Internal Server Error");
24823
- return Response.json(serverError.toResponseObject(), { status: 500 });
24824
- }
24825
- switch (error.code) {
24826
- case OAuthErrorCode.InvalidToken: {
24827
- const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
24828
- return Response.json(error.toResponseObject(), {
24829
- status: 401,
24830
- headers: { "WWW-Authenticate": challenge }
24831
- });
24832
- }
24833
- case OAuthErrorCode.InsufficientScope: {
24834
- const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
24835
- return Response.json(error.toResponseObject(), {
24836
- status: 403,
24837
- headers: { "WWW-Authenticate": challenge }
24838
- });
24839
- }
24840
- case OAuthErrorCode.ServerError: return Response.json(error.toResponseObject(), { status: 500 });
24841
- default: return Response.json(error.toResponseObject(), { status: 400 });
24842
- }
24843
- }
24844
- /**
24845
- * Builds the RFC 9728 Protected Resource Metadata URL for a given MCP server
24846
- * URL by inserting `/.well-known/oauth-protected-resource` ahead of the path.
24847
- *
24848
- * @example
24849
- * ```ts
24850
- * getOAuthProtectedResourceMetadataUrl(new URL('https://api.example.com/mcp'))
24851
- * // → 'https://api.example.com/.well-known/oauth-protected-resource/mcp'
24852
- * ```
24853
- */
24854
- function getOAuthProtectedResourceMetadataUrl(serverUrl) {
24855
- const metadataUrl = new URL(serverUrl);
24856
- metadataUrl.pathname = protectedResourceMetadataPath(serverUrl);
24857
- metadataUrl.hash = "";
24858
- return metadataUrl.href;
24859
- }
24860
- /** The RFC 9728 path-aware well-known path for a resource URL. */
24861
- function protectedResourceMetadataPath(resourceServerUrl) {
24862
- const rsPath = stripTrailingSlash(resourceServerUrl.pathname);
24863
- return `/.well-known/oauth-protected-resource${rsPath === "/" ? "" : rsPath}`;
24864
- }
24865
- function stripTrailingSlash(path) {
24866
- return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
24867
- }
24868
- /** @internal */
24869
- function supportsScopeChallengeResolver(transport) {
24870
- return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
24871
- }
24872
- function assertScope(scope, location) {
24873
- if (typeof scope !== "string" || !/^[\u0021\u0023-\u005B\u005D-\u007E]+$/.test(scope)) throw new TypeError(`${location} must satisfy the OAuth scope-token grammar`);
24874
- }
24875
- function validateScopeChallenge(challenge) {
24876
- if (challenge === null || typeof challenge !== "object" || !Array.isArray(challenge.scopes) || challenge.scopes.length === 0) throw new TypeError("scope challenge must contain at least one scope");
24877
- for (const [index, scope] of challenge.scopes.entries()) assertScope(scope, `scope challenge scopes[${index}]`);
24878
- if (challenge.errorDescription !== void 0 && typeof challenge.errorDescription !== "string") throw new TypeError("scope challenge errorDescription must be a string");
24879
- if (challenge.errorDescription !== void 0 && !/^[\u0020-\u0021\u0023-\u005B\u005D-\u007E]+$/.test(challenge.errorDescription)) throw new TypeError("scope challenge errorDescription must satisfy the RFC 6750 error-description grammar");
24880
- return challenge;
24881
- }
24882
- /** @internal */
24883
- async function findScopeChallenge(requests, authInfo, resolve) {
24884
- for (const request of requests) {
24885
- const challenge = await resolve({
24886
- request,
24887
- ...authInfo !== void 0 && { authInfo }
24888
- });
24889
- if (challenge !== void 0) return validateScopeChallenge(challenge);
24890
- }
24891
- }
24892
- /**
24893
- * The RFC 9728 Protected Resource Metadata URL to advertise on a scope
24894
- * challenge, derived from the verified {@link AuthInfo}: the URL the
24895
- * authentication gate stamped (`authInfo.resourceMetadataUrl`, set by the
24896
- * bearer-auth helpers from their `resourceMetadataUrl` option), falling back
24897
- * to the well-known location for an HTTP(S) RFC 8707 `resource` identifier, or
24898
- * `undefined` when neither is available (the `resource_metadata` parameter is
24899
- * then omitted, matching the bearer-auth challenges).
24900
- *
24901
- * @internal
24902
- */
24903
- function scopeChallengeResourceMetadataUrl(authInfo) {
24904
- if (authInfo?.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
24905
- if (authInfo?.resource?.protocol === "https:" || authInfo?.resource?.protocol === "http:") return getOAuthProtectedResourceMetadataUrl(authInfo.resource);
24906
- }
24907
- /** @internal */
24908
- function createScopeChallengeResponse(challenge, resourceMetadataUrl) {
24909
- return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InsufficientScope, challenge.errorDescription ?? "Insufficient scope"), {
24910
- requiredScopes: [...challenge.scopes],
24911
- resourceMetadataUrl
24912
- });
24913
- }
24914
- /**
24915
- * Default handler re-entries per originating request — tighter than the
24916
- * client driver's 10 because the shim holds a live wire request open.
24917
- */
24918
- const DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
24919
- /** Default per-leg timeout: legs are human-paced, so the 60s protocol default is wrong. */
24920
- const DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
24921
- /** Resolves and validates `ServerOptions.inputRequired`, failing loudly at construction time. */
24922
- function resolveLegacyShimOptions(options) {
24923
- if (options?.maxRounds !== void 0 && (!Number.isInteger(options.maxRounds) || options.maxRounds < 1)) throw new RangeError(`inputRequired.maxRounds must be a positive integer (got ${options.maxRounds})`);
24924
- if (options?.roundTimeoutMs !== void 0 && (!Number.isFinite(options.roundTimeoutMs) || options.roundTimeoutMs <= 0)) throw new RangeError(`inputRequired.roundTimeoutMs must be a positive number (got ${options.roundTimeoutMs})`);
24925
- return {
24926
- maxRounds: options?.maxRounds ?? DEFAULT_LEGACY_SHIM_MAX_ROUNDS,
24927
- roundTimeoutMs: options?.roundTimeoutMs ?? DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS,
24928
- legacyShim: options?.legacyShim ?? true
24929
- };
24930
- }
24931
- /**
24932
- * Validates one `inputRequests` entry: malformed or unknown kinds are server
24933
- * bugs and fail loudly on both eras. Shared by the modern seam's capability
24934
- * check and the shim's gate.
24935
- */
24936
- function coerceEmbeddedInputRequest(method, key, entry) {
24937
- if (entry === null || typeof entry !== "object" || typeof entry.method !== "string") throw new ProtocolError(ProtocolErrorCode.InternalError, `Handler for ${method} returned an invalid input request '${key}': each inputRequests entry must be an embedded elicitation/create, sampling/createMessage, or roots/list request`);
24938
- const embedded = entry;
24939
- const required = requiredClientCapabilitiesForInputRequest(embedded);
24940
- if (required === void 0) throw new ProtocolError(ProtocolErrorCode.InternalError, `Handler for ${method} returned an input request '${key}' of kind '${embedded.method}', which is not an embedded request the 2026-07-28 revision defines`);
24941
- return {
24942
- embedded,
24943
- required
24944
- };
25274
+ function coerceEmbeddedInputRequest(method, key, entry) {
25275
+ if (entry === null || typeof entry !== "object" || typeof entry.method !== "string") throw new ProtocolError(ProtocolErrorCode.InternalError, `Handler for ${method} returned an invalid input request '${key}': each inputRequests entry must be an embedded elicitation/create, sampling/createMessage, or roots/list request`);
25276
+ const embedded = entry;
25277
+ const required = requiredClientCapabilitiesForInputRequest(embedded);
25278
+ if (required === void 0) throw new ProtocolError(ProtocolErrorCode.InternalError, `Handler for ${method} returned an input request '${key}' of kind '${embedded.method}', which is not an embedded request the 2026-07-28 revision defines`);
25279
+ return {
25280
+ embedded,
25281
+ required
25282
+ };
24945
25283
  }
24946
25284
  /**
24947
25285
  * The 2025-11-25 URL-mode wire shape requires an `elicitationId`; the 2026
@@ -25129,6 +25467,7 @@ var Server = class extends Protocol {
25129
25467
  _requestStateVerify;
25130
25468
  _inputRequiredServing;
25131
25469
  _legacyShim;
25470
+ _boundFromTransport = false;
25132
25471
  /** Lazily-built legacy shim; the loop lives in legacyInputRequiredShim.ts behind a narrow host contract. */
25133
25472
  _legacyInputRequiredShim() {
25134
25473
  return this._legacyShim ??= new LegacyInputRequiredShim({
@@ -25168,6 +25507,15 @@ var Server = class extends Protocol {
25168
25507
  /** Attaches to the given transport; rejects while this instance is connected to another one. */
25169
25508
  async connect(transport) {
25170
25509
  if (this.transport !== void 0) throw new SdkError(SdkErrorCode.AlreadyConnected, "Use a separate Server instance per connection: this instance is already connected to a transport. To connect it to a new transport, call close() first.");
25510
+ if (this._boundFromTransport) {
25511
+ this._boundFromTransport = false;
25512
+ this._negotiatedProtocolVersion = void 0;
25513
+ }
25514
+ const revision = perRequestClassificationOf(transport)?.revision;
25515
+ if (revision !== void 0 && !this._servedModernEra() && SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(revision) && modernProtocolVersions(this._supportedProtocolVersions).includes(revision)) {
25516
+ this._negotiatedProtocolVersion = revision;
25517
+ this._boundFromTransport = true;
25518
+ }
25171
25519
  await super.connect(transport);
25172
25520
  }
25173
25521
  /**
@@ -25478,8 +25826,8 @@ var Server = class extends Protocol {
25478
25826
  * @deprecated Read client identity from the per-request handler context instead: on
25479
25827
  * 2026-07-28 (per-request envelope) requests `ctx.mcpReq.envelope` carries the client's
25480
25828
  * declared capabilities, while on 2025-era connections this accessor keeps returning the
25481
- * `initialize`-scoped value. The accessor remains functional — instances serving the
25482
- * 2026-07-28 era are backfilled per request from the validated envelope.
25829
+ * `initialize`-scoped value. The accessor remains functional where `createMcpHandler`
25830
+ * serves the 2026-07-28 era: it backfills it per request from the validated envelope.
25483
25831
  */
25484
25832
  getClientCapabilities() {
25485
25833
  return this._clientCapabilities;
@@ -25490,8 +25838,8 @@ var Server = class extends Protocol {
25490
25838
  * @deprecated Read client identity from the per-request handler context instead: on
25491
25839
  * 2026-07-28 (per-request envelope) requests `ctx.mcpReq.envelope` carries the client's
25492
25840
  * name and version, while on 2025-era connections this accessor keeps returning the
25493
- * `initialize`-scoped value. The accessor remains functional — instances serving the
25494
- * 2026-07-28 era are backfilled per request from the validated envelope.
25841
+ * `initialize`-scoped value. The accessor remains functional where `createMcpHandler`
25842
+ * serves the 2026-07-28 era: it backfills it per request from the validated envelope.
25495
25843
  */
25496
25844
  getClientVersion() {
25497
25845
  return this._clientVersion;
@@ -26084,8 +26432,14 @@ var McpServer = class {
26084
26432
  if (updates.uri) this._registeredResources[updates.uri] = registeredResource;
26085
26433
  }
26086
26434
  if (updates.name !== void 0) registeredResource.name = updates.name;
26087
- if (updates.title !== void 0) registeredResource.title = updates.title;
26088
26435
  if (updates.metadata !== void 0) registeredResource.metadata = updates.metadata;
26436
+ if (updates.title !== void 0) {
26437
+ registeredResource.title = updates.title;
26438
+ registeredResource.metadata = {
26439
+ ...registeredResource.metadata,
26440
+ title: updates.title
26441
+ };
26442
+ }
26089
26443
  if (updates.callback !== void 0) registeredResource.readCallback = updates.callback;
26090
26444
  if (updates.scopeChallenge !== void 0) registeredResource.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
26091
26445
  if (updates.enabled !== void 0) registeredResource.enabled = updates.enabled;
@@ -26111,9 +26465,15 @@ var McpServer = class {
26111
26465
  delete this._registeredResourceTemplates[name];
26112
26466
  if (updates.name) this._registeredResourceTemplates[updates.name] = registeredResourceTemplate;
26113
26467
  }
26114
- if (updates.title !== void 0) registeredResourceTemplate.title = updates.title;
26115
26468
  if (updates.template !== void 0) registeredResourceTemplate.resourceTemplate = updates.template;
26116
26469
  if (updates.metadata !== void 0) registeredResourceTemplate.metadata = updates.metadata;
26470
+ if (updates.title !== void 0) {
26471
+ registeredResourceTemplate.title = updates.title;
26472
+ registeredResourceTemplate.metadata = {
26473
+ ...registeredResourceTemplate.metadata,
26474
+ title: updates.title
26475
+ };
26476
+ }
26117
26477
  if (updates.callback !== void 0) registeredResourceTemplate.readCallback = updates.callback;
26118
26478
  if (updates.scopeChallenge !== void 0) registeredResourceTemplate.scopeChallenge = updates.scopeChallenge === null ? void 0 : updates.scopeChallenge;
26119
26479
  if (updates.enabled !== void 0) registeredResourceTemplate.enabled = updates.enabled;
@@ -26372,234 +26732,298 @@ function convertOutputSchemaJson(outputSchema) {
26372
26732
  return;
26373
26733
  }
26374
26734
  }
26375
- /**
26376
- * Creates a type-safe prompt handler that captures the schema and callback in a closure.
26377
- * This eliminates the need for type assertions at the call site.
26378
- */
26379
- function createPromptHandler(name, argsSchema, callback) {
26380
- if (argsSchema) {
26381
- const typedCallback = callback;
26382
- return async (args, ctx) => {
26383
- const parseResult = await validateStandardSchema(argsSchema, args ?? {});
26384
- if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
26385
- return typedCallback(parseResult.data, ctx);
26735
+ /**
26736
+ * Creates a type-safe prompt handler that captures the schema and callback in a closure.
26737
+ * This eliminates the need for type assertions at the call site.
26738
+ */
26739
+ function createPromptHandler(name, argsSchema, callback) {
26740
+ if (argsSchema) {
26741
+ const typedCallback = callback;
26742
+ return async (args, ctx) => {
26743
+ const parseResult = await validateStandardSchema(argsSchema, args ?? {});
26744
+ if (!parseResult.success) throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid arguments for prompt ${name}: ${parseResult.error}`);
26745
+ return typedCallback(parseResult.data, ctx);
26746
+ };
26747
+ } else {
26748
+ const typedCallback = callback;
26749
+ return async (_args, ctx) => {
26750
+ return typedCallback(ctx);
26751
+ };
26752
+ }
26753
+ }
26754
+ function createCompletionResult(suggestions) {
26755
+ return { completion: {
26756
+ values: suggestions.map(String).slice(0, 100),
26757
+ total: suggestions.length,
26758
+ hasMore: suggestions.length > 100
26759
+ } };
26760
+ }
26761
+ const EMPTY_COMPLETION_RESULT = { completion: {
26762
+ values: [],
26763
+ hasMore: false
26764
+ } };
26765
+ /** @internal Gets the shape of a Zod object schema */
26766
+ function getSchemaShape(schema) {
26767
+ const candidate = schema;
26768
+ if (candidate.shape && typeof candidate.shape === "object") return candidate.shape;
26769
+ }
26770
+ /** @internal Checks if a Zod schema is optional */
26771
+ function isOptionalSchema(schema) {
26772
+ return schema?.type === "optional";
26773
+ }
26774
+ /** @internal Unwraps an optional Zod schema */
26775
+ function unwrapOptionalSchema(schema) {
26776
+ if (!isOptionalSchema(schema)) return schema;
26777
+ return schema.def?.innerType ?? schema;
26778
+ }
26779
+ //#endregion
26780
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/listenRouter-Bartj63y.mjs
26781
+ /**
26782
+ * A `ServerEventBus` backed by an in-process listener set.
26783
+ *
26784
+ * `publish()` delivers synchronously to the live listener set (a listener
26785
+ * unsubscribing itself mid-dispatch is safe; the entry's listen-router
26786
+ * listeners never unsubscribe peers). A throwing listener does not stop
26787
+ * delivery to the others.
26788
+ */
26789
+ var InMemoryServerEventBus = class {
26790
+ _listeners = /* @__PURE__ */ new Set();
26791
+ /**
26792
+ * @param onerror - Optional callback for errors thrown by listeners
26793
+ * during dispatch.
26794
+ */
26795
+ constructor(onerror) {
26796
+ this.onerror = onerror;
26797
+ }
26798
+ publish(event) {
26799
+ for (const listener of this._listeners) try {
26800
+ listener(event);
26801
+ } catch (error) {
26802
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
26803
+ }
26804
+ }
26805
+ subscribe(listener) {
26806
+ this._listeners.add(listener);
26807
+ let live = true;
26808
+ return () => {
26809
+ if (!live) return;
26810
+ live = false;
26811
+ this._listeners.delete(listener);
26812
+ };
26813
+ }
26814
+ /** The number of currently registered listeners (test/introspection only — the routers track capacity via their own open-subscription set). */
26815
+ get listenerCount() {
26816
+ return this._listeners.size;
26817
+ }
26818
+ };
26819
+ /** Build a {@linkcode ServerNotifier} over a bus. */
26820
+ function createServerNotifier(bus) {
26821
+ return {
26822
+ toolsChanged: () => bus.publish({ kind: "tools_list_changed" }),
26823
+ promptsChanged: () => bus.publish({ kind: "prompts_list_changed" }),
26824
+ resourcesChanged: () => bus.publish({ kind: "resources_list_changed" }),
26825
+ resourceUpdated: (uri) => bus.publish({
26826
+ kind: "resource_updated",
26827
+ uri
26828
+ })
26829
+ };
26830
+ }
26831
+ /**
26832
+ * Whether a `subscriptions/listen` filter accepts a given change event.
26833
+ *
26834
+ * Pure: no I/O, no mutation. The filter governs ONLY the four
26835
+ * subscription-gated change types — non-gated notifications never reach the
26836
+ * bus and are not modeled here.
26837
+ *
26838
+ * `resource_updated` matches only when `resourceSubscriptions` is present and
26839
+ * contains the event's URI exactly (per the spec: "for these resource URIs").
26840
+ */
26841
+ function listenFilterAccepts(filter, event) {
26842
+ switch (event.kind) {
26843
+ case "tools_list_changed": return filter.toolsListChanged === true;
26844
+ case "prompts_list_changed": return filter.promptsListChanged === true;
26845
+ case "resources_list_changed": return filter.resourcesListChanged === true;
26846
+ case "resource_updated": return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
26847
+ }
26848
+ }
26849
+ /**
26850
+ * The honored subset of a requested filter: keeps only the fields the client
26851
+ * explicitly opted in to (drops `false` and absent fields), narrowed against
26852
+ * the server's declared capabilities when supplied. The serving entry sends
26853
+ * this back in `notifications/subscriptions/acknowledged` so the ack reflects
26854
+ * what the server can actually deliver.
26855
+ *
26856
+ * - `toolsListChanged` is honored only when `capabilities.tools.listChanged`
26857
+ * is advertised; likewise `promptsListChanged` / `resourcesListChanged`.
26858
+ * - `resourceSubscriptions` is honored only when
26859
+ * `capabilities.resources.subscribe` is advertised.
26860
+ *
26861
+ * `capabilities` is optional on this pure helper for test convenience only —
26862
+ * both wired routers REQUIRE capabilities at the call site (the HTTP router's
26863
+ * `serve()` takes a required parameter; `StdioListenRouter.serve()` throws
26864
+ * before `setServerCapabilities()` was called), so the fail-open
26865
+ * `undefined → honor everything` branch is never reachable on a wired entry.
26866
+ */
26867
+ function honoredSubset(requested, capabilities) {
26868
+ const honored = {};
26869
+ const allow = (bit) => capabilities === void 0 || bit === true;
26870
+ if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
26871
+ if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
26872
+ if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
26873
+ if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
26874
+ return honored;
26875
+ }
26876
+ /** Map a {@linkcode ServerEvent} onto its wire notification `{method, params}`. */
26877
+ function serverEventToNotification(event) {
26878
+ switch (event.kind) {
26879
+ case "tools_list_changed": return {
26880
+ method: "notifications/tools/list_changed",
26881
+ ...event.params !== void 0 && { params: event.params }
26386
26882
  };
26387
- } else {
26388
- const typedCallback = callback;
26389
- return async (_args, ctx) => {
26390
- return typedCallback(ctx);
26883
+ case "prompts_list_changed": return {
26884
+ method: "notifications/prompts/list_changed",
26885
+ ...event.params !== void 0 && { params: event.params }
26886
+ };
26887
+ case "resources_list_changed": return {
26888
+ method: "notifications/resources/list_changed",
26889
+ ...event.params !== void 0 && { params: event.params }
26890
+ };
26891
+ case "resource_updated": return {
26892
+ method: "notifications/resources/updated",
26893
+ params: {
26894
+ ...event.params,
26895
+ uri: event.uri
26896
+ }
26391
26897
  };
26392
26898
  }
26393
26899
  }
26394
- function createCompletionResult(suggestions) {
26395
- return { completion: {
26396
- values: suggestions.map(String).slice(0, 100),
26397
- total: suggestions.length,
26398
- hasMore: suggestions.length > 100
26399
- } };
26400
- }
26401
- const EMPTY_COMPLETION_RESULT = { completion: {
26402
- values: [],
26403
- hasMore: false
26404
- } };
26405
- /** @internal Gets the shape of a Zod object schema */
26406
- function getSchemaShape(schema) {
26407
- const candidate = schema;
26408
- if (candidate.shape && typeof candidate.shape === "object") return candidate.shape;
26409
- }
26410
- /** @internal Checks if a Zod schema is optional */
26411
- function isOptionalSchema(schema) {
26412
- return schema?.type === "optional";
26900
+ /** Default capacity guard: refuse a new subscription when this many are already open. */
26901
+ const DEFAULT_MAX_SUBSCRIPTIONS = 1024;
26902
+ function jsonRpcError(id, code, message) {
26903
+ return Response.json({
26904
+ jsonrpc: "2.0",
26905
+ error: {
26906
+ code,
26907
+ message
26908
+ },
26909
+ id
26910
+ }, { status: 200 });
26413
26911
  }
26414
- /** @internal Unwraps an optional Zod schema */
26415
- function unwrapOptionalSchema(schema) {
26416
- if (!isOptionalSchema(schema)) return schema;
26417
- return schema.def?.innerType ?? schema;
26912
+ /** Stamp the subscription id onto a notification's `_meta`. Non-mutating. */
26913
+ function stampSubscriptionId(notification, subscriptionId) {
26914
+ return {
26915
+ method: notification.method,
26916
+ params: {
26917
+ ...notification.params,
26918
+ _meta: {
26919
+ ...notification.params?._meta,
26920
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId
26921
+ }
26922
+ }
26923
+ };
26418
26924
  }
26419
- //#endregion
26420
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/index.mjs
26421
26925
  /**
26422
- * The per-request micro-transport: a real, connected `Transport` whose whole
26423
- * lifetime is one HTTP exchange. See the module documentation for the
26424
- * response shapes it produces.
26926
+ * Read the requested filter off a `subscriptions/listen` request body.
26927
+ * Returns the validated filter, or `undefined` when `params.notifications`
26928
+ * is absent or fails the schema (the caller answers `-32602` — the spec
26929
+ * marks `notifications` REQUIRED on the listen request).
26425
26930
  */
26426
- var PerRequestHTTPServerTransport = class {
26427
- onclose;
26428
- onerror;
26429
- onmessage;
26430
- _classification;
26431
- _responseMode;
26432
- _started = false;
26433
- _used = false;
26434
- _closed = false;
26435
- _terminalDelivered = false;
26436
- /**
26437
- * `true` only while the inbound message is being delivered synchronously
26438
- * to the connected protocol layer. The pre-handler gates (the era
26439
- * registry gate, the edge→instance handoff check, the missing-handler
26440
- * rejection) answer inside this window; request handlers always run
26441
- * after it (the protocol layer defers them to a microtask). An error
26442
- * sent inside the window is therefore ladder-originated, and an error
26443
- * sent after it is handler-produced.
26444
- */
26445
- _dispatchWindowOpen = false;
26446
- _requestId;
26447
- _deferredResponse;
26448
- _sse;
26449
- _abortCleanup;
26450
- _keepAliveMs;
26451
- constructor(options) {
26452
- this._classification = options.classification;
26453
- this._responseMode = options.responseMode ?? "auto";
26454
- this._keepAliveMs = options.keepAliveMs ?? 15e3;
26455
- }
26456
- async start() {
26457
- if (this._started) throw new Error("PerRequestHTTPServerTransport is already started");
26458
- this._started = true;
26459
- }
26460
- /**
26461
- * Serves the single exchange: delivers the classified message to the
26462
- * connected server instance and resolves with the HTTP response.
26463
- *
26464
- * Throws when called a second time (the transport is strictly
26465
- * single-use), or before a server has been connected to the transport.
26466
- * The returned promise rejects with a connection-closed error when the
26467
- * transport is closed before a response was produced (for example because
26468
- * the client disconnected).
26469
- */
26470
- async handleMessage(message, extra) {
26471
- if (this._used) throw new Error("PerRequestHTTPServerTransport serves exactly one exchange; construct a new transport per request");
26472
- if (!this._started || this.onmessage === void 0) throw new Error("PerRequestHTTPServerTransport is not connected: connect a server to this transport before handling a message");
26473
- if (this._closed) throw new Error("PerRequestHTTPServerTransport is closed");
26474
- this._used = true;
26475
- const signal = extra?.request?.signal;
26476
- if (signal?.aborted) {
26477
- await this.close();
26478
- throw new SdkError(SdkErrorCode.ConnectionClosed, "The request was aborted before it could be handled");
26931
+ function parseListenFilter(message) {
26932
+ const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
26933
+ return outcome.ok ? outcome.value.params?.notifications : void 0;
26934
+ }
26935
+ function createListenRouter(options) {
26936
+ const { bus, onerror } = options;
26937
+ const maxSubscriptions = options.maxSubscriptions ?? 1024;
26938
+ const keepAliveMs = options.keepAliveMs ?? 15e3;
26939
+ const open = /* @__PURE__ */ new Set();
26940
+ function serve(message, signal, capabilities, serverInfo) {
26941
+ if (open.size >= maxSubscriptions) {
26942
+ onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
26943
+ return jsonRpcError(message.id, -32603, "Subscription limit reached");
26479
26944
  }
26480
- const messageExtra = {
26481
- classification: this._classification,
26482
- ...extra?.request !== void 0 && { request: extra.request },
26483
- ...extra?.authInfo !== void 0 && { authInfo: extra.authInfo }
26484
- };
26485
- if (isJSONRPCRequest(message)) {
26486
- this._requestId = message.id;
26487
- let resolve;
26488
- let reject;
26489
- const promise = new Promise((promiseResolve, promiseReject) => {
26490
- resolve = promiseResolve;
26491
- reject = promiseReject;
26492
- });
26493
- this._deferredResponse = {
26494
- promise,
26495
- resolve,
26496
- reject,
26497
- settled: false
26498
- };
26499
- if (signal !== void 0) {
26500
- const onAbort = () => void this.close();
26501
- signal.addEventListener("abort", onAbort, { once: true });
26502
- this._abortCleanup = () => signal.removeEventListener("abort", onAbort);
26503
- }
26504
- this._dispatchWindowOpen = true;
26945
+ const filter = parseListenFilter(message);
26946
+ if (filter === void 0) return jsonRpcError(message.id, -32602, "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter");
26947
+ const honored = honoredSubset(filter, capabilities);
26948
+ const subscriptionId = message.id;
26949
+ const encoder = new TextEncoder();
26950
+ let controller;
26951
+ let closed = false;
26952
+ let unsubscribe;
26953
+ let keepAliveTimer;
26954
+ let abortCleanup;
26955
+ const writeFrame = (frame) => {
26956
+ if (closed) return;
26505
26957
  try {
26506
- this.onmessage(message, messageExtra);
26507
- } finally {
26508
- this._dispatchWindowOpen = false;
26509
- }
26510
- if (this._responseMode === "sse" && !this._closed && !this._deferredResponse.settled) this.upgradeToSse();
26511
- return promise;
26512
- }
26513
- this.onmessage(message, messageExtra);
26514
- return new Response(null, { status: 202 });
26515
- }
26516
- async send(message, options) {
26517
- if (this._closed) return;
26518
- const isResponse = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message);
26519
- const relatedId = isResponse ? message.id : options?.relatedRequestId;
26520
- if (this._requestId === void 0 || relatedId === void 0 || relatedId !== this._requestId) {
26521
- if (isResponse) this.onerror?.(/* @__PURE__ */ new Error(`Received a response for an unknown request id: ${String(message.id)}`));
26522
- return;
26523
- }
26524
- if (isResponse) {
26525
- if (this._terminalDelivered) return;
26526
- this._terminalDelivered = true;
26527
- const errorCode = isJSONRPCErrorResponse(message) ? message.error.code : void 0;
26528
- const ladderStatus = errorCode !== void 0 && (this._dispatchWindowOpen || errorCode === ProtocolErrorCode.MissingRequiredClientCapability) ? LADDER_ERROR_HTTP_STATUS[errorCode] : void 0;
26529
- if (ladderStatus !== void 0 && this._sse === void 0) {
26530
- this.settleResponse(Response.json(message, {
26531
- status: ladderStatus,
26532
- headers: { "Content-Type": "application/json" }
26533
- }));
26534
- queueMicrotask(() => void this.close());
26535
- return;
26536
- }
26537
- if (this._sse !== void 0 || this._responseMode === "sse") {
26538
- if (this._sse === void 0) this.upgradeToSse();
26539
- this.writeMessageFrame(message);
26540
- this.finalizeStream();
26541
- return;
26958
+ controller.enqueue(encoder.encode(frame));
26959
+ } catch (error) {
26960
+ onerror?.(error instanceof Error ? error : new Error(String(error)));
26542
26961
  }
26543
- this.settleResponse(Response.json(message, {
26544
- status: 200,
26545
- headers: { "Content-Type": "application/json" }
26546
- }));
26547
- queueMicrotask(() => void this.close());
26548
- return;
26549
- }
26550
- if (this._responseMode === "json") return;
26551
- if (this._sse === void 0) this.upgradeToSse();
26552
- this.writeMessageFrame(message);
26553
- }
26554
- /**
26555
- * Writes an SSE comment frame (a keep-alive heartbeat). Dropped when the
26556
- * exchange is not currently streaming.
26557
- */
26558
- writeCommentFrame(comment) {
26559
- if (this._closed || this._sse === void 0 || this._sse.closed) return;
26560
- const frame = comment.split("\n").map((line) => `: ${line}`).join("\n");
26561
- this.writeFrame(`${frame}\n\n`);
26562
- }
26563
- async close() {
26564
- if (this._closed) return;
26565
- this._closed = true;
26566
- this._abortCleanup?.();
26567
- this._abortCleanup = void 0;
26568
- if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
26569
- if (this._sse !== void 0 && !this._sse.closed) {
26570
- this._sse.closed = true;
26962
+ };
26963
+ const writeNotification = (method, params) => {
26964
+ writeFrame(`event: message\ndata: ${JSON.stringify({
26965
+ jsonrpc: "2.0",
26966
+ method,
26967
+ params
26968
+ })}\n\n`);
26969
+ };
26970
+ const teardown = (graceful) => {
26971
+ if (closed) return;
26972
+ if (graceful) writeFrame(`event: message\ndata: ${JSON.stringify({
26973
+ jsonrpc: "2.0",
26974
+ id: subscriptionId,
26975
+ result: {
26976
+ resultType: "complete",
26977
+ _meta: {
26978
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId,
26979
+ [SERVER_INFO_META_KEY]: serverInfo
26980
+ }
26981
+ }
26982
+ })}\n\n`);
26983
+ closed = true;
26571
26984
  try {
26572
- this._sse.controller.close();
26985
+ unsubscribe?.();
26986
+ } catch (error) {
26987
+ onerror?.(error instanceof Error ? error : new Error(String(error)));
26988
+ }
26989
+ if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
26990
+ abortCleanup?.();
26991
+ open.delete(teardown);
26992
+ try {
26993
+ controller.close();
26573
26994
  } catch {}
26574
- }
26575
- if (this._deferredResponse !== void 0 && !this._deferredResponse.settled) {
26576
- this._deferredResponse.settled = true;
26577
- this._deferredResponse.reject(new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed before a response was produced"));
26578
- }
26579
- this.onclose?.();
26580
- }
26581
- settleResponse(response) {
26582
- if (this._deferredResponse === void 0 || this._deferredResponse.settled) return;
26583
- this._deferredResponse.settled = true;
26584
- this._deferredResponse.resolve(response);
26585
- }
26586
- upgradeToSse() {
26587
- let controller;
26995
+ };
26588
26996
  const readable = new ReadableStream({
26589
- start: (streamController) => {
26997
+ start(streamController) {
26590
26998
  controller = streamController;
26999
+ const ack = stampSubscriptionId({
27000
+ method: "notifications/subscriptions/acknowledged",
27001
+ params: { notifications: honored }
27002
+ }, subscriptionId);
27003
+ writeNotification(ack.method, ack.params);
27004
+ if (Object.keys(honored).length === 0) {
27005
+ teardown(true);
27006
+ return;
27007
+ }
27008
+ unsubscribe = bus.subscribe((event) => {
27009
+ if (closed || !listenFilterAccepts(honored, event)) return;
27010
+ const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
27011
+ writeNotification(note.method, note.params);
27012
+ });
27013
+ keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
27014
+ open.add(teardown);
26591
27015
  },
26592
- cancel: () => {
26593
- this.close();
27016
+ cancel() {
27017
+ teardown(false);
26594
27018
  }
26595
27019
  });
26596
- this._sse = {
26597
- controller,
26598
- encoder: new TextEncoder(),
26599
- closed: false
26600
- };
26601
- this._sse.keepAliveTimer = armSseKeepAlive(this._keepAliveMs, () => this.writeCommentFrame("keepalive"));
26602
- this.settleResponse(new Response(readable, {
27020
+ if (signal !== void 0) if (signal.aborted) teardown(false);
27021
+ else {
27022
+ const onAbort = () => teardown(false);
27023
+ signal.addEventListener("abort", onAbort, { once: true });
27024
+ abortCleanup = () => signal.removeEventListener("abort", onAbort);
27025
+ }
27026
+ return new Response(readable, {
26603
27027
  status: 200,
26604
27028
  headers: {
26605
27029
  "Content-Type": "text/event-stream",
@@ -26607,30 +27031,170 @@ var PerRequestHTTPServerTransport = class {
26607
27031
  Connection: "keep-alive",
26608
27032
  "X-Accel-Buffering": "no"
26609
27033
  }
26610
- }));
27034
+ });
26611
27035
  }
26612
- finalizeStream() {
26613
- if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
26614
- if (this._sse !== void 0 && !this._sse.closed) {
26615
- this._sse.closed = true;
26616
- try {
26617
- this._sse.controller.close();
26618
- } catch {}
27036
+ return {
27037
+ serve,
27038
+ closeAll() {
27039
+ for (const teardown of open) teardown(true);
27040
+ },
27041
+ get openCount() {
27042
+ return open.size;
26619
27043
  }
26620
- queueMicrotask(() => void this.close());
27044
+ };
27045
+ }
27046
+ const CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
27047
+ "notifications/tools/list_changed",
27048
+ "notifications/prompts/list_changed",
27049
+ "notifications/resources/list_changed",
27050
+ "notifications/resources/updated"
27051
+ ]);
27052
+ /**
27053
+ * Per-connection listen state for the stdio entry. One instance is held by
27054
+ * `serveStdio` for the connection lifetime; it routes inbound
27055
+ * `subscriptions/listen` / `notifications/cancelled` and rewrites outbound
27056
+ * change notifications onto the active subscriptions. No bus — the long-lived
27057
+ * pinned instance's existing `send*ListChanged()` calls feed straight into
27058
+ * `routeOutbound()`.
27059
+ */
27060
+ var StdioListenRouter = class {
27061
+ /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
27062
+ _subs = /* @__PURE__ */ new Map();
27063
+ /**
27064
+ * The serving instance's declared capabilities. Filled in by the entry
27065
+ * once the modern instance is constructed (the router is created before
27066
+ * the instance exists), so the acknowledged filter is narrowed against
27067
+ * what the server can actually deliver.
27068
+ */
27069
+ _serverCapabilities;
27070
+ /**
27071
+ * The serving instance's identity, stamped onto the graceful-close
27072
+ * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
27073
+ * `ResultMetaObject`). Handed over together with the capabilities.
27074
+ */
27075
+ _serverInfo;
27076
+ constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
27077
+ this._maxSubscriptions = _maxSubscriptions;
27078
+ this._serverCapabilities = serverCapabilities;
27079
+ this._serverInfo = serverInfo;
26621
27080
  }
26622
- writeMessageFrame(message) {
26623
- this.writeFrame(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
27081
+ /**
27082
+ * Record the serving instance's declared capabilities and identity once
27083
+ * it has been constructed. Called by `serveStdio`'s connect path;
27084
+ * subsequent `serve()` calls narrow the honored filter against the
27085
+ * capabilities, and `teardownAll()` stamps the identity.
27086
+ */
27087
+ setServerCapabilities(capabilities, serverInfo) {
27088
+ this._serverCapabilities = capabilities;
27089
+ if (serverInfo !== void 0) this._serverInfo = serverInfo;
26624
27090
  }
26625
- writeFrame(frame) {
26626
- if (this._sse === void 0 || this._sse.closed) return;
26627
- try {
26628
- this._sse.controller.enqueue(this._sse.encoder.encode(frame));
26629
- } catch (error) {
26630
- this.onerror?.(/* @__PURE__ */ new Error(`Failed to write to the response stream: ${error}`));
26631
- }
27091
+ /** Whether `id` is an active listen subscription on this connection. */
27092
+ has(id) {
27093
+ return this._subs.has(id);
27094
+ }
27095
+ /**
27096
+ * Serve one inbound `subscriptions/listen` request: registers the
27097
+ * subscription and returns the stamped acknowledged notification (or, on
27098
+ * capacity / params rejection, the in-band JSON-RPC error response).
27099
+ *
27100
+ * @throws when called before {@linkcode setServerCapabilities} (or the
27101
+ * constructor) has supplied the serving instance's capabilities. Honoring a
27102
+ * filter without knowing the server's advertised capabilities would fail
27103
+ * open (deliver unadvertised types); the entry guarantees capabilities are
27104
+ * set before any listen request is routed here.
27105
+ */
27106
+ serve(message) {
27107
+ if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
27108
+ if (this._subs.size >= this._maxSubscriptions) return {
27109
+ jsonrpc: "2.0",
27110
+ id: message.id,
27111
+ error: {
27112
+ code: -32603,
27113
+ message: "Subscription limit reached"
27114
+ }
27115
+ };
27116
+ const filter = parseListenFilter(message);
27117
+ if (filter === void 0) return {
27118
+ jsonrpc: "2.0",
27119
+ id: message.id,
27120
+ error: {
27121
+ code: -32602,
27122
+ message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
27123
+ }
27124
+ };
27125
+ const honored = honoredSubset(filter, this._serverCapabilities);
27126
+ this._subs.set(message.id, honored);
27127
+ return stampSubscriptionId({
27128
+ method: "notifications/subscriptions/acknowledged",
27129
+ params: { notifications: honored }
27130
+ }, message.id);
27131
+ }
27132
+ /**
27133
+ * Tear down one subscription (inbound `notifications/cancelled`). Returns
27134
+ * `true` when a subscription was removed. After this call NOTHING further
27135
+ * is delivered for that subscription id (the post-cancel hardening).
27136
+ */
27137
+ cancel(id) {
27138
+ return this._subs.delete(id);
27139
+ }
27140
+ /**
27141
+ * Route an outbound notification through the active subscriptions.
27142
+ *
27143
+ * - For a subscription-gated change notification, returns one stamped copy
27144
+ * per subscription that opted in to it (an empty array means it is
27145
+ * dropped — the modern era never delivers an un-requested change type).
27146
+ * - For any other outbound message, returns `'passthrough'` (the entry
27147
+ * forwards it as-is).
27148
+ */
27149
+ routeOutbound(message) {
27150
+ if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
27151
+ const uriParam = message.params?.["uri"];
27152
+ const uri = typeof uriParam === "string" ? uriParam : void 0;
27153
+ const event = notificationToServerEvent(message.method, uri);
27154
+ const out = [];
27155
+ for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
27156
+ method: message.method,
27157
+ params: message.params ?? {}
27158
+ }, subscriptionId));
27159
+ return out;
27160
+ }
27161
+ /**
27162
+ * Server-side graceful teardown of every active subscription: returns the
27163
+ * empty `subscriptions/listen` JSON-RPC result for each subscription id —
27164
+ * the spec's graceful-close signal, `_meta` carrying the subscription id
27165
+ * and the serving instance's identity — for the entry to emit before
27166
+ * closing the wire. Clears the set so nothing further is delivered.
27167
+ */
27168
+ teardownAll() {
27169
+ const out = [];
27170
+ for (const id of this._subs.keys()) out.push({
27171
+ jsonrpc: "2.0",
27172
+ id,
27173
+ result: {
27174
+ resultType: "complete",
27175
+ _meta: {
27176
+ [SUBSCRIPTION_ID_META_KEY]: id,
27177
+ ...this._serverInfo !== void 0 && { ["io.modelcontextprotocol/serverInfo"]: this._serverInfo }
27178
+ }
27179
+ }
27180
+ });
27181
+ this._subs.clear();
27182
+ return out;
26632
27183
  }
26633
27184
  };
27185
+ function notificationToServerEvent(method, uri) {
27186
+ switch (method) {
27187
+ case "notifications/tools/list_changed": return { kind: "tools_list_changed" };
27188
+ case "notifications/prompts/list_changed": return { kind: "prompts_list_changed" };
27189
+ case "notifications/resources/list_changed": return { kind: "resources_list_changed" };
27190
+ default: return {
27191
+ kind: "resource_updated",
27192
+ uri: uri ?? ""
27193
+ };
27194
+ }
27195
+ }
27196
+ //#endregion
27197
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/index.mjs
26634
27198
  /**
26635
27199
  * Serves one classified inbound message on the given server instance and
26636
27200
  * returns the HTTP response for the exchange.
@@ -27430,7 +27994,10 @@ function jsonRpcErrorResponse(httpStatus, code, message, data, id = null) {
27430
27994
  ...data !== void 0 && { data }
27431
27995
  },
27432
27996
  id
27433
- }, { status: httpStatus });
27997
+ }, {
27998
+ status: httpStatus,
27999
+ ...httpStatus === 405 && { headers: { Allow: "POST" } }
28000
+ });
27434
28001
  }
27435
28002
  function rejectionResponse(rejection, id = null) {
27436
28003
  return jsonRpcErrorResponse(rejection.httpStatus, rejection.code, rejection.message, rejection.data, id);
@@ -27793,7 +28360,7 @@ function createMcpHandler(factory, options = {}) {
27793
28360
  const inputSchema = toolName === void 0 ? void 0 : product.toolInputSchemaJson(toolName);
27794
28361
  if (inputSchema !== void 0) {
27795
28362
  const scan = scanXMcpHeaderDeclarations(inputSchema);
27796
- if (scan.valid && scan.declarations.length > 0) {
28363
+ if (scan.declarations !== void 0 && scan.declarations.length > 0) {
27797
28364
  const rejection = validateMcpParamHeaders(scan.declarations, callParams?.arguments, request.headers);
27798
28365
  if (rejection !== void 0) {
27799
28366
  product.close().catch(reportError);
@@ -28419,7 +28986,7 @@ const memoisedUntilFailure = (load) => {
28419
28986
  * processes never touch an article, and it is the heaviest code in the bundle.
28420
28987
  * `tests/unit/module-graph.test.ts` keeps it off the static graph.
28421
28988
  */
28422
- const loadArticleFormat = memoisedUntilFailure(() => import("./article-sections-CzTODYsS.js"));
28989
+ const loadArticleFormat = memoisedUntilFailure(() => import("./article-sections-DA7_Jm2H.js"));
28423
28990
  //#endregion
28424
28991
  //#region src/utils/formatting.ts
28425
28992
  const truncateIfNeeded = (text, advice = "Use pagination or filters to reduce results.") => {
@@ -33761,9 +34328,9 @@ const registerSingleProxies = (registration, tools) => {
33761
34328
  * (`dev/reload.ts`) can keep this long-lived shell (and its transport/session)
33762
34329
  * alive while swapping the toolset underneath it via `registerToolset`.
33763
34330
  */
33764
- const createServerShell = (config, requestStateCodec = createRequestStateCodec({})) => {
34331
+ const createServerShell = (config, requestStateCodec = createRequestStateCodec({}), extraInstructions) => {
33765
34332
  const pkg = readPackageInfo();
33766
- const instructions = buildInstructions(config);
34333
+ const instructions = [buildInstructions(config), extraInstructions].filter((block) => block !== void 0).join("\n\n") || void 0;
33767
34334
  return new McpServer({
33768
34335
  name: pkg.name,
33769
34336
  version: pkg.version
@@ -34081,8 +34648,8 @@ const toolEnv = (config) => ({
34081
34648
  subdomain: config.subdomain,
34082
34649
  brandIds: config.brandIds
34083
34650
  });
34084
- const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized, { caches = createServerCaches(config, getToken, onUnauthorized), requestStateCodec, slaRuntime = createSlaRuntime(config), listens, getBackgroundToken, alertPolling = "server", tools = createAllTools(toolEnv(config)) } = {}) => {
34085
- const server = createServerShell(config, requestStateCodec);
34651
+ const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized, { caches = createServerCaches(config, getToken, onUnauthorized), requestStateCodec, slaRuntime = createSlaRuntime(config), listens, getBackgroundToken, alertPolling = "server", tools = createAllTools(toolEnv(config)), extraInstructions, decorate } = {}) => {
34652
+ const server = createServerShell(config, requestStateCodec, extraInstructions);
34086
34653
  const sla = createSlaService(slaSettings(config), slaRuntime, caches.resolveUserId);
34087
34654
  registerToolset(server, {
34088
34655
  config,
@@ -34104,9 +34671,12 @@ const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized
34104
34671
  installAlertSubscriptions(server, config, reader, logger, listens);
34105
34672
  }
34106
34673
  }
34674
+ decorate?.(server);
34107
34675
  return server;
34108
34676
  };
34109
- const defaultServerFactory = ({ config, getToken, logger, onUnauthorized, caches, tools, requestStateCodec, slaRuntime, listens, getBackgroundToken, alertPolling }) => createMcpServer(config, getToken, logger, onUnauthorized, {
34677
+ const defaultServerFactory = ({ config, getToken, logger, onUnauthorized, caches, tools, requestStateCodec, slaRuntime, listens, getBackgroundToken, alertPolling, extraInstructions, decorate }) => createMcpServer(config, getToken, logger, onUnauthorized, {
34678
+ extraInstructions,
34679
+ decorate,
34110
34680
  ...caches ? { caches } : {},
34111
34681
  ...tools ? { tools } : {},
34112
34682
  ...slaRuntime ? { slaRuntime } : {},
@@ -34116,7 +34686,7 @@ const defaultServerFactory = ({ config, getToken, logger, onUnauthorized, caches
34116
34686
  requestStateCodec
34117
34687
  });
34118
34688
  //#endregion
34119
- //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
34689
+ //#region node_modules/.pnpm/@modelcontextprotocol+server@2.4.0/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
34120
34690
  /**
34121
34691
  * Marks a stdout `'error'` listener that a closed transport left attached purely to
34122
34692
  * swallow late write failures (see {@link StdioServerTransport.close}). A fresh
@@ -35146,7 +35716,7 @@ const installShutdown = (options) => {
35146
35716
  //#endregion
35147
35717
  //#region src/index.ts
35148
35718
  const buildStdioTokenStore = async (config, logger) => {
35149
- const { createTokenStore } = await import("./token-store-BqwB-30K.js");
35719
+ const { createTokenStore } = await import("./token-store-EwRwZiJO.js");
35150
35720
  return createTokenStore({
35151
35721
  subdomain: config.subdomain,
35152
35722
  oauthClientId: config.oauthClientId,
@@ -35196,7 +35766,7 @@ const main = async () => {
35196
35766
  logger,
35197
35767
  cleanup: async () => (await starting?.catch(() => void 0))?.close()
35198
35768
  });
35199
- const { startHttpTransport } = await import("./http-D-Xy9BWD.js");
35769
+ const { startHttpTransport } = await import("./http-DuRcQrOc.js");
35200
35770
  starting = startHttpTransport(config, logger);
35201
35771
  await starting;
35202
35772
  };
@@ -35206,4 +35776,4 @@ main().catch((error) => {
35206
35776
  process.exit(1);
35207
35777
  });
35208
35778
  //#endregion
35209
- export { STARTUP_DOCS as A, WebStandardStreamableHTTPServerTransport as C, OAuthError as D, verifyBearerToken as E, errnoCode as M, OAuthErrorCode as O, DEFAULT_MAX_REQUEST_BODY_SIZE as S, isLegacyRequest as T, zendeskGet as _, toolEnv as a, DEFAULT_CALLBACK_PORT as b, clearToken as c, resolveTokenPath as d, saveToken as f, createAllTools as g, silentLogger as h, defaultServerFactory as i, createStartupError as j, compareCodeUnits as k, configDir as l, createRequestStateCodec as m, listenedUris as n, watchGrantAlerts as o, signInRequiredError as p, createServerCaches as r, startSlaRuntime as s, createListenFeed as t, loadToken as u, REQUEST_TIMEOUT_MS as v, createMcpHandler as w, getOAuthUrls as x, deadlineSignal as y };
35779
+ export { _enum as $, ResourceTemplate as A, stampErrorBrands as B, DEFAULT_CALLBACK_PORT as C, createMcpHandler as D, WebStandardStreamableHTTPServerTransport as E, ProtocolErrorCode as F, ResultSchema as G, PaginatedRequestParamsSchema as H, ResourceNotFoundError as I, toStrictStandardSchema as J, buildInstructions as K, assertValidCacheHint as L, OAuthError as M, OAuthErrorCode as N, isLegacyRequest as O, ProtocolError as P, errnoCode as Q, brandedHasInstance as R, deadlineSignal as S, DEFAULT_MAX_REQUEST_BODY_SIZE as T, PaginatedResultSchema as U, BaseRequestParamsSchema as V, ResourceSchema as W, STARTUP_DOCS as X, compareCodeUnits as Y, createStartupError as Z, createAllTools as _, toolEnv as a, object as at, zendeskGet as b, clearToken as c, union as ct, resolveTokenPath as d, array as et, saveToken as f, silentLogger as g, readPackageInfo as h, defaultServerFactory as i, number$1 as it, verifyBearerToken as j, McpServer as k, configDir as l, unknown as lt, createRequestStateCodec as m, listenedUris as n, literal as nt, watchGrantAlerts as o, record as ot, signInRequiredError as p, Namespace as q, createServerCaches as r, looseObject as rt, startSlaRuntime as s, string as st, createListenFeed as t, boolean as tt, loadToken as u, createTopologyProvider as v, getOAuthUrls as w, REQUEST_TIMEOUT_MS as x, ZendeskApiError as y, isModernProtocolVersion as z };