mcp-proxy 5.5.2 → 5.5.4
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/dist/bin/mcp-proxy.js +56 -34
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/{chunk-UFGZ6L2I.js → chunk-JCZNH6HS.js} +17 -8
- package/dist/chunk-JCZNH6HS.js.map +1 -0
- package/dist/index.js +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/JSONFilterTransform.test.ts +106 -0
- package/src/StdioClientTransport.ts +55 -33
- package/src/bin/mcp-proxy.ts +8 -4
- package/src/startHTTPServer.ts +38 -12
- package/dist/chunk-UFGZ6L2I.js.map +0 -1
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-JCZNH6HS.js";
|
|
7
7
|
|
|
8
8
|
// src/bin/mcp-proxy.ts
|
|
9
9
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
serializeMessage
|
|
21
21
|
} from "@modelcontextprotocol/sdk/shared/stdio.js";
|
|
22
22
|
import { spawn } from "child_process";
|
|
23
|
+
import { PassThrough } from "stream";
|
|
23
24
|
|
|
24
25
|
// src/JSONFilterTransform.ts
|
|
25
26
|
import { Transform } from "stream";
|
|
@@ -65,41 +66,58 @@ var StdioClientTransport = class {
|
|
|
65
66
|
onclose;
|
|
66
67
|
onerror;
|
|
67
68
|
onmessage;
|
|
69
|
+
/**
|
|
70
|
+
* The child process pid spawned by this transport.
|
|
71
|
+
*
|
|
72
|
+
* This is only available after the transport has been started.
|
|
73
|
+
*/
|
|
74
|
+
get pid() {
|
|
75
|
+
return this._process?.pid ?? null;
|
|
76
|
+
}
|
|
68
77
|
/**
|
|
69
78
|
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
|
|
70
79
|
*
|
|
71
|
-
*
|
|
80
|
+
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
|
|
81
|
+
* attach listeners before the start method is invoked. This prevents loss of any early
|
|
82
|
+
* error output emitted by the child process.
|
|
72
83
|
*/
|
|
73
84
|
get stderr() {
|
|
74
|
-
|
|
85
|
+
if (this._stderrStream) {
|
|
86
|
+
return this._stderrStream;
|
|
87
|
+
}
|
|
88
|
+
return this._process?.stderr ?? null;
|
|
75
89
|
}
|
|
76
|
-
|
|
90
|
+
_abortController = new AbortController();
|
|
91
|
+
_process;
|
|
92
|
+
_readBuffer = new ReadBuffer();
|
|
93
|
+
_serverParams;
|
|
94
|
+
_stderrStream = null;
|
|
77
95
|
onEvent;
|
|
78
|
-
process;
|
|
79
|
-
readBuffer = new ReadBuffer();
|
|
80
|
-
serverParams;
|
|
81
96
|
constructor(server) {
|
|
82
|
-
this.
|
|
97
|
+
this._serverParams = server;
|
|
98
|
+
if (server.stderr === "pipe" || server.stderr === "overlapped") {
|
|
99
|
+
this._stderrStream = new PassThrough();
|
|
100
|
+
}
|
|
83
101
|
this.onEvent = server.onEvent;
|
|
84
102
|
}
|
|
85
103
|
async close() {
|
|
86
104
|
this.onEvent?.({
|
|
87
105
|
type: "close"
|
|
88
106
|
});
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
91
|
-
this.
|
|
107
|
+
this._abortController.abort();
|
|
108
|
+
this._process = void 0;
|
|
109
|
+
this._readBuffer.clear();
|
|
92
110
|
}
|
|
93
111
|
send(message) {
|
|
94
112
|
return new Promise((resolve) => {
|
|
95
|
-
if (!this.
|
|
113
|
+
if (!this._process?.stdin) {
|
|
96
114
|
throw new Error("Not connected");
|
|
97
115
|
}
|
|
98
116
|
const json = serializeMessage(message);
|
|
99
|
-
if (this.
|
|
117
|
+
if (this._process.stdin.write(json)) {
|
|
100
118
|
resolve();
|
|
101
119
|
} else {
|
|
102
|
-
this.
|
|
120
|
+
this._process.stdin.once("drain", resolve);
|
|
103
121
|
}
|
|
104
122
|
});
|
|
105
123
|
}
|
|
@@ -107,24 +125,24 @@ var StdioClientTransport = class {
|
|
|
107
125
|
* Starts the server process and prepares to communicate with it.
|
|
108
126
|
*/
|
|
109
127
|
async start() {
|
|
110
|
-
if (this.
|
|
128
|
+
if (this._process) {
|
|
111
129
|
throw new Error(
|
|
112
130
|
"StdioClientTransport already started! If using Client class, note that connect() calls start() automatically."
|
|
113
131
|
);
|
|
114
132
|
}
|
|
115
133
|
return new Promise((resolve, reject) => {
|
|
116
|
-
this.
|
|
117
|
-
this.
|
|
118
|
-
this.
|
|
134
|
+
this._process = spawn(
|
|
135
|
+
this._serverParams.command,
|
|
136
|
+
this._serverParams.args ?? [],
|
|
119
137
|
{
|
|
120
|
-
cwd: this.
|
|
121
|
-
env: this.
|
|
122
|
-
shell: this.
|
|
123
|
-
signal: this.
|
|
124
|
-
stdio: ["pipe", "pipe", this.
|
|
138
|
+
cwd: this._serverParams.cwd,
|
|
139
|
+
env: this._serverParams.env,
|
|
140
|
+
shell: this._serverParams.shell ?? false,
|
|
141
|
+
signal: this._abortController.signal,
|
|
142
|
+
stdio: ["pipe", "pipe", this._serverParams.stderr ?? "inherit"]
|
|
125
143
|
}
|
|
126
144
|
);
|
|
127
|
-
this.
|
|
145
|
+
this._process.on("error", (error) => {
|
|
128
146
|
if (error.name === "AbortError") {
|
|
129
147
|
this.onclose?.();
|
|
130
148
|
return;
|
|
@@ -132,17 +150,17 @@ var StdioClientTransport = class {
|
|
|
132
150
|
reject(error);
|
|
133
151
|
this.onerror?.(error);
|
|
134
152
|
});
|
|
135
|
-
this.
|
|
153
|
+
this._process.on("spawn", () => {
|
|
136
154
|
resolve();
|
|
137
155
|
});
|
|
138
|
-
this.
|
|
156
|
+
this._process.on("close", (_code) => {
|
|
139
157
|
this.onEvent?.({
|
|
140
158
|
type: "close"
|
|
141
159
|
});
|
|
142
|
-
this.
|
|
160
|
+
this._process = void 0;
|
|
143
161
|
this.onclose?.();
|
|
144
162
|
});
|
|
145
|
-
this.
|
|
163
|
+
this._process.stdin?.on("error", (error) => {
|
|
146
164
|
this.onEvent?.({
|
|
147
165
|
error,
|
|
148
166
|
type: "error"
|
|
@@ -150,13 +168,13 @@ var StdioClientTransport = class {
|
|
|
150
168
|
this.onerror?.(error);
|
|
151
169
|
});
|
|
152
170
|
const jsonFilterTransform = new JSONFilterTransform();
|
|
153
|
-
this.
|
|
171
|
+
this._process.stdout?.pipe(jsonFilterTransform);
|
|
154
172
|
jsonFilterTransform.on("data", (chunk) => {
|
|
155
173
|
this.onEvent?.({
|
|
156
174
|
chunk: chunk.toString(),
|
|
157
175
|
type: "data"
|
|
158
176
|
});
|
|
159
|
-
this.
|
|
177
|
+
this._readBuffer.append(chunk);
|
|
160
178
|
this.processReadBuffer();
|
|
161
179
|
});
|
|
162
180
|
jsonFilterTransform.on("error", (error) => {
|
|
@@ -166,12 +184,15 @@ var StdioClientTransport = class {
|
|
|
166
184
|
});
|
|
167
185
|
this.onerror?.(error);
|
|
168
186
|
});
|
|
187
|
+
if (this._stderrStream && this._process.stderr) {
|
|
188
|
+
this._process.stderr.pipe(this._stderrStream);
|
|
189
|
+
}
|
|
169
190
|
});
|
|
170
191
|
}
|
|
171
192
|
processReadBuffer() {
|
|
172
193
|
while (true) {
|
|
173
194
|
try {
|
|
174
|
-
const message = this.
|
|
195
|
+
const message = this._readBuffer.readMessage();
|
|
175
196
|
if (message === null) {
|
|
176
197
|
break;
|
|
177
198
|
}
|
|
@@ -273,7 +294,8 @@ var connect = async (client) => {
|
|
|
273
294
|
}
|
|
274
295
|
},
|
|
275
296
|
shell: argv.shell,
|
|
276
|
-
stderr
|
|
297
|
+
// We want to passthrough stderr from the MCP server to enable better debugging
|
|
298
|
+
stderr: "inherit"
|
|
277
299
|
});
|
|
278
300
|
await client.connect(transport);
|
|
279
301
|
};
|
|
@@ -328,8 +350,8 @@ var createGracefulShutdown = ({
|
|
|
328
350
|
process.exit(1);
|
|
329
351
|
}, timeout).unref();
|
|
330
352
|
};
|
|
331
|
-
process.
|
|
332
|
-
process.
|
|
353
|
+
process.once("SIGTERM", gracefulShutdown);
|
|
354
|
+
process.once("SIGINT", gracefulShutdown);
|
|
333
355
|
return () => {
|
|
334
356
|
server.close();
|
|
335
357
|
};
|
|
@@ -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 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"]}
|
|
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:\n \"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\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 // We want to passthrough stderr from the MCP server to enable better debugging\n stderr: \"inherit\",\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.once(\"SIGTERM\", gracefulShutdown);\n process.once(\"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 // We give an extra second for logs to flush\n setTimeout(() => {\n process.exit(1);\n }, 1000);\n }\n};\n\nawait main();\n","/**\n * Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/a1608a6513d18eb965266286904760f830de96fe/src/client/stdio.ts\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 { PassThrough, 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 child process pid spawned by this transport.\n *\n * This is only available after the transport has been started.\n */\n get pid(): null | number {\n return this._process?.pid ?? null;\n }\n /**\n * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to \"pipe\" or \"overlapped\".\n *\n * If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to\n * attach listeners before the start method is invoked. This prevents loss of any early\n * error output emitted by the child process.\n */\n get stderr(): null | Stream {\n if (this._stderrStream) {\n return this._stderrStream;\n }\n\n return this._process?.stderr ?? null;\n }\n private _abortController: AbortController = new AbortController();\n private _process?: ChildProcess;\n private _readBuffer: ReadBuffer = new ReadBuffer();\n private _serverParams: StdioServerParameters;\n private _stderrStream: null | PassThrough = null;\n\n private onEvent?: (event: TransportEvent) => void;\n\n constructor(server: StdioServerParameters) {\n this._serverParams = server;\n if (server.stderr === \"pipe\" || server.stderr === \"overlapped\") {\n this._stderrStream = new PassThrough();\n }\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 // Expected when close() is called.\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 if (this._stderrStream && this._process.stderr) {\n this._process.stderr.pipe(this._stderrStream);\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"]}
|
|
@@ -226,9 +226,7 @@ var handleStreamRequest = async ({
|
|
|
226
226
|
const activeTransport = activeTransports[sessionId];
|
|
227
227
|
if (!activeTransport) {
|
|
228
228
|
res.setHeader("Content-Type", "application/json");
|
|
229
|
-
res.writeHead(404).end(
|
|
230
|
-
createJsonRpcErrorResponse(-32001, "Session not found")
|
|
231
|
-
);
|
|
229
|
+
res.writeHead(404).end(createJsonRpcErrorResponse(-32001, "Session not found"));
|
|
232
230
|
return true;
|
|
233
231
|
}
|
|
234
232
|
transport = activeTransport.transport;
|
|
@@ -247,8 +245,13 @@ var handleStreamRequest = async ({
|
|
|
247
245
|
},
|
|
248
246
|
sessionIdGenerator: stateless ? void 0 : randomUUID
|
|
249
247
|
});
|
|
248
|
+
let isCleaningUp = false;
|
|
250
249
|
transport.onclose = async () => {
|
|
251
250
|
const sid = transport.sessionId;
|
|
251
|
+
if (isCleaningUp) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
isCleaningUp = true;
|
|
252
255
|
if (!stateless && sid && activeTransports[sid]) {
|
|
253
256
|
await cleanupServer(server, onClose);
|
|
254
257
|
delete activeTransports[sid];
|
|
@@ -297,7 +300,10 @@ var handleStreamRequest = async ({
|
|
|
297
300
|
} else {
|
|
298
301
|
res.setHeader("Content-Type", "application/json");
|
|
299
302
|
res.writeHead(400).end(
|
|
300
|
-
createJsonRpcErrorResponse(
|
|
303
|
+
createJsonRpcErrorResponse(
|
|
304
|
+
-32e3,
|
|
305
|
+
"Bad Request: No valid session ID provided"
|
|
306
|
+
)
|
|
301
307
|
);
|
|
302
308
|
return true;
|
|
303
309
|
}
|
|
@@ -306,9 +312,7 @@ var handleStreamRequest = async ({
|
|
|
306
312
|
} catch (error) {
|
|
307
313
|
console.error("[mcp-proxy] error handling request", error);
|
|
308
314
|
res.setHeader("Content-Type", "application/json");
|
|
309
|
-
res.writeHead(500).end(
|
|
310
|
-
createJsonRpcErrorResponse(-32603, "Internal Server Error")
|
|
311
|
-
);
|
|
315
|
+
res.writeHead(500).end(createJsonRpcErrorResponse(-32603, "Internal Server Error"));
|
|
312
316
|
}
|
|
313
317
|
return true;
|
|
314
318
|
}
|
|
@@ -383,8 +387,13 @@ var handleSSERequest = async ({
|
|
|
383
387
|
}
|
|
384
388
|
activeTransports[transport.sessionId] = transport;
|
|
385
389
|
let closed = false;
|
|
390
|
+
let isCleaningUp = false;
|
|
386
391
|
res.on("close", async () => {
|
|
387
392
|
closed = true;
|
|
393
|
+
if (isCleaningUp) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
isCleaningUp = true;
|
|
388
397
|
await cleanupServer(server, onClose);
|
|
389
398
|
delete activeTransports[transport.sessionId];
|
|
390
399
|
});
|
|
@@ -523,4 +532,4 @@ export {
|
|
|
523
532
|
proxyServer,
|
|
524
533
|
startHTTPServer
|
|
525
534
|
};
|
|
526
|
-
//# sourceMappingURL=chunk-
|
|
535
|
+
//# sourceMappingURL=chunk-JCZNH6HS.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 = (\n error: unknown,\n res: http.ServerResponse,\n): 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\n .writeHead(error.status, error.statusText, fixedHeaders)\n .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\n .writeHead(404)\n .end(createJsonRpcErrorResponse(-32001, \"Session not found\"));\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 let isCleaningUp = false;\n\n transport.onclose = async () => {\n const sid = transport.sessionId;\n\n if (isCleaningUp) {\n return;\n }\n\n isCleaningUp = true;\n\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\n .writeHead(400)\n .end(\n createJsonRpcErrorResponse(\n -32000,\n \"Bad Request: No valid session ID provided\",\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\n .writeHead(500)\n .end(createJsonRpcErrorResponse(-32603, \"Internal Server Error\"));\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 let isCleaningUp = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n // Prevent recursive cleanup\n if (isCleaningUp) {\n return;\n }\n\n isCleaningUp = true;\n await cleanupServer(server, onClose);\n\n delete activeTransports[transport.sessionId];\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n if (onConnect) {\n await onConnect(server);\n }\n } catch (error) {\n if (!closed) {\n console.error(\"[mcp-proxy] error connecting to server\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return true;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(req.url, \"https://example.com\").searchParams.get(\n \"sessionId\",\n );\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n const activeTransport: SSEServerTransport | undefined =\n activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return true;\n }\n\n return false;\n};\n\nexport const startHTTPServer = async <T extends ServerLike>({\n createServer,\n enableJsonResponse,\n eventStore,\n host = \"::\",\n onClose,\n onConnect,\n onUnhandledRequest,\n port,\n sseEndpoint = \"/sse\",\n stateless,\n streamEndpoint = \"/mcp\",\n}: {\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n eventStore?: EventStore;\n host?: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ) => Promise<void>;\n port: number;\n sseEndpoint?: null | string;\n stateless?: boolean;\n streamEndpoint?: null | string;\n}): Promise<SSEServer> => {\n const activeSSETransports: Record<string, SSEServerTransport> = {};\n\n const activeStreamTransports: Record<\n string,\n {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n > = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n res.setHeader(\"Access-Control-Expose-Headers\", \"mcp-session-id\");\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing origin\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n return;\n }\n\n if (\n sseEndpoint &&\n (await handleSSERequest({\n activeTransports: activeSSETransports,\n createServer,\n endpoint: sseEndpoint,\n onClose,\n onConnect,\n req,\n res,\n }))\n ) {\n return;\n }\n\n if (\n streamEndpoint &&\n (await handleStreamRequest({\n activeTransports: activeStreamTransports,\n createServer,\n enableJsonResponse,\n endpoint: streamEndpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n stateless,\n }))\n ) {\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, host, () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeSSETransports)) {\n await transport.close();\n }\n\n for (const transport of Object.values(activeStreamTransports)) {\n await transport.transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
import { pipeline } from "node:stream/promises";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { JSONFilterTransform } from "./JSONFilterTransform.js";
|
|
6
|
+
|
|
7
|
+
describe("JSONFilterTransform", () => {
|
|
8
|
+
it("filters out non-JSON lines and passes through JSON lines", async () => {
|
|
9
|
+
const input = [
|
|
10
|
+
'{"type": "request", "id": 1}',
|
|
11
|
+
"This is not JSON",
|
|
12
|
+
'{"type": "response", "id": 2}',
|
|
13
|
+
"Another non-JSON line",
|
|
14
|
+
' {"type": "notification"} ',
|
|
15
|
+
"",
|
|
16
|
+
"Error: something went wrong",
|
|
17
|
+
'{"type": "request", "id": 3}',
|
|
18
|
+
].join("\n");
|
|
19
|
+
|
|
20
|
+
const consoleWarnSpy = vi
|
|
21
|
+
.spyOn(console, "warn")
|
|
22
|
+
.mockImplementation(() => {});
|
|
23
|
+
|
|
24
|
+
const readable = Readable.from([input]);
|
|
25
|
+
const transform = new JSONFilterTransform();
|
|
26
|
+
const chunks: Buffer[] = [];
|
|
27
|
+
|
|
28
|
+
const writable = new Writable({
|
|
29
|
+
write(chunk, _encoding, callback) {
|
|
30
|
+
chunks.push(chunk);
|
|
31
|
+
callback();
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await pipeline(readable, transform, writable);
|
|
36
|
+
|
|
37
|
+
const output = Buffer.concat(chunks).toString();
|
|
38
|
+
const outputLines = output.trim().split("\n");
|
|
39
|
+
|
|
40
|
+
// Should only contain the JSON lines
|
|
41
|
+
expect(outputLines).toHaveLength(4);
|
|
42
|
+
expect(outputLines[0]).toBe('{"type": "request", "id": 1}');
|
|
43
|
+
expect(outputLines[1]).toBe('{"type": "response", "id": 2}');
|
|
44
|
+
expect(outputLines[2]).toBe(' {"type": "notification"} ');
|
|
45
|
+
expect(outputLines[3]).toBe('{"type": "request", "id": 3}');
|
|
46
|
+
|
|
47
|
+
// Should have warned about non-JSON lines
|
|
48
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
49
|
+
"[mcp-proxy] ignoring non-JSON output",
|
|
50
|
+
expect.arrayContaining([
|
|
51
|
+
"This is not JSON",
|
|
52
|
+
"Another non-JSON line",
|
|
53
|
+
"",
|
|
54
|
+
"Error: something went wrong",
|
|
55
|
+
]),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
consoleWarnSpy.mockRestore();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("handles incomplete JSON lines across multiple chunks", async () => {
|
|
62
|
+
// Simulate data arriving in chunks where JSON lines are split
|
|
63
|
+
const chunks = [
|
|
64
|
+
'{"type": "req',
|
|
65
|
+
'uest", "id": 1}\n',
|
|
66
|
+
'Some error message\n{"type',
|
|
67
|
+
'": "response", ',
|
|
68
|
+
'"id": 2}\n',
|
|
69
|
+
'{"partial":',
|
|
70
|
+
' "data"',
|
|
71
|
+
"}",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const transform = new JSONFilterTransform();
|
|
75
|
+
const outputChunks: Buffer[] = [];
|
|
76
|
+
|
|
77
|
+
const writable = new Writable({
|
|
78
|
+
write(chunk, _encoding, callback) {
|
|
79
|
+
outputChunks.push(chunk);
|
|
80
|
+
callback();
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create a readable stream that emits chunks one by one
|
|
85
|
+
const readable = new Readable({
|
|
86
|
+
read() {
|
|
87
|
+
if (chunks.length > 0) {
|
|
88
|
+
this.push(chunks.shift());
|
|
89
|
+
} else {
|
|
90
|
+
this.push(null); // End the stream
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await pipeline(readable, transform, writable);
|
|
96
|
+
|
|
97
|
+
const output = Buffer.concat(outputChunks).toString();
|
|
98
|
+
const outputLines = output.trim().split("\n");
|
|
99
|
+
|
|
100
|
+
// Should correctly reassemble and filter JSON lines
|
|
101
|
+
expect(outputLines).toHaveLength(3);
|
|
102
|
+
expect(outputLines[0]).toBe('{"type": "request", "id": 1}');
|
|
103
|
+
expect(outputLines[1]).toBe('{"type": "response", "id": 2}');
|
|
104
|
+
expect(outputLines[2]).toBe('{"partial": "data"}');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/
|
|
2
|
+
* Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/a1608a6513d18eb965266286904760f830de96fe/src/client/stdio.ts
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
10
10
|
import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { ChildProcess, IOType, spawn } from "node:child_process";
|
|
12
|
-
import { Stream } from "node:stream";
|
|
12
|
+
import { PassThrough, Stream } from "node:stream";
|
|
13
13
|
|
|
14
14
|
import { JSONFilterTransform } from "./JSONFilterTransform.js";
|
|
15
15
|
|
|
@@ -83,24 +83,41 @@ export class StdioClientTransport implements Transport {
|
|
|
83
83
|
|
|
84
84
|
onerror?: (error: Error) => void;
|
|
85
85
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
86
|
+
/**
|
|
87
|
+
* The child process pid spawned by this transport.
|
|
88
|
+
*
|
|
89
|
+
* This is only available after the transport has been started.
|
|
90
|
+
*/
|
|
91
|
+
get pid(): null | number {
|
|
92
|
+
return this._process?.pid ?? null;
|
|
93
|
+
}
|
|
86
94
|
/**
|
|
87
95
|
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
|
|
88
96
|
*
|
|
89
|
-
*
|
|
97
|
+
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
|
|
98
|
+
* attach listeners before the start method is invoked. This prevents loss of any early
|
|
99
|
+
* error output emitted by the child process.
|
|
90
100
|
*/
|
|
91
101
|
get stderr(): null | Stream {
|
|
92
|
-
|
|
102
|
+
if (this._stderrStream) {
|
|
103
|
+
return this._stderrStream;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return this._process?.stderr ?? null;
|
|
93
107
|
}
|
|
94
|
-
private
|
|
108
|
+
private _abortController: AbortController = new AbortController();
|
|
109
|
+
private _process?: ChildProcess;
|
|
110
|
+
private _readBuffer: ReadBuffer = new ReadBuffer();
|
|
111
|
+
private _serverParams: StdioServerParameters;
|
|
112
|
+
private _stderrStream: null | PassThrough = null;
|
|
95
113
|
|
|
96
114
|
private onEvent?: (event: TransportEvent) => void;
|
|
97
|
-
private process?: ChildProcess;
|
|
98
|
-
private readBuffer: ReadBuffer = new ReadBuffer();
|
|
99
|
-
|
|
100
|
-
private serverParams: StdioServerParameters;
|
|
101
115
|
|
|
102
116
|
constructor(server: StdioServerParameters) {
|
|
103
|
-
this.
|
|
117
|
+
this._serverParams = server;
|
|
118
|
+
if (server.stderr === "pipe" || server.stderr === "overlapped") {
|
|
119
|
+
this._stderrStream = new PassThrough();
|
|
120
|
+
}
|
|
104
121
|
this.onEvent = server.onEvent;
|
|
105
122
|
}
|
|
106
123
|
|
|
@@ -109,22 +126,22 @@ export class StdioClientTransport implements Transport {
|
|
|
109
126
|
type: "close",
|
|
110
127
|
});
|
|
111
128
|
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
114
|
-
this.
|
|
129
|
+
this._abortController.abort();
|
|
130
|
+
this._process = undefined;
|
|
131
|
+
this._readBuffer.clear();
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
send(message: JSONRPCMessage): Promise<void> {
|
|
118
135
|
return new Promise((resolve) => {
|
|
119
|
-
if (!this.
|
|
136
|
+
if (!this._process?.stdin) {
|
|
120
137
|
throw new Error("Not connected");
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
const json = serializeMessage(message);
|
|
124
|
-
if (this.
|
|
141
|
+
if (this._process.stdin.write(json)) {
|
|
125
142
|
resolve();
|
|
126
143
|
} else {
|
|
127
|
-
this.
|
|
144
|
+
this._process.stdin.once("drain", resolve);
|
|
128
145
|
}
|
|
129
146
|
});
|
|
130
147
|
}
|
|
@@ -133,27 +150,28 @@ export class StdioClientTransport implements Transport {
|
|
|
133
150
|
* Starts the server process and prepares to communicate with it.
|
|
134
151
|
*/
|
|
135
152
|
async start(): Promise<void> {
|
|
136
|
-
if (this.
|
|
153
|
+
if (this._process) {
|
|
137
154
|
throw new Error(
|
|
138
155
|
"StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.",
|
|
139
156
|
);
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
return new Promise((resolve, reject) => {
|
|
143
|
-
this.
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
160
|
+
this._process = spawn(
|
|
161
|
+
this._serverParams.command,
|
|
162
|
+
this._serverParams.args ?? [],
|
|
146
163
|
{
|
|
147
|
-
cwd: this.
|
|
148
|
-
env: this.
|
|
149
|
-
shell: this.
|
|
150
|
-
signal: this.
|
|
151
|
-
stdio: ["pipe", "pipe", this.
|
|
164
|
+
cwd: this._serverParams.cwd,
|
|
165
|
+
env: this._serverParams.env,
|
|
166
|
+
shell: this._serverParams.shell ?? false,
|
|
167
|
+
signal: this._abortController.signal,
|
|
168
|
+
stdio: ["pipe", "pipe", this._serverParams.stderr ?? "inherit"],
|
|
152
169
|
},
|
|
153
170
|
);
|
|
154
171
|
|
|
155
|
-
this.
|
|
172
|
+
this._process.on("error", (error) => {
|
|
156
173
|
if (error.name === "AbortError") {
|
|
174
|
+
// Expected when close() is called.
|
|
157
175
|
this.onclose?.();
|
|
158
176
|
return;
|
|
159
177
|
}
|
|
@@ -162,21 +180,21 @@ export class StdioClientTransport implements Transport {
|
|
|
162
180
|
this.onerror?.(error);
|
|
163
181
|
});
|
|
164
182
|
|
|
165
|
-
this.
|
|
183
|
+
this._process.on("spawn", () => {
|
|
166
184
|
resolve();
|
|
167
185
|
});
|
|
168
186
|
|
|
169
187
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
170
|
-
this.
|
|
188
|
+
this._process.on("close", (_code) => {
|
|
171
189
|
this.onEvent?.({
|
|
172
190
|
type: "close",
|
|
173
191
|
});
|
|
174
192
|
|
|
175
|
-
this.
|
|
193
|
+
this._process = undefined;
|
|
176
194
|
this.onclose?.();
|
|
177
195
|
});
|
|
178
196
|
|
|
179
|
-
this.
|
|
197
|
+
this._process.stdin?.on("error", (error) => {
|
|
180
198
|
this.onEvent?.({
|
|
181
199
|
error,
|
|
182
200
|
type: "error",
|
|
@@ -187,7 +205,7 @@ export class StdioClientTransport implements Transport {
|
|
|
187
205
|
|
|
188
206
|
const jsonFilterTransform = new JSONFilterTransform();
|
|
189
207
|
|
|
190
|
-
this.
|
|
208
|
+
this._process.stdout?.pipe(jsonFilterTransform);
|
|
191
209
|
|
|
192
210
|
jsonFilterTransform.on("data", (chunk) => {
|
|
193
211
|
this.onEvent?.({
|
|
@@ -195,7 +213,7 @@ export class StdioClientTransport implements Transport {
|
|
|
195
213
|
type: "data",
|
|
196
214
|
});
|
|
197
215
|
|
|
198
|
-
this.
|
|
216
|
+
this._readBuffer.append(chunk);
|
|
199
217
|
this.processReadBuffer();
|
|
200
218
|
});
|
|
201
219
|
|
|
@@ -207,13 +225,17 @@ export class StdioClientTransport implements Transport {
|
|
|
207
225
|
|
|
208
226
|
this.onerror?.(error);
|
|
209
227
|
});
|
|
228
|
+
|
|
229
|
+
if (this._stderrStream && this._process.stderr) {
|
|
230
|
+
this._process.stderr.pipe(this._stderrStream);
|
|
231
|
+
}
|
|
210
232
|
});
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
private processReadBuffer() {
|
|
214
236
|
while (true) {
|
|
215
237
|
try {
|
|
216
|
-
const message = this.
|
|
238
|
+
const message = this._readBuffer.readMessage();
|
|
217
239
|
|
|
218
240
|
if (message === null) {
|
|
219
241
|
break;
|
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -80,7 +80,8 @@ const argv = await yargs(hideBin(process.argv))
|
|
|
80
80
|
},
|
|
81
81
|
stateless: {
|
|
82
82
|
default: false,
|
|
83
|
-
describe:
|
|
83
|
+
describe:
|
|
84
|
+
"Enable stateless mode for HTTP streamable transport (no session management)",
|
|
84
85
|
type: "boolean",
|
|
85
86
|
},
|
|
86
87
|
streamEndpoint: {
|
|
@@ -98,6 +99,7 @@ if (!argv.command) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
const finalCommand = argv.command;
|
|
102
|
+
|
|
101
103
|
// If -- separator was used, args after -- are in argv["--"], otherwise use parsed args
|
|
102
104
|
const finalArgs = (argv["--"] as string[]) || argv.args;
|
|
103
105
|
|
|
@@ -112,7 +114,8 @@ const connect = async (client: Client) => {
|
|
|
112
114
|
}
|
|
113
115
|
},
|
|
114
116
|
shell: argv.shell,
|
|
115
|
-
stderr
|
|
117
|
+
// We want to passthrough stderr from the MCP server to enable better debugging
|
|
118
|
+
stderr: "inherit",
|
|
116
119
|
});
|
|
117
120
|
|
|
118
121
|
await client.connect(transport);
|
|
@@ -197,8 +200,8 @@ const createGracefulShutdown = ({
|
|
|
197
200
|
}, timeout).unref();
|
|
198
201
|
};
|
|
199
202
|
|
|
200
|
-
process.
|
|
201
|
-
process.
|
|
203
|
+
process.once("SIGTERM", gracefulShutdown);
|
|
204
|
+
process.once("SIGINT", gracefulShutdown);
|
|
202
205
|
|
|
203
206
|
return () => {
|
|
204
207
|
server.close();
|
|
@@ -216,6 +219,7 @@ const main = async () => {
|
|
|
216
219
|
} catch (error) {
|
|
217
220
|
console.error("could not start the proxy", error);
|
|
218
221
|
|
|
222
|
+
// We give an extra second for logs to flush
|
|
219
223
|
setTimeout(() => {
|
|
220
224
|
process.exit(1);
|
|
221
225
|
}, 1000);
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -49,7 +49,10 @@ const createJsonRpcErrorResponse = (code: number, message: string) => {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
// Helper function to handle Response errors and send appropriate HTTP response
|
|
52
|
-
const handleResponseError = (
|
|
52
|
+
const handleResponseError = (
|
|
53
|
+
error: unknown,
|
|
54
|
+
res: http.ServerResponse,
|
|
55
|
+
): boolean => {
|
|
53
56
|
if (error instanceof Response) {
|
|
54
57
|
const fixedHeaders: http.OutgoingHttpHeaders = {};
|
|
55
58
|
error.headers.forEach((value, key) => {
|
|
@@ -63,7 +66,9 @@ const handleResponseError = (error: unknown, res: http.ServerResponse): boolean
|
|
|
63
66
|
fixedHeaders[key] = value;
|
|
64
67
|
}
|
|
65
68
|
});
|
|
66
|
-
res
|
|
69
|
+
res
|
|
70
|
+
.writeHead(error.status, error.statusText, fixedHeaders)
|
|
71
|
+
.end(error.statusText);
|
|
67
72
|
return true;
|
|
68
73
|
}
|
|
69
74
|
return false;
|
|
@@ -72,7 +77,7 @@ const handleResponseError = (error: unknown, res: http.ServerResponse): boolean
|
|
|
72
77
|
// Helper function to clean up server resources
|
|
73
78
|
const cleanupServer = async <T extends ServerLike>(
|
|
74
79
|
server: T,
|
|
75
|
-
onClose?: (server: T) => Promise<void
|
|
80
|
+
onClose?: (server: T) => Promise<void>,
|
|
76
81
|
) => {
|
|
77
82
|
if (onClose) {
|
|
78
83
|
await onClose(server);
|
|
@@ -130,9 +135,9 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
130
135
|
const activeTransport = activeTransports[sessionId];
|
|
131
136
|
if (!activeTransport) {
|
|
132
137
|
res.setHeader("Content-Type", "application/json");
|
|
133
|
-
res
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
res
|
|
139
|
+
.writeHead(404)
|
|
140
|
+
.end(createJsonRpcErrorResponse(-32001, "Session not found"));
|
|
136
141
|
|
|
137
142
|
return true;
|
|
138
143
|
}
|
|
@@ -157,8 +162,17 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
157
162
|
});
|
|
158
163
|
|
|
159
164
|
// Handle the server close event
|
|
165
|
+
let isCleaningUp = false;
|
|
166
|
+
|
|
160
167
|
transport.onclose = async () => {
|
|
161
168
|
const sid = transport.sessionId;
|
|
169
|
+
|
|
170
|
+
if (isCleaningUp) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
isCleaningUp = true;
|
|
175
|
+
|
|
162
176
|
if (!stateless && sid && activeTransports[sid]) {
|
|
163
177
|
await cleanupServer(server, onClose);
|
|
164
178
|
delete activeTransports[sid];
|
|
@@ -225,9 +239,14 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
225
239
|
// Error if the server is not created but the request is not an initialize request
|
|
226
240
|
res.setHeader("Content-Type", "application/json");
|
|
227
241
|
|
|
228
|
-
res
|
|
229
|
-
|
|
230
|
-
|
|
242
|
+
res
|
|
243
|
+
.writeHead(400)
|
|
244
|
+
.end(
|
|
245
|
+
createJsonRpcErrorResponse(
|
|
246
|
+
-32000,
|
|
247
|
+
"Bad Request: No valid session ID provided",
|
|
248
|
+
),
|
|
249
|
+
);
|
|
231
250
|
|
|
232
251
|
return true;
|
|
233
252
|
}
|
|
@@ -241,9 +260,9 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
241
260
|
|
|
242
261
|
res.setHeader("Content-Type", "application/json");
|
|
243
262
|
|
|
244
|
-
res
|
|
245
|
-
|
|
246
|
-
|
|
263
|
+
res
|
|
264
|
+
.writeHead(500)
|
|
265
|
+
.end(createJsonRpcErrorResponse(-32603, "Internal Server Error"));
|
|
247
266
|
}
|
|
248
267
|
return true;
|
|
249
268
|
}
|
|
@@ -368,10 +387,17 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
368
387
|
activeTransports[transport.sessionId] = transport;
|
|
369
388
|
|
|
370
389
|
let closed = false;
|
|
390
|
+
let isCleaningUp = false;
|
|
371
391
|
|
|
372
392
|
res.on("close", async () => {
|
|
373
393
|
closed = true;
|
|
374
394
|
|
|
395
|
+
// Prevent recursive cleanup
|
|
396
|
+
if (isCleaningUp) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
isCleaningUp = true;
|
|
375
401
|
await cleanupServer(server, onClose);
|
|
376
402
|
|
|
377
403
|
delete activeTransports[transport.sessionId];
|
|
@@ -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\n// Helper function to create JSON RPC error responses\nconst createJsonRpcErrorResponse = (code: number, message: string) => {\n return JSON.stringify({\n error: { code, message },\n id: null,\n jsonrpc: \"2.0\",\n });\n};\n\n// Helper function to handle Response errors and send appropriate HTTP response\nconst handleResponseError = (error: unknown, res: http.ServerResponse): boolean => {\n if (error instanceof Response) {\n const fixedHeaders: http.OutgoingHttpHeaders = {};\n error.headers.forEach((value, key) => {\n if (fixedHeaders[key]) {\n if (Array.isArray(fixedHeaders[key])) {\n (fixedHeaders[key] as string[]).push(value);\n } else {\n fixedHeaders[key] = [fixedHeaders[key] as string, value];\n }\n } else {\n fixedHeaders[key] = value;\n }\n });\n res.writeHead(error.status, error.statusText, fixedHeaders).end(error.statusText);\n return true;\n }\n return false;\n};\n\n// Helper function to clean up server resources\nconst cleanupServer = async <T extends ServerLike>(\n server: T,\n onClose?: (server: T) => Promise<void>\n) => {\n if (onClose) {\n await onClose(server);\n }\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"[mcp-proxy] error closing server\", error);\n }\n};\n\nconst handleStreamRequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n enableJsonResponse,\n endpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n stateless,\n}: {\n activeTransports: Record<\n string,\n { server: T; transport: StreamableHTTPServerTransport }\n >;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n endpoint: string;\n eventStore?: EventStore;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n stateless?: boolean;\n}) => {\n if (\n req.method === \"POST\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n try {\n const sessionId = Array.isArray(req.headers[\"mcp-session-id\"])\n ? req.headers[\"mcp-session-id\"][0]\n : req.headers[\"mcp-session-id\"];\n\n let transport: StreamableHTTPServerTransport;\n\n let server: T;\n\n const body = await getBody(req);\n\n if (sessionId) {\n const activeTransport = activeTransports[sessionId];\n if (!activeTransport) {\n res.setHeader(\"Content-Type\", \"application/json\");\n res.writeHead(404).end(\n createJsonRpcErrorResponse(-32001, \"Session not found\"),\n );\n\n return true;\n }\n\n transport = activeTransport.transport;\n server = activeTransport.server;\n } else if (!sessionId && isInitializeRequest(body)) {\n // Create a new transport for the session\n transport = new StreamableHTTPServerTransport({\n enableJsonResponse,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: (_sessionId) => {\n // add only when the id Session id is generated (skip in stateless mode)\n if (!stateless && _sessionId) {\n activeTransports[_sessionId] = {\n server,\n transport,\n };\n }\n },\n sessionIdGenerator: stateless ? undefined : randomUUID,\n });\n\n // Handle the server close event\n transport.onclose = async () => {\n const sid = transport.sessionId;\n if (!stateless && sid && activeTransports[sid]) {\n await cleanupServer(server, onClose);\n delete activeTransports[sid];\n } else if (stateless) {\n // In stateless mode, always call onClose when transport closes\n await cleanupServer(server, onClose);\n }\n };\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n server.connect(transport);\n\n if (onConnect) {\n await onConnect(server);\n }\n\n await transport.handleRequest(req, res, body);\n\n return true;\n } else if (stateless && !sessionId && !isInitializeRequest(body)) {\n // In stateless mode, handle non-initialize requests by creating a new transport\n transport = new StreamableHTTPServerTransport({\n enableJsonResponse,\n eventStore: eventStore || new InMemoryEventStore(),\n onsessioninitialized: () => {\n // No session tracking in stateless mode\n },\n sessionIdGenerator: undefined,\n });\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n server.connect(transport);\n\n if (onConnect) {\n await onConnect(server);\n }\n\n await transport.handleRequest(req, res, body);\n\n return true;\n } else {\n // Error if the server is not created but the request is not an initialize request\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(400).end(\n createJsonRpcErrorResponse(-32000, \"Bad Request: No valid session ID provided\"),\n );\n\n return true;\n }\n\n // Handle the request if the server is already created\n await transport.handleRequest(req, res, body);\n\n return true;\n } catch (error) {\n console.error(\"[mcp-proxy] error handling request\", error);\n\n res.setHeader(\"Content-Type\", \"application/json\");\n\n res.writeHead(500).end(\n createJsonRpcErrorResponse(-32603, \"Internal Server Error\"),\n );\n }\n return true;\n }\n\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n const activeTransport:\n | {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n | undefined = sessionId ? activeTransports[sessionId] : undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n const lastEventId = req.headers[\"last-event-id\"] as string | undefined;\n\n if (lastEventId) {\n console.log(\n `[mcp-proxy] client reconnecting with Last-Event-ID ${lastEventId} for session ID ${sessionId}`,\n );\n } else {\n console.log(\n `[mcp-proxy] establishing new SSE stream for session ID ${sessionId}`,\n );\n }\n\n await activeTransport.transport.handleRequest(req, res);\n\n return true;\n }\n\n if (\n req.method === \"DELETE\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n console.log(\"[mcp-proxy] received delete request\");\n\n const sessionId = req.headers[\"mcp-session-id\"] as string | undefined;\n\n if (!sessionId) {\n res.writeHead(400).end(\"Invalid or missing sessionId\");\n\n return true;\n }\n\n console.log(\"[mcp-proxy] received delete request for session\", sessionId);\n\n const activeTransport = activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n return true;\n }\n\n try {\n await activeTransport.transport.handleRequest(req, res);\n\n await cleanupServer(activeTransport.server, onClose);\n } catch (error) {\n console.error(\"[mcp-proxy] error handling delete request\", error);\n\n res.writeHead(500).end(\"Error handling delete request\");\n }\n\n return true;\n }\n\n return false;\n};\n\nconst handleSSERequest = async <T extends ServerLike>({\n activeTransports,\n createServer,\n endpoint,\n onClose,\n onConnect,\n req,\n res,\n}: {\n activeTransports: Record<string, SSEServerTransport>;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n endpoint: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n req: http.IncomingMessage;\n res: http.ServerResponse;\n}) => {\n if (\n req.method === \"GET\" &&\n new URL(req.url!, \"http://localhost\").pathname === endpoint\n ) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n let server: T;\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (handleResponseError(error, res)) {\n return true;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return true;\n }\n\n activeTransports[transport.sessionId] = transport;\n\n let closed = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n await cleanupServer(server, onClose);\n\n delete activeTransports[transport.sessionId];\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n if (onConnect) {\n await onConnect(server);\n }\n } catch (error) {\n if (!closed) {\n console.error(\"[mcp-proxy] error connecting to server\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return true;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(req.url, \"https://example.com\").searchParams.get(\n \"sessionId\",\n );\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return true;\n }\n\n const activeTransport: SSEServerTransport | undefined =\n activeTransports[sessionId];\n\n if (!activeTransport) {\n res.writeHead(400).end(\"No active transport\");\n\n return true;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return true;\n }\n\n return false;\n};\n\nexport const startHTTPServer = async <T extends ServerLike>({\n createServer,\n enableJsonResponse,\n eventStore,\n host = \"::\",\n onClose,\n onConnect,\n onUnhandledRequest,\n port,\n sseEndpoint = \"/sse\",\n stateless,\n streamEndpoint = \"/mcp\",\n}: {\n createServer: (request: http.IncomingMessage) => Promise<T>;\n enableJsonResponse?: boolean;\n eventStore?: EventStore;\n host?: string;\n onClose?: (server: T) => Promise<void>;\n onConnect?: (server: T) => Promise<void>;\n onUnhandledRequest?: (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ) => Promise<void>;\n port: number;\n sseEndpoint?: null | string;\n stateless?: boolean;\n streamEndpoint?: null | string;\n}): Promise<SSEServer> => {\n const activeSSETransports: Record<string, SSEServerTransport> = {};\n\n const activeStreamTransports: Record<\n string,\n {\n server: T;\n transport: StreamableHTTPServerTransport;\n }\n > = {};\n\n /**\n * @author https://dev.classmethod.jp/articles/mcp-sse/\n */\n const httpServer = http.createServer(async (req, res) => {\n if (req.headers.origin) {\n try {\n const origin = new URL(req.headers.origin);\n\n res.setHeader(\"Access-Control-Allow-Origin\", origin.origin);\n res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"*\");\n res.setHeader(\"Access-Control-Expose-Headers\", \"mcp-session-id\");\n } catch (error) {\n console.error(\"[mcp-proxy] error parsing origin\", error);\n }\n }\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === \"GET\" && req.url === `/ping`) {\n res.writeHead(200).end(\"pong\");\n return;\n }\n\n if (\n sseEndpoint &&\n (await handleSSERequest({\n activeTransports: activeSSETransports,\n createServer,\n endpoint: sseEndpoint,\n onClose,\n onConnect,\n req,\n res,\n }))\n ) {\n return;\n }\n\n if (\n streamEndpoint &&\n (await handleStreamRequest({\n activeTransports: activeStreamTransports,\n createServer,\n enableJsonResponse,\n endpoint: streamEndpoint,\n eventStore,\n onClose,\n onConnect,\n req,\n res,\n stateless,\n }))\n ) {\n return;\n }\n\n if (onUnhandledRequest) {\n await onUnhandledRequest(req, res);\n } else {\n res.writeHead(404).end();\n }\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, host, () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeSSETransports)) {\n await transport.close();\n }\n\n for (const transport of Object.values(activeStreamTransports)) {\n await transport.transport.close();\n }\n\n return new Promise((resolve, reject) => {\n httpServer.close((error) => {\n if (error) {\n reject(error);\n\n return;\n }\n\n resolve();\n });\n });\n },\n };\n};\n"],"mappings":"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","names":[]}
|