mcp-proxy 5.4.0 → 5.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,7 @@ options:
33
33
  - `--endpoint`: If `server` is set to `sse` or `stream`, this option sets the endpoint path (default: `/sse` or `/mcp`)
34
34
  - `--sseEndpoint`: Set the SSE endpoint path (default: `/sse`). Overrides `--endpoint` if `server` is set to `sse`.
35
35
  - `--streamEndpoint`: Set the streamable HTTP endpoint path (default: `/mcp`). Overrides `--endpoint` if `server` is set to `stream`.
36
+ - `--stateless`: Enable stateless mode for HTTP streamable transport (no session management). In this mode, each request creates a new server instance instead of maintaining persistent sessions.
36
37
  - `--port`: Specify the port to listen on (default: 8080)
37
38
  - `--debug`: Enable debug logging
38
39
  - `--shell`: Spawn the server via the user's shell
@@ -51,6 +52,37 @@ npx mcp-proxy --port 8080 my-command -v
51
52
  npx mcp-proxy --port 8080 -- my-command -v
52
53
  ```
53
54
 
55
+ ### Stateless Mode
56
+
57
+ By default, MCP Proxy maintains persistent sessions for HTTP streamable transport, where each client connection is associated with a server instance that stays alive for the duration of the session.
58
+
59
+ Stateless mode (`--stateless`) changes this behavior:
60
+
61
+ - **No session management**: Each request creates a new server instance instead of maintaining persistent sessions
62
+ - **Simplified deployment**: Useful for serverless environments or when you want to minimize memory usage
63
+ - **Request isolation**: Each request is completely independent, which can be beneficial for certain use cases
64
+
65
+ Example usage:
66
+
67
+ ```bash
68
+ # Enable stateless mode
69
+ npx mcp-proxy --port 8080 --stateless tsx server.js
70
+
71
+ # Stateless mode with stream-only transport
72
+ npx mcp-proxy --port 8080 --stateless --server stream tsx server.js
73
+ ```
74
+
75
+ > [!NOTE]
76
+ > Stateless mode only affects HTTP streamable transport (`/mcp` endpoint). SSE transport behavior remains unchanged.
77
+
78
+ **When to use stateless mode:**
79
+
80
+ - **Serverless environments**: When deploying to platforms like AWS Lambda, Vercel, or similar
81
+ - **Load balancing**: When requests need to be distributed across multiple instances
82
+ - **Memory optimization**: When you want to minimize server memory usage
83
+ - **Request isolation**: When you need complete independence between requests
84
+ - **Simple deployments**: When you don't need to maintain connection state
85
+
54
86
  ### Node.js SDK
55
87
 
56
88
  The Node.js SDK provides several utilities that are used to create a proxy.
@@ -90,11 +122,25 @@ const { close } = await startHTTPServer({
90
122
  },
91
123
  eventStore: new InMemoryEventStore(),
92
124
  port: 8080,
125
+ stateless: false, // Optional: enable stateless mode for streamable HTTP transport
93
126
  });
94
127
 
95
128
  close();
96
129
  ```
97
130
 
131
+ Options:
132
+
133
+ - `createServer`: Function that creates a new server instance for each connection
134
+ - `eventStore`: Event store for streamable HTTP transport (optional)
135
+ - `port`: Port number to listen on
136
+ - `host`: Host to bind to (default: "::")
137
+ - `sseEndpoint`: SSE endpoint path (default: "/sse", set to null to disable)
138
+ - `streamEndpoint`: Streamable HTTP endpoint path (default: "/mcp", set to null to disable)
139
+ - `stateless`: Enable stateless mode for HTTP streamable transport (default: false)
140
+ - `onConnect`: Callback when a server connects (optional)
141
+ - `onClose`: Callback when a server disconnects (optional)
142
+ - `onUnhandledRequest`: Callback for unhandled HTTP requests (optional)
143
+
98
144
  #### `startStdioServer`
99
145
 
100
146
  Starts a proxy that listens on a `stdio`, and sends messages to the attached `sse` or `streamable` server.
@@ -3,7 +3,7 @@ import {
3
3
  InMemoryEventStore,
4
4
  proxyServer,
5
5
  startHTTPServer
6
- } from "../chunk-UHPMWS2Y.js";
6
+ } from "../chunk-UFGZ6L2I.js";
7
7
 
8
8
  // src/bin/mcp-proxy.ts
9
9
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -246,6 +246,11 @@ var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$
246
246
  describe: "The SSE endpoint to listen on",
247
247
  type: "string"
248
248
  },
249
+ stateless: {
250
+ default: false,
251
+ describe: "Enable stateless mode for HTTP streamable transport (no session management)",
252
+ type: "boolean"
253
+ },
249
254
  streamEndpoint: {
250
255
  default: "/mcp",
251
256
  describe: "The stream endpoint to listen on",
@@ -303,6 +308,7 @@ var proxy = async () => {
303
308
  host: argv.host,
304
309
  port: argv.port,
305
310
  sseEndpoint: argv.server && argv.server !== "sse" ? null : argv.sseEndpoint ?? argv.endpoint,
311
+ stateless: argv.stateless,
306
312
  streamEndpoint: argv.server && argv.server !== "stream" ? null : argv.streamEndpoint ?? argv.endpoint
307
313
  });
308
314
  return {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bin/mcp-proxy.ts","../../src/StdioClientTransport.ts","../../src/JSONFilterTransform.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { EventSource } from \"eventsource\";\nimport { setTimeout } from \"node:timers\";\nimport util from \"node:util\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nimport { InMemoryEventStore } from \"../InMemoryEventStore.js\";\nimport { proxyServer } from \"../proxyServer.js\";\nimport { SSEServer, startHTTPServer } from \"../startHTTPServer.js\";\nimport { StdioClientTransport } from \"../StdioClientTransport.js\";\n\nutil.inspect.defaultOptions.depth = 8;\n\nif (!(\"EventSource\" in global)) {\n // @ts-expect-error - figure out how to use --experimental-eventsource with vitest\n global.EventSource = EventSource;\n}\n\nconst argv = await yargs(hideBin(process.argv))\n .scriptName(\"mcp-proxy\")\n .command(\"$0 <command> [args...]\", \"Run a command with MCP arguments\")\n .positional(\"command\", {\n demandOption: true,\n describe: \"The command to run\",\n type: \"string\",\n })\n .positional(\"args\", {\n array: true,\n describe: \"The arguments to pass to the command\",\n type: \"string\",\n })\n .env(\"MCP_PROXY\")\n .parserConfiguration({\n \"populate--\": true,\n })\n .options({\n debug: {\n default: false,\n describe: \"Enable debug logging\",\n type: \"boolean\",\n },\n endpoint: {\n describe: \"The endpoint to listen on\",\n type: \"string\",\n },\n gracefulShutdownTimeout: {\n default: 5000,\n describe: \"The timeout (in milliseconds) for graceful shutdown\",\n type: \"number\",\n },\n host: {\n default: \"::\",\n describe: \"The host to listen on\",\n type: \"string\",\n },\n port: {\n default: 8080,\n describe: \"The port to listen on\",\n type: \"number\",\n },\n server: {\n choices: [\"sse\", \"stream\"],\n describe:\n \"The server type to use (sse or stream). By default, both are enabled\",\n type: \"string\",\n },\n shell: {\n default: false,\n describe: \"Spawn the server via the user's shell\",\n type: \"boolean\",\n },\n sseEndpoint: {\n default: \"/sse\",\n describe: \"The SSE endpoint to listen on\",\n type: \"string\",\n },\n streamEndpoint: {\n default: \"/mcp\",\n describe: \"The stream endpoint to listen on\",\n type: \"string\",\n },\n })\n .help()\n .parseAsync();\n\n// Determine the final command and args\nif (!argv.command) {\n throw new Error(\"No command specified\");\n}\n\nconst finalCommand = argv.command;\n// If -- separator was used, args after -- are in argv[\"--\"], otherwise use parsed args\nconst finalArgs = (argv[\"--\"] as string[]) || argv.args;\n\nconst connect = async (client: Client) => {\n const transport = new StdioClientTransport({\n args: finalArgs,\n command: finalCommand,\n env: process.env as Record<string, string>,\n onEvent: (event) => {\n if (argv.debug) {\n console.debug(\"transport event\", event);\n }\n },\n shell: argv.shell,\n stderr: \"pipe\",\n });\n\n await client.connect(transport);\n};\n\nconst proxy = async () => {\n const client = new Client(\n {\n name: \"mcp-proxy\",\n version: \"1.0.0\",\n },\n {\n capabilities: {},\n },\n );\n\n await connect(client);\n\n const serverVersion = client.getServerVersion() as {\n name: string;\n version: string;\n };\n\n const serverCapabilities = client.getServerCapabilities() as {\n capabilities: Record<string, unknown>;\n };\n\n console.info(\"starting server on port %d\", argv.port);\n\n const createServer = async () => {\n const server = new Server(serverVersion, {\n capabilities: serverCapabilities,\n });\n\n proxyServer({\n client,\n server,\n serverCapabilities,\n });\n\n return server;\n };\n\n const server = await startHTTPServer({\n createServer,\n eventStore: new InMemoryEventStore(),\n host: argv.host,\n port: argv.port,\n sseEndpoint:\n argv.server && argv.server !== \"sse\"\n ? null\n : (argv.sseEndpoint ?? argv.endpoint),\n streamEndpoint:\n argv.server && argv.server !== \"stream\"\n ? null\n : (argv.streamEndpoint ?? argv.endpoint),\n });\n\n return {\n close: () => {\n return server.close();\n },\n };\n};\n\nconst createGracefulShutdown = ({\n server,\n timeout,\n}: {\n server: SSEServer;\n timeout: number;\n}) => {\n const gracefulShutdown = () => {\n console.info(\"received shutdown signal; shutting down\");\n\n server.close();\n\n setTimeout(() => {\n // Exit with non-zero code to indicate failure to shutdown gracefully\n process.exit(1);\n }, timeout).unref();\n };\n\n process.on(\"SIGTERM\", gracefulShutdown);\n process.on(\"SIGINT\", gracefulShutdown);\n\n return () => {\n server.close();\n };\n};\n\nconst main = async () => {\n try {\n const server = await proxy();\n\n createGracefulShutdown({\n server,\n timeout: argv.gracefulShutdownTimeout,\n });\n } catch (error) {\n console.error(\"could not start the proxy\", error);\n\n setTimeout(() => {\n process.exit(1);\n }, 1000);\n }\n};\n\nawait main();\n","/**\n * Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/66e1508162d37c0b83b0637ebcd7f07946e3d210/src/client/stdio.ts#L90\n */\n\nimport {\n ReadBuffer,\n serializeMessage,\n} from \"@modelcontextprotocol/sdk/shared/stdio.js\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChildProcess, IOType, spawn } from \"node:child_process\";\nimport { Stream } from \"node:stream\";\n\nimport { JSONFilterTransform } from \"./JSONFilterTransform.js\";\n\nexport type StdioServerParameters = {\n /**\n * Command line arguments to pass to the executable.\n */\n args?: string[];\n\n /**\n * The executable to run to start the server.\n */\n command: string;\n\n /**\n * The working directory to use when spawning the process.\n *\n * If not specified, the current working directory will be inherited.\n */\n cwd?: string;\n\n /**\n * The environment to use when spawning the process.\n *\n * If not specified, the result of getDefaultEnvironment() will be used.\n */\n env: Record<string, string>;\n\n /**\n * A function to call when an event occurs.\n */\n onEvent?: (event: TransportEvent) => void;\n\n /**\n * When true, spawn the child process using the user's shell.\n */\n shell?: boolean;\n\n /**\n * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.\n *\n * The default is \"inherit\", meaning messages to stderr will be printed to the parent process's stderr.\n */\n stderr?: IOType | number | Stream;\n};\n\ntype TransportEvent =\n | {\n chunk: string;\n type: \"data\";\n }\n | {\n error: Error;\n type: \"error\";\n }\n | {\n message: JSONRPCMessage;\n type: \"message\";\n }\n | {\n type: \"close\";\n };\n\n/**\n * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.\n *\n * This transport is only available in Node.js environments.\n */\nexport class StdioClientTransport implements Transport {\n onclose?: () => void;\n\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n /**\n * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to \"pipe\" or \"overlapped\".\n *\n * This is only available after the process has been started.\n */\n get stderr(): null | Stream {\n return this.process?.stderr ?? null;\n }\n private abortController: AbortController = new AbortController();\n\n private onEvent?: (event: TransportEvent) => void;\n private process?: ChildProcess;\n private readBuffer: ReadBuffer = new ReadBuffer();\n\n private serverParams: StdioServerParameters;\n\n constructor(server: StdioServerParameters) {\n this.serverParams = server;\n this.onEvent = server.onEvent;\n }\n\n async close(): Promise<void> {\n this.onEvent?.({\n type: \"close\",\n });\n\n this.abortController.abort();\n this.process = undefined;\n this.readBuffer.clear();\n }\n\n send(message: JSONRPCMessage): Promise<void> {\n return new Promise((resolve) => {\n if (!this.process?.stdin) {\n throw new Error(\"Not connected\");\n }\n\n const json = serializeMessage(message);\n if (this.process.stdin.write(json)) {\n resolve();\n } else {\n this.process.stdin.once(\"drain\", resolve);\n }\n });\n }\n\n /**\n * Starts the server process and prepares to communicate with it.\n */\n async start(): Promise<void> {\n if (this.process) {\n throw new Error(\n \"StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.\",\n );\n }\n\n return new Promise((resolve, reject) => {\n this.process = spawn(\n this.serverParams.command,\n this.serverParams.args ?? [],\n {\n cwd: this.serverParams.cwd,\n env: this.serverParams.env,\n shell: this.serverParams.shell ?? false,\n signal: this.abortController.signal,\n stdio: [\"pipe\", \"pipe\", this.serverParams.stderr ?? \"inherit\"],\n },\n );\n\n this.process.on(\"error\", (error) => {\n if (error.name === \"AbortError\") {\n this.onclose?.();\n return;\n }\n\n reject(error);\n this.onerror?.(error);\n });\n\n this.process.on(\"spawn\", () => {\n resolve();\n });\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n this.process.on(\"close\", (_code) => {\n this.onEvent?.({\n type: \"close\",\n });\n\n this.process = undefined;\n this.onclose?.();\n });\n\n this.process.stdin?.on(\"error\", (error) => {\n this.onEvent?.({\n error,\n type: \"error\",\n });\n\n this.onerror?.(error);\n });\n\n const jsonFilterTransform = new JSONFilterTransform();\n\n this.process.stdout?.pipe(jsonFilterTransform);\n\n jsonFilterTransform.on(\"data\", (chunk) => {\n this.onEvent?.({\n chunk: chunk.toString(),\n type: \"data\",\n });\n\n this.readBuffer.append(chunk);\n this.processReadBuffer();\n });\n\n jsonFilterTransform.on(\"error\", (error) => {\n this.onEvent?.({\n error,\n type: \"error\",\n });\n\n this.onerror?.(error);\n });\n });\n }\n\n private processReadBuffer() {\n while (true) {\n try {\n const message = this.readBuffer.readMessage();\n\n if (message === null) {\n break;\n }\n\n this.onEvent?.({\n message,\n type: \"message\",\n });\n\n this.onmessage?.(message);\n } catch (error) {\n this.onEvent?.({\n error: error as Error,\n type: \"error\",\n });\n\n this.onerror?.(error as Error);\n }\n }\n }\n}\n","import { Transform } from \"node:stream\";\n\n/**\n * Filters out lines that do not start with '{' from the input stream.\n * We use this to drop anything that is obviously not a JSON-RPC message.\n */\nexport class JSONFilterTransform extends Transform {\n private buffer = \"\";\n\n constructor() {\n super({ objectMode: false });\n }\n\n _flush(callback: (error: Error | null, chunk: Buffer | null) => void) {\n // Handle any remaining data in buffer\n if (this.buffer.trim().startsWith(\"{\")) {\n callback(null, Buffer.from(this.buffer));\n } else {\n callback(null, null);\n }\n }\n\n _transform(\n chunk: Buffer,\n _encoding: string,\n callback: (error: Error | null, chunk: Buffer | null) => void,\n ) {\n this.buffer += chunk.toString();\n const lines = this.buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n this.buffer = lines.pop() || \"\";\n\n // Filter lines that start with '{'\n const jsonLines = [];\n const nonJsonLines = [];\n\n for (const line of lines) {\n if (line.trim().startsWith(\"{\")) {\n jsonLines.push(line);\n } else {\n nonJsonLines.push(line);\n }\n }\n\n if (nonJsonLines.length > 0) {\n console.warn(\"[mcp-proxy] ignoring non-JSON output\", nonJsonLines);\n }\n\n if (jsonLines.length > 0) {\n // Send filtered lines with newlines\n const output = jsonLines.join(\"\\n\") + \"\\n\";\n\n callback(null, Buffer.from(output));\n } else {\n callback(null, null);\n }\n }\n}\n"],"mappings":"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","names":["server"]}
1
+ {"version":3,"sources":["../../src/bin/mcp-proxy.ts","../../src/StdioClientTransport.ts","../../src/JSONFilterTransform.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { EventSource } from \"eventsource\";\nimport { setTimeout } from \"node:timers\";\nimport util from \"node:util\";\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\n\nimport { InMemoryEventStore } from \"../InMemoryEventStore.js\";\nimport { proxyServer } from \"../proxyServer.js\";\nimport { SSEServer, startHTTPServer } from \"../startHTTPServer.js\";\nimport { StdioClientTransport } from \"../StdioClientTransport.js\";\n\nutil.inspect.defaultOptions.depth = 8;\n\nif (!(\"EventSource\" in global)) {\n // @ts-expect-error - figure out how to use --experimental-eventsource with vitest\n global.EventSource = EventSource;\n}\n\nconst argv = await yargs(hideBin(process.argv))\n .scriptName(\"mcp-proxy\")\n .command(\"$0 <command> [args...]\", \"Run a command with MCP arguments\")\n .positional(\"command\", {\n demandOption: true,\n describe: \"The command to run\",\n type: \"string\",\n })\n .positional(\"args\", {\n array: true,\n describe: \"The arguments to pass to the command\",\n type: \"string\",\n })\n .env(\"MCP_PROXY\")\n .parserConfiguration({\n \"populate--\": true,\n })\n .options({\n debug: {\n default: false,\n describe: \"Enable debug logging\",\n type: \"boolean\",\n },\n endpoint: {\n describe: \"The endpoint to listen on\",\n type: \"string\",\n },\n gracefulShutdownTimeout: {\n default: 5000,\n describe: \"The timeout (in milliseconds) for graceful shutdown\",\n type: \"number\",\n },\n host: {\n default: \"::\",\n describe: \"The host to listen on\",\n type: \"string\",\n },\n port: {\n default: 8080,\n describe: \"The port to listen on\",\n type: \"number\",\n },\n server: {\n choices: [\"sse\", \"stream\"],\n describe:\n \"The server type to use (sse or stream). By default, both are enabled\",\n type: \"string\",\n },\n shell: {\n default: false,\n describe: \"Spawn the server via the user's shell\",\n type: \"boolean\",\n },\n sseEndpoint: {\n default: \"/sse\",\n describe: \"The SSE endpoint to listen on\",\n type: \"string\",\n },\n stateless: {\n default: false,\n describe: \"Enable stateless mode for HTTP streamable transport (no session management)\",\n type: \"boolean\",\n },\n streamEndpoint: {\n default: \"/mcp\",\n describe: \"The stream endpoint to listen on\",\n type: \"string\",\n },\n })\n .help()\n .parseAsync();\n\n// Determine the final command and args\nif (!argv.command) {\n throw new Error(\"No command specified\");\n}\n\nconst finalCommand = argv.command;\n// If -- separator was used, args after -- are in argv[\"--\"], otherwise use parsed args\nconst finalArgs = (argv[\"--\"] as string[]) || argv.args;\n\nconst connect = async (client: Client) => {\n const transport = new StdioClientTransport({\n args: finalArgs,\n command: finalCommand,\n env: process.env as Record<string, string>,\n onEvent: (event) => {\n if (argv.debug) {\n console.debug(\"transport event\", event);\n }\n },\n shell: argv.shell,\n stderr: \"pipe\",\n });\n\n await client.connect(transport);\n};\n\nconst proxy = async () => {\n const client = new Client(\n {\n name: \"mcp-proxy\",\n version: \"1.0.0\",\n },\n {\n capabilities: {},\n },\n );\n\n await connect(client);\n\n const serverVersion = client.getServerVersion() as {\n name: string;\n version: string;\n };\n\n const serverCapabilities = client.getServerCapabilities() as {\n capabilities: Record<string, unknown>;\n };\n\n console.info(\"starting server on port %d\", argv.port);\n\n const createServer = async () => {\n const server = new Server(serverVersion, {\n capabilities: serverCapabilities,\n });\n\n proxyServer({\n client,\n server,\n serverCapabilities,\n });\n\n return server;\n };\n\n const server = await startHTTPServer({\n createServer,\n eventStore: new InMemoryEventStore(),\n host: argv.host,\n port: argv.port,\n sseEndpoint:\n argv.server && argv.server !== \"sse\"\n ? null\n : (argv.sseEndpoint ?? argv.endpoint),\n stateless: argv.stateless,\n streamEndpoint:\n argv.server && argv.server !== \"stream\"\n ? null\n : (argv.streamEndpoint ?? argv.endpoint),\n });\n\n return {\n close: () => {\n return server.close();\n },\n };\n};\n\nconst createGracefulShutdown = ({\n server,\n timeout,\n}: {\n server: SSEServer;\n timeout: number;\n}) => {\n const gracefulShutdown = () => {\n console.info(\"received shutdown signal; shutting down\");\n\n server.close();\n\n setTimeout(() => {\n // Exit with non-zero code to indicate failure to shutdown gracefully\n process.exit(1);\n }, timeout).unref();\n };\n\n process.on(\"SIGTERM\", gracefulShutdown);\n process.on(\"SIGINT\", gracefulShutdown);\n\n return () => {\n server.close();\n };\n};\n\nconst main = async () => {\n try {\n const server = await proxy();\n\n createGracefulShutdown({\n server,\n timeout: argv.gracefulShutdownTimeout,\n });\n } catch (error) {\n console.error(\"could not start the proxy\", error);\n\n setTimeout(() => {\n process.exit(1);\n }, 1000);\n }\n};\n\nawait main();\n","/**\n * Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/66e1508162d37c0b83b0637ebcd7f07946e3d210/src/client/stdio.ts#L90\n */\n\nimport {\n ReadBuffer,\n serializeMessage,\n} from \"@modelcontextprotocol/sdk/shared/stdio.js\";\nimport { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChildProcess, IOType, spawn } from \"node:child_process\";\nimport { Stream } from \"node:stream\";\n\nimport { JSONFilterTransform } from \"./JSONFilterTransform.js\";\n\nexport type StdioServerParameters = {\n /**\n * Command line arguments to pass to the executable.\n */\n args?: string[];\n\n /**\n * The executable to run to start the server.\n */\n command: string;\n\n /**\n * The working directory to use when spawning the process.\n *\n * If not specified, the current working directory will be inherited.\n */\n cwd?: string;\n\n /**\n * The environment to use when spawning the process.\n *\n * If not specified, the result of getDefaultEnvironment() will be used.\n */\n env: Record<string, string>;\n\n /**\n * A function to call when an event occurs.\n */\n onEvent?: (event: TransportEvent) => void;\n\n /**\n * When true, spawn the child process using the user's shell.\n */\n shell?: boolean;\n\n /**\n * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.\n *\n * The default is \"inherit\", meaning messages to stderr will be printed to the parent process's stderr.\n */\n stderr?: IOType | number | Stream;\n};\n\ntype TransportEvent =\n | {\n chunk: string;\n type: \"data\";\n }\n | {\n error: Error;\n type: \"error\";\n }\n | {\n message: JSONRPCMessage;\n type: \"message\";\n }\n | {\n type: \"close\";\n };\n\n/**\n * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.\n *\n * This transport is only available in Node.js environments.\n */\nexport class StdioClientTransport implements Transport {\n onclose?: () => void;\n\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n /**\n * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to \"pipe\" or \"overlapped\".\n *\n * This is only available after the process has been started.\n */\n get stderr(): null | Stream {\n return this.process?.stderr ?? null;\n }\n private abortController: AbortController = new AbortController();\n\n private onEvent?: (event: TransportEvent) => void;\n private process?: ChildProcess;\n private readBuffer: ReadBuffer = new ReadBuffer();\n\n private serverParams: StdioServerParameters;\n\n constructor(server: StdioServerParameters) {\n this.serverParams = server;\n this.onEvent = server.onEvent;\n }\n\n async close(): Promise<void> {\n this.onEvent?.({\n type: \"close\",\n });\n\n this.abortController.abort();\n this.process = undefined;\n this.readBuffer.clear();\n }\n\n send(message: JSONRPCMessage): Promise<void> {\n return new Promise((resolve) => {\n if (!this.process?.stdin) {\n throw new Error(\"Not connected\");\n }\n\n const json = serializeMessage(message);\n if (this.process.stdin.write(json)) {\n resolve();\n } else {\n this.process.stdin.once(\"drain\", resolve);\n }\n });\n }\n\n /**\n * Starts the server process and prepares to communicate with it.\n */\n async start(): Promise<void> {\n if (this.process) {\n throw new Error(\n \"StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.\",\n );\n }\n\n return new Promise((resolve, reject) => {\n this.process = spawn(\n this.serverParams.command,\n this.serverParams.args ?? [],\n {\n cwd: this.serverParams.cwd,\n env: this.serverParams.env,\n shell: this.serverParams.shell ?? false,\n signal: this.abortController.signal,\n stdio: [\"pipe\", \"pipe\", this.serverParams.stderr ?? \"inherit\"],\n },\n );\n\n this.process.on(\"error\", (error) => {\n if (error.name === \"AbortError\") {\n this.onclose?.();\n return;\n }\n\n reject(error);\n this.onerror?.(error);\n });\n\n this.process.on(\"spawn\", () => {\n resolve();\n });\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n this.process.on(\"close\", (_code) => {\n this.onEvent?.({\n type: \"close\",\n });\n\n this.process = undefined;\n this.onclose?.();\n });\n\n this.process.stdin?.on(\"error\", (error) => {\n this.onEvent?.({\n error,\n type: \"error\",\n });\n\n this.onerror?.(error);\n });\n\n const jsonFilterTransform = new JSONFilterTransform();\n\n this.process.stdout?.pipe(jsonFilterTransform);\n\n jsonFilterTransform.on(\"data\", (chunk) => {\n this.onEvent?.({\n chunk: chunk.toString(),\n type: \"data\",\n });\n\n this.readBuffer.append(chunk);\n this.processReadBuffer();\n });\n\n jsonFilterTransform.on(\"error\", (error) => {\n this.onEvent?.({\n error,\n type: \"error\",\n });\n\n this.onerror?.(error);\n });\n });\n }\n\n private processReadBuffer() {\n while (true) {\n try {\n const message = this.readBuffer.readMessage();\n\n if (message === null) {\n break;\n }\n\n this.onEvent?.({\n message,\n type: \"message\",\n });\n\n this.onmessage?.(message);\n } catch (error) {\n this.onEvent?.({\n error: error as Error,\n type: \"error\",\n });\n\n this.onerror?.(error as Error);\n }\n }\n }\n}\n","import { Transform } from \"node:stream\";\n\n/**\n * Filters out lines that do not start with '{' from the input stream.\n * We use this to drop anything that is obviously not a JSON-RPC message.\n */\nexport class JSONFilterTransform extends Transform {\n private buffer = \"\";\n\n constructor() {\n super({ objectMode: false });\n }\n\n _flush(callback: (error: Error | null, chunk: Buffer | null) => void) {\n // Handle any remaining data in buffer\n if (this.buffer.trim().startsWith(\"{\")) {\n callback(null, Buffer.from(this.buffer));\n } else {\n callback(null, null);\n }\n }\n\n _transform(\n chunk: Buffer,\n _encoding: string,\n callback: (error: Error | null, chunk: Buffer | null) => void,\n ) {\n this.buffer += chunk.toString();\n const lines = this.buffer.split(\"\\n\");\n\n // Keep the last incomplete line in the buffer\n this.buffer = lines.pop() || \"\";\n\n // Filter lines that start with '{'\n const jsonLines = [];\n const nonJsonLines = [];\n\n for (const line of lines) {\n if (line.trim().startsWith(\"{\")) {\n jsonLines.push(line);\n } else {\n nonJsonLines.push(line);\n }\n }\n\n if (nonJsonLines.length > 0) {\n console.warn(\"[mcp-proxy] ignoring non-JSON output\", nonJsonLines);\n }\n\n if (jsonLines.length > 0) {\n // Send filtered lines with newlines\n const output = jsonLines.join(\"\\n\") + \"\\n\";\n\n callback(null, Buffer.from(output));\n } else {\n callback(null, null);\n }\n }\n}\n"],"mappings":"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","names":["server"]}
@@ -168,6 +168,42 @@ var getBody = (request) => {
168
168
  });
169
169
  });
170
170
  };
171
+ var createJsonRpcErrorResponse = (code, message) => {
172
+ return JSON.stringify({
173
+ error: { code, message },
174
+ id: null,
175
+ jsonrpc: "2.0"
176
+ });
177
+ };
178
+ var handleResponseError = (error, res) => {
179
+ if (error instanceof Response) {
180
+ const fixedHeaders = {};
181
+ error.headers.forEach((value, key) => {
182
+ if (fixedHeaders[key]) {
183
+ if (Array.isArray(fixedHeaders[key])) {
184
+ fixedHeaders[key].push(value);
185
+ } else {
186
+ fixedHeaders[key] = [fixedHeaders[key], value];
187
+ }
188
+ } else {
189
+ fixedHeaders[key] = value;
190
+ }
191
+ });
192
+ res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);
193
+ return true;
194
+ }
195
+ return false;
196
+ };
197
+ var cleanupServer = async (server, onClose) => {
198
+ if (onClose) {
199
+ await onClose(server);
200
+ }
201
+ try {
202
+ await server.close();
203
+ } catch (error) {
204
+ console.error("[mcp-proxy] error closing server", error);
205
+ }
206
+ };
171
207
  var handleStreamRequest = async ({
172
208
  activeTransports,
173
209
  createServer,
@@ -177,7 +213,8 @@ var handleStreamRequest = async ({
177
213
  onClose,
178
214
  onConnect,
179
215
  req,
180
- res
216
+ res,
217
+ stateless
181
218
  }) => {
182
219
  if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
183
220
  try {
@@ -190,14 +227,7 @@ var handleStreamRequest = async ({
190
227
  if (!activeTransport) {
191
228
  res.setHeader("Content-Type", "application/json");
192
229
  res.writeHead(404).end(
193
- JSON.stringify({
194
- error: {
195
- code: -32001,
196
- message: "Session not found"
197
- },
198
- id: null,
199
- jsonrpc: "2.0"
200
- })
230
+ createJsonRpcErrorResponse(-32001, "Session not found")
201
231
  );
202
232
  return true;
203
233
  }
@@ -208,44 +238,51 @@ var handleStreamRequest = async ({
208
238
  enableJsonResponse,
209
239
  eventStore: eventStore || new InMemoryEventStore(),
210
240
  onsessioninitialized: (_sessionId) => {
211
- activeTransports[_sessionId] = {
212
- server,
213
- transport
214
- };
241
+ if (!stateless && _sessionId) {
242
+ activeTransports[_sessionId] = {
243
+ server,
244
+ transport
245
+ };
246
+ }
215
247
  },
216
- sessionIdGenerator: randomUUID
248
+ sessionIdGenerator: stateless ? void 0 : randomUUID
217
249
  });
218
250
  transport.onclose = async () => {
219
251
  const sid = transport.sessionId;
220
- if (sid && activeTransports[sid]) {
221
- if (onClose) {
222
- await onClose(server);
223
- }
224
- try {
225
- await server.close();
226
- } catch (error) {
227
- console.error("[mcp-proxy] error closing server", error);
228
- }
252
+ if (!stateless && sid && activeTransports[sid]) {
253
+ await cleanupServer(server, onClose);
229
254
  delete activeTransports[sid];
255
+ } else if (stateless) {
256
+ await cleanupServer(server, onClose);
230
257
  }
231
258
  };
232
259
  try {
233
260
  server = await createServer(req);
234
261
  } catch (error) {
235
- if (error instanceof Response) {
236
- const fixedHeaders = {};
237
- error.headers.forEach((value, key) => {
238
- if (fixedHeaders[key]) {
239
- if (Array.isArray(fixedHeaders[key])) {
240
- fixedHeaders[key].push(value);
241
- } else {
242
- fixedHeaders[key] = [fixedHeaders[key], value];
243
- }
244
- } else {
245
- fixedHeaders[key] = value;
246
- }
247
- });
248
- res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);
262
+ if (handleResponseError(error, res)) {
263
+ return true;
264
+ }
265
+ res.writeHead(500).end("Error creating server");
266
+ return true;
267
+ }
268
+ server.connect(transport);
269
+ if (onConnect) {
270
+ await onConnect(server);
271
+ }
272
+ await transport.handleRequest(req, res, body);
273
+ return true;
274
+ } else if (stateless && !sessionId && !isInitializeRequest(body)) {
275
+ transport = new StreamableHTTPServerTransport({
276
+ enableJsonResponse,
277
+ eventStore: eventStore || new InMemoryEventStore(),
278
+ onsessioninitialized: () => {
279
+ },
280
+ sessionIdGenerator: void 0
281
+ });
282
+ try {
283
+ server = await createServer(req);
284
+ } catch (error) {
285
+ if (handleResponseError(error, res)) {
249
286
  return true;
250
287
  }
251
288
  res.writeHead(500).end("Error creating server");
@@ -260,14 +297,7 @@ var handleStreamRequest = async ({
260
297
  } else {
261
298
  res.setHeader("Content-Type", "application/json");
262
299
  res.writeHead(400).end(
263
- JSON.stringify({
264
- error: {
265
- code: -32e3,
266
- message: "Bad Request: No valid session ID provided"
267
- },
268
- id: null,
269
- jsonrpc: "2.0"
270
- })
300
+ createJsonRpcErrorResponse(-32e3, "Bad Request: No valid session ID provided")
271
301
  );
272
302
  return true;
273
303
  }
@@ -277,11 +307,7 @@ var handleStreamRequest = async ({
277
307
  console.error("[mcp-proxy] error handling request", error);
278
308
  res.setHeader("Content-Type", "application/json");
279
309
  res.writeHead(500).end(
280
- JSON.stringify({
281
- error: { code: -32603, message: "Internal Server Error" },
282
- id: null,
283
- jsonrpc: "2.0"
284
- })
310
+ createJsonRpcErrorResponse(-32603, "Internal Server Error")
285
311
  );
286
312
  }
287
313
  return true;
@@ -325,9 +351,7 @@ var handleStreamRequest = async ({
325
351
  }
326
352
  try {
327
353
  await activeTransport.transport.handleRequest(req, res);
328
- if (onClose) {
329
- await onClose(activeTransport.server);
330
- }
354
+ await cleanupServer(activeTransport.server, onClose);
331
355
  } catch (error) {
332
356
  console.error("[mcp-proxy] error handling delete request", error);
333
357
  res.writeHead(500).end("Error handling delete request");
@@ -351,8 +375,7 @@ var handleSSERequest = async ({
351
375
  try {
352
376
  server = await createServer(req);
353
377
  } catch (error) {
354
- if (error instanceof Response) {
355
- res.writeHead(error.status).end(error.statusText);
378
+ if (handleResponseError(error, res)) {
356
379
  return true;
357
380
  }
358
381
  res.writeHead(500).end("Error creating server");
@@ -362,13 +385,8 @@ var handleSSERequest = async ({
362
385
  let closed = false;
363
386
  res.on("close", async () => {
364
387
  closed = true;
365
- try {
366
- await server.close();
367
- } catch (error) {
368
- console.error("[mcp-proxy] error closing server", error);
369
- }
388
+ await cleanupServer(server, onClose);
370
389
  delete activeTransports[transport.sessionId];
371
- await onClose?.(server);
372
390
  });
373
391
  try {
374
392
  await server.connect(transport);
@@ -416,6 +434,7 @@ var startHTTPServer = async ({
416
434
  onUnhandledRequest,
417
435
  port,
418
436
  sseEndpoint = "/sse",
437
+ stateless,
419
438
  streamEndpoint = "/mcp"
420
439
  }) => {
421
440
  const activeSSETransports = {};
@@ -462,7 +481,8 @@ var startHTTPServer = async ({
462
481
  onClose,
463
482
  onConnect,
464
483
  req,
465
- res
484
+ res,
485
+ stateless
466
486
  })) {
467
487
  return;
468
488
  }
@@ -503,4 +523,4 @@ export {
503
523
  proxyServer,
504
524
  startHTTPServer
505
525
  };
506
- //# sourceMappingURL=chunk-UHPMWS2Y.js.map
526
+ //# sourceMappingURL=chunk-UFGZ6L2I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/InMemoryEventStore.ts","../src/proxyServer.ts","../src/startHTTPServer.ts"],"sourcesContent":["/**\n * This is a copy of the InMemoryEventStore from the typescript-sdk\n * https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts\n */\n\nimport type { EventStore } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Simple in-memory implementation of the EventStore interface for resumability\n * This is primarily intended for examples and testing, not for production use\n * where a persistent storage solution would be more appropriate.\n */\nexport class InMemoryEventStore implements EventStore {\n private events: Map<string, { message: JSONRPCMessage; streamId: string }> =\n new Map();\n\n /**\n * Replays events that occurred after a specific event ID\n * Implements EventStore.replayEventsAfter\n */\n async replayEventsAfter(\n lastEventId: string,\n {\n send,\n }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> },\n ): Promise<string> {\n if (!lastEventId || !this.events.has(lastEventId)) {\n return \"\";\n }\n\n // Extract the stream ID from the event ID\n const streamId = this.getStreamIdFromEventId(lastEventId);\n\n if (!streamId) {\n return \"\";\n }\n\n let foundLastEvent = false;\n\n // Sort events by eventId for chronological ordering\n const sortedEvents = [...this.events.entries()].sort((a, b) =>\n a[0].localeCompare(b[0]),\n );\n\n for (const [\n eventId,\n { message, streamId: eventStreamId },\n ] of sortedEvents) {\n // Only include events from the same stream\n if (eventStreamId !== streamId) {\n continue;\n }\n\n // Start sending events after we find the lastEventId\n if (eventId === lastEventId) {\n foundLastEvent = true;\n continue;\n }\n\n if (foundLastEvent) {\n await send(eventId, message);\n }\n }\n\n return streamId;\n }\n\n /**\n * Stores an event with a generated event ID\n * Implements EventStore.storeEvent\n */\n async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {\n const eventId = this.generateEventId(streamId);\n\n this.events.set(eventId, { message, streamId });\n\n return eventId;\n }\n\n /**\n * Generates a unique event ID for a given stream ID\n */\n private generateEventId(streamId: string): string {\n return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;\n }\n\n /**\n * Extracts the stream ID from an event ID\n */\n private getStreamIdFromEventId(eventId: string): string {\n const parts = eventId.split(\"_\");\n\n return parts.length > 0 ? parts[0] : \"\";\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n CompleteRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n LoggingMessageNotificationSchema,\n ReadResourceRequestSchema,\n ResourceUpdatedNotificationSchema,\n ServerCapabilities,\n SubscribeRequestSchema,\n UnsubscribeRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nexport const proxyServer = async ({\n client,\n server,\n serverCapabilities,\n}: {\n client: Client;\n server: Server;\n serverCapabilities: ServerCapabilities;\n}): Promise<void> => {\n if (serverCapabilities?.logging) {\n server.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n client.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return server.notification(args);\n },\n );\n }\n\n if (serverCapabilities?.prompts) {\n server.setRequestHandler(GetPromptRequestSchema, async (args) => {\n return client.getPrompt(args.params);\n });\n\n server.setRequestHandler(ListPromptsRequestSchema, async (args) => {\n return client.listPrompts(args.params);\n });\n }\n\n if (serverCapabilities?.resources) {\n server.setRequestHandler(ListResourcesRequestSchema, async (args) => {\n return client.listResources(args.params);\n });\n\n server.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async (args) => {\n return client.listResourceTemplates(args.params);\n },\n );\n\n server.setRequestHandler(ReadResourceRequestSchema, async (args) => {\n return client.readResource(args.params);\n });\n\n if (serverCapabilities?.resources.subscribe) {\n server.setNotificationHandler(\n ResourceUpdatedNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n\n server.setRequestHandler(SubscribeRequestSchema, async (args) => {\n return client.subscribeResource(args.params);\n });\n\n server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {\n return client.unsubscribeResource(args.params);\n });\n }\n }\n\n if (serverCapabilities?.tools) {\n server.setRequestHandler(CallToolRequestSchema, async (args) => {\n return client.callTool(args.params);\n });\n\n server.setRequestHandler(ListToolsRequestSchema, async (args) => {\n return client.listTools(args.params);\n });\n }\n\n server.setRequestHandler(CompleteRequestSchema, async (args) => {\n return client.complete(args.params);\n });\n};\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n EventStore,\n StreamableHTTPServerTransport,\n} from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport http from \"http\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { InMemoryEventStore } from \"./InMemoryEventStore.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype ServerLike = {\n close: Server[\"close\"];\n connect: Server[\"connect\"];\n};\n\nconst getBody = (request: http.IncomingMessage) => {\n return new Promise((resolve) => {\n const bodyParts: Buffer[] = [];\n let body: string;\n request\n .on(\"data\", (chunk) => {\n bodyParts.push(chunk);\n })\n .on(\"end\", () => {\n body = Buffer.concat(bodyParts).toString();\n try {\n resolve(JSON.parse(body));\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing body\", error);\n resolve(null);\n }\n });\n });\n};\n\n// Helper function to create JSON RPC error responses\nconst createJsonRpcErrorResponse = (code: number, message: string) => {\n return JSON.stringify({\n error: { code, message },\n id: null,\n jsonrpc: \"2.0\",\n });\n};\n\n// Helper function to handle Response errors and send appropriate HTTP response\nconst handleResponseError = (error: unknown, res: http.ServerResponse): boolean => {\n if (error instanceof Response) {\n const fixedHeaders: http.OutgoingHttpHeaders = {};\n error.headers.forEach((value, key) => {\n if (fixedHeaders[key]) {\n if (Array.isArray(fixedHeaders[key])) {\n (fixedHeaders[key] as string[]).push(value);\n } else {\n fixedHeaders[key] = [fixedHeaders[key] as string, value];\n }\n } else {\n fixedHeaders[key] = value;\n }\n });\n res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);\n return true;\n }\n return false;\n};\n\n// Helper function to clean up server resources\nconst cleanupServer = async <T extends ServerLike>(\n server: T,\n onClose?: (server: T) => Promise<void>\n) => {\n if (onClose) {\n await onClose(server);\n }\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"[mcp-proxy] error closing server\", error);\n }\n};\n\nconst handleStreamRequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n enableJsonResponse,\n endpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n stateless,\n}: {\n activeTransports: Record<\n string,\n { server: T; transport: StreamableHTTPServerTransport }\n >;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n endpoint: string;\n eventStore?: EventStore;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n stateless?: boolean;\n}) => {\n if (\n req.method === \"POST\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n try {\n const sessionId = Array.isArray(req.headers[\"mcp-session-id\"])\n ? req.headers[\"mcp-session-id\"][0]\n : req.headers[\"mcp-session-id\"];\n\n let transport: StreamableHTTPServerTransport;\n\n let server: T;\n\n const body = await getBody(req);\n\n if (sessionId) {\n const activeTransport = activeTransports[sessionId];\n if (!activeTransport) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.writeHead(404).end(\n createJsonRpcErrorResponse(-32001, \"Session not found\"),\n );\n\n return true;\n }\n\n transport = activeTransport.transport;\n server = activeTransport.server;\n } else if (!sessionId && isInitializeRequest(body)) {\n // Create a new transport for the session\n transport = new StreamableHTTPServerTransport({\n enableJsonResponse,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: (_sessionId) => {\n // add only when the id Session id is generated (skip in stateless mode)\n if (!stateless && _sessionId) {\n activeTransports[_sessionId] = {\n server,\n transport,\n };\n }\n },\n sessionIdGenerator: stateless ? undefined : randomUUID,\n });\n\n // Handle the server close event\n transport.onclose = async () => {\n const sid = transport.sessionId;\n if (!stateless && sid && activeTransports[sid]) {\n await cleanupServer(server, onClose);\n delete activeTransports[sid];\n } else if (stateless) {\n // In stateless mode, always call onClose when transport closes\n await cleanupServer(server, onClose);\n }\n };\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n server.connect(transport);\n\n if (onConnect) {\n await onConnect(server);\n }\n\n await transport.handleRequest(req, res, body);\n\n return true;\n } else if (stateless && !sessionId && !isInitializeRequest(body)) {\n // In stateless mode, handle non-initialize requests by creating a new transport\n transport = new StreamableHTTPServerTransport({\n enableJsonResponse,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: () => {\n // No session tracking in stateless mode\n },\n sessionIdGenerator: undefined,\n });\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n server.connect(transport);\n\n if (onConnect) {\n await onConnect(server);\n }\n\n await transport.handleRequest(req, res, body);\n\n return true;\n } else {\n // Error if the server is not created but the request is not an initialize request\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(400).end(\n createJsonRpcErrorResponse(-32000, \"Bad Request: No valid session ID provided\"),\n );\n\n return true;\n }\n\n // Handle the request if the server is already created\n await transport.handleRequest(req, res, body);\n\n return true;\n } catch (error) {\n console.error(\"[mcp-proxy] error handling request\", error);\n\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(500).end(\n createJsonRpcErrorResponse(-32603, \"Internal Server Error\"),\n );\n }\n return true;\n }\n\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const activeTransport:\n | {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n | undefined = sessionId ? activeTransports[sessionId] : undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n const lastEventId = req.headers[\"last-event-id\"] as string | undefined;\n\n if (lastEventId) {\n console.log(\n `[mcp-proxy] client reconnecting with Last-Event-ID ${lastEventId} for session ID ${sessionId}`,\n );\n } else {\n console.log(\n `[mcp-proxy] establishing new SSE stream for session ID ${sessionId}`,\n );\n }\n\n await activeTransport.transport.handleRequest(req, res);\n\n return true;\n }\n\n if (\n req.method === \"DELETE\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n console.log(\"[mcp-proxy] received delete request\");\n\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"Invalid or missing sessionId\");\n\n return true;\n }\n\n console.log(\"[mcp-proxy] received delete request for session\", sessionId);\n\n const activeTransport = activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return true;\n }\n\n try {\n await activeTransport.transport.handleRequest(req, res);\n\n await cleanupServer(activeTransport.server, onClose);\n } catch (error) {\n console.error(\"[mcp-proxy] error handling delete request\", error);\n\n res.writeHead(500).end(\"Error handling delete request\");\n }\n\n return true;\n }\n\n return false;\n};\n\nconst handleSSERequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n endpoint,\n onClose,\n onConnect,\n req,\n res,\n}: {\n activeTransports: Record<string, SSEServerTransport>;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n endpoint: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n}) => {\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n let server: T;\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n activeTransports[transport.sessionId] = transport;\n\n let closed = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n await cleanupServer(server, onClose);\n\n delete activeTransports[transport.sessionId];\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n if (onConnect) {\n await onConnect(server);\n }\n } catch (error) {\n if (!closed) {\n console.error(\"[mcp-proxy] error connecting to server\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return true;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(req.url, \"https://example.com\").searchParams.get(\n \"sessionId\",\n );\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n const activeTransport: SSEServerTransport | undefined =\n activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return true;\n }\n\n return false;\n};\n\nexport const startHTTPServer = async <T extends ServerLike>({\n createServer,\n enableJsonResponse,\n eventStore,\n host = \"::\",\n onClose,\n onConnect,\n onUnhandledRequest,\n port,\n sseEndpoint = \"/sse\",\n stateless,\n streamEndpoint = \"/mcp\",\n}: {\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n eventStore?: EventStore;\n host?: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ) => Promise<void>;\n port: number;\n sseEndpoint?: null | string;\n stateless?: boolean;\n streamEndpoint?: null | string;\n}): Promise<SSEServer> => {\n const activeSSETransports: Record<string, SSEServerTransport> = {};\n\n const activeStreamTransports: Record<\n string,\n {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n > = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n res.setHeader(\"Access-Control-Expose-Headers\", \"mcp-session-id\");\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing origin\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n return;\n }\n\n if (\n sseEndpoint &&\n (await handleSSERequest({\n activeTransports: activeSSETransports,\n createServer,\n endpoint: sseEndpoint,\n onClose,\n onConnect,\n req,\n res,\n }))\n ) {\n return;\n }\n\n if (\n streamEndpoint &&\n (await handleStreamRequest({\n activeTransports: activeStreamTransports,\n createServer,\n enableJsonResponse,\n endpoint: streamEndpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n stateless,\n }))\n ) {\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, host, () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeSSETransports)) {\n await transport.close();\n }\n\n for (const transport of Object.values(activeStreamTransports)) {\n await transport.transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -54,7 +54,7 @@ type ServerLike = {
54
54
  close: Server["close"];
55
55
  connect: Server["connect"];
56
56
  };
57
- declare const startHTTPServer: <T extends ServerLike>({ createServer, enableJsonResponse, eventStore, host, onClose, onConnect, onUnhandledRequest, port, sseEndpoint, streamEndpoint, }: {
57
+ declare const startHTTPServer: <T extends ServerLike>({ createServer, enableJsonResponse, eventStore, host, onClose, onConnect, onUnhandledRequest, port, sseEndpoint, stateless, streamEndpoint, }: {
58
58
  createServer: (request: http.IncomingMessage) => Promise<T>;
59
59
  enableJsonResponse?: boolean;
60
60
  eventStore?: EventStore;
@@ -64,6 +64,7 @@ declare const startHTTPServer: <T extends ServerLike>({ createServer, enableJson
64
64
  onUnhandledRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
65
65
  port: number;
66
66
  sseEndpoint?: null | string;
67
+ stateless?: boolean;
67
68
  streamEndpoint?: null | string;
68
69
  }) => Promise<SSEServer>;
69
70
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  InMemoryEventStore,
3
3
  proxyServer,
4
4
  startHTTPServer
5
- } from "./chunk-UHPMWS2Y.js";
5
+ } from "./chunk-UFGZ6L2I.js";
6
6
 
7
7
  // src/startStdioServer.ts
8
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
package/jsr.json CHANGED
@@ -3,5 +3,5 @@
3
3
  "include": ["src/index.ts", "src/bin/mcp-proxy.ts"],
4
4
  "license": "MIT",
5
5
  "name": "@punkpeye/mcp-proxy",
6
- "version": "5.4.0"
6
+ "version": "5.5.1"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "5.4.0",
3
+ "version": "5.5.1",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -22,7 +22,7 @@
22
22
  "module": "dist/index.js",
23
23
  "types": "dist/index.d.ts",
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.13.2",
25
+ "@modelcontextprotocol/sdk": "^1.17.1",
26
26
  "eventsource": "^4.0.0",
27
27
  "yargs": "^18.0.0"
28
28
  },
@@ -43,25 +43,25 @@
43
43
  ]
44
44
  },
45
45
  "devDependencies": {
46
- "@eslint/js": "^9.30.0",
47
- "@sebbo2002/semantic-release-jsr": "^3.0.0",
46
+ "@eslint/js": "^9.32.0",
47
+ "@sebbo2002/semantic-release-jsr": "^3.0.1",
48
48
  "@tsconfig/node22": "^22.0.2",
49
49
  "@types/express": "^5.0.3",
50
- "@types/node": "^24.0.8",
50
+ "@types/node": "^24.2.0",
51
51
  "@types/yargs": "^17.0.33",
52
- "eslint": "^9.30.0",
53
- "eslint-config-prettier": "^10.1.5",
52
+ "eslint": "^9.32.0",
53
+ "eslint-config-prettier": "^10.1.8",
54
54
  "eslint-plugin-perfectionist": "^4.15.0",
55
55
  "express": "^5.0.1",
56
- "get-port-please": "^3.1.2",
57
- "jiti": "^2.4.2",
58
- "jsr": "^0.13.4",
56
+ "get-port-please": "^3.2.0",
57
+ "jiti": "^2.5.1",
58
+ "jsr": "^0.13.5",
59
59
  "prettier": "^3.6.2",
60
- "semantic-release": "^24.2.6",
60
+ "semantic-release": "^24.2.7",
61
61
  "tsup": "^8.5.0",
62
62
  "tsx": "^4.20.3",
63
- "typescript": "^5.8.3",
64
- "typescript-eslint": "^8.35.1",
63
+ "typescript": "^5.9.2",
64
+ "typescript-eslint": "^8.39.0",
65
65
  "vitest": "^3.2.4"
66
66
  },
67
67
  "tsup": {
@@ -78,6 +78,11 @@ const argv = await yargs(hideBin(process.argv))
78
78
  describe: "The SSE endpoint to listen on",
79
79
  type: "string",
80
80
  },
81
+ stateless: {
82
+ default: false,
83
+ describe: "Enable stateless mode for HTTP streamable transport (no session management)",
84
+ type: "boolean",
85
+ },
81
86
  streamEndpoint: {
82
87
  default: "/mcp",
83
88
  describe: "The stream endpoint to listen on",
@@ -160,6 +165,7 @@ const proxy = async () => {
160
165
  argv.server && argv.server !== "sse"
161
166
  ? null
162
167
  : (argv.sseEndpoint ?? argv.endpoint),
168
+ stateless: argv.stateless,
163
169
  streamEndpoint:
164
170
  argv.server && argv.server !== "stream"
165
171
  ? null
@@ -238,3 +238,92 @@ it("proxies messages between SSE and stdio servers", async () => {
238
238
 
239
239
  expect(onClose).toHaveBeenCalled();
240
240
  });
241
+
242
+ it("supports stateless HTTP streamable transport", async () => {
243
+ const stdioTransport = new StdioClientTransport({
244
+ args: ["src/fixtures/simple-stdio-server.ts"],
245
+ command: "tsx",
246
+ });
247
+
248
+ const stdioClient = new Client(
249
+ {
250
+ name: "mcp-proxy",
251
+ version: "1.0.0",
252
+ },
253
+ {
254
+ capabilities: {},
255
+ },
256
+ );
257
+
258
+ await stdioClient.connect(stdioTransport);
259
+
260
+ const serverVersion = stdioClient.getServerVersion() as {
261
+ name: string;
262
+ version: string;
263
+ };
264
+
265
+ const serverCapabilities = stdioClient.getServerCapabilities() as {
266
+ capabilities: Record<string, unknown>;
267
+ };
268
+
269
+ const port = await getRandomPort();
270
+
271
+ const onConnect = vi.fn().mockResolvedValue(undefined);
272
+ const onClose = vi.fn().mockResolvedValue(undefined);
273
+
274
+ const httpServer = await startHTTPServer({
275
+ createServer: async () => {
276
+ const mcpServer = new Server(serverVersion, {
277
+ capabilities: serverCapabilities,
278
+ });
279
+
280
+ await proxyServer({
281
+ client: stdioClient,
282
+ server: mcpServer,
283
+ serverCapabilities,
284
+ });
285
+
286
+ return mcpServer;
287
+ },
288
+ onClose,
289
+ onConnect,
290
+ port,
291
+ stateless: true, // Enable stateless mode
292
+ });
293
+
294
+ // Create a stateless streamable HTTP client
295
+ const streamTransport = new StreamableHTTPClientTransport(
296
+ new URL(`http://localhost:${port}/mcp`),
297
+ );
298
+
299
+ const streamClient = new Client(
300
+ {
301
+ name: "stream-client-stateless",
302
+ version: "1.0.0",
303
+ },
304
+ {
305
+ capabilities: {},
306
+ },
307
+ );
308
+
309
+ await streamClient.connect(streamTransport);
310
+
311
+ // Test that we can still make requests in stateless mode
312
+ const result = await streamClient.listResources();
313
+ expect(result).toEqual({
314
+ resources: [
315
+ {
316
+ name: "Example Resource",
317
+ uri: "file:///example.txt",
318
+ },
319
+ ],
320
+ });
321
+
322
+ await streamClient.close();
323
+ await httpServer.close();
324
+ await stdioClient.close();
325
+
326
+ expect(onConnect).toHaveBeenCalled();
327
+ // Note: in stateless mode, onClose behavior may differ since there's no persistent session
328
+ await delay(100);
329
+ });
@@ -39,6 +39,52 @@ const getBody = (request: http.IncomingMessage) => {
39
39
  });
40
40
  };
41
41
 
42
+ // Helper function to create JSON RPC error responses
43
+ const createJsonRpcErrorResponse = (code: number, message: string) => {
44
+ return JSON.stringify({
45
+ error: { code, message },
46
+ id: null,
47
+ jsonrpc: "2.0",
48
+ });
49
+ };
50
+
51
+ // Helper function to handle Response errors and send appropriate HTTP response
52
+ const handleResponseError = (error: unknown, res: http.ServerResponse): boolean => {
53
+ if (error instanceof Response) {
54
+ const fixedHeaders: http.OutgoingHttpHeaders = {};
55
+ error.headers.forEach((value, key) => {
56
+ if (fixedHeaders[key]) {
57
+ if (Array.isArray(fixedHeaders[key])) {
58
+ (fixedHeaders[key] as string[]).push(value);
59
+ } else {
60
+ fixedHeaders[key] = [fixedHeaders[key] as string, value];
61
+ }
62
+ } else {
63
+ fixedHeaders[key] = value;
64
+ }
65
+ });
66
+ res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);
67
+ return true;
68
+ }
69
+ return false;
70
+ };
71
+
72
+ // Helper function to clean up server resources
73
+ const cleanupServer = async <T extends ServerLike>(
74
+ server: T,
75
+ onClose?: (server: T) => Promise<void>
76
+ ) => {
77
+ if (onClose) {
78
+ await onClose(server);
79
+ }
80
+
81
+ try {
82
+ await server.close();
83
+ } catch (error) {
84
+ console.error("[mcp-proxy] error closing server", error);
85
+ }
86
+ };
87
+
42
88
  const handleStreamRequest = async <T extends ServerLike>({
43
89
  activeTransports,
44
90
  createServer,
@@ -49,6 +95,7 @@ const handleStreamRequest = async <T extends ServerLike>({
49
95
  onConnect,
50
96
  req,
51
97
  res,
98
+ stateless,
52
99
  }: {
53
100
  activeTransports: Record<
54
101
  string,
@@ -62,6 +109,7 @@ const handleStreamRequest = async <T extends ServerLike>({
62
109
  onConnect?: (server: T) => Promise<void>;
63
110
  req: http.IncomingMessage;
64
111
  res: http.ServerResponse;
112
+ stateless?: boolean;
65
113
  }) => {
66
114
  if (
67
115
  req.method === "POST" &&
@@ -83,14 +131,7 @@ const handleStreamRequest = async <T extends ServerLike>({
83
131
  if (!activeTransport) {
84
132
  res.setHeader("Content-Type", "application/json");
85
133
  res.writeHead(404).end(
86
- JSON.stringify({
87
- error: {
88
- code: -32001,
89
- message: "Session not found",
90
- },
91
- id: null,
92
- jsonrpc: "2.0",
93
- }),
134
+ createJsonRpcErrorResponse(-32001, "Session not found"),
94
135
  );
95
136
 
96
137
  return true;
@@ -104,52 +145,65 @@ const handleStreamRequest = async <T extends ServerLike>({
104
145
  enableJsonResponse,
105
146
  eventStore: eventStore || new InMemoryEventStore(),
106
147
  onsessioninitialized: (_sessionId) => {
107
- // add only when the id Sesison id is generated
108
- activeTransports[_sessionId] = {
109
- server,
110
- transport,
111
- };
148
+ // add only when the id Session id is generated (skip in stateless mode)
149
+ if (!stateless && _sessionId) {
150
+ activeTransports[_sessionId] = {
151
+ server,
152
+ transport,
153
+ };
154
+ }
112
155
  },
113
- sessionIdGenerator: randomUUID,
156
+ sessionIdGenerator: stateless ? undefined : randomUUID,
114
157
  });
115
158
 
116
159
  // Handle the server close event
117
160
  transport.onclose = async () => {
118
161
  const sid = transport.sessionId;
119
- if (sid && activeTransports[sid]) {
120
- if (onClose) {
121
- await onClose(server);
122
- }
123
-
124
- try {
125
- await server.close();
126
- } catch (error) {
127
- console.error("[mcp-proxy] error closing server", error);
128
- }
129
-
162
+ if (!stateless && sid && activeTransports[sid]) {
163
+ await cleanupServer(server, onClose);
130
164
  delete activeTransports[sid];
165
+ } else if (stateless) {
166
+ // In stateless mode, always call onClose when transport closes
167
+ await cleanupServer(server, onClose);
131
168
  }
132
169
  };
133
170
 
134
171
  try {
135
172
  server = await createServer(req);
136
173
  } catch (error) {
137
- if (error instanceof Response) {
138
- const fixedHeaders: http.OutgoingHttpHeaders = {};
139
- error.headers.forEach((value, key) => {
140
- // If a header appears multiple times, combine them as an array
141
- if (fixedHeaders[key]) {
142
- if (Array.isArray(fixedHeaders[key])) {
143
- (fixedHeaders[key] as string[]).push(value);
144
- } else {
145
- fixedHeaders[key] = [fixedHeaders[key] as string, value];
146
- }
147
- } else {
148
- fixedHeaders[key] = value;
149
- }
150
- });
151
- res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);
174
+ if (handleResponseError(error, res)) {
175
+ return true;
176
+ }
152
177
 
178
+ res.writeHead(500).end("Error creating server");
179
+
180
+ return true;
181
+ }
182
+
183
+ server.connect(transport);
184
+
185
+ if (onConnect) {
186
+ await onConnect(server);
187
+ }
188
+
189
+ await transport.handleRequest(req, res, body);
190
+
191
+ return true;
192
+ } else if (stateless && !sessionId && !isInitializeRequest(body)) {
193
+ // In stateless mode, handle non-initialize requests by creating a new transport
194
+ transport = new StreamableHTTPServerTransport({
195
+ enableJsonResponse,
196
+ eventStore: eventStore || new InMemoryEventStore(),
197
+ onsessioninitialized: () => {
198
+ // No session tracking in stateless mode
199
+ },
200
+ sessionIdGenerator: undefined,
201
+ });
202
+
203
+ try {
204
+ server = await createServer(req);
205
+ } catch (error) {
206
+ if (handleResponseError(error, res)) {
153
207
  return true;
154
208
  }
155
209
 
@@ -172,14 +226,7 @@ const handleStreamRequest = async <T extends ServerLike>({
172
226
  res.setHeader("Content-Type", "application/json");
173
227
 
174
228
  res.writeHead(400).end(
175
- JSON.stringify({
176
- error: {
177
- code: -32000,
178
- message: "Bad Request: No valid session ID provided",
179
- },
180
- id: null,
181
- jsonrpc: "2.0",
182
- }),
229
+ createJsonRpcErrorResponse(-32000, "Bad Request: No valid session ID provided"),
183
230
  );
184
231
 
185
232
  return true;
@@ -195,11 +242,7 @@ const handleStreamRequest = async <T extends ServerLike>({
195
242
  res.setHeader("Content-Type", "application/json");
196
243
 
197
244
  res.writeHead(500).end(
198
- JSON.stringify({
199
- error: { code: -32603, message: "Internal Server Error" },
200
- id: null,
201
- jsonrpc: "2.0",
202
- }),
245
+ createJsonRpcErrorResponse(-32603, "Internal Server Error"),
203
246
  );
204
247
  }
205
248
  return true;
@@ -272,9 +315,7 @@ const handleStreamRequest = async <T extends ServerLike>({
272
315
  try {
273
316
  await activeTransport.transport.handleRequest(req, res);
274
317
 
275
- if (onClose) {
276
- await onClose(activeTransport.server);
277
- }
318
+ await cleanupServer(activeTransport.server, onClose);
278
319
  } catch (error) {
279
320
  console.error("[mcp-proxy] error handling delete request", error);
280
321
 
@@ -315,9 +356,7 @@ const handleSSERequest = async <T extends ServerLike>({
315
356
  try {
316
357
  server = await createServer(req);
317
358
  } catch (error) {
318
- if (error instanceof Response) {
319
- res.writeHead(error.status).end(error.statusText);
320
-
359
+ if (handleResponseError(error, res)) {
321
360
  return true;
322
361
  }
323
362
 
@@ -333,15 +372,9 @@ const handleSSERequest = async <T extends ServerLike>({
333
372
  res.on("close", async () => {
334
373
  closed = true;
335
374
 
336
- try {
337
- await server.close();
338
- } catch (error) {
339
- console.error("[mcp-proxy] error closing server", error);
340
- }
375
+ await cleanupServer(server, onClose);
341
376
 
342
377
  delete activeTransports[transport.sessionId];
343
-
344
- await onClose?.(server);
345
378
  });
346
379
 
347
380
  try {
@@ -405,6 +438,7 @@ export const startHTTPServer = async <T extends ServerLike>({
405
438
  onUnhandledRequest,
406
439
  port,
407
440
  sseEndpoint = "/sse",
441
+ stateless,
408
442
  streamEndpoint = "/mcp",
409
443
  }: {
410
444
  createServer: (request: http.IncomingMessage) => Promise<T>;
@@ -419,6 +453,7 @@ export const startHTTPServer = async <T extends ServerLike>({
419
453
  ) => Promise<void>;
420
454
  port: number;
421
455
  sseEndpoint?: null | string;
456
+ stateless?: boolean;
422
457
  streamEndpoint?: null | string;
423
458
  }): Promise<SSEServer> => {
424
459
  const activeSSETransports: Record<string, SSEServerTransport> = {};
@@ -487,6 +522,7 @@ export const startHTTPServer = async <T extends ServerLike>({
487
522
  onConnect,
488
523
  req,
489
524
  res,
525
+ stateless,
490
526
  }))
491
527
  ) {
492
528
  return;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/InMemoryEventStore.ts","../src/proxyServer.ts","../src/startHTTPServer.ts"],"sourcesContent":["/**\n * This is a copy of the InMemoryEventStore from the typescript-sdk\n * https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/inMemoryEventStore.ts\n */\n\nimport type { EventStore } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Simple in-memory implementation of the EventStore interface for resumability\n * This is primarily intended for examples and testing, not for production use\n * where a persistent storage solution would be more appropriate.\n */\nexport class InMemoryEventStore implements EventStore {\n private events: Map<string, { message: JSONRPCMessage; streamId: string }> =\n new Map();\n\n /**\n * Replays events that occurred after a specific event ID\n * Implements EventStore.replayEventsAfter\n */\n async replayEventsAfter(\n lastEventId: string,\n {\n send,\n }: { send: (eventId: string, message: JSONRPCMessage) => Promise<void> },\n ): Promise<string> {\n if (!lastEventId || !this.events.has(lastEventId)) {\n return \"\";\n }\n\n // Extract the stream ID from the event ID\n const streamId = this.getStreamIdFromEventId(lastEventId);\n\n if (!streamId) {\n return \"\";\n }\n\n let foundLastEvent = false;\n\n // Sort events by eventId for chronological ordering\n const sortedEvents = [...this.events.entries()].sort((a, b) =>\n a[0].localeCompare(b[0]),\n );\n\n for (const [\n eventId,\n { message, streamId: eventStreamId },\n ] of sortedEvents) {\n // Only include events from the same stream\n if (eventStreamId !== streamId) {\n continue;\n }\n\n // Start sending events after we find the lastEventId\n if (eventId === lastEventId) {\n foundLastEvent = true;\n continue;\n }\n\n if (foundLastEvent) {\n await send(eventId, message);\n }\n }\n\n return streamId;\n }\n\n /**\n * Stores an event with a generated event ID\n * Implements EventStore.storeEvent\n */\n async storeEvent(streamId: string, message: JSONRPCMessage): Promise<string> {\n const eventId = this.generateEventId(streamId);\n\n this.events.set(eventId, { message, streamId });\n\n return eventId;\n }\n\n /**\n * Generates a unique event ID for a given stream ID\n */\n private generateEventId(streamId: string): string {\n return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;\n }\n\n /**\n * Extracts the stream ID from an event ID\n */\n private getStreamIdFromEventId(eventId: string): string {\n const parts = eventId.split(\"_\");\n\n return parts.length > 0 ? parts[0] : \"\";\n }\n}\n","import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport {\n CallToolRequestSchema,\n CompleteRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListResourceTemplatesRequestSchema,\n ListToolsRequestSchema,\n LoggingMessageNotificationSchema,\n ReadResourceRequestSchema,\n ResourceUpdatedNotificationSchema,\n ServerCapabilities,\n SubscribeRequestSchema,\n UnsubscribeRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\nexport const proxyServer = async ({\n client,\n server,\n serverCapabilities,\n}: {\n client: Client;\n server: Server;\n serverCapabilities: ServerCapabilities;\n}): Promise<void> => {\n if (serverCapabilities?.logging) {\n server.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n client.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return server.notification(args);\n },\n );\n }\n\n if (serverCapabilities?.prompts) {\n server.setRequestHandler(GetPromptRequestSchema, async (args) => {\n return client.getPrompt(args.params);\n });\n\n server.setRequestHandler(ListPromptsRequestSchema, async (args) => {\n return client.listPrompts(args.params);\n });\n }\n\n if (serverCapabilities?.resources) {\n server.setRequestHandler(ListResourcesRequestSchema, async (args) => {\n return client.listResources(args.params);\n });\n\n server.setRequestHandler(\n ListResourceTemplatesRequestSchema,\n async (args) => {\n return client.listResourceTemplates(args.params);\n },\n );\n\n server.setRequestHandler(ReadResourceRequestSchema, async (args) => {\n return client.readResource(args.params);\n });\n\n if (serverCapabilities?.resources.subscribe) {\n server.setNotificationHandler(\n ResourceUpdatedNotificationSchema,\n async (args) => {\n return client.notification(args);\n },\n );\n\n server.setRequestHandler(SubscribeRequestSchema, async (args) => {\n return client.subscribeResource(args.params);\n });\n\n server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {\n return client.unsubscribeResource(args.params);\n });\n }\n }\n\n if (serverCapabilities?.tools) {\n server.setRequestHandler(CallToolRequestSchema, async (args) => {\n return client.callTool(args.params);\n });\n\n server.setRequestHandler(ListToolsRequestSchema, async (args) => {\n return client.listTools(args.params);\n });\n }\n\n server.setRequestHandler(CompleteRequestSchema, async (args) => {\n return client.complete(args.params);\n });\n};\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport {\n EventStore,\n StreamableHTTPServerTransport,\n} from \"@modelcontextprotocol/sdk/server/streamableHttp.js\";\nimport { isInitializeRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport http from \"http\";\nimport { randomUUID } from \"node:crypto\";\n\nimport { InMemoryEventStore } from \"./InMemoryEventStore.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype ServerLike = {\n close: Server[\"close\"];\n connect: Server[\"connect\"];\n};\n\nconst getBody = (request: http.IncomingMessage) => {\n return new Promise((resolve) => {\n const bodyParts: Buffer[] = [];\n let body: string;\n request\n .on(\"data\", (chunk) => {\n bodyParts.push(chunk);\n })\n .on(\"end\", () => {\n body = Buffer.concat(bodyParts).toString();\n try {\n resolve(JSON.parse(body));\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing body\", error);\n resolve(null);\n }\n });\n });\n};\n\nconst handleStreamRequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n enableJsonResponse,\n endpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n}: {\n activeTransports: Record<\n string,\n { server: T; transport: StreamableHTTPServerTransport }\n >;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n endpoint: string;\n eventStore?: EventStore;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n}) => {\n if (\n req.method === \"POST\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n try {\n const sessionId = Array.isArray(req.headers[\"mcp-session-id\"])\n ? req.headers[\"mcp-session-id\"][0]\n : req.headers[\"mcp-session-id\"];\n\n let transport: StreamableHTTPServerTransport;\n\n let server: T;\n\n const body = await getBody(req);\n\n if (sessionId) {\n const activeTransport = activeTransports[sessionId];\n if (!activeTransport) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.writeHead(404).end(\n JSON.stringify({\n error: {\n code: -32001,\n message: \"Session not found\",\n },\n id: null,\n jsonrpc: \"2.0\",\n }),\n );\n\n return true;\n }\n\n transport = activeTransport.transport;\n server = activeTransport.server;\n } else if (!sessionId && isInitializeRequest(body)) {\n // Create a new transport for the session\n transport = new StreamableHTTPServerTransport({\n enableJsonResponse,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: (_sessionId) => {\n // add only when the id Sesison id is generated\n activeTransports[_sessionId] = {\n server,\n transport,\n };\n },\n sessionIdGenerator: randomUUID,\n });\n\n // Handle the server close event\n transport.onclose = async () => {\n const sid = transport.sessionId;\n if (sid && activeTransports[sid]) {\n if (onClose) {\n await onClose(server);\n }\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"[mcp-proxy] error closing server\", error);\n }\n\n delete activeTransports[sid];\n }\n };\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (error instanceof Response) {\n const fixedHeaders: http.OutgoingHttpHeaders = {};\n error.headers.forEach((value, key) => {\n // If a header appears multiple times, combine them as an array\n if (fixedHeaders[key]) {\n if (Array.isArray(fixedHeaders[key])) {\n (fixedHeaders[key] as string[]).push(value);\n } else {\n fixedHeaders[key] = [fixedHeaders[key] as string, value];\n }\n } else {\n fixedHeaders[key] = value;\n }\n });\n res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);\n\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n server.connect(transport);\n\n if (onConnect) {\n await onConnect(server);\n }\n\n await transport.handleRequest(req, res, body);\n\n return true;\n } else {\n // Error if the server is not created but the request is not an initialize request\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(400).end(\n JSON.stringify({\n error: {\n code: -32000,\n message: \"Bad Request: No valid session ID provided\",\n },\n id: null,\n jsonrpc: \"2.0\",\n }),\n );\n\n return true;\n }\n\n // Handle the request if the server is already created\n await transport.handleRequest(req, res, body);\n\n return true;\n } catch (error) {\n console.error(\"[mcp-proxy] error handling request\", error);\n\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(500).end(\n JSON.stringify({\n error: { code: -32603, message: \"Internal Server Error\" },\n id: null,\n jsonrpc: \"2.0\",\n }),\n );\n }\n return true;\n }\n\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const activeTransport:\n | {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n | undefined = sessionId ? activeTransports[sessionId] : undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n const lastEventId = req.headers[\"last-event-id\"] as string | undefined;\n\n if (lastEventId) {\n console.log(\n `[mcp-proxy] client reconnecting with Last-Event-ID ${lastEventId} for session ID ${sessionId}`,\n );\n } else {\n console.log(\n `[mcp-proxy] establishing new SSE stream for session ID ${sessionId}`,\n );\n }\n\n await activeTransport.transport.handleRequest(req, res);\n\n return true;\n }\n\n if (\n req.method === \"DELETE\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n console.log(\"[mcp-proxy] received delete request\");\n\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"Invalid or missing sessionId\");\n\n return true;\n }\n\n console.log(\"[mcp-proxy] received delete request for session\", sessionId);\n\n const activeTransport = activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return true;\n }\n\n try {\n await activeTransport.transport.handleRequest(req, res);\n\n if (onClose) {\n await onClose(activeTransport.server);\n }\n } catch (error) {\n console.error(\"[mcp-proxy] error handling delete request\", error);\n\n res.writeHead(500).end(\"Error handling delete request\");\n }\n\n return true;\n }\n\n return false;\n};\n\nconst handleSSERequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n endpoint,\n onClose,\n onConnect,\n req,\n res,\n}: {\n activeTransports: Record<string, SSEServerTransport>;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n endpoint: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n}) => {\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n let server: T;\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (error instanceof Response) {\n res.writeHead(error.status).end(error.statusText);\n\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n activeTransports[transport.sessionId] = transport;\n\n let closed = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"[mcp-proxy] error closing server\", error);\n }\n\n delete activeTransports[transport.sessionId];\n\n await onClose?.(server);\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n if (onConnect) {\n await onConnect(server);\n }\n } catch (error) {\n if (!closed) {\n console.error(\"[mcp-proxy] error connecting to server\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return true;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(req.url, \"https://example.com\").searchParams.get(\n \"sessionId\",\n );\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n const activeTransport: SSEServerTransport | undefined =\n activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return true;\n }\n\n return false;\n};\n\nexport const startHTTPServer = async <T extends ServerLike>({\n createServer,\n enableJsonResponse,\n eventStore,\n host = \"::\",\n onClose,\n onConnect,\n onUnhandledRequest,\n port,\n sseEndpoint = \"/sse\",\n streamEndpoint = \"/mcp\",\n}: {\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n eventStore?: EventStore;\n host?: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ) => Promise<void>;\n port: number;\n sseEndpoint?: null | string;\n streamEndpoint?: null | string;\n}): Promise<SSEServer> => {\n const activeSSETransports: Record<string, SSEServerTransport> = {};\n\n const activeStreamTransports: Record<\n string,\n {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n > = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n res.setHeader(\"Access-Control-Expose-Headers\", \"mcp-session-id\");\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing origin\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n return;\n }\n\n if (\n sseEndpoint &&\n (await handleSSERequest({\n activeTransports: activeSSETransports,\n createServer,\n endpoint: sseEndpoint,\n onClose,\n onConnect,\n req,\n res,\n }))\n ) {\n return;\n }\n\n if (\n streamEndpoint &&\n (await handleStreamRequest({\n activeTransports: activeStreamTransports,\n createServer,\n enableJsonResponse,\n endpoint: streamEndpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n }))\n ) {\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, host, () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeSSETransports)) {\n await transport.close();\n }\n\n for (const transport of Object.values(activeStreamTransports)) {\n await transport.transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n"],"mappings":"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","names":[]}