@warlock.js/ai-tools 5.31.0 → 5.32.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/cjs/index.cjs +130 -26
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/contracts/http.type.d.mts +4 -5
  5. package/esm/contracts/http.type.d.mts.map +1 -1
  6. package/esm/contracts/index.d.mts +2 -1
  7. package/esm/contracts/mcp-serve.type.d.mts +61 -33
  8. package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
  9. package/esm/contracts/mcp.type.d.mts +29 -19
  10. package/esm/contracts/mcp.type.d.mts.map +1 -1
  11. package/esm/contracts/utility.type.d.mts +7 -8
  12. package/esm/contracts/utility.type.d.mts.map +1 -1
  13. package/esm/contracts/web.type.d.mts +9 -10
  14. package/esm/contracts/web.type.d.mts.map +1 -1
  15. package/esm/errors.d.mts +31 -26
  16. package/esm/errors.d.mts.map +1 -1
  17. package/esm/http/http-request.d.mts +2 -3
  18. package/esm/http/http-request.d.mts.map +1 -1
  19. package/esm/http/http-request.mjs +7 -5
  20. package/esm/http/http-request.mjs.map +1 -1
  21. package/esm/index.d.mts +5 -1
  22. package/esm/index.mjs +2 -1
  23. package/esm/mcp/client.mjs +2 -1
  24. package/esm/mcp/client.mjs.map +1 -1
  25. package/esm/mcp/index.d.mts +5 -4
  26. package/esm/mcp/index.d.mts.map +1 -1
  27. package/esm/mcp/index.mjs +2 -1
  28. package/esm/mcp/index.mjs.map +1 -1
  29. package/esm/mcp/json-schema-to-standard.d.mts +1 -3
  30. package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
  31. package/esm/mcp/protocol.d.mts +1 -2
  32. package/esm/mcp/protocol.d.mts.map +1 -1
  33. package/esm/mcp/result.d.mts +17 -0
  34. package/esm/mcp/result.d.mts.map +1 -0
  35. package/esm/mcp/result.mjs +85 -0
  36. package/esm/mcp/result.mjs.map +1 -0
  37. package/esm/mcp/serve-http.mjs +4 -3
  38. package/esm/mcp/serve-http.mjs.map +1 -1
  39. package/esm/mcp/serve.d.mts +5 -5
  40. package/esm/mcp/serve.d.mts.map +1 -1
  41. package/esm/mcp/serve.mjs +17 -7
  42. package/esm/mcp/serve.mjs.map +1 -1
  43. package/esm/mcp/transport.d.mts +4 -5
  44. package/esm/mcp/transport.d.mts.map +1 -1
  45. package/esm/mcp/transport.mjs +12 -6
  46. package/esm/mcp/transport.mjs.map +1 -1
  47. package/esm/mcp/transport.type.d.mts +2 -2
  48. package/esm/mcp/transport.type.d.mts.map +1 -1
  49. package/esm/register.d.mts +3 -4
  50. package/esm/register.d.mts.map +1 -1
  51. package/esm/utility/calculator.d.mts +1 -3
  52. package/esm/utility/calculator.d.mts.map +1 -1
  53. package/esm/utility/date-time.d.mts +3 -5
  54. package/esm/utility/date-time.d.mts.map +1 -1
  55. package/esm/utility/date-time.mjs +2 -1
  56. package/esm/utility/date-time.mjs.map +1 -1
  57. package/esm/utility/index.d.mts +2 -1
  58. package/esm/utility/index.mjs +1 -1
  59. package/esm/web/fetch-url.d.mts +2 -3
  60. package/esm/web/fetch-url.d.mts.map +1 -1
  61. package/esm/web/fetch-url.mjs +2 -1
  62. package/esm/web/fetch-url.mjs.map +1 -1
  63. package/esm/web/index.d.mts +2 -1
  64. package/esm/web/index.mjs +1 -1
  65. package/esm/web/web-search.d.mts +2 -3
  66. package/esm/web/web-search.d.mts.map +1 -1
  67. package/esm/web/web-search.mjs +3 -4
  68. package/esm/web/web-search.mjs.map +1 -1
  69. package/llms-full.txt +53 -1
  70. package/package.json +2 -2
  71. package/skills/expose-as-mcp-server/SKILL.md +53 -1
@@ -0,0 +1,85 @@
1
+ //#region ../../packages/ai-tools/src/mcp/result.ts
2
+ const NATIVE_RESULT = Symbol("mcpResult");
3
+ /**
4
+ * Opt in to native MCP content, error status, structured output and metadata.
5
+ * Validation happens after invoke(), so malformed payloads become JSON-RPC
6
+ * errors rather than being caught as tool execution errors.
7
+ */
8
+ function mcpResult(result) {
9
+ return { [NATIVE_RESULT]: result };
10
+ }
11
+ /** Read a branded value without accepting lookalike application objects. */
12
+ function isMcpResult(value) {
13
+ return isRecord(value) && NATIVE_RESULT in value;
14
+ }
15
+ /** Get the original payload; no cloning, normalization or field stripping. */
16
+ function unwrapMcpResult(value) {
17
+ return value[NATIVE_RESULT];
18
+ }
19
+ /** Objects with result fields are candidates only when native mode is enabled. */
20
+ function looksLikeMcpResult(value) {
21
+ return isRecord(value) && [
22
+ "content",
23
+ "isError",
24
+ "structuredContent",
25
+ "_meta"
26
+ ].some((key) => key in value);
27
+ }
28
+ /** Non-array object used by the MCP JSON object fields. */
29
+ function isRecord(value) {
30
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31
+ }
32
+ function optionalString(value) {
33
+ return value === void 0 || typeof value === "string";
34
+ }
35
+ function validAnnotations(value) {
36
+ if (value === void 0) return true;
37
+ if (!isRecord(value)) return false;
38
+ return (value.audience === void 0 || Array.isArray(value.audience) && value.audience.every((role) => role === "user" || role === "assistant")) && (value.priority === void 0 || typeof value.priority === "number" && value.priority >= 0 && value.priority <= 1) && optionalString(value.lastModified);
39
+ }
40
+ function validMetadata(value) {
41
+ return (value["_meta"] === void 0 || isRecord(value["_meta"])) && validAnnotations(value.annotations);
42
+ }
43
+ function validIcon(value) {
44
+ return isRecord(value) && typeof value.src === "string" && optionalString(value.mimeType) && (value.sizes === void 0 || Array.isArray(value.sizes) && value.sizes.every((size) => typeof size === "string")) && (value.theme === void 0 || value.theme === "light" || value.theme === "dark");
45
+ }
46
+ function validContent(value) {
47
+ if (!isRecord(value) || !validMetadata(value)) return false;
48
+ switch (value.type) {
49
+ case "text": return typeof value.text === "string";
50
+ case "image":
51
+ case "audio": return typeof value.data === "string" && typeof value.mimeType === "string";
52
+ case "resource_link": return typeof value.uri === "string" && typeof value.name === "string" && optionalString(value.title) && optionalString(value.description) && optionalString(value.mimeType) && (value.size === void 0 || typeof value.size === "number") && (value.icons === void 0 || Array.isArray(value.icons) && value.icons.every(validIcon));
53
+ case "resource": {
54
+ const resource = value.resource;
55
+ return isRecord(resource) && typeof resource.uri === "string" && optionalString(resource.mimeType) && validMetadata(resource) && (typeof resource.text === "string" || typeof resource.blob === "string");
56
+ }
57
+ default: return false;
58
+ }
59
+ }
60
+ /** Reject values JSON would drop, coerce or fail to encode (including cycles). */
61
+ function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
62
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
63
+ if (typeof value === "number") return Number.isFinite(value);
64
+ if (typeof value !== "object" || ancestors.has(value)) return false;
65
+ const prototype = Object.getPrototypeOf(value);
66
+ if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;
67
+ ancestors.add(value);
68
+ const valid = Array.isArray(value) ? Array.from(value).every((entry) => isJsonValue(entry, ancestors)) : Object.values(value).every((entry) => entry === void 0 || isJsonValue(entry, ancestors));
69
+ ancestors.delete(value);
70
+ return valid;
71
+ }
72
+ /**
73
+ * MCP 2025-11-25 CallToolResult and ContentBlock fields:
74
+ * https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result
75
+ * https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts
76
+ * structuredContent is preserved even when tools/list declares outputSchema.
77
+ * Tool authors remain responsible for conformity to that application schema.
78
+ */
79
+ function assertMcpResult(value) {
80
+ if (!isRecord(value) || !Array.isArray(value.content) || !Array.from(value.content).every(validContent) || value.isError !== void 0 && typeof value.isError !== "boolean" || value.structuredContent !== void 0 && !isRecord(value.structuredContent) || value["_meta"] !== void 0 && !isRecord(value["_meta"]) || !isJsonValue(value)) throw new TypeError("Invalid native MCP CallToolResult.");
81
+ }
82
+
83
+ //#endregion
84
+ export { assertMcpResult, isMcpResult, isRecord, looksLikeMcpResult, mcpResult, unwrapMcpResult };
85
+ //# sourceMappingURL=result.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/result.ts"],"sourcesContent":["import type { McpToolCallResult } from \"../contracts\";\n\nconst NATIVE_RESULT = Symbol(\"mcpResult\");\n\n/** An explicit native MCP result, unwrapped only by the serving boundary. */\nexport type McpNativeResult<T extends McpToolCallResult = McpToolCallResult> = {\n readonly [NATIVE_RESULT]: T;\n};\n\n/**\n * Opt in to native MCP content, error status, structured output and metadata.\n * Validation happens after invoke(), so malformed payloads become JSON-RPC\n * errors rather than being caught as tool execution errors.\n */\nexport function mcpResult<T extends McpToolCallResult>(result: T): McpNativeResult<T> {\n return { [NATIVE_RESULT]: result };\n}\n\n/** Read a branded value without accepting lookalike application objects. */\nexport function isMcpResult(value: unknown): value is McpNativeResult {\n return isRecord(value) && NATIVE_RESULT in value;\n}\n\n/** Get the original payload; no cloning, normalization or field stripping. */\nexport function unwrapMcpResult(value: McpNativeResult): McpToolCallResult {\n return value[NATIVE_RESULT];\n}\n\n/** Objects with result fields are candidates only when native mode is enabled. */\nexport function looksLikeMcpResult(value: unknown): boolean {\n return (\n isRecord(value) &&\n [\"content\", \"isError\", \"structuredContent\", \"_meta\"].some((key) => key in value)\n );\n}\n\n/** Non-array object used by the MCP JSON object fields. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction optionalString(value: unknown): boolean {\n return value === undefined || typeof value === \"string\";\n}\n\nfunction validAnnotations(value: unknown): boolean {\n if (value === undefined) return true;\n if (!isRecord(value)) return false;\n\n return (\n (value.audience === undefined ||\n (Array.isArray(value.audience) &&\n value.audience.every((role) => role === \"user\" || role === \"assistant\"))) &&\n (value.priority === undefined ||\n (typeof value.priority === \"number\" && value.priority >= 0 && value.priority <= 1)) &&\n optionalString(value.lastModified)\n );\n}\n\nfunction validMetadata(value: Record<string, unknown>): boolean {\n return (\n (value[\"_meta\"] === undefined || isRecord(value[\"_meta\"])) &&\n validAnnotations(value.annotations)\n );\n}\n\nfunction validIcon(value: unknown): boolean {\n return (\n isRecord(value) &&\n typeof value.src === \"string\" &&\n optionalString(value.mimeType) &&\n (value.sizes === undefined ||\n (Array.isArray(value.sizes) && value.sizes.every((size) => typeof size === \"string\"))) &&\n (value.theme === undefined || value.theme === \"light\" || value.theme === \"dark\")\n );\n}\n\nfunction validContent(value: unknown): boolean {\n if (!isRecord(value) || !validMetadata(value)) return false;\n\n switch (value.type) {\n case \"text\":\n return typeof value.text === \"string\";\n case \"image\":\n case \"audio\":\n return typeof value.data === \"string\" && typeof value.mimeType === \"string\";\n case \"resource_link\":\n return (\n typeof value.uri === \"string\" &&\n typeof value.name === \"string\" &&\n optionalString(value.title) &&\n optionalString(value.description) &&\n optionalString(value.mimeType) &&\n (value.size === undefined || typeof value.size === \"number\") &&\n (value.icons === undefined || (Array.isArray(value.icons) && value.icons.every(validIcon)))\n );\n case \"resource\": {\n const resource = value.resource;\n\n return (\n isRecord(resource) &&\n typeof resource.uri === \"string\" &&\n optionalString(resource.mimeType) &&\n validMetadata(resource) &&\n (typeof resource.text === \"string\" || typeof resource.blob === \"string\")\n );\n }\n default:\n return false;\n }\n}\n\n/** Reject values JSON would drop, coerce or fail to encode (including cycles). */\nfunction isJsonValue(value: unknown, ancestors = new Set<object>()): boolean {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return true;\n if (typeof value === \"number\") return Number.isFinite(value);\n if (typeof value !== \"object\" || ancestors.has(value)) return false;\n\n const prototype: unknown = Object.getPrototypeOf(value);\n if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) return false;\n\n ancestors.add(value);\n const valid = Array.isArray(value)\n ? Array.from(value).every((entry) => isJsonValue(entry, ancestors))\n : Object.values(value).every((entry) => entry === undefined || isJsonValue(entry, ancestors));\n ancestors.delete(value);\n return valid;\n}\n\n/**\n * MCP 2025-11-25 CallToolResult and ContentBlock fields:\n * https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-result\n * https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts\n * structuredContent is preserved even when tools/list declares outputSchema.\n * Tool authors remain responsible for conformity to that application schema.\n */\nexport function assertMcpResult(value: unknown): asserts value is McpToolCallResult {\n if (\n !isRecord(value) ||\n !Array.isArray(value.content) ||\n !Array.from(value.content).every(validContent) ||\n (value.isError !== undefined && typeof value.isError !== \"boolean\") ||\n (value.structuredContent !== undefined && !isRecord(value.structuredContent)) ||\n (value[\"_meta\"] !== undefined && !isRecord(value[\"_meta\"])) ||\n !isJsonValue(value)\n ) {\n throw new TypeError(\"Invalid native MCP CallToolResult.\");\n }\n}\n"],"mappings":"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"}
@@ -39,7 +39,7 @@ const JSON_RPC_INVALID_REQUEST = -32600;
39
39
  /** Generic server-defined JSON-RPC code for HTTP-level refusals (auth, origin, size). */
40
40
  const JSON_RPC_SERVER_ERROR = -32e3;
41
41
  /** Host names that always mean "this machine". */
42
- const LOOPBACK_NAMES = new Set([
42
+ const LOOPBACK_NAMES = /* @__PURE__ */ new Set([
43
43
  "localhost",
44
44
  "127.0.0.1",
45
45
  "::1",
@@ -266,7 +266,8 @@ async function handleRequest(request, response, settings, dispatch, signal) {
266
266
  };
267
267
  if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
268
268
  if (!await authorized(info, settings)) {
269
- sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", settings.tokenDigests.length > 0 ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {});
269
+ const challenge = settings.tokenDigests.length > 0 ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
270
+ sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
270
271
  return;
271
272
  }
272
273
  if (method !== "POST") {
@@ -439,5 +440,5 @@ function readBody(request, limit) {
439
440
  }
440
441
 
441
442
  //#endregion
442
- export { startHttpServer };
443
+ export { isLoopbackHost, startHttpServer };
443
444
  //# sourceMappingURL=serve-http.mjs.map
@@ -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\";\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 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) {\n throw new McpServeError(\"MCP http auth needs `bearer` and/or `authorize`.\", {\n type: \"config\",\n });\n }\n\n const authRequired = tokens.length > 0 || auth?.authorize !== 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 } or { authorize }), 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 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 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(request, response, settings, dispatch, shutdown.signal).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): 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 if (!(await authorized(info, settings))) {\n const challenge: Record<string, string> =\n settings.tokenDigests.length > 0 ? { \"www-authenticate\": 'Bearer realm=\"mcp\"' } : {};\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 // A notification (notifications/initialized, notifications/cancelled…).\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 rpcResponse = await dispatch(rpcRequest, { request: info, signal });\n\n sendJson(response, 200, rpcResponse);\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 */\nasync function authorized(info: McpHttpRequestInfo, settings: ResolvedSettings): Promise<boolean> {\n if (!settings.authRequired) {\n return 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 false;\n }\n }\n\n if (settings.authorize) {\n try {\n return (await settings.authorize(info)) === true;\n } catch {\n return false;\n }\n }\n\n return true;\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 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\";\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 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) {\n throw new McpServeError(\"MCP http auth needs `bearer` and/or `authorize`.\", {\n type: \"config\",\n });\n }\n\n const authRequired = tokens.length > 0 || auth?.authorize !== 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 } or { authorize }), 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 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 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(request, response, settings, dispatch, shutdown.signal).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): 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 if (!(await authorized(info, settings))) {\n const challenge: Record<string, string> =\n settings.tokenDigests.length > 0 ? { \"www-authenticate\": 'Bearer realm=\"mcp\"' } : {};\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 // A notification (notifications/initialized, notifications/cancelled…).\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 rpcResponse = await dispatch(rpcRequest, { request: info, signal });\n\n sendJson(response, 200, rpcResponse);\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 */\nasync function authorized(info: McpHttpRequestInfo, settings: ResolvedSettings): Promise<boolean> {\n if (!settings.authRequired) {\n return 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 false;\n }\n }\n\n if (settings.authorize) {\n try {\n return (await settings.authorize(info)) === true;\n } catch {\n return false;\n }\n }\n\n return true;\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,10 +1,11 @@
1
1
  import { JsonRpcRequest, JsonRpcResponse } from "../contracts/mcp.type.mjs";
2
2
  import { McpHttpServeHandle, McpHttpServeTransport, McpServeCallContext, McpServeOptions, McpServeSource, McpServer } from "../contracts/mcp-serve.type.mjs";
3
+ import "../contracts/index.mjs";
3
4
  //#region ../../packages/ai-tools/src/mcp/serve.d.ts
4
5
  /**
5
6
  * The pure protocol handler: one JSON-RPC request in, one response out.
6
7
  */
7
- interface McpServeRequestHandler {
8
+ export interface McpServeRequestHandler {
8
9
  /**
9
10
  * Map one request to its response.
10
11
  *
@@ -25,7 +26,7 @@ interface McpServeRequestHandler {
25
26
  * @param options - Serve options (name / version / schema target / resources / prompts).
26
27
  * @returns An object whose `handle(request, context?)` maps a request to a response.
27
28
  */
28
- declare function createServeHandler(source: McpServeSource, options: McpServeOptions): McpServeRequestHandler;
29
+ export declare function createServeHandler(source: McpServeSource, options: McpServeOptions): McpServeRequestHandler;
29
30
  /**
30
31
  * Expose a built agent / supervisor / orchestrator (or a raw
31
32
  * `ToolContract[]`, or several named groups) AS an MCP server (Direction B:
@@ -56,10 +57,9 @@ declare function createServeHandler(source: McpServeSource, options: McpServeOpt
56
57
  * transport: { type: "http", auth: { bearer: sessionToken } },
57
58
  * }).start();
58
59
  */
59
- declare function serve(source: McpServeSource, options: McpServeOptions & {
60
+ export declare function serve(source: McpServeSource, options: McpServeOptions & {
60
61
  transport: McpHttpServeTransport;
61
62
  }): McpServer<McpHttpServeHandle>;
62
- declare function serve(source: McpServeSource, options: McpServeOptions): McpServer;
63
+ export declare function serve(source: McpServeSource, options: McpServeOptions): McpServer;
63
64
  //#endregion
64
- export { McpServeRequestHandler, createServeHandler, serve };
65
65
  //# sourceMappingURL=serve.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"mappings":";;;;;;UAmnBiB,sBAAA;EAOwD;;;;;;EAAvE,MAAA,CAAO,OAAA,EAAS,cAAA,EAAgB,OAAA,GAAU,mBAAA,GAAsB,OAAA,CAAQ,eAAA;AAAA;;AAAe;AAezF;;;;;;;;;;iBAAgB,kBAAA,CACd,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,eAAA,GACR,sBAAA;;;;AAAsB;AAuIzB;;;;;;;;;;;;;;;;;;;AAG+B;AAC/B;;;;;;iBAJgB,KAAA,CACd,MAAA,EAAQ,cAAA,EACR,OAAA,EAAS,eAAA;EAAoB,SAAA,EAAW,qBAAA;AAAA,IACvC,SAAA,CAAU,kBAAA;AAAA,iBACG,KAAA,CAAM,MAAA,EAAQ,cAAA,EAAgB,OAAA,EAAS,eAAA,GAAkB,SAAA"}
1
+ {"version":3,"file":"serve.d.mts","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"mappings":";;;;;;;iBA6oBiB;;;;;;;EAOf,OAAO,SAAS,gBAAgB,UAAU,sBAAsB,QAAQ;;;;;;;;;;;;;;wBAe1D,mBACd,QAAQ,gBACR,SAAS,kBACR;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBAuIa,MACd,QAAQ,gBACR,SAAS;EAAoB,WAAW;IACvC,UAAU;wBACG,MAAM,QAAQ,gBAAgB,SAAS,kBAAkB"}
package/esm/mcp/serve.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { McpServeError, McpTransportError } from "../errors.mjs";
2
2
  import { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from "./protocol.mjs";
3
3
  import { startHttpServer } from "./serve-http.mjs";
4
+ import { assertMcpResult, isMcpResult, isRecord, looksLikeMcpResult, unwrapMcpResult } from "./result.mjs";
4
5
  import { extractJsonSchema } from "@warlock.js/ai";
5
6
  import { createInterface } from "node:readline";
6
7
 
@@ -135,18 +136,15 @@ var McpServeHandler = class {
135
136
  case "ping": return {};
136
137
  case "tools/list": return { tools: this.listTools() };
137
138
  case "tools/call": return this.callTool(request.params, context);
138
- default: break;
139
139
  }
140
140
  if (this.resources) switch (request.method) {
141
141
  case "resources/list": return { resources: this.listResources(this.resources) };
142
142
  case "resources/templates/list": return { resourceTemplates: [] };
143
143
  case "resources/read": return this.readResource(this.resources, request.params, context);
144
- default: break;
145
144
  }
146
145
  if (this.prompts) switch (request.method) {
147
146
  case "prompts/list": return { prompts: this.listPrompts(this.prompts) };
148
147
  case "prompts/get": return this.getPrompt(this.prompts, request.params, context);
149
- default: break;
150
148
  }
151
149
  throw new RpcFailure(JSON_RPC_METHOD_NOT_FOUND, `Method "${request.method}" is not supported by this MCP server.`);
152
150
  }
@@ -186,15 +184,17 @@ var McpServeHandler = class {
186
184
  return {
187
185
  name,
188
186
  description: contract.description,
189
- inputSchema
187
+ inputSchema,
188
+ ...isRecord(contract.meta?.outputSchema) ? { outputSchema: contract.meta.outputSchema } : {}
190
189
  };
191
190
  });
192
191
  }
193
192
  /**
194
193
  * Route a `tools/call` to the named contract's `invoke()` and map the
195
194
  * never-throwing {@link import("@warlock.js/ai").ToolInvokeResult}: `data`
196
- * → a text content block, `error` → an `isError: true` result. An unknown
197
- * tool name throws (mapped to a JSON-RPC error by {@link handle}).
195
+ * → a text content block (or an opted-in native result), `error` → an
196
+ * `isError: true` result. An unknown tool name throws (mapped to a
197
+ * JSON-RPC error by {@link handle}).
198
198
  *
199
199
  * The contract receives a `ToolContext` carrying the abort signal and,
200
200
  * over HTTP, the inbound request's headers on `ctx.request`.
@@ -223,6 +223,16 @@ var McpServeHandler = class {
223
223
  }],
224
224
  isError: true
225
225
  };
226
+ const mode = contract.meta?.mcpResults ?? this.options.results ?? "text";
227
+ if (isMcpResult(result.data) || mode === "native" && looksLikeMcpResult(result.data)) {
228
+ const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;
229
+ try {
230
+ assertMcpResult(native);
231
+ } catch {
232
+ throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, "Invalid native MCP CallToolResult.");
233
+ }
234
+ return native;
235
+ }
226
236
  return {
227
237
  content: [{
228
238
  type: "text",
@@ -493,5 +503,5 @@ function serve(source, options) {
493
503
  }
494
504
 
495
505
  //#endregion
496
- export { createServeHandler, serve };
506
+ export { answerStdioLine, createServeHandler, serve };
497
507
  //# sourceMappingURL=serve.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"serve.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"sourcesContent":["import { createInterface, type Interface } from \"node:readline\";\nimport { extractJsonSchema, type ToolContext, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpGroupedToolSource,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpPromptMessage,\n McpServeCallContext,\n McpServeHandle,\n McpServeOptions,\n McpServePrompt,\n McpServeResource,\n McpServeSource,\n McpServer,\n McpToolCallResult,\n McpToolDescriptor,\n McpToolSource,\n} from \"../contracts\";\nimport { McpServeError, McpTransportError } from \"../errors\";\nimport { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { startHttpServer } from \"./serve-http\";\n\n/** JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/** Default JSON-Schema dialect emitted for each tool's `inputSchema`. */\nconst DEFAULT_SCHEMA_TARGET = \"draft-2020-12\";\n\n/** Default advertised server version when the caller omits one. */\nconst DEFAULT_VERSION = \"4.4.0\";\n\n/** JSON-RPC standard error codes we emit. */\nconst JSON_RPC_METHOD_NOT_FOUND = -32601;\nconst JSON_RPC_INVALID_PARAMS = -32602;\n\n/** MCP's \"resource not found\" error code (server/resources, error handling). */\nconst MCP_RESOURCE_NOT_FOUND = -32002;\n\n/** One tool as exposed on the wire: its public name plus the contract behind it. */\ntype ExposedTool = {\n /** The name clients see in `tools/list` and send in `tools/call`. */\n name: string;\n /** The contract that runs. */\n contract: ToolContract;\n};\n\n/**\n * A JSON-RPC failure raised inside a method handler, carrying the exact\n * code to answer with (unknown resource, missing prompt argument, …).\n */\nclass RpcFailure extends Error {\n public readonly code: number;\n\n public readonly data: unknown;\n\n public constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.code = code;\n this.data = data;\n }\n}\n\n/** Enumerate one tool set into a flat contract array. */\nfunction toolsOf(source: McpToolSource): ToolContract[] {\n return Array.isArray(source) ? source : source.tools();\n}\n\n/** Narrow a serve source to its grouped shape. */\nfunction isGroupedSource(source: McpServeSource): source is McpGroupedToolSource {\n return !Array.isArray(source) && \"groups\" in source;\n}\n\n/**\n * Resolve the serve source into the exposed tool list, applying\n * `prefixToolNames` and refusing duplicate names. Throws\n * {@link McpServeError} — `\"config\"` for `prefixToolNames` on an ungrouped\n * source, `\"collision\"` when two tools would share a name.\n */\nfunction resolveTools(source: McpServeSource, options: McpServeOptions): ExposedTool[] {\n const prefix = options.prefixToolNames ?? false;\n const exposed: Array<ExposedTool & { origin: string }> = [];\n\n if (isGroupedSource(source)) {\n for (const [group, groupSource] of Object.entries(source.groups)) {\n for (const contract of toolsOf(groupSource)) {\n let name = contract.name;\n\n if (prefix === true) {\n name = `${group}_${contract.name}`;\n } else if (typeof prefix === \"function\") {\n name = prefix(group, contract.name);\n }\n\n exposed.push({ name, contract, origin: `group \"${group}\"` });\n }\n }\n } else {\n if (prefix !== false) {\n throw new McpServeError(\n \"prefixToolNames needs a grouped source: serve({ groups: { name: tools } }, …).\",\n { type: \"config\" },\n );\n }\n\n for (const contract of toolsOf(source)) {\n exposed.push({ name: contract.name, contract, origin: \"the source\" });\n }\n }\n\n const seen = new Map<string, string>();\n\n for (const tool of exposed) {\n const previous = seen.get(tool.name);\n\n if (previous !== undefined) {\n throw new McpServeError(\n `MCP tool name \"${tool.name}\" is exposed twice (from ${previous} and ${tool.origin}). ` +\n \"Rename one tool, or serve a grouped source with prefixToolNames.\",\n { type: \"collision\", context: { name: tool.name } },\n );\n }\n\n seen.set(tool.name, tool.origin);\n }\n\n return exposed.map(({ name, contract }) => ({ name, contract }));\n}\n\n/** Throw a collision error when two entries share the same key. */\nfunction assertUnique<T>(\n entries: readonly T[] | undefined,\n key: (entry: T) => string,\n label: string,\n): void {\n const seen = new Set<string>();\n\n for (const entry of entries ?? []) {\n const value = key(entry);\n\n if (seen.has(value)) {\n throw new McpServeError(`MCP ${label} \"${value}\" is declared twice.`, {\n type: \"collision\",\n context: { [label]: value },\n });\n }\n\n seen.add(value);\n }\n}\n\n/**\n * The pure protocol core of `serve` — maps one JSON-RPC request to its\n * response, with no I/O. Both the stdio and HTTP serve-transports pump\n * their inbound requests through this, and specs can drive it directly.\n *\n * Handles `initialize`, `ping`, `tools/list`, `tools/call`, and — when\n * configured — `resources/list`, `resources/templates/list`,\n * `resources/read`, `prompts/list` and `prompts/get`. Any other method\n * answers with a JSON-RPC `method not found` error.\n *\n * Constructed via {@link createServeHandler}.\n */\nclass McpServeHandler {\n /** The tools this server exposes (snapshotted at construction). */\n private readonly tools: ExposedTool[];\n\n /** Fast lookup by exposed tool name for `tools/call` dispatch. */\n private readonly byName: Map<string, ToolContract>;\n\n /** Resources by URI, or `undefined` when the capability is off. */\n private readonly resources: Map<string, McpServeResource> | undefined;\n\n /** Prompts by name, or `undefined` when the capability is off. */\n private readonly prompts: Map<string, McpServePrompt> | undefined;\n\n /** Construction-time serve options (name / version / schema target). */\n private readonly options: McpServeOptions;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.tools = resolveTools(source, options);\n this.byName = new Map(this.tools.map((tool) => [tool.name, tool.contract]));\n\n assertUnique(options.resources, (resource) => resource.uri, \"resource\");\n assertUnique(options.prompts, (prompt) => prompt.name, \"prompt\");\n\n this.resources = options.resources\n ? new Map(options.resources.map((resource) => [resource.uri, resource]))\n : undefined;\n this.prompts = options.prompts\n ? new Map(options.prompts.map((prompt) => [prompt.name, prompt]))\n : undefined;\n this.options = options;\n }\n\n /**\n * Dispatch one inbound JSON-RPC request to its handler and produce the\n * response. A handler that throws is mapped to a JSON-RPC error response\n * — the serve loop never crashes on a bad request.\n *\n * @param request - The JSON-RPC request.\n * @param context - Per-call context: the inbound HTTP request (headers\n * reach tools as `ctx.request`) and an abort signal.\n */\n public async handle(\n request: JsonRpcRequest,\n context: McpServeCallContext = {},\n ): Promise<JsonRpcResponse> {\n try {\n return this.ok(request.id, await this.dispatch(request, context));\n } catch (cause) {\n if (cause instanceof RpcFailure) {\n return this.error(request.id, cause.code, cause.message, cause.data);\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return this.error(request.id, JSON_RPC_INVALID_PARAMS, message);\n }\n }\n\n /** Route a method to its result builder. */\n private async dispatch(request: JsonRpcRequest, context: McpServeCallContext): Promise<unknown> {\n switch (request.method) {\n case \"initialize\":\n return this.initializeResult(request.params);\n case \"ping\":\n return {};\n case \"tools/list\":\n return { tools: this.listTools() };\n case \"tools/call\":\n return this.callTool(request.params, context);\n default:\n break;\n }\n\n if (this.resources) {\n switch (request.method) {\n case \"resources/list\":\n return { resources: this.listResources(this.resources) };\n case \"resources/templates/list\":\n return { resourceTemplates: [] };\n case \"resources/read\":\n return this.readResource(this.resources, request.params, context);\n default:\n break;\n }\n }\n\n if (this.prompts) {\n switch (request.method) {\n case \"prompts/list\":\n return { prompts: this.listPrompts(this.prompts) };\n case \"prompts/get\":\n return this.getPrompt(this.prompts, request.params, context);\n default:\n break;\n }\n }\n\n throw new RpcFailure(\n JSON_RPC_METHOD_NOT_FOUND,\n `Method \"${request.method}\" is not supported by this MCP server.`,\n );\n }\n\n /**\n * Build the `initialize` result advertising name / version /\n * capabilities, negotiating the protocol version: the client's requested\n * version when supported, otherwise the newest this server speaks.\n */\n private initializeResult(params: unknown): Record<string, unknown> {\n const requested = readRecord(params).protocolVersion;\n const protocolVersion =\n typeof requested === \"string\" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)\n ? requested\n : LATEST_PROTOCOL_VERSION;\n\n const capabilities: Record<string, unknown> = { tools: {} };\n\n if (this.resources) {\n capabilities.resources = {};\n }\n\n if (this.prompts) {\n capabilities.prompts = {};\n }\n\n return {\n protocolVersion,\n capabilities,\n serverInfo: {\n name: this.options.name,\n version: this.options.version ?? DEFAULT_VERSION,\n },\n };\n }\n\n /**\n * Build the `tools/list` payload: one {@link McpToolDescriptor} per\n * exposed tool, its `inputSchema` extracted via `extractJsonSchema` at\n * the configured dialect (default `draft-2020-12` — overriding\n * `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).\n */\n private listTools(): McpToolDescriptor[] {\n const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;\n\n return this.tools.map(({ name, contract }) => {\n const inputSchema = extractJsonSchema(contract.input, { target }) ?? {\n type: \"object\",\n properties: {},\n };\n\n return {\n name,\n description: contract.description,\n inputSchema,\n };\n });\n }\n\n /**\n * Route a `tools/call` to the named contract's `invoke()` and map the\n * never-throwing {@link import(\"@warlock.js/ai\").ToolInvokeResult}: `data`\n * → a text content block, `error` → an `isError: true` result. An unknown\n * tool name throws (mapped to a JSON-RPC error by {@link handle}).\n *\n * The contract receives a `ToolContext` carrying the abort signal and,\n * over HTTP, the inbound request's headers on `ctx.request`.\n */\n private async callTool(\n params: unknown,\n context: McpServeCallContext,\n ): Promise<McpToolCallResult> {\n const { name, args } = readCallParams(params);\n const contract = this.byName.get(name);\n\n if (!contract) {\n throw new McpTransportError(`Unknown tool \"${name}\".`, {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const toolContext: ToolContext = { artifacts: {} };\n\n if (context.signal) {\n toolContext.signal = context.signal;\n }\n\n if (context.request) {\n const { headers, remoteAddress } = context.request;\n\n toolContext.request = remoteAddress === undefined ? { headers } : { headers, remoteAddress };\n }\n\n const result = await contract.invoke(args, toolContext);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: result.error.message }],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: serializeData(result.data) }],\n isError: false,\n };\n }\n\n /** Build the `resources/list` payload (metadata only, no contents). */\n private listResources(resources: Map<string, McpServeResource>): Record<string, unknown>[] {\n return [...resources.values()].map((resource) =>\n compact({\n uri: resource.uri,\n name: resource.name,\n title: resource.title,\n description: resource.description,\n mimeType: resource.mimeType,\n }),\n );\n }\n\n /**\n * Answer `resources/read`: run the resource's `read()` and wrap the\n * result as one text or base64 blob content entry.\n */\n private async readResource(\n resources: Map<string, McpServeResource>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const uri = readRecord(params).uri;\n\n if (typeof uri !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"resources/read requires a string `uri`.\");\n }\n\n const resource = resources.get(uri);\n\n if (!resource) {\n throw new RpcFailure(MCP_RESOURCE_NOT_FOUND, \"Resource not found\", { uri });\n }\n\n const body = await resource.read(context);\n const entry =\n typeof body === \"string\"\n ? { uri, mimeType: resource.mimeType, text: body }\n : { uri, mimeType: resource.mimeType, blob: Buffer.from(body).toString(\"base64\") };\n\n return { contents: [compact(entry)] };\n }\n\n /** Build the `prompts/list` payload. */\n private listPrompts(prompts: Map<string, McpServePrompt>): Record<string, unknown>[] {\n return [...prompts.values()].map((prompt) =>\n compact({\n name: prompt.name,\n title: prompt.title,\n description: prompt.description,\n arguments: prompt.arguments,\n }),\n );\n }\n\n /**\n * Answer `prompts/get`: validate the arguments (strings only; required\n * ones present) and build the messages. An unknown prompt or a missing\n * required argument answers invalid-params, per the MCP prompts spec.\n */\n private async getPrompt(\n prompts: Map<string, McpServePrompt>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const record = readRecord(params);\n const name = record.name;\n\n if (typeof name !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"prompts/get requires a string `name`.\");\n }\n\n const prompt = prompts.get(name);\n\n if (!prompt) {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Unknown prompt \"${name}\".`);\n }\n\n const args: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(readRecord(record.arguments))) {\n if (typeof value !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Prompt argument \"${key}\" must be a string.`);\n }\n\n args[key] = value;\n }\n\n for (const argument of prompt.arguments ?? []) {\n if (argument.required && args[argument.name] === undefined) {\n throw new RpcFailure(\n JSON_RPC_INVALID_PARAMS,\n `Prompt \"${name}\" requires the \"${argument.name}\" argument.`,\n );\n }\n }\n\n const built = await prompt.get(args, context);\n const messages: McpPromptMessage[] =\n typeof built === \"string\"\n ? [{ role: \"user\", content: { type: \"text\", text: built } }]\n : built;\n\n return compact({ description: prompt.description, messages });\n }\n\n /** Build a JSON-RPC success response. */\n private ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: JSONRPC_VERSION, id, result };\n }\n\n /** Build a JSON-RPC error response. */\n private error(\n id: JsonRpcRequest[\"id\"],\n code: number,\n message: string,\n data?: unknown,\n ): JsonRpcResponse {\n const error = data === undefined ? { code, message } : { code, message, data };\n\n return { jsonrpc: JSONRPC_VERSION, id, error };\n }\n}\n\n/** JSON-RPC \"Invalid Request\" — the line is JSON but not a request object. */\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** JSON-RPC \"Internal error\" — answering the request failed unexpectedly. */\nconst JSON_RPC_INTERNAL_ERROR = -32603;\n\n/**\n * Turn one stdio line into the line to write back, or `undefined` when\n * nothing is answered: blank or non-JSON lines and notifications (no `id`).\n * A JSON value that is not a request object (`null`, a number, an array) is\n * answered with a JSON-RPC \"Invalid Request\" error whose `id` is `null`.\n * Never rejects: a dispatch failure is answered as an internal error.\n */\nexport async function answerStdioLine(\n line: string,\n dispatch: (request: JsonRpcRequest) => Promise<JsonRpcResponse>,\n): Promise<string | undefined> {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return undefined;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n const invalid = {\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: { code: JSON_RPC_INVALID_REQUEST, message: \"Invalid request: not a JSON-RPC object.\" },\n };\n\n return `${JSON.stringify(invalid)}\\n`;\n }\n\n const request = message as JsonRpcRequest;\n const id: unknown = request.id;\n\n if (id === undefined || id === null) {\n // A notification (e.g. notifications/initialized) — nothing to answer.\n return undefined;\n }\n\n try {\n const response = await dispatch(request);\n\n return `${JSON.stringify(response)}\\n`;\n } catch {\n const failed = {\n jsonrpc: JSONRPC_VERSION,\n id,\n error: { code: JSON_RPC_INTERNAL_ERROR, message: \"Internal error.\" },\n };\n\n return `${JSON.stringify(failed)}\\n`;\n }\n}\n\n/** Read an unknown value as a plain record (non-objects read as empty). */\nfunction readRecord(value: unknown): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n\n return value as Record<string, unknown>;\n}\n\n/** Drop `undefined` members so optional wire fields are omitted, not `null`. */\nfunction compact(value: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, member] of Object.entries(value)) {\n if (member !== undefined) {\n result[key] = member;\n }\n }\n\n return result;\n}\n\n/**\n * Read and validate the `tools/call` params into `{ name, args }`. Throws\n * a typed {@link McpTransportError} when `name` is missing — mapped to a\n * JSON-RPC `invalid params` error by the handler.\n */\nfunction readCallParams(params: unknown): { name: string; args: unknown } {\n if (typeof params !== \"object\" || params === null) {\n throw new McpTransportError(\"tools/call params must be an object.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const record = params as { name?: unknown; arguments?: unknown };\n\n if (typeof record.name !== \"string\") {\n throw new McpTransportError(\"tools/call requires a string `name`.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n return { name: record.name, args: record.arguments ?? {} };\n}\n\n/**\n * Serialize a tool's `data` for an MCP text content block — a string is\n * passed verbatim, everything else is JSON-stringified so structured\n * output crosses the wire as text the consuming client can re-parse.\n */\nfunction serializeData(data: unknown): string {\n if (typeof data === \"string\") {\n return data;\n }\n\n if (data === undefined) {\n return \"\";\n }\n\n return JSON.stringify(data);\n}\n\n/**\n * The pure protocol handler: one JSON-RPC request in, one response out.\n */\nexport interface McpServeRequestHandler {\n /**\n * Map one request to its response.\n *\n * @param request - The JSON-RPC request.\n * @param context - Optional per-call context (inbound HTTP request, abort signal).\n */\n handle(request: JsonRpcRequest, context?: McpServeCallContext): Promise<JsonRpcResponse>;\n}\n\n/**\n * Build the pure protocol handler for a serve source. Exposed (alongside\n * {@link serve}) so callers and tests can drive the MCP protocol without an\n * actual transport — feed it a JSON-RPC request, get the response.\n *\n * Throws {@link McpServeError} on duplicate tool names / resource URIs /\n * prompt names, or `prefixToolNames` on an ungrouped source.\n *\n * @param source - The tools to expose (one tool set or `{ groups }`).\n * @param options - Serve options (name / version / schema target / resources / prompts).\n * @returns An object whose `handle(request, context?)` maps a request to a response.\n */\nexport function createServeHandler(\n source: McpServeSource,\n options: McpServeOptions,\n): McpServeRequestHandler {\n return new McpServeHandler(source, options);\n}\n\n/**\n * The internal {@link McpServer} — owns a {@link McpServeHandler} and a\n * transport pump. For `stdio` it reads newline-delimited JSON-RPC requests\n * from `process.stdin` and writes responses to `process.stdout`; for\n * `http` it binds a Streamable HTTP endpoint (see `./serve-http`).\n *\n * Constructed via {@link serve}; the class itself is internal.\n */\nclass McpServerImpl implements McpServer {\n /** The pure protocol handler. */\n private readonly handler: McpServeHandler;\n\n /** Serve options (transport selection lives here). */\n private readonly options: McpServeOptions;\n\n /** The running handle (stdio or http), while started or starting. */\n private running: Promise<McpServeHandle> | undefined;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.handler = new McpServeHandler(source, options);\n this.options = options;\n }\n\n public start(): Promise<McpServeHandle> {\n if (!this.running) {\n const transport = this.options.transport ?? { type: \"stdio\" };\n const starting =\n transport.type === \"http\" ? this.startHttp(transport) : Promise.resolve(this.startStdio());\n\n // A failed start leaves the server startable again.\n this.running = starting.catch((error: unknown) => {\n this.running = undefined;\n throw error;\n });\n }\n\n return this.running;\n }\n\n /** Bind the HTTP endpoint; its `close()` also makes this server startable again. */\n private async startHttp(transport: McpHttpServeTransport): Promise<McpHttpServeHandle> {\n const http = await startHttpServer(transport, (request, context) =>\n this.handler.handle(request, context),\n );\n\n return {\n url: http.url,\n close: async () => {\n await http.close();\n this.running = undefined;\n },\n };\n }\n\n /** Wire the stdin line reader and answer each request on stdout. */\n private startStdio(): McpServeHandle {\n const reader: Interface = createInterface({ input: process.stdin });\n\n reader.on(\"line\", (line) => {\n // A failure answering one line must never become an unhandled\n // rejection: that would take the whole server process down.\n void this.onLine(line).catch(() => {});\n });\n\n return {\n close: async () => {\n reader.close();\n this.running = undefined;\n },\n };\n }\n\n /** Answer one stdin line on stdout (see {@link answerStdioLine}). */\n private async onLine(line: string): Promise<void> {\n const reply = await answerStdioLine(line, (request) => this.handler.handle(request));\n\n if (reply !== undefined) {\n process.stdout.write(reply);\n }\n }\n\n public async stop(): Promise<void> {\n const running = this.running;\n\n if (!running) {\n return;\n }\n\n let handle: McpServeHandle;\n\n try {\n handle = await running;\n } catch {\n // A start that failed has nothing to close.\n return;\n }\n\n await handle.close();\n }\n}\n\n/**\n * Expose a built agent / supervisor / orchestrator (or a raw\n * `ToolContract[]`, or several named groups) AS an MCP server (Direction B:\n * local primitive → MCP server other clients consume).\n *\n * Enumerates the tools once, here. Every exposed name must be unique — a\n * duplicate throws {@link McpServeError} (`type: \"collision\"`) right away;\n * serve `{ groups: { … } }` with `prefixToolNames: true` to namespace\n * same-named tools from different sets. `tools/list` answers with each\n * tool's `inputSchema` extracted via `extractJsonSchema` at the configured\n * `schemaTarget` (default `draft-2020-12`). `tools/call` routes to the\n * contract's `invoke()` and maps the never-throwing result — `data`\n * becomes a text content block, `error` an `isError: true` result.\n *\n * Transports: `stdio` (default) pumps `process.stdin` / `process.stdout`;\n * `http` binds an MCP Streamable HTTP endpoint on `127.0.0.1` by default,\n * with bearer / custom auth (required off loopback), `Origin` and `Host`\n * validation against DNS rebinding, a body-size cap, and a draining\n * `close()`. `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n * @returns An {@link McpServer} with `start()` / `stop()`.\n *\n * @example\n * const { url, close } = await serve([calc, clock], {\n * name: \"session-tools\",\n * transport: { type: \"http\", auth: { bearer: sessionToken } },\n * }).start();\n */\nexport function serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n): McpServer<McpHttpServeHandle>;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer {\n return new McpServerImpl(source, options);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"serve.mjs","names":[],"sources":["../../../../../../../../packages/ai-tools/src/mcp/serve.ts"],"sourcesContent":["import { createInterface, type Interface } from \"node:readline\";\nimport { extractJsonSchema, type ToolContext, type ToolContract } from \"@warlock.js/ai\";\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n McpGroupedToolSource,\n McpHttpServeHandle,\n McpHttpServeTransport,\n McpPromptMessage,\n McpServeCallContext,\n McpServeHandle,\n McpServeOptions,\n McpServePrompt,\n McpServeResource,\n McpServeSource,\n McpServer,\n McpToolCallResult,\n McpToolDescriptor,\n McpToolSource,\n} from \"../contracts\";\nimport { McpServeError, McpTransportError } from \"../errors\";\nimport { LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from \"./protocol\";\nimport { startHttpServer } from \"./serve-http\";\nimport {\n assertMcpResult,\n isMcpResult,\n isRecord,\n looksLikeMcpResult,\n unwrapMcpResult,\n} from \"./result\";\n\n/** JSON-RPC version literal every outbound message carries. */\nconst JSONRPC_VERSION = \"2.0\";\n\n/** Default JSON-Schema dialect emitted for each tool's `inputSchema`. */\nconst DEFAULT_SCHEMA_TARGET = \"draft-2020-12\";\n\n/** Default advertised server version when the caller omits one. */\nconst DEFAULT_VERSION = \"4.4.0\";\n\n/** JSON-RPC standard error codes we emit. */\nconst JSON_RPC_METHOD_NOT_FOUND = -32601;\nconst JSON_RPC_INVALID_PARAMS = -32602;\n\n/** MCP's \"resource not found\" error code (server/resources, error handling). */\nconst MCP_RESOURCE_NOT_FOUND = -32002;\n\n/** One tool as exposed on the wire: its public name plus the contract behind it. */\ntype ExposedTool = {\n /** The name clients see in `tools/list` and send in `tools/call`. */\n name: string;\n /** The contract that runs. */\n contract: ToolContract;\n};\n\n/**\n * A JSON-RPC failure raised inside a method handler, carrying the exact\n * code to answer with (unknown resource, missing prompt argument, …).\n */\nclass RpcFailure extends Error {\n public readonly code: number;\n\n public readonly data: unknown;\n\n public constructor(code: number, message: string, data?: unknown) {\n super(message);\n this.code = code;\n this.data = data;\n }\n}\n\n/** Enumerate one tool set into a flat contract array. */\nfunction toolsOf(source: McpToolSource): ToolContract[] {\n return Array.isArray(source) ? source : source.tools();\n}\n\n/** Narrow a serve source to its grouped shape. */\nfunction isGroupedSource(source: McpServeSource): source is McpGroupedToolSource {\n return !Array.isArray(source) && \"groups\" in source;\n}\n\n/**\n * Resolve the serve source into the exposed tool list, applying\n * `prefixToolNames` and refusing duplicate names. Throws\n * {@link McpServeError} — `\"config\"` for `prefixToolNames` on an ungrouped\n * source, `\"collision\"` when two tools would share a name.\n */\nfunction resolveTools(source: McpServeSource, options: McpServeOptions): ExposedTool[] {\n const prefix = options.prefixToolNames ?? false;\n const exposed: Array<ExposedTool & { origin: string }> = [];\n\n if (isGroupedSource(source)) {\n for (const [group, groupSource] of Object.entries(source.groups)) {\n for (const contract of toolsOf(groupSource)) {\n let name = contract.name;\n\n if (prefix === true) {\n name = `${group}_${contract.name}`;\n } else if (typeof prefix === \"function\") {\n name = prefix(group, contract.name);\n }\n\n exposed.push({ name, contract, origin: `group \"${group}\"` });\n }\n }\n } else {\n if (prefix !== false) {\n throw new McpServeError(\n \"prefixToolNames needs a grouped source: serve({ groups: { name: tools } }, …).\",\n { type: \"config\" },\n );\n }\n\n for (const contract of toolsOf(source)) {\n exposed.push({ name: contract.name, contract, origin: \"the source\" });\n }\n }\n\n const seen = new Map<string, string>();\n\n for (const tool of exposed) {\n const previous = seen.get(tool.name);\n\n if (previous !== undefined) {\n throw new McpServeError(\n `MCP tool name \"${tool.name}\" is exposed twice (from ${previous} and ${tool.origin}). ` +\n \"Rename one tool, or serve a grouped source with prefixToolNames.\",\n { type: \"collision\", context: { name: tool.name } },\n );\n }\n\n seen.set(tool.name, tool.origin);\n }\n\n return exposed.map(({ name, contract }) => ({ name, contract }));\n}\n\n/** Throw a collision error when two entries share the same key. */\nfunction assertUnique<T>(\n entries: readonly T[] | undefined,\n key: (entry: T) => string,\n label: string,\n): void {\n const seen = new Set<string>();\n\n for (const entry of entries ?? []) {\n const value = key(entry);\n\n if (seen.has(value)) {\n throw new McpServeError(`MCP ${label} \"${value}\" is declared twice.`, {\n type: \"collision\",\n context: { [label]: value },\n });\n }\n\n seen.add(value);\n }\n}\n\n/**\n * The pure protocol core of `serve` — maps one JSON-RPC request to its\n * response, with no I/O. Both the stdio and HTTP serve-transports pump\n * their inbound requests through this, and specs can drive it directly.\n *\n * Handles `initialize`, `ping`, `tools/list`, `tools/call`, and — when\n * configured — `resources/list`, `resources/templates/list`,\n * `resources/read`, `prompts/list` and `prompts/get`. Any other method\n * answers with a JSON-RPC `method not found` error.\n *\n * Constructed via {@link createServeHandler}.\n */\nclass McpServeHandler {\n /** The tools this server exposes (snapshotted at construction). */\n private readonly tools: ExposedTool[];\n\n /** Fast lookup by exposed tool name for `tools/call` dispatch. */\n private readonly byName: Map<string, ToolContract>;\n\n /** Resources by URI, or `undefined` when the capability is off. */\n private readonly resources: Map<string, McpServeResource> | undefined;\n\n /** Prompts by name, or `undefined` when the capability is off. */\n private readonly prompts: Map<string, McpServePrompt> | undefined;\n\n /** Construction-time serve options (name / version / schema target). */\n private readonly options: McpServeOptions;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.tools = resolveTools(source, options);\n this.byName = new Map(this.tools.map((tool) => [tool.name, tool.contract]));\n\n assertUnique(options.resources, (resource) => resource.uri, \"resource\");\n assertUnique(options.prompts, (prompt) => prompt.name, \"prompt\");\n\n this.resources = options.resources\n ? new Map(options.resources.map((resource) => [resource.uri, resource]))\n : undefined;\n this.prompts = options.prompts\n ? new Map(options.prompts.map((prompt) => [prompt.name, prompt]))\n : undefined;\n this.options = options;\n }\n\n /**\n * Dispatch one inbound JSON-RPC request to its handler and produce the\n * response. A handler that throws is mapped to a JSON-RPC error response\n * — the serve loop never crashes on a bad request.\n *\n * @param request - The JSON-RPC request.\n * @param context - Per-call context: the inbound HTTP request (headers\n * reach tools as `ctx.request`) and an abort signal.\n */\n public async handle(\n request: JsonRpcRequest,\n context: McpServeCallContext = {},\n ): Promise<JsonRpcResponse> {\n try {\n return this.ok(request.id, await this.dispatch(request, context));\n } catch (cause) {\n if (cause instanceof RpcFailure) {\n return this.error(request.id, cause.code, cause.message, cause.data);\n }\n\n const message = cause instanceof Error ? cause.message : String(cause);\n\n return this.error(request.id, JSON_RPC_INVALID_PARAMS, message);\n }\n }\n\n /** Route a method to its result builder. */\n private async dispatch(request: JsonRpcRequest, context: McpServeCallContext): Promise<unknown> {\n switch (request.method) {\n case \"initialize\":\n return this.initializeResult(request.params);\n case \"ping\":\n return {};\n case \"tools/list\":\n return { tools: this.listTools() };\n case \"tools/call\":\n return this.callTool(request.params, context);\n default:\n break;\n }\n\n if (this.resources) {\n switch (request.method) {\n case \"resources/list\":\n return { resources: this.listResources(this.resources) };\n case \"resources/templates/list\":\n return { resourceTemplates: [] };\n case \"resources/read\":\n return this.readResource(this.resources, request.params, context);\n default:\n break;\n }\n }\n\n if (this.prompts) {\n switch (request.method) {\n case \"prompts/list\":\n return { prompts: this.listPrompts(this.prompts) };\n case \"prompts/get\":\n return this.getPrompt(this.prompts, request.params, context);\n default:\n break;\n }\n }\n\n throw new RpcFailure(\n JSON_RPC_METHOD_NOT_FOUND,\n `Method \"${request.method}\" is not supported by this MCP server.`,\n );\n }\n\n /**\n * Build the `initialize` result advertising name / version /\n * capabilities, negotiating the protocol version: the client's requested\n * version when supported, otherwise the newest this server speaks.\n */\n private initializeResult(params: unknown): Record<string, unknown> {\n const requested = readRecord(params).protocolVersion;\n const protocolVersion =\n typeof requested === \"string\" && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)\n ? requested\n : LATEST_PROTOCOL_VERSION;\n\n const capabilities: Record<string, unknown> = { tools: {} };\n\n if (this.resources) {\n capabilities.resources = {};\n }\n\n if (this.prompts) {\n capabilities.prompts = {};\n }\n\n return {\n protocolVersion,\n capabilities,\n serverInfo: {\n name: this.options.name,\n version: this.options.version ?? DEFAULT_VERSION,\n },\n };\n }\n\n /**\n * Build the `tools/list` payload: one {@link McpToolDescriptor} per\n * exposed tool, its `inputSchema` extracted via `extractJsonSchema` at\n * the configured dialect (default `draft-2020-12` — overriding\n * `extractJsonSchema`'s own `openai-strict` default to a neutral MCP draft).\n */\n private listTools(): McpToolDescriptor[] {\n const target = this.options.schemaTarget ?? DEFAULT_SCHEMA_TARGET;\n\n return this.tools.map(({ name, contract }) => {\n const inputSchema = extractJsonSchema(contract.input, { target }) ?? {\n type: \"object\",\n properties: {},\n };\n\n return {\n name,\n description: contract.description,\n inputSchema,\n ...(isRecord(contract.meta?.outputSchema)\n ? { outputSchema: contract.meta.outputSchema }\n : {}),\n };\n });\n }\n\n /**\n * Route a `tools/call` to the named contract's `invoke()` and map the\n * never-throwing {@link import(\"@warlock.js/ai\").ToolInvokeResult}: `data`\n * → a text content block (or an opted-in native result), `error` → an\n * `isError: true` result. An unknown tool name throws (mapped to a\n * JSON-RPC error by {@link handle}).\n *\n * The contract receives a `ToolContext` carrying the abort signal and,\n * over HTTP, the inbound request's headers on `ctx.request`.\n */\n private async callTool(\n params: unknown,\n context: McpServeCallContext,\n ): Promise<McpToolCallResult> {\n const { name, args } = readCallParams(params);\n const contract = this.byName.get(name);\n\n if (!contract) {\n throw new McpTransportError(`Unknown tool \"${name}\".`, {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const toolContext: ToolContext = { artifacts: {} };\n\n if (context.signal) {\n toolContext.signal = context.signal;\n }\n\n if (context.request) {\n const { headers, remoteAddress } = context.request;\n\n toolContext.request = remoteAddress === undefined ? { headers } : { headers, remoteAddress };\n }\n\n const result = await contract.invoke(args, toolContext);\n\n if (result.error) {\n return {\n content: [{ type: \"text\", text: result.error.message }],\n isError: true,\n };\n }\n\n const mode = contract.meta?.mcpResults ?? this.options.results ?? \"text\";\n const branded = isMcpResult(result.data);\n\n if (branded || (mode === \"native\" && looksLikeMcpResult(result.data))) {\n const native = isMcpResult(result.data) ? unwrapMcpResult(result.data) : result.data;\n\n try {\n assertMcpResult(native);\n } catch {\n throw new RpcFailure(JSON_RPC_INTERNAL_ERROR, \"Invalid native MCP CallToolResult.\");\n }\n\n return native;\n }\n\n return {\n content: [{ type: \"text\", text: serializeData(result.data) }],\n isError: false,\n };\n }\n\n /** Build the `resources/list` payload (metadata only, no contents). */\n private listResources(resources: Map<string, McpServeResource>): Record<string, unknown>[] {\n return [...resources.values()].map((resource) =>\n compact({\n uri: resource.uri,\n name: resource.name,\n title: resource.title,\n description: resource.description,\n mimeType: resource.mimeType,\n }),\n );\n }\n\n /**\n * Answer `resources/read`: run the resource's `read()` and wrap the\n * result as one text or base64 blob content entry.\n */\n private async readResource(\n resources: Map<string, McpServeResource>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const uri = readRecord(params).uri;\n\n if (typeof uri !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"resources/read requires a string `uri`.\");\n }\n\n const resource = resources.get(uri);\n\n if (!resource) {\n throw new RpcFailure(MCP_RESOURCE_NOT_FOUND, \"Resource not found\", { uri });\n }\n\n const body = await resource.read(context);\n const entry =\n typeof body === \"string\"\n ? { uri, mimeType: resource.mimeType, text: body }\n : { uri, mimeType: resource.mimeType, blob: Buffer.from(body).toString(\"base64\") };\n\n return { contents: [compact(entry)] };\n }\n\n /** Build the `prompts/list` payload. */\n private listPrompts(prompts: Map<string, McpServePrompt>): Record<string, unknown>[] {\n return [...prompts.values()].map((prompt) =>\n compact({\n name: prompt.name,\n title: prompt.title,\n description: prompt.description,\n arguments: prompt.arguments,\n }),\n );\n }\n\n /**\n * Answer `prompts/get`: validate the arguments (strings only; required\n * ones present) and build the messages. An unknown prompt or a missing\n * required argument answers invalid-params, per the MCP prompts spec.\n */\n private async getPrompt(\n prompts: Map<string, McpServePrompt>,\n params: unknown,\n context: McpServeCallContext,\n ): Promise<Record<string, unknown>> {\n const record = readRecord(params);\n const name = record.name;\n\n if (typeof name !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, \"prompts/get requires a string `name`.\");\n }\n\n const prompt = prompts.get(name);\n\n if (!prompt) {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Unknown prompt \"${name}\".`);\n }\n\n const args: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(readRecord(record.arguments))) {\n if (typeof value !== \"string\") {\n throw new RpcFailure(JSON_RPC_INVALID_PARAMS, `Prompt argument \"${key}\" must be a string.`);\n }\n\n args[key] = value;\n }\n\n for (const argument of prompt.arguments ?? []) {\n if (argument.required && args[argument.name] === undefined) {\n throw new RpcFailure(\n JSON_RPC_INVALID_PARAMS,\n `Prompt \"${name}\" requires the \"${argument.name}\" argument.`,\n );\n }\n }\n\n const built = await prompt.get(args, context);\n const messages: McpPromptMessage[] =\n typeof built === \"string\"\n ? [{ role: \"user\", content: { type: \"text\", text: built } }]\n : built;\n\n return compact({ description: prompt.description, messages });\n }\n\n /** Build a JSON-RPC success response. */\n private ok(id: JsonRpcRequest[\"id\"], result: unknown): JsonRpcResponse {\n return { jsonrpc: JSONRPC_VERSION, id, result };\n }\n\n /** Build a JSON-RPC error response. */\n private error(\n id: JsonRpcRequest[\"id\"],\n code: number,\n message: string,\n data?: unknown,\n ): JsonRpcResponse {\n const error = data === undefined ? { code, message } : { code, message, data };\n\n return { jsonrpc: JSONRPC_VERSION, id, error };\n }\n}\n\n/** JSON-RPC \"Invalid Request\" — the line is JSON but not a request object. */\nconst JSON_RPC_INVALID_REQUEST = -32600;\n\n/** JSON-RPC \"Internal error\" — answering the request failed unexpectedly. */\nconst JSON_RPC_INTERNAL_ERROR = -32603;\n\n/**\n * Turn one stdio line into the line to write back, or `undefined` when\n * nothing is answered: blank or non-JSON lines and notifications (no `id`).\n * A JSON value that is not a request object (`null`, a number, an array) is\n * answered with a JSON-RPC \"Invalid Request\" error whose `id` is `null`.\n * Never rejects: a dispatch failure is answered as an internal error.\n */\nexport async function answerStdioLine(\n line: string,\n dispatch: (request: JsonRpcRequest) => Promise<JsonRpcResponse>,\n): Promise<string | undefined> {\n const trimmed = line.trim();\n\n if (!trimmed) {\n return undefined;\n }\n\n let message: unknown;\n\n try {\n message = JSON.parse(trimmed);\n } catch {\n return undefined;\n }\n\n if (typeof message !== \"object\" || message === null || Array.isArray(message)) {\n const invalid = {\n jsonrpc: JSONRPC_VERSION,\n id: null,\n error: { code: JSON_RPC_INVALID_REQUEST, message: \"Invalid request: not a JSON-RPC object.\" },\n };\n\n return `${JSON.stringify(invalid)}\\n`;\n }\n\n const request = message as JsonRpcRequest;\n const id: unknown = request.id;\n\n if (id === undefined || id === null) {\n // A notification (e.g. notifications/initialized) — nothing to answer.\n return undefined;\n }\n\n try {\n const response = await dispatch(request);\n\n return `${JSON.stringify(response)}\\n`;\n } catch {\n const failed = {\n jsonrpc: JSONRPC_VERSION,\n id,\n error: { code: JSON_RPC_INTERNAL_ERROR, message: \"Internal error.\" },\n };\n\n return `${JSON.stringify(failed)}\\n`;\n }\n}\n\n/** Read an unknown value as a plain record (non-objects read as empty). */\nfunction readRecord(value: unknown): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {};\n }\n\n return value as Record<string, unknown>;\n}\n\n/** Drop `undefined` members so optional wire fields are omitted, not `null`. */\nfunction compact(value: Record<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n\n for (const [key, member] of Object.entries(value)) {\n if (member !== undefined) {\n result[key] = member;\n }\n }\n\n return result;\n}\n\n/**\n * Read and validate the `tools/call` params into `{ name, args }`. Throws\n * a typed {@link McpTransportError} when `name` is missing — mapped to a\n * JSON-RPC `invalid params` error by the handler.\n */\nfunction readCallParams(params: unknown): { name: string; args: unknown } {\n if (typeof params !== \"object\" || params === null) {\n throw new McpTransportError(\"tools/call params must be an object.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n const record = params as { name?: unknown; arguments?: unknown };\n\n if (typeof record.name !== \"string\") {\n throw new McpTransportError(\"tools/call requires a string `name`.\", {\n type: \"protocol\",\n method: \"tools/call\",\n });\n }\n\n return { name: record.name, args: record.arguments ?? {} };\n}\n\n/**\n * Serialize a tool's `data` for an MCP text content block — a string is\n * passed verbatim, everything else is JSON-stringified so structured\n * output crosses the wire as text the consuming client can re-parse.\n */\nfunction serializeData(data: unknown): string {\n if (typeof data === \"string\") {\n return data;\n }\n\n if (data === undefined) {\n return \"\";\n }\n\n return JSON.stringify(data);\n}\n\n/**\n * The pure protocol handler: one JSON-RPC request in, one response out.\n */\nexport interface McpServeRequestHandler {\n /**\n * Map one request to its response.\n *\n * @param request - The JSON-RPC request.\n * @param context - Optional per-call context (inbound HTTP request, abort signal).\n */\n handle(request: JsonRpcRequest, context?: McpServeCallContext): Promise<JsonRpcResponse>;\n}\n\n/**\n * Build the pure protocol handler for a serve source. Exposed (alongside\n * {@link serve}) so callers and tests can drive the MCP protocol without an\n * actual transport — feed it a JSON-RPC request, get the response.\n *\n * Throws {@link McpServeError} on duplicate tool names / resource URIs /\n * prompt names, or `prefixToolNames` on an ungrouped source.\n *\n * @param source - The tools to expose (one tool set or `{ groups }`).\n * @param options - Serve options (name / version / schema target / resources / prompts).\n * @returns An object whose `handle(request, context?)` maps a request to a response.\n */\nexport function createServeHandler(\n source: McpServeSource,\n options: McpServeOptions,\n): McpServeRequestHandler {\n return new McpServeHandler(source, options);\n}\n\n/**\n * The internal {@link McpServer} — owns a {@link McpServeHandler} and a\n * transport pump. For `stdio` it reads newline-delimited JSON-RPC requests\n * from `process.stdin` and writes responses to `process.stdout`; for\n * `http` it binds a Streamable HTTP endpoint (see `./serve-http`).\n *\n * Constructed via {@link serve}; the class itself is internal.\n */\nclass McpServerImpl implements McpServer {\n /** The pure protocol handler. */\n private readonly handler: McpServeHandler;\n\n /** Serve options (transport selection lives here). */\n private readonly options: McpServeOptions;\n\n /** The running handle (stdio or http), while started or starting. */\n private running: Promise<McpServeHandle> | undefined;\n\n public constructor(source: McpServeSource, options: McpServeOptions) {\n this.handler = new McpServeHandler(source, options);\n this.options = options;\n }\n\n public start(): Promise<McpServeHandle> {\n if (!this.running) {\n const transport = this.options.transport ?? { type: \"stdio\" };\n const starting =\n transport.type === \"http\" ? this.startHttp(transport) : Promise.resolve(this.startStdio());\n\n // A failed start leaves the server startable again.\n this.running = starting.catch((error: unknown) => {\n this.running = undefined;\n throw error;\n });\n }\n\n return this.running;\n }\n\n /** Bind the HTTP endpoint; its `close()` also makes this server startable again. */\n private async startHttp(transport: McpHttpServeTransport): Promise<McpHttpServeHandle> {\n const http = await startHttpServer(transport, (request, context) =>\n this.handler.handle(request, context),\n );\n\n return {\n url: http.url,\n close: async () => {\n await http.close();\n this.running = undefined;\n },\n };\n }\n\n /** Wire the stdin line reader and answer each request on stdout. */\n private startStdio(): McpServeHandle {\n const reader: Interface = createInterface({ input: process.stdin });\n\n reader.on(\"line\", (line) => {\n // A failure answering one line must never become an unhandled\n // rejection: that would take the whole server process down.\n void this.onLine(line).catch(() => {});\n });\n\n return {\n close: async () => {\n reader.close();\n this.running = undefined;\n },\n };\n }\n\n /** Answer one stdin line on stdout (see {@link answerStdioLine}). */\n private async onLine(line: string): Promise<void> {\n const reply = await answerStdioLine(line, (request) => this.handler.handle(request));\n\n if (reply !== undefined) {\n process.stdout.write(reply);\n }\n }\n\n public async stop(): Promise<void> {\n const running = this.running;\n\n if (!running) {\n return;\n }\n\n let handle: McpServeHandle;\n\n try {\n handle = await running;\n } catch {\n // A start that failed has nothing to close.\n return;\n }\n\n await handle.close();\n }\n}\n\n/**\n * Expose a built agent / supervisor / orchestrator (or a raw\n * `ToolContract[]`, or several named groups) AS an MCP server (Direction B:\n * local primitive → MCP server other clients consume).\n *\n * Enumerates the tools once, here. Every exposed name must be unique — a\n * duplicate throws {@link McpServeError} (`type: \"collision\"`) right away;\n * serve `{ groups: { … } }` with `prefixToolNames: true` to namespace\n * same-named tools from different sets. `tools/list` answers with each\n * tool's `inputSchema` extracted via `extractJsonSchema` at the configured\n * `schemaTarget` (default `draft-2020-12`). `tools/call` routes to the\n * contract's `invoke()` and maps the never-throwing result — `data`\n * becomes a text content block, `error` an `isError: true` result.\n *\n * Transports: `stdio` (default) pumps `process.stdin` / `process.stdout`;\n * `http` binds an MCP Streamable HTTP endpoint on `127.0.0.1` by default,\n * with bearer / custom auth (required off loopback), `Origin` and `Host`\n * validation against DNS rebinding, a body-size cap, and a draining\n * `close()`. `start()` resolves `{ url, close }`.\n *\n * @param source - The tools to expose.\n * @param options - Server name, version, transport, schema dialect, resources, prompts.\n * @returns An {@link McpServer} with `start()` / `stop()`.\n *\n * @example\n * const { url, close } = await serve([calc, clock], {\n * name: \"session-tools\",\n * transport: { type: \"http\", auth: { bearer: sessionToken } },\n * }).start();\n */\nexport function serve(\n source: McpServeSource,\n options: McpServeOptions & { transport: McpHttpServeTransport },\n): McpServer<McpHttpServeHandle>;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer;\nexport function serve(source: McpServeSource, options: McpServeOptions): McpServer {\n return new McpServerImpl(source, options);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { McpTransport } from "../contracts/mcp.type.mjs";
2
+ import "../contracts/index.mjs";
2
3
  import { McpTransportClient } from "./transport.type.mjs";
3
-
4
4
  //#region ../../packages/ai-tools/src/mcp/transport.d.ts
5
5
  /**
6
6
  * Build the concrete {@link McpTransportClient} for an {@link McpTransport}
@@ -11,7 +11,7 @@ import { McpTransportClient } from "./transport.type.mjs";
11
11
  * @param transport - The transport config (discriminated by `type`).
12
12
  * @returns A transport client paired with its id allocator.
13
13
  */
14
- declare function createTransport(transport: McpTransport): McpTransportClient & {
14
+ export declare function createTransport(transport: McpTransport): McpTransportClient & {
15
15
  allocateId(): number;
16
16
  };
17
17
  /**
@@ -19,7 +19,7 @@ declare function createTransport(transport: McpTransport): McpTransportClient &
19
19
  * transport client (tests inject a fake) or an {@link McpTransport} config
20
20
  * to spawn/connect a real one.
21
21
  */
22
- interface JsonRpcClientHandle {
22
+ export interface JsonRpcClientHandle {
23
23
  /** Issue a request and resolve with its `result` (throws on error). */
24
24
  call<TResult = unknown>(method: string, params?: unknown, options?: {
25
25
  signal?: AbortSignal;
@@ -42,7 +42,6 @@ interface JsonRpcClientHandle {
42
42
  * @param source - A transport client or an `McpTransport` config.
43
43
  * @returns A JSON-RPC client handle.
44
44
  */
45
- declare function createJsonRpcClient(source: McpTransport | McpTransportClient): JsonRpcClientHandle;
45
+ export declare function createJsonRpcClient(source: McpTransport | McpTransportClient): JsonRpcClientHandle;
46
46
  //#endregion
47
- export { JsonRpcClientHandle, createJsonRpcClient, createTransport };
48
47
  //# sourceMappingURL=transport.d.mts.map