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 +46 -0
- package/dist/bin/mcp-proxy.js +7 -1
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/{chunk-UHPMWS2Y.js → chunk-UFGZ6L2I.js} +83 -63
- package/dist/chunk-UFGZ6L2I.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/jsr.json +1 -1
- package/package.json +13 -13
- package/src/bin/mcp-proxy.ts +6 -0
- package/src/startHTTPServer.test.ts +89 -0
- package/src/startHTTPServer.ts +102 -66
- package/dist/chunk-UHPMWS2Y.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,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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
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":";AAaO,IAAM,qBAAN,MAA+C;AAAA,EAC5C,SACN,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAMV,MAAM,kBACJ,aACA;AAAA,IACE;AAAA,EACF,GACiB;AACjB,QAAI,CAAC,eAAe,CAAC,KAAK,OAAO,IAAI,WAAW,GAAG;AACjD,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,KAAK,uBAAuB,WAAW;AAExD,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,IACT;AAEA,QAAI,iBAAiB;AAGrB,UAAM,eAAe,CAAC,GAAG,KAAK,OAAO,QAAQ,CAAC,EAAE;AAAA,MAAK,CAAC,GAAG,MACvD,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;AAAA,IACzB;AAEA,eAAW;AAAA,MACT;AAAA,MACA,EAAE,SAAS,UAAU,cAAc;AAAA,IACrC,KAAK,cAAc;AAEjB,UAAI,kBAAkB,UAAU;AAC9B;AAAA,MACF;AAGA,UAAI,YAAY,aAAa;AAC3B,yBAAiB;AACjB;AAAA,MACF;AAEA,UAAI,gBAAgB;AAClB,cAAM,KAAK,SAAS,OAAO;AAAA,MAC7B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,UAAkB,SAA0C;AAC3E,UAAM,UAAU,KAAK,gBAAgB,QAAQ;AAE7C,SAAK,OAAO,IAAI,SAAS,EAAE,SAAS,SAAS,CAAC;AAE9C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKQ,gBAAgB,UAA0B;AAChD,WAAO,GAAG,QAAQ,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE,CAAC;AAAA,EACjF;AAAA;AAAA;AAAA;AAAA,EAKQ,uBAAuB,SAAyB;AACtD,UAAM,QAAQ,QAAQ,MAAM,GAAG;AAE/B,WAAO,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI;AAAA,EACvC;AACF;;;AC7FA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AAEA,IAAM,cAAc,OAAO;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AACF,MAIqB;AACnB,MAAI,oBAAoB,SAAS;AAC/B,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS;AACd,eAAO,OAAO,aAAa,IAAI;AAAA,MACjC;AAAA,IACF;AACA,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS;AACd,eAAO,OAAO,aAAa,IAAI;AAAA,MACjC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,oBAAoB,SAAS;AAC/B,WAAO,kBAAkB,wBAAwB,OAAO,SAAS;AAC/D,aAAO,OAAO,UAAU,KAAK,MAAM;AAAA,IACrC,CAAC;AAED,WAAO,kBAAkB,0BAA0B,OAAO,SAAS;AACjE,aAAO,OAAO,YAAY,KAAK,MAAM;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,MAAI,oBAAoB,WAAW;AACjC,WAAO,kBAAkB,4BAA4B,OAAO,SAAS;AACnE,aAAO,OAAO,cAAc,KAAK,MAAM;AAAA,IACzC,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,OAAO,SAAS;AACd,eAAO,OAAO,sBAAsB,KAAK,MAAM;AAAA,MACjD;AAAA,IACF;AAEA,WAAO,kBAAkB,2BAA2B,OAAO,SAAS;AAClE,aAAO,OAAO,aAAa,KAAK,MAAM;AAAA,IACxC,CAAC;AAED,QAAI,oBAAoB,UAAU,WAAW;AAC3C,aAAO;AAAA,QACL;AAAA,QACA,OAAO,SAAS;AACd,iBAAO,OAAO,aAAa,IAAI;AAAA,QACjC;AAAA,MACF;AAEA,aAAO,kBAAkB,wBAAwB,OAAO,SAAS;AAC/D,eAAO,OAAO,kBAAkB,KAAK,MAAM;AAAA,MAC7C,CAAC;AAED,aAAO,kBAAkB,0BAA0B,OAAO,SAAS;AACjE,eAAO,OAAO,oBAAoB,KAAK,MAAM;AAAA,MAC/C,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,oBAAoB,OAAO;AAC7B,WAAO,kBAAkB,uBAAuB,OAAO,SAAS;AAC9D,aAAO,OAAO,SAAS,KAAK,MAAM;AAAA,IACpC,CAAC;AAED,WAAO,kBAAkB,wBAAwB,OAAO,SAAS;AAC/D,aAAO,OAAO,UAAU,KAAK,MAAM;AAAA,IACrC,CAAC;AAAA,EACH;AAEA,SAAO,kBAAkB,uBAAuB,OAAO,SAAS;AAC9D,WAAO,OAAO,SAAS,KAAK,MAAM;AAAA,EACpC,CAAC;AACH;;;AClGA,SAAS,0BAA0B;AACnC;AAAA,EAEE;AAAA,OACK;AACP,SAAS,2BAA2B;AACpC,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAa3B,IAAM,UAAU,CAAC,YAAkC;AACjD,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,YAAsB,CAAC;AAC7B,QAAI;AACJ,YACG,GAAG,QAAQ,CAAC,UAAU;AACrB,gBAAU,KAAK,KAAK;AAAA,IACtB,CAAC,EACA,GAAG,OAAO,MAAM;AACf,aAAO,OAAO,OAAO,SAAS,EAAE,SAAS;AACzC,UAAI;AACF,gBAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,MAC1B,SAAS,OAAO;AACd,gBAAQ,MAAM,kCAAkC,KAAK;AACrD,gBAAQ,IAAI;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACL,CAAC;AACH;AAGA,IAAM,6BAA6B,CAAC,MAAc,YAAoB;AACpE,SAAO,KAAK,UAAU;AAAA,IACpB,OAAO,EAAE,MAAM,QAAQ;AAAA,IACvB,IAAI;AAAA,IACJ,SAAS;AAAA,EACX,CAAC;AACH;AAGA,IAAM,sBAAsB,CAAC,OAAgB,QAAsC;AACjF,MAAI,iBAAiB,UAAU;AAC7B,UAAM,eAAyC,CAAC;AAChD,UAAM,QAAQ,QAAQ,CAAC,OAAO,QAAQ;AACpC,UAAI,aAAa,GAAG,GAAG;AACrB,YAAI,MAAM,QAAQ,aAAa,GAAG,CAAC,GAAG;AACpC,UAAC,aAAa,GAAG,EAAe,KAAK,KAAK;AAAA,QAC5C,OAAO;AACL,uBAAa,GAAG,IAAI,CAAC,aAAa,GAAG,GAAa,KAAK;AAAA,QACzD;AAAA,MACF,OAAO;AACL,qBAAa,GAAG,IAAI;AAAA,MACtB;AAAA,IACF,CAAC;AACD,QAAI,UAAU,MAAM,QAAQ,MAAM,YAAY,YAAY,EAAE,IAAI,MAAM,UAAU;AAChF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,IAAM,gBAAgB,OACpB,QACA,YACG;AACH,MAAI,SAAS;AACX,UAAM,QAAQ,MAAM;AAAA,EACtB;AAEA,MAAI;AACF,UAAM,OAAO,MAAM;AAAA,EACrB,SAAS,OAAO;AACd,YAAQ,MAAM,oCAAoC,KAAK;AAAA,EACzD;AACF;AAEA,IAAM,sBAAsB,OAA6B;AAAA,EACvD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAcM;AACJ,MACE,IAAI,WAAW,UACf,IAAI,IAAI,IAAI,KAAM,kBAAkB,EAAE,aAAa,UACnD;AACA,QAAI;AACF,YAAM,YAAY,MAAM,QAAQ,IAAI,QAAQ,gBAAgB,CAAC,IACzD,IAAI,QAAQ,gBAAgB,EAAE,CAAC,IAC/B,IAAI,QAAQ,gBAAgB;AAEhC,UAAI;AAEJ,UAAI;AAEJ,YAAM,OAAO,MAAM,QAAQ,GAAG;AAE9B,UAAI,WAAW;AACb,cAAM,kBAAkB,iBAAiB,SAAS;AAClD,YAAI,CAAC,iBAAiB;AACpB,cAAI,UAAU,gBAAgB,kBAAkB;AAChD,cAAI,UAAU,GAAG,EAAE;AAAA,YACjB,2BAA2B,QAAQ,mBAAmB;AAAA,UACxD;AAEA,iBAAO;AAAA,QACT;AAEA,oBAAY,gBAAgB;AAC5B,iBAAS,gBAAgB;AAAA,MAC3B,WAAW,CAAC,aAAa,oBAAoB,IAAI,GAAG;AAElD,oBAAY,IAAI,8BAA8B;AAAA,UAC5C;AAAA,UACA,YAAY,cAAc,IAAI,mBAAmB;AAAA,UACjD,sBAAsB,CAAC,eAAe;AAEpC,gBAAI,CAAC,aAAa,YAAY;AAC5B,+BAAiB,UAAU,IAAI;AAAA,gBAC7B;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,UACA,oBAAoB,YAAY,SAAY;AAAA,QAC9C,CAAC;AAGD,kBAAU,UAAU,YAAY;AAC9B,gBAAM,MAAM,UAAU;AACtB,cAAI,CAAC,aAAa,OAAO,iBAAiB,GAAG,GAAG;AAC9C,kBAAM,cAAc,QAAQ,OAAO;AACnC,mBAAO,iBAAiB,GAAG;AAAA,UAC7B,WAAW,WAAW;AAEpB,kBAAM,cAAc,QAAQ,OAAO;AAAA,UACrC;AAAA,QACF;AAEA,YAAI;AACF,mBAAS,MAAM,aAAa,GAAG;AAAA,QACjC,SAAS,OAAO;AACd,cAAI,oBAAoB,OAAO,GAAG,GAAG;AACnC,mBAAO;AAAA,UACT;AAEA,cAAI,UAAU,GAAG,EAAE,IAAI,uBAAuB;AAE9C,iBAAO;AAAA,QACT;AAEA,eAAO,QAAQ,SAAS;AAExB,YAAI,WAAW;AACb,gBAAM,UAAU,MAAM;AAAA,QACxB;AAEA,cAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAE5C,eAAO;AAAA,MACT,WAAW,aAAa,CAAC,aAAa,CAAC,oBAAoB,IAAI,GAAG;AAEhE,oBAAY,IAAI,8BAA8B;AAAA,UAC5C;AAAA,UACA,YAAY,cAAc,IAAI,mBAAmB;AAAA,UACjD,sBAAsB,MAAM;AAAA,UAE5B;AAAA,UACA,oBAAoB;AAAA,QACtB,CAAC;AAED,YAAI;AACF,mBAAS,MAAM,aAAa,GAAG;AAAA,QACjC,SAAS,OAAO;AACd,cAAI,oBAAoB,OAAO,GAAG,GAAG;AACnC,mBAAO;AAAA,UACT;AAEA,cAAI,UAAU,GAAG,EAAE,IAAI,uBAAuB;AAE9C,iBAAO;AAAA,QACT;AAEA,eAAO,QAAQ,SAAS;AAExB,YAAI,WAAW;AACb,gBAAM,UAAU,MAAM;AAAA,QACxB;AAEA,cAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAE5C,eAAO;AAAA,MACT,OAAO;AAEL,YAAI,UAAU,gBAAgB,kBAAkB;AAEhD,YAAI,UAAU,GAAG,EAAE;AAAA,UACjB,2BAA2B,OAAQ,2CAA2C;AAAA,QAChF;AAEA,eAAO;AAAA,MACT;AAGA,YAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAE5C,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,sCAAsC,KAAK;AAEzD,UAAI,UAAU,gBAAgB,kBAAkB;AAEhD,UAAI,UAAU,GAAG,EAAE;AAAA,QACjB,2BAA2B,QAAQ,uBAAuB;AAAA,MAC5D;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,MACE,IAAI,WAAW,SACf,IAAI,IAAI,IAAI,KAAM,kBAAkB,EAAE,aAAa,UACnD;AACA,UAAM,YAAY,IAAI,QAAQ,gBAAgB;AAC9C,UAAM,kBAKU,YAAY,iBAAiB,SAAS,IAAI;AAE1D,QAAI,CAAC,WAAW;AACd,UAAI,UAAU,GAAG,EAAE,IAAI,cAAc;AAErC,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,iBAAiB;AACpB,UAAI,UAAU,GAAG,EAAE,IAAI,qBAAqB;AAE5C,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,IAAI,QAAQ,eAAe;AAE/C,QAAI,aAAa;AACf,cAAQ;AAAA,QACN,sDAAsD,WAAW,mBAAmB,SAAS;AAAA,MAC/F;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,0DAA0D,SAAS;AAAA,MACrE;AAAA,IACF;AAEA,UAAM,gBAAgB,UAAU,cAAc,KAAK,GAAG;AAEtD,WAAO;AAAA,EACT;AAEA,MACE,IAAI,WAAW,YACf,IAAI,IAAI,IAAI,KAAM,kBAAkB,EAAE,aAAa,UACnD;AACA,YAAQ,IAAI,qCAAqC;AAEjD,UAAM,YAAY,IAAI,QAAQ,gBAAgB;AAE9C,QAAI,CAAC,WAAW;AACd,UAAI,UAAU,GAAG,EAAE,IAAI,8BAA8B;AAErD,aAAO;AAAA,IACT;AAEA,YAAQ,IAAI,mDAAmD,SAAS;AAExE,UAAM,kBAAkB,iBAAiB,SAAS;AAElD,QAAI,CAAC,iBAAiB;AACpB,UAAI,UAAU,GAAG,EAAE,IAAI,qBAAqB;AAC5C,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,gBAAgB,UAAU,cAAc,KAAK,GAAG;AAEtD,YAAM,cAAc,gBAAgB,QAAQ,OAAO;AAAA,IACrD,SAAS,OAAO;AACd,cAAQ,MAAM,6CAA6C,KAAK;AAEhE,UAAI,UAAU,GAAG,EAAE,IAAI,+BAA+B;AAAA,IACxD;AAEA,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEA,IAAM,mBAAmB,OAA6B;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAQM;AACJ,MACE,IAAI,WAAW,SACf,IAAI,IAAI,IAAI,KAAM,kBAAkB,EAAE,aAAa,UACnD;AACA,UAAM,YAAY,IAAI,mBAAmB,aAAa,GAAG;AAEzD,QAAI;AAEJ,QAAI;AACF,eAAS,MAAM,aAAa,GAAG;AAAA,IACjC,SAAS,OAAO;AACd,UAAI,oBAAoB,OAAO,GAAG,GAAG;AACnC,eAAO;AAAA,MACT;AAEA,UAAI,UAAU,GAAG,EAAE,IAAI,uBAAuB;AAE9C,aAAO;AAAA,IACT;AAEA,qBAAiB,UAAU,SAAS,IAAI;AAExC,QAAI,SAAS;AAEb,QAAI,GAAG,SAAS,YAAY;AAC1B,eAAS;AAET,YAAM,cAAc,QAAQ,OAAO;AAEnC,aAAO,iBAAiB,UAAU,SAAS;AAAA,IAC7C,CAAC;AAED,QAAI;AACF,YAAM,OAAO,QAAQ,SAAS;AAE9B,YAAM,UAAU,KAAK;AAAA,QACnB,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,QAAQ,EAAE,SAAS,6BAA6B;AAAA,MAClD,CAAC;AAED,UAAI,WAAW;AACb,cAAM,UAAU,MAAM;AAAA,MACxB;AAAA,IACF,SAAS,OAAO;AACd,UAAI,CAAC,QAAQ;AACX,gBAAQ,MAAM,0CAA0C,KAAK;AAE7D,YAAI,UAAU,GAAG,EAAE,IAAI,4BAA4B;AAAA,MACrD;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,WAAW,UAAU,IAAI,KAAK,WAAW,WAAW,GAAG;AAC7D,UAAM,YAAY,IAAI,IAAI,IAAI,KAAK,qBAAqB,EAAE,aAAa;AAAA,MACrE;AAAA,IACF;AAEA,QAAI,CAAC,WAAW;AACd,UAAI,UAAU,GAAG,EAAE,IAAI,cAAc;AAErC,aAAO;AAAA,IACT;AAEA,UAAM,kBACJ,iBAAiB,SAAS;AAE5B,QAAI,CAAC,iBAAiB;AACpB,UAAI,UAAU,GAAG,EAAE,IAAI,qBAAqB;AAE5C,aAAO;AAAA,IACT;AAEA,UAAM,gBAAgB,kBAAkB,KAAK,GAAG;AAEhD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,IAAM,kBAAkB,OAA6B;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,iBAAiB;AACnB,MAe0B;AACxB,QAAM,sBAA0D,CAAC;AAEjE,QAAM,yBAMF,CAAC;AAKL,QAAM,aAAa,KAAK,aAAa,OAAO,KAAK,QAAQ;AACvD,QAAI,IAAI,QAAQ,QAAQ;AACtB,UAAI;AACF,cAAM,SAAS,IAAI,IAAI,IAAI,QAAQ,MAAM;AAEzC,YAAI,UAAU,+BAA+B,OAAO,MAAM;AAC1D,YAAI,UAAU,oCAAoC,MAAM;AACxD,YAAI,UAAU,gCAAgC,oBAAoB;AAClE,YAAI,UAAU,gCAAgC,GAAG;AACjD,YAAI,UAAU,iCAAiC,gBAAgB;AAAA,MACjE,SAAS,OAAO;AACd,gBAAQ,MAAM,oCAAoC,KAAK;AAAA,MACzD;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,SAAS;AAC/C,UAAI,UAAU,GAAG,EAAE,IAAI,MAAM;AAC7B;AAAA,IACF;AAEA,QACE,eACC,MAAM,iBAAiB;AAAA,MACtB,kBAAkB;AAAA,MAClB;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,GACD;AACA;AAAA,IACF;AAEA,QACE,kBACC,MAAM,oBAAoB;AAAA,MACzB,kBAAkB;AAAA,MAClB;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC,GACD;AACA;AAAA,IACF;AAEA,QAAI,oBAAoB;AACtB,YAAM,mBAAmB,KAAK,GAAG;AAAA,IACnC,OAAO;AACL,UAAI,UAAU,GAAG,EAAE,IAAI;AAAA,IACzB;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAQ,CAAC,YAAY;AAC7B,eAAW,OAAO,MAAM,MAAM,MAAM;AAClC,cAAQ,MAAS;AAAA,IACnB,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL,OAAO,YAAY;AACjB,iBAAW,aAAa,OAAO,OAAO,mBAAmB,GAAG;AAC1D,cAAM,UAAU,MAAM;AAAA,MACxB;AAEA,iBAAW,aAAa,OAAO,OAAO,sBAAsB,GAAG;AAC7D,cAAM,UAAU,UAAU,MAAM;AAAA,MAClC;AAEA,aAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,mBAAW,MAAM,CAAC,UAAU;AAC1B,cAAI,OAAO;AACT,mBAAO,KAAK;AAEZ;AAAA,UACF;AAEA,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AACF;","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
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-proxy",
|
|
3
|
-
"version": "5.
|
|
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.
|
|
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.
|
|
47
|
-
"@sebbo2002/semantic-release-jsr": "^3.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
|
|
50
|
+
"@types/node": "^24.2.0",
|
|
51
51
|
"@types/yargs": "^17.0.33",
|
|
52
|
-
"eslint": "^9.
|
|
53
|
-
"eslint-config-prettier": "^10.1.
|
|
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.
|
|
57
|
-
"jiti": "^2.
|
|
58
|
-
"jsr": "^0.13.
|
|
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.
|
|
60
|
+
"semantic-release": "^24.2.7",
|
|
61
61
|
"tsup": "^8.5.0",
|
|
62
62
|
"tsx": "^4.20.3",
|
|
63
|
-
"typescript": "^5.
|
|
64
|
-
"typescript-eslint": "^8.
|
|
63
|
+
"typescript": "^5.9.2",
|
|
64
|
+
"typescript-eslint": "^8.39.0",
|
|
65
65
|
"vitest": "^3.2.4"
|
|
66
66
|
},
|
|
67
67
|
"tsup": {
|
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,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
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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":[]}
|