mcp-proxy 2.8.2 → 2.9.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.
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin/mcp-proxy.ts
4
+ import yargs from "yargs";
5
+ import { hideBin } from "yargs/helpers";
6
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { EventSource } from "eventsource";
9
+ import { setTimeout } from "node:timers/promises";
10
+
11
+ // src/StdioClientTransport.ts
12
+ import {
13
+ ReadBuffer,
14
+ serializeMessage
15
+ } from "@modelcontextprotocol/sdk/shared/stdio.js";
16
+ import { spawn } from "node:child_process";
17
+ var StdioClientTransport = class {
18
+ process;
19
+ abortController = new AbortController();
20
+ readBuffer = new ReadBuffer();
21
+ serverParams;
22
+ onEvent;
23
+ onclose;
24
+ onerror;
25
+ onmessage;
26
+ constructor(server) {
27
+ this.serverParams = server;
28
+ this.onEvent = server.onEvent;
29
+ }
30
+ /**
31
+ * Starts the server process and prepares to communicate with it.
32
+ */
33
+ async start() {
34
+ if (this.process) {
35
+ throw new Error(
36
+ "StdioClientTransport already started! If using Client class, note that connect() calls start() automatically."
37
+ );
38
+ }
39
+ return new Promise((resolve, reject) => {
40
+ this.process = spawn(
41
+ this.serverParams.command,
42
+ this.serverParams.args ?? [],
43
+ {
44
+ env: this.serverParams.env,
45
+ stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"],
46
+ shell: false,
47
+ signal: this.abortController.signal,
48
+ cwd: this.serverParams.cwd
49
+ }
50
+ );
51
+ this.process.on("error", (error) => {
52
+ if (error.name === "AbortError") {
53
+ this.onclose?.();
54
+ return;
55
+ }
56
+ reject(error);
57
+ this.onerror?.(error);
58
+ });
59
+ this.process.on("spawn", () => {
60
+ resolve();
61
+ });
62
+ this.process.on("close", (_code) => {
63
+ this.onEvent?.({
64
+ type: "close"
65
+ });
66
+ this.process = void 0;
67
+ this.onclose?.();
68
+ });
69
+ this.process.stdin?.on("error", (error) => {
70
+ this.onEvent?.({
71
+ type: "error",
72
+ error
73
+ });
74
+ this.onerror?.(error);
75
+ });
76
+ this.process.stdout?.on("data", (chunk) => {
77
+ this.onEvent?.({
78
+ type: "data",
79
+ chunk: chunk.toString()
80
+ });
81
+ this.readBuffer.append(chunk);
82
+ this.processReadBuffer();
83
+ });
84
+ this.process.stdout?.on("error", (error) => {
85
+ this.onEvent?.({
86
+ type: "error",
87
+ error
88
+ });
89
+ this.onerror?.(error);
90
+ });
91
+ });
92
+ }
93
+ /**
94
+ * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
95
+ *
96
+ * This is only available after the process has been started.
97
+ */
98
+ get stderr() {
99
+ return this.process?.stderr ?? null;
100
+ }
101
+ processReadBuffer() {
102
+ while (true) {
103
+ try {
104
+ const message = this.readBuffer.readMessage();
105
+ if (message === null) {
106
+ break;
107
+ }
108
+ this.onEvent?.({
109
+ type: "message",
110
+ message
111
+ });
112
+ this.onmessage?.(message);
113
+ } catch (error) {
114
+ this.onEvent?.({
115
+ type: "error",
116
+ error
117
+ });
118
+ this.onerror?.(error);
119
+ }
120
+ }
121
+ }
122
+ async close() {
123
+ this.onEvent?.({
124
+ type: "close"
125
+ });
126
+ this.abortController.abort();
127
+ this.process = void 0;
128
+ this.readBuffer.clear();
129
+ }
130
+ send(message) {
131
+ return new Promise((resolve) => {
132
+ if (!this.process?.stdin) {
133
+ throw new Error("Not connected");
134
+ }
135
+ const json = serializeMessage(message);
136
+ if (this.process.stdin.write(json)) {
137
+ resolve();
138
+ } else {
139
+ this.process.stdin.once("drain", resolve);
140
+ }
141
+ });
142
+ }
143
+ };
144
+
145
+ // src/bin/mcp-proxy.ts
146
+ import util from "node:util";
147
+
148
+ // src/startSSEServer.ts
149
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
150
+ import http from "http";
151
+ var startSSEServer = async ({
152
+ port,
153
+ createServer,
154
+ endpoint,
155
+ onConnect,
156
+ onClose
157
+ }) => {
158
+ const activeTransports = {};
159
+ const httpServer = http.createServer(async (req, res) => {
160
+ if (req.headers.origin) {
161
+ try {
162
+ const origin = new URL(req.headers.origin);
163
+ res.setHeader("Access-Control-Allow-Origin", origin.origin);
164
+ res.setHeader("Access-Control-Allow-Credentials", "true");
165
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
166
+ res.setHeader("Access-Control-Allow-Headers", "*");
167
+ } catch (error) {
168
+ console.error("Error parsing origin:", error);
169
+ }
170
+ }
171
+ if (req.method === "OPTIONS") {
172
+ res.writeHead(204);
173
+ res.end();
174
+ return;
175
+ }
176
+ if (req.method === "GET" && req.url === `/ping`) {
177
+ res.writeHead(200).end("pong");
178
+ return;
179
+ }
180
+ if (req.method === "GET" && req.url === endpoint) {
181
+ const transport = new SSEServerTransport("/messages", res);
182
+ let server;
183
+ try {
184
+ server = await createServer(req);
185
+ } catch (error) {
186
+ if (error instanceof Response) {
187
+ res.writeHead(error.status).end(error.statusText);
188
+ return;
189
+ }
190
+ res.writeHead(500).end("Error creating server");
191
+ return;
192
+ }
193
+ activeTransports[transport.sessionId] = transport;
194
+ let closed = false;
195
+ res.on("close", async () => {
196
+ closed = true;
197
+ try {
198
+ await server.close();
199
+ } catch (error) {
200
+ console.error("Error closing server:", error);
201
+ }
202
+ delete activeTransports[transport.sessionId];
203
+ onClose?.(server);
204
+ });
205
+ try {
206
+ await server.connect(transport);
207
+ await transport.send({
208
+ jsonrpc: "2.0",
209
+ method: "sse/connection",
210
+ params: { message: "SSE Connection established" }
211
+ });
212
+ onConnect?.(server);
213
+ } catch (error) {
214
+ if (!closed) {
215
+ console.error("Error connecting to server:", error);
216
+ res.writeHead(500).end("Error connecting to server");
217
+ }
218
+ }
219
+ return;
220
+ }
221
+ if (req.method === "POST" && req.url?.startsWith("/messages")) {
222
+ const sessionId = new URL(
223
+ req.url,
224
+ "https://example.com"
225
+ ).searchParams.get("sessionId");
226
+ if (!sessionId) {
227
+ res.writeHead(400).end("No sessionId");
228
+ return;
229
+ }
230
+ const activeTransport = activeTransports[sessionId];
231
+ if (!activeTransport) {
232
+ res.writeHead(400).end("No active transport");
233
+ return;
234
+ }
235
+ await activeTransport.handlePostMessage(req, res);
236
+ return;
237
+ }
238
+ res.writeHead(404).end();
239
+ });
240
+ await new Promise((resolve) => {
241
+ httpServer.listen(port, "::", () => {
242
+ resolve(void 0);
243
+ });
244
+ });
245
+ return {
246
+ close: async () => {
247
+ for (const transport of Object.values(activeTransports)) {
248
+ await transport.close();
249
+ }
250
+ return new Promise((resolve, reject) => {
251
+ httpServer.close((error) => {
252
+ if (error) {
253
+ reject(error);
254
+ return;
255
+ }
256
+ resolve();
257
+ });
258
+ });
259
+ }
260
+ };
261
+ };
262
+
263
+ // src/proxyServer.ts
264
+ import {
265
+ CallToolRequestSchema,
266
+ CompleteRequestSchema,
267
+ GetPromptRequestSchema,
268
+ ListPromptsRequestSchema,
269
+ ListResourcesRequestSchema,
270
+ ListResourceTemplatesRequestSchema,
271
+ ListToolsRequestSchema,
272
+ LoggingMessageNotificationSchema,
273
+ ReadResourceRequestSchema
274
+ } from "@modelcontextprotocol/sdk/types.js";
275
+ var proxyServer = async ({
276
+ server,
277
+ client,
278
+ serverCapabilities
279
+ }) => {
280
+ if (serverCapabilities?.logging) {
281
+ server.setNotificationHandler(
282
+ LoggingMessageNotificationSchema,
283
+ async (args) => {
284
+ return client.notification(args);
285
+ }
286
+ );
287
+ }
288
+ if (serverCapabilities?.prompts) {
289
+ server.setRequestHandler(GetPromptRequestSchema, async (args) => {
290
+ return client.getPrompt(args.params);
291
+ });
292
+ server.setRequestHandler(ListPromptsRequestSchema, async (args) => {
293
+ return client.listPrompts(args.params);
294
+ });
295
+ }
296
+ if (serverCapabilities?.resources) {
297
+ server.setRequestHandler(ListResourcesRequestSchema, async (args) => {
298
+ return client.listResources(args.params);
299
+ });
300
+ server.setRequestHandler(
301
+ ListResourceTemplatesRequestSchema,
302
+ async (args) => {
303
+ return client.listResourceTemplates(args.params);
304
+ }
305
+ );
306
+ server.setRequestHandler(ReadResourceRequestSchema, async (args) => {
307
+ return client.readResource(args.params);
308
+ });
309
+ }
310
+ if (serverCapabilities?.tools) {
311
+ server.setRequestHandler(CallToolRequestSchema, async (args) => {
312
+ return client.callTool(args.params);
313
+ });
314
+ server.setRequestHandler(ListToolsRequestSchema, async (args) => {
315
+ return client.listTools(args.params);
316
+ });
317
+ }
318
+ server.setRequestHandler(CompleteRequestSchema, async (args) => {
319
+ return client.complete(args.params);
320
+ });
321
+ };
322
+
323
+ // src/bin/mcp-proxy.ts
324
+ util.inspect.defaultOptions.depth = 8;
325
+ if (!("EventSource" in global)) {
326
+ global.EventSource = EventSource;
327
+ }
328
+ var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$0 <command> [args...]", "Run a command with MCP arguments").positional("command", {
329
+ type: "string",
330
+ describe: "The command to run",
331
+ demandOption: true
332
+ }).positional("args", {
333
+ type: "string",
334
+ array: true,
335
+ describe: "The arguments to pass to the command"
336
+ }).options({
337
+ debug: {
338
+ type: "boolean",
339
+ describe: "Enable debug logging",
340
+ default: false
341
+ },
342
+ endpoint: {
343
+ type: "string",
344
+ describe: "The endpoint to listen on for SSE",
345
+ default: "/sse"
346
+ },
347
+ port: {
348
+ type: "number",
349
+ describe: "The port to listen on for SSE",
350
+ default: 8080
351
+ }
352
+ }).help().parseAsync();
353
+ var connect = async (client) => {
354
+ const transport = new StdioClientTransport({
355
+ command: argv.command,
356
+ args: argv.args,
357
+ env: process.env,
358
+ stderr: "pipe",
359
+ onEvent: (event) => {
360
+ console.debug("transport event", event);
361
+ }
362
+ });
363
+ await client.connect(transport);
364
+ };
365
+ var proxy = async () => {
366
+ const client = new Client(
367
+ {
368
+ name: "mcp-proxy",
369
+ version: "1.0.0"
370
+ },
371
+ {
372
+ capabilities: {}
373
+ }
374
+ );
375
+ await connect(client);
376
+ const serverVersion = client.getServerVersion();
377
+ const serverCapabilities = client.getServerCapabilities();
378
+ console.info("starting the SSE server on port %d", argv.port);
379
+ await startSSEServer({
380
+ createServer: async () => {
381
+ const server = new Server(serverVersion, {
382
+ capabilities: serverCapabilities
383
+ });
384
+ proxyServer({
385
+ server,
386
+ client,
387
+ serverCapabilities
388
+ });
389
+ return server;
390
+ },
391
+ port: argv.port,
392
+ endpoint: argv.endpoint
393
+ });
394
+ };
395
+ var main = async () => {
396
+ try {
397
+ await proxy();
398
+ } catch (error) {
399
+ console.error("could not start the proxy", error);
400
+ await setTimeout(1e3);
401
+ process.exit(1);
402
+ }
403
+ };
404
+ await main();
405
+ //# sourceMappingURL=mcp-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bin/mcp-proxy.ts","../src/StdioClientTransport.ts","../src/startSSEServer.ts","../src/proxyServer.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport yargs from \"yargs\";\nimport { hideBin } from \"yargs/helpers\";\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/promises\";\nimport { StdioClientTransport } from \"../StdioClientTransport.js\";\nimport util from \"node:util\";\nimport { startSSEServer } from \"../startSSEServer.js\";\nimport { proxyServer } from \"../proxyServer.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 type: \"string\",\n describe: \"The command to run\",\n demandOption: true,\n })\n .positional(\"args\", {\n type: \"string\",\n array: true,\n describe: \"The arguments to pass to the command\",\n })\n .options({\n debug: {\n type: \"boolean\",\n describe: \"Enable debug logging\",\n default: false,\n },\n endpoint: {\n type: \"string\",\n describe: \"The endpoint to listen on for SSE\",\n default: \"/sse\",\n },\n port: {\n type: \"number\",\n describe: \"The port to listen on for SSE\",\n default: 8080,\n },\n })\n .help()\n .parseAsync();\n\nconst connect = async (client: Client) => {\n const transport = new StdioClientTransport({\n command: argv.command,\n args: argv.args,\n env: process.env as Record<string, string>,\n stderr: \"pipe\",\n onEvent: (event) => {\n console.debug(\"transport event\", event);\n },\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\n console.info(\"starting the SSE server on port %d\", argv.port);\n\n await startSSEServer({\n createServer: async () => {\n const server = new Server(serverVersion, {\n capabilities: serverCapabilities,\n });\n\n proxyServer({\n server,\n client,\n serverCapabilities,\n });\n\n return server;\n },\n port: argv.port,\n endpoint: argv.endpoint as `/${string}`,\n });\n};\n\nconst main = async () => {\n try {\n await proxy();\n } catch (error) {\n console.error(\"could not start the proxy\", error);\n\n await setTimeout(1000);\n\n process.exit(1);\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\ntype TransportEvent =\n | {\n type: \"close\";\n }\n | {\n type: \"error\";\n error: Error;\n }\n | {\n type: \"data\";\n chunk: string;\n }\n | {\n type: \"message\";\n message: JSONRPCMessage;\n };\n\nexport type StdioServerParameters = {\n /**\n * The executable to run to start the server.\n */\n command: string;\n\n /**\n * Command line arguments to pass to the executable.\n */\n args?: 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 * 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 | Stream | number;\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 * A function to call when an event occurs.\n */\n onEvent?: (event: TransportEvent) => void;\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 private process?: ChildProcess;\n private abortController: AbortController = new AbortController();\n private readBuffer: ReadBuffer = new ReadBuffer();\n private serverParams: StdioServerParameters;\n private onEvent?: (event: TransportEvent) => void;\n\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n constructor(server: StdioServerParameters) {\n this.serverParams = server;\n this.onEvent = server.onEvent;\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 env: this.serverParams.env,\n stdio: [\"pipe\", \"pipe\", this.serverParams.stderr ?? \"inherit\"],\n shell: false,\n signal: this.abortController.signal,\n cwd: this.serverParams.cwd,\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 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 type: \"error\",\n error,\n });\n\n this.onerror?.(error);\n });\n\n this.process.stdout?.on(\"data\", (chunk) => {\n this.onEvent?.({\n type: \"data\",\n chunk: chunk.toString(),\n });\n\n this.readBuffer.append(chunk);\n this.processReadBuffer();\n });\n\n this.process.stdout?.on(\"error\", (error) => {\n this.onEvent?.({\n type: \"error\",\n error,\n });\n\n this.onerror?.(error);\n });\n });\n }\n\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(): Stream | null {\n return this.process?.stderr ?? null;\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 type: \"message\",\n message,\n });\n\n this.onmessage?.(message);\n } catch (error) {\n this.onEvent?.({\n type: \"error\",\n error: error as Error,\n });\n\n this.onerror?.(error as Error);\n }\n }\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","import { SSEServerTransport } from \"@modelcontextprotocol/sdk/server/sse.js\";\nimport http from \"http\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\n\nexport type SSEServer = {\n close: () => Promise<void>;\n};\n\ntype ServerLike = {\n connect: Server[\"connect\"];\n close: Server[\"close\"];\n};\n\nexport const startSSEServer = async <T extends ServerLike>({\n port,\n createServer,\n endpoint,\n onConnect,\n onClose,\n}: {\n port: number;\n endpoint: string;\n createServer: (request: http.IncomingMessage) => Promise<T>;\n onConnect?: (server: T) => void;\n onClose?: (server: T) => void;\n}): Promise<SSEServer> => {\n const activeTransports: Record<string, SSEServerTransport> = {};\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 } catch (error) {\n console.error(\"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\n return;\n }\n\n if (req.method === \"GET\" && req.url === endpoint) {\n const transport = new SSEServerTransport(\"/messages\", res);\n\n let server: T;\n\n try {\n server = await createServer(req);\n } catch (error) {\n if (error instanceof Response) {\n res.writeHead(error.status).end(error.statusText);\n\n return;\n }\n\n res.writeHead(500).end(\"Error creating server\");\n\n return;\n }\n\n activeTransports[transport.sessionId] = transport;\n\n let closed = false;\n\n res.on(\"close\", async () => {\n closed = true;\n\n try {\n await server.close();\n } catch (error) {\n console.error(\"Error closing server:\", error);\n }\n\n delete activeTransports[transport.sessionId];\n\n onClose?.(server);\n });\n\n try {\n await server.connect(transport);\n\n await transport.send({\n jsonrpc: \"2.0\",\n method: \"sse/connection\",\n params: { message: \"SSE Connection established\" },\n });\n\n onConnect?.(server);\n } catch (error) {\n if (!closed) {\n console.error(\"Error connecting to server:\", error);\n\n res.writeHead(500).end(\"Error connecting to server\");\n }\n }\n\n return;\n }\n\n if (req.method === \"POST\" && req.url?.startsWith(\"/messages\")) {\n const sessionId = new URL(\n req.url,\n \"https://example.com\",\n ).searchParams.get(\"sessionId\");\n\n if (!sessionId) {\n res.writeHead(400).end(\"No sessionId\");\n\n return;\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;\n }\n\n await activeTransport.handlePostMessage(req, res);\n\n return;\n }\n\n res.writeHead(404).end();\n });\n\n await new Promise((resolve) => {\n httpServer.listen(port, \"::\", () => {\n resolve(undefined);\n });\n });\n\n return {\n close: async () => {\n for (const transport of Object.values(activeTransports)) {\n await 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","import { 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 ServerCapabilities,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\n\nexport const proxyServer = async ({\n server,\n client,\n serverCapabilities,\n}: {\n server: Server;\n client: Client;\n serverCapabilities: ServerCapabilities;\n}) => {\n if (serverCapabilities?.logging) {\n server.setNotificationHandler(\n LoggingMessageNotificationSchema,\n async (args) => {\n return client.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\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"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-proxy",
3
- "version": "2.8.2",
3
+ "version": "2.9.0",
4
4
  "main": "dist/MCPProxy.js",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -19,8 +19,8 @@
19
19
  "author": "Frank Fiegel <frank@glama.ai>",
20
20
  "license": "MIT",
21
21
  "description": "A TypeScript SSE proxy for MCP servers that use stdio transport.",
22
- "module": "dist/MCPProxy.js",
23
- "types": "dist/MCPProxy.d.ts",
22
+ "module": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.6.0",
26
26
  "eventsource": "^3.0.5",
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Forked from https://github.com/modelcontextprotocol/typescript-sdk/blob/66e1508162d37c0b83b0637ebcd7f07946e3d210/src/client/stdio.ts#L90
3
+ */
4
+
5
+ import {
6
+ ReadBuffer,
7
+ serializeMessage,
8
+ } from "@modelcontextprotocol/sdk/shared/stdio.js";
9
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
10
+ import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
11
+ import { ChildProcess, IOType, spawn } from "node:child_process";
12
+ import { Stream } from "node:stream";
13
+
14
+ type TransportEvent =
15
+ | {
16
+ type: "close";
17
+ }
18
+ | {
19
+ type: "error";
20
+ error: Error;
21
+ }
22
+ | {
23
+ type: "data";
24
+ chunk: string;
25
+ }
26
+ | {
27
+ type: "message";
28
+ message: JSONRPCMessage;
29
+ };
30
+
31
+ export type StdioServerParameters = {
32
+ /**
33
+ * The executable to run to start the server.
34
+ */
35
+ command: string;
36
+
37
+ /**
38
+ * Command line arguments to pass to the executable.
39
+ */
40
+ args?: string[];
41
+
42
+ /**
43
+ * The environment to use when spawning the process.
44
+ *
45
+ * If not specified, the result of getDefaultEnvironment() will be used.
46
+ */
47
+ env: Record<string, string>;
48
+
49
+ /**
50
+ * How to handle stderr of the child process. This matches the semantics of Node's `child_process.spawn`.
51
+ *
52
+ * The default is "inherit", meaning messages to stderr will be printed to the parent process's stderr.
53
+ */
54
+ stderr?: IOType | Stream | number;
55
+
56
+ /**
57
+ * The working directory to use when spawning the process.
58
+ *
59
+ * If not specified, the current working directory will be inherited.
60
+ */
61
+ cwd?: string;
62
+
63
+ /**
64
+ * A function to call when an event occurs.
65
+ */
66
+ onEvent?: (event: TransportEvent) => void;
67
+ };
68
+
69
+ /**
70
+ * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
71
+ *
72
+ * This transport is only available in Node.js environments.
73
+ */
74
+ export class StdioClientTransport implements Transport {
75
+ private process?: ChildProcess;
76
+ private abortController: AbortController = new AbortController();
77
+ private readBuffer: ReadBuffer = new ReadBuffer();
78
+ private serverParams: StdioServerParameters;
79
+ private onEvent?: (event: TransportEvent) => void;
80
+
81
+ onclose?: () => void;
82
+ onerror?: (error: Error) => void;
83
+ onmessage?: (message: JSONRPCMessage) => void;
84
+
85
+ constructor(server: StdioServerParameters) {
86
+ this.serverParams = server;
87
+ this.onEvent = server.onEvent;
88
+ }
89
+
90
+ /**
91
+ * Starts the server process and prepares to communicate with it.
92
+ */
93
+ async start(): Promise<void> {
94
+ if (this.process) {
95
+ throw new Error(
96
+ "StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.",
97
+ );
98
+ }
99
+
100
+ return new Promise((resolve, reject) => {
101
+ this.process = spawn(
102
+ this.serverParams.command,
103
+ this.serverParams.args ?? [],
104
+ {
105
+ env: this.serverParams.env,
106
+ stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"],
107
+ shell: false,
108
+ signal: this.abortController.signal,
109
+ cwd: this.serverParams.cwd,
110
+ },
111
+ );
112
+
113
+ this.process.on("error", (error) => {
114
+ if (error.name === "AbortError") {
115
+ // Expected when close() is called.
116
+ this.onclose?.();
117
+ return;
118
+ }
119
+
120
+ reject(error);
121
+ this.onerror?.(error);
122
+ });
123
+
124
+ this.process.on("spawn", () => {
125
+ resolve();
126
+ });
127
+
128
+ this.process.on("close", (_code) => {
129
+ this.onEvent?.({
130
+ type: "close",
131
+ });
132
+
133
+ this.process = undefined;
134
+ this.onclose?.();
135
+ });
136
+
137
+ this.process.stdin?.on("error", (error) => {
138
+ this.onEvent?.({
139
+ type: "error",
140
+ error,
141
+ });
142
+
143
+ this.onerror?.(error);
144
+ });
145
+
146
+ this.process.stdout?.on("data", (chunk) => {
147
+ this.onEvent?.({
148
+ type: "data",
149
+ chunk: chunk.toString(),
150
+ });
151
+
152
+ this.readBuffer.append(chunk);
153
+ this.processReadBuffer();
154
+ });
155
+
156
+ this.process.stdout?.on("error", (error) => {
157
+ this.onEvent?.({
158
+ type: "error",
159
+ error,
160
+ });
161
+
162
+ this.onerror?.(error);
163
+ });
164
+ });
165
+ }
166
+
167
+ /**
168
+ * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
169
+ *
170
+ * This is only available after the process has been started.
171
+ */
172
+ get stderr(): Stream | null {
173
+ return this.process?.stderr ?? null;
174
+ }
175
+
176
+ private processReadBuffer() {
177
+ while (true) {
178
+ try {
179
+ const message = this.readBuffer.readMessage();
180
+
181
+ if (message === null) {
182
+ break;
183
+ }
184
+
185
+ this.onEvent?.({
186
+ type: "message",
187
+ message,
188
+ });
189
+
190
+ this.onmessage?.(message);
191
+ } catch (error) {
192
+ this.onEvent?.({
193
+ type: "error",
194
+ error: error as Error,
195
+ });
196
+
197
+ this.onerror?.(error as Error);
198
+ }
199
+ }
200
+ }
201
+
202
+ async close(): Promise<void> {
203
+ this.onEvent?.({
204
+ type: "close",
205
+ });
206
+
207
+ this.abortController.abort();
208
+ this.process = undefined;
209
+ this.readBuffer.clear();
210
+ }
211
+
212
+ send(message: JSONRPCMessage): Promise<void> {
213
+ return new Promise((resolve) => {
214
+ if (!this.process?.stdin) {
215
+ throw new Error("Not connected");
216
+ }
217
+
218
+ const json = serializeMessage(message);
219
+ if (this.process.stdin.write(json)) {
220
+ resolve();
221
+ } else {
222
+ this.process.stdin.once("drain", resolve);
223
+ }
224
+ });
225
+ }
226
+ }