@fruggr/zendesk-mcp-server 3.3.1 → 3.4.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import { parseArgs } from "node:util";
4
- import { randomUUID } from "node:crypto";
4
+ import { randomBytes, randomUUID } from "node:crypto";
5
5
  import minpath, { dirname, join } from "node:path";
6
6
  import { fileURLToPath, fileURLToPath as urlToPath, pathToFileURL } from "node:url";
7
7
  import minproc, { default as process$2 } from "node:process";
@@ -39,7 +39,7 @@ var __toESM$1 = (mod, isNodeMode, target) => (target = mod != null ? __create$1(
39
39
  }) : target, mod));
40
40
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
41
41
  //#endregion
42
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js
42
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/util.js
43
43
  function getEnumValues(entries) {
44
44
  const numericValues = Object.values(entries).filter((v) => typeof v === "number");
45
45
  return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);
@@ -189,7 +189,7 @@ const allowsEval = /* @__PURE__*/ cached$1(() => {
189
189
  return false;
190
190
  }
191
191
  });
192
- function isPlainObject$3(o) {
192
+ function isPlainObject$9(o) {
193
193
  if (isObject(o) === false) return false;
194
194
  const ctor = o.constructor;
195
195
  if (ctor === void 0) return true;
@@ -200,7 +200,7 @@ function isPlainObject$3(o) {
200
200
  return true;
201
201
  }
202
202
  function shallowClone(o) {
203
- if (isPlainObject$3(o)) return { ...o };
203
+ if (isPlainObject$9(o)) return { ...o };
204
204
  if (Array.isArray(o)) return [...o];
205
205
  if (o instanceof Map) return new Map(o);
206
206
  if (o instanceof Set) return new Set(o);
@@ -288,7 +288,7 @@ function omit(schema, mask) {
288
288
  }));
289
289
  }
290
290
  function extend$1(schema, shape) {
291
- if (!isPlainObject$3(shape)) throw new Error("Invalid input to extend: expected a plain object");
291
+ if (!isPlainObject$9(shape)) throw new Error("Invalid input to extend: expected a plain object");
292
292
  const checks = schema._zod.def.checks;
293
293
  if (checks && checks.length > 0) {
294
294
  const existingShape = sourceShape(schema);
@@ -303,7 +303,7 @@ function extended(schema, shape) {
303
303
  return newShape;
304
304
  }
305
305
  function safeExtend(schema, shape) {
306
- if (!isPlainObject$3(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
306
+ if (!isPlainObject$9(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
307
307
  return clone$1(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));
308
308
  }
309
309
  function merge$2(a, b) {
@@ -364,6 +364,17 @@ function prefixIssues(path, issues) {
364
364
  function unwrapMessage(message) {
365
365
  return typeof message === "string" ? message : message?.message;
366
366
  }
367
+ /** The leading size checks of `defChecks` that set an upper bound, each paired with it. */
368
+ function sizeCaps(defChecks) {
369
+ const caps = [];
370
+ for (const ch of defChecks ?? []) {
371
+ const d = ch._zod.def;
372
+ if (!/_(length|size)$|_equals$/.test(d.check)) break;
373
+ const max = d.maximum ?? d.length ?? d.size;
374
+ if (max >= 0) caps.push([max, ch]);
375
+ }
376
+ return caps;
377
+ }
367
378
  function attachSchema(issues, start, inst) {
368
379
  var _a;
369
380
  for (let i = start; i < issues.length; i++) (_a = issues[i]).schema ?? (_a.schema = inst);
@@ -580,7 +591,7 @@ function constantCatch(value) {
580
591
  return fn;
581
592
  }
582
593
  //#endregion
583
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/core.js
594
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/core.js
584
595
  var _a$4;
585
596
  /** A special constant with type `never` */
586
597
  const NEVER = /*@__PURE__*/ Object.freeze({ status: "aborted" });
@@ -688,7 +699,7 @@ function config(newConfig) {
688
699
  return globalConfig;
689
700
  }
690
701
  //#endregion
691
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/doc.js
702
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/doc.js
692
703
  var Doc = class {
693
704
  constructor(args = [], closed = {}) {
694
705
  this.content = [];
@@ -722,7 +733,7 @@ var Doc = class {
722
733
  }
723
734
  };
724
735
  //#endregion
725
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/memoizer.js
736
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/memoizer.js
726
737
  var $ZodCyclicError = class extends Error {
727
738
  constructor() {
728
739
  super(`Cannot parse a reference cycle that closes through a transform`);
@@ -984,7 +995,7 @@ function isBackEdge(ctx, value) {
984
995
  return backEdges !== void 0 && isRef(value) && backEdges.has(value);
985
996
  }
986
997
  //#endregion
987
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/regexes.js
998
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/regexes.js
988
999
  /**
989
1000
  * @deprecated CUID v1 is deprecated by its authors due to information leakage
990
1001
  * (timestamps embedded in the id). Use {@link cuid2} instead.
@@ -1059,7 +1070,7 @@ const _null$2 = /^null$/i;
1059
1070
  const lowercase = /^[^A-Z]*$/;
1060
1071
  const uppercase = /^[^a-z]*$/;
1061
1072
  //#endregion
1062
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/checks.js
1073
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/checks.js
1063
1074
  const $ZodCheck = /*@__PURE__*/ $constructor("$ZodCheck", (inst, def) => {
1064
1075
  var _a;
1065
1076
  inst._zod ?? (inst._zod = {});
@@ -1355,7 +1366,7 @@ const $ZodCheckOverwrite = /*@__PURE__*/ $constructor("$ZodCheckOverwrite", (ins
1355
1366
  };
1356
1367
  });
1357
1368
  //#endregion
1358
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/errors.js
1369
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/errors.js
1359
1370
  function _getMessage() {
1360
1371
  const internals = this._zod;
1361
1372
  internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));
@@ -1471,7 +1482,7 @@ function formatError(error, mapper = (issue) => issue.message) {
1471
1482
  return fieldErrors;
1472
1483
  }
1473
1484
  //#endregion
1474
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/parse.js
1485
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/parse.js
1475
1486
  function finalizeParams(callee, params) {
1476
1487
  return {
1477
1488
  callee: params?.callee ?? callee,
@@ -1674,14 +1685,14 @@ const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
1674
1685
  return _safeParseAsync(_Err)(schema, value, _ctx);
1675
1686
  };
1676
1687
  //#endregion
1677
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/versions.js
1688
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/versions.js
1678
1689
  const version = {
1679
1690
  major: 4,
1680
1691
  minor: 6,
1681
- patch: 5
1692
+ patch: 6
1682
1693
  };
1683
1694
  //#endregion
1684
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/schemas.js
1695
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/schemas.js
1685
1696
  const $ZodType = /*@__PURE__*/ $constructor("$ZodType", (inst, def) => {
1686
1697
  var _a;
1687
1698
  inst ?? (inst = {});
@@ -2243,6 +2254,26 @@ const $ZodNever = /*@__PURE__*/ $constructor("$ZodNever", (inst, def) => {
2243
2254
  return payload;
2244
2255
  };
2245
2256
  });
2257
+ function capSize(inst, size) {
2258
+ const caps = sizeCaps(inst._zod.def.checks);
2259
+ if (!caps.length) return;
2260
+ const cap = Math.min(...caps.map((c) => c[0]));
2261
+ const parse = inst._zod.parse;
2262
+ inst._zod.parse = (payload, ctx) => {
2263
+ const n = size(payload.value);
2264
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
2265
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) if (n > max && ch._zod.def.when(payload)) {
2266
+ const start = payload.issues.length;
2267
+ ch._zod.check(payload);
2268
+ payload.issues[start].continue = false;
2269
+ attachSchema(payload.issues, start, inst);
2270
+ return payload;
2271
+ }
2272
+ }
2273
+ return parse(payload, ctx);
2274
+ };
2275
+ }
2276
+ const arraySize = (input) => Array.isArray(input) ? input.length : -1;
2246
2277
  function handleArrayResult(result, final, index) {
2247
2278
  if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));
2248
2279
  final.value[index] = result.value;
@@ -2280,6 +2311,7 @@ const $ZodArray = /*@__PURE__*/ $constructor("$ZodArray", (inst, def) => {
2280
2311
  if (proms.length) return Promise.all(proms).then(() => payload);
2281
2312
  return payload;
2282
2313
  };
2314
+ capSize(inst, arraySize);
2283
2315
  });
2284
2316
  function handlePropertyResult(result, final, key, input, optin, optout) {
2285
2317
  const isPresent = key in input;
@@ -2388,7 +2420,7 @@ const $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
2388
2420
  }
2389
2421
  return propValues;
2390
2422
  });
2391
- const isObject$2 = isObject;
2423
+ const isObject$1 = isObject;
2392
2424
  const catchall = def.catchall;
2393
2425
  let value;
2394
2426
  const memo = globalConfig.memoizer;
@@ -2396,7 +2428,7 @@ const $ZodObject = /*@__PURE__*/ $constructor("$ZodObject", (inst, def) => {
2396
2428
  inst._zod.parse = (payload, ctx) => {
2397
2429
  value ?? (value = _normalized.value);
2398
2430
  const input = payload.value;
2399
- if (!isObject$2(input)) {
2431
+ if (!isObject$1(input)) {
2400
2432
  payload.issues.push({
2401
2433
  expected: "object",
2402
2434
  code: "invalid_type",
@@ -2526,7 +2558,7 @@ const $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) =>
2526
2558
  return doc.compile();
2527
2559
  };
2528
2560
  let fastpass;
2529
- const isObject$1 = isObject;
2561
+ const isObject$2 = isObject;
2530
2562
  const jit = !globalConfig.jitless;
2531
2563
  const fastEnabled = jit && allowsEval.value;
2532
2564
  const catchall = def.catchall;
@@ -2534,7 +2566,7 @@ const $ZodObjectJIT = /*@__PURE__*/ $constructor("$ZodObjectJIT", (inst, def) =>
2534
2566
  inst._zod.parse = (payload, ctx) => {
2535
2567
  value ?? (value = _normalized.value);
2536
2568
  const input = payload.value;
2537
- if (!isObject$1(input)) {
2569
+ if (!isObject$2(input)) {
2538
2570
  payload.issues.push({
2539
2571
  expected: "object",
2540
2572
  code: "invalid_type",
@@ -2698,7 +2730,7 @@ function mergeValues(a, b) {
2698
2730
  valid: true,
2699
2731
  data: a
2700
2732
  };
2701
- if (isPlainObject$3(a) && isPlainObject$3(b)) {
2733
+ if (isPlainObject$9(a) && isPlainObject$9(b)) {
2702
2734
  const bKeys = Object.keys(b);
2703
2735
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
2704
2736
  const newObj = {
@@ -2791,7 +2823,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
2791
2823
  memo?.attach(inst);
2792
2824
  inst._zod.parse = (payload, ctx) => {
2793
2825
  const input = payload.value;
2794
- if (!isPlainObject$3(input)) {
2826
+ if (!isPlainObject$9(input)) {
2795
2827
  payload.issues.push({
2796
2828
  expected: "record",
2797
2829
  code: "invalid_type",
@@ -3198,7 +3230,7 @@ function handleRefineResult(result, payload, input, inst) {
3198
3230
  }
3199
3231
  }
3200
3232
  //#endregion
3201
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/compile.js
3233
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/compile.js
3202
3234
  /** @internal Sentinel the compiled fast path returns when validation fails. */
3203
3235
  const INVALID = Symbol.for("zod.compile.invalid");
3204
3236
  const FALLBACK_FLAG = Symbol.for("zod.compile.fallback");
@@ -4183,9 +4215,13 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
4183
4215
  doc.write(`}`);
4184
4216
  return outputVar;
4185
4217
  }
4218
+ function generateSizeGate(doc, schema, size) {
4219
+ for (const [max, check] of sizeCaps(schema._zod.def.checks)) doc.write(`if (${size} > ${numericOperand(max, check._zod.def.check)}) return INVALID;`);
4220
+ }
4186
4221
  function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
4187
4222
  const def = schema._zod.def;
4188
4223
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
4224
+ generateSizeGate(doc, schema, `${accessor}.length`);
4189
4225
  const outputVar = buildsValue ? newVar(ctx) : null;
4190
4226
  const iVar = newVar(ctx);
4191
4227
  const elemVar = newVar(ctx);
@@ -4432,7 +4468,7 @@ function generateIntersectionCheck(doc, ctx, schema, accessor) {
4432
4468
  }
4433
4469
  function generateRecordCheck(doc, ctx, schema, accessor) {
4434
4470
  const def = schema._zod.def;
4435
- const isPlainObjectConst = addConstant(ctx, isPlainObject$3);
4471
+ const isPlainObjectConst = addConstant(ctx, isPlainObject$9);
4436
4472
  doc.write(`if (!${isPlainObjectConst}(${accessor})) return INVALID;`);
4437
4473
  const outputVar = newVar(ctx);
4438
4474
  const kVar = newVar(ctx);
@@ -4522,6 +4558,7 @@ function literalPropertyKey(ctx, key) {
4522
4558
  function generateMapCheck(doc, ctx, schema, accessor) {
4523
4559
  const def = schema._zod.def;
4524
4560
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
4561
+ generateSizeGate(doc, schema, `${accessor}.size`);
4525
4562
  const outputVar = newVar(ctx);
4526
4563
  const kVar = newVar(ctx);
4527
4564
  const valVar = newVar(ctx);
@@ -4538,6 +4575,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
4538
4575
  function generateSetCheck(doc, ctx, schema, accessor) {
4539
4576
  const def = schema._zod.def;
4540
4577
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
4578
+ generateSizeGate(doc, schema, `${accessor}.size`);
4541
4579
  const outputVar = newVar(ctx);
4542
4580
  const valVar = newVar(ctx);
4543
4581
  doc.write(`const ${outputVar} = new Set();`);
@@ -4677,7 +4715,7 @@ function generateTransformCheck(doc, ctx, schema, accessor) {
4677
4715
  return accessor;
4678
4716
  }
4679
4717
  //#endregion
4680
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/en.js
4718
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/locales/en.js
4681
4719
  const error = () => {
4682
4720
  const Sizable = {
4683
4721
  string: {
@@ -4785,7 +4823,7 @@ function en_default() {
4785
4823
  return { localeError: error() };
4786
4824
  }
4787
4825
  //#endregion
4788
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/registries.js
4826
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/registries.js
4789
4827
  var _a$3;
4790
4828
  var $ZodRegistry = class {
4791
4829
  constructor() {
@@ -4832,7 +4870,7 @@ function registry() {
4832
4870
  (_a$3 = globalThis).__zod_globalRegistry ?? (_a$3.__zod_globalRegistry = registry());
4833
4871
  const globalRegistry = globalThis.__zod_globalRegistry;
4834
4872
  //#endregion
4835
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/api.js
4873
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/api.js
4836
4874
  function snapshotChecks(def) {
4837
4875
  if (def.checks) def.checks = [...def.checks];
4838
4876
  return def;
@@ -5361,7 +5399,7 @@ function _check(fn, params) {
5361
5399
  return ch;
5362
5400
  }
5363
5401
  //#endregion
5364
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/to-json-schema.js
5402
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/to-json-schema.js
5365
5403
  function assignProps(target, ...sources) {
5366
5404
  for (const source of sources) for (const key of Reflect.ownKeys(source)) if (Object.prototype.propertyIsEnumerable.call(source, key)) assignProp(target, key, source[key]);
5367
5405
  return target;
@@ -5805,7 +5843,7 @@ const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params)
5805
5843
  return finalize(ctx, schema);
5806
5844
  };
5807
5845
  //#endregion
5808
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/json-schema-processors.js
5846
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/core/json-schema-processors.js
5809
5847
  const narrowMin = (agg, key, value) => {
5810
5848
  if (agg[key] === void 0 || value > agg[key]) agg[key] = value;
5811
5849
  };
@@ -6451,7 +6489,7 @@ function toJSONSchema(input, params) {
6451
6489
  return finalize(ctx, input);
6452
6490
  }
6453
6491
  //#endregion
6454
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/compile.js
6492
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/compile.js
6455
6493
  let compiling = false;
6456
6494
  globalConfig.postProcessor = (inst) => {
6457
6495
  if (compiling) return;
@@ -6480,7 +6518,7 @@ globalConfig.postProcessor = (inst) => {
6480
6518
  inst._zod.run = shim;
6481
6519
  };
6482
6520
  //#endregion
6483
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/errors.js
6521
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/errors.js
6484
6522
  const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
6485
6523
  function _lazyMethod(proto, key, make) {
6486
6524
  Object.defineProperty(proto, key, {
@@ -6530,7 +6568,7 @@ const initializer = (inst, issues) => {
6530
6568
  };
6531
6569
  const ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, void 0, { Parent: Error });
6532
6570
  //#endregion
6533
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/parse.js
6571
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/parse.js
6534
6572
  const parse$10 = /* @__PURE__ */ _parse(ZodRealError);
6535
6573
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
6536
6574
  const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
@@ -6544,7 +6582,7 @@ const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
6544
6582
  const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
6545
6583
  const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
6546
6584
  //#endregion
6547
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/schemas.js
6585
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/schemas.js
6548
6586
  function _ensureDefaultLocale() {
6549
6587
  if (!globalConfig.localeError) config(en_default());
6550
6588
  }
@@ -7479,7 +7517,7 @@ function preprocess$1(fn, schema) {
7479
7517
  });
7480
7518
  }
7481
7519
  //#endregion
7482
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/compat.js
7520
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/compat.js
7483
7521
  /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
7484
7522
  const ZodIssueCode = {
7485
7523
  invalid_type: "invalid_type",
@@ -7498,7 +7536,7 @@ const ZodIssueCode = {
7498
7536
  var ZodFirstPartyTypeKind;
7499
7537
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
7500
7538
  //#endregion
7501
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/iso.js
7539
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/iso.js
7502
7540
  function datetime(params) {
7503
7541
  return /* @__PURE__ */ _isoDateTime(ZodISODateTime, params);
7504
7542
  }
@@ -7506,7 +7544,7 @@ function date(params) {
7506
7544
  return /* @__PURE__ */ _isoDate(ZodISODate, params);
7507
7545
  }
7508
7546
  //#endregion
7509
- //#region node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/coerce.js
7547
+ //#region node_modules/.pnpm/zod@4.6.6/node_modules/zod/v4/classic/coerce.js
7510
7548
  function number$1(params) {
7511
7549
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
7512
7550
  }
@@ -7933,8 +7971,8 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7933
7971
  readOnly: cli.readOnly ?? false,
7934
7972
  namespaces,
7935
7973
  tools: cli.tools,
7936
- topology: cli.topology ?? true,
7937
- promotedArticles: cli.promotedArticles ?? true,
7974
+ topology: cli.topology,
7975
+ promotedArticles: cli.promotedArticles,
7938
7976
  hcResourceScheme,
7939
7977
  dev: cli.dev ?? false,
7940
7978
  printTools: cli.printTools ?? false,
@@ -7944,388 +7982,6 @@ const loadConfig = (argv = process.argv.slice(2)) => {
7944
7982
  });
7945
7983
  };
7946
7984
  //#endregion
7947
- //#region src/constants.ts
7948
- const positiveIntEnv = (name, fallback) => {
7949
- const raw = readEnv(name).value;
7950
- if (raw === void 0 || raw.trim() === "") return fallback;
7951
- const parsed = Number(raw);
7952
- return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
7953
- };
7954
- const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
7955
- const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
7956
- const DEFAULT_CALLBACK_PORT = 27439;
7957
- const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
7958
- const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
7959
- const STDIO_MAX_MESSAGE_BYTES = 10485760;
7960
- const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
7961
- const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
7962
- const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
7963
- const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
7964
- const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
7965
- const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
7966
- const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
7967
- const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
7968
- const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
7969
- const getOAuthUrls = (subdomain) => ({
7970
- authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
7971
- tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
7972
- });
7973
- //#endregion
7974
- //#region src/client/retry.ts
7975
- const MAX_ATTEMPTS = 3;
7976
- const BASE_DELAY_MS = 250;
7977
- const MAX_DELAY_MS = 4e3;
7978
- const REQUEST_TIMEOUT_MS = 3e4;
7979
- const TRANSFER_TIMEOUT_MS = 12e4;
7980
- /**
7981
- * A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
7982
- * is numeric, not a syscall string, so it classifies as `unknown`: a GET is
7983
- * retried, a write is not — the request may have arrived before the deadline.
7984
- */
7985
- const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
7986
- const MAX_RETRY_AFTER_MS = 5e3;
7987
- const POLICIES = {
7988
- GET: {
7989
- network: "any",
7990
- serverErrors: true
7991
- },
7992
- DELETE: {
7993
- network: "pre-send",
7994
- serverErrors: false
7995
- },
7996
- POST: {
7997
- network: "pre-send",
7998
- serverErrors: false
7999
- },
8000
- PUT: {
8001
- network: "pre-send",
8002
- serverErrors: false
8003
- }
8004
- };
8005
- const DELAY_SECONDS = /^\d+$/;
8006
- const HTTP_DATE_START = /^[A-Za-z]/;
8007
- const NON_ASCII = /[^ -~]/g;
8008
- const TOKEN_SEGMENT = /\/token\/[^/]+/;
8009
- const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
8010
- const PRE_SEND_CODES = /* @__PURE__ */ new Set([
8011
- "ENOTFOUND",
8012
- "EAI_AGAIN",
8013
- "ECONNREFUSED",
8014
- "UND_ERR_CONNECT_TIMEOUT"
8015
- ]);
8016
- const defaultRetryDeps = {
8017
- sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
8018
- random: Math.random
8019
- };
8020
- /** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
8021
- const toAscii = (text) => text.replace(NON_ASCII, "?");
8022
- /**
8023
- * Identifies the request without leaking a credential: uploads carry an upload
8024
- * token in the query string, and an attachment `content_url` carries a download
8025
- * token as a path segment (`/attachments/token/<token>/`).
8026
- */
8027
- const describeTarget = (url) => {
8028
- const { origin, pathname } = new URL(url);
8029
- return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
8030
- };
8031
- /** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
8032
- const errorCode = (err) => {
8033
- let current = err;
8034
- for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
8035
- const { code, cause } = current;
8036
- if (typeof code === "string") return code;
8037
- current = cause;
8038
- }
8039
- };
8040
- const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
8041
- const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
8042
- /**
8043
- * The client refuses to replay a write that may have landed — but the caller is
8044
- * an LLM whose reflex on a failed tool call is to try again, which would undo
8045
- * that. So a failed write says whether it may have taken effect. ASCII only,
8046
- * same rule as the rest of the message.
8047
- */
8048
- const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
8049
- const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
8050
- const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
8051
- /** Only a write can be duplicated by a replay, so only a write carries a note. */
8052
- const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
8053
- /**
8054
- * A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
8055
- * carrying credentials by printing the whole thing, query string included — which
8056
- * would smuggle back exactly what `describeTarget` drops. Same treatment for both,
8057
- * so one function owns what a URL may look like in our output.
8058
- */
8059
- const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
8060
- try {
8061
- return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
8062
- } catch {
8063
- return "[url]";
8064
- }
8065
- });
8066
- const failureDetail = (cause) => {
8067
- if (!(cause instanceof Error)) return redactUrls(String(cause));
8068
- const message = redactUrls(cause.message);
8069
- const code = errorCode(cause);
8070
- if (code !== void 0) return `${code}: ${message}`;
8071
- return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
8072
- };
8073
- const networkErrorMessage = (method, target, attempts, cause) => {
8074
- const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
8075
- const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
8076
- return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
8077
- };
8078
- const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
8079
- name: "ZendeskNetworkError",
8080
- method,
8081
- target,
8082
- attempts
8083
- });
8084
- /** Exponential backoff with equal jitter: half the window fixed, half random. */
8085
- const computeBackoffMs = (attempt, random) => {
8086
- const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
8087
- return Math.round(window / 2 + window * random() / 2);
8088
- };
8089
- /** `Retry-After` in ms — delay-seconds or HTTP-date. */
8090
- const parseRetryAfter = (header, now = Date.now()) => {
8091
- if (header === null) return void 0;
8092
- const value = header.trim();
8093
- if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
8094
- if (!HTTP_DATE_START.test(value)) return void 0;
8095
- const date = Date.parse(value);
8096
- if (Number.isNaN(date)) return void 0;
8097
- return Math.max(0, date - now);
8098
- };
8099
- /**
8100
- * How long to wait before replaying this response, or undefined to accept it.
8101
- * `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
8102
- * during maintenance as well as on a 429 — and a value past the cap means the
8103
- * response is surfaced rather than parking the call for that long.
8104
- */
8105
- const retryDelayFor = (response, policy, attempt, random) => {
8106
- if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
8107
- const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
8108
- if (retryAfter === void 0) return computeBackoffMs(attempt, random);
8109
- return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
8110
- };
8111
- /**
8112
- * Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
8113
- * attempt budget. Returns the last response for the caller to inspect (a
8114
- * non-ok status is still the caller's to turn into a `ZendeskApiError`), and
8115
- * throws `ZendeskNetworkError` when no response was ever obtained.
8116
- */
8117
- const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
8118
- const policy = POLICIES[method];
8119
- for (let tries = 1;; tries += 1) {
8120
- const last = tries >= MAX_ATTEMPTS;
8121
- let response;
8122
- try {
8123
- response = await attempt();
8124
- } catch (err) {
8125
- const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
8126
- if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
8127
- await deps.sleep(computeBackoffMs(tries, deps.random));
8128
- continue;
8129
- }
8130
- if (last) return response;
8131
- const delayMs = retryDelayFor(response, policy, tries, deps.random);
8132
- if (delayMs === void 0) return response;
8133
- await response.text().catch(() => void 0);
8134
- await deps.sleep(delayMs);
8135
- }
8136
- };
8137
- //#endregion
8138
- //#region src/client/zendesk-api.ts
8139
- var ZendeskApiError = class ZendeskApiError extends Error {
8140
- status;
8141
- statusText;
8142
- body;
8143
- method;
8144
- constructor(status, statusText, body, method) {
8145
- super(ZendeskApiError.buildMessage(status, statusText, body, method));
8146
- this.status = status;
8147
- this.statusText = statusText;
8148
- this.body = body;
8149
- this.method = method;
8150
- this.name = "ZendeskApiError";
8151
- }
8152
- static buildMessage(status, statusText, body, method) {
8153
- switch (status) {
8154
- case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
8155
- case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
8156
- case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
8157
- case 422: return `Validation error: ${body}`;
8158
- case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
8159
- default: {
8160
- const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
8161
- return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
8162
- }
8163
- }
8164
- }
8165
- };
8166
- const buildAuthHeader = (token) => `Bearer ${token}`;
8167
- const buildUrl = (base, path, params) => {
8168
- const url = new URL(`${base}${path}`);
8169
- if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
8170
- return url.toString();
8171
- };
8172
- const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
8173
- ...init,
8174
- method,
8175
- signal: deadlineSignal(timeoutMs)
8176
- }), method, describeTarget(url));
8177
- const executeRequest = async (url, token, options = {}) => {
8178
- const { method = "GET", body } = options;
8179
- const headers = {
8180
- Authorization: buildAuthHeader(token),
8181
- Accept: "application/json"
8182
- };
8183
- if (body) headers["Content-Type"] = "application/json";
8184
- const init = { headers };
8185
- if (body) init.body = JSON.stringify(body);
8186
- const response = await performFetch(method, url, init);
8187
- if (!response.ok) {
8188
- const responseBody = await response.text();
8189
- throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
8190
- }
8191
- if (response.status === 204) return {};
8192
- return response.json();
8193
- };
8194
- const zendeskGet = (subdomain, token, path, params) => {
8195
- const url = buildUrl(getBaseUrl(subdomain), path, params);
8196
- return executeRequest(url, token);
8197
- };
8198
- const zendeskPost = (subdomain, token, path, body) => {
8199
- const url = buildUrl(getBaseUrl(subdomain), path);
8200
- return executeRequest(url, token, {
8201
- method: "POST",
8202
- body
8203
- });
8204
- };
8205
- const zendeskPut = (subdomain, token, path, body) => {
8206
- const url = buildUrl(getBaseUrl(subdomain), path);
8207
- return executeRequest(url, token, {
8208
- method: "PUT",
8209
- body
8210
- });
8211
- };
8212
- const helpCenterGet = (subdomain, token, path, params) => {
8213
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
8214
- return executeRequest(url, token);
8215
- };
8216
- const helpCenterPost = (subdomain, token, path, body) => {
8217
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8218
- return executeRequest(url, token, {
8219
- method: "POST",
8220
- body
8221
- });
8222
- };
8223
- const helpCenterPut = (subdomain, token, path, body) => {
8224
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8225
- return executeRequest(url, token, {
8226
- method: "PUT",
8227
- body
8228
- });
8229
- };
8230
- const helpCenterDelete = (subdomain, token, path) => {
8231
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8232
- return executeRequest(url, token, { method: "DELETE" });
8233
- };
8234
- const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
8235
- const expectedHost = `${subdomain}.zendesk.com`;
8236
- const headers = {};
8237
- if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
8238
- const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
8239
- if (!response.ok) {
8240
- const body = await response.text();
8241
- throw new ZendeskApiError(response.status, response.statusText, body, "GET");
8242
- }
8243
- const contentType = response.headers.get("content-type") ?? "application/octet-stream";
8244
- const arrayBuffer = await response.arrayBuffer();
8245
- return {
8246
- data: Buffer.from(arrayBuffer),
8247
- contentType
8248
- };
8249
- };
8250
- const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
8251
- const params = { filename };
8252
- if (uploadToken) params["token"] = uploadToken;
8253
- const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
8254
- const response = await performFetch("POST", url, {
8255
- headers: {
8256
- Authorization: buildAuthHeader(token),
8257
- "Content-Type": contentType
8258
- },
8259
- body: data
8260
- }, TRANSFER_TIMEOUT_MS);
8261
- if (!response.ok) {
8262
- const responseBody = await response.text();
8263
- throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8264
- }
8265
- return response.json();
8266
- };
8267
- const helpCenterUpload = async (subdomain, token, path, formData) => {
8268
- const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
8269
- const response = await performFetch("POST", url, {
8270
- headers: { Authorization: buildAuthHeader(token) },
8271
- body: formData
8272
- }, TRANSFER_TIMEOUT_MS);
8273
- if (!response.ok) {
8274
- const responseBody = await response.text();
8275
- throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
8276
- }
8277
- return response.json();
8278
- };
8279
- //#endregion
8280
- //#region src/client/brands.ts
8281
- const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
8282
- /**
8283
- * Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
8284
- * the FIRST request too: without it Zendesk falls back to OFFSET pagination,
8285
- * which answers without `meta.after_cursor`, and the loop below would stop
8286
- * after page 1 — a brand past the first 100 would resolve as Unknown.
8287
- */
8288
- const fetchAllBrands = async (subdomain, token) => {
8289
- const brands = [];
8290
- let cursor;
8291
- do {
8292
- const response = await zendeskGet(subdomain, token, "/brands", {
8293
- "page[size]": String(100),
8294
- ...cursor ? { "page[after]": cursor } : {}
8295
- });
8296
- brands.push(...response.brands ?? []);
8297
- const meta = response.meta;
8298
- if (meta?.has_more) {
8299
- if (!meta.after_cursor || meta.after_cursor === cursor) throw new Error("GET /brands reported more pages but returned no usable after_cursor (missing or identical to the cursor just sent). Cannot continue pagination safely.");
8300
- cursor = meta.after_cursor;
8301
- } else cursor = void 0;
8302
- } while (cursor);
8303
- return brands;
8304
- };
8305
- const createBrandSubdomainResolver = (subdomain, getToken) => {
8306
- let cached;
8307
- const brands = () => {
8308
- if (!cached || Date.now() - cached.at >= 3e5) {
8309
- const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
8310
- cached = {
8311
- at: Date.now(),
8312
- promise
8313
- };
8314
- promise.catch(() => {
8315
- cached = void 0;
8316
- });
8317
- }
8318
- return cached.promise;
8319
- };
8320
- return async (idOrSubdomain) => {
8321
- const all = await brands();
8322
- const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
8323
- if (!brand) throw new Error(`Unknown brand "${idOrSubdomain}": no brand with that id or subdomain exists on this account; call list_brands to see the available brands.`);
8324
- if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
8325
- return brand.subdomain;
8326
- };
8327
- };
8328
- //#endregion
8329
7985
  //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
8330
7986
  var __create = Object.create;
8331
7987
  var __defProp = Object.defineProperty;
@@ -10433,6 +10089,26 @@ var SdkHttpError = class extends SdkError {
10433
10089
  return this.data.statusText;
10434
10090
  }
10435
10091
  };
10092
+ /**
10093
+ * Inbound request methods whose processing structurally requires a client
10094
+ * capability, keyed by method, valued by the capabilities required.
10095
+ *
10096
+ * Currently empty: none of the request methods served on the 2026-07-28
10097
+ * registry unconditionally requires a client capability. Entries appear here
10098
+ * when such methods exist — for example requests whose handling embeds
10099
+ * elicitation or sampling input requests (the input-request engine), or
10100
+ * opt-in subscription delivery. Handler-conditional requirements (a specific
10101
+ * tool that needs sampling) are not expressible as a static method table and
10102
+ * are enforced at the point the requirement arises instead.
10103
+ */
10104
+ const REQUIRED_CLIENT_CAPABILITIES_BY_METHOD = {};
10105
+ /**
10106
+ * The client capabilities a request method structurally requires, or
10107
+ * `undefined` when the method has no static requirement.
10108
+ */
10109
+ function requiredClientCapabilitiesForRequest(method) {
10110
+ return Object.hasOwn(REQUIRED_CLIENT_CAPABILITIES_BY_METHOD, method) ? REQUIRED_CLIENT_CAPABILITIES_BY_METHOD[method] : void 0;
10111
+ }
10436
10112
  function isPlainObject$7(value) {
10437
10113
  return value !== null && typeof value === "object" && !Array.isArray(value);
10438
10114
  }
@@ -10515,6 +10191,13 @@ function missingClientCapabilities(required, declared) {
10515
10191
  * are ISO dates, so lexicographic comparison orders them chronologically.
10516
10192
  */
10517
10193
  const FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
10194
+ /**
10195
+ * Modern-era protocol revisions this SDK can negotiate via `server/discover`.
10196
+ * Deliberately separate from {@linkcode SUPPORTED_PROTOCOL_VERSIONS} (the legacy
10197
+ * `initialize` list), so adding a revision here can never leak a modern version
10198
+ * string into a 2025-era handshake. Internal — not part of the public API surface.
10199
+ */
10200
+ const SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
10518
10201
  /** Whether the given protocol revision belongs to the modern (2026-07-28+) era. */
10519
10202
  function isModernProtocolVersion(version) {
10520
10203
  return version >= FIRST_MODERN_PROTOCOL_VERSION;
@@ -14099,6 +13782,66 @@ function isExtensionReusedRequestMethod(method) {
14099
13782
  return method === "tasks/get" || method === "tasks/cancel";
14100
13783
  }
14101
13784
  const ALL_CODECS = [rev2025Codec, rev2026Codec];
13785
+ /**
13786
+ * Per-request `_meta` envelope claim helpers (protocol revision 2026-07-28).
13787
+ *
13788
+ * Pure, value-returning helpers used by the inbound HTTP classifier
13789
+ * (`classifyInboundRequest`): claim detection and envelope validation with
13790
+ * self-identifying issues. The envelope schema itself stays the wire layer's
13791
+ * single source of truth (`RequestMetaEnvelopeSchema`); this module only maps
13792
+ * its outcomes into the shapes the validation ladder emits.
13793
+ *
13794
+ * Claim detection is deliberately narrow: a message claims the 2026-07-28
13795
+ * envelope mechanism if and only if the reserved protocol-version `_meta` key
13796
+ * is present in `params._meta`. Other reserved keys (client info, client
13797
+ * capabilities, log level), a bare `progressToken`, or unrelated keys under
13798
+ * the `io.modelcontextprotocol/` prefix do NOT constitute a claim on their
13799
+ * own — but once the claim key is present, a malformed envelope is a
13800
+ * validation error, never a silent fall back to legacy handling.
13801
+ *
13802
+ * The wire-exact envelope schema, the required-key set, and the per-key issue
13803
+ * mapping live in the wire layer (the 2026-era codec's `validateEnvelopeMeta`).
13804
+ * This module never reaches into a per-revision wire module directly.
13805
+ */
13806
+ function isPlainObject$3(value) {
13807
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13808
+ }
13809
+ /** The `_meta` object of a message's params, when present. */
13810
+ function requestMetaOf(params) {
13811
+ if (!isPlainObject$3(params)) return void 0;
13812
+ const meta = params["_meta"];
13813
+ return isPlainObject$3(meta) ? meta : void 0;
13814
+ }
13815
+ /**
13816
+ * Whether a message's params carry the per-request envelope claim: the
13817
+ * reserved protocol-version `_meta` key is present (regardless of whether the
13818
+ * rest of the envelope is valid — validation is a separate, later step).
13819
+ */
13820
+ function hasEnvelopeClaim(params) {
13821
+ const meta = requestMetaOf(params);
13822
+ return meta !== void 0 && "io.modelcontextprotocol/protocolVersion" in meta;
13823
+ }
13824
+ /**
13825
+ * The protocol version named by a message's envelope claim, when the claim is
13826
+ * present and carries a string value. A present claim with a non-string value
13827
+ * still counts as a claim ({@linkcode hasEnvelopeClaim}); it surfaces as a
13828
+ * validation issue instead of a version.
13829
+ */
13830
+ function envelopeClaimVersion(params) {
13831
+ const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
13832
+ return typeof value === "string" ? value : void 0;
13833
+ }
13834
+ /**
13835
+ * Validates a request's `_meta` object as a 2026-07-28 per-request envelope
13836
+ * and reports problems as self-identifying issues (which key, what problem).
13837
+ *
13838
+ * Returns an empty array when the envelope is valid. Missing required keys are
13839
+ * reported first (as `problem: 'missing'`), then schema violations inside
13840
+ * present keys, in a stable order.
13841
+ */
13842
+ function validateEnvelopeMeta(meta) {
13843
+ return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta);
13844
+ }
14102
13845
  var schemas_exports = /* @__PURE__ */ __exportAll({
14103
13846
  AnnotationsSchema: () => AnnotationsSchema,
14104
13847
  AudioContentSchema: () => AudioContentSchema,
@@ -14303,6 +14046,8 @@ function assertCompleteRequestPrompt(request) {
14303
14046
  function assertCompleteRequestResourceTemplate(request) {
14304
14047
  if (request.params.ref.type !== "ref/resource") throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
14305
14048
  }
14049
+ /** The fixed prefix every custom-parameter header carries. */
14050
+ const MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
14306
14051
  /** The schema-extension property name a tool's `inputSchema` carries. */
14307
14052
  const X_MCP_HEADER_KEY = "x-mcp-header";
14308
14053
  /**
@@ -14425,8 +14170,566 @@ const OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
14425
14170
  function pathName(path) {
14426
14171
  return path.length === 0 ? "<root>" : path.join(".");
14427
14172
  }
14173
+ const BASE64_SENTINEL_PREFIX = "=?base64?";
14174
+ const BASE64_SENTINEL_SUFFIX = "?=";
14175
+ const BASE64_CANONICAL = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
14176
+ const CANONICAL_DECIMAL = /^-?\d+(\.\d+)?$/;
14177
+ /**
14178
+ * Convert a primitive argument value to its string representation per the
14179
+ * spec's type-conversion rules: strings pass through, integers and numbers
14180
+ * become their decimal string, booleans become lowercase `'true'` / `'false'`.
14181
+ * Non-finite numbers and integers outside the safe range are refused (the
14182
+ * caller treats `undefined` as "do not emit a header for this value").
14183
+ */
14184
+ function mcpParamPrimitiveToString(value) {
14185
+ if (typeof value === "string") return value;
14186
+ if (typeof value === "boolean") return value ? "true" : "false";
14187
+ if (typeof value === "number") {
14188
+ if (!Number.isFinite(value)) return void 0;
14189
+ if (Number.isInteger(value) && !Number.isSafeInteger(value)) return void 0;
14190
+ return String(value);
14191
+ }
14192
+ }
14193
+ function base64ToUtf8(b64) {
14194
+ const bin = atob(b64);
14195
+ const bytes = new Uint8Array(bin.length);
14196
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.codePointAt(i);
14197
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14198
+ }
14199
+ /**
14200
+ * Decode an `Mcp-Param-*` header value: when it carries the Base64 sentinel,
14201
+ * the payload is decoded as UTF-8; otherwise the value is returned as-is.
14202
+ * Returns `undefined` when the sentinel is present but the payload is not
14203
+ * canonical Base64 (or not valid UTF-8) — the spec requires servers to reject
14204
+ * such values.
14205
+ */
14206
+ function decodeMcpParamValue(value) {
14207
+ if (!(value.startsWith(BASE64_SENTINEL_PREFIX) && value.endsWith(BASE64_SENTINEL_SUFFIX))) return value;
14208
+ const b64 = value.slice(9, value.length - 2);
14209
+ if (!BASE64_CANONICAL.test(b64)) return void 0;
14210
+ try {
14211
+ return base64ToUtf8(b64);
14212
+ } catch {
14213
+ return;
14214
+ }
14215
+ }
14216
+ function valueAtPath(root, path) {
14217
+ let node = root;
14218
+ for (const key of path) {
14219
+ if (node === null || typeof node !== "object") return void 0;
14220
+ node = node[key];
14221
+ }
14222
+ return node;
14223
+ }
14224
+ /**
14225
+ * The header/body comparison the server performs at tool-resolution time.
14226
+ *
14227
+ * For each `x-mcp-header` declaration on the named tool: when the body
14228
+ * `arguments` carries a value, the matching `Mcp-Param-{Name}` header MUST be
14229
+ * present and decode to an equal value; when the body value is `null` or
14230
+ * absent the server MUST NOT expect the header (a present header is ignored).
14231
+ * A sentinel-carrying header whose payload is not canonical Base64 / valid
14232
+ * UTF-8 is rejected as invalid characters.
14233
+ *
14234
+ * Integer-typed declarations are compared numerically (the spec's SHOULD —
14235
+ * `42.0` and `42` are equal); everything else is compared as decoded strings.
14236
+ *
14237
+ * Returns `undefined` when every check passes, or an
14238
+ * {@linkcode InboundLadderRejection} carrying the same `-32020`
14239
+ * (`HeaderMismatch`) shape the inbound classifier emits for the
14240
+ * standard-header cross-checks — `400 Bad Request` with the disagreeing pair
14241
+ * in `data.mismatch`.
14242
+ */
14243
+ function validateMcpParamHeaders(declarations, args, headers) {
14244
+ for (const decl of declarations) {
14245
+ const headerKey = `${MCP_PARAM_HEADER_PREFIX}${decl.headerName}`;
14246
+ const headerValue = headers.get(headerKey);
14247
+ const bodyRaw = valueAtPath(args, decl.path);
14248
+ if (bodyRaw === void 0 || bodyRaw === null) continue;
14249
+ const bodyString = mcpParamPrimitiveToString(bodyRaw);
14250
+ if (bodyString === void 0) continue;
14251
+ if (headerValue === null) return paramHeaderMismatchRejection("param-header-missing", headerKey, `the body carries ${pathName(decl.path)}=${JSON.stringify(bodyRaw)} but the ${headerKey} header is absent`);
14252
+ const decoded = decodeMcpParamValue(headerValue);
14253
+ if (decoded === void 0) return paramHeaderMismatchRejection("param-header-invalid-encoding", headerKey, `the ${headerKey} header carries an invalid Base64 sentinel value`);
14254
+ if (!((decl.type === "integer" || decl.type === "number") && CANONICAL_DECIMAL.test(decoded) && typeof bodyRaw === "number" ? Number(decoded) === bodyRaw : decoded === bodyString)) return paramHeaderMismatchRejection("param-header-mismatch", headerKey, `the ${headerKey} header decodes to ${JSON.stringify(decoded)} but the body carries ${pathName(decl.path)}=${JSON.stringify(bodyRaw)}`);
14255
+ }
14256
+ }
14257
+ /**
14258
+ * Build the `-32020` (`HeaderMismatch`) rejection for an `Mcp-Param-*`
14259
+ * disagreement. Same shape as the inbound classifier's standard-header
14260
+ * cross-check mismatch (HTTP `400`, `data.mismatch` naming the disagreeing
14261
+ * pair, `settled: true`); only the rung differs because this check runs at the
14262
+ * pre-dispatch step against a known tool's schema rather than at the edge.
14263
+ */
14264
+ function paramHeaderMismatchRejection(cell, header, body) {
14265
+ return {
14266
+ kind: "reject",
14267
+ rung: "param-header-validation",
14268
+ cell,
14269
+ httpStatus: 400,
14270
+ code: HEADER_MISMATCH_ERROR_CODE,
14271
+ message: `Bad Request: the request headers and body disagree: ${body}`,
14272
+ data: { mismatch: {
14273
+ header,
14274
+ body
14275
+ } },
14276
+ settled: true
14277
+ };
14278
+ }
14279
+ /**
14280
+ * Inbound HTTP request classification and the inbound validation ladder
14281
+ * (protocol revision 2026-07-28).
14282
+ *
14283
+ * `classifyInboundRequest` is the body-primary era predicate for an HTTP
14284
+ * entry that serves both protocol eras on one endpoint. It is evaluated
14285
+ * exactly once, at the entry boundary, on the already-parsed request body:
14286
+ *
14287
+ * - `initialize` is a legacy-era request by definition (the modern era has no
14288
+ * `initialize` handshake) — unless it carries a valid envelope claim naming
14289
+ * a modern revision, in which case the claim wins and the request is
14290
+ * classified like any other enveloped request (the modern era then answers
14291
+ * it with method-not-found, exactly like every other method it does not
14292
+ * define).
14293
+ * - A request whose `params._meta` carries the reserved protocol-version key
14294
+ * claims the per-request envelope mechanism and classifies into the era the
14295
+ * named revision belongs to (a malformed envelope behind a present claim is
14296
+ * a validation error, never a silent fall back to legacy handling).
14297
+ * - A request without a claim is legacy-era traffic.
14298
+ * - The `MCP-Protocol-Version` header is a cross-check only *for
14299
+ * classification*: it never upgrades or downgrades a body-derived
14300
+ * classification, and a disagreement between header and body is an explicit
14301
+ * ladder outcome. Its absence likewise never changes the era — but the spec
14302
+ * requires the header on every modern *request* POST, so a
14303
+ * modern-classified request that omits it is refused one rung later, by
14304
+ * {@linkcode validateStandardRequestHeaders}, not here. Notification POSTs
14305
+ * are exempt (see the next bullet), and that rung enforces presence on
14306
+ * requests only.
14307
+ * - Notifications carry no envelope claim of their own under the current
14308
+ * spec, so for notification POSTs without a body claim the modern header is
14309
+ * determinative; the `Mcp-Method` header is validated against the body when
14310
+ * the message classifies modern and is never enforced on legacy traffic.
14311
+ * A notification that does carry a claim is treated body-primary like a
14312
+ * request, and a malformed claim is rejected the same way a request's
14313
+ * malformed claim is — never silently resolved against the header.
14314
+ * The notification-POST header cross-checks here are an SDK-defensive
14315
+ * posture, not a spec requirement: the spec leaves header rules for posted
14316
+ * notifications undefined (core client notifications do not occur over
14317
+ * Streamable HTTP); applying the request rules symmetrically is what an
14318
+ * ecosystem custom-notification POST expects, and the −32020 cells stay
14319
+ * passing for them.
14320
+ * - `GET`/`DELETE` (and any other non-`POST` method) are body-less 2025-era
14321
+ * session operations: the modern era is `POST`-only, so they are routed to
14322
+ * legacy serving when it is configured and rejected otherwise.
14323
+ * - Array (batch) bodies are classified element-wise: an array containing a
14324
+ * modern-claiming or invalid element is rejected, an all-legacy array is
14325
+ * legacy traffic unchanged, and a single-element array is still an array.
14326
+ *
14327
+ * The classifier returns plain values (it never throws and never touches a
14328
+ * transport): a routing outcome (`legacy`/`modern`) or a ladder rejection
14329
+ * carrying the JSON-RPC error to emit and the HTTP status to emit it with.
14330
+ * Legacy routing outcomes deliberately carry NO `MessageClassification` —
14331
+ * legacy and hand-wired traffic is never classified, which keeps its
14332
+ * dispatch behavior byte-identical to today's.
14333
+ *
14334
+ * Error codes for the modern-path rejection cells follow the published
14335
+ * conformance suite (and the spec text it asserts):
14336
+ *
14337
+ * - A header/body cross-check mismatch (the `MCP-Protocol-Version` header
14338
+ * disagreeing with the body, or the `Mcp-Method` header disagreeing with the
14339
+ * body method) is rejected with `-32020` (`HeaderMismatch`) on HTTP 400.
14340
+ * - A request whose protocol-version header names a modern revision but whose
14341
+ * body carries no `_meta` envelope claim — including an envelope present but
14342
+ * missing the required protocol-version key — is rejected with `-32602`
14343
+ * (invalid params) naming the missing key(s), on HTTP 400.
14344
+ *
14345
+ * Should a future spec revision or conformance release change these
14346
+ * assignments, the affected cells are re-derived against that release; the
14347
+ * `settled` flag on {@linkcode InboundLadderRejection} stays available to mark
14348
+ * a cell provisional again while such a change is in flight.
14349
+ */
14350
+ /**
14351
+ * The error code emitted for header/body cross-check mismatches: the
14352
+ * `MCP-Protocol-Version` header disagreeing with the body's envelope claim (or
14353
+ * with the body's classification), and the `Mcp-Method` header disagreeing
14354
+ * with the body method.
14355
+ *
14356
+ * `-32020` is the draft schema's `HEADER_MISMATCH` constant (the SEP-2243
14357
+ * `HeaderMismatch` code; the spec requires HTTP 400 for it), as also asserted
14358
+ * by the published conformance suite for header-validation failures. It has no
14359
+ * {@linkcode ProtocolErrorCode} member because it is not part of the 2025-era
14360
+ * wire vocabulary; the validation ladder is its only emitter.
14361
+ */
14362
+ const HEADER_MISMATCH_ERROR_CODE = -32020;
14428
14363
  ProtocolErrorCode.InvalidRequest, ProtocolErrorCode.UnsupportedProtocolVersion, ProtocolErrorCode.InvalidParams, ProtocolErrorCode.MethodNotFound, ProtocolErrorCode.InvalidParams, ProtocolErrorCode.MissingRequiredClientCapability;
14429
- ProtocolErrorCode.ParseError, ProtocolErrorCode.InvalidRequest, ProtocolErrorCode.MethodNotFound, ProtocolErrorCode.UnsupportedProtocolVersion, ProtocolErrorCode.MissingRequiredClientCapability;
14364
+ /**
14365
+ * HTTP status for ladder-originated JSON-RPC error codes.
14366
+ *
14367
+ * Keyed on origin, not on the bare code: this table only applies to errors
14368
+ * the ladder (or a pre-handler protocol gate) produced. Errors produced by
14369
+ * request handlers — whatever their code — stay in-band on HTTP 200, and are
14370
+ * never mapped to an HTTP status by this table; in particular `-32603` and
14371
+ * domain-specific codes never become a blanket 500. The single exception is
14372
+ * `MissingRequiredClientCapability` (-32021) — see
14373
+ * {@linkcode httpStatusForErrorCode}.
14374
+ *
14375
+ * `-32602` (invalid params) deliberately has NO entry: the only invalid-params
14376
+ * rejection that maps to HTTP 400 is the classifier's own envelope rung
14377
+ * short-circuit, which carries its HTTP status directly. A dispatch- or
14378
+ * handler-produced invalid-params error is always in-band.
14379
+ */
14380
+ const LADDER_ERROR_HTTP_STATUS = {
14381
+ [ProtocolErrorCode.ParseError]: 400,
14382
+ [ProtocolErrorCode.InvalidRequest]: 400,
14383
+ [ProtocolErrorCode.MethodNotFound]: 404,
14384
+ [ProtocolErrorCode.UnsupportedProtocolVersion]: 400,
14385
+ [ProtocolErrorCode.MissingRequiredClientCapability]: 400,
14386
+ [HEADER_MISMATCH_ERROR_CODE]: 400
14387
+ };
14388
+ /**
14389
+ * The HTTP status to answer a JSON-RPC error with, keyed on the error's
14390
+ * origin. `in-band` errors (anything produced by a request handler) are
14391
+ * HTTP 200 — the JSON-RPC error response is the payload, not an HTTP
14392
+ * failure — with ONE exception: `MissingRequiredClientCapability` (-32021),
14393
+ * whose 400 the spec mandates on the error itself with no origin condition,
14394
+ * and which the SDK genuinely produces after dispatch (the `input_required`
14395
+ * capability gate). A handler relaying some downstream peer's `-32020`/`-32022`
14396
+ * is NOT that peer's spec error and stays in-band like every other handler
14397
+ * code. `ladder` errors map through {@linkcode LADDER_ERROR_HTTP_STATUS}.
14398
+ *
14399
+ * The per-request transport intentionally does NOT delegate to this function:
14400
+ * its `?? 400` ladder fallback is only correct for entry-gate codes known to
14401
+ * the table, and would wrongly map dispatch-window errors outside it (a
14402
+ * window `-32602` must stay in-band on 200). The transport indexes the table
14403
+ * directly; keep the two in agreement when editing either.
14404
+ */
14405
+ function httpStatusForErrorCode(code, origin) {
14406
+ if (origin === "in-band") return code === ProtocolErrorCode.MissingRequiredClientCapability ? 400 : 200;
14407
+ return LADDER_ERROR_HTTP_STATUS[code] ?? 400;
14408
+ }
14409
+ function rejection(rung, cell, httpStatus, error, settled) {
14410
+ return {
14411
+ kind: "reject",
14412
+ rung,
14413
+ cell,
14414
+ httpStatus,
14415
+ code: error.code,
14416
+ message: error.message,
14417
+ ...error.data !== void 0 && { data: error.data },
14418
+ settled
14419
+ };
14420
+ }
14421
+ function crossCheckMismatch(cell, header, body, rung = "era-classification") {
14422
+ return rejection(rung, cell, 400, new ProtocolError(HEADER_MISMATCH_ERROR_CODE, `Bad Request: the request headers and body disagree: ${body}`, { mismatch: {
14423
+ header,
14424
+ body
14425
+ } }), true);
14426
+ }
14427
+ /**
14428
+ * The methods whose body carries a `params.name` / `params.uri` /
14429
+ * `params.taskId` value the `Mcp-Name` header must mirror, and which body
14430
+ * field supplies it. The core rows come from SEP-2243 § Standard Request
14431
+ * Headers (`Required For` column); the `tasks/*` rows come from SEP-2663's
14432
+ * Streamable HTTP binding ("the client MUST set the `Mcp-Name` header to the
14433
+ * value of `params.taskId`" for `tasks/get` / `tasks/update` /
14434
+ * `tasks/cancel`, so intermediaries can route every request for a task to the
14435
+ * instance holding its state). Shared by the client transport (header
14436
+ * emission) and the server ladder (validation) so both sides derive from one
14437
+ * table.
14438
+ */
14439
+ const MCP_NAME_HEADER_SOURCE = {
14440
+ "tools/call": "name",
14441
+ "prompts/get": "name",
14442
+ "resources/read": "uri",
14443
+ "tasks/get": "taskId",
14444
+ "tasks/update": "taskId",
14445
+ "tasks/cancel": "taskId"
14446
+ };
14447
+ /**
14448
+ * Resolve the `Mcp-Name` source for one request against
14449
+ * {@linkcode MCP_NAME_HEADER_SOURCE}: which body field the header mirrors for
14450
+ * `method`, and the string value that field carries in `params`.
14451
+ *
14452
+ * Returns `undefined` when `method` is off-table (no `Mcp-Name` is emitted or
14453
+ * required). Otherwise `value` is the field's string value, or `undefined`
14454
+ * when `params` is not a plain object or the field is absent / not a string —
14455
+ * the client emits no header then, and the server leaves that body to the
14456
+ * rungs further down the ladder.
14457
+ *
14458
+ * `method` is peer-supplied on the server and caller-supplied on the client,
14459
+ * so the table lookup is `Object.hasOwn`-guarded against `Object.prototype`
14460
+ * collisions (`constructor`, `toString`, …). Shared by the client transport
14461
+ * (emission) and {@linkcode validateStandardRequestHeaders} (validation) so
14462
+ * the extraction — not just the table — cannot drift between the two sides.
14463
+ */
14464
+ function mcpNameSource(method, params) {
14465
+ const field = Object.hasOwn(MCP_NAME_HEADER_SOURCE, method) ? MCP_NAME_HEADER_SOURCE[method] : void 0;
14466
+ if (field === void 0) return;
14467
+ const raw = isPlainObject$2(params) ? params[field] : void 0;
14468
+ return {
14469
+ field,
14470
+ value: typeof raw === "string" ? raw : void 0
14471
+ };
14472
+ }
14473
+ /** Strip RFC 9110 optional whitespace (SP / HTAB) around a field value in linear time. */
14474
+ function stripHttpOws(value) {
14475
+ let start = 0;
14476
+ while (start < value.length) {
14477
+ const code = value.codePointAt(start);
14478
+ if (code !== 9 && code !== 32) break;
14479
+ start += 1;
14480
+ }
14481
+ let end = value.length;
14482
+ while (end > start) {
14483
+ const code = value.codePointAt(end - 1);
14484
+ if (code !== 9 && code !== 32) break;
14485
+ end -= 1;
14486
+ }
14487
+ return start === 0 && end === value.length ? value : value.slice(start, end);
14488
+ }
14489
+ /**
14490
+ * SEP-2243 standard-header server-side validation, evaluated by the HTTP
14491
+ * entry on a modern-classified request immediately after
14492
+ * {@linkcode classifyInboundRequest} returns a modern route.
14493
+ *
14494
+ * Returns the `-32020` (`HeaderMismatch`) ladder rejection (HTTP `400`,
14495
+ * `standard-header-validation` rung — the same shape
14496
+ * {@linkcode classifyInboundRequest} already emits on the edge
14497
+ * `era-classification` rung for the `MCP-Protocol-Version` and
14498
+ * `Mcp-Method` *mismatch* cells) when:
14499
+ *
14500
+ * - the required `MCP-Protocol-Version` header is absent (SEP-2243 requires it
14501
+ * on every modern *request* POST, and lists it first among the required
14502
+ * standard headers — so a request missing it *and* `Mcp-Method` is answered
14503
+ * by this cell);
14504
+ * - the required `Mcp-Method` header is absent;
14505
+ * - the required `Mcp-Name` header is absent on a `tools/call`,
14506
+ * `prompts/get`, `resources/read`, or (per SEP-2663's Streamable HTTP
14507
+ * binding) `tasks/get` / `tasks/update` / `tasks/cancel` request whose
14508
+ * body carries the `params.name` / `params.uri` / `params.taskId` value
14509
+ * the header mirrors;
14510
+ * - the `Mcp-Name` header carries an invalid `=?base64?…?=` sentinel; or
14511
+ * - the (decoded) `Mcp-Name` value disagrees with the body's
14512
+ * `params.name` / `params.uri` / `params.taskId`.
14513
+ *
14514
+ * Returns `undefined` (pass) for notifications (the spec table reads
14515
+ * "All requests"), for methods that have no `Mcp-Name` source, and when the
14516
+ * headers agree with the body. Never enforced on legacy traffic — the entry
14517
+ * only calls this on a modern route.
14518
+ *
14519
+ * Kept separate from {@linkcode classifyInboundRequest} so that a body-only
14520
+ * call to the classifier (no headers passed) keeps routing a modern request
14521
+ * unchanged: the classifier remains a pure body-primary router, and this
14522
+ * function is the presence/`Mcp-Name` half of the standard-header rung the
14523
+ * entry layers on top. That separation is what lets the missing
14524
+ * `MCP-Protocol-Version` cell live here without disturbing the body-primary
14525
+ * rule — classification still resolves from the body (a proxy stripping the
14526
+ * header must not change the era), and only this rung refuses to serve it.
14527
+ */
14528
+ function validateStandardRequestHeaders(request, route) {
14529
+ if (route.messageKind !== "request") return;
14530
+ const method = route.message.method;
14531
+ if (request.protocolVersionHeader === void 0) {
14532
+ const claimed = route.classification.revision;
14533
+ return crossCheckMismatch("version-header-missing", "(missing)", claimed === void 0 ? "the body carries a modern per-request envelope but the required MCP-Protocol-Version header is absent" : `the body envelope names protocol version ${claimed} but the required MCP-Protocol-Version header is absent`, "standard-header-validation");
14534
+ }
14535
+ if (request.mcpMethodHeader === void 0) return crossCheckMismatch("method-header-missing", "(missing)", `the body names method ${method} but the required Mcp-Method header is absent`, "standard-header-validation");
14536
+ const source = mcpNameSource(method, route.message.params);
14537
+ if (source === void 0) return;
14538
+ const { field: sourceField, value: bodyValue } = source;
14539
+ if (request.mcpNameHeader === void 0) {
14540
+ if (bodyValue === void 0) return;
14541
+ return crossCheckMismatch("name-header-missing", "(missing)", `the body carries params.${sourceField}="${bodyValue}" but the required Mcp-Name header is absent`, "standard-header-validation");
14542
+ }
14543
+ const normalizedNameHeader = stripHttpOws(request.mcpNameHeader);
14544
+ const decoded = decodeMcpParamValue(normalizedNameHeader);
14545
+ if (decoded === void 0) return crossCheckMismatch("name-header-invalid-encoding", normalizedNameHeader, "the Mcp-Name header carries an invalid Base64 sentinel value", "standard-header-validation");
14546
+ if (bodyValue !== void 0 && decoded !== bodyValue) return crossCheckMismatch("name-header-mismatch", normalizedNameHeader, `the body carries params.${sourceField}="${bodyValue}" but the Mcp-Name header names "${decoded}"`, "standard-header-validation");
14547
+ }
14548
+ function isPlainObject$2(value) {
14549
+ return value !== null && typeof value === "object" && !Array.isArray(value);
14550
+ }
14551
+ function classificationForClaim(claimedVersion) {
14552
+ if (claimedVersion === void 0) return { era: "modern" };
14553
+ return {
14554
+ era: isModernProtocolVersion(claimedVersion) ? "modern" : "legacy",
14555
+ revision: claimedVersion
14556
+ };
14557
+ }
14558
+ /**
14559
+ * Whether a request's params carry a per-request envelope claim that is both
14560
+ * well-formed and names a modern protocol revision.
14561
+ *
14562
+ * Used by the `initialize` precedence rule: only such a claim overrides the
14563
+ * `initialize` ⇒ legacy-handshake classification — a request carrying a valid
14564
+ * modern envelope is a modern request regardless of its method name, and the
14565
+ * modern era then answers `initialize` exactly like any other method it does
14566
+ * not define (method-not-found). A malformed claim, or one naming a pre-2026
14567
+ * revision, keeps the legacy-handshake routing unchanged.
14568
+ *
14569
+ * Exported on the core internal barrel for the stdio serving entry, which
14570
+ * applies the same precedence rule to a connection's opening message; not
14571
+ * public API.
14572
+ */
14573
+ function carriesValidModernEnvelopeClaim(params) {
14574
+ if (!hasEnvelopeClaim(params)) return false;
14575
+ const claimedVersion = envelopeClaimVersion(params);
14576
+ if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
14577
+ const meta = requestMetaOf(params);
14578
+ return meta !== void 0 && validateEnvelopeMeta(meta).length === 0;
14579
+ }
14580
+ function classifyBatch(body) {
14581
+ if (body.length === 0) return rejection("jsonrpc-shape", "empty-batch", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: empty JSON-RPC batch"), true);
14582
+ for (const element of body) {
14583
+ if (hasEnvelopeClaim(isPlainObject$2(element) ? element["params"] : void 0)) return rejection("jsonrpc-shape", "batch-with-modern-element", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches may not contain requests for protocol revision 2026-07-28 or later"), true);
14584
+ if (!(isJSONRPCRequest(element) || isJSONRPCNotification(element) || isJSONRPCResultResponse(element) || isJSONRPCErrorResponse(element))) return rejection("jsonrpc-shape", "batch-with-invalid-element", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batch contains an invalid message"), true);
14585
+ }
14586
+ return {
14587
+ kind: "legacy",
14588
+ reason: "batch"
14589
+ };
14590
+ }
14591
+ function classifyRequestBody(request, body) {
14592
+ const params = body.params;
14593
+ const method = body.method;
14594
+ const headerVersion = request.protocolVersionHeader;
14595
+ const headerNamesModern = headerVersion !== void 0 && isModernProtocolVersion(headerVersion);
14596
+ if (method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
14597
+ if (headerNamesModern) return crossCheckMismatch("initialize-with-modern-header", headerVersion, "an initialize request (legacy handshake) was sent with a modern MCP-Protocol-Version header");
14598
+ const requestedVersion = isPlainObject$2(params) && typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : void 0;
14599
+ return {
14600
+ kind: "legacy",
14601
+ reason: "initialize",
14602
+ ...requestedVersion !== void 0 && { requestedVersion }
14603
+ };
14604
+ }
14605
+ if (hasEnvelopeClaim(params)) {
14606
+ const meta = requestMetaOf(params);
14607
+ const firstIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0];
14608
+ if (firstIssue !== void 0) return rejection("envelope", "envelope-invalid", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid _meta envelope for protocol revision 2026-07-28: ${firstIssue.key}: ${firstIssue.problem}`, { envelope: firstIssue }), true);
14609
+ const claimedVersion = envelopeClaimVersion(params);
14610
+ if (headerVersion !== void 0 && claimedVersion !== void 0 && headerVersion !== claimedVersion) return crossCheckMismatch("header-body-version-mismatch", headerVersion, `the body envelope names protocol version ${claimedVersion} but the MCP-Protocol-Version header names ${headerVersion}`);
14611
+ if (request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("method-header-mismatch", request.mcpMethodHeader, `the body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
14612
+ return {
14613
+ kind: "modern",
14614
+ messageKind: "request",
14615
+ message: body,
14616
+ classification: classificationForClaim(claimedVersion)
14617
+ };
14618
+ }
14619
+ if (headerNamesModern) {
14620
+ const meta = requestMetaOf(params);
14621
+ const missingFromEnvelope = validateEnvelopeMeta(meta ?? {}).filter((issue) => issue.problem === "missing").map((issue) => issue.key);
14622
+ const missing = meta === void 0 ? ["_meta"] : missingFromEnvelope.length > 0 ? missingFromEnvelope : [PROTOCOL_VERSION_META_KEY];
14623
+ return rejection("envelope", "modern-header-without-claim", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid params: the MCP-Protocol-Version header names protocol revision ${headerVersion}, but the request is missing the required per-request envelope key(s): ${missing.join(", ")}`, { envelope: { missing } }), true);
14624
+ }
14625
+ return {
14626
+ kind: "legacy",
14627
+ reason: "no-claim",
14628
+ ...headerVersion !== void 0 && { requestedVersion: headerVersion }
14629
+ };
14630
+ }
14631
+ function classifyNotificationBody(request, body) {
14632
+ const params = body.params;
14633
+ const method = body.method;
14634
+ const headerVersion = request.protocolVersionHeader;
14635
+ const headerNamesModern = headerVersion !== void 0 && isModernProtocolVersion(headerVersion);
14636
+ if (hasEnvelopeClaim(params)) {
14637
+ const claimedVersion = envelopeClaimVersion(params);
14638
+ if (claimedVersion === void 0) {
14639
+ const meta = requestMetaOf(params);
14640
+ const claimIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta)).find((issue) => issue.key === "io.modelcontextprotocol/protocolVersion") ?? {
14641
+ key: "io.modelcontextprotocol/protocolVersion",
14642
+ problem: "expected a protocol version string"
14643
+ };
14644
+ return rejection("envelope", "notification-envelope-invalid", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid _meta envelope for protocol revision 2026-07-28: ${claimIssue.key}: ${claimIssue.problem}`, { envelope: claimIssue }), true);
14645
+ }
14646
+ if (headerVersion !== void 0 && headerVersion !== claimedVersion) return crossCheckMismatch("notification-header-body-version-mismatch", headerVersion, `the notification envelope names protocol version ${claimedVersion} but the MCP-Protocol-Version header names ${headerVersion}`);
14647
+ const classification = classificationForClaim(claimedVersion);
14648
+ if (classification.era === "modern" && request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("notification-method-header-mismatch", request.mcpMethodHeader, `the notification body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
14649
+ return {
14650
+ kind: "modern",
14651
+ messageKind: "notification",
14652
+ message: body,
14653
+ classification
14654
+ };
14655
+ }
14656
+ if (headerNamesModern) {
14657
+ if (request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("notification-method-header-mismatch", request.mcpMethodHeader, `the notification body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
14658
+ return {
14659
+ kind: "modern",
14660
+ messageKind: "notification",
14661
+ message: body,
14662
+ classification: {
14663
+ era: "modern",
14664
+ revision: headerVersion
14665
+ }
14666
+ };
14667
+ }
14668
+ return {
14669
+ kind: "legacy",
14670
+ reason: "notification",
14671
+ ...headerVersion !== void 0 && { requestedVersion: headerVersion }
14672
+ };
14673
+ }
14674
+ /**
14675
+ * Classifies one inbound HTTP request for dual-era serving.
14676
+ *
14677
+ * The body-primary predicate, evaluated once at the entry boundary: see the
14678
+ * module documentation for the rules. Returns a routing outcome (`legacy` or
14679
+ * `modern`) or a ladder rejection; it never throws.
14680
+ */
14681
+ function classifyInboundRequest(request) {
14682
+ request = {
14683
+ ...request,
14684
+ ...request.protocolVersionHeader !== void 0 && { protocolVersionHeader: stripHttpOws(request.protocolVersionHeader) },
14685
+ ...request.mcpMethodHeader !== void 0 && { mcpMethodHeader: stripHttpOws(request.mcpMethodHeader) },
14686
+ ...request.mcpNameHeader !== void 0 && { mcpNameHeader: stripHttpOws(request.mcpNameHeader) }
14687
+ };
14688
+ if (request.httpMethod.toUpperCase() !== "POST") return {
14689
+ kind: "legacy",
14690
+ reason: "http-method"
14691
+ };
14692
+ const body = request.body;
14693
+ if (Array.isArray(body)) return classifyBatch(body);
14694
+ if (isJSONRPCResultResponse(body) || isJSONRPCErrorResponse(body)) return {
14695
+ kind: "legacy",
14696
+ reason: "response"
14697
+ };
14698
+ if (isPlainObject$2(body) && isJSONRPCRequest(body)) return classifyRequestBody(request, body);
14699
+ if (isPlainObject$2(body) && isJSONRPCNotification(body)) return classifyNotificationBody(request, body);
14700
+ return rejection("jsonrpc-shape", "invalid-json-rpc-body", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: the request body is not a valid JSON-RPC message"), true);
14701
+ }
14702
+ /**
14703
+ * The rejection a modern-only endpoint (no legacy serving configured)
14704
+ * answers a legacy-classified request with.
14705
+ *
14706
+ * - Envelope-less requests (including `initialize`) are answered with the
14707
+ * unsupported-protocol-version error carrying the endpoint's supported
14708
+ * versions and echoing the version the request named (when it named one —
14709
+ * `requested` is omitted rather than fabricated when the request named no
14710
+ * version at all), so a legacy client can discover what the endpoint serves
14711
+ * from the error alone.
14712
+ * - Posted responses and batch arrays are invalid requests on the modern era.
14713
+ * - Non-`POST` methods are not allowed.
14714
+ * - Legacy-classified notifications return `undefined`: the caller answers
14715
+ * 202 with no body and does not dispatch the notification (accept-and-drop).
14716
+ */
14717
+ function modernOnlyStrictRejection(route, supportedVersions) {
14718
+ switch (route.reason) {
14719
+ case "http-method": return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
14720
+ case "batch": return rejection("jsonrpc-shape", "modern-only-batch-not-supported", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches are not supported by this endpoint"), true);
14721
+ case "response": return rejection("jsonrpc-shape", "modern-only-response-post", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC responses cannot be posted to this endpoint"), true);
14722
+ case "notification": return;
14723
+ case "initialize":
14724
+ case "no-claim": {
14725
+ const requested = route.requestedVersion;
14726
+ return rejection("era-classification", "modern-only-missing-envelope", 400, requested === void 0 ? new ProtocolError(ProtocolErrorCode.UnsupportedProtocolVersion, "Unsupported protocol version: the request did not name a protocol version", { supported: [...supportedVersions] }) : new UnsupportedProtocolVersionError({
14727
+ supported: [...supportedVersions],
14728
+ requested
14729
+ }), true);
14730
+ }
14731
+ }
14732
+ }
14430
14733
  /**
14431
14734
  * Internal Zod schema utilities for protocol handling.
14432
14735
  * These are used internally by the SDK for protocol message validation.
@@ -15119,6 +15422,17 @@ function withRequestStateValue(ctx, value) {
15119
15422
  }
15120
15423
  };
15121
15424
  }
15425
+ let writeNegotiatedProtocolVersion;
15426
+ /**
15427
+ * Package-internal write channel for a {@linkcode Protocol} instance's
15428
+ * negotiated protocol version, for callers outside the class hierarchy:
15429
+ * tests and the (future) modern-era server entry that marks a factory
15430
+ * instance modern at binding time. Exported on the core internal barrel
15431
+ * only — never public API.
15432
+ */
15433
+ function setNegotiatedProtocolVersion(instance, version) {
15434
+ writeNegotiatedProtocolVersion(instance, version);
15435
+ }
15122
15436
  /**
15123
15437
  * Implements MCP protocol framing on top of a pluggable transport, including
15124
15438
  * features like request/response linking, notifications, and progress.
@@ -15145,7 +15459,11 @@ var Protocol = class {
15145
15459
  * (`Client.connect`, `Server._oninitialize`).
15146
15460
  */
15147
15461
  _negotiatedProtocolVersion;
15148
- static {}
15462
+ static {
15463
+ writeNegotiatedProtocolVersion = (instance, version) => {
15464
+ instance._negotiatedProtocolVersion = version;
15465
+ };
15466
+ }
15149
15467
  _supportedProtocolVersions;
15150
15468
  /**
15151
15469
  * Callback for when the connection is closed for any reason.
@@ -15872,7 +16190,7 @@ function mergeCapabilities(base, additional) {
15872
16190
  }
15873
16191
  return result;
15874
16192
  }
15875
- function isPlainObject$2(value) {
16193
+ function isPlainObject$8(value) {
15876
16194
  return typeof value === "object" && value !== null && !Array.isArray(value);
15877
16195
  }
15878
16196
  /**
@@ -15886,12 +16204,12 @@ function isPlainObject$2(value) {
15886
16204
  function partitionInputResponses(inputResponses) {
15887
16205
  const accepted = {};
15888
16206
  const droppedKeys = [];
15889
- if (!isPlainObject$2(inputResponses)) return {
16207
+ if (!isPlainObject$8(inputResponses)) return {
15890
16208
  accepted,
15891
16209
  droppedKeys
15892
16210
  };
15893
16211
  for (const [key, entry] of Object.entries(inputResponses)) {
15894
- if (!isPlainObject$2(entry) || "method" in entry || "result" in entry) {
16212
+ if (!isPlainObject$8(entry) || "method" in entry || "result" in entry) {
15895
16213
  droppedKeys.push(key);
15896
16214
  continue;
15897
16215
  }
@@ -23559,6 +23877,409 @@ function armSseKeepAlive(intervalMs, onTick) {
23559
23877
  timer.unref?.();
23560
23878
  return timer;
23561
23879
  }
23880
+ /**
23881
+ * A `ServerEventBus` backed by an in-process listener set.
23882
+ *
23883
+ * `publish()` delivers synchronously to the live listener set (a listener
23884
+ * unsubscribing itself mid-dispatch is safe; the entry's listen-router
23885
+ * listeners never unsubscribe peers). A throwing listener does not stop
23886
+ * delivery to the others.
23887
+ */
23888
+ var InMemoryServerEventBus = class {
23889
+ _listeners = /* @__PURE__ */ new Set();
23890
+ /**
23891
+ * @param onerror - Optional callback for errors thrown by listeners
23892
+ * during dispatch.
23893
+ */
23894
+ constructor(onerror) {
23895
+ this.onerror = onerror;
23896
+ }
23897
+ publish(event) {
23898
+ for (const listener of this._listeners) try {
23899
+ listener(event);
23900
+ } catch (error) {
23901
+ this.onerror?.(error instanceof Error ? error : new Error(String(error)));
23902
+ }
23903
+ }
23904
+ subscribe(listener) {
23905
+ this._listeners.add(listener);
23906
+ let live = true;
23907
+ return () => {
23908
+ if (!live) return;
23909
+ live = false;
23910
+ this._listeners.delete(listener);
23911
+ };
23912
+ }
23913
+ /** The number of currently registered listeners (test/introspection only — the routers track capacity via their own open-subscription set). */
23914
+ get listenerCount() {
23915
+ return this._listeners.size;
23916
+ }
23917
+ };
23918
+ /** Build a {@linkcode ServerNotifier} over a bus. */
23919
+ function createServerNotifier(bus) {
23920
+ return {
23921
+ toolsChanged: () => bus.publish({ kind: "tools_list_changed" }),
23922
+ promptsChanged: () => bus.publish({ kind: "prompts_list_changed" }),
23923
+ resourcesChanged: () => bus.publish({ kind: "resources_list_changed" }),
23924
+ resourceUpdated: (uri) => bus.publish({
23925
+ kind: "resource_updated",
23926
+ uri
23927
+ })
23928
+ };
23929
+ }
23930
+ /**
23931
+ * Whether a `subscriptions/listen` filter accepts a given change event.
23932
+ *
23933
+ * Pure: no I/O, no mutation. The filter governs ONLY the four
23934
+ * subscription-gated change types — non-gated notifications never reach the
23935
+ * bus and are not modeled here.
23936
+ *
23937
+ * `resource_updated` matches only when `resourceSubscriptions` is present and
23938
+ * contains the event's URI exactly (per the spec: "for these resource URIs").
23939
+ */
23940
+ function listenFilterAccepts(filter, event) {
23941
+ switch (event.kind) {
23942
+ case "tools_list_changed": return filter.toolsListChanged === true;
23943
+ case "prompts_list_changed": return filter.promptsListChanged === true;
23944
+ case "resources_list_changed": return filter.resourcesListChanged === true;
23945
+ case "resource_updated": return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
23946
+ }
23947
+ }
23948
+ /**
23949
+ * The honored subset of a requested filter: keeps only the fields the client
23950
+ * explicitly opted in to (drops `false` and absent fields), narrowed against
23951
+ * the server's declared capabilities when supplied. The serving entry sends
23952
+ * this back in `notifications/subscriptions/acknowledged` so the ack reflects
23953
+ * what the server can actually deliver.
23954
+ *
23955
+ * - `toolsListChanged` is honored only when `capabilities.tools.listChanged`
23956
+ * is advertised; likewise `promptsListChanged` / `resourcesListChanged`.
23957
+ * - `resourceSubscriptions` is honored only when
23958
+ * `capabilities.resources.subscribe` is advertised.
23959
+ *
23960
+ * `capabilities` is optional on this pure helper for test convenience only —
23961
+ * both wired routers REQUIRE capabilities at the call site (the HTTP router's
23962
+ * `serve()` takes a required parameter; `StdioListenRouter.serve()` throws
23963
+ * before `setServerCapabilities()` was called), so the fail-open
23964
+ * `undefined → honor everything` branch is never reachable on a wired entry.
23965
+ */
23966
+ function honoredSubset(requested, capabilities) {
23967
+ const honored = {};
23968
+ const allow = (bit) => capabilities === void 0 || bit === true;
23969
+ if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
23970
+ if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
23971
+ if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
23972
+ if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
23973
+ return honored;
23974
+ }
23975
+ /** Map a {@linkcode ServerEvent} onto its wire notification `{method, params}`. */
23976
+ function serverEventToNotification(event) {
23977
+ switch (event.kind) {
23978
+ case "tools_list_changed": return { method: "notifications/tools/list_changed" };
23979
+ case "prompts_list_changed": return { method: "notifications/prompts/list_changed" };
23980
+ case "resources_list_changed": return { method: "notifications/resources/list_changed" };
23981
+ case "resource_updated": return {
23982
+ method: "notifications/resources/updated",
23983
+ params: { uri: event.uri }
23984
+ };
23985
+ }
23986
+ }
23987
+ /** Default capacity guard: refuse a new subscription when this many are already open. */
23988
+ const DEFAULT_MAX_SUBSCRIPTIONS = 1024;
23989
+ function jsonRpcError(id, code, message) {
23990
+ return Response.json({
23991
+ jsonrpc: "2.0",
23992
+ error: {
23993
+ code,
23994
+ message
23995
+ },
23996
+ id
23997
+ }, { status: 200 });
23998
+ }
23999
+ /** Stamp the subscription id onto a notification's `_meta`. Non-mutating. */
24000
+ function stampSubscriptionId(notification, subscriptionId) {
24001
+ return {
24002
+ method: notification.method,
24003
+ params: {
24004
+ ...notification.params,
24005
+ _meta: {
24006
+ ...notification.params?._meta,
24007
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId
24008
+ }
24009
+ }
24010
+ };
24011
+ }
24012
+ /**
24013
+ * Read the requested filter off a `subscriptions/listen` request body.
24014
+ * Returns the validated filter, or `undefined` when `params.notifications`
24015
+ * is absent or fails the schema (the caller answers `-32602` — the spec
24016
+ * marks `notifications` REQUIRED on the listen request).
24017
+ */
24018
+ function parseListenFilter(message) {
24019
+ const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
24020
+ return outcome.ok ? outcome.value.params?.notifications : void 0;
24021
+ }
24022
+ function createListenRouter(options) {
24023
+ const { bus, onerror } = options;
24024
+ const maxSubscriptions = options.maxSubscriptions ?? 1024;
24025
+ const keepAliveMs = options.keepAliveMs ?? 15e3;
24026
+ const open = /* @__PURE__ */ new Set();
24027
+ function serve(message, signal, capabilities, serverInfo) {
24028
+ if (open.size >= maxSubscriptions) {
24029
+ onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
24030
+ return jsonRpcError(message.id, -32603, "Subscription limit reached");
24031
+ }
24032
+ const filter = parseListenFilter(message);
24033
+ if (filter === void 0) return jsonRpcError(message.id, -32602, "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter");
24034
+ const honored = honoredSubset(filter, capabilities);
24035
+ const subscriptionId = message.id;
24036
+ const encoder = new TextEncoder();
24037
+ let controller;
24038
+ let closed = false;
24039
+ let unsubscribe;
24040
+ let keepAliveTimer;
24041
+ let abortCleanup;
24042
+ const writeFrame = (frame) => {
24043
+ if (closed) return;
24044
+ try {
24045
+ controller.enqueue(encoder.encode(frame));
24046
+ } catch (error) {
24047
+ onerror?.(error instanceof Error ? error : new Error(String(error)));
24048
+ }
24049
+ };
24050
+ const writeNotification = (method, params) => {
24051
+ writeFrame(`event: message\ndata: ${JSON.stringify({
24052
+ jsonrpc: "2.0",
24053
+ method,
24054
+ params
24055
+ })}\n\n`);
24056
+ };
24057
+ const teardown = (graceful) => {
24058
+ if (closed) return;
24059
+ if (graceful) writeFrame(`event: message\ndata: ${JSON.stringify({
24060
+ jsonrpc: "2.0",
24061
+ id: subscriptionId,
24062
+ result: {
24063
+ resultType: "complete",
24064
+ _meta: {
24065
+ [SUBSCRIPTION_ID_META_KEY]: subscriptionId,
24066
+ [SERVER_INFO_META_KEY]: serverInfo
24067
+ }
24068
+ }
24069
+ })}\n\n`);
24070
+ closed = true;
24071
+ try {
24072
+ unsubscribe?.();
24073
+ } catch (error) {
24074
+ onerror?.(error instanceof Error ? error : new Error(String(error)));
24075
+ }
24076
+ if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
24077
+ abortCleanup?.();
24078
+ open.delete(teardown);
24079
+ try {
24080
+ controller.close();
24081
+ } catch {}
24082
+ };
24083
+ const readable = new ReadableStream({
24084
+ start(streamController) {
24085
+ controller = streamController;
24086
+ const ack = stampSubscriptionId({
24087
+ method: "notifications/subscriptions/acknowledged",
24088
+ params: { notifications: honored }
24089
+ }, subscriptionId);
24090
+ writeNotification(ack.method, ack.params);
24091
+ if (Object.keys(honored).length === 0) {
24092
+ teardown(true);
24093
+ return;
24094
+ }
24095
+ unsubscribe = bus.subscribe((event) => {
24096
+ if (closed || !listenFilterAccepts(honored, event)) return;
24097
+ const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
24098
+ writeNotification(note.method, note.params);
24099
+ });
24100
+ keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
24101
+ open.add(teardown);
24102
+ },
24103
+ cancel() {
24104
+ teardown(false);
24105
+ }
24106
+ });
24107
+ if (signal !== void 0) if (signal.aborted) teardown(false);
24108
+ else {
24109
+ const onAbort = () => teardown(false);
24110
+ signal.addEventListener("abort", onAbort, { once: true });
24111
+ abortCleanup = () => signal.removeEventListener("abort", onAbort);
24112
+ }
24113
+ return new Response(readable, {
24114
+ status: 200,
24115
+ headers: {
24116
+ "Content-Type": "text/event-stream",
24117
+ "Cache-Control": "no-cache, no-transform",
24118
+ Connection: "keep-alive",
24119
+ "X-Accel-Buffering": "no"
24120
+ }
24121
+ });
24122
+ }
24123
+ return {
24124
+ serve,
24125
+ closeAll() {
24126
+ for (const teardown of open) teardown(true);
24127
+ },
24128
+ get openCount() {
24129
+ return open.size;
24130
+ }
24131
+ };
24132
+ }
24133
+ const CHANGE_NOTIFICATION_METHODS = /* @__PURE__ */ new Set([
24134
+ "notifications/tools/list_changed",
24135
+ "notifications/prompts/list_changed",
24136
+ "notifications/resources/list_changed",
24137
+ "notifications/resources/updated"
24138
+ ]);
24139
+ /**
24140
+ * Per-connection listen state for the stdio entry. One instance is held by
24141
+ * `serveStdio` for the connection lifetime; it routes inbound
24142
+ * `subscriptions/listen` / `notifications/cancelled` and rewrites outbound
24143
+ * change notifications onto the active subscriptions. No bus — the long-lived
24144
+ * pinned instance's existing `send*ListChanged()` calls feed straight into
24145
+ * `routeOutbound()`.
24146
+ */
24147
+ var StdioListenRouter = class {
24148
+ /** Active subscriptions, keyed by the listen request's JSON-RPC id verbatim. */
24149
+ _subs = /* @__PURE__ */ new Map();
24150
+ /**
24151
+ * The serving instance's declared capabilities. Filled in by the entry
24152
+ * once the modern instance is constructed (the router is created before
24153
+ * the instance exists), so the acknowledged filter is narrowed against
24154
+ * what the server can actually deliver.
24155
+ */
24156
+ _serverCapabilities;
24157
+ /**
24158
+ * The serving instance's identity, stamped onto the graceful-close
24159
+ * results' `_meta` (the spec's `SubscriptionsListenResultMetaObject` extends
24160
+ * `ResultMetaObject`). Handed over together with the capabilities.
24161
+ */
24162
+ _serverInfo;
24163
+ constructor(_maxSubscriptions = DEFAULT_MAX_SUBSCRIPTIONS, serverCapabilities, serverInfo) {
24164
+ this._maxSubscriptions = _maxSubscriptions;
24165
+ this._serverCapabilities = serverCapabilities;
24166
+ this._serverInfo = serverInfo;
24167
+ }
24168
+ /**
24169
+ * Record the serving instance's declared capabilities and identity once
24170
+ * it has been constructed. Called by `serveStdio`'s connect path;
24171
+ * subsequent `serve()` calls narrow the honored filter against the
24172
+ * capabilities, and `teardownAll()` stamps the identity.
24173
+ */
24174
+ setServerCapabilities(capabilities, serverInfo) {
24175
+ this._serverCapabilities = capabilities;
24176
+ if (serverInfo !== void 0) this._serverInfo = serverInfo;
24177
+ }
24178
+ /** Whether `id` is an active listen subscription on this connection. */
24179
+ has(id) {
24180
+ return this._subs.has(id);
24181
+ }
24182
+ /**
24183
+ * Serve one inbound `subscriptions/listen` request: registers the
24184
+ * subscription and returns the stamped acknowledged notification (or, on
24185
+ * capacity / params rejection, the in-band JSON-RPC error response).
24186
+ *
24187
+ * @throws when called before {@linkcode setServerCapabilities} (or the
24188
+ * constructor) has supplied the serving instance's capabilities. Honoring a
24189
+ * filter without knowing the server's advertised capabilities would fail
24190
+ * open (deliver unadvertised types); the entry guarantees capabilities are
24191
+ * set before any listen request is routed here.
24192
+ */
24193
+ serve(message) {
24194
+ if (this._serverCapabilities === void 0) throw new Error("StdioListenRouter.serve() called before setServerCapabilities(); refusing to honor a filter without capabilities");
24195
+ if (this._subs.size >= this._maxSubscriptions) return {
24196
+ jsonrpc: "2.0",
24197
+ id: message.id,
24198
+ error: {
24199
+ code: -32603,
24200
+ message: "Subscription limit reached"
24201
+ }
24202
+ };
24203
+ const filter = parseListenFilter(message);
24204
+ if (filter === void 0) return {
24205
+ jsonrpc: "2.0",
24206
+ id: message.id,
24207
+ error: {
24208
+ code: -32602,
24209
+ message: "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter"
24210
+ }
24211
+ };
24212
+ const honored = honoredSubset(filter, this._serverCapabilities);
24213
+ this._subs.set(message.id, honored);
24214
+ return stampSubscriptionId({
24215
+ method: "notifications/subscriptions/acknowledged",
24216
+ params: { notifications: honored }
24217
+ }, message.id);
24218
+ }
24219
+ /**
24220
+ * Tear down one subscription (inbound `notifications/cancelled`). Returns
24221
+ * `true` when a subscription was removed. After this call NOTHING further
24222
+ * is delivered for that subscription id (the post-cancel hardening).
24223
+ */
24224
+ cancel(id) {
24225
+ return this._subs.delete(id);
24226
+ }
24227
+ /**
24228
+ * Route an outbound notification through the active subscriptions.
24229
+ *
24230
+ * - For a subscription-gated change notification, returns one stamped copy
24231
+ * per subscription that opted in to it (an empty array means it is
24232
+ * dropped — the modern era never delivers an un-requested change type).
24233
+ * - For any other outbound message, returns `'passthrough'` (the entry
24234
+ * forwards it as-is).
24235
+ */
24236
+ routeOutbound(message) {
24237
+ if (!CHANGE_NOTIFICATION_METHODS.has(message.method)) return "passthrough";
24238
+ const uriParam = message.params?.["uri"];
24239
+ const uri = typeof uriParam === "string" ? uriParam : void 0;
24240
+ const event = notificationToServerEvent(message.method, uri);
24241
+ const out = [];
24242
+ for (const [subscriptionId, filter] of this._subs) if (listenFilterAccepts(filter, event)) out.push(stampSubscriptionId({
24243
+ method: message.method,
24244
+ params: message.params ?? {}
24245
+ }, subscriptionId));
24246
+ return out;
24247
+ }
24248
+ /**
24249
+ * Server-side graceful teardown of every active subscription: returns the
24250
+ * empty `subscriptions/listen` JSON-RPC result for each subscription id —
24251
+ * the spec's graceful-close signal, `_meta` carrying the subscription id
24252
+ * and the serving instance's identity — for the entry to emit before
24253
+ * closing the wire. Clears the set so nothing further is delivered.
24254
+ */
24255
+ teardownAll() {
24256
+ const out = [];
24257
+ for (const id of this._subs.keys()) out.push({
24258
+ jsonrpc: "2.0",
24259
+ id,
24260
+ result: {
24261
+ resultType: "complete",
24262
+ _meta: {
24263
+ [SUBSCRIPTION_ID_META_KEY]: id,
24264
+ ...this._serverInfo !== void 0 && { ["io.modelcontextprotocol/serverInfo"]: this._serverInfo }
24265
+ }
24266
+ }
24267
+ });
24268
+ this._subs.clear();
24269
+ return out;
24270
+ }
24271
+ };
24272
+ function notificationToServerEvent(method, uri) {
24273
+ switch (method) {
24274
+ case "notifications/tools/list_changed": return { kind: "tools_list_changed" };
24275
+ case "notifications/prompts/list_changed": return { kind: "prompts_list_changed" };
24276
+ case "notifications/resources/list_changed": return { kind: "resources_list_changed" };
24277
+ default: return {
24278
+ kind: "resource_updated",
24279
+ uri: uri ?? ""
24280
+ };
24281
+ }
24282
+ }
23562
24283
  function comparableResource(value) {
23563
24284
  const text = String(value);
23564
24285
  const hash = text.indexOf("#");
@@ -23877,6 +24598,41 @@ const INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
23877
24598
  "prompts/get",
23878
24599
  "resources/read"
23879
24600
  ]);
24601
+ let writeClientIdentity;
24602
+ let installDiscoverHandler;
24603
+ let readServerIdentity;
24604
+ /**
24605
+ * Package-internal: backfills the connection-scoped client-identity fields of a
24606
+ * per-request server instance from the request's validated `_meta` envelope, so the
24607
+ * (deprecated) {@linkcode Server.getClientCapabilities} / {@linkcode Server.getClientVersion}
24608
+ * accessors keep answering on instances that never see an `initialize` handshake.
24609
+ * Not public API.
24610
+ */
24611
+ function seedClientIdentityFromEnvelope(server, identity) {
24612
+ writeClientIdentity(server, identity);
24613
+ }
24614
+ /**
24615
+ * Package-internal: installs the modern-only `server/discover` handler on an instance
24616
+ * the HTTP entry has marked as serving the 2026-07-28 era, and makes sure the modern
24617
+ * revisions the entry serves appear in the instance's supported-versions list (so the
24618
+ * discover advertisement and version-mismatch errors name them). Idempotent.
24619
+ * Hand-constructed instances are unaffected: nothing else calls this, so they keep
24620
+ * answering `-32601` unless their own supported-versions list opts into a modern
24621
+ * revision. Not public API.
24622
+ */
24623
+ function installModernOnlyHandlers(server, servedModernVersions) {
24624
+ installDiscoverHandler(server, servedModernVersions);
24625
+ }
24626
+ /**
24627
+ * Package-internal: the instance's implementation identity, for the serving
24628
+ * entries to stamp onto entry-built results (the `subscriptions/listen`
24629
+ * graceful-close result — built outside the encode seam, but the spec's
24630
+ * `SubscriptionsListenResultMetaObject` extends `ResultMetaObject`, so it carries
24631
+ * the serverInfo SHOULD like every other result). Not public API.
24632
+ */
24633
+ function serverIdentityOf(server) {
24634
+ return readServerIdentity(server);
24635
+ }
23880
24636
  /**
23881
24637
  * An MCP server on top of a pluggable transport.
23882
24638
  *
@@ -23887,7 +24643,18 @@ const INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
23887
24643
  var Server = class extends Protocol {
23888
24644
  _clientCapabilities;
23889
24645
  _clientVersion;
23890
- static {}
24646
+ static {
24647
+ writeClientIdentity = (server, identity) => {
24648
+ if (identity.clientCapabilities !== void 0) server._clientCapabilities = identity.clientCapabilities;
24649
+ if (identity.clientInfo !== void 0) server._clientVersion = identity.clientInfo;
24650
+ };
24651
+ installDiscoverHandler = (server, servedModernVersions) => {
24652
+ const missing = servedModernVersions.filter((version) => !server._supportedProtocolVersions.includes(version));
24653
+ if (missing.length > 0) server._supportedProtocolVersions = [...server._supportedProtocolVersions, ...missing];
24654
+ server.setRequestHandler("server/discover", () => server._ondiscover());
24655
+ };
24656
+ readServerIdentity = (server) => server._serverInfo;
24657
+ }
23891
24658
  _capabilities;
23892
24659
  _instructions;
23893
24660
  _jsonSchemaValidator;
@@ -25184,6 +25951,245 @@ function unwrapOptionalSchema(schema) {
25184
25951
  }
25185
25952
  //#endregion
25186
25953
  //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/index.mjs
25954
+ /**
25955
+ * The per-request micro-transport: a real, connected `Transport` whose whole
25956
+ * lifetime is one HTTP exchange. See the module documentation for the
25957
+ * response shapes it produces.
25958
+ */
25959
+ var PerRequestHTTPServerTransport = class {
25960
+ onclose;
25961
+ onerror;
25962
+ onmessage;
25963
+ _classification;
25964
+ _responseMode;
25965
+ _started = false;
25966
+ _used = false;
25967
+ _closed = false;
25968
+ _terminalDelivered = false;
25969
+ /**
25970
+ * `true` only while the inbound message is being delivered synchronously
25971
+ * to the connected protocol layer. The pre-handler gates (the era
25972
+ * registry gate, the edge→instance handoff check, the missing-handler
25973
+ * rejection) answer inside this window; request handlers always run
25974
+ * after it (the protocol layer defers them to a microtask). An error
25975
+ * sent inside the window is therefore ladder-originated, and an error
25976
+ * sent after it is handler-produced.
25977
+ */
25978
+ _dispatchWindowOpen = false;
25979
+ _requestId;
25980
+ _deferredResponse;
25981
+ _sse;
25982
+ _abortCleanup;
25983
+ _keepAliveMs;
25984
+ constructor(options) {
25985
+ this._classification = options.classification;
25986
+ this._responseMode = options.responseMode ?? "auto";
25987
+ this._keepAliveMs = options.keepAliveMs ?? 15e3;
25988
+ }
25989
+ async start() {
25990
+ if (this._started) throw new Error("PerRequestHTTPServerTransport is already started");
25991
+ this._started = true;
25992
+ }
25993
+ /**
25994
+ * Serves the single exchange: delivers the classified message to the
25995
+ * connected server instance and resolves with the HTTP response.
25996
+ *
25997
+ * Throws when called a second time (the transport is strictly
25998
+ * single-use), or before a server has been connected to the transport.
25999
+ * The returned promise rejects with a connection-closed error when the
26000
+ * transport is closed before a response was produced (for example because
26001
+ * the client disconnected).
26002
+ */
26003
+ async handleMessage(message, extra) {
26004
+ if (this._used) throw new Error("PerRequestHTTPServerTransport serves exactly one exchange; construct a new transport per request");
26005
+ if (!this._started || this.onmessage === void 0) throw new Error("PerRequestHTTPServerTransport is not connected: connect a server to this transport before handling a message");
26006
+ if (this._closed) throw new Error("PerRequestHTTPServerTransport is closed");
26007
+ this._used = true;
26008
+ const signal = extra?.request?.signal;
26009
+ if (signal?.aborted) {
26010
+ await this.close();
26011
+ throw new SdkError(SdkErrorCode.ConnectionClosed, "The request was aborted before it could be handled");
26012
+ }
26013
+ const messageExtra = {
26014
+ classification: this._classification,
26015
+ ...extra?.request !== void 0 && { request: extra.request },
26016
+ ...extra?.authInfo !== void 0 && { authInfo: extra.authInfo }
26017
+ };
26018
+ if (isJSONRPCRequest(message)) {
26019
+ this._requestId = message.id;
26020
+ let resolve;
26021
+ let reject;
26022
+ const promise = new Promise((promiseResolve, promiseReject) => {
26023
+ resolve = promiseResolve;
26024
+ reject = promiseReject;
26025
+ });
26026
+ this._deferredResponse = {
26027
+ promise,
26028
+ resolve,
26029
+ reject,
26030
+ settled: false
26031
+ };
26032
+ if (signal !== void 0) {
26033
+ const onAbort = () => void this.close();
26034
+ signal.addEventListener("abort", onAbort, { once: true });
26035
+ this._abortCleanup = () => signal.removeEventListener("abort", onAbort);
26036
+ }
26037
+ this._dispatchWindowOpen = true;
26038
+ try {
26039
+ this.onmessage(message, messageExtra);
26040
+ } finally {
26041
+ this._dispatchWindowOpen = false;
26042
+ }
26043
+ if (this._responseMode === "sse" && !this._closed && !this._deferredResponse.settled) this.upgradeToSse();
26044
+ return promise;
26045
+ }
26046
+ this.onmessage(message, messageExtra);
26047
+ return new Response(null, { status: 202 });
26048
+ }
26049
+ async send(message, options) {
26050
+ if (this._closed) return;
26051
+ const isResponse = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message);
26052
+ const relatedId = isResponse ? message.id : options?.relatedRequestId;
26053
+ if (this._requestId === void 0 || relatedId === void 0 || relatedId !== this._requestId) {
26054
+ if (isResponse) this.onerror?.(/* @__PURE__ */ new Error(`Received a response for an unknown request id: ${String(message.id)}`));
26055
+ return;
26056
+ }
26057
+ if (isResponse) {
26058
+ if (this._terminalDelivered) return;
26059
+ this._terminalDelivered = true;
26060
+ const errorCode = isJSONRPCErrorResponse(message) ? message.error.code : void 0;
26061
+ const ladderStatus = errorCode !== void 0 && (this._dispatchWindowOpen || errorCode === ProtocolErrorCode.MissingRequiredClientCapability) ? LADDER_ERROR_HTTP_STATUS[errorCode] : void 0;
26062
+ if (ladderStatus !== void 0 && this._sse === void 0) {
26063
+ this.settleResponse(Response.json(message, {
26064
+ status: ladderStatus,
26065
+ headers: { "Content-Type": "application/json" }
26066
+ }));
26067
+ queueMicrotask(() => void this.close());
26068
+ return;
26069
+ }
26070
+ if (this._sse !== void 0 || this._responseMode === "sse") {
26071
+ if (this._sse === void 0) this.upgradeToSse();
26072
+ this.writeMessageFrame(message);
26073
+ this.finalizeStream();
26074
+ return;
26075
+ }
26076
+ this.settleResponse(Response.json(message, {
26077
+ status: 200,
26078
+ headers: { "Content-Type": "application/json" }
26079
+ }));
26080
+ queueMicrotask(() => void this.close());
26081
+ return;
26082
+ }
26083
+ if (this._responseMode === "json") return;
26084
+ if (this._sse === void 0) this.upgradeToSse();
26085
+ this.writeMessageFrame(message);
26086
+ }
26087
+ /**
26088
+ * Writes an SSE comment frame (a keep-alive heartbeat). Dropped when the
26089
+ * exchange is not currently streaming.
26090
+ */
26091
+ writeCommentFrame(comment) {
26092
+ if (this._closed || this._sse === void 0 || this._sse.closed) return;
26093
+ const frame = comment.split("\n").map((line) => `: ${line}`).join("\n");
26094
+ this.writeFrame(`${frame}\n\n`);
26095
+ }
26096
+ async close() {
26097
+ if (this._closed) return;
26098
+ this._closed = true;
26099
+ this._abortCleanup?.();
26100
+ this._abortCleanup = void 0;
26101
+ if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
26102
+ if (this._sse !== void 0 && !this._sse.closed) {
26103
+ this._sse.closed = true;
26104
+ try {
26105
+ this._sse.controller.close();
26106
+ } catch {}
26107
+ }
26108
+ if (this._deferredResponse !== void 0 && !this._deferredResponse.settled) {
26109
+ this._deferredResponse.settled = true;
26110
+ this._deferredResponse.reject(new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed before a response was produced"));
26111
+ }
26112
+ this.onclose?.();
26113
+ }
26114
+ settleResponse(response) {
26115
+ if (this._deferredResponse === void 0 || this._deferredResponse.settled) return;
26116
+ this._deferredResponse.settled = true;
26117
+ this._deferredResponse.resolve(response);
26118
+ }
26119
+ upgradeToSse() {
26120
+ let controller;
26121
+ const readable = new ReadableStream({
26122
+ start: (streamController) => {
26123
+ controller = streamController;
26124
+ },
26125
+ cancel: () => {
26126
+ this.close();
26127
+ }
26128
+ });
26129
+ this._sse = {
26130
+ controller,
26131
+ encoder: new TextEncoder(),
26132
+ closed: false
26133
+ };
26134
+ this._sse.keepAliveTimer = armSseKeepAlive(this._keepAliveMs, () => this.writeCommentFrame("keepalive"));
26135
+ this.settleResponse(new Response(readable, {
26136
+ status: 200,
26137
+ headers: {
26138
+ "Content-Type": "text/event-stream",
26139
+ "Cache-Control": "no-cache, no-transform",
26140
+ Connection: "keep-alive",
26141
+ "X-Accel-Buffering": "no"
26142
+ }
26143
+ }));
26144
+ }
26145
+ finalizeStream() {
26146
+ if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
26147
+ if (this._sse !== void 0 && !this._sse.closed) {
26148
+ this._sse.closed = true;
26149
+ try {
26150
+ this._sse.controller.close();
26151
+ } catch {}
26152
+ }
26153
+ queueMicrotask(() => void this.close());
26154
+ }
26155
+ writeMessageFrame(message) {
26156
+ this.writeFrame(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
26157
+ }
26158
+ writeFrame(frame) {
26159
+ if (this._sse === void 0 || this._sse.closed) return;
26160
+ try {
26161
+ this._sse.controller.enqueue(this._sse.encoder.encode(frame));
26162
+ } catch (error) {
26163
+ this.onerror?.(/* @__PURE__ */ new Error(`Failed to write to the response stream: ${error}`));
26164
+ }
26165
+ }
26166
+ };
26167
+ /**
26168
+ * Serves one classified inbound message on the given server instance and
26169
+ * returns the HTTP response for the exchange.
26170
+ *
26171
+ * The instance is connected to a fresh single-exchange transport, the message
26172
+ * is injected through the normal transport message path, and whatever the
26173
+ * dispatch layer produces (the handler result, a protocol-level rejection, or
26174
+ * streamed related messages followed by the result) is captured as the
26175
+ * returned `Response`. For request exchanges, teardown rides the transport's
26176
+ * close chain once the terminal response has been delivered; notification
26177
+ * exchanges resolve with the 202 response immediately and do NOT run the
26178
+ * close chain — the transport stays connected until the caller closes it or
26179
+ * drops the per-request instance, which is the caller's choice either way.
26180
+ */
26181
+ async function invoke(server, message, ctx) {
26182
+ const transport = new PerRequestHTTPServerTransport({
26183
+ classification: ctx.classification,
26184
+ ...ctx.responseMode !== void 0 && { responseMode: ctx.responseMode },
26185
+ ...ctx.keepAliveMs !== void 0 && { keepAliveMs: ctx.keepAliveMs }
26186
+ });
26187
+ await server.connect(transport);
26188
+ return transport.handleMessage(message, {
26189
+ ...ctx.request !== void 0 && { request: ctx.request },
26190
+ ...ctx.authInfo !== void 0 && { authInfo: ctx.authInfo }
26191
+ });
26192
+ }
25187
26193
  /** Default upper bound, in bytes, on a request body read by the HTTP entry points (4 MiB). */
25188
26194
  const DEFAULT_MAX_REQUEST_BODY_SIZE = 4194304;
25189
26195
  /** Upper bound on the number of messages accepted in one JSON-RPC batch array. */
@@ -25934,6 +26940,995 @@ var WebStandardStreamableHTTPServerTransport = class {
25934
26940
  }
25935
26941
  }
25936
26942
  };
26943
+ /**
26944
+ * The JSON-RPC id to echo on an entry-built error response: the body's `id`
26945
+ * when the body is a single JSON-RPC request whose id is a string or number,
26946
+ * `null` otherwise. Error responses must carry the id of the request they
26947
+ * correspond to whenever it could be read; `null` is reserved for the cases
26948
+ * where no single request id is determinable — unparseable bodies, body-less
26949
+ * methods, notifications, posted responses and batch arrays.
26950
+ */
26951
+ function echoableRequestId(body) {
26952
+ if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
26953
+ const { method, id } = body;
26954
+ if (typeof method !== "string") return null;
26955
+ return typeof id === "string" || typeof id === "number" ? id : null;
26956
+ }
26957
+ function jsonRpcErrorResponse(httpStatus, code, message, data, id = null) {
26958
+ return Response.json({
26959
+ jsonrpc: "2.0",
26960
+ error: {
26961
+ code,
26962
+ message,
26963
+ ...data !== void 0 && { data }
26964
+ },
26965
+ id
26966
+ }, { status: httpStatus });
26967
+ }
26968
+ function rejectionResponse(rejection, id = null) {
26969
+ return jsonRpcErrorResponse(rejection.httpStatus, rejection.code, rejection.message, rejection.data, id);
26970
+ }
26971
+ function toError$1(value) {
26972
+ return value instanceof Error ? value : new Error(String(value));
26973
+ }
26974
+ function internalServerErrorResponse(id = null) {
26975
+ return jsonRpcErrorResponse(500, -32603, "Internal server error", void 0, id);
26976
+ }
26977
+ /** Throws when the factory returned an instance that is still connected to another exchange. */
26978
+ function assertNotServing(server) {
26979
+ if (server.transport !== void 0) throw new Error("The factory returned an instance that is still serving another request. Return a new instance for each request.");
26980
+ }
26981
+ /**
26982
+ * The entry's default legacy serving (`legacy: 'stateless'`): per-request
26983
+ * stateless serving of 2025-era traffic using the same factory as the modern
26984
+ * path. Exported as a standalone building block for hand-wired compositions
26985
+ * (for example mounting legacy stateless serving on its own route next to a
26986
+ * strict modern endpoint).
26987
+ *
26988
+ * Each POST is served by a fresh instance from the factory connected to a
26989
+ * fresh streamable HTTP transport constructed with only
26990
+ * `sessionIdGenerator: undefined` — the established stateless idiom, unchanged.
26991
+ * Because serving is per-request and stateless, GET and DELETE (2025 session
26992
+ * operations) are answered with `405` / `Method not allowed.`, exactly like the
26993
+ * canonical stateless example.
26994
+ *
26995
+ * The optional `onerror` callback receives factory and serving failures on
26996
+ * this leg (reporting only — the response stays the 500 internal-error body).
26997
+ * The entry passes its own `onerror` here when expanding the default, so
26998
+ * legacy-leg failures are never silently swallowed.
26999
+ */
27000
+ function createLegacyStatelessFallback(factory, onerror, keepAliveMs, maxRequestBodySize) {
27001
+ return async (request, options) => {
27002
+ if (request.method.toUpperCase() !== "POST") return jsonRpcErrorResponse(405, -32e3, "Method not allowed.");
27003
+ try {
27004
+ const product = await factory({
27005
+ era: "legacy",
27006
+ ...options?.authInfo !== void 0 && { authInfo: options.authInfo },
27007
+ requestInfo: request
27008
+ });
27009
+ const transport = new WebStandardStreamableHTTPServerTransport({
27010
+ sessionIdGenerator: void 0,
27011
+ ...keepAliveMs !== void 0 && { keepAliveMs },
27012
+ ...maxRequestBodySize !== void 0 && { maxRequestBodySize }
27013
+ });
27014
+ await product.connect(transport);
27015
+ const teardown = () => {
27016
+ transport.close().catch(() => {});
27017
+ product.close().catch(() => {});
27018
+ };
27019
+ request.signal?.addEventListener("abort", teardown, { once: true });
27020
+ const response = await transport.handleRequest(request, {
27021
+ ...options?.authInfo !== void 0 && { authInfo: options.authInfo },
27022
+ ...options?.parsedBody !== void 0 && { parsedBody: options.parsedBody }
27023
+ });
27024
+ if (response.body === null || mediaTypeEssence(response.headers.get("content-type")) !== "text/event-stream") {
27025
+ teardown();
27026
+ return response;
27027
+ }
27028
+ const reader = response.body.getReader();
27029
+ let toreDown = false;
27030
+ const completeExchange = () => {
27031
+ if (!toreDown) {
27032
+ toreDown = true;
27033
+ teardown();
27034
+ }
27035
+ };
27036
+ const monitoredBody = new ReadableStream({
27037
+ pull: async (controller) => {
27038
+ try {
27039
+ const { done, value } = await reader.read();
27040
+ if (done) {
27041
+ completeExchange();
27042
+ controller.close();
27043
+ return;
27044
+ }
27045
+ if (value !== void 0) controller.enqueue(value);
27046
+ } catch (error) {
27047
+ completeExchange();
27048
+ controller.error(error);
27049
+ }
27050
+ },
27051
+ cancel: (reason) => {
27052
+ completeExchange();
27053
+ return reader.cancel(reason).catch(() => {});
27054
+ }
27055
+ });
27056
+ return new Response(monitoredBody, {
27057
+ status: response.status,
27058
+ statusText: response.statusText,
27059
+ headers: response.headers
27060
+ });
27061
+ } catch (error) {
27062
+ try {
27063
+ onerror?.(toError$1(error));
27064
+ } catch {}
27065
+ return internalServerErrorResponse(echoableRequestId(options?.parsedBody));
27066
+ }
27067
+ };
27068
+ }
27069
+ /**
27070
+ * Read the SEP-2243 standard request headers off the inbound request.
27071
+ *
27072
+ * Both halves of the standard-header story need them — the body-primary
27073
+ * classifier for its cross-check cells, and
27074
+ * {@linkcode validateStandardRequestHeaders} for the presence half — and a
27075
+ * header read at one site but not the other is precisely how a required header
27076
+ * goes unenforced: that divergence is what let a modern POST omitting
27077
+ * `MCP-Protocol-Version` be served. Read them here once so a header added to
27078
+ * {@linkcode InboundHttpRequest} reaches both sites together.
27079
+ */
27080
+ function standardHeadersOf(request) {
27081
+ return {
27082
+ protocolVersionHeader: request.headers.get("mcp-protocol-version") ?? void 0,
27083
+ mcpMethodHeader: request.headers.get("mcp-method") ?? void 0,
27084
+ mcpNameHeader: request.headers.get("mcp-name") ?? void 0
27085
+ };
27086
+ }
27087
+ /**
27088
+ * The entry's classification step: read the request body exactly once (unless
27089
+ * a pre-parsed body is supplied) and classify the request with
27090
+ * {@linkcode classifyInboundRequest}. This is the single code path behind both
27091
+ * {@linkcode createMcpHandler}'s routing and the exported
27092
+ * {@linkcode isLegacyRequest} predicate, so the two can never disagree.
27093
+ *
27094
+ * Pass `needsForward: false` when the caller never reads `forwardRequest` —
27095
+ * the body-preserving clone is then skipped and `forwardRequest` is the
27096
+ * (consumed) input request.
27097
+ */
27098
+ async function classifyEntryRequest(request, providedParsedBody, needsForward = true, maxRequestBodySize = DEFAULT_MAX_REQUEST_BODY_SIZE) {
27099
+ const httpMethod = request.method.toUpperCase();
27100
+ let body;
27101
+ let parsedBody = providedParsedBody;
27102
+ let forwardRequest = request;
27103
+ let unparseable = false;
27104
+ if (httpMethod === "POST") {
27105
+ if (parsedBody === void 0) {
27106
+ if (needsForward) forwardRequest = request.clone();
27107
+ let bodyText;
27108
+ try {
27109
+ const read = await readRequestBody(request, maxRequestBodySize);
27110
+ if (read.tooLarge) return { step: "body-too-large" };
27111
+ bodyText = read.text;
27112
+ } catch {
27113
+ return { step: "unreadable-body" };
27114
+ }
27115
+ try {
27116
+ body = bodyText.length === 0 ? void 0 : JSON.parse(bodyText);
27117
+ } catch {
27118
+ unparseable = true;
27119
+ }
27120
+ if (!unparseable && body !== void 0) parsedBody = body;
27121
+ } else body = parsedBody;
27122
+ if (unparseable || body === void 0) return {
27123
+ step: "no-json-body",
27124
+ forwardRequest
27125
+ };
27126
+ }
27127
+ return {
27128
+ step: "classified",
27129
+ outcome: classifyInboundRequest({
27130
+ httpMethod,
27131
+ ...standardHeadersOf(request),
27132
+ ...body !== void 0 && { body }
27133
+ }),
27134
+ body,
27135
+ parsedBody,
27136
+ forwardRequest
27137
+ };
27138
+ }
27139
+ /**
27140
+ * Whether {@linkcode createMcpHandler} would route this request to its legacy
27141
+ * (2025-era) serving rather than the modern (2026-07-28) path.
27142
+ *
27143
+ * Call it with just the request: `await isLegacyRequest(request)`. For a
27144
+ * `POST` the body is read from an internal clone, so the request you pass
27145
+ * stays fully readable for whichever handler you route it to — no second
27146
+ * argument is needed. (In a Node `(req, res)` handler, build that `Request`
27147
+ * with `toWebRequest(req)` from `@modelcontextprotocol/node`; behind a body
27148
+ * parser, which has already drained the Node stream, build it as
27149
+ * `toWebRequest(req, req.body)` so the bytes come from the parsed body —
27150
+ * either way the predicate still takes just the request.) The optional
27151
+ * `parsedBody` is a perf escape hatch for a body you already hold parsed:
27152
+ * pass it and the predicate classifies from the value directly, reading and
27153
+ * cloning nothing. It is needed, not just faster, when the request's own
27154
+ * body was already read — the internal clone is then impossible (cloning a
27155
+ * used body throws a `TypeError`), so such a single-argument call rejects
27156
+ * instead of guessing.
27157
+ *
27158
+ * This is the entry's own classification step exported as a predicate — it
27159
+ * runs exactly the code `createMcpHandler` runs to make the routing decision,
27160
+ * not a re-implementation — so a hand-wired composition that branches on it
27161
+ * can never disagree with the entry. It is classification only: hand-wired
27162
+ * compositions must validate Content-Type themselves (415 for POSTs whose
27163
+ * media type is not `application/json`, via {@linkcode isJsonContentType})
27164
+ * before dispatching either leg — routing the legacy leg into the SDK
27165
+ * transports gets their built-in check, but a custom modern leg has none. Use it to keep an existing legacy
27166
+ * deployment (for example a sessionful streamable HTTP wiring) serving 2025
27167
+ * traffic next to a strict modern endpoint, now that the entry has no
27168
+ * handler-valued `legacy` option:
27169
+ *
27170
+ * ```ts
27171
+ * import { createMcpHandler, isLegacyRequest } from '@modelcontextprotocol/server';
27172
+ *
27173
+ * const modern = createMcpHandler(factory, { legacy: 'reject' });
27174
+ *
27175
+ * export default {
27176
+ * async fetch(request: Request): Promise<Response> {
27177
+ * if (await isLegacyRequest(request)) {
27178
+ * // e.g. an existing sessionful WebStandardStreamableHTTPServerTransport wiring
27179
+ * return myExistingLegacyHandler(request);
27180
+ * }
27181
+ * return modern.fetch(request);
27182
+ * }
27183
+ * };
27184
+ * ```
27185
+ *
27186
+ * Semantics (identical to the entry's routing):
27187
+ *
27188
+ * - Returns `true` only for requests with no per-request `_meta` envelope
27189
+ * claim: claim-less POSTs (including the `initialize` handshake and 2025-era
27190
+ * notification POSTs without a modern protocol-version header), body-less
27191
+ * GET/DELETE session operations, all-legacy JSON-RPC batch arrays, posted
27192
+ * JSON-RPC responses, and POSTs whose body is empty or not valid JSON.
27193
+ * - Returns `false` for everything the modern path answers, including its
27194
+ * validation-ladder rejections: a request carrying the envelope claim (even
27195
+ * one naming a revision the endpoint does not serve — the modern path
27196
+ * answers it with the unsupported-protocol-version error), a malformed
27197
+ * envelope behind a present claim (answered `-32602`), a request whose
27198
+ * `MCP-Protocol-Version` header names a modern revision but that lacks the
27199
+ * envelope (`-32602`), header/body mismatches (`-32020`), and a POST body
27200
+ * over the size limit (413). Consumers
27201
+ * routing on the predicate must send `false` traffic to the modern handler,
27202
+ * never to a legacy handler — the modern path owns those error answers.
27203
+ * - `server/discover` probes sent by negotiating clients always carry the
27204
+ * envelope claim, so they are never legacy; a hand-built claim-less POST to
27205
+ * a method named `server/discover` has no claim and classifies legacy,
27206
+ * exactly as the entry itself routes it.
27207
+ */
27208
+ async function isLegacyRequest(request, parsedBody, options) {
27209
+ const maxRequestBodySize = resolveMaxRequestBodySize(options?.maxRequestBodySize);
27210
+ const classified = await classifyEntryRequest(parsedBody === void 0 && request.method.toUpperCase() === "POST" ? request.clone() : request, parsedBody, false, maxRequestBodySize);
27211
+ return classified.step === "no-json-body" || classified.step === "classified" && classified.outcome.kind === "legacy";
27212
+ }
27213
+ /**
27214
+ * Creates an HTTP handler that serves the 2026-07-28 protocol revision from a
27215
+ * per-request server factory and, by default, falls back to old-school
27216
+ * stateless serving for 2025-era traffic. Pass `legacy: 'reject'` for a
27217
+ * modern-only strict endpoint.
27218
+ *
27219
+ * Mounting: `handler.fetch` is the web-standard face (Cloudflare Workers,
27220
+ * Deno, Bun, Hono's `c.req.raw`); for Express/Fastify/plain `node:http`, wrap
27221
+ * the handler once with `toNodeHandler(handler)` from
27222
+ * `@modelcontextprotocol/node`. When mounting bare on a fetch-native runtime,
27223
+ * put Origin/Host validation in front of the handler — the entry itself is
27224
+ * deliberately validation-free:
27225
+ *
27226
+ * ```ts
27227
+ * import { hostHeaderValidationResponse, originValidationResponse, localhostAllowedHostnames, localhostAllowedOrigins } from '@modelcontextprotocol/server';
27228
+ *
27229
+ * export default {
27230
+ * async fetch(request: Request): Promise<Response> {
27231
+ * const rejected =
27232
+ * hostHeaderValidationResponse(request, localhostAllowedHostnames()) ??
27233
+ * originValidationResponse(request, localhostAllowedOrigins());
27234
+ * return rejected ?? handler.fetch(request);
27235
+ * }
27236
+ * };
27237
+ * ```
27238
+ *
27239
+ * Use ONE factory for both legs: the same tools/resources/prompts definition
27240
+ * backs the modern path and the stateless legacy fallback, so the two eras can
27241
+ * never drift apart. To keep an existing legacy deployment (for example a
27242
+ * sessionful streamable HTTP wiring) serving 2025 traffic instead of the
27243
+ * stateless fallback, route in user land with {@linkcode isLegacyRequest} in
27244
+ * front of a strict handler — see that predicate's documentation for the
27245
+ * pattern. Power users composing transport-neutral routing can also use the
27246
+ * exported building blocks directly: {@linkcode classifyInboundRequest} for
27247
+ * the era decision and `PerRequestHTTPServerTransport` for single-exchange
27248
+ * serving — such compositions must reject POSTs whose Content-Type media type
27249
+ * is not `application/json` (415) before parsing the body, using
27250
+ * {@linkcode isJsonContentType}; neither building block performs this
27251
+ * validation itself.
27252
+ *
27253
+ * The entry performs no token verification: `authInfo` given to `fetch` is
27254
+ * passed through to handlers and the factory as-is and is never derived from
27255
+ * request headers.
27256
+ */
27257
+ function createMcpHandler(factory, options = {}) {
27258
+ const { legacy, onerror, responseMode } = options;
27259
+ const maxRequestBodySize = resolveMaxRequestBodySize(options.maxRequestBodySize);
27260
+ if (typeof legacy === "function") throw new TypeError("The 'legacy' option only accepts 'stateless' or 'reject', not a handler function. To serve 2025-era traffic with your own handler, route in user land with the exported isLegacyRequest(request) predicate in front of a strict (legacy: 'reject') handler.");
27261
+ /** Modern per-request instances with an exchange still in flight (close() tears these down). */
27262
+ const inflight = /* @__PURE__ */ new Set();
27263
+ let closed = false;
27264
+ const reportError = (error) => {
27265
+ try {
27266
+ onerror?.(error);
27267
+ } catch {}
27268
+ };
27269
+ const bus = options.bus ?? new InMemoryServerEventBus(reportError);
27270
+ const notify = createServerNotifier(bus);
27271
+ const listenRouter = createListenRouter({
27272
+ bus,
27273
+ maxSubscriptions: options.maxSubscriptions ?? 1024,
27274
+ keepAliveMs: options.keepAliveMs ?? 15e3,
27275
+ onerror: reportError
27276
+ });
27277
+ if (responseMode === "json") console.warn("responseMode: 'json' drops mid-call notifications. subscriptions/listen streams are always served over SSE regardless; other notifications emitted before a result are dropped.");
27278
+ const legacyHandler = legacy === "reject" ? void 0 : createLegacyStatelessFallback(factory, reportError, options.keepAliveMs, maxRequestBodySize);
27279
+ async function serveModern(route, request, authInfo) {
27280
+ const claimedRevision = route.classification.revision;
27281
+ if (claimedRevision === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedRevision)) {
27282
+ const error = new UnsupportedProtocolVersionError({
27283
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
27284
+ requested: claimedRevision ?? "unknown"
27285
+ });
27286
+ reportError(error);
27287
+ return jsonRpcErrorResponse(400, error.code, error.message, error.data, echoableRequestId(route.message));
27288
+ }
27289
+ const stdHeaderRejection = validateStandardRequestHeaders({
27290
+ httpMethod: request.method,
27291
+ ...standardHeadersOf(request)
27292
+ }, route);
27293
+ if (stdHeaderRejection !== void 0) {
27294
+ reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${stdHeaderRejection.cell}): ${stdHeaderRejection.message}`));
27295
+ return rejectionResponse(stdHeaderRejection, echoableRequestId(route.message));
27296
+ }
27297
+ const meta = route.messageKind === "request" ? requestMetaOf(route.message.params) : void 0;
27298
+ const declaredClientCapabilities = meta?.[CLIENT_CAPABILITIES_META_KEY];
27299
+ if (route.messageKind === "request") {
27300
+ const required = requiredClientCapabilitiesForRequest(route.message.method);
27301
+ if (required !== void 0) {
27302
+ const missing = missingClientCapabilities(required, declaredClientCapabilities);
27303
+ if (missing !== void 0) {
27304
+ const error = new MissingRequiredClientCapabilityError({ requiredCapabilities: missing });
27305
+ reportError(error);
27306
+ return jsonRpcErrorResponse(httpStatusForErrorCode(error.code, "ladder"), error.code, error.message, error.data, route.message.id);
27307
+ }
27308
+ }
27309
+ }
27310
+ const product = await factory({
27311
+ era: "modern",
27312
+ ...authInfo !== void 0 && { authInfo },
27313
+ requestInfo: request
27314
+ });
27315
+ const server = product instanceof McpServer ? product.server : product;
27316
+ assertNotServing(server);
27317
+ if (route.messageKind === "request" && route.message.method === "subscriptions/listen") {
27318
+ const capabilities = server.getCapabilities();
27319
+ const serverInfo = serverIdentityOf(server);
27320
+ product.close().catch(reportError);
27321
+ return listenRouter.serve(route.message, request.signal, capabilities, serverInfo);
27322
+ }
27323
+ if (route.messageKind === "request" && route.message.method === "tools/call" && product instanceof McpServer) {
27324
+ const callParams = route.message.params;
27325
+ const toolName = typeof callParams?.name === "string" ? callParams.name : void 0;
27326
+ const inputSchema = toolName === void 0 ? void 0 : product.toolInputSchemaJson(toolName);
27327
+ if (inputSchema !== void 0) {
27328
+ const scan = scanXMcpHeaderDeclarations(inputSchema);
27329
+ if (scan.valid && scan.declarations.length > 0) {
27330
+ const rejection = validateMcpParamHeaders(scan.declarations, callParams?.arguments, request.headers);
27331
+ if (rejection !== void 0) {
27332
+ product.close().catch(reportError);
27333
+ reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${rejection.cell}): ${rejection.message}`));
27334
+ return rejectionResponse(rejection, route.message.id);
27335
+ }
27336
+ }
27337
+ }
27338
+ }
27339
+ if (route.messageKind === "request" && product instanceof McpServer) try {
27340
+ const challenge = await findScopeChallenge([route.message], authInfo, (context) => product.resolveScopeChallenge(context));
27341
+ if (challenge !== void 0) {
27342
+ product.close().catch(reportError);
27343
+ return createScopeChallengeResponse(challenge, scopeChallengeResourceMetadataUrl(authInfo));
27344
+ }
27345
+ } catch (error) {
27346
+ product.close().catch(reportError);
27347
+ reportError(toError$1(error));
27348
+ return internalServerErrorResponse(route.message.id);
27349
+ }
27350
+ assertNotServing(server);
27351
+ setNegotiatedProtocolVersion(server, claimedRevision);
27352
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
27353
+ if (meta !== void 0) seedClientIdentityFromEnvelope(server, {
27354
+ clientInfo: meta[CLIENT_INFO_META_KEY],
27355
+ clientCapabilities: declaredClientCapabilities
27356
+ });
27357
+ const previousOnClose = server.onclose;
27358
+ inflight.add(server);
27359
+ const onExchangeClose = () => {
27360
+ inflight.delete(server);
27361
+ if (server.onclose === onExchangeClose) server.onclose = previousOnClose;
27362
+ previousOnClose?.();
27363
+ };
27364
+ server.onclose = onExchangeClose;
27365
+ try {
27366
+ const response = await invoke(product, route.message, {
27367
+ classification: route.classification,
27368
+ request,
27369
+ ...authInfo !== void 0 && { authInfo },
27370
+ ...responseMode !== void 0 && { responseMode },
27371
+ ...options.keepAliveMs !== void 0 && { keepAliveMs: options.keepAliveMs }
27372
+ });
27373
+ if (route.messageKind === "notification") queueMicrotask(() => void server.close().catch(() => {}));
27374
+ return response;
27375
+ } catch (error) {
27376
+ if (error instanceof SdkError && error.code === SdkErrorCode.ConnectionClosed) return new Response(null, { status: 499 });
27377
+ await server.close().catch(() => {});
27378
+ inflight.delete(server);
27379
+ reportError(toError$1(error));
27380
+ return internalServerErrorResponse(echoableRequestId(route.message));
27381
+ }
27382
+ }
27383
+ async function serveLegacyRoute(route, forwardRequest, authInfo, parsedBody) {
27384
+ if (legacyHandler !== void 0) return legacyHandler(forwardRequest, {
27385
+ ...authInfo !== void 0 && { authInfo },
27386
+ ...parsedBody !== void 0 && { parsedBody }
27387
+ });
27388
+ const strict = modernOnlyStrictRejection(route, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
27389
+ if (strict === void 0) return new Response(null, { status: 202 });
27390
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only endpoint (${strict.cell}): ${strict.message}`));
27391
+ return rejectionResponse(strict, echoableRequestId(parsedBody));
27392
+ }
27393
+ async function handle(request, requestOptions) {
27394
+ const authInfo = requestOptions?.authInfo;
27395
+ if (request.method.toUpperCase() === "POST" && !isJsonContentType(request.headers.get("content-type"))) {
27396
+ reportError(/* @__PURE__ */ new Error("Unsupported Media Type: Content-Type must be application/json"));
27397
+ return jsonRpcErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
27398
+ }
27399
+ const classified = await classifyEntryRequest(request, requestOptions?.parsedBody, true, maxRequestBodySize);
27400
+ if (classified.step === "unreadable-body") return jsonRpcErrorResponse(400, -32700, "Parse error: the request body could not be read");
27401
+ if (classified.step === "body-too-large") return jsonRpcErrorResponse(413, -32e3, requestBodyTooLargeMessage(maxRequestBodySize));
27402
+ if (classified.step === "no-json-body") {
27403
+ if (legacyHandler !== void 0) return legacyHandler(classified.forwardRequest, { ...authInfo !== void 0 && { authInfo } });
27404
+ return jsonRpcErrorResponse(400, -32700, "Parse error: the request body is not valid JSON");
27405
+ }
27406
+ const { outcome, body, parsedBody, forwardRequest } = classified;
27407
+ try {
27408
+ switch (outcome.kind) {
27409
+ case "reject":
27410
+ reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${outcome.cell}): ${outcome.message}`));
27411
+ return rejectionResponse(outcome, echoableRequestId(body));
27412
+ case "modern": return await serveModern(outcome, request, authInfo);
27413
+ case "legacy": return await serveLegacyRoute(outcome, forwardRequest, authInfo, parsedBody);
27414
+ }
27415
+ } catch (error) {
27416
+ reportError(toError$1(error));
27417
+ return internalServerErrorResponse(echoableRequestId(body));
27418
+ }
27419
+ }
27420
+ const fetchFace = async (request, requestOptions) => {
27421
+ if (closed) throw new Error("This MCP handler has been closed");
27422
+ try {
27423
+ return await handle(request, requestOptions);
27424
+ } catch (error) {
27425
+ reportError(toError$1(error));
27426
+ return internalServerErrorResponse(echoableRequestId(requestOptions?.parsedBody));
27427
+ }
27428
+ };
27429
+ return {
27430
+ fetch: fetchFace,
27431
+ notify,
27432
+ bus,
27433
+ close: async () => {
27434
+ closed = true;
27435
+ listenRouter.closeAll();
27436
+ const closing = [...inflight].map((server) => server.close().catch(() => {}));
27437
+ inflight.clear();
27438
+ await Promise.all(closing);
27439
+ }
27440
+ };
27441
+ }
27442
+ const PREFIX = "v1.";
27443
+ function bytesToBase64Url(bytes) {
27444
+ let bin = "";
27445
+ for (const b of bytes) bin += String.fromCodePoint(b);
27446
+ return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
27447
+ }
27448
+ function constantTimeTagEqual(a, b) {
27449
+ if (a.length !== b.length) return false;
27450
+ let r = 0;
27451
+ for (let i = 0; i < a.length; i++) r |= a.codePointAt(i) ^ b.codePointAt(i);
27452
+ return r === 0;
27453
+ }
27454
+ function base64UrlToBytes(s) {
27455
+ const b64 = s.replaceAll("-", "+").replaceAll("_", "/");
27456
+ const bin = atob(b64);
27457
+ const bytes = new Uint8Array(bin.length);
27458
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.codePointAt(i);
27459
+ return bytes;
27460
+ }
27461
+ /**
27462
+ * Create an opt-in HMAC-SHA256 codec for the multi-round-trip `requestState`
27463
+ * (protocol revision 2026-07-28).
27464
+ *
27465
+ * `requestState` round-trips through the client and is attacker-controlled
27466
+ * input on re-entry. The SDK applies no protection of its own; this helper is
27467
+ * the convenience implementation of the spec's integrity MUST so authors don't
27468
+ * hand-roll HMAC. Wire shape:
27469
+ *
27470
+ * "v1." b64url({"p":<payload>,"exp":<unixSeconds>,"b":<bindTag>?}) "." b64url(mac)
27471
+ *
27472
+ * where `bindTag` is `b64url(HMAC(key, "mcp.requestState.bind:" + bind(ctx))[:16])`
27473
+ * — the binding value is never embedded raw.
27474
+ *
27475
+ * The codec is **signed, not encrypted**: the body is integrity-protected but
27476
+ * the client can base64url-decode it and read the payload (`p`) in clear. Do
27477
+ * not put secrets in the payload; use an AEAD construction if confidentiality
27478
+ * is required. The handler reads its payload back via the typed
27479
+ * `ctx.mcpReq.requestState<T>()` accessor — the seam has already run `verify`
27480
+ * (integrity proven, payload decoded) by the time the handler is entered.
27481
+ *
27482
+ * Verification is fail-closed and constant-time (WebCrypto `subtle.verify` for
27483
+ * the body MAC; a fixed-length XOR-accumulator compare for the bind tag).
27484
+ * See `examples/mrtr/server.ts` for a worked end-to-end example.
27485
+ *
27486
+ * Design comparison (mcp.d `secureRequestState`, the peer SDK's reference
27487
+ * implementation): mcp.d additionally offers an AES-256-GCM encrypted mode and
27488
+ * derives independent cipher / bind-HMAC sub-keys from the operator secret via
27489
+ * HKDF-SHA256, with an auto-generated per-process ephemeral key when none is
27490
+ * supplied. This codec deliberately ships only the signed mode and a single
27491
+ * keyed HMAC (domain-separated by input prefix) — HKDF sub-key derivation and
27492
+ * an encrypted mode are intentionally out of scope for the initial release.
27493
+ */
27494
+ function createRequestStateCodec$1(options) {
27495
+ const subtle = globalThis.crypto?.subtle;
27496
+ if (subtle === void 0) throw new TypeError("createRequestStateCodec requires the Web Crypto API (globalThis.crypto.subtle); see https://ts.sdk.modelcontextprotocol.io/v2/troubleshooting for the Node.js polyfill instructions");
27497
+ const keyBytes = typeof options.key === "string" ? new TextEncoder().encode(options.key) : Uint8Array.from(options.key);
27498
+ if (keyBytes.byteLength < 32) throw new RangeError(`createRequestStateCodec: key must be at least 32 bytes (got ${keyBytes.byteLength})`);
27499
+ const ttlSeconds = options.ttlSeconds ?? 600;
27500
+ if (!Number.isFinite(ttlSeconds)) throw new RangeError("createRequestStateCodec: ttlSeconds must be a finite number");
27501
+ const bind = options.bind;
27502
+ let cryptoKey;
27503
+ const importedKey = () => cryptoKey ??= subtle.importKey("raw", keyBytes, {
27504
+ name: "HMAC",
27505
+ hash: "SHA-256"
27506
+ }, false, ["sign", "verify"]);
27507
+ const utf8 = new TextEncoder();
27508
+ const BIND_LABEL = "mcp.requestState.bind:";
27509
+ const bindTag = async (value) => {
27510
+ return bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(BIND_LABEL + value))).slice(0, 16));
27511
+ };
27512
+ return {
27513
+ async mint(payload, ctx) {
27514
+ const envelope = {
27515
+ p: payload,
27516
+ exp: Math.floor(Date.now() / 1e3) + ttlSeconds
27517
+ };
27518
+ if (bind !== void 0) {
27519
+ if (ctx === void 0) throw new TypeError("createRequestStateCodec: mint() requires ctx when a bind callback is configured");
27520
+ envelope.b = await bindTag(bind(ctx));
27521
+ }
27522
+ const body = bytesToBase64Url(utf8.encode(JSON.stringify(envelope)));
27523
+ return `${PREFIX}${body}.${bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(PREFIX + body))))}`;
27524
+ },
27525
+ async verify(state, ctx) {
27526
+ const dot = state.lastIndexOf(".");
27527
+ if (!state.startsWith(PREFIX) || dot <= 3) throw new Error("malformed");
27528
+ const body = state.slice(3, dot);
27529
+ let macBytes;
27530
+ try {
27531
+ macBytes = base64UrlToBytes(state.slice(dot + 1));
27532
+ } catch {
27533
+ throw new Error("malformed");
27534
+ }
27535
+ if (!await subtle.verify("HMAC", await importedKey(), macBytes, utf8.encode(PREFIX + body))) throw new Error("mac");
27536
+ let envelope;
27537
+ try {
27538
+ envelope = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(base64UrlToBytes(body)));
27539
+ } catch {
27540
+ throw new Error("malformed");
27541
+ }
27542
+ if (typeof envelope.exp !== "number" || envelope.exp < Math.floor(Date.now() / 1e3)) throw new Error("expired");
27543
+ if (bind !== void 0) {
27544
+ const expected = await bindTag(bind(ctx));
27545
+ if (envelope.b === void 0 || !constantTimeTagEqual(envelope.b, expected)) throw new Error("bind");
27546
+ } else if (envelope.b !== void 0) throw new Error("bind");
27547
+ return envelope.p;
27548
+ }
27549
+ };
27550
+ }
27551
+ //#endregion
27552
+ //#region src/constants.ts
27553
+ const positiveIntEnv = (name, fallback) => {
27554
+ const raw = readEnv(name).value;
27555
+ if (raw === void 0 || raw.trim() === "") return fallback;
27556
+ const parsed = Number(raw);
27557
+ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
27558
+ };
27559
+ const CHARACTER_LIMIT = positiveIntEnv("RESPONSE_CHARACTER_LIMIT", 25e3);
27560
+ const ARTICLE_RESOURCES_SCAN_MAX_PAGES = positiveIntEnv("ARTICLE_RESOURCES_SCAN_MAX_PAGES", 20);
27561
+ const DEFAULT_CALLBACK_PORT = 27439;
27562
+ const MAX_ATTACHMENT_BYTES = positiveIntEnv("ATTACHMENT_MAX_BYTES", 5242880);
27563
+ const MAX_EMBEDDED_IMAGE_COUNT = positiveIntEnv("EMBEDDED_IMAGES_MAX", 10);
27564
+ const MESSAGE_CONTENT_BUDGET_BYTES = 10420224;
27565
+ const MAX_RESPONSE_BYTES = Math.min(positiveIntEnv("RESPONSE_MAX_BYTES", MESSAGE_CONTENT_BUDGET_BYTES), MESSAGE_CONTENT_BUDGET_BYTES);
27566
+ const MAX_BASE64_INPUT_CHARS = MESSAGE_CONTENT_BUDGET_BYTES;
27567
+ const MAX_BASE64_INPUT_MB = Number.parseFloat((MAX_BASE64_INPUT_CHARS / 4 * 3 / 1048576).toFixed(2));
27568
+ const MAX_COMMENT_PAGES = positiveIntEnv("COMMENT_MAX_PAGES", 10);
27569
+ const TICKET_FIELD_SCAN_MAX_PAGES = positiveIntEnv("TICKET_FIELD_SCAN_MAX_PAGES", 10);
27570
+ const REORDER_CONFIRM_THRESHOLD = positiveIntEnv("REORDER_CONFIRM_THRESHOLD", 20);
27571
+ const getBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2`;
27572
+ const getHelpCenterBaseUrl = (subdomain) => `https://${subdomain}.zendesk.com/api/v2/help_center`;
27573
+ const getOAuthUrls = (subdomain) => ({
27574
+ authorizeUrl: `https://${subdomain}.zendesk.com/oauth/authorizations/new`,
27575
+ tokenUrl: `https://${subdomain}.zendesk.com/oauth/tokens`
27576
+ });
27577
+ //#endregion
27578
+ //#region src/client/retry.ts
27579
+ const MAX_ATTEMPTS = 3;
27580
+ const BASE_DELAY_MS = 250;
27581
+ const MAX_DELAY_MS = 4e3;
27582
+ const REQUEST_TIMEOUT_MS = 3e4;
27583
+ const TRANSFER_TIMEOUT_MS = 12e4;
27584
+ /**
27585
+ * A deadline for one attempt. The abort surfaces as a `TimeoutError` whose `code`
27586
+ * is numeric, not a syscall string, so it classifies as `unknown`: a GET is
27587
+ * retried, a write is not — the request may have arrived before the deadline.
27588
+ */
27589
+ const deadlineSignal = (timeoutMs) => AbortSignal.timeout(timeoutMs);
27590
+ const MAX_RETRY_AFTER_MS = 5e3;
27591
+ const POLICIES = {
27592
+ GET: {
27593
+ network: "any",
27594
+ serverErrors: true
27595
+ },
27596
+ DELETE: {
27597
+ network: "pre-send",
27598
+ serverErrors: false
27599
+ },
27600
+ POST: {
27601
+ network: "pre-send",
27602
+ serverErrors: false
27603
+ },
27604
+ PUT: {
27605
+ network: "pre-send",
27606
+ serverErrors: false
27607
+ }
27608
+ };
27609
+ const DELAY_SECONDS = /^\d+$/;
27610
+ const HTTP_DATE_START = /^[A-Za-z]/;
27611
+ const NON_ASCII = /[^ -~]/g;
27612
+ const TOKEN_SEGMENT = /\/token\/[^/]+/;
27613
+ const URL_IN_TEXT = /[a-z][a-z0-9+.-]*:\/\/\S+/gi;
27614
+ const PRE_SEND_CODES = /* @__PURE__ */ new Set([
27615
+ "ENOTFOUND",
27616
+ "EAI_AGAIN",
27617
+ "ECONNREFUSED",
27618
+ "UND_ERR_CONNECT_TIMEOUT"
27619
+ ]);
27620
+ const defaultRetryDeps = {
27621
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
27622
+ random: Math.random
27623
+ };
27624
+ /** Non-ASCII bytes break `node:http` headers, so client error text stays ASCII. */
27625
+ const toAscii = (text) => text.replace(NON_ASCII, "?");
27626
+ /**
27627
+ * Identifies the request without leaking a credential: uploads carry an upload
27628
+ * token in the query string, and an attachment `content_url` carries a download
27629
+ * token as a path segment (`/attachments/token/<token>/`).
27630
+ */
27631
+ const describeTarget = (url) => {
27632
+ const { origin, pathname } = new URL(url);
27633
+ return `${origin}${pathname.replace(TOKEN_SEGMENT, "/token/[redacted]")}`;
27634
+ };
27635
+ /** First `code` in the cause chain, inspecting 5 levels so a cycle cannot hang. */
27636
+ const errorCode = (err) => {
27637
+ let current = err;
27638
+ for (let depth = 0; depth < 5 && current !== null && typeof current === "object"; depth += 1) {
27639
+ const { code, cause } = current;
27640
+ if (typeof code === "string") return code;
27641
+ current = cause;
27642
+ }
27643
+ };
27644
+ const classifyNetworkError = (err) => PRE_SEND_CODES.has(errorCode(err)) ? "pre-send" : "unknown";
27645
+ const UNINFORMATIVE_NAMES = /* @__PURE__ */ new Set(["Error", "TypeError"]);
27646
+ /**
27647
+ * The client refuses to replay a write that may have landed — but the caller is
27648
+ * an LLM whose reflex on a failed tool call is to try again, which would undo
27649
+ * that. So a failed write says whether it may have taken effect. ASCII only,
27650
+ * same rule as the rest of the message.
27651
+ */
27652
+ const MAY_HAVE_APPLIED_NOTE = "The write may already have been applied. Check the current state before retrying, or you may duplicate it.";
27653
+ const NEVER_SENT_NOTE = "The request never reached Zendesk, so nothing was applied. Retrying is safe.";
27654
+ const REFUSED_NOTE = "Zendesk refused the request, so nothing was applied. Retrying is safe.";
27655
+ /** Only a write can be duplicated by a replay, so only a write carries a note. */
27656
+ const writeNote = (method, note) => method === "GET" ? "" : ` ${note}`;
27657
+ /**
27658
+ * A cause message can quote the URL it failed on — Node's `fetch` refuses a URL
27659
+ * carrying credentials by printing the whole thing, query string included — which
27660
+ * would smuggle back exactly what `describeTarget` drops. Same treatment for both,
27661
+ * so one function owns what a URL may look like in our output.
27662
+ */
27663
+ const redactUrls = (text) => text.replace(URL_IN_TEXT, (found) => {
27664
+ try {
27665
+ return new URL(found).origin === "null" ? "[url]" : describeTarget(found);
27666
+ } catch {
27667
+ return "[url]";
27668
+ }
27669
+ });
27670
+ const failureDetail = (cause) => {
27671
+ if (!(cause instanceof Error)) return redactUrls(String(cause));
27672
+ const message = redactUrls(cause.message);
27673
+ const code = errorCode(cause);
27674
+ if (code !== void 0) return `${code}: ${message}`;
27675
+ return UNINFORMATIVE_NAMES.has(cause.name) ? message : `${cause.name}: ${message}`;
27676
+ };
27677
+ const networkErrorMessage = (method, target, attempts, cause) => {
27678
+ const base = `Network error on ${method} ${target} after ${attempts === 1 ? "1 attempt" : `${attempts} attempts`}: ${failureDetail(cause)}`;
27679
+ const note = classifyNetworkError(cause) === "pre-send" ? NEVER_SENT_NOTE : MAY_HAVE_APPLIED_NOTE;
27680
+ return toAscii(method === "GET" ? base : `${base}.${writeNote(method, note)}`);
27681
+ };
27682
+ const createZendeskNetworkError = (method, target, attempts, cause) => Object.assign(new Error(networkErrorMessage(method, target, attempts, cause), { cause }), {
27683
+ name: "ZendeskNetworkError",
27684
+ method,
27685
+ target,
27686
+ attempts
27687
+ });
27688
+ /** Exponential backoff with equal jitter: half the window fixed, half random. */
27689
+ const computeBackoffMs = (attempt, random) => {
27690
+ const window = Math.min(BASE_DELAY_MS * 2 ** (attempt - 1), MAX_DELAY_MS);
27691
+ return Math.round(window / 2 + window * random() / 2);
27692
+ };
27693
+ /** `Retry-After` in ms — delay-seconds or HTTP-date. */
27694
+ const parseRetryAfter = (header, now = Date.now()) => {
27695
+ if (header === null) return void 0;
27696
+ const value = header.trim();
27697
+ if (DELAY_SECONDS.test(value)) return Number(value) * 1e3;
27698
+ if (!HTTP_DATE_START.test(value)) return void 0;
27699
+ const date = Date.parse(value);
27700
+ if (Number.isNaN(date)) return void 0;
27701
+ return Math.max(0, date - now);
27702
+ };
27703
+ /**
27704
+ * How long to wait before replaying this response, or undefined to accept it.
27705
+ * `Retry-After` wins over backoff wherever it appears — Zendesk sends it on a 503
27706
+ * during maintenance as well as on a 429 — and a value past the cap means the
27707
+ * response is surfaced rather than parking the call for that long.
27708
+ */
27709
+ const retryDelayFor = (response, policy, attempt, random) => {
27710
+ if (!(response.status === 429 || response.status >= 500 && policy.serverErrors)) return void 0;
27711
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
27712
+ if (retryAfter === void 0) return computeBackoffMs(attempt, random);
27713
+ return retryAfter > MAX_RETRY_AFTER_MS ? void 0 : retryAfter;
27714
+ };
27715
+ /**
27716
+ * Runs `attempt` until it succeeds, hits a terminal outcome, or spends the
27717
+ * attempt budget. Returns the last response for the caller to inspect (a
27718
+ * non-ok status is still the caller's to turn into a `ZendeskApiError`), and
27719
+ * throws `ZendeskNetworkError` when no response was ever obtained.
27720
+ */
27721
+ const fetchWithRetry = async (attempt, method, target, deps = defaultRetryDeps) => {
27722
+ const policy = POLICIES[method];
27723
+ for (let tries = 1;; tries += 1) {
27724
+ const last = tries >= MAX_ATTEMPTS;
27725
+ let response;
27726
+ try {
27727
+ response = await attempt();
27728
+ } catch (err) {
27729
+ const replayable = policy.network === "any" || classifyNetworkError(err) === policy.network;
27730
+ if (last || !replayable) throw createZendeskNetworkError(method, target, tries, err);
27731
+ await deps.sleep(computeBackoffMs(tries, deps.random));
27732
+ continue;
27733
+ }
27734
+ if (last) return response;
27735
+ const delayMs = retryDelayFor(response, policy, tries, deps.random);
27736
+ if (delayMs === void 0) return response;
27737
+ await response.text().catch(() => void 0);
27738
+ await deps.sleep(delayMs);
27739
+ }
27740
+ };
27741
+ //#endregion
27742
+ //#region src/client/zendesk-api.ts
27743
+ var ZendeskApiError = class ZendeskApiError extends Error {
27744
+ status;
27745
+ statusText;
27746
+ body;
27747
+ method;
27748
+ constructor(status, statusText, body, method) {
27749
+ super(ZendeskApiError.buildMessage(status, statusText, body, method));
27750
+ this.status = status;
27751
+ this.statusText = statusText;
27752
+ this.body = body;
27753
+ this.method = method;
27754
+ this.name = "ZendeskApiError";
27755
+ }
27756
+ static buildMessage(status, statusText, body, method) {
27757
+ switch (status) {
27758
+ case 401: return "Authentication failed. Your Zendesk token may be expired or invalid. Re-authenticate to get a new token.";
27759
+ case 403: return "Permission denied. Your Zendesk account does not have access to this resource.";
27760
+ case 404: return `Resource not found. Please verify the ID is correct. (${statusText})`;
27761
+ case 422: return `Validation error: ${body}`;
27762
+ case 429: return `Rate limit exceeded. Please wait before making more requests.${writeNote(method, REFUSED_NOTE)}`;
27763
+ default: {
27764
+ const message = `Zendesk API error ${status}: ${statusText}. ${body}`;
27765
+ return status >= 500 ? `${message}${writeNote(method, MAY_HAVE_APPLIED_NOTE)}` : message;
27766
+ }
27767
+ }
27768
+ }
27769
+ };
27770
+ const buildAuthHeader = (token) => `Bearer ${token}`;
27771
+ const buildUrl = (base, path, params) => {
27772
+ const url = new URL(`${base}${path}`);
27773
+ if (params) for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
27774
+ return url.toString();
27775
+ };
27776
+ const performFetch = (method, url, init, timeoutMs = REQUEST_TIMEOUT_MS) => fetchWithRetry(() => fetch(url, {
27777
+ ...init,
27778
+ method,
27779
+ signal: deadlineSignal(timeoutMs)
27780
+ }), method, describeTarget(url));
27781
+ const executeRequest = async (url, token, options = {}) => {
27782
+ const { method = "GET", body } = options;
27783
+ const headers = {
27784
+ Authorization: buildAuthHeader(token),
27785
+ Accept: "application/json"
27786
+ };
27787
+ if (body) headers["Content-Type"] = "application/json";
27788
+ const init = { headers };
27789
+ if (body) init.body = JSON.stringify(body);
27790
+ const response = await performFetch(method, url, init);
27791
+ if (!response.ok) {
27792
+ const responseBody = await response.text();
27793
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, method);
27794
+ }
27795
+ if (response.status === 204) return {};
27796
+ return response.json();
27797
+ };
27798
+ const zendeskGet = (subdomain, token, path, params) => {
27799
+ const url = buildUrl(getBaseUrl(subdomain), path, params);
27800
+ return executeRequest(url, token);
27801
+ };
27802
+ const zendeskPost = (subdomain, token, path, body) => {
27803
+ const url = buildUrl(getBaseUrl(subdomain), path);
27804
+ return executeRequest(url, token, {
27805
+ method: "POST",
27806
+ body
27807
+ });
27808
+ };
27809
+ const zendeskPut = (subdomain, token, path, body) => {
27810
+ const url = buildUrl(getBaseUrl(subdomain), path);
27811
+ return executeRequest(url, token, {
27812
+ method: "PUT",
27813
+ body
27814
+ });
27815
+ };
27816
+ const helpCenterGet = (subdomain, token, path, params) => {
27817
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path, params);
27818
+ return executeRequest(url, token);
27819
+ };
27820
+ const helpCenterPost = (subdomain, token, path, body) => {
27821
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
27822
+ return executeRequest(url, token, {
27823
+ method: "POST",
27824
+ body
27825
+ });
27826
+ };
27827
+ const helpCenterPut = (subdomain, token, path, body) => {
27828
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
27829
+ return executeRequest(url, token, {
27830
+ method: "PUT",
27831
+ body
27832
+ });
27833
+ };
27834
+ const helpCenterDelete = (subdomain, token, path) => {
27835
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
27836
+ return executeRequest(url, token, { method: "DELETE" });
27837
+ };
27838
+ const fetchZendeskBinary = async (subdomain, token, contentUrl) => {
27839
+ const expectedHost = `${subdomain}.zendesk.com`;
27840
+ const headers = {};
27841
+ if (new URL(contentUrl).hostname === expectedHost) headers["Authorization"] = buildAuthHeader(token);
27842
+ const response = await performFetch("GET", contentUrl, { headers }, TRANSFER_TIMEOUT_MS);
27843
+ if (!response.ok) {
27844
+ const body = await response.text();
27845
+ throw new ZendeskApiError(response.status, response.statusText, body, "GET");
27846
+ }
27847
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
27848
+ const arrayBuffer = await response.arrayBuffer();
27849
+ return {
27850
+ data: Buffer.from(arrayBuffer),
27851
+ contentType
27852
+ };
27853
+ };
27854
+ const zendeskUpload = async (subdomain, token, filename, data, contentType, uploadToken) => {
27855
+ const params = { filename };
27856
+ if (uploadToken) params["token"] = uploadToken;
27857
+ const url = buildUrl(getBaseUrl(subdomain), "/uploads", params);
27858
+ const response = await performFetch("POST", url, {
27859
+ headers: {
27860
+ Authorization: buildAuthHeader(token),
27861
+ "Content-Type": contentType
27862
+ },
27863
+ body: data
27864
+ }, TRANSFER_TIMEOUT_MS);
27865
+ if (!response.ok) {
27866
+ const responseBody = await response.text();
27867
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
27868
+ }
27869
+ return response.json();
27870
+ };
27871
+ const helpCenterUpload = async (subdomain, token, path, formData) => {
27872
+ const url = buildUrl(getHelpCenterBaseUrl(subdomain), path);
27873
+ const response = await performFetch("POST", url, {
27874
+ headers: { Authorization: buildAuthHeader(token) },
27875
+ body: formData
27876
+ }, TRANSFER_TIMEOUT_MS);
27877
+ if (!response.ok) {
27878
+ const responseBody = await response.text();
27879
+ throw new ZendeskApiError(response.status, response.statusText, responseBody, "POST");
27880
+ }
27881
+ return response.json();
27882
+ };
27883
+ //#endregion
27884
+ //#region src/client/brands.ts
27885
+ const BRAND_SUBDOMAIN = /^[a-z0-9][a-z0-9-]*$/;
27886
+ /**
27887
+ * Fetch every brand of the account (cursor-paginated). `page[size]` is sent on
27888
+ * the FIRST request too: without it Zendesk falls back to OFFSET pagination,
27889
+ * which answers without `meta.after_cursor`, and the loop below would stop
27890
+ * after page 1 — a brand past the first 100 would resolve as Unknown.
27891
+ */
27892
+ const fetchAllBrands = async (subdomain, token) => {
27893
+ const brands = [];
27894
+ let cursor;
27895
+ do {
27896
+ const response = await zendeskGet(subdomain, token, "/brands", {
27897
+ "page[size]": String(100),
27898
+ ...cursor ? { "page[after]": cursor } : {}
27899
+ });
27900
+ brands.push(...response.brands ?? []);
27901
+ const meta = response.meta;
27902
+ if (meta?.has_more) {
27903
+ if (!meta.after_cursor || meta.after_cursor === cursor) throw new Error("GET /brands reported more pages but returned no usable after_cursor (missing or identical to the cursor just sent). Cannot continue pagination safely.");
27904
+ cursor = meta.after_cursor;
27905
+ } else cursor = void 0;
27906
+ } while (cursor);
27907
+ return brands;
27908
+ };
27909
+ const createBrandSubdomainResolver = (subdomain, getToken) => {
27910
+ let cached;
27911
+ const brands = () => {
27912
+ if (!cached || Date.now() - cached.at >= 3e5) {
27913
+ const promise = (async () => fetchAllBrands(subdomain, await getToken()))();
27914
+ cached = {
27915
+ at: Date.now(),
27916
+ promise
27917
+ };
27918
+ promise.catch(() => {
27919
+ cached = void 0;
27920
+ });
27921
+ }
27922
+ return cached.promise;
27923
+ };
27924
+ return async (idOrSubdomain) => {
27925
+ const all = await brands();
27926
+ const brand = all.find((b) => String(b.id) === idOrSubdomain) ?? all.find((b) => b.subdomain.toLowerCase() === idOrSubdomain.toLowerCase());
27927
+ if (!brand) throw new Error(`Unknown brand "${idOrSubdomain}": no brand with that id or subdomain exists on this account; call list_brands to see the available brands.`);
27928
+ if (!BRAND_SUBDOMAIN.test(brand.subdomain)) throw new Error(`Brand ${brand.id} has an unexpected subdomain; refusing to send a request to it.`);
27929
+ return brand.subdomain;
27930
+ };
27931
+ };
25937
27932
  //#endregion
25938
27933
  //#region node_modules/.pnpm/cheerio@1.2.0/node_modules/cheerio/dist/esm/options.js
25939
27934
  const defaultOpts$2 = { _useHtmlParser2: false };
@@ -103710,7 +105705,6 @@ const countWords = (text) => {
103710
105705
  return trimmed.split(WHITESPACE_RUN).length;
103711
105706
  };
103712
105707
  const textOf = (html) => {
103713
- if (!html) return "";
103714
105708
  return load(`<div>${html}</div>`, null, false)("div").first().text();
103715
105709
  };
103716
105710
  const parseSections = (html) => {
@@ -103826,14 +105820,8 @@ const htmlToMdProcessor = unified().use(rehypeParse, { fragment: true }).use(reh
103826
105820
  emphasis: "_"
103827
105821
  });
103828
105822
  const mdToHtmlProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeStringify);
103829
- const htmlToMarkdown = (html) => {
103830
- if (!html) return "";
103831
- return String(htmlToMdProcessor.processSync(html));
103832
- };
103833
- const markdownToHtml = (markdown) => {
103834
- if (!markdown) return "";
103835
- return String(mdToHtmlProcessor.processSync(markdown));
103836
- };
105823
+ const htmlToMarkdown = (html) => String(htmlToMdProcessor.processSync(html));
105824
+ const markdownToHtml = (markdown) => String(mdToHtmlProcessor.processSync(markdown));
103837
105825
  //#endregion
103838
105826
  //#region src/utils/formatting.ts
103839
105827
  const truncateIfNeeded = (text, advice = "Use pagination or filters to reduce results.") => {
@@ -107808,12 +109796,6 @@ const SEVERITY = {
107808
109796
  warn: 2,
107809
109797
  error: 3
107810
109798
  };
107811
- const MCP_LEVEL = {
107812
- debug: "debug",
107813
- info: "info",
107814
- warn: "warning",
107815
- error: "error"
107816
- };
107817
109799
  const REDACTED_KEYS = /* @__PURE__ */ new Set([
107818
109800
  "token",
107819
109801
  "accesstoken",
@@ -107862,37 +109844,21 @@ const silentLogger = {
107862
109844
  debug: noop,
107863
109845
  info: noop,
107864
109846
  warn: noop,
107865
- error: noop,
107866
- attachServer: noop
109847
+ error: noop
107867
109848
  };
107868
109849
  const createLogger = (level) => {
107869
109850
  const min = SEVERITY[level];
107870
- let server;
107871
109851
  const emit = (lvl, event, fields) => {
107872
109852
  if (SEVERITY[lvl] < min) return;
107873
- const safe = sanitise(fields);
107874
109853
  try {
107875
- console.error(formatLine(lvl, event, safe));
107876
- } catch {}
107877
- if (server) try {
107878
- server.sendLoggingMessage({
107879
- level: MCP_LEVEL[lvl],
107880
- logger: "zendesk-mcp-server",
107881
- data: {
107882
- ...safe,
107883
- event
107884
- }
107885
- }).catch(noop);
109854
+ console.error(formatLine(lvl, event, sanitise(fields)));
107886
109855
  } catch {}
107887
109856
  };
107888
109857
  return {
107889
109858
  debug: (event, fields) => emit("debug", event, fields),
107890
109859
  info: (event, fields) => emit("info", event, fields),
107891
109860
  warn: (event, fields) => emit("warn", event, fields),
107892
- error: (event, fields) => emit("error", event, fields),
107893
- attachServer: (s) => {
107894
- server = s;
107895
- }
109861
+ error: (event, fields) => emit("error", event, fields)
107896
109862
  };
107897
109863
  };
107898
109864
  //#endregion
@@ -107915,16 +109881,13 @@ const readPackageInfo = () => {
107915
109881
  let dir = dirname(fileURLToPath(import.meta.url));
107916
109882
  for (let depth = 0; depth < 8; depth++) {
107917
109883
  try {
107918
- const raw = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
107919
- if (raw && typeof raw === "object") {
107920
- const pkg = raw;
107921
- if (typeof pkg.name === "string" && typeof pkg.version === "string") {
107922
- cached = {
107923
- name: pkg.name,
107924
- version: pkg.version
107925
- };
107926
- return cached;
107927
- }
109884
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
109885
+ if (typeof pkg?.name === "string" && typeof pkg.version === "string") {
109886
+ cached = {
109887
+ name: pkg.name,
109888
+ version: pkg.version
109889
+ };
109890
+ return cached;
107928
109891
  }
107929
109892
  } catch {}
107930
109893
  const parent = dirname(dir);
@@ -107935,6 +109898,19 @@ const readPackageInfo = () => {
107935
109898
  return cached;
107936
109899
  };
107937
109900
  //#endregion
109901
+ //#region src/utils/request-state.ts
109902
+ /**
109903
+ * `requestState` round-trips through the client, so it comes back as
109904
+ * attacker-controlled input: the spec requires integrity protection and binding
109905
+ * to the user. Every server verifies it with this codec (`requestState.verify`)
109906
+ * and handlers mint state only through it. Bound to the method and the
109907
+ * principal, so state minted for one grant or one method is refused elsewhere.
109908
+ */
109909
+ const createRequestStateCodec = ({ key = randomBytes(32), principal = () => "" }) => createRequestStateCodec$1({
109910
+ key,
109911
+ bind: (ctx) => `${ctx.mcpReq.method}\0${principal()}`
109912
+ });
109913
+ //#endregion
107938
109914
  //#region src/server.ts
107939
109915
  /**
107940
109916
  * Invoke a tool handler, notifying `onUnauthorized` when Zendesk rejects the
@@ -107997,24 +109973,29 @@ const registerProxyTool = (server, toolName, title, tools, readOnlyMode, onUnaut
107997
109973
  }, async (args) => dispatch(args));
107998
109974
  };
107999
109975
  /**
108000
- * Build the bare `McpServer` — identity, capabilities, `instructions` and the
108001
- * logging sink — with no tools or resources registered yet. Split out from
108002
- * `createMcpServer` so the dev reload (`dev/reload.ts`) can keep this
108003
- * long-lived shell (and its transport/session) alive while swapping the toolset
108004
- * underneath it via `registerToolset`.
109976
+ * Build the bare `McpServer` — identity, `instructions` — with no tools or
109977
+ * resources registered yet. Split out from `createMcpServer` so the dev reload
109978
+ * (`dev/reload.ts`) can keep this long-lived shell (and its transport/session)
109979
+ * alive while swapping the toolset underneath it via `registerToolset`.
108005
109980
  */
108006
- const createServerShell = (config, logger = silentLogger) => {
109981
+ const createServerShell = (config, requestStateCodec = createRequestStateCodec({})) => {
108007
109982
  const pkg = readPackageInfo();
108008
109983
  const instructions = buildInstructions(config);
108009
- const server = new McpServer({
109984
+ return new McpServer({
108010
109985
  name: pkg.name,
108011
109986
  version: pkg.version
108012
109987
  }, {
108013
- capabilities: { logging: {} },
108014
- ...instructions ? { instructions } : {}
109988
+ ...instructions ? { instructions } : {},
109989
+ requestState: { verify: (state, ctx) => requestStateCodec.verify(state, ctx) }
108015
109990
  });
108016
- logger.attachServer(server);
108017
- return server;
109991
+ };
109992
+ const createServerCaches = (config, getToken, onUnauthorized) => {
109993
+ const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
109994
+ return {
109995
+ resolveBrandSubdomain,
109996
+ topology: helpCenterContextEnabled(config) ? createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, resolveBrandSubdomain)) : void 0,
109997
+ articles: articleResourceEnabled(config) ? createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, resolveBrandSubdomain), config.brandIds) : void 0
109998
+ };
108018
109999
  };
108019
110000
  /**
108020
110001
  * Lazily resolve the Help Center subdomain the topology resource pins to: the
@@ -108051,8 +110032,8 @@ const resourceBrandResolver = (config, resolveBrandSubdomain) => {
108051
110032
  * without dropping the transport. `tools` is passed in (not built here) so the
108052
110033
  * reload path can hand over freshly re-imported definitions.
108053
110034
  */
108054
- const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUnauthorized, logger = silentLogger }, tools) => {
108055
- const sharedResolver = resolveBrandSubdomain ?? createBrandSubdomainResolver(config.subdomain, getToken);
110035
+ const registerToolset = (server, { config, getToken, caches, onUnauthorized, logger = silentLogger }, tools) => {
110036
+ const { topology, articles } = caches ?? createServerCaches(config, getToken, onUnauthorized);
108056
110037
  const registered = [];
108057
110038
  const dispose = () => {
108058
110039
  for (const handle of registered) handle.remove();
@@ -108088,20 +110069,16 @@ const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUn
108088
110069
  throw new Error(`Unsupported tool mode: ${String(unhandled)}`);
108089
110070
  }
108090
110071
  }
108091
- if (helpCenterContextEnabled(config)) {
108092
- const topology = createTopologyProvider(getToken, config.subdomain, onUnauthorized, config.brandIds, firstBrandSubdomainResolver(config, sharedResolver));
108093
- registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
108094
- title: "Zendesk Help Center topology",
108095
- description: "Active locales, category → section tree, visibility segments, permission groups, and your role. Useful context when creating or editing content; admin-only sections (permission groups, user segments) are marked unavailable rather than empty when your role lacks Guide-admin rights.",
108096
- mimeType: "text/markdown"
108097
- }, async (uri) => ({ contents: [{
108098
- uri: uri.toString(),
108099
- mimeType: "text/markdown",
108100
- text: await topology.read()
108101
- }] })));
108102
- }
108103
- if (articleResourceEnabled(config)) {
108104
- const articles = createArticleResourcesProvider(getToken, config.subdomain, onUnauthorized, resourceBrandResolver(config, sharedResolver), config.brandIds);
110072
+ if (topology) registered.push(server.registerResource("help-center-topology", topologyResourceUri(config), {
110073
+ title: "Zendesk Help Center topology",
110074
+ description: "Active locales, category → section tree, visibility segments, permission groups, and your role. Useful context when creating or editing content; admin-only sections (permission groups, user segments) are marked unavailable rather than empty when your role lacks Guide-admin rights.",
110075
+ mimeType: "text/markdown"
110076
+ }, async (uri) => ({ contents: [{
110077
+ uri: uri.toString(),
110078
+ mimeType: "text/markdown",
110079
+ text: await topology.read()
110080
+ }] })));
110081
+ if (articles) {
108105
110082
  const listPromotedEnabled = promotedArticlesEnabled(config);
108106
110083
  const template = new ResourceTemplate(articleResourceUriTemplate(config), { list: async () => {
108107
110084
  if (!listPromotedEnabled) return { resources: [] };
@@ -108144,7 +110121,7 @@ const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUn
108144
110121
  dispose();
108145
110122
  throw err;
108146
110123
  }
108147
- logger.info("tools_registered", {
110124
+ logger.debug("tools_registered", {
108148
110125
  count: filteredTools.length,
108149
110126
  mode: config.mode
108150
110127
  });
@@ -108153,24 +110130,27 @@ const registerToolset = (server, { config, getToken, resolveBrandSubdomain, onUn
108153
110130
  dispose
108154
110131
  };
108155
110132
  };
108156
- const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
108157
- const server = createServerShell(config, logger);
108158
- const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
110133
+ const createMcpServer = (config, getToken, logger = silentLogger, onUnauthorized, { caches = createServerCaches(config, getToken, onUnauthorized), requestStateCodec } = {}) => {
110134
+ const server = createServerShell(config, requestStateCodec);
108159
110135
  const tools = createAllTools({
108160
110136
  subdomain: config.subdomain,
108161
110137
  brandIds: config.brandIds,
108162
- resolveBrandSubdomain,
110138
+ resolveBrandSubdomain: caches.resolveBrandSubdomain,
108163
110139
  getToken
108164
110140
  });
108165
110141
  registerToolset(server, {
108166
110142
  config,
108167
110143
  getToken,
108168
- resolveBrandSubdomain,
110144
+ caches,
108169
110145
  onUnauthorized,
108170
110146
  logger
108171
110147
  }, tools);
108172
110148
  return server;
108173
110149
  };
110150
+ const defaultServerFactory = ({ config, getToken, logger, onUnauthorized, caches, requestStateCodec }) => createMcpServer(config, getToken, logger, onUnauthorized, {
110151
+ ...caches ? { caches } : {},
110152
+ requestStateCodec
110153
+ });
108174
110154
  //#endregion
108175
110155
  //#region node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/stdio.mjs
108176
110156
  /**
@@ -108296,12 +110276,485 @@ var StdioServerTransport = class {
108296
110276
  });
108297
110277
  }
108298
110278
  };
110279
+ /**
110280
+ * How long the probe-discard path waits for the probe instance to answer the
110281
+ * requests it was delivered before closing it. The wait normally settles as
110282
+ * soon as the DiscoverResult is handed to the wire (or immediately, when a
110283
+ * delivered cancellation already settled the probe); the bound is a backstop
110284
+ * so no edge can ever hold the connection's inbound pump indefinitely behind
110285
+ * the discard.
110286
+ */
110287
+ const DISCARD_ANSWER_TIMEOUT_MS = 3e3;
110288
+ /**
110289
+ * The transport a pinned instance is connected to: a thin channel that writes
110290
+ * through to the entry-owned wire transport and receives the messages the
110291
+ * entry forwards. The wire transport itself is never handed to an instance —
110292
+ * that is what lets the entry discard an optimistic probe instance (close the
110293
+ * channel) without tearing down the connection.
110294
+ */
110295
+ var StdioConnectionChannel = class {
110296
+ onclose;
110297
+ onerror;
110298
+ onmessage;
110299
+ _closed = false;
110300
+ /** Request ids the entry delivered to the instance that the instance has not yet answered. */
110301
+ _pendingRequests = /* @__PURE__ */ new Set();
110302
+ _drainWaiters = [];
110303
+ constructor(_wire, _onInstanceClose, _outboundIntercept) {
110304
+ this._wire = _wire;
110305
+ this._onInstanceClose = _onInstanceClose;
110306
+ this._outboundIntercept = _outboundIntercept;
110307
+ }
110308
+ async start() {}
110309
+ async send(message, options) {
110310
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
110311
+ const { id } = message;
110312
+ if (id !== void 0) this._settle(id);
110313
+ }
110314
+ if (this._closed) return;
110315
+ if (this._outboundIntercept?.(message) === "handled") return;
110316
+ return this._wire.send(message, options);
110317
+ }
110318
+ setProtocolVersion = (version) => {
110319
+ this._wire.setProtocolVersion?.(version);
110320
+ };
110321
+ /** Forwards one inbound message to the connected instance. */
110322
+ deliver(message, extra) {
110323
+ if (this._closed) return;
110324
+ if (isJSONRPCRequest(message)) this._pendingRequests.add(message.id);
110325
+ else if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
110326
+ const cancelledId = message.params?.requestId;
110327
+ if (cancelledId !== void 0) this._settle(cancelledId);
110328
+ }
110329
+ this.onmessage?.(message, extra);
110330
+ }
110331
+ /**
110332
+ * Resolves once every request delivered to the instance has been answered
110333
+ * through {@linkcode send}, settled by a delivered cancellation, or the
110334
+ * channel has been closed and nothing further can be answered. The wait is
110335
+ * bounded by `timeoutMs` as a backstop so no edge can hold the caller
110336
+ * indefinitely; resolves `false` only when the bound elapsed with requests
110337
+ * still unanswered. Used by the probe-discard path so a probe request the
110338
+ * entry accepted is never silently dropped.
110339
+ */
110340
+ async whenRequestsAnswered(timeoutMs) {
110341
+ if (this._closed || this._pendingRequests.size === 0) return true;
110342
+ return await new Promise((resolve) => {
110343
+ const waiter = () => {
110344
+ clearTimeout(timer);
110345
+ resolve(true);
110346
+ };
110347
+ const timer = setTimeout(() => {
110348
+ this._drainWaiters = this._drainWaiters.filter((pending) => pending !== waiter);
110349
+ resolve(false);
110350
+ }, timeoutMs);
110351
+ this._drainWaiters.push(waiter);
110352
+ });
110353
+ }
110354
+ async close() {
110355
+ if (this._closed) return;
110356
+ this._closed = true;
110357
+ this._pendingRequests.clear();
110358
+ this._releaseDrainWaiters();
110359
+ try {
110360
+ this._onInstanceClose();
110361
+ } finally {
110362
+ this.onclose?.();
110363
+ }
110364
+ }
110365
+ _settle(id) {
110366
+ this._pendingRequests.delete(id);
110367
+ if (this._pendingRequests.size === 0) this._releaseDrainWaiters();
110368
+ }
110369
+ _releaseDrainWaiters() {
110370
+ const waiters = this._drainWaiters;
110371
+ this._drainWaiters = [];
110372
+ for (const waiter of waiters) waiter();
110373
+ }
110374
+ };
110375
+ /**
110376
+ * Classifies one message of the opening exchange with the same body-primary
110377
+ * rules the HTTP entry applies per request: `initialize` is the legacy
110378
+ * handshake unless it carries a valid modern envelope claim; a present claim
110379
+ * is validated (never silently ignored); a claim-less message is 2025-era
110380
+ * traffic. There is no header layer on stdio, so the body is the only signal.
110381
+ */
110382
+ function classifyOpeningMessage(message) {
110383
+ const params = message.params;
110384
+ if (message.method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
110385
+ const requestedVersion = params !== null && typeof params === "object" && typeof params.protocolVersion === "string" ? params.protocolVersion : void 0;
110386
+ return {
110387
+ kind: "legacy",
110388
+ reason: "initialize",
110389
+ ...requestedVersion !== void 0 && { requestedVersion }
110390
+ };
110391
+ }
110392
+ if (!hasEnvelopeClaim(params)) return {
110393
+ kind: "legacy",
110394
+ reason: "no-claim"
110395
+ };
110396
+ const meta = requestMetaOf(params);
110397
+ const firstIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0];
110398
+ if (firstIssue !== void 0) return {
110399
+ kind: "invalid-envelope",
110400
+ issue: firstIssue
110401
+ };
110402
+ const claimedVersion = envelopeClaimVersion(params);
110403
+ if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) return {
110404
+ kind: "unsupported-revision",
110405
+ requested: claimedVersion ?? "unknown"
110406
+ };
110407
+ return {
110408
+ kind: "modern",
110409
+ revision: claimedVersion,
110410
+ classification: {
110411
+ era: "modern",
110412
+ revision: claimedVersion
110413
+ }
110414
+ };
110415
+ }
110416
+ /**
110417
+ * Serves MCP over stdio from a server factory, owning the era decision for
110418
+ * the connection: the opening exchange selects the era, ONE instance from the
110419
+ * factory is pinned for the connection lifetime, and everything after passes
110420
+ * straight through to it. See the module documentation for the opening rules.
110421
+ *
110422
+ * ```ts
110423
+ * import { serveStdio } from '@modelcontextprotocol/server/stdio';
110424
+ *
110425
+ * serveStdio(() => {
110426
+ * const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });
110427
+ * // register tools/resources/prompts once — the same factory serves both eras
110428
+ * return server;
110429
+ * });
110430
+ * ```
110431
+ */
110432
+ function serveStdio(factory, options = {}) {
110433
+ const legacyMode = options.legacy ?? "serve";
110434
+ const wire = options.transport ?? new StdioServerTransport();
110435
+ let state = { phase: "opening" };
110436
+ /** Channel currently being discarded (its close must not tear the connection down). */
110437
+ let discarding;
110438
+ let closing = false;
110439
+ /**
110440
+ * Whether the connection has been torn down (`handle.close()` or the wire
110441
+ * closing). The opening arms re-check this after every await: a close can
110442
+ * race factory construction, and the continuation must neither resurrect
110443
+ * the connection state nor keep a late-resolved instance around.
110444
+ */
110445
+ const isTornDown = () => closing || state.phase === "closed";
110446
+ const reportError = (error) => {
110447
+ try {
110448
+ options.onerror?.(error);
110449
+ } catch {}
110450
+ };
110451
+ const writeErrorResponse = (id, code, message, data) => wire.send({
110452
+ jsonrpc: "2.0",
110453
+ id,
110454
+ error: {
110455
+ code,
110456
+ message,
110457
+ ...data !== void 0 && { data }
110458
+ }
110459
+ }).catch((error) => reportError(toError(error)));
110460
+ /**
110461
+ * Entry-handled `subscriptions/listen` for this connection: holds the
110462
+ * active subscriptions, serves inbound listen / cancelled-of-listen
110463
+ * before the pinned instance is consulted, and rewrites the instance's
110464
+ * outbound change notifications onto the active subscriptions. Only
110465
+ * consulted on a modern-pinned connection — on a legacy connection
110466
+ * change notifications pass straight through (the 2025 unsolicited
110467
+ * delivery model is unchanged).
110468
+ */
110469
+ const listenRouter = new StdioListenRouter(options.maxSubscriptions ?? 1024);
110470
+ /** Outbound intercept installed on a modern instance's channel. */
110471
+ const modernOutboundIntercept = (message) => {
110472
+ if (!isJSONRPCNotification(message)) return void 0;
110473
+ const routed = listenRouter.routeOutbound(message);
110474
+ if (routed === "passthrough") return void 0;
110475
+ for (const stamped of routed) wire.send({
110476
+ jsonrpc: "2.0",
110477
+ ...stamped
110478
+ }).catch((error) => reportError(toError(error)));
110479
+ return "handled";
110480
+ };
110481
+ /**
110482
+ * Entry-handled inbound listen routing for a modern-pinned connection.
110483
+ * Returns `true` when the message was served at the entry and must NOT
110484
+ * be delivered to the pinned instance.
110485
+ */
110486
+ const tryServeListen = async (message) => {
110487
+ if (isJSONRPCRequest(message) && message.method === "subscriptions/listen") {
110488
+ const meta = requestMetaOf(message.params);
110489
+ const issue = hasEnvelopeClaim(message.params) ? (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0] : {
110490
+ key: "_meta",
110491
+ problem: "the per-request envelope is required on protocol revision 2026-07-28"
110492
+ };
110493
+ const claimedVersion = envelopeClaimVersion(message.params);
110494
+ let reply;
110495
+ if (issue !== void 0) reply = {
110496
+ jsonrpc: "2.0",
110497
+ id: message.id,
110498
+ error: {
110499
+ code: -32602,
110500
+ message: `Invalid _meta envelope: ${issue.key}: ${issue.problem}`
110501
+ }
110502
+ };
110503
+ else if (claimedVersion === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedVersion)) {
110504
+ const error = new UnsupportedProtocolVersionError({
110505
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
110506
+ requested: claimedVersion ?? "unknown"
110507
+ });
110508
+ reply = {
110509
+ jsonrpc: "2.0",
110510
+ id: message.id,
110511
+ error: {
110512
+ code: error.code,
110513
+ message: error.message,
110514
+ data: error.data
110515
+ }
110516
+ };
110517
+ } else reply = listenRouter.serve(message);
110518
+ await wire.send("error" in reply ? reply : {
110519
+ jsonrpc: "2.0",
110520
+ method: reply.method,
110521
+ params: reply.params
110522
+ }).catch((error) => reportError(toError(error)));
110523
+ return true;
110524
+ }
110525
+ if (isJSONRPCNotification(message) && message.method === "notifications/cancelled") {
110526
+ const cancelledId = message.params?.requestId;
110527
+ if (cancelledId !== void 0 && listenRouter.cancel(cancelledId)) return true;
110528
+ }
110529
+ return false;
110530
+ };
110531
+ /** Answers a 2025-era request the entry will not serve (the modern-only rejection cells). */
110532
+ const answerLegacyRejection = (request, reason, requestedVersion) => {
110533
+ const rejection = modernOnlyStrictRejection({
110534
+ kind: "legacy",
110535
+ reason,
110536
+ ...requestedVersion !== void 0 && { requestedVersion }
110537
+ }, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
110538
+ if (rejection === void 0) return Promise.resolve();
110539
+ reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only stdio connection (${rejection.cell}): ${rejection.message}`));
110540
+ return writeErrorResponse(request.id, rejection.code, rejection.message, rejection.data);
110541
+ };
110542
+ const onInstanceClosed = (channel) => {
110543
+ if (closing || channel === discarding) return;
110544
+ closeAll();
110545
+ };
110546
+ const connectInstance = async (era, revision) => {
110547
+ const product = await factory({ era });
110548
+ const server = product instanceof McpServer ? product.server : product;
110549
+ if (era === "modern") {
110550
+ setNegotiatedProtocolVersion(server, revision);
110551
+ installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
110552
+ listenRouter.setServerCapabilities(server.getCapabilities(), serverIdentityOf(server));
110553
+ }
110554
+ const channel = new StdioConnectionChannel(wire, () => onInstanceClosed(channel), era === "modern" ? modernOutboundIntercept : void 0);
110555
+ await product.connect(channel);
110556
+ return {
110557
+ product,
110558
+ channel
110559
+ };
110560
+ };
110561
+ /** Closes an instance whose factory resolved only after the connection was torn down. */
110562
+ const disposeLateInstance = (instance) => instance.product.close().catch((error) => reportError(toError(error)));
110563
+ const discardProbeInstance = async (instance) => {
110564
+ discarding = instance.channel;
110565
+ try {
110566
+ if (!await instance.channel.whenRequestsAnswered(DISCARD_ANSWER_TIMEOUT_MS)) reportError(/* @__PURE__ */ new Error(`Discarded the probe instance with requests still unanswered after ${DISCARD_ANSWER_TIMEOUT_MS}ms; continuing with the fallback`));
110567
+ await instance.product.close();
110568
+ } catch (error) {
110569
+ reportError(toError(error));
110570
+ } finally {
110571
+ discarding = void 0;
110572
+ }
110573
+ };
110574
+ const processMessage = async (message) => {
110575
+ if (state.phase === "closed") return;
110576
+ if (state.phase === "pinned") {
110577
+ if (state.era === "modern" && isJSONRPCRequest(message) && message.method === "initialize" && !carriesValidModernEnvelopeClaim(message.params)) {
110578
+ await answerLegacyRejection(message, "initialize", message.params !== null && typeof message.params === "object" && typeof message.params.protocolVersion === "string" ? message.params.protocolVersion : void 0);
110579
+ return;
110580
+ }
110581
+ if (state.era === "modern" && await tryServeListen(message)) return;
110582
+ if (isTornDown()) return;
110583
+ state.instance.channel.deliver(message);
110584
+ return;
110585
+ }
110586
+ if (!isJSONRPCRequest(message) && !isJSONRPCNotification(message)) {
110587
+ reportError(/* @__PURE__ */ new Error("Discarded a JSON-RPC response received before the connection negotiated an era"));
110588
+ return;
110589
+ }
110590
+ const opening = classifyOpeningMessage(message);
110591
+ switch (opening.kind) {
110592
+ case "invalid-envelope": {
110593
+ const detail = `Invalid _meta envelope for protocol revision 2026-07-28: ${opening.issue.key}: ${opening.issue.problem}`;
110594
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InvalidParams, detail, { envelope: opening.issue });
110595
+ else reportError(/* @__PURE__ */ new Error(`Discarded a notification with a malformed envelope: ${detail}`));
110596
+ return;
110597
+ }
110598
+ case "unsupported-revision":
110599
+ if (isJSONRPCRequest(message)) {
110600
+ const error = new UnsupportedProtocolVersionError({
110601
+ supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
110602
+ requested: opening.requested
110603
+ });
110604
+ reportError(error);
110605
+ await writeErrorResponse(message.id, error.code, error.message, error.data);
110606
+ } else reportError(/* @__PURE__ */ new Error(`Discarded a notification claiming unsupported protocol revision ${opening.requested}`));
110607
+ return;
110608
+ case "modern":
110609
+ if (isJSONRPCRequest(message) && message.method === "server/discover") {
110610
+ if (state.phase === "probe") {
110611
+ state.instance.channel.deliver(message, { classification: opening.classification });
110612
+ return;
110613
+ }
110614
+ const instance = await connectInstance("modern", opening.revision);
110615
+ if (isTornDown()) {
110616
+ await disposeLateInstance(instance);
110617
+ return;
110618
+ }
110619
+ state = {
110620
+ phase: "probe",
110621
+ instance
110622
+ };
110623
+ instance.channel.deliver(message, { classification: opening.classification });
110624
+ return;
110625
+ }
110626
+ if (state.phase === "probe") {
110627
+ if (isJSONRPCNotification(message)) {
110628
+ state.instance.channel.deliver(message, { classification: opening.classification });
110629
+ return;
110630
+ }
110631
+ state = {
110632
+ phase: "pinned",
110633
+ era: "modern",
110634
+ instance: state.instance
110635
+ };
110636
+ } else {
110637
+ const instance = await connectInstance("modern", opening.revision);
110638
+ if (isTornDown()) {
110639
+ await disposeLateInstance(instance);
110640
+ return;
110641
+ }
110642
+ state = {
110643
+ phase: "pinned",
110644
+ era: "modern",
110645
+ instance
110646
+ };
110647
+ }
110648
+ if (await tryServeListen(message)) return;
110649
+ if (isTornDown()) return;
110650
+ state.instance.channel.deliver(message, { classification: opening.classification });
110651
+ return;
110652
+ case "legacy": {
110653
+ if (legacyMode === "reject") {
110654
+ if (isJSONRPCRequest(message)) await answerLegacyRejection(message, opening.reason, opening.requestedVersion);
110655
+ return;
110656
+ }
110657
+ if (state.phase === "probe") {
110658
+ await discardProbeInstance(state.instance);
110659
+ if (isTornDown()) return;
110660
+ state = { phase: "opening" };
110661
+ }
110662
+ const instance = await connectInstance("legacy");
110663
+ if (isTornDown()) {
110664
+ await disposeLateInstance(instance);
110665
+ return;
110666
+ }
110667
+ state = {
110668
+ phase: "pinned",
110669
+ era: "legacy",
110670
+ instance
110671
+ };
110672
+ state.instance.channel.deliver(message);
110673
+ return;
110674
+ }
110675
+ }
110676
+ };
110677
+ const queue = [];
110678
+ let pumping = false;
110679
+ const pump = async () => {
110680
+ if (pumping) return;
110681
+ pumping = true;
110682
+ try {
110683
+ while (queue.length > 0) {
110684
+ const message = queue.shift();
110685
+ try {
110686
+ await processMessage(message);
110687
+ } catch (error) {
110688
+ if (isJSONRPCRequest(message)) await writeErrorResponse(message.id, ProtocolErrorCode.InternalError, "Internal server error");
110689
+ reportError(toError(error));
110690
+ }
110691
+ }
110692
+ } finally {
110693
+ pumping = false;
110694
+ }
110695
+ };
110696
+ const closeAll = async () => {
110697
+ if (closing || state.phase === "closed") return;
110698
+ closing = true;
110699
+ const current = state;
110700
+ state = { phase: "closed" };
110701
+ for (const result of listenRouter.teardownAll()) await wire.send(result).catch((error) => reportError(toError(error)));
110702
+ if (current.phase === "probe" || current.phase === "pinned") await current.instance.product.close().catch((error) => reportError(toError(error)));
110703
+ await wire.close().catch((error) => reportError(toError(error)));
110704
+ };
110705
+ wire.onmessage = (message) => {
110706
+ queue.push(message);
110707
+ pump();
110708
+ };
110709
+ wire.onerror = (error) => {
110710
+ reportError(error);
110711
+ if (state.phase === "probe" || state.phase === "pinned") state.instance.channel.onerror?.(error);
110712
+ };
110713
+ wire.onclose = () => {
110714
+ if (closing || state.phase === "closed") return;
110715
+ closing = true;
110716
+ const current = state;
110717
+ state = { phase: "closed" };
110718
+ if (current.phase === "probe" || current.phase === "pinned") current.instance.product.close().catch((error) => reportError(toError(error)));
110719
+ };
110720
+ const started = wire.start().catch((error) => {
110721
+ reportError(toError(error));
110722
+ throw error;
110723
+ });
110724
+ started.catch(() => {});
110725
+ return { close: async () => {
110726
+ await started.catch(() => {});
110727
+ await closeAll();
110728
+ } };
110729
+ }
110730
+ function toError(value) {
110731
+ return value instanceof Error ? value : new Error(String(value));
110732
+ }
108299
110733
  //#endregion
108300
110734
  //#region src/transports/stdio.ts
108301
- const startStdioTransport = async (server, logger = silentLogger) => {
108302
- const transport = new StdioServerTransport(void 0, void 0, { maxBufferSize: STDIO_MAX_MESSAGE_BYTES });
108303
- await server.connect(transport);
110735
+ /**
110736
+ * Serve MCP over stdio, both protocol eras: the SDK entry picks the era from the
110737
+ * client's opening exchange and pins one server from `build` per connection (a
110738
+ * `server/discover` probe that falls back to `initialize` builds one more, then
110739
+ * drops it).
110740
+ */
110741
+ const startStdioTransport = (build, logger = silentLogger, options = {}) => {
110742
+ const requestStateCodec = createRequestStateCodec({});
110743
+ const handle = serveStdio(() => {
110744
+ let server;
110745
+ server = build({
110746
+ requestStateCodec,
110747
+ notifyResourceUpdated: (uri) => {
110748
+ server?.server.sendResourceUpdated({ uri }).catch(() => void 0);
110749
+ }
110750
+ });
110751
+ return server;
110752
+ }, {
110753
+ transport: options.transport ?? new StdioServerTransport(void 0, void 0, { maxBufferSize: 10485760 }),
110754
+ onerror: (error) => logger.warn("stdio_transport_error", { error: error.message })
110755
+ });
108304
110756
  logger.info("stdio_transport_ready");
110757
+ return handle;
108305
110758
  };
108306
110759
  //#endregion
108307
110760
  //#region src/dev/reload.ts
@@ -108353,19 +110806,19 @@ const loadFreshTools = async (ctx) => {
108353
110806
  * only matters after an edit); `reload()` disposes the current generation and
108354
110807
  * registers a fresh one.
108355
110808
  */
108356
- const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools) => {
108357
- const server = createServerShell(config, logger);
108358
- const resolveBrandSubdomain = createBrandSubdomainResolver(config.subdomain, getToken);
110809
+ const createReloadableServer = (config, getToken, logger = silentLogger, onUnauthorized, loadTools = loadFreshTools, requestStateCodec) => {
110810
+ const server = createServerShell(config, requestStateCodec);
110811
+ const caches = createServerCaches(config, getToken, onUnauthorized);
108359
110812
  const ctx = {
108360
110813
  subdomain: config.subdomain,
108361
110814
  brandIds: config.brandIds,
108362
- resolveBrandSubdomain,
110815
+ resolveBrandSubdomain: caches.resolveBrandSubdomain,
108363
110816
  getToken
108364
110817
  };
108365
110818
  const params = {
108366
110819
  config,
108367
110820
  getToken,
108368
- resolveBrandSubdomain,
110821
+ caches,
108369
110822
  onUnauthorized,
108370
110823
  logger
108371
110824
  };
@@ -108402,7 +110855,7 @@ const createReloadableServer = (config, getToken, logger = silentLogger, onUnaut
108402
110855
  const registerReloadTool = (server, reload, logger = silentLogger) => {
108403
110856
  server.registerTool("reload_tools", {
108404
110857
  title: "Reload tools from source (dev)",
108405
- description: "Dev only. Re-imports the Zendesk tool modules from source and re-registers them on this live session, so tool code you just edited takes effect without restarting the server or reconnecting the client. Call it once at the end of an edit cycle, before testing your changes; the refreshed tool list is announced via tools/list_changed. Only the tool modules (tickets, search, help_center, users) are reloaded — edits to shared infrastructure (HTTP client, shared definitions, server wiring) still require a full restart. Takes no arguments and makes no Zendesk API calls.",
110858
+ description: "Dev only. Re-imports the Zendesk tool modules from source and re-registers them on this live session, so tool code you just edited takes effect without restarting the server or reconnecting the client. Call it once at the end of an edit cycle, before testing your changes; the refreshed tool list is announced via tools/list_changed (a client on the 2026-07-28 revision receives it only on an open subscriptions/listen stream; otherwise call tools/list again). Only the tool modules (tickets, search, help_center, users) are reloaded — edits to shared infrastructure (HTTP client, shared definitions, server wiring) still require a full restart. Takes no arguments and makes no Zendesk API calls.",
108406
110859
  inputSchema: object({}).strict(),
108407
110860
  annotations: {
108408
110861
  readOnlyHint: false,
@@ -108416,7 +110869,7 @@ const registerReloadTool = (server, reload, logger = silentLogger) => {
108416
110869
  logger.info("tools_reloaded", { count });
108417
110870
  return { content: [{
108418
110871
  type: "text",
108419
- text: `Reloaded ${count} tool(s) from source. The updated tool list is now live (tools/list_changed sent).`
110872
+ text: `Reloaded ${count} tool(s) from source. The updated tool list is now live (tools/list_changed sent; if your client did not receive it, call tools/list again).`
108420
110873
  }] };
108421
110874
  } catch (err) {
108422
110875
  const message = err instanceof Error ? err.message : String(err);
@@ -108434,21 +110887,23 @@ const registerReloadTool = (server, reload, logger = silentLogger) => {
108434
110887
  /**
108435
110888
  * Start the stdio server in dev mode: the normal toolset plus a persistent
108436
110889
  * `reload_tools` tool that hot-reloads edited tool code on demand. stdio only —
108437
- * HTTP builds a per-session server per request, so there is no long-lived
110890
+ * HTTP builds a server per session or per request, so there is no long-lived
108438
110891
  * server to hot-swap.
108439
110892
  *
108440
- * Returns the running server so the caller can close it on shutdown: dev mode
110893
+ * Returns the transport handle so the caller can close it on shutdown: dev mode
108441
110894
  * runs over the same stdio transport as normal mode and must exit the same way
108442
110895
  * when the client disconnects.
108443
110896
  */
108444
110897
  /* v8 ignore start -- runtime bootstrap: binds the reload tool to a real stdio
108445
110898
  transport; the reload machinery it wires up is covered by dev-reload.test.ts */
108446
- const startDevServer = async (config, getToken, logger = silentLogger, onUnauthorized) => {
108447
- const { server, reload } = createReloadableServer(config, getToken, logger, onUnauthorized);
108448
- registerReloadTool(server, reload, logger);
108449
- await startStdioTransport(server, logger);
110899
+ const startDevServer = (config, getToken, logger = silentLogger, onUnauthorized) => {
110900
+ const handle = startStdioTransport(({ requestStateCodec }) => {
110901
+ const { server, reload } = createReloadableServer(config, getToken, logger, onUnauthorized, loadFreshTools, requestStateCodec);
110902
+ registerReloadTool(server, reload, logger);
110903
+ return server;
110904
+ }, logger);
108450
110905
  logger.info("dev_mode_enabled");
108451
- return server;
110906
+ return handle;
108452
110907
  };
108453
110908
  /* v8 ignore stop */
108454
110909
  //#endregion
@@ -108600,7 +111055,7 @@ const installShutdown = (options) => {
108600
111055
  //#endregion
108601
111056
  //#region src/index.ts
108602
111057
  const buildStdioTokenStore = async (config, logger) => {
108603
- const { createTokenStore } = await import("./token-store-BLAkLLQO.js");
111058
+ const { createTokenStore } = await import("./token-store-DyO12sbo.js");
108604
111059
  return createTokenStore({
108605
111060
  subdomain: config.subdomain,
108606
111061
  oauthClientId: config.oauthClientId,
@@ -108608,11 +111063,15 @@ const buildStdioTokenStore = async (config, logger) => {
108608
111063
  readOnly: config.readOnly
108609
111064
  }, logger);
108610
111065
  };
108611
- const connectStdio = async (config, tokenStore, logger) => {
111066
+ const connectStdio = (config, tokenStore, logger) => {
108612
111067
  if (config.dev) return startDevServer(config, tokenStore.getToken, logger, tokenStore.invalidate);
108613
- const server = createMcpServer(config, tokenStore.getToken, logger, tokenStore.invalidate);
108614
- await startStdioTransport(server, logger);
108615
- return server;
111068
+ return startStdioTransport((extras) => defaultServerFactory({
111069
+ config,
111070
+ getToken: tokenStore.getToken,
111071
+ logger,
111072
+ onUnauthorized: tokenStore.invalidate,
111073
+ ...extras
111074
+ }), logger);
108616
111075
  };
108617
111076
  const main = async () => {
108618
111077
  const config = loadConfig();
@@ -108629,19 +111088,19 @@ const main = async () => {
108629
111088
  const logger = createLogger(config.logLevel);
108630
111089
  if (config.transport === "stdio") {
108631
111090
  const tokenStore = await buildStdioTokenStore(config, logger);
108632
- const server = await connectStdio(config, tokenStore, logger);
111091
+ const stdio = connectStdio(config, tokenStore, logger);
108633
111092
  installShutdown({
108634
111093
  watchStdin: true,
108635
111094
  logger,
108636
111095
  cleanup: async () => {
108637
- await server.close();
111096
+ await stdio.close();
108638
111097
  tokenStore.dispose();
108639
111098
  }
108640
111099
  });
108641
111100
  return;
108642
111101
  }
108643
111102
  if (config.dev) logger.warn("dev_mode_ignored_http");
108644
- const { startHttpTransport } = await import("./http-BYYYoQaK.js");
111103
+ const { startHttpTransport } = await import("./http-DU1dSrPe.js");
108645
111104
  const http = await startHttpTransport(config, logger);
108646
111105
  installShutdown({
108647
111106
  watchStdin: false,
@@ -108655,4 +111114,4 @@ main().catch((error) => {
108655
111114
  process.exit(1);
108656
111115
  });
108657
111116
  //#endregion
108658
- export { __commonJSMin$1 as _, verifyBearerToken as a, __toESM$1 as b, zendeskGet as c, DEFAULT_CALLBACK_PORT as d, getOAuthUrls as f, readEnv as g, errnoCode as h, WebStandardStreamableHTTPServerTransport as i, REQUEST_TIMEOUT_MS as l, createStartupError as m, readPackageInfo as n, OAuthError as o, STARTUP_DOCS as p, silentLogger as r, OAuthErrorCode as s, createMcpServer as t, deadlineSignal as u, __exportAll$1 as v, __require as y };
111117
+ export { __exportAll$1 as C, __commonJSMin$1 as S, __toESM$1 as T, OAuthErrorCode as _, silentLogger as a, errnoCode as b, deadlineSignal as c, DEFAULT_MAX_REQUEST_BODY_SIZE as d, WebStandardStreamableHTTPServerTransport as f, OAuthError as g, verifyBearerToken as h, readPackageInfo as i, DEFAULT_CALLBACK_PORT as l, isLegacyRequest as m, defaultServerFactory as n, zendeskGet as o, createMcpHandler as p, createRequestStateCodec as r, REQUEST_TIMEOUT_MS as s, createServerCaches as t, getOAuthUrls as u, STARTUP_DOCS as v, __require as w, readEnv as x, createStartupError as y };