UNPKG

mcp-proxy

Version:

A TypeScript SSE proxy for MCP servers that use stdio transport.

241 lines (238 loc) 6.03 kB
#!/usr/bin/env node import { proxyServer, startSSEServer } from "../chunk-YBSC4ELC.js"; // src/bin/mcp-proxy.ts import yargs from "yargs"; import { hideBin } from "yargs/helpers"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { EventSource } from "eventsource"; import { setTimeout } from "node:timers"; // src/StdioClientTransport.ts import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js"; import { spawn } from "node:child_process"; var StdioClientTransport = class { process; abortController = new AbortController(); readBuffer = new ReadBuffer(); serverParams; onEvent; onclose; onerror; onmessage; constructor(server) { this.serverParams = server; this.onEvent = server.onEvent; } /** * Starts the server process and prepares to communicate with it. */ async start() { if (this.process) { throw new Error( "StdioClientTransport already started! If using Client class, note that connect() calls start() automatically." ); } return new Promise((resolve, reject) => { this.process = spawn( this.serverParams.command, this.serverParams.args ?? [], { env: this.serverParams.env, stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"], shell: false, signal: this.abortController.signal, cwd: this.serverParams.cwd } ); this.process.on("error", (error) => { if (error.name === "AbortError") { this.onclose?.(); return; } reject(error); this.onerror?.(error); }); this.process.on("spawn", () => { resolve(); }); this.process.on("close", (_code) => { this.onEvent?.({ type: "close" }); this.process = void 0; this.onclose?.(); }); this.process.stdin?.on("error", (error) => { this.onEvent?.({ type: "error", error }); this.onerror?.(error); }); this.process.stdout?.on("data", (chunk) => { this.onEvent?.({ type: "data", chunk: chunk.toString() }); this.readBuffer.append(chunk); this.processReadBuffer(); }); this.process.stdout?.on("error", (error) => { this.onEvent?.({ type: "error", error }); this.onerror?.(error); }); }); } /** * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped". * * This is only available after the process has been started. */ get stderr() { return this.process?.stderr ?? null; } processReadBuffer() { while (true) { try { const message = this.readBuffer.readMessage(); if (message === null) { break; } this.onEvent?.({ type: "message", message }); this.onmessage?.(message); } catch (error) { this.onEvent?.({ type: "error", error }); this.onerror?.(error); } } } async close() { this.onEvent?.({ type: "close" }); this.abortController.abort(); this.process = void 0; this.readBuffer.clear(); } send(message) { return new Promise((resolve) => { if (!this.process?.stdin) { throw new Error("Not connected"); } const json = serializeMessage(message); if (this.process.stdin.write(json)) { resolve(); } else { this.process.stdin.once("drain", resolve); } }); } }; // src/bin/mcp-proxy.ts import util from "node:util"; util.inspect.defaultOptions.depth = 8; if (!("EventSource" in global)) { global.EventSource = EventSource; } var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$0 <command> [args...]", "Run a command with MCP arguments").positional("command", { type: "string", describe: "The command to run", demandOption: true }).positional("args", { type: "string", array: true, describe: "The arguments to pass to the command" }).env("MCP_PROXY").options({ debug: { type: "boolean", describe: "Enable debug logging", default: false }, endpoint: { type: "string", describe: "The endpoint to listen on for SSE", default: "/sse" }, port: { type: "number", describe: "The port to listen on for SSE", default: 8080 } }).help().parseAsync(); var connect = async (client) => { const transport = new StdioClientTransport({ command: argv.command, args: argv.args, env: process.env, stderr: "pipe", onEvent: (event) => { if (argv.debug) { console.debug("transport event", event); } } }); await client.connect(transport); }; var proxy = async () => { const client = new Client( { name: "mcp-proxy", version: "1.0.0" }, { capabilities: {} } ); await connect(client); const serverVersion = client.getServerVersion(); const serverCapabilities = client.getServerCapabilities(); console.info("starting the SSE server on port %d", argv.port); await startSSEServer({ createServer: async () => { const server = new Server(serverVersion, { capabilities: serverCapabilities }); proxyServer({ server, client, serverCapabilities }); return server; }, port: argv.port, endpoint: argv.endpoint }); }; var main = async () => { process.on("SIGINT", () => { console.info("SIGINT received, shutting down"); setTimeout(() => { process.exit(0); }, 1e3); }); try { await proxy(); } catch (error) { console.error("could not start the proxy", error); setTimeout(() => { process.exit(1); }, 1e3); } }; await main(); //# sourceMappingURL=mcp-proxy.js.map