@warlock.js/ai-tools 5.33.0 → 5.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import { McpServeError } from "../errors.mjs";
2
2
  import { SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
3
+ import { isRecord } from "./result.mjs";
3
4
  import { McpServeRequests } from "./serve-requests.mjs";
4
5
  import { createHash, timingSafeEqual } from "node:crypto";
5
- import { createServer } from "node:http";
6
+ import { createServer, validateHeaderValue } from "node:http";
6
7
  import { isIPv4, isIPv6 } from "node:net";
7
8
 
8
9
  //#region ../../packages/ai-tools/src/mcp/serve-http.ts
@@ -13,7 +14,7 @@ import { isIPv4, isIPv6 } from "node:net";
13
14
  * specification (https://modelcontextprotocol.io/specification/2025-11-25/basic/transports,
14
15
  * checked 2026-10-17) on `node:http`, with no SDK dependency:
15
16
  *
16
- * - one endpoint path; each POST carries exactly one JSON-RPC message
17
+ * - one MCP endpoint path (plus optional authenticated app routes); each MCP POST carries exactly one JSON-RPC message
17
18
  * (batches were removed from the protocol in 2025-06-18 and get 400);
18
19
  * - a request is answered with `Content-Type: application/json` (the spec
19
20
  * lets a server pick JSON or SSE per request); a notification or a
@@ -71,6 +72,35 @@ function hostnameOf(hostHeader) {
71
72
  function digest(value) {
72
73
  return createHash("sha256").update(value, "utf8").digest();
73
74
  }
75
+ /** Resolve exact descendant routes; no route may replace the MCP endpoint. */
76
+ function resolveRoutes(path, routes) {
77
+ const resolved = /* @__PURE__ */ new Map();
78
+ const methods = /* @__PURE__ */ new Set([
79
+ "GET",
80
+ "POST",
81
+ "PUT",
82
+ "PATCH",
83
+ "DELETE",
84
+ "HEAD",
85
+ "OPTIONS"
86
+ ]);
87
+ if (routes.length > 0) {
88
+ let canonical = false;
89
+ try {
90
+ canonical = !path.startsWith("//") && new URL(path, "http://localhost").pathname === path;
91
+ } catch {}
92
+ if (!canonical) throw new McpServeError("MCP http routes require a canonical endpoint pathname.", { type: "config" });
93
+ }
94
+ for (const route of routes) {
95
+ if (!methods.has(route.method) || !/^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/.test(route.path) || route.path.split("/").some((segment) => segment === "." || segment === "..") || typeof route.handler !== "function") throw new McpServeError("Invalid MCP http route method, path or handler.", { type: "config" });
96
+ const fullPath = `${path.replace(/\/+$/, "")}${route.path}`;
97
+ const byMethod = resolved.get(fullPath) ?? /* @__PURE__ */ new Map();
98
+ if (fullPath === path || byMethod.has(route.method)) throw new McpServeError(`MCP http route "${route.method} ${fullPath}" conflicts.`, { type: "collision" });
99
+ byMethod.set(route.method, { ...route });
100
+ resolved.set(fullPath, byMethod);
101
+ }
102
+ return resolved;
103
+ }
74
104
  /** Normalise a configured origin to `scheme://host[:port]`, or throw a config error. */
75
105
  function normalizeOrigin(origin) {
76
106
  try {
@@ -118,9 +148,11 @@ function resolveSettings(transport) {
118
148
  tokenDigests: tokens.map(digest),
119
149
  authorize: auth?.authorize,
120
150
  authenticate: auth?.authenticate,
151
+ onAuthRefusal: transport.onAuthRefusal,
121
152
  authRequired,
122
153
  allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),
123
- allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name)))
154
+ allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),
155
+ routes: resolveRoutes(path, transport.routes ?? [])
124
156
  };
125
157
  }
126
158
  /** Flatten Node's header bag into lower-cased `name → value` strings. */
@@ -157,6 +189,52 @@ function sendRefusal(response, status, code, message, headers = {}) {
157
189
  }
158
190
  }, headers);
159
191
  }
192
+ const AUTH_REFUSAL_HEADERS = /* @__PURE__ */ new Set([
193
+ "www-authenticate",
194
+ "cache-control",
195
+ "retry-after",
196
+ "content-language",
197
+ "x-request-id"
198
+ ]);
199
+ /** Snapshot plain JSON, rejecting cycles and values JSON would silently coerce/drop. */
200
+ function copyRefusalBody(value, ancestors = /* @__PURE__ */ new Set()) {
201
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
202
+ if (typeof value === "number" && Number.isFinite(value)) return value;
203
+ if (typeof value !== "object" || value === null || ancestors.has(value)) throw new TypeError("Invalid auth refusal body.");
204
+ const prototype = Object.getPrototypeOf(value);
205
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) throw new TypeError("Invalid auth refusal body.");
206
+ ancestors.add(value);
207
+ const result = Array.isArray(value) ? Array.from(value, (entry) => copyRefusalBody(entry, ancestors)) : Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, copyRefusalBody(entry, ancestors)]));
208
+ ancestors.delete(value);
209
+ return result;
210
+ }
211
+ /** A custom response can only end a denied exchange; it cannot authorize dispatch. */
212
+ async function sendAuthRefusal(response, settings, context) {
213
+ const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
214
+ if (settings.onAuthRefusal) try {
215
+ const result = await settings.onAuthRefusal(Object.freeze(context));
216
+ if (!isRecord(result)) throw new TypeError("Invalid auth refusal response.");
217
+ const customStatus = result.status;
218
+ const status = customStatus === void 0 ? 401 : customStatus;
219
+ if (typeof status !== "number" || !Number.isInteger(status) || status < 400 || status > 599) throw new TypeError("Invalid auth refusal status.");
220
+ const headers = { ...challenge };
221
+ const customHeaders = result.headers;
222
+ if (customHeaders !== void 0) {
223
+ if (!isRecord(customHeaders)) throw new TypeError("Invalid auth refusal headers.");
224
+ const prototype = Object.getPrototypeOf(customHeaders);
225
+ if (prototype !== Object.prototype && prototype !== null) throw new TypeError("Invalid auth refusal headers.");
226
+ for (const [name, value] of Object.entries(customHeaders)) {
227
+ const normalized = name.toLowerCase();
228
+ if (!AUTH_REFUSAL_HEADERS.has(normalized) || typeof value !== "string") throw new TypeError("Invalid auth refusal header.");
229
+ validateHeaderValue(normalized, value);
230
+ headers[normalized] = value;
231
+ }
232
+ }
233
+ sendJson(response, status, copyRefusalBody(result.body), headers);
234
+ return;
235
+ } catch {}
236
+ sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
237
+ }
160
238
  /** Write an empty-bodied status (202 / 405). */
161
239
  function sendEmpty(response, status, headers = {}) {
162
240
  if (response.headersSent || response.destroyed) return;
@@ -243,13 +321,15 @@ async function startHttpServer(transport, dispatch) {
243
321
  };
244
322
  }
245
323
  /**
246
- * Validate and answer one HTTP request: path → Host / Origin → auth →
247
- * method → headers → body → JSON-RPC dispatch.
324
+ * Validate and answer one HTTP request: Host / Origin → path → auth →
325
+ * method → route handler or MCP headers/body/JSON-RPC dispatch.
248
326
  */
249
327
  async function handleRequest(request, response, settings, dispatch, signal, requests) {
250
328
  const headers = flattenHeaders(request.headers);
251
329
  const method = (request.method ?? "GET").toUpperCase();
252
- const path = new URL(request.url ?? "/", "http://localhost").pathname;
330
+ const url = new URL(request.url ?? "/", "http://localhost");
331
+ const path = url.pathname;
332
+ const routes = settings.routes.get(path);
253
333
  if (!hostAllowed(headers.host, settings)) {
254
334
  sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Host header not allowed.");
255
335
  return;
@@ -258,7 +338,7 @@ async function handleRequest(request, response, settings, dispatch, signal, requ
258
338
  sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, "Forbidden: Origin not allowed.");
259
339
  return;
260
340
  }
261
- if (path !== settings.path) {
341
+ if (path !== settings.path && !routes) {
262
342
  sendRefusal(response, 404, JSON_RPC_SERVER_ERROR, "Not found.");
263
343
  return;
264
344
  }
@@ -270,8 +350,21 @@ async function handleRequest(request, response, settings, dispatch, signal, requ
270
350
  if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
271
351
  const authentication = await authorized(info, settings);
272
352
  if (!authentication.allowed) {
273
- const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
274
- sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
353
+ await sendAuthRefusal(response, settings, {
354
+ check: authentication.check,
355
+ method,
356
+ path,
357
+ target: routes ? "route" : "mcp"
358
+ });
359
+ return;
360
+ }
361
+ if (routes) {
362
+ const route = routes.get(method);
363
+ if (!route) {
364
+ sendEmpty(response, 405, { allow: [...routes.keys()].sort().join(", ") });
365
+ return;
366
+ }
367
+ await handleRoute(request, response, route, info, url, authentication.principal, signal, settings);
275
368
  return;
276
369
  }
277
370
  if (method !== "POST") {
@@ -347,6 +440,32 @@ async function handleRequest(request, response, settings, dispatch, signal, requ
347
440
  }
348
441
  sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, "Invalid request: not a JSON-RPC message.");
349
442
  }
443
+ /** Run a side route after the shared guards, tracking disconnect and shutdown. */
444
+ async function handleRoute(request, response, route, info, url, principal, shutdown, settings) {
445
+ const disconnected = new AbortController();
446
+ const onClose = () => disconnected.abort();
447
+ response.once("close", onClose);
448
+ try {
449
+ const body = await readBody(request, settings.maxBodyBytes);
450
+ if (body.type === "too-large") {
451
+ sendRefusal(response, 413, JSON_RPC_SERVER_ERROR, `Request body exceeds ${settings.maxBodyBytes} bytes.`, { connection: "close" });
452
+ return;
453
+ }
454
+ const signal = AbortSignal.any([shutdown, disconnected.signal]);
455
+ signal.throwIfAborted();
456
+ const result = await route.handler({
457
+ request: info,
458
+ url,
459
+ principal,
460
+ body: body.text,
461
+ signal
462
+ });
463
+ if (result.body === void 0 || info.method === "HEAD") sendEmpty(response, result.status ?? 200, result.headers);
464
+ else sendJson(response, result.status ?? 200, result.body, result.headers);
465
+ } finally {
466
+ response.off("close", onClose);
467
+ }
468
+ }
350
469
  /**
351
470
  * Host-header check. While bound to loopback only loopback names (plus
352
471
  * `allowedHosts`) are accepted, so a DNS-rebound name pointing at
@@ -384,21 +503,38 @@ async function authorized(info, settings) {
384
503
  const presented = digest(match?.[1]?.trim() ?? "");
385
504
  let matched = false;
386
505
  for (const expected of settings.tokenDigests) matched = timingSafeEqual(presented, expected) || matched;
387
- if (!match || !matched) return { allowed: false };
506
+ if (!match || !matched) return {
507
+ allowed: false,
508
+ check: "bearer"
509
+ };
388
510
  }
389
- try {
390
- if (settings.authorize && await settings.authorize(info) !== true) return { allowed: false };
391
- if (settings.authenticate) {
392
- const principal = await settings.authenticate(info);
393
- return principal === void 0 ? { allowed: false } : {
394
- allowed: true,
395
- principal
396
- };
397
- }
398
- return { allowed: true };
511
+ if (settings.authorize) try {
512
+ if (await settings.authorize(info) !== true) return {
513
+ allowed: false,
514
+ check: "authorize"
515
+ };
399
516
  } catch {
400
- return { allowed: false };
517
+ return {
518
+ allowed: false,
519
+ check: "authorize"
520
+ };
521
+ }
522
+ if (settings.authenticate) try {
523
+ const principal = await settings.authenticate(info);
524
+ return principal === void 0 ? {
525
+ allowed: false,
526
+ check: "authenticate"
527
+ } : {
528
+ allowed: true,
529
+ principal
530
+ };
531
+ } catch {
532
+ return {
533
+ allowed: false,
534
+ check: "authenticate"
535
+ };
401
536
  }
537
+ return { allowed: true };
402
538
  }
403
539
  /**
404
540
  * Stateless HTTP has no connection/session id. Repeat the caller's credential
@@ -1 +1 @@
1
- {"version":3,"file":"serve-http.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve-http.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport {\n createServer,\n type IncomingHttpHeaders,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from \"node:http\";\nimport { isIPv4, isIPv6 } from \"node:net\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpHttpAuth,\n McpHttpRequestInfo,\n McpHttpServeTransport,\n McpServeCallContext,\n} from \"../contracts\";\nimport { McpServeError } from \"../errors\";\nimport { SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { McpServeRequests } from \"./serve-requests\";\n\n/**\n * MCP Streamable HTTP server transport for `ai.mcp.serve`.\n *\n * Implements the server side of the Streamable HTTP transport from the MCP\n * specification (https://modelcontextprotocol.io/specification/2025-11-25/basic/transports,\n * checked 2026-10-17) on `node:http`, with no SDK dependency:\n *\n * - one endpoint path; each POST carries exactly one JSON-RPC message\n * (batches were removed from the protocol in 2025-06-18 and get 400);\n * - a request is answered with `Content-Type: application/json` (the spec\n * lets a server pick JSON or SSE per request); a notification or a\n * response is acknowledged with `202 Accepted` and no body;\n * - stateless: no `MCP-Session-Id` is issued (optional in the spec), so\n * GET (server-initiated SSE stream) and DELETE (session end) answer\n * `405 Method Not Allowed`, as the spec allows;\n * - an unsupported `MCP-Protocol-Version` header gets 400;\n * - security warning items: `Origin` is validated on every request (an\n * invalid one gets 403), the default bind is `127.0.0.1`, and\n * authentication is required for any non-loopback bind.\n */\n\n/** Default bind interface — loopback only. */\nconst DEFAULT_HOST = \"127.0.0.1\";\n\n/** Default endpoint path. */\nconst DEFAULT_PATH = \"/mcp\";\n\n/** Default request body cap: 1 MiB. */\nconst DEFAULT_MAX_BODY_BYTES = 1_048_576;\n\n/** Default time `close()` waits for in-flight requests. */\nconst DEFAULT_DRAIN_TIMEOUT_MS = 5_000;\n\n/** JSON-RPC codes used for transport-level error bodies. */\nconst JSON_RPC_PARSE_ERROR = -32700;\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** Generic server-defined JSON-RPC code for HTTP-level refusals (auth, origin, size). */\nconst JSON_RPC_SERVER_ERROR = -32000;\n\n/** Host names that always mean \"this machine\". */\nconst LOOPBACK_NAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\n\n/** The function the transport pumps each JSON-RPC request through. */\nexport type McpHttpDispatch = (\n request: JsonRpcRequest,\n context: McpServeCallContext,\n) => Promise<JsonRpcResponse>;\n\n/** A bound HTTP endpoint. */\nexport type RunningMcpHttpServer = {\n /** Full endpoint URL. */\n url: string;\n /** Drain in-flight requests (up to the drain timeout) and free the port. Idempotent. */\n close(): Promise<void>;\n};\n\n/**\n * True when `host` names a loopback interface (`localhost`, `127.0.0.0/8`,\n * `::1`, or an IPv4-mapped `::ffff:127.x.x.x`).\n */\nexport function isLoopbackHost(host: string): boolean {\n const normalized = host.trim().toLowerCase();\n\n if (LOOPBACK_NAMES.has(normalized)) {\n return true;\n }\n\n const unbracketed = normalized.replace(/^\\[|\\]$/g, \"\");\n const mapped = unbracketed.startsWith(\"::ffff:\")\n ? unbracketed.slice(\"::ffff:\".length)\n : unbracketed;\n\n return isIPv4(mapped) && mapped.startsWith(\"127.\");\n}\n\n/** Strip the port from a `Host` header value (`[::1]:3000` → `[::1]`, `a.b:80` → `a.b`). */\nfunction hostnameOf(hostHeader: string): string {\n const value = hostHeader.trim().toLowerCase();\n\n if (value.startsWith(\"[\")) {\n const end = value.indexOf(\"]\");\n\n return end === -1 ? value : value.slice(0, end + 1);\n }\n\n const colon = value.lastIndexOf(\":\");\n\n return colon === -1 ? value : value.slice(0, colon);\n}\n\n/** SHA-256 digest — equal-length input for `timingSafeEqual`. */\nfunction digest(value: string): Buffer {\n return createHash(\"sha256\").update(value, \"utf8\").digest();\n}\n\n/**\n * Validated, defaulted transport settings plus the precomputed security\n * material (token digests, origin / host allowlists).\n */\ntype ResolvedSettings = {\n host: string;\n port: number;\n path: string;\n loopback: boolean;\n maxBodyBytes: number;\n drainTimeoutMs: number;\n tokenDigests: Buffer[];\n authorize: McpHttpAuth[\"authorize\"];\n authenticate: McpHttpAuth[\"authenticate\"];\n authRequired: boolean;\n allowedOrigins: Set<string>;\n allowedHosts: Set<string>;\n};\n\n/** Normalise a configured origin to `scheme://host[:port]`, or throw a config error. */\nfunction normalizeOrigin(origin: string): string {\n try {\n return new URL(origin).origin;\n } catch (cause) {\n throw new McpServeError(`allowedOrigins entry \"${origin}\" is not a valid origin URL.`, {\n type: \"config\",\n cause,\n });\n }\n}\n\n/**\n * Validate and default the transport options. Throws {@link McpServeError}\n * (`\"config\"`) for a non-loopback bind without auth, an empty bearer\n * token, an `auth` with neither check, a bad path / port / limit.\n */\nfunction resolveSettings(transport: McpHttpServeTransport): ResolvedSettings {\n const host = transport.host ?? DEFAULT_HOST;\n const port = transport.port ?? 0;\n const path = transport.path ?? DEFAULT_PATH;\n const loopback = isLoopbackHost(host);\n const auth = transport.auth;\n\n if (!Number.isInteger(port) || port < 0 || port > 65_535) {\n throw new McpServeError(`MCP http port must be an integer in 0–65535 (got ${port}).`, {\n type: \"config\",\n });\n }\n\n if (!path.startsWith(\"/\")) {\n throw new McpServeError(`MCP http path must start with \"/\" (got \"${path}\").`, {\n type: \"config\",\n });\n }\n\n const maxBodyBytes = transport.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n\n if (!Number.isInteger(maxBodyBytes) || maxBodyBytes <= 0) {\n throw new McpServeError(\"MCP http maxBodyBytes must be a positive integer.\", {\n type: \"config\",\n });\n }\n\n const drainTimeoutMs = transport.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n\n if (!Number.isFinite(drainTimeoutMs) || drainTimeoutMs < 0) {\n throw new McpServeError(\"MCP http drainTimeoutMs must be zero or a positive number.\", {\n type: \"config\",\n });\n }\n\n const tokens =\n auth?.bearer === undefined\n ? []\n : typeof auth.bearer === \"string\"\n ? [auth.bearer]\n : [...auth.bearer];\n\n if (tokens.some((token) => token.length === 0)) {\n throw new McpServeError(\"MCP http auth.bearer tokens must be non-empty strings.\", {\n type: \"config\",\n });\n }\n\n if (auth && auth.bearer !== undefined && tokens.length === 0) {\n throw new McpServeError(\"MCP http auth.bearer needs at least one token.\", { type: \"config\" });\n }\n\n if (auth && tokens.length === 0 && !auth.authorize && !auth.authenticate) {\n throw new McpServeError(\"MCP http auth needs `bearer`, `authorize` and/or `authenticate`.\", {\n type: \"config\",\n });\n }\n\n const authRequired =\n tokens.length > 0 || auth?.authorize !== undefined || auth?.authenticate !== undefined;\n\n if (!loopback && !authRequired) {\n throw new McpServeError(\n `Refusing to serve MCP over http on non-loopback host \"${host}\" without auth. ` +\n \"Set transport.auth ({ bearer }, { authorize } or { authenticate }), or bind 127.0.0.1.\",\n { type: \"config\", context: { host } },\n );\n }\n\n return {\n host,\n port,\n path,\n loopback,\n maxBodyBytes,\n drainTimeoutMs,\n tokenDigests: tokens.map(digest),\n authorize: auth?.authorize,\n authenticate: auth?.authenticate,\n authRequired,\n allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),\n allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),\n };\n}\n\n/** Flatten Node's header bag into lower-cased `name → value` strings. */\nfunction flattenHeaders(headers: IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n\n for (const [name, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n\n flat[name.toLowerCase()] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n return flat;\n}\n\n/** Write a JSON body with a status code. */\nfunction sendJson(\n response: ServerResponse,\n status: number,\n body: unknown,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n\n const payload = JSON.stringify(body);\n\n response.writeHead(status, {\n ...headers,\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(payload).toString(),\n });\n response.end(payload);\n}\n\n/**\n * Write an HTTP-level refusal: the status plus a JSON-RPC error body with\n * no request id (the spec allows such a body on 4xx transport errors).\n */\nfunction sendRefusal(\n response: ServerResponse,\n status: number,\n code: number,\n message: string,\n headers: Record<string, string> = {},\n): void {\n sendJson(response, status, { jsonrpc: \"2.0\", id: null, error: { code, message } }, headers);\n}\n\n/** Write an empty-bodied status (202 / 405). */\nfunction sendEmpty(\n response: ServerResponse,\n status: number,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n\n response.writeHead(status, headers);\n response.end();\n}\n\n/**\n * Bind an MCP Streamable HTTP endpoint and pump each JSON-RPC request\n * through `dispatch`. Resolves once the port is bound.\n *\n * Throws {@link McpServeError}: `\"config\"` for invalid / unsafe options\n * (checked before anything binds), `\"listen\"` when binding fails.\n *\n * @param transport - The http transport options.\n * @param dispatch - Maps one JSON-RPC request (plus call context) to its response.\n */\nexport async function startHttpServer(\n transport: McpHttpServeTransport,\n dispatch: McpHttpDispatch,\n): Promise<RunningMcpHttpServer> {\n const settings = resolveSettings(transport);\n const shutdown = new AbortController();\n const requests = new McpServeRequests();\n const inFlight = new Set<Promise<void>>();\n let closing: Promise<void> | undefined;\n\n const server: Server = createServer((request, response) => {\n if (closing) {\n sendRefusal(response, 503, JSON_RPC_SERVER_ERROR, \"Server is shutting down.\", {\n connection: \"close\",\n });\n\n return;\n }\n\n const work = handleRequest(\n request,\n response,\n settings,\n dispatch,\n shutdown.signal,\n requests,\n ).catch(() => {\n sendRefusal(response, 500, JSON_RPC_SERVER_ERROR, \"Internal server error.\");\n });\n\n inFlight.add(work);\n void work.finally(() => {\n inFlight.delete(work);\n\n if (closing) {\n server.closeIdleConnections();\n }\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const onError = (cause: Error) => {\n reject(\n new McpServeError(\n `MCP http server could not listen on ${settings.host}:${settings.port}: ${cause.message}`,\n { type: \"listen\", cause, context: { host: settings.host, port: settings.port } },\n ),\n );\n };\n\n server.once(\"error\", onError);\n server.listen(settings.port, settings.host, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n\n const address = server.address();\n const boundPort = typeof address === \"object\" && address !== null ? address.port : settings.port;\n const urlHost = isIPv6(settings.host) ? `[${settings.host}]` : settings.host;\n const url = `http://${urlHost}:${boundPort}${settings.path}`;\n\n const close = (): Promise<void> => {\n if (closing) {\n return closing;\n }\n\n closing = (async () => {\n const closed = new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n server.closeIdleConnections();\n\n if (inFlight.size > 0) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), settings.drainTimeoutMs);\n });\n\n const outcome = await Promise.race([\n Promise.allSettled([...inFlight]).then(() => \"drained\" as const),\n timedOut,\n ]);\n\n clearTimeout(timer);\n\n if (outcome === \"timeout\") {\n shutdown.abort();\n server.closeAllConnections();\n }\n }\n\n server.closeIdleConnections();\n await closed;\n })();\n\n return closing;\n };\n\n return { url, close };\n}\n\n/**\n * Validate and answer one HTTP request: path → Host / Origin → auth →\n * method → headers → body → JSON-RPC dispatch.\n */\nasync function handleRequest(\n request: IncomingMessage,\n response: ServerResponse,\n settings: ResolvedSettings,\n dispatch: McpHttpDispatch,\n signal: AbortSignal,\n requests: McpServeRequests,\n): Promise<void> {\n const headers = flattenHeaders(request.headers);\n const method = (request.method ?? \"GET\").toUpperCase();\n const path = new URL(request.url ?? \"/\", \"http://localhost\").pathname;\n\n // DNS-rebinding guards apply to every request on every path.\n if (!hostAllowed(headers.host, settings)) {\n sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, \"Forbidden: Host header not allowed.\");\n\n return;\n }\n\n if (!originAllowed(headers.origin, settings)) {\n sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, \"Forbidden: Origin not allowed.\");\n\n return;\n }\n\n if (path !== settings.path) {\n sendRefusal(response, 404, JSON_RPC_SERVER_ERROR, \"Not found.\");\n\n return;\n }\n\n const info: McpHttpRequestInfo = { method, path, headers };\n\n if (request.socket.remoteAddress !== undefined) {\n info.remoteAddress = request.socket.remoteAddress;\n }\n\n const authentication = await authorized(info, settings);\n\n if (!authentication.allowed) {\n const challenge: Record<string, string> =\n settings.tokenDigests.length > 0 || settings.authenticate\n ? { \"www-authenticate\": 'Bearer realm=\"mcp\"' }\n : {};\n\n sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, \"Unauthorized.\", challenge);\n\n return;\n }\n\n if (method !== \"POST\") {\n // Stateless server: no server-initiated SSE stream (GET) and no\n // session to terminate (DELETE). The spec allows 405 for both.\n sendEmpty(response, 405, { allow: \"POST\" });\n\n return;\n }\n\n const protocolVersion = headers[\"mcp-protocol-version\"];\n\n if (protocolVersion !== undefined && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {\n sendRefusal(\n response,\n 400,\n JSON_RPC_SERVER_ERROR,\n `Unsupported MCP-Protocol-Version \"${protocolVersion}\" (supported: ${SUPPORTED_PROTOCOL_VERSIONS.join(\", \")}).`,\n );\n\n return;\n }\n\n if (!isJsonContentType(headers[\"content-type\"])) {\n sendRefusal(response, 415, JSON_RPC_SERVER_ERROR, \"Content-Type must be application/json.\");\n\n return;\n }\n\n if (!acceptsJson(headers.accept)) {\n sendRefusal(response, 406, JSON_RPC_SERVER_ERROR, \"Accept must allow application/json.\");\n\n return;\n }\n\n const body = await readBody(request, settings.maxBodyBytes);\n\n if (body.type === \"too-large\") {\n sendRefusal(\n response,\n 413,\n JSON_RPC_SERVER_ERROR,\n `Request body exceeds ${settings.maxBodyBytes} bytes.`,\n { connection: \"close\" },\n );\n\n return;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(body.text);\n } catch {\n sendRefusal(response, 400, JSON_RPC_PARSE_ERROR, \"Parse error: body is not valid JSON.\");\n\n return;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n sendRefusal(\n response,\n 400,\n JSON_RPC_INVALID_REQUEST,\n \"Invalid request: the body must be one JSON-RPC message (batches are not supported).\",\n );\n\n return;\n }\n\n const record = message as Record<string, unknown>;\n\n if (record.jsonrpc !== \"2.0\") {\n sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, 'Invalid request: jsonrpc must be \"2.0\".');\n\n return;\n }\n\n const id = record.id;\n\n if (typeof record.method === \"string\") {\n if (typeof id !== \"string\" && typeof id !== \"number\") {\n // Only the same caller credentials can cancel an in-flight request.\n requests.cancel(record, cancellationScope(info));\n sendEmpty(response, 202);\n\n return;\n }\n\n const rpcRequest: JsonRpcRequest = { jsonrpc: \"2.0\", id, method: record.method };\n\n if (record.params !== undefined) {\n rpcRequest.params = record.params;\n }\n\n const disconnected = new AbortController();\n const onClose = () => disconnected.abort();\n response.once(\"close\", onClose);\n\n try {\n const rpcResponse = await requests.run(\n rpcRequest,\n {\n request: info,\n principal: authentication.principal,\n signal: AbortSignal.any([signal, disconnected.signal]),\n },\n (context) => dispatch(rpcRequest, context),\n cancellationScope(info),\n );\n\n if (rpcResponse === undefined) {\n // End the HTTP exchange without a JSON-RPC response for cancelled work.\n sendEmpty(response, 202);\n } else {\n sendJson(response, 200, rpcResponse);\n }\n } finally {\n response.off(\"close\", onClose);\n }\n\n return;\n }\n\n if (\n (typeof id === \"string\" || typeof id === \"number\") &&\n (\"result\" in record || \"error\" in record)\n ) {\n // A response to a server-initiated request; this server sends none, so acknowledge.\n sendEmpty(response, 202);\n\n return;\n }\n\n sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, \"Invalid request: not a JSON-RPC message.\");\n}\n\n/**\n * Host-header check. While bound to loopback only loopback names (plus\n * `allowedHosts`) are accepted, so a DNS-rebound name pointing at\n * 127.0.0.1 is refused. Off loopback the check applies only when\n * `allowedHosts` is set.\n */\nfunction hostAllowed(hostHeader: string | undefined, settings: ResolvedSettings): boolean {\n if (!settings.loopback && settings.allowedHosts.size === 0) {\n return true;\n }\n\n if (hostHeader === undefined) {\n return false;\n }\n\n const name = hostnameOf(hostHeader);\n\n if (settings.allowedHosts.has(name)) {\n return true;\n }\n\n return settings.loopback && isLoopbackHost(name);\n}\n\n/**\n * Origin check (MCP Streamable HTTP security warning). No `Origin` header —\n * a non-browser client — passes; otherwise the origin must be a loopback\n * `http(s)` origin or listed in `allowedOrigins`. `null` and unparsable\n * origins are refused.\n */\nfunction originAllowed(origin: string | undefined, settings: ResolvedSettings): boolean {\n if (origin === undefined) {\n return true;\n }\n\n let parsed: URL;\n\n try {\n parsed = new URL(origin);\n } catch {\n return false;\n }\n\n if (settings.allowedOrigins.has(parsed.origin)) {\n return true;\n }\n\n return (\n (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") && isLoopbackHost(parsed.hostname)\n );\n}\n\n/**\n * Run the configured auth. Bearer tokens are compared in constant time\n * over SHA-256 digests (equal-length buffers for `timingSafeEqual`), and\n * every configured token is compared so the match position does not leak.\n * An `authorize` hook that throws denies the request.\n */\ntype Authentication = { allowed: false } | { allowed: true; principal?: unknown };\n\nasync function authorized(\n info: McpHttpRequestInfo,\n settings: ResolvedSettings,\n): Promise<Authentication> {\n if (!settings.authRequired) {\n return { allowed: true };\n }\n\n if (settings.tokenDigests.length > 0) {\n const match = /^Bearer[ \\t]+(.+)$/i.exec(info.headers.authorization ?? \"\");\n const presented = digest(match?.[1]?.trim() ?? \"\");\n let matched = false;\n\n for (const expected of settings.tokenDigests) {\n matched = timingSafeEqual(presented, expected) || matched;\n }\n\n if (!match || !matched) {\n return { allowed: false };\n }\n }\n\n try {\n if (settings.authorize && (await settings.authorize(info)) !== true) {\n return { allowed: false };\n }\n\n if (settings.authenticate) {\n const principal = await settings.authenticate(info);\n\n return principal === undefined ? { allowed: false } : { allowed: true, principal };\n }\n\n return { allowed: true };\n } catch {\n return { allowed: false };\n }\n}\n\n/**\n * Stateless HTTP has no connection/session id. Repeat the caller's credential\n * and application headers on notifications; per-message transport headers do\n * not identify a caller. Hash the material rather than retaining secrets as keys.\n */\nfunction cancellationScope(info: McpHttpRequestInfo): string {\n const transportHeaders = new Set([\n \"host\",\n \"origin\",\n \"accept\",\n \"accept-encoding\",\n \"accept-language\",\n \"connection\",\n \"content-type\",\n \"content-length\",\n \"transfer-encoding\",\n \"mcp-protocol-version\",\n \"user-agent\",\n ]);\n const credentials = Object.entries(info.headers)\n .filter(([name]) => !transportHeaders.has(name))\n .sort(([left], [right]) => left.localeCompare(right));\n\n return digest(JSON.stringify([info.remoteAddress, credentials])).toString(\"hex\");\n}\n\n/** True for `application/json` (any parameters, e.g. `; charset=utf-8`). */\nfunction isJsonContentType(contentType: string | undefined): boolean {\n return contentType?.split(\";\")[0]?.trim().toLowerCase() === \"application/json\";\n}\n\n/**\n * True when the `Accept` header allows a JSON reply. Clients are required\n * to list both `application/json` and `text/event-stream`; this server\n * always answers JSON, so it only insists on that half. A missing header\n * is accepted.\n */\nfunction acceptsJson(accept: string | undefined): boolean {\n if (accept === undefined || accept.trim() === \"\") {\n return true;\n }\n\n return accept\n .split(\",\")\n .map((part) => part.split(\";\")[0]?.trim().toLowerCase())\n .some((type) => type === \"application/json\" || type === \"application/*\" || type === \"*/*\");\n}\n\n/** The outcome of reading a request body. */\ntype BodyResult = { type: \"ok\"; text: string } | { type: \"too-large\" };\n\n/**\n * Read the request body as UTF-8, refusing anything over `limit` bytes —\n * up front from `Content-Length`, and while streaming for chunked bodies.\n * A refused body is left unread; the 413 response closes the connection.\n */\nfunction readBody(request: IncomingMessage, limit: number): Promise<BodyResult> {\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > limit) {\n return Promise.resolve({ type: \"too-large\" });\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n const finish = (result: BodyResult) => {\n if (!done) {\n done = true;\n resolve(result);\n }\n };\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) {\n return;\n }\n\n size += chunk.length;\n\n if (size > limit) {\n request.pause();\n finish({ type: \"too-large\" });\n\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => finish({ type: \"ok\", text: Buffer.concat(chunks).toString(\"utf8\") }));\n request.on(\"error\", (error) => {\n if (!done) {\n done = true;\n reject(error);\n }\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"serve-http.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve-http.ts"],"sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport {\n createServer,\n validateHeaderValue,\n type IncomingHttpHeaders,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from \"node:http\";\nimport { isIPv4, isIPv6 } from \"node:net\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpHttpAuth,\n McpHttpAuthRefusalBody,\n McpHttpAuthRefusalContext,\n McpHttpRequestInfo,\n McpHttpRoute,\n McpHttpServeTransport,\n McpServeCallContext,\n} from \"../contracts\";\nimport { McpServeError } from \"../errors\";\nimport { SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { McpServeRequests } from \"./serve-requests\";\nimport { isRecord } from \"./result\";\n\n/**\n * MCP Streamable HTTP server transport for `ai.mcp.serve`.\n *\n * Implements the server side of the Streamable HTTP transport from the MCP\n * specification (https://modelcontextprotocol.io/specification/2025-11-25/basic/transports,\n * checked 2026-10-17) on `node:http`, with no SDK dependency:\n *\n * - one MCP endpoint path (plus optional authenticated app routes); each MCP POST carries exactly one JSON-RPC message\n * (batches were removed from the protocol in 2025-06-18 and get 400);\n * - a request is answered with `Content-Type: application/json` (the spec\n * lets a server pick JSON or SSE per request); a notification or a\n * response is acknowledged with `202 Accepted` and no body;\n * - stateless: no `MCP-Session-Id` is issued (optional in the spec), so\n * GET (server-initiated SSE stream) and DELETE (session end) answer\n * `405 Method Not Allowed`, as the spec allows;\n * - an unsupported `MCP-Protocol-Version` header gets 400;\n * - security warning items: `Origin` is validated on every request (an\n * invalid one gets 403), the default bind is `127.0.0.1`, and\n * authentication is required for any non-loopback bind.\n */\n\n/** Default bind interface — loopback only. */\nconst DEFAULT_HOST = \"127.0.0.1\";\n\n/** Default endpoint path. */\nconst DEFAULT_PATH = \"/mcp\";\n\n/** Default request body cap: 1 MiB. */\nconst DEFAULT_MAX_BODY_BYTES = 1_048_576;\n\n/** Default time `close()` waits for in-flight requests. */\nconst DEFAULT_DRAIN_TIMEOUT_MS = 5_000;\n\n/** JSON-RPC codes used for transport-level error bodies. */\nconst JSON_RPC_PARSE_ERROR = -32700;\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** Generic server-defined JSON-RPC code for HTTP-level refusals (auth, origin, size). */\nconst JSON_RPC_SERVER_ERROR = -32000;\n\n/** Host names that always mean \"this machine\". */\nconst LOOPBACK_NAMES = new Set([\"localhost\", \"127.0.0.1\", \"::1\", \"[::1]\"]);\n\n/** The function the transport pumps each JSON-RPC request through. */\nexport type McpHttpDispatch = (\n request: JsonRpcRequest,\n context: McpServeCallContext,\n) => Promise<JsonRpcResponse>;\n\n/** A bound HTTP endpoint. */\nexport type RunningMcpHttpServer = {\n /** Full endpoint URL. */\n url: string;\n /** Drain in-flight requests (up to the drain timeout) and free the port. Idempotent. */\n close(): Promise<void>;\n};\n\n/**\n * True when `host` names a loopback interface (`localhost`, `127.0.0.0/8`,\n * `::1`, or an IPv4-mapped `::ffff:127.x.x.x`).\n */\nexport function isLoopbackHost(host: string): boolean {\n const normalized = host.trim().toLowerCase();\n\n if (LOOPBACK_NAMES.has(normalized)) {\n return true;\n }\n\n const unbracketed = normalized.replace(/^\\[|\\]$/g, \"\");\n const mapped = unbracketed.startsWith(\"::ffff:\")\n ? unbracketed.slice(\"::ffff:\".length)\n : unbracketed;\n\n return isIPv4(mapped) && mapped.startsWith(\"127.\");\n}\n\n/** Strip the port from a `Host` header value (`[::1]:3000` → `[::1]`, `a.b:80` → `a.b`). */\nfunction hostnameOf(hostHeader: string): string {\n const value = hostHeader.trim().toLowerCase();\n\n if (value.startsWith(\"[\")) {\n const end = value.indexOf(\"]\");\n\n return end === -1 ? value : value.slice(0, end + 1);\n }\n\n const colon = value.lastIndexOf(\":\");\n\n return colon === -1 ? value : value.slice(0, colon);\n}\n\n/** SHA-256 digest — equal-length input for `timingSafeEqual`. */\nfunction digest(value: string): Buffer {\n return createHash(\"sha256\").update(value, \"utf8\").digest();\n}\n\n/**\n * Validated, defaulted transport settings plus the precomputed security\n * material (token digests, origin / host allowlists).\n */\ntype ResolvedSettings = {\n host: string;\n port: number;\n path: string;\n loopback: boolean;\n maxBodyBytes: number;\n drainTimeoutMs: number;\n tokenDigests: Buffer[];\n authorize: McpHttpAuth[\"authorize\"];\n authenticate: McpHttpAuth[\"authenticate\"];\n onAuthRefusal: McpHttpServeTransport[\"onAuthRefusal\"];\n authRequired: boolean;\n allowedOrigins: Set<string>;\n allowedHosts: Set<string>;\n routes: Map<string, Map<string, McpHttpRoute>>;\n};\n\n/** Resolve exact descendant routes; no route may replace the MCP endpoint. */\nfunction resolveRoutes(\n path: string,\n routes: readonly McpHttpRoute[],\n): Map<string, Map<string, McpHttpRoute>> {\n const resolved = new Map<string, Map<string, McpHttpRoute>>();\n const methods = new Set([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"HEAD\", \"OPTIONS\"]);\n\n if (routes.length > 0) {\n let canonical = false;\n\n try {\n canonical = !path.startsWith(\"//\") && new URL(path, \"http://localhost\").pathname === path;\n } catch {\n // Invalid URLs are configuration errors too, before the server binds.\n }\n\n if (!canonical) {\n throw new McpServeError(\"MCP http routes require a canonical endpoint pathname.\", {\n type: \"config\",\n });\n }\n }\n\n for (const route of routes) {\n if (\n !methods.has(route.method) ||\n !/^\\/[A-Za-z0-9._~-]+(?:\\/[A-Za-z0-9._~-]+)*$/.test(route.path) ||\n route.path.split(\"/\").some((segment) => segment === \".\" || segment === \"..\") ||\n typeof route.handler !== \"function\"\n ) {\n throw new McpServeError(\"Invalid MCP http route method, path or handler.\", {\n type: \"config\",\n });\n }\n\n const fullPath = `${path.replace(/\\/+$/, \"\")}${route.path}`;\n const byMethod = resolved.get(fullPath) ?? new Map<string, McpHttpRoute>();\n\n if (fullPath === path || byMethod.has(route.method)) {\n throw new McpServeError(`MCP http route \"${route.method} ${fullPath}\" conflicts.`, {\n type: \"collision\",\n });\n }\n\n byMethod.set(route.method, { ...route });\n resolved.set(fullPath, byMethod);\n }\n\n return resolved;\n}\n\n/** Normalise a configured origin to `scheme://host[:port]`, or throw a config error. */\nfunction normalizeOrigin(origin: string): string {\n try {\n return new URL(origin).origin;\n } catch (cause) {\n throw new McpServeError(`allowedOrigins entry \"${origin}\" is not a valid origin URL.`, {\n type: \"config\",\n cause,\n });\n }\n}\n\n/**\n * Validate and default the transport options. Throws {@link McpServeError}\n * (`\"config\"`) for a non-loopback bind without auth, an empty bearer\n * token, an `auth` with neither check, a bad path / port / limit.\n */\nfunction resolveSettings(transport: McpHttpServeTransport): ResolvedSettings {\n const host = transport.host ?? DEFAULT_HOST;\n const port = transport.port ?? 0;\n const path = transport.path ?? DEFAULT_PATH;\n const loopback = isLoopbackHost(host);\n const auth = transport.auth;\n\n if (!Number.isInteger(port) || port < 0 || port > 65_535) {\n throw new McpServeError(`MCP http port must be an integer in 0–65535 (got ${port}).`, {\n type: \"config\",\n });\n }\n\n if (!path.startsWith(\"/\")) {\n throw new McpServeError(`MCP http path must start with \"/\" (got \"${path}\").`, {\n type: \"config\",\n });\n }\n\n const maxBodyBytes = transport.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;\n\n if (!Number.isInteger(maxBodyBytes) || maxBodyBytes <= 0) {\n throw new McpServeError(\"MCP http maxBodyBytes must be a positive integer.\", {\n type: \"config\",\n });\n }\n\n const drainTimeoutMs = transport.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;\n\n if (!Number.isFinite(drainTimeoutMs) || drainTimeoutMs < 0) {\n throw new McpServeError(\"MCP http drainTimeoutMs must be zero or a positive number.\", {\n type: \"config\",\n });\n }\n\n const tokens =\n auth?.bearer === undefined\n ? []\n : typeof auth.bearer === \"string\"\n ? [auth.bearer]\n : [...auth.bearer];\n\n if (tokens.some((token) => token.length === 0)) {\n throw new McpServeError(\"MCP http auth.bearer tokens must be non-empty strings.\", {\n type: \"config\",\n });\n }\n\n if (auth && auth.bearer !== undefined && tokens.length === 0) {\n throw new McpServeError(\"MCP http auth.bearer needs at least one token.\", { type: \"config\" });\n }\n\n if (auth && tokens.length === 0 && !auth.authorize && !auth.authenticate) {\n throw new McpServeError(\"MCP http auth needs `bearer`, `authorize` and/or `authenticate`.\", {\n type: \"config\",\n });\n }\n\n const authRequired =\n tokens.length > 0 || auth?.authorize !== undefined || auth?.authenticate !== undefined;\n\n if (!loopback && !authRequired) {\n throw new McpServeError(\n `Refusing to serve MCP over http on non-loopback host \"${host}\" without auth. ` +\n \"Set transport.auth ({ bearer }, { authorize } or { authenticate }), or bind 127.0.0.1.\",\n { type: \"config\", context: { host } },\n );\n }\n\n return {\n host,\n port,\n path,\n loopback,\n maxBodyBytes,\n drainTimeoutMs,\n tokenDigests: tokens.map(digest),\n authorize: auth?.authorize,\n authenticate: auth?.authenticate,\n onAuthRefusal: transport.onAuthRefusal,\n authRequired,\n allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),\n allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),\n routes: resolveRoutes(path, transport.routes ?? []),\n };\n}\n\n/** Flatten Node's header bag into lower-cased `name → value` strings. */\nfunction flattenHeaders(headers: IncomingHttpHeaders): Record<string, string> {\n const flat: Record<string, string> = {};\n\n for (const [name, value] of Object.entries(headers)) {\n if (value === undefined) {\n continue;\n }\n\n flat[name.toLowerCase()] = Array.isArray(value) ? value.join(\", \") : value;\n }\n\n return flat;\n}\n\n/** Write a JSON body with a status code. */\nfunction sendJson(\n response: ServerResponse,\n status: number,\n body: unknown,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n\n const payload = JSON.stringify(body);\n\n response.writeHead(status, {\n ...headers,\n \"content-type\": \"application/json\",\n \"content-length\": Buffer.byteLength(payload).toString(),\n });\n response.end(payload);\n}\n\n/**\n * Write an HTTP-level refusal: the status plus a JSON-RPC error body with\n * no request id (the spec allows such a body on 4xx transport errors).\n */\nfunction sendRefusal(\n response: ServerResponse,\n status: number,\n code: number,\n message: string,\n headers: Record<string, string> = {},\n): void {\n sendJson(response, status, { jsonrpc: \"2.0\", id: null, error: { code, message } }, headers);\n}\n\nconst AUTH_REFUSAL_HEADERS = new Set([\n \"www-authenticate\",\n \"cache-control\",\n \"retry-after\",\n \"content-language\",\n \"x-request-id\",\n]);\n\n/** Snapshot plain JSON, rejecting cycles and values JSON would silently coerce/drop. */\nfunction copyRefusalBody(value: unknown, ancestors = new Set<object>()): McpHttpAuthRefusalBody {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return value;\n if (typeof value === \"number\" && Number.isFinite(value)) return value;\n if (typeof value !== \"object\" || value === null || ancestors.has(value)) {\n throw new TypeError(\"Invalid auth refusal body.\");\n }\n\n const prototype: unknown = Object.getPrototypeOf(value);\n if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {\n throw new TypeError(\"Invalid auth refusal body.\");\n }\n\n ancestors.add(value);\n const result = Array.isArray(value)\n ? Array.from(value, (entry: unknown) => copyRefusalBody(entry, ancestors))\n : Object.fromEntries(\n Object.entries(value).map(([key, entry]) => [key, copyRefusalBody(entry, ancestors)]),\n );\n ancestors.delete(value);\n return result;\n}\n\n/** A custom response can only end a denied exchange; it cannot authorize dispatch. */\nasync function sendAuthRefusal(\n response: ServerResponse,\n settings: ResolvedSettings,\n context: McpHttpAuthRefusalContext,\n): Promise<void> {\n const challenge: Record<string, string> =\n settings.tokenDigests.length > 0 || settings.authenticate\n ? { \"www-authenticate\": 'Bearer realm=\"mcp\"' }\n : {};\n\n if (settings.onAuthRefusal) {\n try {\n const result: unknown = await settings.onAuthRefusal(Object.freeze(context));\n if (!isRecord(result)) throw new TypeError(\"Invalid auth refusal response.\");\n const customStatus = result.status;\n const status = customStatus === undefined ? 401 : customStatus;\n if (typeof status !== \"number\" || !Number.isInteger(status) || status < 400 || status > 599) {\n throw new TypeError(\"Invalid auth refusal status.\");\n }\n const headers = { ...challenge };\n const customHeaders = result.headers;\n if (customHeaders !== undefined) {\n if (!isRecord(customHeaders)) throw new TypeError(\"Invalid auth refusal headers.\");\n const prototype: unknown = Object.getPrototypeOf(customHeaders);\n if (prototype !== Object.prototype && prototype !== null) {\n throw new TypeError(\"Invalid auth refusal headers.\");\n }\n for (const [name, value] of Object.entries(customHeaders)) {\n const normalized = name.toLowerCase();\n if (!AUTH_REFUSAL_HEADERS.has(normalized) || typeof value !== \"string\") {\n throw new TypeError(\"Invalid auth refusal header.\");\n }\n // Node's own HTTP validation rejects CR/LF and invalid header characters.\n // https://nodejs.org/docs/latest-v22.x/api/http.html#httpvalidateheadervaluename-value\n validateHeaderValue(normalized, value);\n headers[normalized] = value;\n }\n }\n const body = copyRefusalBody(result.body);\n sendJson(response, status, body, headers);\n return;\n } catch {\n // Neither authentication nor refusal-hook errors become wire content.\n }\n }\n\n sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, \"Unauthorized.\", challenge);\n}\n\n/** Write an empty-bodied status (202 / 405). */\nfunction sendEmpty(\n response: ServerResponse,\n status: number,\n headers: Record<string, string> = {},\n): void {\n if (response.headersSent || response.destroyed) {\n return;\n }\n\n response.writeHead(status, headers);\n response.end();\n}\n\n/**\n * Bind an MCP Streamable HTTP endpoint and pump each JSON-RPC request\n * through `dispatch`. Resolves once the port is bound.\n *\n * Throws {@link McpServeError}: `\"config\"` for invalid / unsafe options\n * (checked before anything binds), `\"listen\"` when binding fails.\n *\n * @param transport - The http transport options.\n * @param dispatch - Maps one JSON-RPC request (plus call context) to its response.\n */\nexport async function startHttpServer(\n transport: McpHttpServeTransport,\n dispatch: McpHttpDispatch,\n): Promise<RunningMcpHttpServer> {\n const settings = resolveSettings(transport);\n const shutdown = new AbortController();\n const requests = new McpServeRequests();\n const inFlight = new Set<Promise<void>>();\n let closing: Promise<void> | undefined;\n\n const server: Server = createServer((request, response) => {\n if (closing) {\n sendRefusal(response, 503, JSON_RPC_SERVER_ERROR, \"Server is shutting down.\", {\n connection: \"close\",\n });\n\n return;\n }\n\n const work = handleRequest(\n request,\n response,\n settings,\n dispatch,\n shutdown.signal,\n requests,\n ).catch(() => {\n sendRefusal(response, 500, JSON_RPC_SERVER_ERROR, \"Internal server error.\");\n });\n\n inFlight.add(work);\n void work.finally(() => {\n inFlight.delete(work);\n\n if (closing) {\n server.closeIdleConnections();\n }\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const onError = (cause: Error) => {\n reject(\n new McpServeError(\n `MCP http server could not listen on ${settings.host}:${settings.port}: ${cause.message}`,\n { type: \"listen\", cause, context: { host: settings.host, port: settings.port } },\n ),\n );\n };\n\n server.once(\"error\", onError);\n server.listen(settings.port, settings.host, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n\n const address = server.address();\n const boundPort = typeof address === \"object\" && address !== null ? address.port : settings.port;\n const urlHost = isIPv6(settings.host) ? `[${settings.host}]` : settings.host;\n const url = `http://${urlHost}:${boundPort}${settings.path}`;\n\n const close = (): Promise<void> => {\n if (closing) {\n return closing;\n }\n\n closing = (async () => {\n const closed = new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n\n server.closeIdleConnections();\n\n if (inFlight.size > 0) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), settings.drainTimeoutMs);\n });\n\n const outcome = await Promise.race([\n Promise.allSettled([...inFlight]).then(() => \"drained\" as const),\n timedOut,\n ]);\n\n clearTimeout(timer);\n\n if (outcome === \"timeout\") {\n shutdown.abort();\n server.closeAllConnections();\n }\n }\n\n server.closeIdleConnections();\n await closed;\n })();\n\n return closing;\n };\n\n return { url, close };\n}\n\n/**\n * Validate and answer one HTTP request: Host / Origin → path → auth →\n * method → route handler or MCP headers/body/JSON-RPC dispatch.\n */\nasync function handleRequest(\n request: IncomingMessage,\n response: ServerResponse,\n settings: ResolvedSettings,\n dispatch: McpHttpDispatch,\n signal: AbortSignal,\n requests: McpServeRequests,\n): Promise<void> {\n const headers = flattenHeaders(request.headers);\n const method = (request.method ?? \"GET\").toUpperCase();\n const url = new URL(request.url ?? \"/\", \"http://localhost\");\n const path = url.pathname;\n const routes = settings.routes.get(path);\n\n // DNS-rebinding guards apply to every request on every path.\n if (!hostAllowed(headers.host, settings)) {\n sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, \"Forbidden: Host header not allowed.\");\n\n return;\n }\n\n if (!originAllowed(headers.origin, settings)) {\n sendRefusal(response, 403, JSON_RPC_SERVER_ERROR, \"Forbidden: Origin not allowed.\");\n\n return;\n }\n\n if (path !== settings.path && !routes) {\n sendRefusal(response, 404, JSON_RPC_SERVER_ERROR, \"Not found.\");\n\n return;\n }\n\n const info: McpHttpRequestInfo = { method, path, headers };\n\n if (request.socket.remoteAddress !== undefined) {\n info.remoteAddress = request.socket.remoteAddress;\n }\n\n const authentication = await authorized(info, settings);\n\n if (!authentication.allowed) {\n await sendAuthRefusal(response, settings, {\n check: authentication.check,\n method,\n path,\n target: routes ? \"route\" : \"mcp\",\n });\n\n return;\n }\n\n if (routes) {\n const route = routes.get(method);\n\n if (!route) {\n sendEmpty(response, 405, { allow: [...routes.keys()].sort().join(\", \") });\n\n return;\n }\n\n await handleRoute(\n request,\n response,\n route,\n info,\n url,\n authentication.principal,\n signal,\n settings,\n );\n\n return;\n }\n\n if (method !== \"POST\") {\n // Stateless server: no server-initiated SSE stream (GET) and no\n // session to terminate (DELETE). The spec allows 405 for both.\n sendEmpty(response, 405, { allow: \"POST\" });\n\n return;\n }\n\n const protocolVersion = headers[\"mcp-protocol-version\"];\n\n if (protocolVersion !== undefined && !SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {\n sendRefusal(\n response,\n 400,\n JSON_RPC_SERVER_ERROR,\n `Unsupported MCP-Protocol-Version \"${protocolVersion}\" (supported: ${SUPPORTED_PROTOCOL_VERSIONS.join(\", \")}).`,\n );\n\n return;\n }\n\n if (!isJsonContentType(headers[\"content-type\"])) {\n sendRefusal(response, 415, JSON_RPC_SERVER_ERROR, \"Content-Type must be application/json.\");\n\n return;\n }\n\n if (!acceptsJson(headers.accept)) {\n sendRefusal(response, 406, JSON_RPC_SERVER_ERROR, \"Accept must allow application/json.\");\n\n return;\n }\n\n const body = await readBody(request, settings.maxBodyBytes);\n\n if (body.type === \"too-large\") {\n sendRefusal(\n response,\n 413,\n JSON_RPC_SERVER_ERROR,\n `Request body exceeds ${settings.maxBodyBytes} bytes.`,\n { connection: \"close\" },\n );\n\n return;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(body.text);\n } catch {\n sendRefusal(response, 400, JSON_RPC_PARSE_ERROR, \"Parse error: body is not valid JSON.\");\n\n return;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n sendRefusal(\n response,\n 400,\n JSON_RPC_INVALID_REQUEST,\n \"Invalid request: the body must be one JSON-RPC message (batches are not supported).\",\n );\n\n return;\n }\n\n const record = message as Record<string, unknown>;\n\n if (record.jsonrpc !== \"2.0\") {\n sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, 'Invalid request: jsonrpc must be \"2.0\".');\n\n return;\n }\n\n const id = record.id;\n\n if (typeof record.method === \"string\") {\n if (typeof id !== \"string\" && typeof id !== \"number\") {\n // Only the same caller credentials can cancel an in-flight request.\n requests.cancel(record, cancellationScope(info));\n sendEmpty(response, 202);\n\n return;\n }\n\n const rpcRequest: JsonRpcRequest = { jsonrpc: \"2.0\", id, method: record.method };\n\n if (record.params !== undefined) {\n rpcRequest.params = record.params;\n }\n\n const disconnected = new AbortController();\n const onClose = () => disconnected.abort();\n response.once(\"close\", onClose);\n\n try {\n const rpcResponse = await requests.run(\n rpcRequest,\n {\n request: info,\n principal: authentication.principal,\n signal: AbortSignal.any([signal, disconnected.signal]),\n },\n (context) => dispatch(rpcRequest, context),\n cancellationScope(info),\n );\n\n if (rpcResponse === undefined) {\n // End the HTTP exchange without a JSON-RPC response for cancelled work.\n sendEmpty(response, 202);\n } else {\n sendJson(response, 200, rpcResponse);\n }\n } finally {\n response.off(\"close\", onClose);\n }\n\n return;\n }\n\n if (\n (typeof id === \"string\" || typeof id === \"number\") &&\n (\"result\" in record || \"error\" in record)\n ) {\n // A response to a server-initiated request; this server sends none, so acknowledge.\n sendEmpty(response, 202);\n\n return;\n }\n\n sendRefusal(response, 400, JSON_RPC_INVALID_REQUEST, \"Invalid request: not a JSON-RPC message.\");\n}\n\n/** Run a side route after the shared guards, tracking disconnect and shutdown. */\nasync function handleRoute(\n request: IncomingMessage,\n response: ServerResponse,\n route: McpHttpRoute,\n info: McpHttpRequestInfo,\n url: URL,\n principal: unknown,\n shutdown: AbortSignal,\n settings: ResolvedSettings,\n): Promise<void> {\n const disconnected = new AbortController();\n const onClose = () => disconnected.abort();\n response.once(\"close\", onClose);\n\n try {\n const body = await readBody(request, settings.maxBodyBytes);\n\n if (body.type === \"too-large\") {\n sendRefusal(\n response,\n 413,\n JSON_RPC_SERVER_ERROR,\n `Request body exceeds ${settings.maxBodyBytes} bytes.`,\n {\n connection: \"close\",\n },\n );\n\n return;\n }\n\n const signal = AbortSignal.any([shutdown, disconnected.signal]);\n signal.throwIfAborted();\n const result = await route.handler({ request: info, url, principal, body: body.text, signal });\n\n if (result.body === undefined || info.method === \"HEAD\") {\n sendEmpty(response, result.status ?? 200, result.headers);\n } else {\n sendJson(response, result.status ?? 200, result.body, result.headers);\n }\n } finally {\n response.off(\"close\", onClose);\n }\n}\n\n/**\n * Host-header check. While bound to loopback only loopback names (plus\n * `allowedHosts`) are accepted, so a DNS-rebound name pointing at\n * 127.0.0.1 is refused. Off loopback the check applies only when\n * `allowedHosts` is set.\n */\nfunction hostAllowed(hostHeader: string | undefined, settings: ResolvedSettings): boolean {\n if (!settings.loopback && settings.allowedHosts.size === 0) {\n return true;\n }\n\n if (hostHeader === undefined) {\n return false;\n }\n\n const name = hostnameOf(hostHeader);\n\n if (settings.allowedHosts.has(name)) {\n return true;\n }\n\n return settings.loopback && isLoopbackHost(name);\n}\n\n/**\n * Origin check (MCP Streamable HTTP security warning). No `Origin` header —\n * a non-browser client — passes; otherwise the origin must be a loopback\n * `http(s)` origin or listed in `allowedOrigins`. `null` and unparsable\n * origins are refused.\n */\nfunction originAllowed(origin: string | undefined, settings: ResolvedSettings): boolean {\n if (origin === undefined) {\n return true;\n }\n\n let parsed: URL;\n\n try {\n parsed = new URL(origin);\n } catch {\n return false;\n }\n\n if (settings.allowedOrigins.has(parsed.origin)) {\n return true;\n }\n\n return (\n (parsed.protocol === \"http:\" || parsed.protocol === \"https:\") && isLoopbackHost(parsed.hostname)\n );\n}\n\n/**\n * Run the configured auth. Bearer tokens are compared in constant time\n * over SHA-256 digests (equal-length buffers for `timingSafeEqual`), and\n * every configured token is compared so the match position does not leak.\n * An `authorize` hook that throws denies the request.\n */\ntype Authentication =\n | { allowed: false; check: McpHttpAuthRefusalContext[\"check\"] }\n | { allowed: true; principal?: unknown };\n\nasync function authorized(\n info: McpHttpRequestInfo,\n settings: ResolvedSettings,\n): Promise<Authentication> {\n if (!settings.authRequired) {\n return { allowed: true };\n }\n\n if (settings.tokenDigests.length > 0) {\n const match = /^Bearer[ \\t]+(.+)$/i.exec(info.headers.authorization ?? \"\");\n const presented = digest(match?.[1]?.trim() ?? \"\");\n let matched = false;\n\n for (const expected of settings.tokenDigests) {\n matched = timingSafeEqual(presented, expected) || matched;\n }\n\n if (!match || !matched) {\n return { allowed: false, check: \"bearer\" };\n }\n }\n\n if (settings.authorize) {\n try {\n if ((await settings.authorize(info)) !== true) {\n return { allowed: false, check: \"authorize\" };\n }\n } catch {\n return { allowed: false, check: \"authorize\" };\n }\n }\n\n if (settings.authenticate) {\n try {\n const principal = await settings.authenticate(info);\n\n return principal === undefined\n ? { allowed: false, check: \"authenticate\" }\n : { allowed: true, principal };\n } catch {\n return { allowed: false, check: \"authenticate\" };\n }\n }\n\n return { allowed: true };\n}\n\n/**\n * Stateless HTTP has no connection/session id. Repeat the caller's credential\n * and application headers on notifications; per-message transport headers do\n * not identify a caller. Hash the material rather than retaining secrets as keys.\n */\nfunction cancellationScope(info: McpHttpRequestInfo): string {\n const transportHeaders = new Set([\n \"host\",\n \"origin\",\n \"accept\",\n \"accept-encoding\",\n \"accept-language\",\n \"connection\",\n \"content-type\",\n \"content-length\",\n \"transfer-encoding\",\n \"mcp-protocol-version\",\n \"user-agent\",\n ]);\n const credentials = Object.entries(info.headers)\n .filter(([name]) => !transportHeaders.has(name))\n .sort(([left], [right]) => left.localeCompare(right));\n\n return digest(JSON.stringify([info.remoteAddress, credentials])).toString(\"hex\");\n}\n\n/** True for `application/json` (any parameters, e.g. `; charset=utf-8`). */\nfunction isJsonContentType(contentType: string | undefined): boolean {\n return contentType?.split(\";\")[0]?.trim().toLowerCase() === \"application/json\";\n}\n\n/**\n * True when the `Accept` header allows a JSON reply. Clients are required\n * to list both `application/json` and `text/event-stream`; this server\n * always answers JSON, so it only insists on that half. A missing header\n * is accepted.\n */\nfunction acceptsJson(accept: string | undefined): boolean {\n if (accept === undefined || accept.trim() === \"\") {\n return true;\n }\n\n return accept\n .split(\",\")\n .map((part) => part.split(\";\")[0]?.trim().toLowerCase())\n .some((type) => type === \"application/json\" || type === \"application/*\" || type === \"*/*\");\n}\n\n/** The outcome of reading a request body. */\ntype BodyResult = { type: \"ok\"; text: string } | { type: \"too-large\" };\n\n/**\n * Read the request body as UTF-8, refusing anything over `limit` bytes —\n * up front from `Content-Length`, and while streaming for chunked bodies.\n * A refused body is left unread; the 413 response closes the connection.\n */\nfunction readBody(request: IncomingMessage, limit: number): Promise<BodyResult> {\n const declared = Number(request.headers[\"content-length\"]);\n\n if (Number.isFinite(declared) && declared > limit) {\n return Promise.resolve({ type: \"too-large\" });\n }\n\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let done = false;\n\n const finish = (result: BodyResult) => {\n if (!done) {\n done = true;\n resolve(result);\n }\n };\n\n request.on(\"data\", (chunk: Buffer) => {\n if (done) {\n return;\n }\n\n size += chunk.length;\n\n if (size > limit) {\n request.pause();\n finish({ type: \"too-large\" });\n\n return;\n }\n\n chunks.push(chunk);\n });\n request.on(\"end\", () => finish({ type: \"ok\", text: Buffer.concat(chunks).toString(\"utf8\") }));\n request.on(\"error\", (error) => {\n if (!done) {\n done = true;\n reject(error);\n }\n });\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"mappings":";;;;;;;iBA6tBiB;;;;;;;EAOf,OAAO,SAAS,gBAAgB,UAAU,sBAAsB,QAAQ;;;;;;;;;;;;;;wBAe1D,mBACd,QAAQ,gBACR,SAAS,kBACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA6Ja,MACd,QAAQ,gBACR,SAAS;EAAoB,WAAW;IACvC,UAAU;wBACG,MAAM,QAAQ,gBAAgB,SAAS,kBAAkB"}
1
+ {"version":3,"file":"serve.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"mappings":";;;;;;;iBA2vBiB;;;;;;;EAOf,OAAO,SAAS,gBAAgB,UAAU,sBAAsB,QAAQ;;;;;;;;;;;;;;wBAe1D,mBACd,QAAQ,gBACR,SAAS,kBACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA6Ja,MACd,QAAQ,gBACR,SAAS;EAAoB,WAAW;IACvC,UAAU;wBACG,MAAM,QAAQ,gBAAgB,SAAS,kBAAkB"}
package/esm/mcp/serve.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { McpServeError, McpTransportError } from "../errors.mjs";
2
+ import { isMcpError } from "./error.mjs";
2
3
  import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
3
4
  import { assertMcpResult, isMcpResult, isRecord, looksLikeMcpResult, unwrapMcpResult } from "./result.mjs";
4
5
  import { McpServeRequests } from "./serve-requests.mjs";
@@ -138,7 +139,10 @@ var McpServeHandler = class {
138
139
  switch (request.method) {
139
140
  case "initialize": return this.initializeResult(request.params);
140
141
  case "ping": return {};
141
- case "tools/list": return { tools: this.listTools() };
142
+ case "tools/list": return { tools: await this.listTools({
143
+ ...context,
144
+ requestId: request.id
145
+ }) };
142
146
  case "tools/call": return this.callTool(request.params, {
143
147
  ...context,
144
148
  requestId: request.id
@@ -181,9 +185,9 @@ var McpServeHandler = class {
181
185
  * the configured dialect (default `draft-2020-12` — overriding
182
186
  * `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).
183
187
  */
184
- listTools() {
188
+ async listTools(context) {
185
189
  const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;
186
- return this.tools.map(({ name, contract }) => {
190
+ const tools = this.tools.map(({ name, contract }) => {
187
191
  const inputSchema = extractJsonSchema(contract.input, { target }) ?? {
188
192
  type: "object",
189
193
  properties: {}
@@ -195,6 +199,8 @@ var McpServeHandler = class {
195
199
  ...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
196
200
  };
197
201
  });
202
+ context.signal?.throwIfAborted();
203
+ return this.options.listFilter ? this.options.listFilter(context, tools) : tools;
198
204
  }
199
205
  /**
200
206
  * Route a `tools/call` to the named contract's `invoke()` and map the
@@ -208,6 +214,15 @@ var McpServeHandler = class {
208
214
  */
209
215
  async callTool(params, context) {
210
216
  const { name, args } = readCallParams(params);
217
+ if (this.options.validateCallParams) {
218
+ context.signal?.throwIfAborted();
219
+ try {
220
+ await this.options.validateCallParams(context, readRecord(params));
221
+ } catch (cause) {
222
+ if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
223
+ throw cause;
224
+ }
225
+ }
211
226
  const contract = this.byName.get(name);
212
227
  if (!contract) throw new McpTransportError(`Unknown tool "${name}".`, {
213
228
  type: "protocol",
@@ -251,7 +266,13 @@ var McpServeHandler = class {
251
266
  input = validation.value;
252
267
  }
253
268
  context.signal?.throwIfAborted();
254
- const refusal = await this.options.beforeCall(context, contract, input);
269
+ let refusal;
270
+ try {
271
+ refusal = await this.options.beforeCall(context, contract, input);
272
+ } catch (cause) {
273
+ if (isMcpError(cause)) throw new RpcFailure(cause.code, cause.message, cause.data);
274
+ throw cause;
275
+ }
255
276
  if (refusal !== void 0) {
256
277
  try {
257
278
  assertMcpResult(refusal);