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