@clipform/mcp-server 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.js +1 -1
- package/dist/{chunk-JRASOR5T.js → chunk-4IAURGG7.js} +3 -3
- package/dist/chunk-4IAURGG7.js.map +1 -0
- package/dist/{chunk-MSLK3FRO.js → chunk-5A66GD3U.js} +1975 -24
- package/dist/{chunk-MSLK3FRO.js.map → chunk-5A66GD3U.js.map} +1 -1
- package/dist/{chunk-54RZGUSA.js → chunk-5RUWQOYD.js} +88 -239
- package/dist/{chunk-54RZGUSA.js.map → chunk-5RUWQOYD.js.map} +1 -1
- package/dist/{chunk-S6RYUVDM.js → chunk-PDI6VJYQ.js} +2 -2
- package/dist/{chunk-5MJGBBKA.js → chunk-TXUWAYBZ.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/prompts.d.ts +11 -2
- package/dist/prompts.js +13 -3
- package/dist/resources.js +3 -3
- package/dist/server.d.ts +1 -0
- package/dist/server.js +11 -7
- package/package.json +1 -1
- package/dist/chunk-JRASOR5T.js.map +0 -1
- /package/dist/{chunk-S6RYUVDM.js.map → chunk-PDI6VJYQ.js.map} +0 -0
- /package/dist/{chunk-5MJGBBKA.js.map → chunk-TXUWAYBZ.js.map} +0 -0
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
union,
|
|
30
30
|
unknown,
|
|
31
31
|
url
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-5RUWQOYD.js";
|
|
33
33
|
import {
|
|
34
34
|
GUIDE_TYPES,
|
|
35
35
|
QUIZ_VARIANTS,
|
|
@@ -38,13 +38,13 @@ import {
|
|
|
38
38
|
getGuideUri,
|
|
39
39
|
guideFallbackText,
|
|
40
40
|
registerResources
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-TXUWAYBZ.js";
|
|
42
42
|
import {
|
|
43
43
|
ANON_PREVIEW_NOTICE,
|
|
44
44
|
FULL_CAPS,
|
|
45
45
|
anonPreviewNotice,
|
|
46
46
|
capsFromToolsets
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-PDI6VJYQ.js";
|
|
48
48
|
import {
|
|
49
49
|
ACTIVE_NODE_TYPES,
|
|
50
50
|
CONTACT_FIELDS,
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
resolveFormType,
|
|
67
67
|
structuredResult,
|
|
68
68
|
textResult
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-4IAURGG7.js";
|
|
70
70
|
import {
|
|
71
71
|
getMcpAuth,
|
|
72
72
|
runWithMcpAuth,
|
|
@@ -132,6 +132,7 @@ function declaredDialect(schema, remedy) {
|
|
|
132
132
|
|
|
133
133
|
// ../../node_modules/.pnpm/@modelcontextprotocol+core@2.3.1/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
|
|
134
134
|
var LATEST_PROTOCOL_VERSION = "2025-11-25";
|
|
135
|
+
var DEFAULT_NEGOTIATED_PROTOCOL_VERSION = "2025-03-26";
|
|
135
136
|
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
136
137
|
LATEST_PROTOCOL_VERSION,
|
|
137
138
|
"2025-06-18",
|
|
@@ -1098,6 +1099,29 @@ function brandedHasInstance(cls, value) {
|
|
|
1098
1099
|
}
|
|
1099
1100
|
return Function.prototype[Symbol.hasInstance].call(cls, value);
|
|
1100
1101
|
}
|
|
1102
|
+
var OAuthErrorCode = /* @__PURE__ */ (function(OAuthErrorCode$1) {
|
|
1103
|
+
OAuthErrorCode$1["InvalidRequest"] = "invalid_request";
|
|
1104
|
+
OAuthErrorCode$1["InvalidClient"] = "invalid_client";
|
|
1105
|
+
OAuthErrorCode$1["InvalidGrant"] = "invalid_grant";
|
|
1106
|
+
OAuthErrorCode$1["UnauthorizedClient"] = "unauthorized_client";
|
|
1107
|
+
OAuthErrorCode$1["UnsupportedGrantType"] = "unsupported_grant_type";
|
|
1108
|
+
OAuthErrorCode$1["InvalidScope"] = "invalid_scope";
|
|
1109
|
+
OAuthErrorCode$1["AccessDenied"] = "access_denied";
|
|
1110
|
+
OAuthErrorCode$1["ServerError"] = "server_error";
|
|
1111
|
+
OAuthErrorCode$1["TemporarilyUnavailable"] = "temporarily_unavailable";
|
|
1112
|
+
OAuthErrorCode$1["UnsupportedResponseType"] = "unsupported_response_type";
|
|
1113
|
+
OAuthErrorCode$1["UnsupportedTokenType"] = "unsupported_token_type";
|
|
1114
|
+
OAuthErrorCode$1["InvalidToken"] = "invalid_token";
|
|
1115
|
+
OAuthErrorCode$1["MethodNotAllowed"] = "method_not_allowed";
|
|
1116
|
+
OAuthErrorCode$1["TooManyRequests"] = "too_many_requests";
|
|
1117
|
+
OAuthErrorCode$1["InvalidClientMetadata"] = "invalid_client_metadata";
|
|
1118
|
+
OAuthErrorCode$1["InvalidRedirectUri"] = "invalid_redirect_uri";
|
|
1119
|
+
OAuthErrorCode$1["InsufficientScope"] = "insufficient_scope";
|
|
1120
|
+
OAuthErrorCode$1["InvalidTarget"] = "invalid_target";
|
|
1121
|
+
OAuthErrorCode$1["InvalidDpopProof"] = "invalid_dpop_proof";
|
|
1122
|
+
OAuthErrorCode$1["UseDpopNonce"] = "use_dpop_nonce";
|
|
1123
|
+
return OAuthErrorCode$1;
|
|
1124
|
+
})({});
|
|
1101
1125
|
var OAuthError = class OAuthError2 extends Error {
|
|
1102
1126
|
static {
|
|
1103
1127
|
Object.defineProperty(this, "mcpBrand", { value: "mcp.OAuthError" });
|
|
@@ -1221,6 +1245,10 @@ var SdkHttpError = class extends SdkError {
|
|
|
1221
1245
|
return this.data.statusText;
|
|
1222
1246
|
}
|
|
1223
1247
|
};
|
|
1248
|
+
var REQUIRED_CLIENT_CAPABILITIES_BY_METHOD = {};
|
|
1249
|
+
function requiredClientCapabilitiesForRequest(method) {
|
|
1250
|
+
return Object.hasOwn(REQUIRED_CLIENT_CAPABILITIES_BY_METHOD, method) ? REQUIRED_CLIENT_CAPABILITIES_BY_METHOD[method] : void 0;
|
|
1251
|
+
}
|
|
1224
1252
|
function isPlainObject$7(value) {
|
|
1225
1253
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1226
1254
|
}
|
|
@@ -1261,6 +1289,7 @@ function missingClientCapabilities(required, declared) {
|
|
|
1261
1289
|
return Object.keys(missing).length > 0 ? missing : void 0;
|
|
1262
1290
|
}
|
|
1263
1291
|
var FIRST_MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
1292
|
+
var SUPPORTED_MODERN_PROTOCOL_VERSIONS = [FIRST_MODERN_PROTOCOL_VERSION];
|
|
1264
1293
|
function isModernProtocolVersion(version) {
|
|
1265
1294
|
return version >= FIRST_MODERN_PROTOCOL_VERSION;
|
|
1266
1295
|
}
|
|
@@ -3826,6 +3855,25 @@ function isExtensionReusedRequestMethod(method) {
|
|
|
3826
3855
|
return method === "tasks/get" || method === "tasks/cancel";
|
|
3827
3856
|
}
|
|
3828
3857
|
var ALL_CODECS = [rev2025Codec, rev2026Codec];
|
|
3858
|
+
function isPlainObject$3(value) {
|
|
3859
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3860
|
+
}
|
|
3861
|
+
function requestMetaOf(params) {
|
|
3862
|
+
if (!isPlainObject$3(params)) return void 0;
|
|
3863
|
+
const meta = params["_meta"];
|
|
3864
|
+
return isPlainObject$3(meta) ? meta : void 0;
|
|
3865
|
+
}
|
|
3866
|
+
function hasEnvelopeClaim(params) {
|
|
3867
|
+
const meta = requestMetaOf(params);
|
|
3868
|
+
return meta !== void 0 && PROTOCOL_VERSION_META_KEY in meta;
|
|
3869
|
+
}
|
|
3870
|
+
function envelopeClaimVersion(params) {
|
|
3871
|
+
const value = requestMetaOf(params)?.[PROTOCOL_VERSION_META_KEY];
|
|
3872
|
+
return typeof value === "string" ? value : void 0;
|
|
3873
|
+
}
|
|
3874
|
+
function validateEnvelopeMeta(meta) {
|
|
3875
|
+
return codecForVersion(MODERN_WIRE_REVISION).validateEnvelopeMeta(meta);
|
|
3876
|
+
}
|
|
3829
3877
|
var schemas_exports = /* @__PURE__ */ __exportAll({
|
|
3830
3878
|
AnnotationsSchema: () => AnnotationsSchema,
|
|
3831
3879
|
AudioContentSchema: () => AudioContentSchema,
|
|
@@ -3999,12 +4047,14 @@ var isJSONRPCNotification = (value) => JSONRPCNotificationSchema.safeParse(value
|
|
|
3999
4047
|
var isJSONRPCResultResponse = (value) => JSONRPCResultResponseSchema.safeParse(value).success;
|
|
4000
4048
|
var isJSONRPCErrorResponse = (value) => JSONRPCErrorResponseSchema.safeParse(value).success;
|
|
4001
4049
|
var isInputRequiredResult = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && value.resultType === "input_required";
|
|
4050
|
+
var isInitializeRequest = (value) => InitializeRequestSchema.safeParse(value).success;
|
|
4002
4051
|
function assertCompleteRequestPrompt(request) {
|
|
4003
4052
|
if (request.params.ref.type !== "ref/prompt") throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
|
4004
4053
|
}
|
|
4005
4054
|
function assertCompleteRequestResourceTemplate(request) {
|
|
4006
4055
|
if (request.params.ref.type !== "ref/resource") throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
|
4007
4056
|
}
|
|
4057
|
+
var MCP_PARAM_HEADER_PREFIX = "Mcp-Param-";
|
|
4008
4058
|
var X_MCP_HEADER_KEY = "x-mcp-header";
|
|
4009
4059
|
var RFC9110_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
4010
4060
|
var PERMITTED_X_MCP_HEADER_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -4089,6 +4139,72 @@ var OBJECT_VALUED_SUBSCHEMA_KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
4089
4139
|
function pathName(path) {
|
|
4090
4140
|
return path.length === 0 ? "<root>" : path.join(".");
|
|
4091
4141
|
}
|
|
4142
|
+
var BASE64_SENTINEL_PREFIX = "=?base64?";
|
|
4143
|
+
var BASE64_SENTINEL_SUFFIX = "?=";
|
|
4144
|
+
var BASE64_CANONICAL = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
4145
|
+
var CANONICAL_DECIMAL = /^-?\d+(\.\d+)?$/;
|
|
4146
|
+
function mcpParamPrimitiveToString(value) {
|
|
4147
|
+
if (typeof value === "string") return value;
|
|
4148
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
4149
|
+
if (typeof value === "number") {
|
|
4150
|
+
if (!Number.isFinite(value)) return void 0;
|
|
4151
|
+
if (Number.isInteger(value) && !Number.isSafeInteger(value)) return void 0;
|
|
4152
|
+
return String(value);
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
function base64ToUtf8(b64) {
|
|
4156
|
+
const bin = atob(b64);
|
|
4157
|
+
const bytes = new Uint8Array(bin.length);
|
|
4158
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.codePointAt(i);
|
|
4159
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4160
|
+
}
|
|
4161
|
+
function decodeMcpParamValue(value) {
|
|
4162
|
+
if (!(value.startsWith(BASE64_SENTINEL_PREFIX) && value.endsWith(BASE64_SENTINEL_SUFFIX))) return value;
|
|
4163
|
+
const b64 = value.slice(9, value.length - 2);
|
|
4164
|
+
if (!BASE64_CANONICAL.test(b64)) return void 0;
|
|
4165
|
+
try {
|
|
4166
|
+
return base64ToUtf8(b64);
|
|
4167
|
+
} catch {
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
function valueAtPath(root, path) {
|
|
4172
|
+
let node = root;
|
|
4173
|
+
for (const key of path) {
|
|
4174
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
4175
|
+
node = node[key];
|
|
4176
|
+
}
|
|
4177
|
+
return node;
|
|
4178
|
+
}
|
|
4179
|
+
function validateMcpParamHeaders(declarations, args, headers) {
|
|
4180
|
+
for (const decl of declarations) {
|
|
4181
|
+
const headerKey = `${MCP_PARAM_HEADER_PREFIX}${decl.headerName}`;
|
|
4182
|
+
const headerValue = headers.get(headerKey);
|
|
4183
|
+
const bodyRaw = valueAtPath(args, decl.path);
|
|
4184
|
+
if (bodyRaw === void 0 || bodyRaw === null) continue;
|
|
4185
|
+
const bodyString = mcpParamPrimitiveToString(bodyRaw);
|
|
4186
|
+
if (bodyString === void 0) continue;
|
|
4187
|
+
if (headerValue === null) return paramHeaderMismatchRejection("param-header-missing", headerKey, `the body carries ${pathName(decl.path)}=${JSON.stringify(bodyRaw)} but the ${headerKey} header is absent`);
|
|
4188
|
+
const decoded = decodeMcpParamValue(headerValue);
|
|
4189
|
+
if (decoded === void 0) return paramHeaderMismatchRejection("param-header-invalid-encoding", headerKey, `the ${headerKey} header carries an invalid Base64 sentinel value`);
|
|
4190
|
+
if (!((decl.type === "integer" || decl.type === "number") && CANONICAL_DECIMAL.test(decoded) && typeof bodyRaw === "number" ? Number(decoded) === bodyRaw : decoded === bodyString)) return paramHeaderMismatchRejection("param-header-mismatch", headerKey, `the ${headerKey} header decodes to ${JSON.stringify(decoded)} but the body carries ${pathName(decl.path)}=${JSON.stringify(bodyRaw)}`);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
function paramHeaderMismatchRejection(cell, header, body) {
|
|
4194
|
+
return {
|
|
4195
|
+
kind: "reject",
|
|
4196
|
+
rung: "param-header-validation",
|
|
4197
|
+
cell,
|
|
4198
|
+
httpStatus: 400,
|
|
4199
|
+
code: HEADER_MISMATCH_ERROR_CODE,
|
|
4200
|
+
message: `Bad Request: the request headers and body disagree: ${body}`,
|
|
4201
|
+
data: { mismatch: {
|
|
4202
|
+
header,
|
|
4203
|
+
body
|
|
4204
|
+
} },
|
|
4205
|
+
settled: true
|
|
4206
|
+
};
|
|
4207
|
+
}
|
|
4092
4208
|
var HEADER_MISMATCH_ERROR_CODE = -32020;
|
|
4093
4209
|
var INBOUND_VALIDATION_LADDER = [
|
|
4094
4210
|
{
|
|
@@ -4176,6 +4292,232 @@ var LADDER_ERROR_HTTP_STATUS = {
|
|
|
4176
4292
|
[ProtocolErrorCode.MissingRequiredClientCapability]: 400,
|
|
4177
4293
|
[HEADER_MISMATCH_ERROR_CODE]: 400
|
|
4178
4294
|
};
|
|
4295
|
+
function httpStatusForErrorCode(code, origin) {
|
|
4296
|
+
if (origin === "in-band") return code === ProtocolErrorCode.MissingRequiredClientCapability ? 400 : 200;
|
|
4297
|
+
return LADDER_ERROR_HTTP_STATUS[code] ?? 400;
|
|
4298
|
+
}
|
|
4299
|
+
function rejection(rung, cell, httpStatus, error, settled) {
|
|
4300
|
+
return {
|
|
4301
|
+
kind: "reject",
|
|
4302
|
+
rung,
|
|
4303
|
+
cell,
|
|
4304
|
+
httpStatus,
|
|
4305
|
+
code: error.code,
|
|
4306
|
+
message: error.message,
|
|
4307
|
+
...error.data !== void 0 && { data: error.data },
|
|
4308
|
+
settled
|
|
4309
|
+
};
|
|
4310
|
+
}
|
|
4311
|
+
function crossCheckMismatch(cell, header, body, rung = "era-classification") {
|
|
4312
|
+
return rejection(rung, cell, 400, new ProtocolError(HEADER_MISMATCH_ERROR_CODE, `Bad Request: the request headers and body disagree: ${body}`, { mismatch: {
|
|
4313
|
+
header,
|
|
4314
|
+
body
|
|
4315
|
+
} }), true);
|
|
4316
|
+
}
|
|
4317
|
+
var MCP_NAME_HEADER_SOURCE = {
|
|
4318
|
+
"tools/call": "name",
|
|
4319
|
+
"prompts/get": "name",
|
|
4320
|
+
"resources/read": "uri",
|
|
4321
|
+
"tasks/get": "taskId",
|
|
4322
|
+
"tasks/update": "taskId",
|
|
4323
|
+
"tasks/cancel": "taskId"
|
|
4324
|
+
};
|
|
4325
|
+
function mcpNameSource(method, params) {
|
|
4326
|
+
const field = Object.hasOwn(MCP_NAME_HEADER_SOURCE, method) ? MCP_NAME_HEADER_SOURCE[method] : void 0;
|
|
4327
|
+
if (field === void 0) return;
|
|
4328
|
+
const raw = isPlainObject$2(params) ? params[field] : void 0;
|
|
4329
|
+
return {
|
|
4330
|
+
field,
|
|
4331
|
+
value: typeof raw === "string" ? raw : void 0
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
function stripHttpOws(value) {
|
|
4335
|
+
let start = 0;
|
|
4336
|
+
while (start < value.length) {
|
|
4337
|
+
const code = value.codePointAt(start);
|
|
4338
|
+
if (code !== 9 && code !== 32) break;
|
|
4339
|
+
start += 1;
|
|
4340
|
+
}
|
|
4341
|
+
let end = value.length;
|
|
4342
|
+
while (end > start) {
|
|
4343
|
+
const code = value.codePointAt(end - 1);
|
|
4344
|
+
if (code !== 9 && code !== 32) break;
|
|
4345
|
+
end -= 1;
|
|
4346
|
+
}
|
|
4347
|
+
return start === 0 && end === value.length ? value : value.slice(start, end);
|
|
4348
|
+
}
|
|
4349
|
+
function validateStandardRequestHeaders(request, route) {
|
|
4350
|
+
if (route.messageKind !== "request") return;
|
|
4351
|
+
const method = route.message.method;
|
|
4352
|
+
if (request.protocolVersionHeader === void 0) {
|
|
4353
|
+
const claimed = route.classification.revision;
|
|
4354
|
+
return crossCheckMismatch("version-header-missing", "(missing)", claimed === void 0 ? "the body carries a modern per-request envelope but the required MCP-Protocol-Version header is absent" : `the body envelope names protocol version ${claimed} but the required MCP-Protocol-Version header is absent`, "standard-header-validation");
|
|
4355
|
+
}
|
|
4356
|
+
if (request.mcpMethodHeader === void 0) return crossCheckMismatch("method-header-missing", "(missing)", `the body names method ${method} but the required Mcp-Method header is absent`, "standard-header-validation");
|
|
4357
|
+
const source = mcpNameSource(method, route.message.params);
|
|
4358
|
+
if (source === void 0) return;
|
|
4359
|
+
const { field: sourceField, value: bodyValue } = source;
|
|
4360
|
+
if (request.mcpNameHeader === void 0) {
|
|
4361
|
+
if (bodyValue === void 0) return;
|
|
4362
|
+
return crossCheckMismatch("name-header-missing", "(missing)", `the body carries params.${sourceField}="${bodyValue}" but the required Mcp-Name header is absent`, "standard-header-validation");
|
|
4363
|
+
}
|
|
4364
|
+
const normalizedNameHeader = stripHttpOws(request.mcpNameHeader);
|
|
4365
|
+
const decoded = decodeMcpParamValue(normalizedNameHeader);
|
|
4366
|
+
if (decoded === void 0) return crossCheckMismatch("name-header-invalid-encoding", normalizedNameHeader, "the Mcp-Name header carries an invalid Base64 sentinel value", "standard-header-validation");
|
|
4367
|
+
if (bodyValue !== void 0 && decoded !== bodyValue) return crossCheckMismatch("name-header-mismatch", normalizedNameHeader, `the body carries params.${sourceField}="${bodyValue}" but the Mcp-Name header names "${decoded}"`, "standard-header-validation");
|
|
4368
|
+
}
|
|
4369
|
+
function isPlainObject$2(value) {
|
|
4370
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4371
|
+
}
|
|
4372
|
+
function classificationForClaim(claimedVersion) {
|
|
4373
|
+
if (claimedVersion === void 0) return { era: "modern" };
|
|
4374
|
+
return {
|
|
4375
|
+
era: isModernProtocolVersion(claimedVersion) ? "modern" : "legacy",
|
|
4376
|
+
revision: claimedVersion
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
function carriesValidModernEnvelopeClaim(params) {
|
|
4380
|
+
if (!hasEnvelopeClaim(params)) return false;
|
|
4381
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
4382
|
+
if (claimedVersion === void 0 || !isModernProtocolVersion(claimedVersion)) return false;
|
|
4383
|
+
const meta = requestMetaOf(params);
|
|
4384
|
+
return meta !== void 0 && validateEnvelopeMeta(meta).length === 0;
|
|
4385
|
+
}
|
|
4386
|
+
function classifyBatch(body) {
|
|
4387
|
+
if (body.length === 0) return rejection("jsonrpc-shape", "empty-batch", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: empty JSON-RPC batch"), true);
|
|
4388
|
+
for (const element of body) {
|
|
4389
|
+
if (hasEnvelopeClaim(isPlainObject$2(element) ? element["params"] : void 0)) return rejection("jsonrpc-shape", "batch-with-modern-element", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches may not contain requests for protocol revision 2026-07-28 or later"), true);
|
|
4390
|
+
if (!(isJSONRPCRequest(element) || isJSONRPCNotification(element) || isJSONRPCResultResponse(element) || isJSONRPCErrorResponse(element))) return rejection("jsonrpc-shape", "batch-with-invalid-element", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batch contains an invalid message"), true);
|
|
4391
|
+
}
|
|
4392
|
+
return {
|
|
4393
|
+
kind: "legacy",
|
|
4394
|
+
reason: "batch"
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
function classifyRequestBody(request, body) {
|
|
4398
|
+
const params = body.params;
|
|
4399
|
+
const method = body.method;
|
|
4400
|
+
const headerVersion = request.protocolVersionHeader;
|
|
4401
|
+
const headerNamesModern = headerVersion !== void 0 && isModernProtocolVersion(headerVersion);
|
|
4402
|
+
if (method === "initialize" && !carriesValidModernEnvelopeClaim(params)) {
|
|
4403
|
+
if (headerNamesModern) return crossCheckMismatch("initialize-with-modern-header", headerVersion, "an initialize request (legacy handshake) was sent with a modern MCP-Protocol-Version header");
|
|
4404
|
+
const requestedVersion = isPlainObject$2(params) && typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : void 0;
|
|
4405
|
+
return {
|
|
4406
|
+
kind: "legacy",
|
|
4407
|
+
reason: "initialize",
|
|
4408
|
+
...requestedVersion !== void 0 && { requestedVersion }
|
|
4409
|
+
};
|
|
4410
|
+
}
|
|
4411
|
+
if (hasEnvelopeClaim(params)) {
|
|
4412
|
+
const meta = requestMetaOf(params);
|
|
4413
|
+
const firstIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta))[0];
|
|
4414
|
+
if (firstIssue !== void 0) return rejection("envelope", "envelope-invalid", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid _meta envelope for protocol revision 2026-07-28: ${firstIssue.key}: ${firstIssue.problem}`, { envelope: firstIssue }), true);
|
|
4415
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
4416
|
+
if (headerVersion !== void 0 && claimedVersion !== void 0 && headerVersion !== claimedVersion) return crossCheckMismatch("header-body-version-mismatch", headerVersion, `the body envelope names protocol version ${claimedVersion} but the MCP-Protocol-Version header names ${headerVersion}`);
|
|
4417
|
+
if (request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("method-header-mismatch", request.mcpMethodHeader, `the body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
|
|
4418
|
+
return {
|
|
4419
|
+
kind: "modern",
|
|
4420
|
+
messageKind: "request",
|
|
4421
|
+
message: body,
|
|
4422
|
+
classification: classificationForClaim(claimedVersion)
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4425
|
+
if (headerNamesModern) {
|
|
4426
|
+
const meta = requestMetaOf(params);
|
|
4427
|
+
const missingFromEnvelope = validateEnvelopeMeta(meta ?? {}).filter((issue) => issue.problem === "missing").map((issue) => issue.key);
|
|
4428
|
+
const missing = meta === void 0 ? ["_meta"] : missingFromEnvelope.length > 0 ? missingFromEnvelope : [PROTOCOL_VERSION_META_KEY];
|
|
4429
|
+
return rejection("envelope", "modern-header-without-claim", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid params: the MCP-Protocol-Version header names protocol revision ${headerVersion}, but the request is missing the required per-request envelope key(s): ${missing.join(", ")}`, { envelope: { missing } }), true);
|
|
4430
|
+
}
|
|
4431
|
+
return {
|
|
4432
|
+
kind: "legacy",
|
|
4433
|
+
reason: "no-claim",
|
|
4434
|
+
...headerVersion !== void 0 && { requestedVersion: headerVersion }
|
|
4435
|
+
};
|
|
4436
|
+
}
|
|
4437
|
+
function classifyNotificationBody(request, body) {
|
|
4438
|
+
const params = body.params;
|
|
4439
|
+
const method = body.method;
|
|
4440
|
+
const headerVersion = request.protocolVersionHeader;
|
|
4441
|
+
const headerNamesModern = headerVersion !== void 0 && isModernProtocolVersion(headerVersion);
|
|
4442
|
+
if (hasEnvelopeClaim(params)) {
|
|
4443
|
+
const claimedVersion = envelopeClaimVersion(params);
|
|
4444
|
+
if (claimedVersion === void 0) {
|
|
4445
|
+
const meta = requestMetaOf(params);
|
|
4446
|
+
const claimIssue = (meta === void 0 ? [] : validateEnvelopeMeta(meta)).find((issue) => issue.key === PROTOCOL_VERSION_META_KEY) ?? {
|
|
4447
|
+
key: PROTOCOL_VERSION_META_KEY,
|
|
4448
|
+
problem: "expected a protocol version string"
|
|
4449
|
+
};
|
|
4450
|
+
return rejection("envelope", "notification-envelope-invalid", 400, new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid _meta envelope for protocol revision 2026-07-28: ${claimIssue.key}: ${claimIssue.problem}`, { envelope: claimIssue }), true);
|
|
4451
|
+
}
|
|
4452
|
+
if (headerVersion !== void 0 && headerVersion !== claimedVersion) return crossCheckMismatch("notification-header-body-version-mismatch", headerVersion, `the notification envelope names protocol version ${claimedVersion} but the MCP-Protocol-Version header names ${headerVersion}`);
|
|
4453
|
+
const classification = classificationForClaim(claimedVersion);
|
|
4454
|
+
if (classification.era === "modern" && request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("notification-method-header-mismatch", request.mcpMethodHeader, `the notification body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
|
|
4455
|
+
return {
|
|
4456
|
+
kind: "modern",
|
|
4457
|
+
messageKind: "notification",
|
|
4458
|
+
message: body,
|
|
4459
|
+
classification
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
if (headerNamesModern) {
|
|
4463
|
+
if (request.mcpMethodHeader !== void 0 && request.mcpMethodHeader !== method) return crossCheckMismatch("notification-method-header-mismatch", request.mcpMethodHeader, `the notification body names method ${method} but the Mcp-Method header names ${request.mcpMethodHeader}`);
|
|
4464
|
+
return {
|
|
4465
|
+
kind: "modern",
|
|
4466
|
+
messageKind: "notification",
|
|
4467
|
+
message: body,
|
|
4468
|
+
classification: {
|
|
4469
|
+
era: "modern",
|
|
4470
|
+
revision: headerVersion
|
|
4471
|
+
}
|
|
4472
|
+
};
|
|
4473
|
+
}
|
|
4474
|
+
return {
|
|
4475
|
+
kind: "legacy",
|
|
4476
|
+
reason: "notification",
|
|
4477
|
+
...headerVersion !== void 0 && { requestedVersion: headerVersion }
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
function classifyInboundRequest(request) {
|
|
4481
|
+
request = {
|
|
4482
|
+
...request,
|
|
4483
|
+
...request.protocolVersionHeader !== void 0 && { protocolVersionHeader: stripHttpOws(request.protocolVersionHeader) },
|
|
4484
|
+
...request.mcpMethodHeader !== void 0 && { mcpMethodHeader: stripHttpOws(request.mcpMethodHeader) },
|
|
4485
|
+
...request.mcpNameHeader !== void 0 && { mcpNameHeader: stripHttpOws(request.mcpNameHeader) }
|
|
4486
|
+
};
|
|
4487
|
+
if (request.httpMethod.toUpperCase() !== "POST") return {
|
|
4488
|
+
kind: "legacy",
|
|
4489
|
+
reason: "http-method"
|
|
4490
|
+
};
|
|
4491
|
+
const body = request.body;
|
|
4492
|
+
if (Array.isArray(body)) return classifyBatch(body);
|
|
4493
|
+
if (isJSONRPCResultResponse(body) || isJSONRPCErrorResponse(body)) return {
|
|
4494
|
+
kind: "legacy",
|
|
4495
|
+
reason: "response"
|
|
4496
|
+
};
|
|
4497
|
+
if (isPlainObject$2(body) && isJSONRPCRequest(body)) return classifyRequestBody(request, body);
|
|
4498
|
+
if (isPlainObject$2(body) && isJSONRPCNotification(body)) return classifyNotificationBody(request, body);
|
|
4499
|
+
return rejection("jsonrpc-shape", "invalid-json-rpc-body", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: the request body is not a valid JSON-RPC message"), true);
|
|
4500
|
+
}
|
|
4501
|
+
function modernOnlyStrictRejection(route, supportedVersions) {
|
|
4502
|
+
switch (route.reason) {
|
|
4503
|
+
case "http-method":
|
|
4504
|
+
return rejection("http-method", "modern-only-method-not-allowed", 405, new ProtocolError(-32e3, "Method not allowed."), true);
|
|
4505
|
+
case "batch":
|
|
4506
|
+
return rejection("jsonrpc-shape", "modern-only-batch-not-supported", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC batches are not supported by this endpoint"), true);
|
|
4507
|
+
case "response":
|
|
4508
|
+
return rejection("jsonrpc-shape", "modern-only-response-post", 400, new ProtocolError(ProtocolErrorCode.InvalidRequest, "Bad Request: JSON-RPC responses cannot be posted to this endpoint"), true);
|
|
4509
|
+
case "notification":
|
|
4510
|
+
return;
|
|
4511
|
+
case "initialize":
|
|
4512
|
+
case "no-claim": {
|
|
4513
|
+
const requested = route.requestedVersion;
|
|
4514
|
+
return rejection("era-classification", "modern-only-missing-envelope", 400, requested === void 0 ? new ProtocolError(ProtocolErrorCode.UnsupportedProtocolVersion, "Unsupported protocol version: the request did not name a protocol version", { supported: [...supportedVersions] }) : new UnsupportedProtocolVersionError({
|
|
4515
|
+
supported: [...supportedVersions],
|
|
4516
|
+
requested
|
|
4517
|
+
}), true);
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4520
|
+
}
|
|
4179
4521
|
function parseSchema(schema, data) {
|
|
4180
4522
|
return safeParse(schema, data);
|
|
4181
4523
|
}
|
|
@@ -4737,6 +5079,9 @@ function withRequestStateValue(ctx, value) {
|
|
|
4737
5079
|
};
|
|
4738
5080
|
}
|
|
4739
5081
|
var writeNegotiatedProtocolVersion;
|
|
5082
|
+
function setNegotiatedProtocolVersion(instance, version) {
|
|
5083
|
+
writeNegotiatedProtocolVersion(instance, version);
|
|
5084
|
+
}
|
|
4740
5085
|
var Protocol = class {
|
|
4741
5086
|
_transport;
|
|
4742
5087
|
_requestMessageId = 0;
|
|
@@ -5566,6 +5911,20 @@ var require_content_type = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
5566
5911
|
}
|
|
5567
5912
|
}));
|
|
5568
5913
|
var import_content_type = /* @__PURE__ */ __toESM(require_content_type(), 1);
|
|
5914
|
+
function mediaTypeEssence(header) {
|
|
5915
|
+
if (!header) return;
|
|
5916
|
+
try {
|
|
5917
|
+
return import_content_type.parse(header).type;
|
|
5918
|
+
} catch {
|
|
5919
|
+
const essence = (header.split(";", 1)[0] ?? "").trim().toLowerCase();
|
|
5920
|
+
if (essence === "" || header.slice(essence.length).includes(",")) return;
|
|
5921
|
+
return essence;
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
function isJsonContentType(header) {
|
|
5925
|
+
if (header === "application/json") return true;
|
|
5926
|
+
return mediaTypeEssence(header) === "application/json";
|
|
5927
|
+
}
|
|
5569
5928
|
var STDIO_DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
|
|
5570
5929
|
var ReadBuffer = class {
|
|
5571
5930
|
_buffer;
|
|
@@ -12689,10 +13048,317 @@ function isCompletable(schema) {
|
|
|
12689
13048
|
function getCompleter(schema) {
|
|
12690
13049
|
return schema[COMPLETABLE_SYMBOL]?.complete;
|
|
12691
13050
|
}
|
|
13051
|
+
var DEFAULT_SSE_KEEP_ALIVE_MS = 15e3;
|
|
12692
13052
|
var MAX_TIMER_DELAY_MS = 2 ** 31 - 1;
|
|
13053
|
+
function armSseKeepAlive(intervalMs, onTick) {
|
|
13054
|
+
if (!Number.isFinite(intervalMs) || intervalMs < 1) return;
|
|
13055
|
+
const timer = setInterval(onTick, Math.min(intervalMs, MAX_TIMER_DELAY_MS));
|
|
13056
|
+
timer.unref?.();
|
|
13057
|
+
return timer;
|
|
13058
|
+
}
|
|
13059
|
+
var InMemoryServerEventBus = class {
|
|
13060
|
+
_listeners = /* @__PURE__ */ new Set();
|
|
13061
|
+
/**
|
|
13062
|
+
* @param onerror - Optional callback for errors thrown by listeners
|
|
13063
|
+
* during dispatch.
|
|
13064
|
+
*/
|
|
13065
|
+
constructor(onerror) {
|
|
13066
|
+
this.onerror = onerror;
|
|
13067
|
+
}
|
|
13068
|
+
publish(event) {
|
|
13069
|
+
for (const listener of this._listeners) try {
|
|
13070
|
+
listener(event);
|
|
13071
|
+
} catch (error) {
|
|
13072
|
+
this.onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
13073
|
+
}
|
|
13074
|
+
}
|
|
13075
|
+
subscribe(listener) {
|
|
13076
|
+
this._listeners.add(listener);
|
|
13077
|
+
let live = true;
|
|
13078
|
+
return () => {
|
|
13079
|
+
if (!live) return;
|
|
13080
|
+
live = false;
|
|
13081
|
+
this._listeners.delete(listener);
|
|
13082
|
+
};
|
|
13083
|
+
}
|
|
13084
|
+
/** The number of currently registered listeners (test/introspection only — the routers track capacity via their own open-subscription set). */
|
|
13085
|
+
get listenerCount() {
|
|
13086
|
+
return this._listeners.size;
|
|
13087
|
+
}
|
|
13088
|
+
};
|
|
13089
|
+
function createServerNotifier(bus) {
|
|
13090
|
+
return {
|
|
13091
|
+
toolsChanged: () => bus.publish({ kind: "tools_list_changed" }),
|
|
13092
|
+
promptsChanged: () => bus.publish({ kind: "prompts_list_changed" }),
|
|
13093
|
+
resourcesChanged: () => bus.publish({ kind: "resources_list_changed" }),
|
|
13094
|
+
resourceUpdated: (uri) => bus.publish({
|
|
13095
|
+
kind: "resource_updated",
|
|
13096
|
+
uri
|
|
13097
|
+
})
|
|
13098
|
+
};
|
|
13099
|
+
}
|
|
13100
|
+
function listenFilterAccepts(filter, event) {
|
|
13101
|
+
switch (event.kind) {
|
|
13102
|
+
case "tools_list_changed":
|
|
13103
|
+
return filter.toolsListChanged === true;
|
|
13104
|
+
case "prompts_list_changed":
|
|
13105
|
+
return filter.promptsListChanged === true;
|
|
13106
|
+
case "resources_list_changed":
|
|
13107
|
+
return filter.resourcesListChanged === true;
|
|
13108
|
+
case "resource_updated":
|
|
13109
|
+
return filter.resourceSubscriptions !== void 0 && filter.resourceSubscriptions.includes(event.uri);
|
|
13110
|
+
}
|
|
13111
|
+
}
|
|
13112
|
+
function honoredSubset(requested, capabilities) {
|
|
13113
|
+
const honored = {};
|
|
13114
|
+
const allow = (bit) => capabilities === void 0 || bit === true;
|
|
13115
|
+
if (requested.toolsListChanged === true && allow(capabilities?.tools?.listChanged)) honored.toolsListChanged = true;
|
|
13116
|
+
if (requested.promptsListChanged === true && allow(capabilities?.prompts?.listChanged)) honored.promptsListChanged = true;
|
|
13117
|
+
if (requested.resourcesListChanged === true && allow(capabilities?.resources?.listChanged)) honored.resourcesListChanged = true;
|
|
13118
|
+
if (requested.resourceSubscriptions !== void 0 && requested.resourceSubscriptions.length > 0 && allow(capabilities?.resources?.subscribe)) honored.resourceSubscriptions = [...requested.resourceSubscriptions];
|
|
13119
|
+
return honored;
|
|
13120
|
+
}
|
|
13121
|
+
function serverEventToNotification(event) {
|
|
13122
|
+
switch (event.kind) {
|
|
13123
|
+
case "tools_list_changed":
|
|
13124
|
+
return { method: "notifications/tools/list_changed" };
|
|
13125
|
+
case "prompts_list_changed":
|
|
13126
|
+
return { method: "notifications/prompts/list_changed" };
|
|
13127
|
+
case "resources_list_changed":
|
|
13128
|
+
return { method: "notifications/resources/list_changed" };
|
|
13129
|
+
case "resource_updated":
|
|
13130
|
+
return {
|
|
13131
|
+
method: "notifications/resources/updated",
|
|
13132
|
+
params: { uri: event.uri }
|
|
13133
|
+
};
|
|
13134
|
+
}
|
|
13135
|
+
}
|
|
13136
|
+
var DEFAULT_MAX_SUBSCRIPTIONS = 1024;
|
|
13137
|
+
function jsonRpcError(id, code, message) {
|
|
13138
|
+
return Response.json({
|
|
13139
|
+
jsonrpc: "2.0",
|
|
13140
|
+
error: {
|
|
13141
|
+
code,
|
|
13142
|
+
message
|
|
13143
|
+
},
|
|
13144
|
+
id
|
|
13145
|
+
}, { status: 200 });
|
|
13146
|
+
}
|
|
13147
|
+
function stampSubscriptionId(notification, subscriptionId) {
|
|
13148
|
+
return {
|
|
13149
|
+
method: notification.method,
|
|
13150
|
+
params: {
|
|
13151
|
+
...notification.params,
|
|
13152
|
+
_meta: {
|
|
13153
|
+
...notification.params?._meta,
|
|
13154
|
+
[SUBSCRIPTION_ID_META_KEY]: subscriptionId
|
|
13155
|
+
}
|
|
13156
|
+
}
|
|
13157
|
+
};
|
|
13158
|
+
}
|
|
13159
|
+
function parseListenFilter(message) {
|
|
13160
|
+
const outcome = codecForVersion(MODERN_WIRE_REVISION).validateRequest("subscriptions/listen", message);
|
|
13161
|
+
return outcome.ok ? outcome.value.params?.notifications : void 0;
|
|
13162
|
+
}
|
|
13163
|
+
function createListenRouter(options) {
|
|
13164
|
+
const { bus, onerror } = options;
|
|
13165
|
+
const maxSubscriptions = options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS;
|
|
13166
|
+
const keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
13167
|
+
const open = /* @__PURE__ */ new Set();
|
|
13168
|
+
function serve(message, signal, capabilities, serverInfo) {
|
|
13169
|
+
if (open.size >= maxSubscriptions) {
|
|
13170
|
+
onerror?.(/* @__PURE__ */ new Error(`subscriptions/listen refused: subscription limit reached (${maxSubscriptions})`));
|
|
13171
|
+
return jsonRpcError(message.id, -32603, "Subscription limit reached");
|
|
13172
|
+
}
|
|
13173
|
+
const filter = parseListenFilter(message);
|
|
13174
|
+
if (filter === void 0) return jsonRpcError(message.id, -32602, "Invalid params: 'notifications' is required and must be a valid SubscriptionFilter");
|
|
13175
|
+
const honored = honoredSubset(filter, capabilities);
|
|
13176
|
+
const subscriptionId = message.id;
|
|
13177
|
+
const encoder = new TextEncoder();
|
|
13178
|
+
let controller;
|
|
13179
|
+
let closed = false;
|
|
13180
|
+
let unsubscribe;
|
|
13181
|
+
let keepAliveTimer;
|
|
13182
|
+
let abortCleanup;
|
|
13183
|
+
const writeFrame = (frame) => {
|
|
13184
|
+
if (closed) return;
|
|
13185
|
+
try {
|
|
13186
|
+
controller.enqueue(encoder.encode(frame));
|
|
13187
|
+
} catch (error) {
|
|
13188
|
+
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
13189
|
+
}
|
|
13190
|
+
};
|
|
13191
|
+
const writeNotification = (method, params) => {
|
|
13192
|
+
writeFrame(`event: message
|
|
13193
|
+
data: ${JSON.stringify({
|
|
13194
|
+
jsonrpc: "2.0",
|
|
13195
|
+
method,
|
|
13196
|
+
params
|
|
13197
|
+
})}
|
|
13198
|
+
|
|
13199
|
+
`);
|
|
13200
|
+
};
|
|
13201
|
+
const teardown = (graceful) => {
|
|
13202
|
+
if (closed) return;
|
|
13203
|
+
if (graceful) writeFrame(`event: message
|
|
13204
|
+
data: ${JSON.stringify({
|
|
13205
|
+
jsonrpc: "2.0",
|
|
13206
|
+
id: subscriptionId,
|
|
13207
|
+
result: {
|
|
13208
|
+
resultType: "complete",
|
|
13209
|
+
_meta: {
|
|
13210
|
+
[SUBSCRIPTION_ID_META_KEY]: subscriptionId,
|
|
13211
|
+
[SERVER_INFO_META_KEY]: serverInfo
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
13214
|
+
})}
|
|
13215
|
+
|
|
13216
|
+
`);
|
|
13217
|
+
closed = true;
|
|
13218
|
+
try {
|
|
13219
|
+
unsubscribe?.();
|
|
13220
|
+
} catch (error) {
|
|
13221
|
+
onerror?.(error instanceof Error ? error : new Error(String(error)));
|
|
13222
|
+
}
|
|
13223
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
13224
|
+
abortCleanup?.();
|
|
13225
|
+
open.delete(teardown);
|
|
13226
|
+
try {
|
|
13227
|
+
controller.close();
|
|
13228
|
+
} catch {
|
|
13229
|
+
}
|
|
13230
|
+
};
|
|
13231
|
+
const readable = new ReadableStream({
|
|
13232
|
+
start(streamController) {
|
|
13233
|
+
controller = streamController;
|
|
13234
|
+
const ack = stampSubscriptionId({
|
|
13235
|
+
method: "notifications/subscriptions/acknowledged",
|
|
13236
|
+
params: { notifications: honored }
|
|
13237
|
+
}, subscriptionId);
|
|
13238
|
+
writeNotification(ack.method, ack.params);
|
|
13239
|
+
if (Object.keys(honored).length === 0) {
|
|
13240
|
+
teardown(true);
|
|
13241
|
+
return;
|
|
13242
|
+
}
|
|
13243
|
+
unsubscribe = bus.subscribe((event) => {
|
|
13244
|
+
if (closed || !listenFilterAccepts(honored, event)) return;
|
|
13245
|
+
const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
|
|
13246
|
+
writeNotification(note.method, note.params);
|
|
13247
|
+
});
|
|
13248
|
+
keepAliveTimer = armSseKeepAlive(keepAliveMs, () => writeFrame(": keepalive\n\n"));
|
|
13249
|
+
open.add(teardown);
|
|
13250
|
+
},
|
|
13251
|
+
cancel() {
|
|
13252
|
+
teardown(false);
|
|
13253
|
+
}
|
|
13254
|
+
});
|
|
13255
|
+
if (signal !== void 0) if (signal.aborted) teardown(false);
|
|
13256
|
+
else {
|
|
13257
|
+
const onAbort = () => teardown(false);
|
|
13258
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
13259
|
+
abortCleanup = () => signal.removeEventListener("abort", onAbort);
|
|
13260
|
+
}
|
|
13261
|
+
return new Response(readable, {
|
|
13262
|
+
status: 200,
|
|
13263
|
+
headers: {
|
|
13264
|
+
"Content-Type": "text/event-stream",
|
|
13265
|
+
"Cache-Control": "no-cache, no-transform",
|
|
13266
|
+
Connection: "keep-alive",
|
|
13267
|
+
"X-Accel-Buffering": "no"
|
|
13268
|
+
}
|
|
13269
|
+
});
|
|
13270
|
+
}
|
|
13271
|
+
return {
|
|
13272
|
+
serve,
|
|
13273
|
+
closeAll() {
|
|
13274
|
+
for (const teardown of open) teardown(true);
|
|
13275
|
+
},
|
|
13276
|
+
get openCount() {
|
|
13277
|
+
return open.size;
|
|
13278
|
+
}
|
|
13279
|
+
};
|
|
13280
|
+
}
|
|
13281
|
+
function headerQuotedValue(value) {
|
|
13282
|
+
return value.replaceAll(/[\\"]/g, String.raw`\$&`).replaceAll(/[^\u0020-\u007E]/g, " ");
|
|
13283
|
+
}
|
|
13284
|
+
function buildWwwAuthenticateHeader(errorCode, description, requiredScopes, resourceMetadataUrl) {
|
|
13285
|
+
let header = `Bearer error="${headerQuotedValue(errorCode)}", error_description="${headerQuotedValue(description)}"`;
|
|
13286
|
+
if (requiredScopes.length > 0) header += `, scope="${headerQuotedValue(requiredScopes.join(" "))}"`;
|
|
13287
|
+
if (resourceMetadataUrl) header += `, resource_metadata="${headerQuotedValue(resourceMetadataUrl)}"`;
|
|
13288
|
+
return header;
|
|
13289
|
+
}
|
|
13290
|
+
function bearerAuthChallengeResponse(error, options) {
|
|
13291
|
+
const { requiredScopes = [], resourceMetadataUrl } = options ?? {};
|
|
13292
|
+
if (!(error instanceof OAuthError)) {
|
|
13293
|
+
const serverError = new OAuthError(OAuthErrorCode.ServerError, "Internal Server Error");
|
|
13294
|
+
return Response.json(serverError.toResponseObject(), { status: 500 });
|
|
13295
|
+
}
|
|
13296
|
+
switch (error.code) {
|
|
13297
|
+
case OAuthErrorCode.InvalidToken: {
|
|
13298
|
+
const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
|
|
13299
|
+
return Response.json(error.toResponseObject(), {
|
|
13300
|
+
status: 401,
|
|
13301
|
+
headers: { "WWW-Authenticate": challenge }
|
|
13302
|
+
});
|
|
13303
|
+
}
|
|
13304
|
+
case OAuthErrorCode.InsufficientScope: {
|
|
13305
|
+
const challenge = buildWwwAuthenticateHeader(error.code, error.message, requiredScopes, resourceMetadataUrl);
|
|
13306
|
+
return Response.json(error.toResponseObject(), {
|
|
13307
|
+
status: 403,
|
|
13308
|
+
headers: { "WWW-Authenticate": challenge }
|
|
13309
|
+
});
|
|
13310
|
+
}
|
|
13311
|
+
case OAuthErrorCode.ServerError:
|
|
13312
|
+
return Response.json(error.toResponseObject(), { status: 500 });
|
|
13313
|
+
default:
|
|
13314
|
+
return Response.json(error.toResponseObject(), { status: 400 });
|
|
13315
|
+
}
|
|
13316
|
+
}
|
|
13317
|
+
function getOAuthProtectedResourceMetadataUrl(serverUrl) {
|
|
13318
|
+
const metadataUrl = new URL(serverUrl);
|
|
13319
|
+
metadataUrl.pathname = protectedResourceMetadataPath(serverUrl);
|
|
13320
|
+
metadataUrl.hash = "";
|
|
13321
|
+
return metadataUrl.href;
|
|
13322
|
+
}
|
|
13323
|
+
function protectedResourceMetadataPath(resourceServerUrl) {
|
|
13324
|
+
const rsPath = stripTrailingSlash(resourceServerUrl.pathname);
|
|
13325
|
+
return `/.well-known/oauth-protected-resource${rsPath === "/" ? "" : rsPath}`;
|
|
13326
|
+
}
|
|
13327
|
+
function stripTrailingSlash(path) {
|
|
13328
|
+
return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
13329
|
+
}
|
|
12693
13330
|
function supportsScopeChallengeResolver(transport) {
|
|
12694
13331
|
return typeof transport === "object" && transport !== null && "setScopeChallengeResolver" in transport && typeof transport.setScopeChallengeResolver === "function";
|
|
12695
13332
|
}
|
|
13333
|
+
function assertScope(scope, location) {
|
|
13334
|
+
if (typeof scope !== "string" || !/^[\u0021\u0023-\u005B\u005D-\u007E]+$/.test(scope)) throw new TypeError(`${location} must satisfy the OAuth scope-token grammar`);
|
|
13335
|
+
}
|
|
13336
|
+
function validateScopeChallenge(challenge) {
|
|
13337
|
+
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");
|
|
13338
|
+
for (const [index, scope] of challenge.scopes.entries()) assertScope(scope, `scope challenge scopes[${index}]`);
|
|
13339
|
+
if (challenge.errorDescription !== void 0 && typeof challenge.errorDescription !== "string") throw new TypeError("scope challenge errorDescription must be a string");
|
|
13340
|
+
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");
|
|
13341
|
+
return challenge;
|
|
13342
|
+
}
|
|
13343
|
+
async function findScopeChallenge(requests, authInfo, resolve) {
|
|
13344
|
+
for (const request of requests) {
|
|
13345
|
+
const challenge = await resolve({
|
|
13346
|
+
request,
|
|
13347
|
+
...authInfo !== void 0 && { authInfo }
|
|
13348
|
+
});
|
|
13349
|
+
if (challenge !== void 0) return validateScopeChallenge(challenge);
|
|
13350
|
+
}
|
|
13351
|
+
}
|
|
13352
|
+
function scopeChallengeResourceMetadataUrl(authInfo) {
|
|
13353
|
+
if (authInfo?.resourceMetadataUrl !== void 0) return authInfo.resourceMetadataUrl;
|
|
13354
|
+
if (authInfo?.resource?.protocol === "https:" || authInfo?.resource?.protocol === "http:") return getOAuthProtectedResourceMetadataUrl(authInfo.resource);
|
|
13355
|
+
}
|
|
13356
|
+
function createScopeChallengeResponse(challenge, resourceMetadataUrl) {
|
|
13357
|
+
return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InsufficientScope, challenge.errorDescription ?? "Insufficient scope"), {
|
|
13358
|
+
requiredScopes: [...challenge.scopes],
|
|
13359
|
+
resourceMetadataUrl
|
|
13360
|
+
});
|
|
13361
|
+
}
|
|
12696
13362
|
var DEFAULT_LEGACY_SHIM_MAX_ROUNDS = 8;
|
|
12697
13363
|
var DEFAULT_LEGACY_SHIM_ROUND_TIMEOUT_MS = 6e5;
|
|
12698
13364
|
function resolveLegacyShimOptions(options) {
|
|
@@ -12830,6 +13496,15 @@ var INPUT_REQUIRED_CAPABLE_METHODS = /* @__PURE__ */ new Set([
|
|
|
12830
13496
|
var writeClientIdentity;
|
|
12831
13497
|
var installDiscoverHandler;
|
|
12832
13498
|
var readServerIdentity;
|
|
13499
|
+
function seedClientIdentityFromEnvelope(server, identity) {
|
|
13500
|
+
writeClientIdentity(server, identity);
|
|
13501
|
+
}
|
|
13502
|
+
function installModernOnlyHandlers(server, servedModernVersions) {
|
|
13503
|
+
installDiscoverHandler(server, servedModernVersions);
|
|
13504
|
+
}
|
|
13505
|
+
function serverIdentityOf(server) {
|
|
13506
|
+
return readServerIdentity(server);
|
|
13507
|
+
}
|
|
12833
13508
|
var Server = class extends Protocol {
|
|
12834
13509
|
_clientCapabilities;
|
|
12835
13510
|
_clientVersion;
|
|
@@ -14090,26 +14765,1295 @@ function unwrapOptionalSchema(schema) {
|
|
|
14090
14765
|
}
|
|
14091
14766
|
|
|
14092
14767
|
// ../../node_modules/.pnpm/@modelcontextprotocol+server@2.3.1/node_modules/@modelcontextprotocol/server/dist/index.mjs
|
|
14093
|
-
var
|
|
14768
|
+
var PerRequestHTTPServerTransport = class {
|
|
14769
|
+
onclose;
|
|
14770
|
+
onerror;
|
|
14771
|
+
onmessage;
|
|
14772
|
+
_classification;
|
|
14773
|
+
_responseMode;
|
|
14774
|
+
_started = false;
|
|
14775
|
+
_used = false;
|
|
14776
|
+
_closed = false;
|
|
14777
|
+
_terminalDelivered = false;
|
|
14778
|
+
/**
|
|
14779
|
+
* `true` only while the inbound message is being delivered synchronously
|
|
14780
|
+
* to the connected protocol layer. The pre-handler gates (the era
|
|
14781
|
+
* registry gate, the edge→instance handoff check, the missing-handler
|
|
14782
|
+
* rejection) answer inside this window; request handlers always run
|
|
14783
|
+
* after it (the protocol layer defers them to a microtask). An error
|
|
14784
|
+
* sent inside the window is therefore ladder-originated, and an error
|
|
14785
|
+
* sent after it is handler-produced.
|
|
14786
|
+
*/
|
|
14787
|
+
_dispatchWindowOpen = false;
|
|
14788
|
+
_requestId;
|
|
14789
|
+
_deferredResponse;
|
|
14790
|
+
_sse;
|
|
14791
|
+
_abortCleanup;
|
|
14792
|
+
_keepAliveMs;
|
|
14793
|
+
constructor(options) {
|
|
14794
|
+
this._classification = options.classification;
|
|
14795
|
+
this._responseMode = options.responseMode ?? "auto";
|
|
14796
|
+
this._keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
14797
|
+
}
|
|
14798
|
+
async start() {
|
|
14799
|
+
if (this._started) throw new Error("PerRequestHTTPServerTransport is already started");
|
|
14800
|
+
this._started = true;
|
|
14801
|
+
}
|
|
14802
|
+
/**
|
|
14803
|
+
* Serves the single exchange: delivers the classified message to the
|
|
14804
|
+
* connected server instance and resolves with the HTTP response.
|
|
14805
|
+
*
|
|
14806
|
+
* Throws when called a second time (the transport is strictly
|
|
14807
|
+
* single-use), or before a server has been connected to the transport.
|
|
14808
|
+
* The returned promise rejects with a connection-closed error when the
|
|
14809
|
+
* transport is closed before a response was produced (for example because
|
|
14810
|
+
* the client disconnected).
|
|
14811
|
+
*/
|
|
14812
|
+
async handleMessage(message, extra) {
|
|
14813
|
+
if (this._used) throw new Error("PerRequestHTTPServerTransport serves exactly one exchange; construct a new transport per request");
|
|
14814
|
+
if (!this._started || this.onmessage === void 0) throw new Error("PerRequestHTTPServerTransport is not connected: connect a server to this transport before handling a message");
|
|
14815
|
+
if (this._closed) throw new Error("PerRequestHTTPServerTransport is closed");
|
|
14816
|
+
this._used = true;
|
|
14817
|
+
const signal = extra?.request?.signal;
|
|
14818
|
+
if (signal?.aborted) {
|
|
14819
|
+
await this.close();
|
|
14820
|
+
throw new SdkError(SdkErrorCode.ConnectionClosed, "The request was aborted before it could be handled");
|
|
14821
|
+
}
|
|
14822
|
+
const messageExtra = {
|
|
14823
|
+
classification: this._classification,
|
|
14824
|
+
...extra?.request !== void 0 && { request: extra.request },
|
|
14825
|
+
...extra?.authInfo !== void 0 && { authInfo: extra.authInfo }
|
|
14826
|
+
};
|
|
14827
|
+
if (isJSONRPCRequest(message)) {
|
|
14828
|
+
this._requestId = message.id;
|
|
14829
|
+
let resolve;
|
|
14830
|
+
let reject;
|
|
14831
|
+
const promise = new Promise((promiseResolve, promiseReject) => {
|
|
14832
|
+
resolve = promiseResolve;
|
|
14833
|
+
reject = promiseReject;
|
|
14834
|
+
});
|
|
14835
|
+
this._deferredResponse = {
|
|
14836
|
+
promise,
|
|
14837
|
+
resolve,
|
|
14838
|
+
reject,
|
|
14839
|
+
settled: false
|
|
14840
|
+
};
|
|
14841
|
+
if (signal !== void 0) {
|
|
14842
|
+
const onAbort = () => void this.close();
|
|
14843
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
14844
|
+
this._abortCleanup = () => signal.removeEventListener("abort", onAbort);
|
|
14845
|
+
}
|
|
14846
|
+
this._dispatchWindowOpen = true;
|
|
14847
|
+
try {
|
|
14848
|
+
this.onmessage(message, messageExtra);
|
|
14849
|
+
} finally {
|
|
14850
|
+
this._dispatchWindowOpen = false;
|
|
14851
|
+
}
|
|
14852
|
+
if (this._responseMode === "sse" && !this._closed && !this._deferredResponse.settled) this.upgradeToSse();
|
|
14853
|
+
return promise;
|
|
14854
|
+
}
|
|
14855
|
+
this.onmessage(message, messageExtra);
|
|
14856
|
+
return new Response(null, { status: 202 });
|
|
14857
|
+
}
|
|
14858
|
+
async send(message, options) {
|
|
14859
|
+
if (this._closed) return;
|
|
14860
|
+
const isResponse = isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message);
|
|
14861
|
+
const relatedId = isResponse ? message.id : options?.relatedRequestId;
|
|
14862
|
+
if (this._requestId === void 0 || relatedId === void 0 || relatedId !== this._requestId) {
|
|
14863
|
+
if (isResponse) this.onerror?.(/* @__PURE__ */ new Error(`Received a response for an unknown request id: ${String(message.id)}`));
|
|
14864
|
+
return;
|
|
14865
|
+
}
|
|
14866
|
+
if (isResponse) {
|
|
14867
|
+
if (this._terminalDelivered) return;
|
|
14868
|
+
this._terminalDelivered = true;
|
|
14869
|
+
const errorCode = isJSONRPCErrorResponse(message) ? message.error.code : void 0;
|
|
14870
|
+
const ladderStatus = errorCode !== void 0 && (this._dispatchWindowOpen || errorCode === ProtocolErrorCode.MissingRequiredClientCapability) ? LADDER_ERROR_HTTP_STATUS[errorCode] : void 0;
|
|
14871
|
+
if (ladderStatus !== void 0 && this._sse === void 0) {
|
|
14872
|
+
this.settleResponse(Response.json(message, {
|
|
14873
|
+
status: ladderStatus,
|
|
14874
|
+
headers: { "Content-Type": "application/json" }
|
|
14875
|
+
}));
|
|
14876
|
+
queueMicrotask(() => void this.close());
|
|
14877
|
+
return;
|
|
14878
|
+
}
|
|
14879
|
+
if (this._sse !== void 0 || this._responseMode === "sse") {
|
|
14880
|
+
if (this._sse === void 0) this.upgradeToSse();
|
|
14881
|
+
this.writeMessageFrame(message);
|
|
14882
|
+
this.finalizeStream();
|
|
14883
|
+
return;
|
|
14884
|
+
}
|
|
14885
|
+
this.settleResponse(Response.json(message, {
|
|
14886
|
+
status: 200,
|
|
14887
|
+
headers: { "Content-Type": "application/json" }
|
|
14888
|
+
}));
|
|
14889
|
+
queueMicrotask(() => void this.close());
|
|
14890
|
+
return;
|
|
14891
|
+
}
|
|
14892
|
+
if (this._responseMode === "json") return;
|
|
14893
|
+
if (this._sse === void 0) this.upgradeToSse();
|
|
14894
|
+
this.writeMessageFrame(message);
|
|
14895
|
+
}
|
|
14896
|
+
/**
|
|
14897
|
+
* Writes an SSE comment frame (a keep-alive heartbeat). Dropped when the
|
|
14898
|
+
* exchange is not currently streaming.
|
|
14899
|
+
*/
|
|
14900
|
+
writeCommentFrame(comment) {
|
|
14901
|
+
if (this._closed || this._sse === void 0 || this._sse.closed) return;
|
|
14902
|
+
const frame = comment.split("\n").map((line) => `: ${line}`).join("\n");
|
|
14903
|
+
this.writeFrame(`${frame}
|
|
14094
14904
|
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14905
|
+
`);
|
|
14906
|
+
}
|
|
14907
|
+
async close() {
|
|
14908
|
+
if (this._closed) return;
|
|
14909
|
+
this._closed = true;
|
|
14910
|
+
this._abortCleanup?.();
|
|
14911
|
+
this._abortCleanup = void 0;
|
|
14912
|
+
if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
|
|
14913
|
+
if (this._sse !== void 0 && !this._sse.closed) {
|
|
14914
|
+
this._sse.closed = true;
|
|
14915
|
+
try {
|
|
14916
|
+
this._sse.controller.close();
|
|
14917
|
+
} catch {
|
|
14918
|
+
}
|
|
14919
|
+
}
|
|
14920
|
+
if (this._deferredResponse !== void 0 && !this._deferredResponse.settled) {
|
|
14921
|
+
this._deferredResponse.settled = true;
|
|
14922
|
+
this._deferredResponse.reject(new SdkError(SdkErrorCode.ConnectionClosed, "Connection closed before a response was produced"));
|
|
14923
|
+
}
|
|
14924
|
+
this.onclose?.();
|
|
14925
|
+
}
|
|
14926
|
+
settleResponse(response) {
|
|
14927
|
+
if (this._deferredResponse === void 0 || this._deferredResponse.settled) return;
|
|
14928
|
+
this._deferredResponse.settled = true;
|
|
14929
|
+
this._deferredResponse.resolve(response);
|
|
14930
|
+
}
|
|
14931
|
+
upgradeToSse() {
|
|
14932
|
+
let controller;
|
|
14933
|
+
const readable = new ReadableStream({
|
|
14934
|
+
start: (streamController) => {
|
|
14935
|
+
controller = streamController;
|
|
14936
|
+
},
|
|
14937
|
+
cancel: () => {
|
|
14938
|
+
this.close();
|
|
14939
|
+
}
|
|
14940
|
+
});
|
|
14941
|
+
this._sse = {
|
|
14942
|
+
controller,
|
|
14943
|
+
encoder: new TextEncoder(),
|
|
14944
|
+
closed: false
|
|
14945
|
+
};
|
|
14946
|
+
this._sse.keepAliveTimer = armSseKeepAlive(this._keepAliveMs, () => this.writeCommentFrame("keepalive"));
|
|
14947
|
+
this.settleResponse(new Response(readable, {
|
|
14948
|
+
status: 200,
|
|
14949
|
+
headers: {
|
|
14950
|
+
"Content-Type": "text/event-stream",
|
|
14951
|
+
"Cache-Control": "no-cache, no-transform",
|
|
14952
|
+
Connection: "keep-alive",
|
|
14953
|
+
"X-Accel-Buffering": "no"
|
|
14954
|
+
}
|
|
14955
|
+
}));
|
|
14956
|
+
}
|
|
14957
|
+
finalizeStream() {
|
|
14958
|
+
if (this._sse?.keepAliveTimer !== void 0) clearInterval(this._sse.keepAliveTimer);
|
|
14959
|
+
if (this._sse !== void 0 && !this._sse.closed) {
|
|
14960
|
+
this._sse.closed = true;
|
|
14961
|
+
try {
|
|
14962
|
+
this._sse.controller.close();
|
|
14963
|
+
} catch {
|
|
14964
|
+
}
|
|
14965
|
+
}
|
|
14966
|
+
queueMicrotask(() => void this.close());
|
|
14967
|
+
}
|
|
14968
|
+
writeMessageFrame(message) {
|
|
14969
|
+
this.writeFrame(`event: message
|
|
14970
|
+
data: ${JSON.stringify(message)}
|
|
14099
14971
|
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14972
|
+
`);
|
|
14973
|
+
}
|
|
14974
|
+
writeFrame(frame) {
|
|
14975
|
+
if (this._sse === void 0 || this._sse.closed) return;
|
|
14976
|
+
try {
|
|
14977
|
+
this._sse.controller.enqueue(this._sse.encoder.encode(frame));
|
|
14978
|
+
} catch (error) {
|
|
14979
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Failed to write to the response stream: ${error}`));
|
|
14980
|
+
}
|
|
14981
|
+
}
|
|
14110
14982
|
};
|
|
14111
|
-
function
|
|
14112
|
-
|
|
14983
|
+
async function invoke(server, message, ctx) {
|
|
14984
|
+
const transport = new PerRequestHTTPServerTransport({
|
|
14985
|
+
classification: ctx.classification,
|
|
14986
|
+
...ctx.responseMode !== void 0 && { responseMode: ctx.responseMode },
|
|
14987
|
+
...ctx.keepAliveMs !== void 0 && { keepAliveMs: ctx.keepAliveMs }
|
|
14988
|
+
});
|
|
14989
|
+
await server.connect(transport);
|
|
14990
|
+
return transport.handleMessage(message, {
|
|
14991
|
+
...ctx.request !== void 0 && { request: ctx.request },
|
|
14992
|
+
...ctx.authInfo !== void 0 && { authInfo: ctx.authInfo }
|
|
14993
|
+
});
|
|
14994
|
+
}
|
|
14995
|
+
var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
|
|
14996
|
+
var MAX_BATCH_SIZE = 100;
|
|
14997
|
+
function requestBodyTooLargeMessage(maxBytes) {
|
|
14998
|
+
return `Payload Too Large: Request body must not exceed ${maxBytes} bytes`;
|
|
14999
|
+
}
|
|
15000
|
+
function resolveMaxRequestBodySize(value) {
|
|
15001
|
+
if (value === void 0) return DEFAULT_MAX_REQUEST_BODY_SIZE;
|
|
15002
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) throw new RangeError(`maxRequestBodySize must be a positive number of bytes, got ${String(value)}`);
|
|
15003
|
+
return value;
|
|
15004
|
+
}
|
|
15005
|
+
async function readRequestBody(request, maxBytes = DEFAULT_MAX_REQUEST_BODY_SIZE) {
|
|
15006
|
+
if (Number(request.headers.get("content-length")) > maxBytes) return { tooLarge: true };
|
|
15007
|
+
if (request.body === null) return {
|
|
15008
|
+
tooLarge: false,
|
|
15009
|
+
text: ""
|
|
15010
|
+
};
|
|
15011
|
+
const reader = request.body.getReader();
|
|
15012
|
+
const decoder = new TextDecoder();
|
|
15013
|
+
let received = 0;
|
|
15014
|
+
let text = "";
|
|
15015
|
+
try {
|
|
15016
|
+
for (; ; ) {
|
|
15017
|
+
const { done, value } = await reader.read();
|
|
15018
|
+
if (done) break;
|
|
15019
|
+
received += value.byteLength;
|
|
15020
|
+
if (received > maxBytes) return { tooLarge: true };
|
|
15021
|
+
text += decoder.decode(value, { stream: true });
|
|
15022
|
+
}
|
|
15023
|
+
} finally {
|
|
15024
|
+
reader.releaseLock();
|
|
15025
|
+
}
|
|
15026
|
+
return {
|
|
15027
|
+
tooLarge: false,
|
|
15028
|
+
text: text + decoder.decode()
|
|
15029
|
+
};
|
|
15030
|
+
}
|
|
15031
|
+
var WebStandardStreamableHTTPServerTransport = class {
|
|
15032
|
+
sessionIdGenerator;
|
|
15033
|
+
_started = false;
|
|
15034
|
+
_hasHandledRequest = false;
|
|
15035
|
+
_closed = false;
|
|
15036
|
+
_streamMapping = /* @__PURE__ */ new Map();
|
|
15037
|
+
_requestToStreamMapping = /* @__PURE__ */ new Map();
|
|
15038
|
+
_requestResponseMap = /* @__PURE__ */ new Map();
|
|
15039
|
+
_initialized = false;
|
|
15040
|
+
_enableJsonResponse = false;
|
|
15041
|
+
_standaloneSseStreamId = "_GET_stream";
|
|
15042
|
+
_eventStore;
|
|
15043
|
+
_onsessioninitialized;
|
|
15044
|
+
_onsessionclosed;
|
|
15045
|
+
_allowedHosts;
|
|
15046
|
+
_allowedOrigins;
|
|
15047
|
+
_enableDnsRebindingProtection;
|
|
15048
|
+
_retryInterval;
|
|
15049
|
+
_supportedProtocolVersions;
|
|
15050
|
+
_keepAliveMs;
|
|
15051
|
+
_maxRequestBodySize;
|
|
15052
|
+
_scopeChallengeResolver;
|
|
15053
|
+
sessionId;
|
|
15054
|
+
onclose;
|
|
15055
|
+
onerror;
|
|
15056
|
+
onmessage;
|
|
15057
|
+
constructor(options = {}) {
|
|
15058
|
+
this.sessionIdGenerator = options.sessionIdGenerator;
|
|
15059
|
+
this._enableJsonResponse = options.enableJsonResponse ?? false;
|
|
15060
|
+
this._eventStore = options.eventStore;
|
|
15061
|
+
this._onsessioninitialized = options.onsessioninitialized;
|
|
15062
|
+
this._onsessionclosed = options.onsessionclosed;
|
|
15063
|
+
this._allowedHosts = options.allowedHosts;
|
|
15064
|
+
this._allowedOrigins = options.allowedOrigins;
|
|
15065
|
+
this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false;
|
|
15066
|
+
this._retryInterval = options.retryInterval;
|
|
15067
|
+
this._supportedProtocolVersions = options.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;
|
|
15068
|
+
this._keepAliveMs = options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS;
|
|
15069
|
+
this._maxRequestBodySize = resolveMaxRequestBodySize(options.maxRequestBodySize);
|
|
15070
|
+
}
|
|
15071
|
+
startKeepAlive(controller, encoder) {
|
|
15072
|
+
if (this._closed) return void 0;
|
|
15073
|
+
const timer = armSseKeepAlive(this._keepAliveMs, () => {
|
|
15074
|
+
try {
|
|
15075
|
+
controller.enqueue(encoder.encode(": keepalive\n\n"));
|
|
15076
|
+
} catch {
|
|
15077
|
+
if (timer !== void 0) clearInterval(timer);
|
|
15078
|
+
}
|
|
15079
|
+
});
|
|
15080
|
+
return timer;
|
|
15081
|
+
}
|
|
15082
|
+
/** Sets the scope challenge resolver for parsed JSON-RPC requests. */
|
|
15083
|
+
setScopeChallengeResolver(resolver) {
|
|
15084
|
+
this._scopeChallengeResolver = resolver;
|
|
15085
|
+
}
|
|
15086
|
+
/**
|
|
15087
|
+
* Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op
|
|
15088
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
15089
|
+
*/
|
|
15090
|
+
async start() {
|
|
15091
|
+
if (this._started) throw new Error("Transport already started");
|
|
15092
|
+
this._started = true;
|
|
15093
|
+
}
|
|
15094
|
+
/**
|
|
15095
|
+
* Sets the supported protocol versions for header validation.
|
|
15096
|
+
* Called by the server during {@linkcode server/server.Server.connect | connect()} to pass its supported versions.
|
|
15097
|
+
*/
|
|
15098
|
+
setSupportedProtocolVersions(versions) {
|
|
15099
|
+
this._supportedProtocolVersions = versions;
|
|
15100
|
+
}
|
|
15101
|
+
/**
|
|
15102
|
+
* Helper to create a JSON error response
|
|
15103
|
+
*/
|
|
15104
|
+
createJsonErrorResponse(status, code, message, options) {
|
|
15105
|
+
const error = {
|
|
15106
|
+
code,
|
|
15107
|
+
message
|
|
15108
|
+
};
|
|
15109
|
+
if (options?.data !== void 0) error.data = options.data;
|
|
15110
|
+
return Response.json({
|
|
15111
|
+
jsonrpc: "2.0",
|
|
15112
|
+
error,
|
|
15113
|
+
id: null
|
|
15114
|
+
}, {
|
|
15115
|
+
status,
|
|
15116
|
+
headers: {
|
|
15117
|
+
"Content-Type": "application/json",
|
|
15118
|
+
...options?.headers
|
|
15119
|
+
}
|
|
15120
|
+
});
|
|
15121
|
+
}
|
|
15122
|
+
async _checkScopeChallenge(messages, authInfo) {
|
|
15123
|
+
if (!this._scopeChallengeResolver) return;
|
|
15124
|
+
const challenge = await findScopeChallenge(messages.filter((message) => isJSONRPCRequest(message)), authInfo, this._scopeChallengeResolver);
|
|
15125
|
+
return challenge === void 0 ? void 0 : createScopeChallengeResponse(challenge, scopeChallengeResourceMetadataUrl(authInfo));
|
|
15126
|
+
}
|
|
15127
|
+
/**
|
|
15128
|
+
* Validates request headers for DNS rebinding protection.
|
|
15129
|
+
* @returns Error response if validation fails, `undefined` if validation passes.
|
|
15130
|
+
*/
|
|
15131
|
+
validateRequestHeaders(req) {
|
|
15132
|
+
if (!this._enableDnsRebindingProtection) return;
|
|
15133
|
+
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
15134
|
+
const hostHeader = req.headers.get("host");
|
|
15135
|
+
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
15136
|
+
const error = `Invalid Host header: ${hostHeader}`;
|
|
15137
|
+
this.onerror?.(new Error(error));
|
|
15138
|
+
return this.createJsonErrorResponse(403, -32e3, error);
|
|
15139
|
+
}
|
|
15140
|
+
}
|
|
15141
|
+
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
15142
|
+
const originHeader = req.headers.get("origin");
|
|
15143
|
+
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
15144
|
+
const error = `Invalid Origin header: ${originHeader}`;
|
|
15145
|
+
this.onerror?.(new Error(error));
|
|
15146
|
+
return this.createJsonErrorResponse(403, -32e3, error);
|
|
15147
|
+
}
|
|
15148
|
+
}
|
|
15149
|
+
}
|
|
15150
|
+
/**
|
|
15151
|
+
* Handles an incoming HTTP request, whether `GET`, `POST`, or `DELETE`
|
|
15152
|
+
* Returns a `Response` object (Web Standard)
|
|
15153
|
+
*/
|
|
15154
|
+
async handleRequest(req, options) {
|
|
15155
|
+
if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
15156
|
+
if (!this.sessionIdGenerator && this._hasHandledRequest) throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");
|
|
15157
|
+
this._hasHandledRequest = true;
|
|
15158
|
+
const validationError = this.validateRequestHeaders(req);
|
|
15159
|
+
if (validationError) return validationError;
|
|
15160
|
+
switch (req.method) {
|
|
15161
|
+
case "POST":
|
|
15162
|
+
return this.handlePostRequest(req, options);
|
|
15163
|
+
case "GET":
|
|
15164
|
+
return this.handleGetRequest(req);
|
|
15165
|
+
case "DELETE":
|
|
15166
|
+
return this.handleDeleteRequest(req);
|
|
15167
|
+
default:
|
|
15168
|
+
return this.handleUnsupportedRequest();
|
|
15169
|
+
}
|
|
15170
|
+
}
|
|
15171
|
+
/**
|
|
15172
|
+
* Returns true if the client's protocol version supports empty SSE data in
|
|
15173
|
+
* priming events (the fix shipped with protocol version `2025-11-25`).
|
|
15174
|
+
*
|
|
15175
|
+
* The version is checked for membership in this transport instance's
|
|
15176
|
+
* supported protocol versions rather than with an open-ended
|
|
15177
|
+
* `>= '2025-11-25'` comparison: the value may come from an `initialize`
|
|
15178
|
+
* request body, which (unlike the `MCP-Protocol-Version` header) is not
|
|
15179
|
+
* validated against `supportedProtocolVersions` before reaching this
|
|
15180
|
+
* check. An unknown future version string must not silently enable
|
|
15181
|
+
* behavior reserved for versions this transport actually supports.
|
|
15182
|
+
*/
|
|
15183
|
+
supportsEmptySSEData(protocolVersion) {
|
|
15184
|
+
return this._supportedProtocolVersions.includes(protocolVersion) && protocolVersion >= "2025-11-25";
|
|
15185
|
+
}
|
|
15186
|
+
/**
|
|
15187
|
+
* Writes a priming event to establish resumption capability.
|
|
15188
|
+
* Only sends if `eventStore` is configured (opt-in for resumability) and
|
|
15189
|
+
* the client's protocol version supports empty SSE data (a supported
|
|
15190
|
+
* version that is >= `2025-11-25`).
|
|
15191
|
+
*/
|
|
15192
|
+
async writePrimingEvent(controller, encoder, streamId, protocolVersion) {
|
|
15193
|
+
if (!this._eventStore) return;
|
|
15194
|
+
if (!this.supportsEmptySSEData(protocolVersion)) return;
|
|
15195
|
+
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
15196
|
+
let primingEvent = `id: ${primingEventId}
|
|
15197
|
+
data:
|
|
15198
|
+
|
|
15199
|
+
`;
|
|
15200
|
+
if (this._retryInterval !== void 0) primingEvent = `id: ${primingEventId}
|
|
15201
|
+
retry: ${this._retryInterval}
|
|
15202
|
+
data:
|
|
15203
|
+
|
|
15204
|
+
`;
|
|
15205
|
+
controller.enqueue(encoder.encode(primingEvent));
|
|
15206
|
+
}
|
|
15207
|
+
/**
|
|
15208
|
+
* Handles `GET` requests for SSE stream
|
|
15209
|
+
*/
|
|
15210
|
+
async handleGetRequest(req) {
|
|
15211
|
+
if (!req.headers.get("accept")?.includes("text/event-stream")) {
|
|
15212
|
+
this.onerror?.(/* @__PURE__ */ new Error("Not Acceptable: Client must accept text/event-stream"));
|
|
15213
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept text/event-stream");
|
|
15214
|
+
}
|
|
15215
|
+
const sessionError = this.validateSession(req);
|
|
15216
|
+
if (sessionError) return sessionError;
|
|
15217
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
15218
|
+
if (protocolError) return protocolError;
|
|
15219
|
+
if (this._eventStore) {
|
|
15220
|
+
const lastEventId = req.headers.get("last-event-id");
|
|
15221
|
+
if (lastEventId) return this.replayEvents(lastEventId);
|
|
15222
|
+
}
|
|
15223
|
+
if (this._streamMapping.get(this._standaloneSseStreamId) !== void 0) {
|
|
15224
|
+
this.onerror?.(/* @__PURE__ */ new Error("Conflict: Only one SSE stream is allowed per session"));
|
|
15225
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Only one SSE stream is allowed per session");
|
|
15226
|
+
}
|
|
15227
|
+
const encoder = new TextEncoder();
|
|
15228
|
+
let streamController;
|
|
15229
|
+
let keepAliveTimer;
|
|
15230
|
+
const readable = new ReadableStream({
|
|
15231
|
+
start: (controller) => {
|
|
15232
|
+
streamController = controller;
|
|
15233
|
+
},
|
|
15234
|
+
cancel: () => {
|
|
15235
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15236
|
+
if (this._streamMapping.get(this._standaloneSseStreamId)?.controller === streamController) this._streamMapping.delete(this._standaloneSseStreamId);
|
|
15237
|
+
}
|
|
15238
|
+
});
|
|
15239
|
+
const headers = {
|
|
15240
|
+
"Content-Type": "text/event-stream",
|
|
15241
|
+
"Cache-Control": "no-cache, no-transform",
|
|
15242
|
+
Connection: "keep-alive",
|
|
15243
|
+
"X-Accel-Buffering": "no"
|
|
15244
|
+
};
|
|
15245
|
+
if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
|
|
15246
|
+
this._streamMapping.set(this._standaloneSseStreamId, {
|
|
15247
|
+
controller: streamController,
|
|
15248
|
+
encoder,
|
|
15249
|
+
cleanup: () => {
|
|
15250
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15251
|
+
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
15252
|
+
try {
|
|
15253
|
+
streamController.close();
|
|
15254
|
+
} catch {
|
|
15255
|
+
}
|
|
15256
|
+
}
|
|
15257
|
+
});
|
|
15258
|
+
keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
15259
|
+
return new Response(readable, { headers });
|
|
15260
|
+
}
|
|
15261
|
+
/**
|
|
15262
|
+
* Replays events that would have been sent after the specified event ID
|
|
15263
|
+
* Only used when resumability is enabled
|
|
15264
|
+
*/
|
|
15265
|
+
async replayEvents(lastEventId) {
|
|
15266
|
+
if (!this._eventStore) {
|
|
15267
|
+
this.onerror?.(/* @__PURE__ */ new Error("Event store not configured"));
|
|
15268
|
+
return this.createJsonErrorResponse(400, -32e3, "Event store not configured");
|
|
15269
|
+
}
|
|
15270
|
+
try {
|
|
15271
|
+
let streamId;
|
|
15272
|
+
if (this._eventStore.getStreamIdForEventId) {
|
|
15273
|
+
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
15274
|
+
if (!streamId) {
|
|
15275
|
+
this.onerror?.(/* @__PURE__ */ new Error("Invalid event ID format"));
|
|
15276
|
+
return this.createJsonErrorResponse(400, -32e3, "Invalid event ID format");
|
|
15277
|
+
}
|
|
15278
|
+
if (this._streamMapping.get(streamId) !== void 0) {
|
|
15279
|
+
this.onerror?.(/* @__PURE__ */ new Error("Conflict: Stream already has an active connection"));
|
|
15280
|
+
return this.createJsonErrorResponse(409, -32e3, "Conflict: Stream already has an active connection");
|
|
15281
|
+
}
|
|
15282
|
+
}
|
|
15283
|
+
const headers = {
|
|
15284
|
+
"Content-Type": "text/event-stream",
|
|
15285
|
+
"Cache-Control": "no-cache, no-transform",
|
|
15286
|
+
Connection: "keep-alive",
|
|
15287
|
+
"X-Accel-Buffering": "no"
|
|
15288
|
+
};
|
|
15289
|
+
if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
|
|
15290
|
+
const encoder = new TextEncoder();
|
|
15291
|
+
let streamController;
|
|
15292
|
+
let keepAliveTimer;
|
|
15293
|
+
let cancelled = false;
|
|
15294
|
+
let replayedStreamId;
|
|
15295
|
+
const readable = new ReadableStream({
|
|
15296
|
+
start: (controller) => {
|
|
15297
|
+
streamController = controller;
|
|
15298
|
+
},
|
|
15299
|
+
cancel: () => {
|
|
15300
|
+
cancelled = true;
|
|
15301
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15302
|
+
if (replayedStreamId !== void 0 && this._streamMapping.get(replayedStreamId)?.controller === streamController) this._streamMapping.delete(replayedStreamId);
|
|
15303
|
+
}
|
|
15304
|
+
});
|
|
15305
|
+
const replayedEventIds = /* @__PURE__ */ new Set();
|
|
15306
|
+
replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, { send: async (eventId, message) => {
|
|
15307
|
+
replayedEventIds.add(eventId);
|
|
15308
|
+
if (!this.writeSSEEvent(streamController, encoder, message, eventId)) try {
|
|
15309
|
+
streamController.close();
|
|
15310
|
+
} catch {
|
|
15311
|
+
}
|
|
15312
|
+
} });
|
|
15313
|
+
if (this._closed || cancelled) {
|
|
15314
|
+
try {
|
|
15315
|
+
streamController.close();
|
|
15316
|
+
} catch {
|
|
15317
|
+
}
|
|
15318
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
15319
|
+
}
|
|
15320
|
+
this._streamMapping.get(replayedStreamId)?.cleanup();
|
|
15321
|
+
this._streamMapping.set(replayedStreamId, {
|
|
15322
|
+
controller: streamController,
|
|
15323
|
+
encoder,
|
|
15324
|
+
replayedEventIds,
|
|
15325
|
+
cleanup: () => {
|
|
15326
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15327
|
+
this._streamMapping.delete(replayedStreamId);
|
|
15328
|
+
try {
|
|
15329
|
+
streamController.close();
|
|
15330
|
+
} catch {
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
});
|
|
15334
|
+
if (replayedStreamId !== this._standaloneSseStreamId) {
|
|
15335
|
+
if (![...this._requestToStreamMapping.values()].includes(replayedStreamId)) {
|
|
15336
|
+
this._streamMapping.delete(replayedStreamId);
|
|
15337
|
+
try {
|
|
15338
|
+
streamController.close();
|
|
15339
|
+
} catch {
|
|
15340
|
+
}
|
|
15341
|
+
}
|
|
15342
|
+
}
|
|
15343
|
+
if (this._streamMapping.get(replayedStreamId)?.controller === streamController) keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
15344
|
+
return new Response(readable, { headers });
|
|
15345
|
+
} catch (error) {
|
|
15346
|
+
this.onerror?.(error);
|
|
15347
|
+
return this.createJsonErrorResponse(500, -32e3, "Error replaying events");
|
|
15348
|
+
}
|
|
15349
|
+
}
|
|
15350
|
+
/**
|
|
15351
|
+
* Writes an event to an SSE stream via controller with proper formatting
|
|
15352
|
+
*/
|
|
15353
|
+
writeSSEEvent(controller, encoder, message, eventId) {
|
|
15354
|
+
try {
|
|
15355
|
+
let eventData = `event: message
|
|
15356
|
+
`;
|
|
15357
|
+
if (eventId) eventData += `id: ${eventId}
|
|
15358
|
+
`;
|
|
15359
|
+
eventData += `data: ${JSON.stringify(message)}
|
|
15360
|
+
|
|
15361
|
+
`;
|
|
15362
|
+
controller.enqueue(encoder.encode(eventData));
|
|
15363
|
+
return true;
|
|
15364
|
+
} catch (error) {
|
|
15365
|
+
this.onerror?.(error);
|
|
15366
|
+
return false;
|
|
15367
|
+
}
|
|
15368
|
+
}
|
|
15369
|
+
/**
|
|
15370
|
+
* Handles unsupported requests (`PUT`, `PATCH`, etc.)
|
|
15371
|
+
*/
|
|
15372
|
+
handleUnsupportedRequest() {
|
|
15373
|
+
this.onerror?.(/* @__PURE__ */ new Error("Method not allowed."));
|
|
15374
|
+
return Response.json({
|
|
15375
|
+
jsonrpc: "2.0",
|
|
15376
|
+
error: {
|
|
15377
|
+
code: -32e3,
|
|
15378
|
+
message: "Method not allowed."
|
|
15379
|
+
},
|
|
15380
|
+
id: null
|
|
15381
|
+
}, {
|
|
15382
|
+
status: 405,
|
|
15383
|
+
headers: {
|
|
15384
|
+
Allow: "GET, POST, DELETE",
|
|
15385
|
+
"Content-Type": "application/json"
|
|
15386
|
+
}
|
|
15387
|
+
});
|
|
15388
|
+
}
|
|
15389
|
+
/**
|
|
15390
|
+
* Handles `POST` requests containing JSON-RPC messages
|
|
15391
|
+
*/
|
|
15392
|
+
async handlePostRequest(req, options) {
|
|
15393
|
+
try {
|
|
15394
|
+
const acceptHeader = req.headers.get("accept");
|
|
15395
|
+
if (!acceptHeader?.includes("application/json") || !acceptHeader.includes("text/event-stream")) {
|
|
15396
|
+
this.onerror?.(/* @__PURE__ */ new Error("Not Acceptable: Client must accept both application/json and text/event-stream"));
|
|
15397
|
+
return this.createJsonErrorResponse(406, -32e3, "Not Acceptable: Client must accept both application/json and text/event-stream");
|
|
15398
|
+
}
|
|
15399
|
+
if (!isJsonContentType(req.headers.get("content-type"))) {
|
|
15400
|
+
this.onerror?.(/* @__PURE__ */ new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
15401
|
+
return this.createJsonErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
|
|
15402
|
+
}
|
|
15403
|
+
const request = req;
|
|
15404
|
+
let rawMessage;
|
|
15405
|
+
if (options?.parsedBody === void 0) try {
|
|
15406
|
+
const body = await readRequestBody(req, this._maxRequestBodySize);
|
|
15407
|
+
if (body.tooLarge) {
|
|
15408
|
+
const message = requestBodyTooLargeMessage(this._maxRequestBodySize);
|
|
15409
|
+
this.onerror?.(new Error(message));
|
|
15410
|
+
return this.createJsonErrorResponse(413, -32e3, message);
|
|
15411
|
+
}
|
|
15412
|
+
rawMessage = JSON.parse(body.text);
|
|
15413
|
+
} catch (error) {
|
|
15414
|
+
this.onerror?.(error);
|
|
15415
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON");
|
|
15416
|
+
}
|
|
15417
|
+
else rawMessage = options.parsedBody;
|
|
15418
|
+
if (Array.isArray(rawMessage) && rawMessage.length > MAX_BATCH_SIZE) {
|
|
15419
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages`));
|
|
15420
|
+
return this.createJsonErrorResponse(400, -32600, `Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages`);
|
|
15421
|
+
}
|
|
15422
|
+
let messages;
|
|
15423
|
+
try {
|
|
15424
|
+
messages = Array.isArray(rawMessage) ? rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(rawMessage)];
|
|
15425
|
+
} catch (error) {
|
|
15426
|
+
this.onerror?.(error);
|
|
15427
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error: Invalid JSON-RPC message");
|
|
15428
|
+
}
|
|
15429
|
+
if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
15430
|
+
const isInitializationRequest = messages.some((element) => isInitializeRequest(element));
|
|
15431
|
+
if (isInitializationRequest) {
|
|
15432
|
+
if (this._initialized && this.sessionId !== void 0) {
|
|
15433
|
+
this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Server already initialized"));
|
|
15434
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Server already initialized");
|
|
15435
|
+
}
|
|
15436
|
+
if (messages.length > 1) {
|
|
15437
|
+
this.onerror?.(/* @__PURE__ */ new Error("Invalid Request: Only one initialization request is allowed"));
|
|
15438
|
+
return this.createJsonErrorResponse(400, -32600, "Invalid Request: Only one initialization request is allowed");
|
|
15439
|
+
}
|
|
15440
|
+
this.sessionId = this.sessionIdGenerator?.();
|
|
15441
|
+
this._initialized = true;
|
|
15442
|
+
if (this.sessionId && this._onsessioninitialized) await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
15443
|
+
}
|
|
15444
|
+
if (!isInitializationRequest) {
|
|
15445
|
+
const sessionError = this.validateSession(req);
|
|
15446
|
+
if (sessionError) return sessionError;
|
|
15447
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
15448
|
+
if (protocolError) return protocolError;
|
|
15449
|
+
}
|
|
15450
|
+
if (this._closed) return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
15451
|
+
let scopeChallengeResponse;
|
|
15452
|
+
try {
|
|
15453
|
+
scopeChallengeResponse = await this._checkScopeChallenge(messages, options?.authInfo);
|
|
15454
|
+
} catch (error) {
|
|
15455
|
+
this.onerror?.(error);
|
|
15456
|
+
return this.createJsonErrorResponse(500, -32603, "Internal server error");
|
|
15457
|
+
}
|
|
15458
|
+
if (scopeChallengeResponse) return scopeChallengeResponse;
|
|
15459
|
+
if (!messages.some((element) => isJSONRPCRequest(element))) {
|
|
15460
|
+
for (const message of messages) this.onmessage?.(message, {
|
|
15461
|
+
authInfo: options?.authInfo,
|
|
15462
|
+
request
|
|
15463
|
+
});
|
|
15464
|
+
return new Response(null, { status: 202 });
|
|
15465
|
+
}
|
|
15466
|
+
const streamId = crypto.randomUUID();
|
|
15467
|
+
const initRequest = messages.find((m) => isInitializeRequest(m));
|
|
15468
|
+
const clientProtocolVersion = initRequest ? initRequest.params.protocolVersion : req.headers.get("mcp-protocol-version") ?? DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
15469
|
+
if (this._enableJsonResponse) return new Promise((resolve) => {
|
|
15470
|
+
this._streamMapping.set(streamId, {
|
|
15471
|
+
resolveJson: resolve,
|
|
15472
|
+
cleanup: () => {
|
|
15473
|
+
this._streamMapping.delete(streamId);
|
|
15474
|
+
}
|
|
15475
|
+
});
|
|
15476
|
+
for (const message of messages) if (isJSONRPCRequest(message)) this._requestToStreamMapping.set(message.id, streamId);
|
|
15477
|
+
for (const message of messages) this.onmessage?.(message, {
|
|
15478
|
+
authInfo: options?.authInfo,
|
|
15479
|
+
request
|
|
15480
|
+
});
|
|
15481
|
+
});
|
|
15482
|
+
const encoder = new TextEncoder();
|
|
15483
|
+
let streamController;
|
|
15484
|
+
let keepAliveTimer;
|
|
15485
|
+
const readable = new ReadableStream({
|
|
15486
|
+
start: (controller) => {
|
|
15487
|
+
streamController = controller;
|
|
15488
|
+
},
|
|
15489
|
+
cancel: () => {
|
|
15490
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15491
|
+
if (this._streamMapping.get(streamId)?.controller === streamController) this._streamMapping.delete(streamId);
|
|
15492
|
+
}
|
|
15493
|
+
});
|
|
15494
|
+
const headers = {
|
|
15495
|
+
"Content-Type": "text/event-stream",
|
|
15496
|
+
"Cache-Control": "no-cache, no-transform",
|
|
15497
|
+
Connection: "keep-alive",
|
|
15498
|
+
"X-Accel-Buffering": "no"
|
|
15499
|
+
};
|
|
15500
|
+
if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
|
|
15501
|
+
for (const message of messages) if (isJSONRPCRequest(message)) {
|
|
15502
|
+
this._streamMapping.set(streamId, {
|
|
15503
|
+
controller: streamController,
|
|
15504
|
+
encoder,
|
|
15505
|
+
cleanup: () => {
|
|
15506
|
+
if (keepAliveTimer !== void 0) clearInterval(keepAliveTimer);
|
|
15507
|
+
this._streamMapping.delete(streamId);
|
|
15508
|
+
try {
|
|
15509
|
+
streamController.close();
|
|
15510
|
+
} catch {
|
|
15511
|
+
}
|
|
15512
|
+
}
|
|
15513
|
+
});
|
|
15514
|
+
this._requestToStreamMapping.set(message.id, streamId);
|
|
15515
|
+
}
|
|
15516
|
+
await this.writePrimingEvent(streamController, encoder, streamId, clientProtocolVersion);
|
|
15517
|
+
for (const message of messages) {
|
|
15518
|
+
let closeSSEStream;
|
|
15519
|
+
let closeStandaloneSSEStream;
|
|
15520
|
+
if (isJSONRPCRequest(message) && this._eventStore && this.supportsEmptySSEData(clientProtocolVersion)) {
|
|
15521
|
+
closeSSEStream = () => {
|
|
15522
|
+
this.closeSSEStream(message.id);
|
|
15523
|
+
};
|
|
15524
|
+
closeStandaloneSSEStream = () => {
|
|
15525
|
+
this.closeStandaloneSSEStream();
|
|
15526
|
+
};
|
|
15527
|
+
}
|
|
15528
|
+
this.onmessage?.(message, {
|
|
15529
|
+
authInfo: options?.authInfo,
|
|
15530
|
+
request,
|
|
15531
|
+
closeSSEStream,
|
|
15532
|
+
closeStandaloneSSEStream
|
|
15533
|
+
});
|
|
15534
|
+
}
|
|
15535
|
+
if (this._streamMapping.get(streamId)?.controller === streamController) keepAliveTimer = this.startKeepAlive(streamController, encoder);
|
|
15536
|
+
return new Response(readable, {
|
|
15537
|
+
status: 200,
|
|
15538
|
+
headers
|
|
15539
|
+
});
|
|
15540
|
+
} catch (error) {
|
|
15541
|
+
this.onerror?.(error);
|
|
15542
|
+
return this.createJsonErrorResponse(400, -32700, "Parse error", { data: String(error) });
|
|
15543
|
+
}
|
|
15544
|
+
}
|
|
15545
|
+
/**
|
|
15546
|
+
* Handles `DELETE` requests to terminate sessions
|
|
15547
|
+
*/
|
|
15548
|
+
async handleDeleteRequest(req) {
|
|
15549
|
+
const sessionError = this.validateSession(req);
|
|
15550
|
+
if (sessionError) return sessionError;
|
|
15551
|
+
const protocolError = this.validateProtocolVersion(req);
|
|
15552
|
+
if (protocolError) return protocolError;
|
|
15553
|
+
try {
|
|
15554
|
+
await Promise.resolve(this._onsessionclosed?.(this.sessionId));
|
|
15555
|
+
return new Response(null, { status: 200 });
|
|
15556
|
+
} finally {
|
|
15557
|
+
await this.close();
|
|
15558
|
+
}
|
|
15559
|
+
}
|
|
15560
|
+
/**
|
|
15561
|
+
* Validates session ID for non-initialization requests.
|
|
15562
|
+
* Returns `Response` error if invalid, `undefined` otherwise
|
|
15563
|
+
*/
|
|
15564
|
+
validateSession(req) {
|
|
15565
|
+
if (this.sessionIdGenerator === void 0) return;
|
|
15566
|
+
if (!this._initialized) {
|
|
15567
|
+
this.onerror?.(/* @__PURE__ */ new Error("Bad Request: Server not initialized"));
|
|
15568
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Server not initialized");
|
|
15569
|
+
}
|
|
15570
|
+
const sessionId = req.headers.get("mcp-session-id");
|
|
15571
|
+
if (!sessionId) {
|
|
15572
|
+
this.onerror?.(/* @__PURE__ */ new Error("Bad Request: Mcp-Session-Id header is required"));
|
|
15573
|
+
return this.createJsonErrorResponse(400, -32e3, "Bad Request: Mcp-Session-Id header is required");
|
|
15574
|
+
}
|
|
15575
|
+
if (sessionId !== this.sessionId) {
|
|
15576
|
+
this.onerror?.(/* @__PURE__ */ new Error("Session not found"));
|
|
15577
|
+
return this.createJsonErrorResponse(404, -32001, "Session not found");
|
|
15578
|
+
}
|
|
15579
|
+
}
|
|
15580
|
+
/**
|
|
15581
|
+
* Validates the `MCP-Protocol-Version` header on incoming requests.
|
|
15582
|
+
*
|
|
15583
|
+
* For initialization: Version negotiation handles unknown versions gracefully
|
|
15584
|
+
* (server responds with its supported version).
|
|
15585
|
+
*
|
|
15586
|
+
* For subsequent requests with `MCP-Protocol-Version` header:
|
|
15587
|
+
* - Accept if in supported list
|
|
15588
|
+
* - 400 if unsupported
|
|
15589
|
+
*
|
|
15590
|
+
* For HTTP requests without the `MCP-Protocol-Version` header:
|
|
15591
|
+
* - Accept and default to the version negotiated at initialization
|
|
15592
|
+
*/
|
|
15593
|
+
validateProtocolVersion(req) {
|
|
15594
|
+
const protocolVersion = req.headers.get("mcp-protocol-version");
|
|
15595
|
+
if (protocolVersion !== null && !this._supportedProtocolVersions.includes(protocolVersion)) {
|
|
15596
|
+
const error = `Bad Request: Unsupported protocol version: ${protocolVersion} (supported versions: ${this._supportedProtocolVersions.join(", ")})`;
|
|
15597
|
+
this.onerror?.(new Error(error));
|
|
15598
|
+
return this.createJsonErrorResponse(400, -32e3, error);
|
|
15599
|
+
}
|
|
15600
|
+
}
|
|
15601
|
+
async close() {
|
|
15602
|
+
if (this._closed) return;
|
|
15603
|
+
this._closed = true;
|
|
15604
|
+
for (const { cleanup } of this._streamMapping.values()) cleanup();
|
|
15605
|
+
this._streamMapping.clear();
|
|
15606
|
+
this._requestResponseMap.clear();
|
|
15607
|
+
this.onclose?.();
|
|
15608
|
+
}
|
|
15609
|
+
/**
|
|
15610
|
+
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
15611
|
+
* Use this to implement polling behavior during long-running operations -
|
|
15612
|
+
* client will reconnect after the retry interval specified in the priming event.
|
|
15613
|
+
*/
|
|
15614
|
+
closeSSEStream(requestId) {
|
|
15615
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
15616
|
+
if (!streamId) return;
|
|
15617
|
+
const stream = this._streamMapping.get(streamId);
|
|
15618
|
+
if (stream) stream.cleanup();
|
|
15619
|
+
}
|
|
15620
|
+
/**
|
|
15621
|
+
* Close the standalone `GET` SSE stream, triggering client reconnection.
|
|
15622
|
+
* Use this to implement polling behavior for server-initiated notifications.
|
|
15623
|
+
*/
|
|
15624
|
+
closeStandaloneSSEStream() {
|
|
15625
|
+
const stream = this._streamMapping.get(this._standaloneSseStreamId);
|
|
15626
|
+
if (stream) stream.cleanup();
|
|
15627
|
+
}
|
|
15628
|
+
async send(message, options) {
|
|
15629
|
+
let requestId = options?.relatedRequestId;
|
|
15630
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) requestId = message.id;
|
|
15631
|
+
if (requestId === void 0) {
|
|
15632
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) throw new Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");
|
|
15633
|
+
let eventId;
|
|
15634
|
+
if (this._eventStore) eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
15635
|
+
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
15636
|
+
if (standaloneSse === void 0) return;
|
|
15637
|
+
if (standaloneSse.controller && standaloneSse.encoder && (eventId === void 0 || !standaloneSse.replayedEventIds?.has(eventId))) this.writeSSEEvent(standaloneSse.controller, standaloneSse.encoder, message, eventId);
|
|
15638
|
+
return;
|
|
15639
|
+
}
|
|
15640
|
+
const streamId = this._requestToStreamMapping.get(requestId);
|
|
15641
|
+
if (!streamId) throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
15642
|
+
let stream = this._streamMapping.get(streamId);
|
|
15643
|
+
if (!this._enableJsonResponse) {
|
|
15644
|
+
let eventId;
|
|
15645
|
+
if (this._eventStore) {
|
|
15646
|
+
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
15647
|
+
stream = this._streamMapping.get(streamId);
|
|
15648
|
+
}
|
|
15649
|
+
if (stream?.controller && stream?.encoder && (eventId === void 0 || !stream.replayedEventIds?.has(eventId))) this.writeSSEEvent(stream.controller, stream.encoder, message, eventId);
|
|
15650
|
+
}
|
|
15651
|
+
if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
|
15652
|
+
this._requestResponseMap.set(requestId, message);
|
|
15653
|
+
const relatedIds = [...this._requestToStreamMapping.entries()].filter(([_, sid]) => sid === streamId).map(([id]) => id);
|
|
15654
|
+
if (relatedIds.every((id) => this._requestResponseMap.has(id))) {
|
|
15655
|
+
if (!stream) {
|
|
15656
|
+
if (this._enableJsonResponse) throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
15657
|
+
if (!this._eventStore) {
|
|
15658
|
+
this.onerror?.(/* @__PURE__ */ new Error(`Response for request ID ${String(requestId)} is undeliverable: per-request stream is disconnected and no eventStore is configured`));
|
|
15659
|
+
for (const id of relatedIds) {
|
|
15660
|
+
this._requestResponseMap.delete(id);
|
|
15661
|
+
this._requestToStreamMapping.delete(id);
|
|
15662
|
+
}
|
|
15663
|
+
return;
|
|
15664
|
+
}
|
|
15665
|
+
for (const id of relatedIds) {
|
|
15666
|
+
this._requestResponseMap.delete(id);
|
|
15667
|
+
this._requestToStreamMapping.delete(id);
|
|
15668
|
+
}
|
|
15669
|
+
return;
|
|
15670
|
+
}
|
|
15671
|
+
if (this._enableJsonResponse && stream.resolveJson) {
|
|
15672
|
+
const headers = { "Content-Type": "application/json" };
|
|
15673
|
+
if (this.sessionId !== void 0) headers["mcp-session-id"] = this.sessionId;
|
|
15674
|
+
const responses = relatedIds.map((id) => this._requestResponseMap.get(id));
|
|
15675
|
+
if (responses.length === 1) stream.resolveJson(Response.json(responses[0], {
|
|
15676
|
+
status: 200,
|
|
15677
|
+
headers
|
|
15678
|
+
}));
|
|
15679
|
+
else stream.resolveJson(Response.json(responses, {
|
|
15680
|
+
status: 200,
|
|
15681
|
+
headers
|
|
15682
|
+
}));
|
|
15683
|
+
stream.cleanup();
|
|
15684
|
+
} else stream.cleanup();
|
|
15685
|
+
for (const id of relatedIds) {
|
|
15686
|
+
this._requestResponseMap.delete(id);
|
|
15687
|
+
this._requestToStreamMapping.delete(id);
|
|
15688
|
+
}
|
|
15689
|
+
}
|
|
15690
|
+
}
|
|
15691
|
+
}
|
|
15692
|
+
};
|
|
15693
|
+
function echoableRequestId(body) {
|
|
15694
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
15695
|
+
const { method, id } = body;
|
|
15696
|
+
if (typeof method !== "string") return null;
|
|
15697
|
+
return typeof id === "string" || typeof id === "number" ? id : null;
|
|
15698
|
+
}
|
|
15699
|
+
function jsonRpcErrorResponse(httpStatus, code, message, data, id = null) {
|
|
15700
|
+
return Response.json({
|
|
15701
|
+
jsonrpc: "2.0",
|
|
15702
|
+
error: {
|
|
15703
|
+
code,
|
|
15704
|
+
message,
|
|
15705
|
+
...data !== void 0 && { data }
|
|
15706
|
+
},
|
|
15707
|
+
id
|
|
15708
|
+
}, { status: httpStatus });
|
|
15709
|
+
}
|
|
15710
|
+
function rejectionResponse(rejection2, id = null) {
|
|
15711
|
+
return jsonRpcErrorResponse(rejection2.httpStatus, rejection2.code, rejection2.message, rejection2.data, id);
|
|
15712
|
+
}
|
|
15713
|
+
function toError(value) {
|
|
15714
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
15715
|
+
}
|
|
15716
|
+
function internalServerErrorResponse(id = null) {
|
|
15717
|
+
return jsonRpcErrorResponse(500, -32603, "Internal server error", void 0, id);
|
|
15718
|
+
}
|
|
15719
|
+
function assertNotServing(server) {
|
|
15720
|
+
if (server.transport !== void 0) throw new Error("The factory returned an instance that is still serving another request. Return a new instance for each request.");
|
|
15721
|
+
}
|
|
15722
|
+
function createLegacyStatelessFallback(factory, onerror, keepAliveMs, maxRequestBodySize) {
|
|
15723
|
+
return async (request, options) => {
|
|
15724
|
+
if (request.method.toUpperCase() !== "POST") return jsonRpcErrorResponse(405, -32e3, "Method not allowed.");
|
|
15725
|
+
try {
|
|
15726
|
+
const product = await factory({
|
|
15727
|
+
era: "legacy",
|
|
15728
|
+
...options?.authInfo !== void 0 && { authInfo: options.authInfo },
|
|
15729
|
+
requestInfo: request
|
|
15730
|
+
});
|
|
15731
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
15732
|
+
sessionIdGenerator: void 0,
|
|
15733
|
+
...keepAliveMs !== void 0 && { keepAliveMs },
|
|
15734
|
+
...maxRequestBodySize !== void 0 && { maxRequestBodySize }
|
|
15735
|
+
});
|
|
15736
|
+
await product.connect(transport);
|
|
15737
|
+
const teardown = () => {
|
|
15738
|
+
transport.close().catch(() => {
|
|
15739
|
+
});
|
|
15740
|
+
product.close().catch(() => {
|
|
15741
|
+
});
|
|
15742
|
+
};
|
|
15743
|
+
request.signal?.addEventListener("abort", teardown, { once: true });
|
|
15744
|
+
const response = await transport.handleRequest(request, {
|
|
15745
|
+
...options?.authInfo !== void 0 && { authInfo: options.authInfo },
|
|
15746
|
+
...options?.parsedBody !== void 0 && { parsedBody: options.parsedBody }
|
|
15747
|
+
});
|
|
15748
|
+
if (response.body === null || mediaTypeEssence(response.headers.get("content-type")) !== "text/event-stream") {
|
|
15749
|
+
teardown();
|
|
15750
|
+
return response;
|
|
15751
|
+
}
|
|
15752
|
+
const reader = response.body.getReader();
|
|
15753
|
+
let toreDown = false;
|
|
15754
|
+
const completeExchange = () => {
|
|
15755
|
+
if (!toreDown) {
|
|
15756
|
+
toreDown = true;
|
|
15757
|
+
teardown();
|
|
15758
|
+
}
|
|
15759
|
+
};
|
|
15760
|
+
const monitoredBody = new ReadableStream({
|
|
15761
|
+
pull: async (controller) => {
|
|
15762
|
+
try {
|
|
15763
|
+
const { done, value } = await reader.read();
|
|
15764
|
+
if (done) {
|
|
15765
|
+
completeExchange();
|
|
15766
|
+
controller.close();
|
|
15767
|
+
return;
|
|
15768
|
+
}
|
|
15769
|
+
if (value !== void 0) controller.enqueue(value);
|
|
15770
|
+
} catch (error) {
|
|
15771
|
+
completeExchange();
|
|
15772
|
+
controller.error(error);
|
|
15773
|
+
}
|
|
15774
|
+
},
|
|
15775
|
+
cancel: (reason) => {
|
|
15776
|
+
completeExchange();
|
|
15777
|
+
return reader.cancel(reason).catch(() => {
|
|
15778
|
+
});
|
|
15779
|
+
}
|
|
15780
|
+
});
|
|
15781
|
+
return new Response(monitoredBody, {
|
|
15782
|
+
status: response.status,
|
|
15783
|
+
statusText: response.statusText,
|
|
15784
|
+
headers: response.headers
|
|
15785
|
+
});
|
|
15786
|
+
} catch (error) {
|
|
15787
|
+
try {
|
|
15788
|
+
onerror?.(toError(error));
|
|
15789
|
+
} catch {
|
|
15790
|
+
}
|
|
15791
|
+
return internalServerErrorResponse(echoableRequestId(options?.parsedBody));
|
|
15792
|
+
}
|
|
15793
|
+
};
|
|
15794
|
+
}
|
|
15795
|
+
function standardHeadersOf(request) {
|
|
15796
|
+
return {
|
|
15797
|
+
protocolVersionHeader: request.headers.get("mcp-protocol-version") ?? void 0,
|
|
15798
|
+
mcpMethodHeader: request.headers.get("mcp-method") ?? void 0,
|
|
15799
|
+
mcpNameHeader: request.headers.get("mcp-name") ?? void 0
|
|
15800
|
+
};
|
|
15801
|
+
}
|
|
15802
|
+
async function classifyEntryRequest(request, providedParsedBody, needsForward = true, maxRequestBodySize = DEFAULT_MAX_REQUEST_BODY_SIZE) {
|
|
15803
|
+
const httpMethod = request.method.toUpperCase();
|
|
15804
|
+
let body;
|
|
15805
|
+
let parsedBody = providedParsedBody;
|
|
15806
|
+
let forwardRequest = request;
|
|
15807
|
+
let unparseable = false;
|
|
15808
|
+
if (httpMethod === "POST") {
|
|
15809
|
+
if (parsedBody === void 0) {
|
|
15810
|
+
if (needsForward) forwardRequest = request.clone();
|
|
15811
|
+
let bodyText;
|
|
15812
|
+
try {
|
|
15813
|
+
const read = await readRequestBody(request, maxRequestBodySize);
|
|
15814
|
+
if (read.tooLarge) return { step: "body-too-large" };
|
|
15815
|
+
bodyText = read.text;
|
|
15816
|
+
} catch {
|
|
15817
|
+
return { step: "unreadable-body" };
|
|
15818
|
+
}
|
|
15819
|
+
try {
|
|
15820
|
+
body = bodyText.length === 0 ? void 0 : JSON.parse(bodyText);
|
|
15821
|
+
} catch {
|
|
15822
|
+
unparseable = true;
|
|
15823
|
+
}
|
|
15824
|
+
if (!unparseable && body !== void 0) parsedBody = body;
|
|
15825
|
+
} else body = parsedBody;
|
|
15826
|
+
if (unparseable || body === void 0) return {
|
|
15827
|
+
step: "no-json-body",
|
|
15828
|
+
forwardRequest
|
|
15829
|
+
};
|
|
15830
|
+
}
|
|
15831
|
+
return {
|
|
15832
|
+
step: "classified",
|
|
15833
|
+
outcome: classifyInboundRequest({
|
|
15834
|
+
httpMethod,
|
|
15835
|
+
...standardHeadersOf(request),
|
|
15836
|
+
...body !== void 0 && { body }
|
|
15837
|
+
}),
|
|
15838
|
+
body,
|
|
15839
|
+
parsedBody,
|
|
15840
|
+
forwardRequest
|
|
15841
|
+
};
|
|
15842
|
+
}
|
|
15843
|
+
async function isLegacyRequest(request, parsedBody, options) {
|
|
15844
|
+
const maxRequestBodySize = resolveMaxRequestBodySize(options?.maxRequestBodySize);
|
|
15845
|
+
const classified = await classifyEntryRequest(parsedBody === void 0 && request.method.toUpperCase() === "POST" ? request.clone() : request, parsedBody, false, maxRequestBodySize);
|
|
15846
|
+
return classified.step === "no-json-body" || classified.step === "classified" && classified.outcome.kind === "legacy";
|
|
15847
|
+
}
|
|
15848
|
+
function createMcpHandler(factory, options = {}) {
|
|
15849
|
+
const { legacy, onerror, responseMode } = options;
|
|
15850
|
+
const maxRequestBodySize = resolveMaxRequestBodySize(options.maxRequestBodySize);
|
|
15851
|
+
if (typeof legacy === "function") throw new TypeError("The 'legacy' option only accepts 'stateless' or 'reject', not a handler function. To serve 2025-era traffic with your own handler, route in user land with the exported isLegacyRequest(request) predicate in front of a strict (legacy: 'reject') handler.");
|
|
15852
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
15853
|
+
let closed = false;
|
|
15854
|
+
const reportError = (error) => {
|
|
15855
|
+
try {
|
|
15856
|
+
onerror?.(error);
|
|
15857
|
+
} catch {
|
|
15858
|
+
}
|
|
15859
|
+
};
|
|
15860
|
+
const bus = options.bus ?? new InMemoryServerEventBus(reportError);
|
|
15861
|
+
const notify = createServerNotifier(bus);
|
|
15862
|
+
const listenRouter = createListenRouter({
|
|
15863
|
+
bus,
|
|
15864
|
+
maxSubscriptions: options.maxSubscriptions ?? DEFAULT_MAX_SUBSCRIPTIONS,
|
|
15865
|
+
keepAliveMs: options.keepAliveMs ?? DEFAULT_SSE_KEEP_ALIVE_MS,
|
|
15866
|
+
onerror: reportError
|
|
15867
|
+
});
|
|
15868
|
+
if (responseMode === "json") console.warn("responseMode: 'json' drops mid-call notifications. subscriptions/listen streams are always served over SSE regardless; other notifications emitted before a result are dropped.");
|
|
15869
|
+
const legacyHandler = legacy === "reject" ? void 0 : createLegacyStatelessFallback(factory, reportError, options.keepAliveMs, maxRequestBodySize);
|
|
15870
|
+
async function serveModern(route, request, authInfo) {
|
|
15871
|
+
const claimedRevision = route.classification.revision;
|
|
15872
|
+
if (claimedRevision === void 0 || !SUPPORTED_MODERN_PROTOCOL_VERSIONS.includes(claimedRevision)) {
|
|
15873
|
+
const error = new UnsupportedProtocolVersionError({
|
|
15874
|
+
supported: [...SUPPORTED_MODERN_PROTOCOL_VERSIONS],
|
|
15875
|
+
requested: claimedRevision ?? "unknown"
|
|
15876
|
+
});
|
|
15877
|
+
reportError(error);
|
|
15878
|
+
return jsonRpcErrorResponse(400, error.code, error.message, error.data, echoableRequestId(route.message));
|
|
15879
|
+
}
|
|
15880
|
+
const stdHeaderRejection = validateStandardRequestHeaders({
|
|
15881
|
+
httpMethod: request.method,
|
|
15882
|
+
...standardHeadersOf(request)
|
|
15883
|
+
}, route);
|
|
15884
|
+
if (stdHeaderRejection !== void 0) {
|
|
15885
|
+
reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${stdHeaderRejection.cell}): ${stdHeaderRejection.message}`));
|
|
15886
|
+
return rejectionResponse(stdHeaderRejection, echoableRequestId(route.message));
|
|
15887
|
+
}
|
|
15888
|
+
const meta = route.messageKind === "request" ? requestMetaOf(route.message.params) : void 0;
|
|
15889
|
+
const declaredClientCapabilities = meta?.[CLIENT_CAPABILITIES_META_KEY];
|
|
15890
|
+
if (route.messageKind === "request") {
|
|
15891
|
+
const required = requiredClientCapabilitiesForRequest(route.message.method);
|
|
15892
|
+
if (required !== void 0) {
|
|
15893
|
+
const missing = missingClientCapabilities(required, declaredClientCapabilities);
|
|
15894
|
+
if (missing !== void 0) {
|
|
15895
|
+
const error = new MissingRequiredClientCapabilityError({ requiredCapabilities: missing });
|
|
15896
|
+
reportError(error);
|
|
15897
|
+
return jsonRpcErrorResponse(httpStatusForErrorCode(error.code, "ladder"), error.code, error.message, error.data, route.message.id);
|
|
15898
|
+
}
|
|
15899
|
+
}
|
|
15900
|
+
}
|
|
15901
|
+
const product = await factory({
|
|
15902
|
+
era: "modern",
|
|
15903
|
+
...authInfo !== void 0 && { authInfo },
|
|
15904
|
+
requestInfo: request
|
|
15905
|
+
});
|
|
15906
|
+
const server = product instanceof McpServer ? product.server : product;
|
|
15907
|
+
assertNotServing(server);
|
|
15908
|
+
if (route.messageKind === "request" && route.message.method === "subscriptions/listen") {
|
|
15909
|
+
const capabilities = server.getCapabilities();
|
|
15910
|
+
const serverInfo = serverIdentityOf(server);
|
|
15911
|
+
product.close().catch(reportError);
|
|
15912
|
+
return listenRouter.serve(route.message, request.signal, capabilities, serverInfo);
|
|
15913
|
+
}
|
|
15914
|
+
if (route.messageKind === "request" && route.message.method === "tools/call" && product instanceof McpServer) {
|
|
15915
|
+
const callParams = route.message.params;
|
|
15916
|
+
const toolName = typeof callParams?.name === "string" ? callParams.name : void 0;
|
|
15917
|
+
const inputSchema = toolName === void 0 ? void 0 : product.toolInputSchemaJson(toolName);
|
|
15918
|
+
if (inputSchema !== void 0) {
|
|
15919
|
+
const scan = scanXMcpHeaderDeclarations(inputSchema);
|
|
15920
|
+
if (scan.valid && scan.declarations.length > 0) {
|
|
15921
|
+
const rejection2 = validateMcpParamHeaders(scan.declarations, callParams?.arguments, request.headers);
|
|
15922
|
+
if (rejection2 !== void 0) {
|
|
15923
|
+
product.close().catch(reportError);
|
|
15924
|
+
reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${rejection2.cell}): ${rejection2.message}`));
|
|
15925
|
+
return rejectionResponse(rejection2, route.message.id);
|
|
15926
|
+
}
|
|
15927
|
+
}
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
15930
|
+
if (route.messageKind === "request" && product instanceof McpServer) try {
|
|
15931
|
+
const challenge = await findScopeChallenge([route.message], authInfo, (context) => product.resolveScopeChallenge(context));
|
|
15932
|
+
if (challenge !== void 0) {
|
|
15933
|
+
product.close().catch(reportError);
|
|
15934
|
+
return createScopeChallengeResponse(challenge, scopeChallengeResourceMetadataUrl(authInfo));
|
|
15935
|
+
}
|
|
15936
|
+
} catch (error) {
|
|
15937
|
+
product.close().catch(reportError);
|
|
15938
|
+
reportError(toError(error));
|
|
15939
|
+
return internalServerErrorResponse(route.message.id);
|
|
15940
|
+
}
|
|
15941
|
+
assertNotServing(server);
|
|
15942
|
+
setNegotiatedProtocolVersion(server, claimedRevision);
|
|
15943
|
+
installModernOnlyHandlers(server, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
15944
|
+
if (meta !== void 0) seedClientIdentityFromEnvelope(server, {
|
|
15945
|
+
clientInfo: meta[CLIENT_INFO_META_KEY],
|
|
15946
|
+
clientCapabilities: declaredClientCapabilities
|
|
15947
|
+
});
|
|
15948
|
+
const previousOnClose = server.onclose;
|
|
15949
|
+
inflight.add(server);
|
|
15950
|
+
const onExchangeClose = () => {
|
|
15951
|
+
inflight.delete(server);
|
|
15952
|
+
if (server.onclose === onExchangeClose) server.onclose = previousOnClose;
|
|
15953
|
+
previousOnClose?.();
|
|
15954
|
+
};
|
|
15955
|
+
server.onclose = onExchangeClose;
|
|
15956
|
+
try {
|
|
15957
|
+
const response = await invoke(product, route.message, {
|
|
15958
|
+
classification: route.classification,
|
|
15959
|
+
request,
|
|
15960
|
+
...authInfo !== void 0 && { authInfo },
|
|
15961
|
+
...responseMode !== void 0 && { responseMode },
|
|
15962
|
+
...options.keepAliveMs !== void 0 && { keepAliveMs: options.keepAliveMs }
|
|
15963
|
+
});
|
|
15964
|
+
if (route.messageKind === "notification") queueMicrotask(() => void server.close().catch(() => {
|
|
15965
|
+
}));
|
|
15966
|
+
return response;
|
|
15967
|
+
} catch (error) {
|
|
15968
|
+
if (error instanceof SdkError && error.code === SdkErrorCode.ConnectionClosed) return new Response(null, { status: 499 });
|
|
15969
|
+
await server.close().catch(() => {
|
|
15970
|
+
});
|
|
15971
|
+
inflight.delete(server);
|
|
15972
|
+
reportError(toError(error));
|
|
15973
|
+
return internalServerErrorResponse(echoableRequestId(route.message));
|
|
15974
|
+
}
|
|
15975
|
+
}
|
|
15976
|
+
async function serveLegacyRoute(route, forwardRequest, authInfo, parsedBody) {
|
|
15977
|
+
if (legacyHandler !== void 0) return legacyHandler(forwardRequest, {
|
|
15978
|
+
...authInfo !== void 0 && { authInfo },
|
|
15979
|
+
...parsedBody !== void 0 && { parsedBody }
|
|
15980
|
+
});
|
|
15981
|
+
const strict = modernOnlyStrictRejection(route, SUPPORTED_MODERN_PROTOCOL_VERSIONS);
|
|
15982
|
+
if (strict === void 0) return new Response(null, { status: 202 });
|
|
15983
|
+
reportError(/* @__PURE__ */ new Error(`Rejected 2025-era request on a modern-only endpoint (${strict.cell}): ${strict.message}`));
|
|
15984
|
+
return rejectionResponse(strict, echoableRequestId(parsedBody));
|
|
15985
|
+
}
|
|
15986
|
+
async function handle(request, requestOptions) {
|
|
15987
|
+
const authInfo = requestOptions?.authInfo;
|
|
15988
|
+
if (request.method.toUpperCase() === "POST" && !isJsonContentType(request.headers.get("content-type"))) {
|
|
15989
|
+
reportError(/* @__PURE__ */ new Error("Unsupported Media Type: Content-Type must be application/json"));
|
|
15990
|
+
return jsonRpcErrorResponse(415, -32e3, "Unsupported Media Type: Content-Type must be application/json");
|
|
15991
|
+
}
|
|
15992
|
+
const classified = await classifyEntryRequest(request, requestOptions?.parsedBody, true, maxRequestBodySize);
|
|
15993
|
+
if (classified.step === "unreadable-body") return jsonRpcErrorResponse(400, -32700, "Parse error: the request body could not be read");
|
|
15994
|
+
if (classified.step === "body-too-large") return jsonRpcErrorResponse(413, -32e3, requestBodyTooLargeMessage(maxRequestBodySize));
|
|
15995
|
+
if (classified.step === "no-json-body") {
|
|
15996
|
+
if (legacyHandler !== void 0) return legacyHandler(classified.forwardRequest, { ...authInfo !== void 0 && { authInfo } });
|
|
15997
|
+
return jsonRpcErrorResponse(400, -32700, "Parse error: the request body is not valid JSON");
|
|
15998
|
+
}
|
|
15999
|
+
const { outcome, body, parsedBody, forwardRequest } = classified;
|
|
16000
|
+
try {
|
|
16001
|
+
switch (outcome.kind) {
|
|
16002
|
+
case "reject":
|
|
16003
|
+
reportError(/* @__PURE__ */ new Error(`Rejected inbound request (${outcome.cell}): ${outcome.message}`));
|
|
16004
|
+
return rejectionResponse(outcome, echoableRequestId(body));
|
|
16005
|
+
case "modern":
|
|
16006
|
+
return await serveModern(outcome, request, authInfo);
|
|
16007
|
+
case "legacy":
|
|
16008
|
+
return await serveLegacyRoute(outcome, forwardRequest, authInfo, parsedBody);
|
|
16009
|
+
}
|
|
16010
|
+
} catch (error) {
|
|
16011
|
+
reportError(toError(error));
|
|
16012
|
+
return internalServerErrorResponse(echoableRequestId(body));
|
|
16013
|
+
}
|
|
16014
|
+
}
|
|
16015
|
+
const fetchFace = async (request, requestOptions) => {
|
|
16016
|
+
if (closed) throw new Error("This MCP handler has been closed");
|
|
16017
|
+
try {
|
|
16018
|
+
return await handle(request, requestOptions);
|
|
16019
|
+
} catch (error) {
|
|
16020
|
+
reportError(toError(error));
|
|
16021
|
+
return internalServerErrorResponse(echoableRequestId(requestOptions?.parsedBody));
|
|
16022
|
+
}
|
|
16023
|
+
};
|
|
16024
|
+
return {
|
|
16025
|
+
fetch: fetchFace,
|
|
16026
|
+
notify,
|
|
16027
|
+
bus,
|
|
16028
|
+
close: async () => {
|
|
16029
|
+
closed = true;
|
|
16030
|
+
listenRouter.closeAll();
|
|
16031
|
+
const closing = [...inflight].map((server) => server.close().catch(() => {
|
|
16032
|
+
}));
|
|
16033
|
+
inflight.clear();
|
|
16034
|
+
await Promise.all(closing);
|
|
16035
|
+
}
|
|
16036
|
+
};
|
|
16037
|
+
}
|
|
16038
|
+
|
|
16039
|
+
// src/server.ts
|
|
16040
|
+
import { readFileSync } from "fs";
|
|
16041
|
+
import { dirname, join } from "path";
|
|
16042
|
+
import { fileURLToPath } from "url";
|
|
16043
|
+
|
|
16044
|
+
// src/lib/onboarding-copy.ts
|
|
16045
|
+
var ONBOARDING_OFFER_CONTROL_VARIANT = "control";
|
|
16046
|
+
var ONBOARDING_OFFER_COPY = {
|
|
16047
|
+
// #4089 review: this cohort has already completed OAuth to connect - they
|
|
16048
|
+
// have an account. Never claim "no account needed" or otherwise imply an
|
|
16049
|
+
// anonymous/no-sign-up lane here; that framing contradicts what they just
|
|
16050
|
+
// did and reads as broken. Acknowledge the connection instead, then offer
|
|
16051
|
+
// to build. (The anonymous/no-auth entry point is a different surface,
|
|
16052
|
+
// #4088 - not this cohort.)
|
|
16053
|
+
[ONBOARDING_OFFER_CONTROL_VARIANT]: "When this is the start of a new conversation and the user has not yet said what they want to build, welcome them as someone who is now connected to Clipform and offer to build their first form together, then offer these starting points as plain text: lead-gen, feedback, quiz, or something else - ask which one fits, or invite them to describe what they need instead. If the user's first message already states what they want to build, even loosely, skip that offer entirely and go straight into the brief-driven flow below."
|
|
16054
|
+
};
|
|
16055
|
+
function buildOnboardingOffer(variant = ONBOARDING_OFFER_CONTROL_VARIANT) {
|
|
16056
|
+
return ONBOARDING_OFFER_COPY[variant] ?? ONBOARDING_OFFER_COPY[ONBOARDING_OFFER_CONTROL_VARIANT];
|
|
14113
16057
|
}
|
|
14114
16058
|
var ONBOARDING_OFFER_TOOL_CUE = "If the user hasn't said what to build yet, offer to build their first form and suggest starting points: lead-gen, feedback, quiz, or something else.";
|
|
14115
16059
|
|
|
@@ -17907,7 +19851,7 @@ Quiz variants (optional): ${QUIZ_VARIANTS.join(", ")} - returns the variant-spec
|
|
|
17907
19851
|
Optional args by type:
|
|
17908
19852
|
- quiz: topic, question_count (8)
|
|
17909
19853
|
- quiz (variant: personality): topic, categories, question_count (8)
|
|
17910
|
-
- quiz (variant: comprehension):
|
|
19854
|
+
- quiz (variant: comprehension): question_count (8), audience
|
|
17911
19855
|
- interview: purpose, response_format (all), needs_consent (true)
|
|
17912
19856
|
- survey: topic, anonymous (true)
|
|
17913
19857
|
- funnel: outcomes, criteria, needs_contact (true)
|
|
@@ -17922,7 +19866,6 @@ Optional args by type:
|
|
|
17922
19866
|
topic: external_exports.string().optional().describe("Topic or theme (quiz, survey)"),
|
|
17923
19867
|
question_count: external_exports.number().optional().describe("Number of questions, default 8 (quiz types)"),
|
|
17924
19868
|
categories: external_exports.string().optional().describe("Comma-separated outcome categories (quiz variant: personality)"),
|
|
17925
|
-
youtube_url: external_exports.string().optional().describe("YouTube video URL (quiz variant: comprehension)"),
|
|
17926
19869
|
audience: external_exports.string().optional().describe("Target audience (quiz variant: comprehension)"),
|
|
17927
19870
|
purpose: external_exports.string().optional().describe("What you're collecting (interview)"),
|
|
17928
19871
|
response_format: external_exports.enum(["video", "audio", "text", "all"]).optional().describe("How respondents reply (interview)"),
|
|
@@ -18097,6 +20040,12 @@ function createServer(options) {
|
|
|
18097
20040
|
};
|
|
18098
20041
|
});
|
|
18099
20042
|
}
|
|
20043
|
+
const declared = rawServer.getCapabilities();
|
|
20044
|
+
const noListChanged = {};
|
|
20045
|
+
for (const key of ["tools", "prompts", "resources"]) {
|
|
20046
|
+
if (declared[key]) noListChanged[key] = { listChanged: false };
|
|
20047
|
+
}
|
|
20048
|
+
rawServer.registerCapabilities(noListChanged);
|
|
18100
20049
|
if (baseCallToolHandler) {
|
|
18101
20050
|
nativeSetRequestHandler("tools/call", async (request, extra) => {
|
|
18102
20051
|
try {
|
|
@@ -18121,6 +20070,8 @@ export {
|
|
|
18121
20070
|
ReadBuffer,
|
|
18122
20071
|
serializeMessage,
|
|
18123
20072
|
process2 as process,
|
|
20073
|
+
isLegacyRequest,
|
|
20074
|
+
createMcpHandler,
|
|
18124
20075
|
RENDER_TIMING,
|
|
18125
20076
|
MCP_SERVER_IDENTITY,
|
|
18126
20077
|
buildInstructions,
|
|
@@ -18135,4 +20086,4 @@ export {
|
|
|
18135
20086
|
* MIT Licensed
|
|
18136
20087
|
*)
|
|
18137
20088
|
*/
|
|
18138
|
-
//# sourceMappingURL=chunk-
|
|
20089
|
+
//# sourceMappingURL=chunk-5A66GD3U.js.map
|