mcp-proxy 5.3.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/bin/mcp-proxy.js +7 -1
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/{chunk-MXVPEZER.js → chunk-UFGZ6L2I.js} +87 -51
- package/dist/chunk-UFGZ6L2I.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/bin/mcp-proxy.ts +6 -0
- package/src/startHTTPServer.test.ts +89 -0
- package/src/startHTTPServer.ts +108 -53
- package/dist/chunk-MXVPEZER.js.map +0 -1
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.
|
package/dist/bin/mcp-proxy.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
InMemoryEventStore,
|
|
4
4
|
proxyServer,
|
|
5
5
|
startHTTPServer
|
|
6
|
-
} from "../chunk-
|
|
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,15 +168,53 @@ 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,
|
|
210
|
+
enableJsonResponse,
|
|
174
211
|
endpoint,
|
|
175
212
|
eventStore,
|
|
176
213
|
onClose,
|
|
177
214
|
onConnect,
|
|
178
215
|
req,
|
|
179
|
-
res
|
|
216
|
+
res,
|
|
217
|
+
stateless
|
|
180
218
|
}) => {
|
|
181
219
|
if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
182
220
|
try {
|
|
@@ -189,14 +227,7 @@ var handleStreamRequest = async ({
|
|
|
189
227
|
if (!activeTransport) {
|
|
190
228
|
res.setHeader("Content-Type", "application/json");
|
|
191
229
|
res.writeHead(404).end(
|
|
192
|
-
|
|
193
|
-
error: {
|
|
194
|
-
code: -32001,
|
|
195
|
-
message: "Session not found"
|
|
196
|
-
},
|
|
197
|
-
id: null,
|
|
198
|
-
jsonrpc: "2.0"
|
|
199
|
-
})
|
|
230
|
+
createJsonRpcErrorResponse(-32001, "Session not found")
|
|
200
231
|
);
|
|
201
232
|
return true;
|
|
202
233
|
}
|
|
@@ -204,34 +235,54 @@ var handleStreamRequest = async ({
|
|
|
204
235
|
server = activeTransport.server;
|
|
205
236
|
} else if (!sessionId && isInitializeRequest(body)) {
|
|
206
237
|
transport = new StreamableHTTPServerTransport({
|
|
238
|
+
enableJsonResponse,
|
|
207
239
|
eventStore: eventStore || new InMemoryEventStore(),
|
|
208
240
|
onsessioninitialized: (_sessionId) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
241
|
+
if (!stateless && _sessionId) {
|
|
242
|
+
activeTransports[_sessionId] = {
|
|
243
|
+
server,
|
|
244
|
+
transport
|
|
245
|
+
};
|
|
246
|
+
}
|
|
213
247
|
},
|
|
214
|
-
sessionIdGenerator: randomUUID
|
|
248
|
+
sessionIdGenerator: stateless ? void 0 : randomUUID
|
|
215
249
|
});
|
|
216
250
|
transport.onclose = async () => {
|
|
217
251
|
const sid = transport.sessionId;
|
|
218
|
-
if (sid && activeTransports[sid]) {
|
|
219
|
-
|
|
220
|
-
await onClose(server);
|
|
221
|
-
}
|
|
222
|
-
try {
|
|
223
|
-
await server.close();
|
|
224
|
-
} catch (error) {
|
|
225
|
-
console.error("[mcp-proxy] error closing server", error);
|
|
226
|
-
}
|
|
252
|
+
if (!stateless && sid && activeTransports[sid]) {
|
|
253
|
+
await cleanupServer(server, onClose);
|
|
227
254
|
delete activeTransports[sid];
|
|
255
|
+
} else if (stateless) {
|
|
256
|
+
await cleanupServer(server, onClose);
|
|
228
257
|
}
|
|
229
258
|
};
|
|
230
259
|
try {
|
|
231
260
|
server = await createServer(req);
|
|
232
261
|
} catch (error) {
|
|
233
|
-
if (error
|
|
234
|
-
|
|
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)) {
|
|
235
286
|
return true;
|
|
236
287
|
}
|
|
237
288
|
res.writeHead(500).end("Error creating server");
|
|
@@ -246,14 +297,7 @@ var handleStreamRequest = async ({
|
|
|
246
297
|
} else {
|
|
247
298
|
res.setHeader("Content-Type", "application/json");
|
|
248
299
|
res.writeHead(400).end(
|
|
249
|
-
|
|
250
|
-
error: {
|
|
251
|
-
code: -32e3,
|
|
252
|
-
message: "Bad Request: No valid session ID provided"
|
|
253
|
-
},
|
|
254
|
-
id: null,
|
|
255
|
-
jsonrpc: "2.0"
|
|
256
|
-
})
|
|
300
|
+
createJsonRpcErrorResponse(-32e3, "Bad Request: No valid session ID provided")
|
|
257
301
|
);
|
|
258
302
|
return true;
|
|
259
303
|
}
|
|
@@ -263,11 +307,7 @@ var handleStreamRequest = async ({
|
|
|
263
307
|
console.error("[mcp-proxy] error handling request", error);
|
|
264
308
|
res.setHeader("Content-Type", "application/json");
|
|
265
309
|
res.writeHead(500).end(
|
|
266
|
-
|
|
267
|
-
error: { code: -32603, message: "Internal Server Error" },
|
|
268
|
-
id: null,
|
|
269
|
-
jsonrpc: "2.0"
|
|
270
|
-
})
|
|
310
|
+
createJsonRpcErrorResponse(-32603, "Internal Server Error")
|
|
271
311
|
);
|
|
272
312
|
}
|
|
273
313
|
return true;
|
|
@@ -311,9 +351,7 @@ var handleStreamRequest = async ({
|
|
|
311
351
|
}
|
|
312
352
|
try {
|
|
313
353
|
await activeTransport.transport.handleRequest(req, res);
|
|
314
|
-
|
|
315
|
-
await onClose(activeTransport.server);
|
|
316
|
-
}
|
|
354
|
+
await cleanupServer(activeTransport.server, onClose);
|
|
317
355
|
} catch (error) {
|
|
318
356
|
console.error("[mcp-proxy] error handling delete request", error);
|
|
319
357
|
res.writeHead(500).end("Error handling delete request");
|
|
@@ -337,8 +375,7 @@ var handleSSERequest = async ({
|
|
|
337
375
|
try {
|
|
338
376
|
server = await createServer(req);
|
|
339
377
|
} catch (error) {
|
|
340
|
-
if (error
|
|
341
|
-
res.writeHead(error.status).end(error.statusText);
|
|
378
|
+
if (handleResponseError(error, res)) {
|
|
342
379
|
return true;
|
|
343
380
|
}
|
|
344
381
|
res.writeHead(500).end("Error creating server");
|
|
@@ -348,13 +385,8 @@ var handleSSERequest = async ({
|
|
|
348
385
|
let closed = false;
|
|
349
386
|
res.on("close", async () => {
|
|
350
387
|
closed = true;
|
|
351
|
-
|
|
352
|
-
await server.close();
|
|
353
|
-
} catch (error) {
|
|
354
|
-
console.error("[mcp-proxy] error closing server", error);
|
|
355
|
-
}
|
|
388
|
+
await cleanupServer(server, onClose);
|
|
356
389
|
delete activeTransports[transport.sessionId];
|
|
357
|
-
await onClose?.(server);
|
|
358
390
|
});
|
|
359
391
|
try {
|
|
360
392
|
await server.connect(transport);
|
|
@@ -394,6 +426,7 @@ var handleSSERequest = async ({
|
|
|
394
426
|
};
|
|
395
427
|
var startHTTPServer = async ({
|
|
396
428
|
createServer,
|
|
429
|
+
enableJsonResponse,
|
|
397
430
|
eventStore,
|
|
398
431
|
host = "::",
|
|
399
432
|
onClose,
|
|
@@ -401,6 +434,7 @@ var startHTTPServer = async ({
|
|
|
401
434
|
onUnhandledRequest,
|
|
402
435
|
port,
|
|
403
436
|
sseEndpoint = "/sse",
|
|
437
|
+
stateless,
|
|
404
438
|
streamEndpoint = "/mcp"
|
|
405
439
|
}) => {
|
|
406
440
|
const activeSSETransports = {};
|
|
@@ -441,12 +475,14 @@ var startHTTPServer = async ({
|
|
|
441
475
|
if (streamEndpoint && await handleStreamRequest({
|
|
442
476
|
activeTransports: activeStreamTransports,
|
|
443
477
|
createServer,
|
|
478
|
+
enableJsonResponse,
|
|
444
479
|
endpoint: streamEndpoint,
|
|
445
480
|
eventStore,
|
|
446
481
|
onClose,
|
|
447
482
|
onConnect,
|
|
448
483
|
req,
|
|
449
|
-
res
|
|
484
|
+
res,
|
|
485
|
+
stateless
|
|
450
486
|
})) {
|
|
451
487
|
return;
|
|
452
488
|
}
|
|
@@ -487,4 +523,4 @@ export {
|
|
|
487
523
|
proxyServer,
|
|
488
524
|
startHTTPServer
|
|
489
525
|
};
|
|
490
|
-
//# sourceMappingURL=chunk-
|
|
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,8 +54,9 @@ type ServerLike = {
|
|
|
54
54
|
close: Server["close"];
|
|
55
55
|
connect: Server["connect"];
|
|
56
56
|
};
|
|
57
|
-
declare const startHTTPServer: <T extends ServerLike>({ createServer, 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
|
+
enableJsonResponse?: boolean;
|
|
59
60
|
eventStore?: EventStore;
|
|
60
61
|
host?: string;
|
|
61
62
|
onClose?: (server: T) => Promise<void>;
|
|
@@ -63,6 +64,7 @@ declare const startHTTPServer: <T extends ServerLike>({ createServer, eventStore
|
|
|
63
64
|
onUnhandledRequest?: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
|
|
64
65
|
port: number;
|
|
65
66
|
sseEndpoint?: null | string;
|
|
67
|
+
stateless?: boolean;
|
|
66
68
|
streamEndpoint?: null | string;
|
|
67
69
|
}) => Promise<SSEServer>;
|
|
68
70
|
|
package/dist/index.js
CHANGED
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -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
|
+
});
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -39,27 +39,77 @@ 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,
|
|
91
|
+
enableJsonResponse,
|
|
45
92
|
endpoint,
|
|
46
93
|
eventStore,
|
|
47
94
|
onClose,
|
|
48
95
|
onConnect,
|
|
49
96
|
req,
|
|
50
97
|
res,
|
|
98
|
+
stateless,
|
|
51
99
|
}: {
|
|
52
100
|
activeTransports: Record<
|
|
53
101
|
string,
|
|
54
102
|
{ server: T; transport: StreamableHTTPServerTransport }
|
|
55
103
|
>;
|
|
56
104
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
105
|
+
enableJsonResponse?: boolean;
|
|
57
106
|
endpoint: string;
|
|
58
107
|
eventStore?: EventStore;
|
|
59
108
|
onClose?: (server: T) => Promise<void>;
|
|
60
109
|
onConnect?: (server: T) => Promise<void>;
|
|
61
110
|
req: http.IncomingMessage;
|
|
62
111
|
res: http.ServerResponse;
|
|
112
|
+
stateless?: boolean;
|
|
63
113
|
}) => {
|
|
64
114
|
if (
|
|
65
115
|
req.method === "POST" &&
|
|
@@ -81,14 +131,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
81
131
|
if (!activeTransport) {
|
|
82
132
|
res.setHeader("Content-Type", "application/json");
|
|
83
133
|
res.writeHead(404).end(
|
|
84
|
-
|
|
85
|
-
error: {
|
|
86
|
-
code: -32001,
|
|
87
|
-
message: "Session not found",
|
|
88
|
-
},
|
|
89
|
-
id: null,
|
|
90
|
-
jsonrpc: "2.0",
|
|
91
|
-
}),
|
|
134
|
+
createJsonRpcErrorResponse(-32001, "Session not found"),
|
|
92
135
|
);
|
|
93
136
|
|
|
94
137
|
return true;
|
|
@@ -99,41 +142,68 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
99
142
|
} else if (!sessionId && isInitializeRequest(body)) {
|
|
100
143
|
// Create a new transport for the session
|
|
101
144
|
transport = new StreamableHTTPServerTransport({
|
|
145
|
+
enableJsonResponse,
|
|
102
146
|
eventStore: eventStore || new InMemoryEventStore(),
|
|
103
147
|
onsessioninitialized: (_sessionId) => {
|
|
104
|
-
// add only when the id
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
155
|
},
|
|
110
|
-
sessionIdGenerator: randomUUID,
|
|
156
|
+
sessionIdGenerator: stateless ? undefined : randomUUID,
|
|
111
157
|
});
|
|
112
158
|
|
|
113
159
|
// Handle the server close event
|
|
114
160
|
transport.onclose = async () => {
|
|
115
161
|
const sid = transport.sessionId;
|
|
116
|
-
if (sid && activeTransports[sid]) {
|
|
117
|
-
|
|
118
|
-
await onClose(server);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
await server.close();
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error("[mcp-proxy] error closing server", error);
|
|
125
|
-
}
|
|
126
|
-
|
|
162
|
+
if (!stateless && sid && activeTransports[sid]) {
|
|
163
|
+
await cleanupServer(server, onClose);
|
|
127
164
|
delete activeTransports[sid];
|
|
165
|
+
} else if (stateless) {
|
|
166
|
+
// In stateless mode, always call onClose when transport closes
|
|
167
|
+
await cleanupServer(server, onClose);
|
|
128
168
|
}
|
|
129
169
|
};
|
|
130
170
|
|
|
131
171
|
try {
|
|
132
172
|
server = await createServer(req);
|
|
133
173
|
} catch (error) {
|
|
134
|
-
if (error
|
|
135
|
-
|
|
174
|
+
if (handleResponseError(error, res)) {
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
res.writeHead(500).end("Error creating server");
|
|
136
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)) {
|
|
137
207
|
return true;
|
|
138
208
|
}
|
|
139
209
|
|
|
@@ -156,14 +226,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
156
226
|
res.setHeader("Content-Type", "application/json");
|
|
157
227
|
|
|
158
228
|
res.writeHead(400).end(
|
|
159
|
-
|
|
160
|
-
error: {
|
|
161
|
-
code: -32000,
|
|
162
|
-
message: "Bad Request: No valid session ID provided",
|
|
163
|
-
},
|
|
164
|
-
id: null,
|
|
165
|
-
jsonrpc: "2.0",
|
|
166
|
-
}),
|
|
229
|
+
createJsonRpcErrorResponse(-32000, "Bad Request: No valid session ID provided"),
|
|
167
230
|
);
|
|
168
231
|
|
|
169
232
|
return true;
|
|
@@ -179,11 +242,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
179
242
|
res.setHeader("Content-Type", "application/json");
|
|
180
243
|
|
|
181
244
|
res.writeHead(500).end(
|
|
182
|
-
|
|
183
|
-
error: { code: -32603, message: "Internal Server Error" },
|
|
184
|
-
id: null,
|
|
185
|
-
jsonrpc: "2.0",
|
|
186
|
-
}),
|
|
245
|
+
createJsonRpcErrorResponse(-32603, "Internal Server Error"),
|
|
187
246
|
);
|
|
188
247
|
}
|
|
189
248
|
return true;
|
|
@@ -256,9 +315,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
256
315
|
try {
|
|
257
316
|
await activeTransport.transport.handleRequest(req, res);
|
|
258
317
|
|
|
259
|
-
|
|
260
|
-
await onClose(activeTransport.server);
|
|
261
|
-
}
|
|
318
|
+
await cleanupServer(activeTransport.server, onClose);
|
|
262
319
|
} catch (error) {
|
|
263
320
|
console.error("[mcp-proxy] error handling delete request", error);
|
|
264
321
|
|
|
@@ -299,9 +356,7 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
299
356
|
try {
|
|
300
357
|
server = await createServer(req);
|
|
301
358
|
} catch (error) {
|
|
302
|
-
if (error
|
|
303
|
-
res.writeHead(error.status).end(error.statusText);
|
|
304
|
-
|
|
359
|
+
if (handleResponseError(error, res)) {
|
|
305
360
|
return true;
|
|
306
361
|
}
|
|
307
362
|
|
|
@@ -317,15 +372,9 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
317
372
|
res.on("close", async () => {
|
|
318
373
|
closed = true;
|
|
319
374
|
|
|
320
|
-
|
|
321
|
-
await server.close();
|
|
322
|
-
} catch (error) {
|
|
323
|
-
console.error("[mcp-proxy] error closing server", error);
|
|
324
|
-
}
|
|
375
|
+
await cleanupServer(server, onClose);
|
|
325
376
|
|
|
326
377
|
delete activeTransports[transport.sessionId];
|
|
327
|
-
|
|
328
|
-
await onClose?.(server);
|
|
329
378
|
});
|
|
330
379
|
|
|
331
380
|
try {
|
|
@@ -381,6 +430,7 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
381
430
|
|
|
382
431
|
export const startHTTPServer = async <T extends ServerLike>({
|
|
383
432
|
createServer,
|
|
433
|
+
enableJsonResponse,
|
|
384
434
|
eventStore,
|
|
385
435
|
host = "::",
|
|
386
436
|
onClose,
|
|
@@ -388,9 +438,11 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
388
438
|
onUnhandledRequest,
|
|
389
439
|
port,
|
|
390
440
|
sseEndpoint = "/sse",
|
|
441
|
+
stateless,
|
|
391
442
|
streamEndpoint = "/mcp",
|
|
392
443
|
}: {
|
|
393
444
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
445
|
+
enableJsonResponse?: boolean;
|
|
394
446
|
eventStore?: EventStore;
|
|
395
447
|
host?: string;
|
|
396
448
|
onClose?: (server: T) => Promise<void>;
|
|
@@ -401,6 +453,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
401
453
|
) => Promise<void>;
|
|
402
454
|
port: number;
|
|
403
455
|
sseEndpoint?: null | string;
|
|
456
|
+
stateless?: boolean;
|
|
404
457
|
streamEndpoint?: null | string;
|
|
405
458
|
}): Promise<SSEServer> => {
|
|
406
459
|
const activeSSETransports: Record<string, SSEServerTransport> = {};
|
|
@@ -462,12 +515,14 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
462
515
|
(await handleStreamRequest({
|
|
463
516
|
activeTransports: activeStreamTransports,
|
|
464
517
|
createServer,
|
|
518
|
+
enableJsonResponse,
|
|
465
519
|
endpoint: streamEndpoint,
|
|
466
520
|
eventStore,
|
|
467
521
|
onClose,
|
|
468
522
|
onConnect,
|
|
469
523
|
req,
|
|
470
524
|
res,
|
|
525
|
+
stateless,
|
|
471
526
|
}))
|
|
472
527
|
) {
|
|
473
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 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 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 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 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 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 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 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 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":[]}
|