mcp-proxy 5.2.1 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/bin/mcp-proxy.js +20 -6
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/bin/mcp-proxy.ts +30 -9
package/README.md
CHANGED
|
@@ -119,3 +119,11 @@ const transport = tapTransport(new StdioClientTransport(), (event) => {
|
|
|
119
119
|
console.log(event);
|
|
120
120
|
});
|
|
121
121
|
```
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
### Running MCP Proxy with a local server
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
tsx src/bin/mcp-proxy.ts --debug tsx src/fixtures/simple-stdio-server.ts
|
|
129
|
+
```
|
package/dist/bin/mcp-proxy.js
CHANGED
|
@@ -311,15 +311,29 @@ var proxy = async () => {
|
|
|
311
311
|
}
|
|
312
312
|
};
|
|
313
313
|
};
|
|
314
|
+
var createGracefulShutdown = ({
|
|
315
|
+
server,
|
|
316
|
+
timeout
|
|
317
|
+
}) => {
|
|
318
|
+
const gracefulShutdown = () => {
|
|
319
|
+
console.info("received shutdown signal; shutting down");
|
|
320
|
+
server.close();
|
|
321
|
+
setTimeout(() => {
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}, timeout).unref();
|
|
324
|
+
};
|
|
325
|
+
process.on("SIGTERM", gracefulShutdown);
|
|
326
|
+
process.on("SIGINT", gracefulShutdown);
|
|
327
|
+
return () => {
|
|
328
|
+
server.close();
|
|
329
|
+
};
|
|
330
|
+
};
|
|
314
331
|
var main = async () => {
|
|
315
332
|
try {
|
|
316
333
|
const server = await proxy();
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
setTimeout(() => {
|
|
321
|
-
process.exit(0);
|
|
322
|
-
}, argv.gracefulShutdownTimeout).unref();
|
|
334
|
+
createGracefulShutdown({
|
|
335
|
+
server,
|
|
336
|
+
timeout: argv.gracefulShutdownTimeout
|
|
323
337
|
});
|
|
324
338
|
} catch (error) {
|
|
325
339
|
console.error("could not start the proxy", error);
|
|
@@ -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 { 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 main = async () => {\n try {\n const server = await proxy();\n\n process.on(\"SIGINT\", () => {\n console.info(\"SIGINT received, shutting down\");\n\n server.close();\n\n setTimeout(() => {\n process.exit(0);\n }, argv.gracefulShutdownTimeout).unref();\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 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"]}
|
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/bin/mcp-proxy.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
10
10
|
|
|
11
11
|
import { InMemoryEventStore } from "../InMemoryEventStore.js";
|
|
12
12
|
import { proxyServer } from "../proxyServer.js";
|
|
13
|
-
import { startHTTPServer } from "../startHTTPServer.js";
|
|
13
|
+
import { SSEServer, startHTTPServer } from "../startHTTPServer.js";
|
|
14
14
|
import { StdioClientTransport } from "../StdioClientTransport.js";
|
|
15
15
|
|
|
16
16
|
util.inspect.defaultOptions.depth = 8;
|
|
@@ -173,18 +173,39 @@ const proxy = async () => {
|
|
|
173
173
|
};
|
|
174
174
|
};
|
|
175
175
|
|
|
176
|
+
const createGracefulShutdown = ({
|
|
177
|
+
server,
|
|
178
|
+
timeout,
|
|
179
|
+
}: {
|
|
180
|
+
server: SSEServer;
|
|
181
|
+
timeout: number;
|
|
182
|
+
}) => {
|
|
183
|
+
const gracefulShutdown = () => {
|
|
184
|
+
console.info("received shutdown signal; shutting down");
|
|
185
|
+
|
|
186
|
+
server.close();
|
|
187
|
+
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
// Exit with non-zero code to indicate failure to shutdown gracefully
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}, timeout).unref();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
process.on("SIGTERM", gracefulShutdown);
|
|
195
|
+
process.on("SIGINT", gracefulShutdown);
|
|
196
|
+
|
|
197
|
+
return () => {
|
|
198
|
+
server.close();
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
|
|
176
202
|
const main = async () => {
|
|
177
203
|
try {
|
|
178
204
|
const server = await proxy();
|
|
179
205
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
server.close();
|
|
184
|
-
|
|
185
|
-
setTimeout(() => {
|
|
186
|
-
process.exit(0);
|
|
187
|
-
}, argv.gracefulShutdownTimeout).unref();
|
|
206
|
+
createGracefulShutdown({
|
|
207
|
+
server,
|
|
208
|
+
timeout: argv.gracefulShutdownTimeout,
|
|
188
209
|
});
|
|
189
210
|
} catch (error) {
|
|
190
211
|
console.error("could not start the proxy", error);
|