New:Socket for Asana Is Now Available.Learn more
Get Started

@modelcontextprotocol/node

Package Overview
Dependencies
Maintainers
6
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@modelcontextprotocol/node - npm Package Compare versions

Comparing version
2.0.0-alpha.3
to
2.0.0-alpha.4
+156
-3
dist/index.d.mts

@@ -1,7 +0,66 @@

import { AuthInfo, JSONRPCMessage, MessageExtraInfo, RequestId, Transport, WebStandardStreamableHTTPServerTransportOptions } from "@modelcontextprotocol/server";
import { AuthInfo, JSONRPCMessage, McpHandlerRequestOptions, MessageExtraInfo, RequestId, Transport, WebStandardStreamableHTTPServerTransportOptions } from "@modelcontextprotocol/server";
import { IncomingMessage, ServerResponse } from "node:http";
//#region src/streamableHttp.d.ts
//#region src/middleware/hostHeaderValidation.d.ts
/**
* Node.js request guard for DNS rebinding protection.
* Validates the `Host` header hostname (port-agnostic) against an allowed list.
*
* Unlike the framework adapters, plain `node:http` has no middleware chain, so
* the guard returns whether the request may proceed: when it returns `false`
* it has already answered the request with a `403` JSON-RPC error and the
* caller must not handle it further.
*
* @param allowedHostnames - List of allowed hostnames (without ports).
* For IPv6, provide the address with brackets (e.g., `[::1]`).
*
* @example
* ```ts
* const validateHost = hostHeaderValidation(['localhost', '127.0.0.1', '[::1]']);
* http.createServer((req, res) => {
* if (!validateHost(req, res)) return;
* void transport.handleRequest(req, res);
* });
* ```
*/
declare function hostHeaderValidation(allowedHostnames: string[]): (req: IncomingMessage, res: ServerResponse) => boolean;
/**
* Convenience guard for localhost DNS rebinding protection.
* Allows only `localhost`, `127.0.0.1`, and `[::1]` (IPv6 localhost) hostnames.
*/
declare function localhostHostValidation(): (req: IncomingMessage, res: ServerResponse) => boolean;
//#endregion
//#region src/middleware/originValidation.d.ts
/**
* Node.js request guard for Origin header validation.
* Validates the `Origin` header hostname (port-agnostic) against an allowed list.
*
* Requests without an `Origin` header pass (non-browser MCP clients do not send
* one); a present value that is not allowed, or that cannot be parsed, is
* rejected with `403`. The guard returns whether the request may proceed: when
* it returns `false` it has already answered the request and the caller must
* not handle it further.
*
* @param allowedOriginHostnames - List of allowed origin hostnames (without scheme or port).
* For IPv6, provide the address with brackets (e.g., `[::1]`).
*
* @example
* ```ts
* const validateOrigin = originValidation(['localhost', '127.0.0.1', '[::1]']);
* http.createServer((req, res) => {
* if (!validateOrigin(req, res)) return;
* void transport.handleRequest(req, res);
* });
* ```
*/
declare function originValidation(allowedOriginHostnames: string[]): (req: IncomingMessage, res: ServerResponse) => boolean;
/**
* Convenience guard for localhost Origin validation.
* Allows only origins whose hostname is `localhost`, `127.0.0.1`, or `[::1]` (IPv6 localhost).
*/
declare function localhostOriginValidation(): (req: IncomingMessage, res: ServerResponse) => boolean;
//#endregion
//#region src/streamableHttp.d.ts
/**
* Configuration options for {@linkcode NodeStreamableHTTPServerTransport}

@@ -95,2 +154,10 @@ *

/**
* Forwards the supported protocol versions to the wrapped Web Standard
* transport for `MCP-Protocol-Version` header validation. Called by the
* protocol layer during connect; without this delegation a server's
* `supportedProtocolVersions` option never reached the Node adapter's
* header validation.
*/
setSupportedProtocolVersions(versions: string[]): void;
/**
* Handles an incoming HTTP request, whether `GET` or `POST`.

@@ -121,3 +188,89 @@ *

//#endregion
export { NodeStreamableHTTPServerTransport, StreamableHTTPServerTransportOptions };
//#region src/toNodeHandler.d.ts
/**
* Minimal duck-typed shape of a Node.js `IncomingMessage` accepted by
* {@linkcode toNodeHandler}. Kept structural so the adapter stays free of
* `node:` imports.
*/
interface NodeIncomingMessageLike extends AsyncIterable<unknown> {
method?: string;
url?: string;
headers: Record<string, string | string[] | undefined>;
/** Validated authentication info attached by upstream middleware (pass-through). */
auth?: AuthInfo;
}
/** Minimal duck-typed shape of a Node.js `ServerResponse` accepted by {@linkcode toNodeHandler}. */
interface NodeServerResponseLike {
writeHead(statusCode: number, headers?: Record<string, string>): unknown;
write(chunk: string | Uint8Array): unknown;
end(chunk?: string | Uint8Array): unknown;
on(event: string, listener: (...args: unknown[]) => void): unknown;
destroyed?: boolean;
}
/**
* The web-standard fetch face of an `McpHttpHandler` (or any
* fetch-shaped MCP handler) — the only surface {@linkcode toNodeHandler}
* touches. Accepting the face structurally keeps the adapter usable with
* hand-wired compositions that route over `isLegacyRequest` and produce a
* `Response` directly.
*/
interface FetchLikeMcpHandler {
fetch: (request: Request, options?: McpHandlerRequestOptions) => Promise<Response>;
}
/**
* A Node.js `(req, res, parsedBody?)` request handler produced by
* {@linkcode toNodeHandler}. The third argument is an optional pre-parsed body
* (`req.body` from `express.json()`); a function third argument (Express's
* `next` when the handler is mounted as middleware) is ignored.
*/
type NodeMcpRequestHandler = (req: NodeIncomingMessageLike, res: NodeServerResponseLike, parsedBody?: unknown) => Promise<void>;
/** Options for {@linkcode toNodeHandler}. */
interface ToNodeHandlerOptions {
/**
* Called when the adapter answers `500` because request conversion or
* `handler.fetch` itself threw (e.g. a closed handler). Restores the
* observability the removed `.node` face had via the entry's own
* `onerror` — entry-internal failures are still reported through
* `handler.fetch` and surface via the entry's `onerror` option as before.
*/
onerror?: (error: Error) => void;
}
/**
* Adapts a web-standard MCP handler (`handler.fetch`) to a Node.js
* `(req, res, parsedBody?)` request handler. The returned function converts the
* Node request to a web-standard `Request`, calls `handler.fetch`, then writes
* the `Response` back to `res` (honoring write backpressure for streamed SSE
* responses).
*
* `req.auth` is forwarded as the handler's pass-through `authInfo`. A function
* third argument (Express's `next`) is ignored, never treated as a body.
*
* Pass `{ onerror }` to observe the adapter-level error fallback (request
* conversion / `handler.fetch` throw) before the `500` response is written.
*/
declare function toNodeHandler(handler: FetchLikeMcpHandler, opts?: ToNodeHandlerOptions): NodeMcpRequestHandler;
/** Options for {@linkcode toWebRequest}. */
interface ToWebRequestOptions {
/** An `AbortSignal` to attach to the constructed `Request` (`request.signal`). */
signal?: AbortSignal;
}
/**
* Convert a Node.js `IncomingMessage` (duck-typed — an Express `req` works) to
* the web-standard `Request` that `handler.fetch()` and `isLegacyRequest()`
* take. This is the conversion {@linkcode toNodeHandler} performs internally,
* exported for hand-wired compositions:
*
* ```ts
* const probe = await toWebRequest(req, req.body);
* await ((await isLegacyRequest(probe)) ? legacy(req, res) : modern(req, res, req.body));
* ```
*
* With no `parsedBody` the Node stream is read to completion — read the body
* from the returned `Request` afterwards, not from `req`. When a body parser
* already consumed the stream (`express.json()`), pass the parsed value as
* `parsedBody` and nothing is read from `req`.
*/
declare function toWebRequest(req: NodeIncomingMessageLike, parsedBody?: unknown, options?: ToWebRequestOptions): Promise<Request>;
//#endregion
export { type FetchLikeMcpHandler, type NodeIncomingMessageLike, type NodeMcpRequestHandler, type NodeServerResponseLike, NodeStreamableHTTPServerTransport, StreamableHTTPServerTransportOptions, type ToNodeHandlerOptions, type ToWebRequestOptions, hostHeaderValidation, localhostHostValidation, localhostOriginValidation, originValidation, toNodeHandler, toWebRequest };
//# sourceMappingURL=index.d.mts.map
+1
-1

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.mts","names":[],"sources":["../src/streamableHttp.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA4JwB,KAjIZ,oCAAA,GAAuC,+CAiI3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cApFX,iCAAA,YAA6C;;;;wBAMjC;;;;;;;;;;;;;gCAyCS;0BAIN;;;;oCAOU,wBAAwB;8BAI9B,wBAAwB;;;;;WAQrC;;;;WAOA;;;;gBAOK;uBAA+C;MAAc;;;;;;;;;;;qBAcxD;WAA2B;UAAiB,uCAAuC;;;;;;4BA4BlF"}
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/middleware/hostHeaderValidation.ts","../src/middleware/originValidation.ts","../src/streamableHttp.ts","../src/toNodeHandler.ts"],"sourcesContent":[],"mappings":"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"}

@@ -0,4 +1,99 @@

import { WebStandardStreamableHTTPServerTransport, localhostAllowedHostnames, localhostAllowedOrigins, validateHostHeader, validateOriginHeader } from "@modelcontextprotocol/server";
import { getRequestListener } from "@hono/node-server";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/server";
//#region src/middleware/hostHeaderValidation.ts
/**
* Node.js request guard for DNS rebinding protection.
* Validates the `Host` header hostname (port-agnostic) against an allowed list.
*
* Unlike the framework adapters, plain `node:http` has no middleware chain, so
* the guard returns whether the request may proceed: when it returns `false`
* it has already answered the request with a `403` JSON-RPC error and the
* caller must not handle it further.
*
* @param allowedHostnames - List of allowed hostnames (without ports).
* For IPv6, provide the address with brackets (e.g., `[::1]`).
*
* @example
* ```ts
* const validateHost = hostHeaderValidation(['localhost', '127.0.0.1', '[::1]']);
* http.createServer((req, res) => {
* if (!validateHost(req, res)) return;
* void transport.handleRequest(req, res);
* });
* ```
*/
function hostHeaderValidation(allowedHostnames) {
return (req, res) => {
const result = validateHostHeader(req.headers.host, allowedHostnames);
if (result.ok) return true;
res.writeHead(403, { "Content-Type": "application/json" });
res.end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32e3,
message: result.message
},
id: null
}));
return false;
};
}
/**
* Convenience guard for localhost DNS rebinding protection.
* Allows only `localhost`, `127.0.0.1`, and `[::1]` (IPv6 localhost) hostnames.
*/
function localhostHostValidation() {
return hostHeaderValidation(localhostAllowedHostnames());
}
//#endregion
//#region src/middleware/originValidation.ts
/**
* Node.js request guard for Origin header validation.
* Validates the `Origin` header hostname (port-agnostic) against an allowed list.
*
* Requests without an `Origin` header pass (non-browser MCP clients do not send
* one); a present value that is not allowed, or that cannot be parsed, is
* rejected with `403`. The guard returns whether the request may proceed: when
* it returns `false` it has already answered the request and the caller must
* not handle it further.
*
* @param allowedOriginHostnames - List of allowed origin hostnames (without scheme or port).
* For IPv6, provide the address with brackets (e.g., `[::1]`).
*
* @example
* ```ts
* const validateOrigin = originValidation(['localhost', '127.0.0.1', '[::1]']);
* http.createServer((req, res) => {
* if (!validateOrigin(req, res)) return;
* void transport.handleRequest(req, res);
* });
* ```
*/
function originValidation(allowedOriginHostnames) {
return (req, res) => {
const result = validateOriginHeader(req.headers.origin, allowedOriginHostnames);
if (result.ok) return true;
res.writeHead(403, { "Content-Type": "application/json" });
res.end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32e3,
message: result.message
},
id: null
}));
return false;
};
}
/**
* Convenience guard for localhost Origin validation.
* Allows only origins whose hostname is `localhost`, `127.0.0.1`, or `[::1]` (IPv6 localhost).
*/
function localhostOriginValidation() {
return originValidation(localhostAllowedOrigins());
}
//#endregion
//#region src/streamableHttp.ts

@@ -115,2 +210,12 @@ /**

/**
* Forwards the supported protocol versions to the wrapped Web Standard
* transport for `MCP-Protocol-Version` header validation. Called by the
* protocol layer during connect; without this delegation a server's
* `supportedProtocolVersions` option never reached the Node adapter's
* header validation.
*/
setSupportedProtocolVersions(versions) {
this._webStandardTransport.setSupportedProtocolVersions(versions);
}
/**
* Handles an incoming HTTP request, whether `GET` or `POST`.

@@ -152,3 +257,143 @@ *

//#endregion
export { NodeStreamableHTTPServerTransport };
//#region src/toNodeHandler.ts
/**
* Adapts a web-standard MCP handler (`handler.fetch`) to a Node.js
* `(req, res, parsedBody?)` request handler. The returned function converts the
* Node request to a web-standard `Request`, calls `handler.fetch`, then writes
* the `Response` back to `res` (honoring write backpressure for streamed SSE
* responses).
*
* `req.auth` is forwarded as the handler's pass-through `authInfo`. A function
* third argument (Express's `next`) is ignored, never treated as a body.
*
* Pass `{ onerror }` to observe the adapter-level error fallback (request
* conversion / `handler.fetch` throw) before the `500` response is written.
*/
function toNodeHandler(handler, opts) {
return async (req, res, parsedBody) => {
if (typeof parsedBody === "function") parsedBody = void 0;
let finished = false;
const abort = new AbortController();
res.on("close", () => {
if (!finished) abort.abort();
});
if (res.destroyed === true) abort.abort();
let response;
try {
const request = await toWebRequest(req, parsedBody, { signal: abort.signal });
response = await handler.fetch(request, {
...req.auth !== void 0 && { authInfo: req.auth },
...parsedBody !== void 0 && { parsedBody }
});
} catch (error) {
try {
opts?.onerror?.(error instanceof Error ? error : new Error(String(error)));
} catch {}
response = internalServerErrorResponse(echoableRequestId(parsedBody));
}
const headers = {};
for (const [name, value] of response.headers) headers[name] = value;
res.writeHead(response.status, headers);
if (response.body === null) {
finished = true;
res.end();
return;
}
let drainResolve;
const releaseDrainWait = () => {
drainResolve?.();
drainResolve = void 0;
};
res.on("drain", releaseDrainWait);
const closed = new Promise((resolve) => {
abort.signal.addEventListener("abort", () => resolve(), { once: true });
});
try {
for await (const chunk of response.body) {
if (abort.signal.aborted) break;
if (res.write(chunk) === false) await Promise.race([new Promise((resolve) => {
drainResolve = resolve;
}), closed]);
}
} catch {}
finished = true;
res.end();
};
}
function singleHeaderValue(value) {
return Array.isArray(value) ? value[0] : value;
}
/**
* Convert a Node.js `IncomingMessage` (duck-typed — an Express `req` works) to
* the web-standard `Request` that `handler.fetch()` and `isLegacyRequest()`
* take. This is the conversion {@linkcode toNodeHandler} performs internally,
* exported for hand-wired compositions:
*
* ```ts
* const probe = await toWebRequest(req, req.body);
* await ((await isLegacyRequest(probe)) ? legacy(req, res) : modern(req, res, req.body));
* ```
*
* With no `parsedBody` the Node stream is read to completion — read the body
* from the returned `Request` afterwards, not from `req`. When a body parser
* already consumed the stream (`express.json()`), pass the parsed value as
* `parsedBody` and nothing is read from `req`.
*/
async function toWebRequest(req, parsedBody, options) {
const method = (req.method ?? "GET").toUpperCase();
const url = `http://${singleHeaderValue(req.headers["host"]) ?? singleHeaderValue(req.headers[":authority"]) ?? "localhost"}${req.url ?? "/"}`;
const headers = new Headers();
for (const [name, value] of Object.entries(req.headers)) {
if (value === void 0 || name.startsWith(":")) continue;
if (Array.isArray(value)) for (const item of value) headers.append(name, item);
else headers.set(name, value);
}
let body;
if (method !== "GET" && method !== "HEAD") if (parsedBody === void 0) {
const decoder = new TextDecoder();
let collected = "";
for await (const chunk of req) collected += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
collected += decoder.decode();
if (collected.length > 0) body = collected;
} else {
const serialized = JSON.stringify(parsedBody);
headers.delete("content-encoding");
headers.delete("transfer-encoding");
if (serialized === void 0) headers.delete("content-length");
else {
body = serialized;
headers.set("content-length", String(new TextEncoder().encode(serialized).byteLength));
}
}
return new Request(url, {
method,
headers,
...options?.signal !== void 0 && { signal: options.signal },
...body !== void 0 && { body }
});
}
/**
* The JSON-RPC id to echo on an adapter-built error response: the body's `id`
* when the body is a single JSON-RPC request whose id is a string or number,
* `null` otherwise.
*/
function echoableRequestId(body) {
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
const { method, id } = body;
if (typeof method !== "string") return null;
return typeof id === "string" || typeof id === "number" ? id : null;
}
function internalServerErrorResponse(id) {
return Response.json({
jsonrpc: "2.0",
error: {
code: -32603,
message: "Internal server error"
},
id
}, { status: 500 });
}
//#endregion
export { NodeStreamableHTTPServerTransport, hostHeaderValidation, localhostHostValidation, localhostOriginValidation, originValidation, toNodeHandler, toWebRequest };
//# sourceMappingURL=index.mjs.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.mjs","names":[],"sources":["../src/streamableHttp.ts"],"sourcesContent":["/**\n * Node.js Streamable HTTP Server Transport\n *\n * This is a thin wrapper around {@linkcode WebStandardStreamableHTTPServerTransport} that provides\n * compatibility with Node.js HTTP server (`IncomingMessage`/`ServerResponse`).\n *\n * For web-standard environments (Cloudflare Workers, Deno, Bun), use {@linkcode WebStandardStreamableHTTPServerTransport} directly.\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { getRequestListener } from '@hono/node-server';\nimport type {\n AuthInfo,\n JSONRPCMessage,\n MessageExtraInfo,\n RequestId,\n Transport,\n WebStandardStreamableHTTPServerTransportOptions\n} from '@modelcontextprotocol/server';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';\n\n/**\n * Configuration options for {@linkcode NodeStreamableHTTPServerTransport}\n *\n * This is an alias for {@linkcode WebStandardStreamableHTTPServerTransportOptions} for backward compatibility.\n */\nexport type StreamableHTTPServerTransportOptions = WebStandardStreamableHTTPServerTransportOptions;\n\n/**\n * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.\n * It supports both SSE streaming and direct HTTP responses.\n *\n * This is a wrapper around {@linkcode WebStandardStreamableHTTPServerTransport} that provides Node.js HTTP compatibility.\n * It uses the `@hono/node-server` library to convert between Node.js HTTP and Web Standard APIs.\n *\n * In stateful mode:\n * - Session ID is generated and included in response headers\n * - Session ID is always included in initialization responses\n * - Requests with invalid session IDs are rejected with `404 Not Found`\n * - Non-initialization requests without a session ID are rejected with `400 Bad Request`\n * - State is maintained in-memory (connections, message history)\n *\n * In stateless mode:\n * - No Session ID is included in any responses\n * - No session validation is performed\n *\n * @example Stateful setup\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_stateful\"\n * const server = new McpServer({ name: 'my-server', version: '1.0.0' });\n *\n * const transport = new NodeStreamableHTTPServerTransport({\n * sessionIdGenerator: () => randomUUID()\n * });\n *\n * await server.connect(transport);\n * ```\n *\n * @example Stateless setup\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_stateless\"\n * const transport = new NodeStreamableHTTPServerTransport({\n * sessionIdGenerator: undefined\n * });\n * ```\n *\n * @example Using with a pre-parsed request body (e.g. Express)\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_express\"\n * app.post('/mcp', (req, res) => {\n * transport.handleRequest(req, res, req.body);\n * });\n * ```\n */\nexport class NodeStreamableHTTPServerTransport implements Transport {\n private _webStandardTransport: WebStandardStreamableHTTPServerTransport;\n private _requestListener: ReturnType<typeof getRequestListener>;\n // Store auth and parsedBody per request for passing through to handleRequest\n private _requestContext: WeakMap<Request, { authInfo?: AuthInfo; parsedBody?: unknown }> = new WeakMap();\n\n constructor(options: StreamableHTTPServerTransportOptions = {}) {\n this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);\n\n // Create a request listener that wraps the web standard transport\n // getRequestListener converts Node.js HTTP to Web Standard and properly handles SSE streaming\n // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would\n // break frameworks like Next.js whose response classes extend the native Response\n this._requestListener = getRequestListener(\n async (webRequest: Request) => {\n // Get context if available (set during handleRequest)\n const context = this._requestContext.get(webRequest);\n return this._webStandardTransport.handleRequest(webRequest, {\n authInfo: context?.authInfo,\n parsedBody: context?.parsedBody\n });\n },\n { overrideGlobalObjects: false }\n );\n }\n\n /**\n * Gets the session ID for this transport instance.\n */\n get sessionId(): string | undefined {\n return this._webStandardTransport.sessionId;\n }\n\n /**\n * Sets callback for when the transport is closed.\n */\n set onclose(handler: (() => void) | undefined) {\n this._webStandardTransport.onclose = handler;\n }\n\n get onclose(): (() => void) | undefined {\n return this._webStandardTransport.onclose;\n }\n\n /**\n * Sets callback for transport errors.\n */\n set onerror(handler: ((error: Error) => void) | undefined) {\n this._webStandardTransport.onerror = handler;\n }\n\n get onerror(): ((error: Error) => void) | undefined {\n return this._webStandardTransport.onerror;\n }\n\n /**\n * Sets callback for incoming messages.\n */\n set onmessage(handler: ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined) {\n this._webStandardTransport.onmessage = handler;\n }\n\n get onmessage(): ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined {\n return this._webStandardTransport.onmessage;\n }\n\n /**\n * Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op\n * for the Streamable HTTP transport as connections are managed per-request.\n */\n async start(): Promise<void> {\n return this._webStandardTransport.start();\n }\n\n /**\n * Closes the transport and all active connections.\n */\n async close(): Promise<void> {\n return this._webStandardTransport.close();\n }\n\n /**\n * Sends a JSON-RPC message through the transport.\n */\n async send(message: JSONRPCMessage, options?: { relatedRequestId?: RequestId }): Promise<void> {\n return this._webStandardTransport.send(message, options);\n }\n\n /**\n * Handles an incoming HTTP request, whether `GET` or `POST`.\n *\n * This method converts Node.js HTTP objects to Web Standard Request/Response\n * and delegates to the underlying {@linkcode WebStandardStreamableHTTPServerTransport}.\n *\n * @param req - Node.js `IncomingMessage`, optionally with `auth` property from middleware\n * @param res - Node.js `ServerResponse`\n * @param parsedBody - Optional pre-parsed body from body-parser middleware\n */\n async handleRequest(req: IncomingMessage & { auth?: AuthInfo }, res: ServerResponse, parsedBody?: unknown): Promise<void> {\n // Store context for this request to pass through auth and parsedBody\n // We need to intercept the request creation to attach this context\n const authInfo = req.auth;\n\n // Create a custom handler that includes our context\n // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would\n // break frameworks like Next.js whose response classes extend the native Response\n const handler = getRequestListener(\n async (webRequest: Request) => {\n return this._webStandardTransport.handleRequest(webRequest, {\n authInfo,\n parsedBody\n });\n },\n { overrideGlobalObjects: false }\n );\n\n // Delegate to the request listener which handles all the Node.js <-> Web Standard conversion\n // including proper SSE streaming support\n await handler(req, res);\n }\n\n /**\n * Close an SSE stream for a specific request, triggering client reconnection.\n * Use this to implement polling behavior during long-running operations -\n * client will reconnect after the retry interval specified in the priming event.\n */\n closeSSEStream(requestId: RequestId): void {\n this._webStandardTransport.closeSSEStream(requestId);\n }\n\n /**\n * Close the standalone GET SSE stream, triggering client reconnection.\n * Use this to implement polling behavior for server-initiated notifications.\n */\n closeStandaloneSSEStream(): void {\n this._webStandardTransport.closeStandaloneSSEStream();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,IAAa,oCAAb,MAAoE;CAChE,AAAQ;CACR,AAAQ;CAER,AAAQ,kCAAmF,IAAI,SAAS;CAExG,YAAY,UAAgD,EAAE,EAAE;AAC5D,OAAK,wBAAwB,IAAI,yCAAyC,QAAQ;AAMlF,OAAK,mBAAmB,mBACpB,OAAO,eAAwB;GAE3B,MAAM,UAAU,KAAK,gBAAgB,IAAI,WAAW;AACpD,UAAO,KAAK,sBAAsB,cAAc,YAAY;IACxD,UAAU,SAAS;IACnB,YAAY,SAAS;IACxB,CAAC;KAEN,EAAE,uBAAuB,OAAO,CACnC;;;;;CAML,IAAI,YAAgC;AAChC,SAAO,KAAK,sBAAsB;;;;;CAMtC,IAAI,QAAQ,SAAmC;AAC3C,OAAK,sBAAsB,UAAU;;CAGzC,IAAI,UAAoC;AACpC,SAAO,KAAK,sBAAsB;;;;;CAMtC,IAAI,QAAQ,SAA+C;AACvD,OAAK,sBAAsB,UAAU;;CAGzC,IAAI,UAAgD;AAChD,SAAO,KAAK,sBAAsB;;;;;CAMtC,IAAI,UAAU,SAAoF;AAC9F,OAAK,sBAAsB,YAAY;;CAG3C,IAAI,YAAuF;AACvF,SAAO,KAAK,sBAAsB;;;;;;CAOtC,MAAM,QAAuB;AACzB,SAAO,KAAK,sBAAsB,OAAO;;;;;CAM7C,MAAM,QAAuB;AACzB,SAAO,KAAK,sBAAsB,OAAO;;;;;CAM7C,MAAM,KAAK,SAAyB,SAA2D;AAC3F,SAAO,KAAK,sBAAsB,KAAK,SAAS,QAAQ;;;;;;;;;;;;CAa5D,MAAM,cAAc,KAA4C,KAAqB,YAAqC;EAGtH,MAAM,WAAW,IAAI;AAiBrB,QAZgB,mBACZ,OAAO,eAAwB;AAC3B,UAAO,KAAK,sBAAsB,cAAc,YAAY;IACxD;IACA;IACH,CAAC;KAEN,EAAE,uBAAuB,OAAO,CACnC,CAIa,KAAK,IAAI;;;;;;;CAQ3B,eAAe,WAA4B;AACvC,OAAK,sBAAsB,eAAe,UAAU;;;;;;CAOxD,2BAAiC;AAC7B,OAAK,sBAAsB,0BAA0B"}
{"version":3,"file":"index.mjs","names":["response: Response","headers: Record<string, string>","drainResolve: (() => void) | undefined","body: string | undefined","serialized: string | undefined"],"sources":["../src/middleware/hostHeaderValidation.ts","../src/middleware/originValidation.ts","../src/streamableHttp.ts","../src/toNodeHandler.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { localhostAllowedHostnames, validateHostHeader } from '@modelcontextprotocol/server';\n\n/**\n * Node.js request guard for DNS rebinding protection.\n * Validates the `Host` header hostname (port-agnostic) against an allowed list.\n *\n * Unlike the framework adapters, plain `node:http` has no middleware chain, so\n * the guard returns whether the request may proceed: when it returns `false`\n * it has already answered the request with a `403` JSON-RPC error and the\n * caller must not handle it further.\n *\n * @param allowedHostnames - List of allowed hostnames (without ports).\n * For IPv6, provide the address with brackets (e.g., `[::1]`).\n *\n * @example\n * ```ts\n * const validateHost = hostHeaderValidation(['localhost', '127.0.0.1', '[::1]']);\n * http.createServer((req, res) => {\n * if (!validateHost(req, res)) return;\n * void transport.handleRequest(req, res);\n * });\n * ```\n */\nexport function hostHeaderValidation(allowedHostnames: string[]): (req: IncomingMessage, res: ServerResponse) => boolean {\n return (req, res) => {\n const result = validateHostHeader(req.headers.host, allowedHostnames);\n if (result.ok) {\n return true;\n }\n res.writeHead(403, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32_000,\n message: result.message\n },\n id: null\n })\n );\n return false;\n };\n}\n\n/**\n * Convenience guard for localhost DNS rebinding protection.\n * Allows only `localhost`, `127.0.0.1`, and `[::1]` (IPv6 localhost) hostnames.\n */\nexport function localhostHostValidation(): (req: IncomingMessage, res: ServerResponse) => boolean {\n return hostHeaderValidation(localhostAllowedHostnames());\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { localhostAllowedOrigins, validateOriginHeader } from '@modelcontextprotocol/server';\n\n/**\n * Node.js request guard for Origin header validation.\n * Validates the `Origin` header hostname (port-agnostic) against an allowed list.\n *\n * Requests without an `Origin` header pass (non-browser MCP clients do not send\n * one); a present value that is not allowed, or that cannot be parsed, is\n * rejected with `403`. The guard returns whether the request may proceed: when\n * it returns `false` it has already answered the request and the caller must\n * not handle it further.\n *\n * @param allowedOriginHostnames - List of allowed origin hostnames (without scheme or port).\n * For IPv6, provide the address with brackets (e.g., `[::1]`).\n *\n * @example\n * ```ts\n * const validateOrigin = originValidation(['localhost', '127.0.0.1', '[::1]']);\n * http.createServer((req, res) => {\n * if (!validateOrigin(req, res)) return;\n * void transport.handleRequest(req, res);\n * });\n * ```\n */\nexport function originValidation(allowedOriginHostnames: string[]): (req: IncomingMessage, res: ServerResponse) => boolean {\n return (req, res) => {\n const result = validateOriginHeader(req.headers.origin, allowedOriginHostnames);\n if (result.ok) {\n return true;\n }\n res.writeHead(403, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n jsonrpc: '2.0',\n error: {\n code: -32_000,\n message: result.message\n },\n id: null\n })\n );\n return false;\n };\n}\n\n/**\n * Convenience guard for localhost Origin validation.\n * Allows only origins whose hostname is `localhost`, `127.0.0.1`, or `[::1]` (IPv6 localhost).\n */\nexport function localhostOriginValidation(): (req: IncomingMessage, res: ServerResponse) => boolean {\n return originValidation(localhostAllowedOrigins());\n}\n","/**\n * Node.js Streamable HTTP Server Transport\n *\n * This is a thin wrapper around {@linkcode WebStandardStreamableHTTPServerTransport} that provides\n * compatibility with Node.js HTTP server (`IncomingMessage`/`ServerResponse`).\n *\n * For web-standard environments (Cloudflare Workers, Deno, Bun), use {@linkcode WebStandardStreamableHTTPServerTransport} directly.\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { getRequestListener } from '@hono/node-server';\nimport type {\n AuthInfo,\n JSONRPCMessage,\n MessageExtraInfo,\n RequestId,\n Transport,\n WebStandardStreamableHTTPServerTransportOptions\n} from '@modelcontextprotocol/server';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server';\n\n/**\n * Configuration options for {@linkcode NodeStreamableHTTPServerTransport}\n *\n * This is an alias for {@linkcode WebStandardStreamableHTTPServerTransportOptions} for backward compatibility.\n */\nexport type StreamableHTTPServerTransportOptions = WebStandardStreamableHTTPServerTransportOptions;\n\n/**\n * Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.\n * It supports both SSE streaming and direct HTTP responses.\n *\n * This is a wrapper around {@linkcode WebStandardStreamableHTTPServerTransport} that provides Node.js HTTP compatibility.\n * It uses the `@hono/node-server` library to convert between Node.js HTTP and Web Standard APIs.\n *\n * In stateful mode:\n * - Session ID is generated and included in response headers\n * - Session ID is always included in initialization responses\n * - Requests with invalid session IDs are rejected with `404 Not Found`\n * - Non-initialization requests without a session ID are rejected with `400 Bad Request`\n * - State is maintained in-memory (connections, message history)\n *\n * In stateless mode:\n * - No Session ID is included in any responses\n * - No session validation is performed\n *\n * @example Stateful setup\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_stateful\"\n * const server = new McpServer({ name: 'my-server', version: '1.0.0' });\n *\n * const transport = new NodeStreamableHTTPServerTransport({\n * sessionIdGenerator: () => randomUUID()\n * });\n *\n * await server.connect(transport);\n * ```\n *\n * @example Stateless setup\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_stateless\"\n * const transport = new NodeStreamableHTTPServerTransport({\n * sessionIdGenerator: undefined\n * });\n * ```\n *\n * @example Using with a pre-parsed request body (e.g. Express)\n * ```ts source=\"./streamableHttp.examples.ts#NodeStreamableHTTPServerTransport_express\"\n * app.post('/mcp', (req, res) => {\n * transport.handleRequest(req, res, req.body);\n * });\n * ```\n */\nexport class NodeStreamableHTTPServerTransport implements Transport {\n private _webStandardTransport: WebStandardStreamableHTTPServerTransport;\n private _requestListener: ReturnType<typeof getRequestListener>;\n // Store auth and parsedBody per request for passing through to handleRequest\n private _requestContext: WeakMap<Request, { authInfo?: AuthInfo; parsedBody?: unknown }> = new WeakMap();\n\n constructor(options: StreamableHTTPServerTransportOptions = {}) {\n this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);\n\n // Create a request listener that wraps the web standard transport\n // getRequestListener converts Node.js HTTP to Web Standard and properly handles SSE streaming\n // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would\n // break frameworks like Next.js whose response classes extend the native Response\n this._requestListener = getRequestListener(\n async (webRequest: Request) => {\n // Get context if available (set during handleRequest)\n const context = this._requestContext.get(webRequest);\n return this._webStandardTransport.handleRequest(webRequest, {\n authInfo: context?.authInfo,\n parsedBody: context?.parsedBody\n });\n },\n { overrideGlobalObjects: false }\n );\n }\n\n /**\n * Gets the session ID for this transport instance.\n */\n get sessionId(): string | undefined {\n return this._webStandardTransport.sessionId;\n }\n\n /**\n * Sets callback for when the transport is closed.\n */\n set onclose(handler: (() => void) | undefined) {\n this._webStandardTransport.onclose = handler;\n }\n\n get onclose(): (() => void) | undefined {\n return this._webStandardTransport.onclose;\n }\n\n /**\n * Sets callback for transport errors.\n */\n set onerror(handler: ((error: Error) => void) | undefined) {\n this._webStandardTransport.onerror = handler;\n }\n\n get onerror(): ((error: Error) => void) | undefined {\n return this._webStandardTransport.onerror;\n }\n\n /**\n * Sets callback for incoming messages.\n */\n set onmessage(handler: ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined) {\n this._webStandardTransport.onmessage = handler;\n }\n\n get onmessage(): ((message: JSONRPCMessage, extra?: MessageExtraInfo) => void) | undefined {\n return this._webStandardTransport.onmessage;\n }\n\n /**\n * Starts the transport. This is required by the {@linkcode Transport} interface but is a no-op\n * for the Streamable HTTP transport as connections are managed per-request.\n */\n async start(): Promise<void> {\n return this._webStandardTransport.start();\n }\n\n /**\n * Closes the transport and all active connections.\n */\n async close(): Promise<void> {\n return this._webStandardTransport.close();\n }\n\n /**\n * Sends a JSON-RPC message through the transport.\n */\n async send(message: JSONRPCMessage, options?: { relatedRequestId?: RequestId }): Promise<void> {\n return this._webStandardTransport.send(message, options);\n }\n\n /**\n * Forwards the supported protocol versions to the wrapped Web Standard\n * transport for `MCP-Protocol-Version` header validation. Called by the\n * protocol layer during connect; without this delegation a server's\n * `supportedProtocolVersions` option never reached the Node adapter's\n * header validation.\n */\n setSupportedProtocolVersions(versions: string[]): void {\n this._webStandardTransport.setSupportedProtocolVersions(versions);\n }\n\n /**\n * Handles an incoming HTTP request, whether `GET` or `POST`.\n *\n * This method converts Node.js HTTP objects to Web Standard Request/Response\n * and delegates to the underlying {@linkcode WebStandardStreamableHTTPServerTransport}.\n *\n * @param req - Node.js `IncomingMessage`, optionally with `auth` property from middleware\n * @param res - Node.js `ServerResponse`\n * @param parsedBody - Optional pre-parsed body from body-parser middleware\n */\n async handleRequest(req: IncomingMessage & { auth?: AuthInfo }, res: ServerResponse, parsedBody?: unknown): Promise<void> {\n // Store context for this request to pass through auth and parsedBody\n // We need to intercept the request creation to attach this context\n const authInfo = req.auth;\n\n // Create a custom handler that includes our context\n // overrideGlobalObjects: false prevents Hono from overwriting global Response, which would\n // break frameworks like Next.js whose response classes extend the native Response\n const handler = getRequestListener(\n async (webRequest: Request) => {\n return this._webStandardTransport.handleRequest(webRequest, {\n authInfo,\n parsedBody\n });\n },\n { overrideGlobalObjects: false }\n );\n\n // Delegate to the request listener which handles all the Node.js <-> Web Standard conversion\n // including proper SSE streaming support\n await handler(req, res);\n }\n\n /**\n * Close an SSE stream for a specific request, triggering client reconnection.\n * Use this to implement polling behavior during long-running operations -\n * client will reconnect after the retry interval specified in the priming event.\n */\n closeSSEStream(requestId: RequestId): void {\n this._webStandardTransport.closeSSEStream(requestId);\n }\n\n /**\n * Close the standalone GET SSE stream, triggering client reconnection.\n * Use this to implement polling behavior for server-initiated notifications.\n */\n closeStandaloneSSEStream(): void {\n this._webStandardTransport.closeStandaloneSSEStream();\n }\n}\n","/**\n * `toNodeHandler` — adapt the web-standard {@linkcode McpHttpHandler} returned\n * by `createMcpHandler` to a Node.js `(req, res, parsedBody?)` request handler.\n *\n * The handler itself is web-standards-only (`{ fetch, close, notify, bus }` — the\n * shape Workers/Bun/Deno expect from `export default`). Node frameworks\n * (Express, Fastify, plain `node:http`) wrap it once with this helper:\n *\n * ```ts\n * import { createMcpHandler } from '@modelcontextprotocol/server';\n * import { toNodeHandler } from '@modelcontextprotocol/node';\n *\n * const handler = createMcpHandler(factory);\n * app.all('/mcp', toNodeHandler(handler));\n * // or, when a body parser already consumed the stream:\n * const node = toNodeHandler(handler);\n * app.all('/mcp', (req, res) => void node(req, res, req.body));\n * ```\n *\n * The Node→web `Request` conversion the adapter performs is also exported on\n * its own as {@linkcode toWebRequest}, for hand-wired compositions (for\n * example, routing on `isLegacyRequest`).\n *\n * The Node request/response shapes are duck-typed (kept structural so this\n * module stays free of `node:` imports); the conversion reads `req.auth`\n * (validated authentication info attached by upstream middleware) and forwards\n * it as the handler's pass-through `authInfo`.\n */\nimport type { AuthInfo, McpHandlerRequestOptions } from '@modelcontextprotocol/server';\n\n/**\n * Minimal duck-typed shape of a Node.js `IncomingMessage` accepted by\n * {@linkcode toNodeHandler}. Kept structural so the adapter stays free of\n * `node:` imports.\n */\nexport interface NodeIncomingMessageLike extends AsyncIterable<unknown> {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n /** Validated authentication info attached by upstream middleware (pass-through). */\n auth?: AuthInfo;\n}\n\n/** Minimal duck-typed shape of a Node.js `ServerResponse` accepted by {@linkcode toNodeHandler}. */\nexport interface NodeServerResponseLike {\n writeHead(statusCode: number, headers?: Record<string, string>): unknown;\n write(chunk: string | Uint8Array): unknown;\n end(chunk?: string | Uint8Array): unknown;\n on(event: string, listener: (...args: unknown[]) => void): unknown;\n destroyed?: boolean;\n}\n\n/**\n * The web-standard fetch face of an `McpHttpHandler` (or any\n * fetch-shaped MCP handler) — the only surface {@linkcode toNodeHandler}\n * touches. Accepting the face structurally keeps the adapter usable with\n * hand-wired compositions that route over `isLegacyRequest` and produce a\n * `Response` directly.\n */\nexport interface FetchLikeMcpHandler {\n fetch: (request: Request, options?: McpHandlerRequestOptions) => Promise<Response>;\n}\n\n/**\n * A Node.js `(req, res, parsedBody?)` request handler produced by\n * {@linkcode toNodeHandler}. The third argument is an optional pre-parsed body\n * (`req.body` from `express.json()`); a function third argument (Express's\n * `next` when the handler is mounted as middleware) is ignored.\n */\nexport type NodeMcpRequestHandler = (req: NodeIncomingMessageLike, res: NodeServerResponseLike, parsedBody?: unknown) => Promise<void>;\n\n/** Options for {@linkcode toNodeHandler}. */\nexport interface ToNodeHandlerOptions {\n /**\n * Called when the adapter answers `500` because request conversion or\n * `handler.fetch` itself threw (e.g. a closed handler). Restores the\n * observability the removed `.node` face had via the entry's own\n * `onerror` — entry-internal failures are still reported through\n * `handler.fetch` and surface via the entry's `onerror` option as before.\n */\n onerror?: (error: Error) => void;\n}\n\n/**\n * Adapts a web-standard MCP handler (`handler.fetch`) to a Node.js\n * `(req, res, parsedBody?)` request handler. The returned function converts the\n * Node request to a web-standard `Request`, calls `handler.fetch`, then writes\n * the `Response` back to `res` (honoring write backpressure for streamed SSE\n * responses).\n *\n * `req.auth` is forwarded as the handler's pass-through `authInfo`. A function\n * third argument (Express's `next`) is ignored, never treated as a body.\n *\n * Pass `{ onerror }` to observe the adapter-level error fallback (request\n * conversion / `handler.fetch` throw) before the `500` response is written.\n */\nexport function toNodeHandler(handler: FetchLikeMcpHandler, opts?: ToNodeHandlerOptions): NodeMcpRequestHandler {\n return async (req, res, parsedBody) => {\n // Express passes (req, res, next) when the handler is mounted as a\n // middleware function; a function third argument is `next`, not a body.\n if (typeof parsedBody === 'function') {\n parsedBody = undefined;\n }\n\n let finished = false;\n const abort = new AbortController();\n res.on('close', () => {\n if (!finished) {\n abort.abort();\n }\n });\n if (res.destroyed === true) {\n abort.abort();\n }\n\n let response: Response;\n try {\n const request = await toWebRequest(req, parsedBody, { signal: abort.signal });\n response = await handler.fetch(request, {\n ...(req.auth !== undefined && { authInfo: req.auth }),\n ...(parsedBody !== undefined && { parsedBody })\n });\n } catch (error) {\n try {\n opts?.onerror?.(error instanceof Error ? error : new Error(String(error)));\n } catch {\n // Reporting must never alter the response.\n }\n response = internalServerErrorResponse(echoableRequestId(parsedBody));\n }\n\n const headers: Record<string, string> = {};\n for (const [name, value] of response.headers) {\n headers[name] = value;\n }\n res.writeHead(response.status, headers);\n if (response.body === null) {\n finished = true;\n res.end();\n return;\n }\n // Honor write backpressure: when write() reports a full buffer (Node's\n // `false` return), wait for the response to drain before pulling the\n // next chunk. The abort signal (wired to 'close' above, and seeded\n // from `res.destroyed` at entry to cover the pre-registration window\n // when 'close' already fired during async middleware) is the single\n // termination source — racing it against the drain wait means a\n // vanished client cannot park the loop, and breaking out of the async\n // iterator calls return() to cancel the upstream stream.\n let drainResolve: (() => void) | undefined;\n const releaseDrainWait = () => {\n drainResolve?.();\n drainResolve = undefined;\n };\n res.on('drain', releaseDrainWait);\n const closed = new Promise<void>(resolve => {\n abort.signal.addEventListener('abort', () => resolve(), { once: true });\n });\n try {\n for await (const chunk of response.body) {\n if (abort.signal.aborted) {\n break;\n }\n if (res.write(chunk) === false) {\n await Promise.race([\n new Promise<void>(resolve => {\n drainResolve = resolve;\n }),\n closed\n ]);\n }\n }\n } catch {\n // Stream aborted upstream; the abort signal already cancelled the exchange.\n }\n finished = true;\n res.end();\n };\n}\n\n/* ------------------------------------------------------------------------ *\n * Node request conversion — `toWebRequest` (duck-typed; no node: imports)\n * ------------------------------------------------------------------------ */\n\nfunction singleHeaderValue(value: string | string[] | undefined): string | undefined {\n return Array.isArray(value) ? value[0] : value;\n}\n\n/** Options for {@linkcode toWebRequest}. */\nexport interface ToWebRequestOptions {\n /** An `AbortSignal` to attach to the constructed `Request` (`request.signal`). */\n signal?: AbortSignal;\n}\n\n/**\n * Convert a Node.js `IncomingMessage` (duck-typed — an Express `req` works) to\n * the web-standard `Request` that `handler.fetch()` and `isLegacyRequest()`\n * take. This is the conversion {@linkcode toNodeHandler} performs internally,\n * exported for hand-wired compositions:\n *\n * ```ts\n * const probe = await toWebRequest(req, req.body);\n * await ((await isLegacyRequest(probe)) ? legacy(req, res) : modern(req, res, req.body));\n * ```\n *\n * With no `parsedBody` the Node stream is read to completion — read the body\n * from the returned `Request` afterwards, not from `req`. When a body parser\n * already consumed the stream (`express.json()`), pass the parsed value as\n * `parsedBody` and nothing is read from `req`.\n */\nexport async function toWebRequest(req: NodeIncomingMessageLike, parsedBody?: unknown, options?: ToWebRequestOptions): Promise<Request> {\n const method = (req.method ?? 'GET').toUpperCase();\n // HTTP/2 requests carry their authority as the `:authority` pseudo-header,\n // usually with no `host` entry at all (mirrors Node's `request.authority`).\n const host = singleHeaderValue(req.headers['host']) ?? singleHeaderValue(req.headers[':authority']) ?? 'localhost';\n const url = `http://${host}${req.url ?? '/'}`;\n\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n // HTTP/2 pseudo-headers (`:method`, `:path`, `:authority`, …) are\n // connection metadata, not header fields — `Headers` rejects their\n // names, so they are skipped rather than copied.\n if (value === undefined || name.startsWith(':')) {\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n\n // The body is carried as text: MCP request bodies are JSON, and a string\n // body keeps the constructed Request portable across runtime lib versions.\n let body: string | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n if (parsedBody === undefined) {\n const decoder = new TextDecoder();\n let collected = '';\n for await (const chunk of req) {\n collected += typeof chunk === 'string' ? chunk : decoder.decode(chunk as Uint8Array, { stream: true });\n }\n collected += decoder.decode();\n if (collected.length > 0) {\n body = collected;\n }\n } else {\n // The caller already consumed and parsed the Node stream (the\n // documented `(req, res, req.body)` mounting behind\n // `express.json()`), so the bytes cannot be re-read. Re-serialize\n // the parsed value so consumers of the forwarded Request — anything\n // on the legacy leg reading `request.json()`/`text()` instead of\n // the pass-through parsedBody — still receive the body, and replace\n // the entity headers that described the original raw bytes.\n const serialized: string | undefined = JSON.stringify(parsedBody);\n headers.delete('content-encoding');\n headers.delete('transfer-encoding');\n if (serialized === undefined) {\n headers.delete('content-length');\n } else {\n body = serialized;\n headers.set('content-length', String(new TextEncoder().encode(serialized).byteLength));\n }\n }\n }\n\n return new Request(url, {\n method,\n headers,\n ...(options?.signal !== undefined && { signal: options.signal }),\n ...(body !== undefined && { body })\n });\n}\n\n/* ------------------------------------------------------------------------ *\n * Adapter-level error fallback (request conversion failure / closed handler)\n * ------------------------------------------------------------------------ */\n\n/**\n * The JSON-RPC id to echo on an adapter-built error response: the body's `id`\n * when the body is a single JSON-RPC request whose id is a string or number,\n * `null` otherwise.\n */\nfunction echoableRequestId(body: unknown): string | number | null {\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return null;\n }\n const { method, id } = body as { method?: unknown; id?: unknown };\n if (typeof method !== 'string') {\n return null;\n }\n return typeof id === 'string' || typeof id === 'number' ? id : null;\n}\n\nfunction internalServerErrorResponse(id: string | number | null): Response {\n return Response.json({ jsonrpc: '2.0', error: { code: -32_603, message: 'Internal server error' }, id }, { status: 500 });\n}\n"],"mappings":"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"}
{
"name": "@modelcontextprotocol/node",
"version": "2.0.0-alpha.3",
"version": "2.0.0-alpha.4",
"description": "Model Context Protocol implementation for TypeScript - Node.js middleware",

@@ -30,9 +30,2 @@ "license": "MIT",

"types": "./dist/index.d.mts",
"typesVersions": {
"*": {
"sse": [
"dist/sse.d.mts"
]
}
},
"files": [

@@ -46,3 +39,3 @@ "dist"

"hono": "^4.11.4",
"@modelcontextprotocol/server": "^2.0.0-alpha.3"
"@modelcontextprotocol/server": "^2.0.0-alpha.4"
},

@@ -65,4 +58,4 @@ "peerDependenciesMeta": {

"vitest": "^4.0.15",
"@modelcontextprotocol/server": "^2.0.0-alpha.3",
"@modelcontextprotocol/core-internal": "^2.0.0-alpha.2",
"@modelcontextprotocol/server": "^2.0.0-alpha.4",
"@modelcontextprotocol/core-internal": "^2.0.0-alpha.3",
"@modelcontextprotocol/eslint-config": "^2.0.0",

@@ -69,0 +62,0 @@ "@modelcontextprotocol/test-helpers": "^2.0.0-alpha.0",

@@ -19,2 +19,7 @@ # `@modelcontextprotocol/node`

- `StreamableHTTPServerTransportOptions` (type alias for `WebStandardStreamableHTTPServerTransportOptions`)
- `toNodeHandler(handler, opts?)` — adapt a web-standard `{ fetch }` MCP handler to a Node `(req, res, parsedBody?)` handler
- `ToNodeHandlerOptions`, `FetchLikeMcpHandler`, `NodeMcpRequestHandler` (types for `toNodeHandler`)
- `toWebRequest(req, parsedBody?, opts?)` — the Node `IncomingMessage` → web-standard `Request` conversion `toNodeHandler` performs internally, exported on its own (for example to feed `isLegacyRequest()` from a hand-wired `(req, res)` handler)
- `ToWebRequestOptions` (options type for `toWebRequest`)
- `NodeIncomingMessageLike`, `NodeServerResponseLike` (structural Node request/response shapes)

@@ -21,0 +26,0 @@ ## Usage