mcp-proxy 2.14.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -31
- package/dist/bin/mcp-proxy.js +8 -18
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/chunk-EX66KMYB.js +453 -0
- package/dist/chunk-EX66KMYB.js.map +1 -0
- package/dist/index.d.ts +3 -20
- package/dist/index.js +3 -5
- package/dist/index.js.map +1 -1
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/bin/mcp-proxy.ts +7 -17
- package/src/index.ts +1 -2
- package/src/{startHTTPStreamServer.test.ts → startHTTPServer.test.ts} +114 -3
- package/src/startHTTPServer.ts +444 -0
- package/dist/chunk-43AXMLZU.js +0 -471
- package/dist/chunk-43AXMLZU.js.map +0 -1
- package/src/startHTTPStreamServer.ts +0 -281
- package/src/startSSEServer.test.ts +0 -127
- package/src/startSSEServer.ts +0 -187
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MCP Proxy
|
|
2
2
|
|
|
3
|
-
A TypeScript SSE proxy for [MCP](https://modelcontextprotocol.io/) servers that use `stdio` transport.
|
|
3
|
+
A TypeScript streamable HTTP and SSE proxy for [MCP](https://modelcontextprotocol.io/) servers that use `stdio` transport.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
6
|
> CORS is enabled by default.
|
|
@@ -22,16 +22,14 @@ npm install mcp-proxy
|
|
|
22
22
|
### Command-line
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
npx mcp-proxy --port 8080
|
|
25
|
+
npx mcp-proxy --port 8080 tsx server.js
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
This starts a server and `stdio` server (`tsx server.js`). The server listens on port 8080 and
|
|
28
|
+
This starts a server and `stdio` server (`tsx server.js`). The server listens on port 8080 and `/stream` (streamable HTTP) and `/sse` (SSE) endpoints, and forwards messages to the `stdio` server.
|
|
29
29
|
|
|
30
30
|
options:
|
|
31
31
|
|
|
32
32
|
- `--port`: Specify the port to listen on (default: 8080)
|
|
33
|
-
- `--endpoint`: Specify the endpoint to listen on (default: `/sse` for SSE server, `/stream` for stream server)
|
|
34
|
-
- `--server`: Specify the server type to use (default: `sse`)
|
|
35
33
|
- `--debug`: Enable debug logging
|
|
36
34
|
|
|
37
35
|
### Node.js SDK
|
|
@@ -59,40 +57,20 @@ proxyServer({
|
|
|
59
57
|
|
|
60
58
|
In this example, the server will proxy all requests to the client and vice versa.
|
|
61
59
|
|
|
62
|
-
#### `
|
|
60
|
+
#### `startHTTPServer`
|
|
63
61
|
|
|
64
|
-
Starts a proxy that listens on a `port
|
|
62
|
+
Starts a proxy that listens on a `port`, and sends messages to the attached server via `StreamableHTTPServerTransport` and `SSEServerTransport`.
|
|
65
63
|
|
|
66
64
|
```ts
|
|
67
65
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
68
|
-
import {
|
|
66
|
+
import { startHTTPServer } from "mcp-proxy";
|
|
69
67
|
|
|
70
|
-
const { close } = await
|
|
71
|
-
port: 8080,
|
|
72
|
-
endpoint: "/sse",
|
|
68
|
+
const { close } = await startHTTPServer({
|
|
73
69
|
createServer: async () => {
|
|
74
70
|
return new Server();
|
|
75
71
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
close();
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
#### `startHTTPStreamServer`
|
|
82
|
-
|
|
83
|
-
Starts a proxy that listens on a `port` and `endpoint`, and sends messages to the attached server via `StreamableHTTPServerTransport`.
|
|
84
|
-
|
|
85
|
-
```ts
|
|
86
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
87
|
-
import { startHTTPStreamServer, InMemoryEventStore } from "mcp-proxy";
|
|
88
|
-
|
|
89
|
-
const { close } = await startHTTPStreamServer({
|
|
72
|
+
eventStore: new InMemoryEventStore(),
|
|
90
73
|
port: 8080,
|
|
91
|
-
endpoint: "/stream",
|
|
92
|
-
createServer: async () => {
|
|
93
|
-
return new Server();
|
|
94
|
-
},
|
|
95
|
-
eventStore: new InMemoryEventStore(), // optional you can provide your own event store
|
|
96
74
|
});
|
|
97
75
|
|
|
98
76
|
close();
|
|
@@ -107,7 +85,7 @@ import { ServerType, startStdioServer } from "./startStdioServer.js";
|
|
|
107
85
|
|
|
108
86
|
await startStdioServer({
|
|
109
87
|
serverType: ServerType.SSE,
|
|
110
|
-
url: "http://127.0.0.1:
|
|
88
|
+
url: "http://127.0.0.1:8080/sse",
|
|
111
89
|
});
|
|
112
90
|
```
|
|
113
91
|
|
package/dist/bin/mcp-proxy.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
InMemoryEventStore,
|
|
4
4
|
proxyServer,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "../chunk-43AXMLZU.js";
|
|
5
|
+
startHTTPServer
|
|
6
|
+
} from "../chunk-EX66KMYB.js";
|
|
8
7
|
|
|
9
8
|
// src/bin/mcp-proxy.ts
|
|
10
9
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -211,7 +210,7 @@ var proxy = async () => {
|
|
|
211
210
|
await connect(client);
|
|
212
211
|
const serverVersion = client.getServerVersion();
|
|
213
212
|
const serverCapabilities = client.getServerCapabilities();
|
|
214
|
-
console.info("starting
|
|
213
|
+
console.info("starting server on port %d", argv.port);
|
|
215
214
|
const createServer = async () => {
|
|
216
215
|
const server = new Server(serverVersion, {
|
|
217
216
|
capabilities: serverCapabilities
|
|
@@ -223,20 +222,11 @@ var proxy = async () => {
|
|
|
223
222
|
});
|
|
224
223
|
return server;
|
|
225
224
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
} else {
|
|
233
|
-
await startHTTPStreamServer({
|
|
234
|
-
createServer,
|
|
235
|
-
endpoint: argv.endpoint || "/stream",
|
|
236
|
-
eventStore: new InMemoryEventStore(),
|
|
237
|
-
port: argv.port
|
|
238
|
-
});
|
|
239
|
-
}
|
|
225
|
+
await startHTTPServer({
|
|
226
|
+
createServer,
|
|
227
|
+
eventStore: new InMemoryEventStore(),
|
|
228
|
+
port: argv.port
|
|
229
|
+
});
|
|
240
230
|
};
|
|
241
231
|
var main = async () => {
|
|
242
232
|
process.on("SIGINT", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/mcp-proxy.ts","../../src/StdioClientTransport.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 { startHTTPStreamServer } from \"../startHTTPStreamServer.js\";\nimport { startSSEServer } from \"../startSSEServer.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 .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 port: {\n default: 8080,\n describe: \"The port to listen on\",\n type: \"number\",\n },\n server: {\n choices: [\"sse\", \"stream\"],\n default: \"sse\",\n describe: \"The server type to use (sse or stream)\",\n type: \"string\",\n },\n })\n .help()\n .parseAsync();\n\nconst connect = async (client: Client) => {\n const transport = new StdioClientTransport({\n args: argv.args,\n command: argv.command,\n env: process.env as Record<string, string>,\n onEvent: (event) => {\n if (argv.debug) {\n console.debug(\"transport event\", event);\n }\n },\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 the %s server on port %d\", argv.server, 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 if (argv.server === \"sse\") {\n await startSSEServer({\n createServer,\n endpoint: argv.endpoint || (\"/sse\" as `/${string}`),\n port: argv.port,\n });\n } else {\n await startHTTPStreamServer({\n createServer,\n endpoint: argv.endpoint || (\"/stream\" as `/${string}`),\n eventStore: new InMemoryEventStore(),\n port: argv.port,\n });\n }\n};\n\nconst main = async () => {\n process.on(\"SIGINT\", () => {\n console.info(\"SIGINT received, shutting down\");\n\n setTimeout(() => {\n process.exit(0);\n }, 1000);\n });\n\n try {\n await proxy();\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\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 * 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 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: 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 this.process.stdout?.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 this.process.stdout?.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"],"mappings":";;;;;;;;;AAEA,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,SAAS,eAAe;;;ACJxB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAGP,SAA+B,aAAa;AA+DrC,IAAM,uBAAN,MAAgD;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAAwB;AAC1B,WAAO,KAAK,SAAS,UAAU;AAAA,EACjC;AAAA,EACQ,kBAAmC,IAAI,gBAAgB;AAAA,EAEvD;AAAA,EACA;AAAA,EACA,aAAyB,IAAI,WAAW;AAAA,EAExC;AAAA,EAER,YAAY,QAA+B;AACzC,SAAK,eAAe;AACpB,SAAK,UAAU,OAAO;AAAA,EACxB;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,UAAU;AAAA,MACb,MAAM;AAAA,IACR,CAAC;AAED,SAAK,gBAAgB,MAAM;AAC3B,SAAK,UAAU;AACf,SAAK,WAAW,MAAM;AAAA,EACxB;AAAA,EAEA,KAAK,SAAwC;AAC3C,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,CAAC,KAAK,SAAS,OAAO;AACxB,cAAM,IAAI,MAAM,eAAe;AAAA,MACjC;AAEA,YAAM,OAAO,iBAAiB,OAAO;AACrC,UAAI,KAAK,QAAQ,MAAM,MAAM,IAAI,GAAG;AAClC,gBAAQ;AAAA,MACV,OAAO;AACL,aAAK,QAAQ,MAAM,KAAK,SAAS,OAAO;AAAA,MAC1C;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,WAAK,UAAU;AAAA,QACb,KAAK,aAAa;AAAA,QAClB,KAAK,aAAa,QAAQ,CAAC;AAAA,QAC3B;AAAA,UACE,KAAK,KAAK,aAAa;AAAA,UACvB,KAAK,KAAK,aAAa;AAAA,UACvB,OAAO;AAAA,UACP,QAAQ,KAAK,gBAAgB;AAAA,UAC7B,OAAO,CAAC,QAAQ,QAAQ,KAAK,aAAa,UAAU,SAAS;AAAA,QAC/D;AAAA,MACF;AAEA,WAAK,QAAQ,GAAG,SAAS,CAAC,UAAU;AAClC,YAAI,MAAM,SAAS,cAAc;AAE/B,eAAK,UAAU;AACf;AAAA,QACF;AAEA,eAAO,KAAK;AACZ,aAAK,UAAU,KAAK;AAAA,MACtB,CAAC;AAED,WAAK,QAAQ,GAAG,SAAS,MAAM;AAC7B,gBAAQ;AAAA,MACV,CAAC;AAGD,WAAK,QAAQ,GAAG,SAAS,CAAC,UAAU;AAClC,aAAK,UAAU;AAAA,UACb,MAAM;AAAA,QACR,CAAC;AAED,aAAK,UAAU;AACf,aAAK,UAAU;AAAA,MACjB,CAAC;AAED,WAAK,QAAQ,OAAO,GAAG,SAAS,CAAC,UAAU;AACzC,aAAK,UAAU;AAAA,UACb;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAED,aAAK,UAAU,KAAK;AAAA,MACtB,CAAC;AAED,WAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,UAAU;AACzC,aAAK,UAAU;AAAA,UACb,OAAO,MAAM,SAAS;AAAA,UACtB,MAAM;AAAA,QACR,CAAC;AAED,aAAK,WAAW,OAAO,KAAK;AAC5B,aAAK,kBAAkB;AAAA,MACzB,CAAC;AAED,WAAK,QAAQ,QAAQ,GAAG,SAAS,CAAC,UAAU;AAC1C,aAAK,UAAU;AAAA,UACb;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAED,aAAK,UAAU,KAAK;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEQ,oBAAoB;AAC1B,WAAO,MAAM;AACX,UAAI;AACF,cAAM,UAAU,KAAK,WAAW,YAAY;AAE5C,YAAI,YAAY,MAAM;AACpB;AAAA,QACF;AAEA,aAAK,UAAU;AAAA,UACb;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAED,aAAK,YAAY,OAAO;AAAA,MAC1B,SAAS,OAAO;AACd,aAAK,UAAU;AAAA,UACb;AAAA,UACA,MAAM;AAAA,QACR,CAAC;AAED,aAAK,UAAU,KAAc;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AACF;;;ADlNA,KAAK,QAAQ,eAAe,QAAQ;AAEpC,IAAI,EAAE,iBAAiB,SAAS;AAE9B,SAAO,cAAc;AACvB;AAEA,IAAM,OAAO,MAAM,MAAM,QAAQ,QAAQ,IAAI,CAAC,EAC3C,WAAW,WAAW,EACtB,QAAQ,0BAA0B,kCAAkC,EACpE,WAAW,WAAW;AAAA,EACrB,cAAc;AAAA,EACd,UAAU;AAAA,EACV,MAAM;AACR,CAAC,EACA,WAAW,QAAQ;AAAA,EAClB,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AACR,CAAC,EACA,IAAI,WAAW,EACf,QAAQ;AAAA,EACP,OAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AAAA,EACA,UAAU;AAAA,IACR,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AAAA,EACA,MAAM;AAAA,IACJ,SAAS;AAAA,IACT,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AAAA,EACA,QAAQ;AAAA,IACN,SAAS,CAAC,OAAO,QAAQ;AAAA,IACzB,SAAS;AAAA,IACT,UAAU;AAAA,IACV,MAAM;AAAA,EACR;AACF,CAAC,EACA,KAAK,EACL,WAAW;AAEd,IAAM,UAAU,OAAO,WAAmB;AACxC,QAAM,YAAY,IAAI,qBAAqB;AAAA,IACzC,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,KAAK,QAAQ;AAAA,IACb,SAAS,CAAC,UAAU;AAClB,UAAI,KAAK,OAAO;AACd,gBAAQ,MAAM,mBAAmB,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AAED,QAAM,OAAO,QAAQ,SAAS;AAChC;AAEA,IAAM,QAAQ,YAAY;AACxB,QAAM,SAAS,IAAI;AAAA,IACjB;AAAA,MACE,MAAM;AAAA,MACN,SAAS;AAAA,IACX;AAAA,IACA;AAAA,MACE,cAAc,CAAC;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAEpB,QAAM,gBAAgB,OAAO,iBAAiB;AAK9C,QAAM,qBAAqB,OAAO,sBAAsB;AAIxD,UAAQ,KAAK,qCAAqC,KAAK,QAAQ,KAAK,IAAI;AAExE,QAAM,eAAe,YAAY;AAC/B,UAAM,SAAS,IAAI,OAAO,eAAe;AAAA,MACvC,cAAc;AAAA,IAChB,CAAC;AAED,gBAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,WAAW,OAAO;AACzB,UAAM,eAAe;AAAA,MACnB;AAAA,MACA,UAAU,KAAK,YAAa;AAAA,MAC5B,MAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH,OAAO;AACL,UAAM,sBAAsB;AAAA,MAC1B;AAAA,MACA,UAAU,KAAK,YAAa;AAAA,MAC5B,YAAY,IAAI,mBAAmB;AAAA,MACnC,MAAM,KAAK;AAAA,IACb,CAAC;AAAA,EACH;AACF;AAEA,IAAM,OAAO,YAAY;AACvB,UAAQ,GAAG,UAAU,MAAM;AACzB,YAAQ,KAAK,gCAAgC;AAE7C,eAAW,MAAM;AACf,cAAQ,KAAK,CAAC;AAAA,IAChB,GAAG,GAAI;AAAA,EACT,CAAC;AAED,MAAI;AACF,UAAM,MAAM;AAAA,EACd,SAAS,OAAO;AACd,YAAQ,MAAM,6BAA6B,KAAK;AAEhD,eAAW,MAAM;AACf,cAAQ,KAAK,CAAC;AAAA,IAChB,GAAG,GAAI;AAAA,EACT;AACF;AAEA,MAAM,KAAK;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/bin/mcp-proxy.ts","../../src/StdioClientTransport.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 .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 port: {\n default: 8080,\n describe: \"The port to listen on\",\n type: \"number\",\n },\n server: {\n choices: [\"sse\", \"stream\"],\n default: \"sse\",\n describe: \"The server type to use (sse or stream)\",\n type: \"string\",\n },\n })\n .help()\n .parseAsync();\n\nconst connect = async (client: Client) => {\n const transport = new StdioClientTransport({\n args: argv.args,\n command: argv.command,\n env: process.env as Record<string, string>,\n onEvent: (event) => {\n if (argv.debug) {\n console.debug(\"transport event\", event);\n }\n },\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 await startHTTPServer({\n createServer,\n eventStore: new InMemoryEventStore(),\n port: argv.port,\n });\n};\n\nconst main = async () => {\n process.on(\"SIGINT\", () => {\n console.info(\"SIGINT received, shutting down\");\n\n setTimeout(() => {\n process.exit(0);\n }, 1000);\n });\n\n try {\n await proxy();\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\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 * 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 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: 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 this.process.stdout?.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 this.process.stdout?.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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// src/InMemoryEventStore.ts
|
|
2
|
+
var InMemoryEventStore = class {
|
|
3
|
+
events = /* @__PURE__ */ new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Replays events that occurred after a specific event ID
|
|
6
|
+
* Implements EventStore.replayEventsAfter
|
|
7
|
+
*/
|
|
8
|
+
async replayEventsAfter(lastEventId, {
|
|
9
|
+
send
|
|
10
|
+
}) {
|
|
11
|
+
if (!lastEventId || !this.events.has(lastEventId)) {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
const streamId = this.getStreamIdFromEventId(lastEventId);
|
|
15
|
+
if (!streamId) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
let foundLastEvent = false;
|
|
19
|
+
const sortedEvents = [...this.events.entries()].sort(
|
|
20
|
+
(a, b) => a[0].localeCompare(b[0])
|
|
21
|
+
);
|
|
22
|
+
for (const [
|
|
23
|
+
eventId,
|
|
24
|
+
{ message, streamId: eventStreamId }
|
|
25
|
+
] of sortedEvents) {
|
|
26
|
+
if (eventStreamId !== streamId) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (eventId === lastEventId) {
|
|
30
|
+
foundLastEvent = true;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (foundLastEvent) {
|
|
34
|
+
await send(eventId, message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return streamId;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Stores an event with a generated event ID
|
|
41
|
+
* Implements EventStore.storeEvent
|
|
42
|
+
*/
|
|
43
|
+
async storeEvent(streamId, message) {
|
|
44
|
+
const eventId = this.generateEventId(streamId);
|
|
45
|
+
this.events.set(eventId, { message, streamId });
|
|
46
|
+
return eventId;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Generates a unique event ID for a given stream ID
|
|
50
|
+
*/
|
|
51
|
+
generateEventId(streamId) {
|
|
52
|
+
return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extracts the stream ID from an event ID
|
|
56
|
+
*/
|
|
57
|
+
getStreamIdFromEventId(eventId) {
|
|
58
|
+
const parts = eventId.split("_");
|
|
59
|
+
return parts.length > 0 ? parts[0] : "";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/proxyServer.ts
|
|
64
|
+
import {
|
|
65
|
+
CallToolRequestSchema,
|
|
66
|
+
CompleteRequestSchema,
|
|
67
|
+
GetPromptRequestSchema,
|
|
68
|
+
ListPromptsRequestSchema,
|
|
69
|
+
ListResourcesRequestSchema,
|
|
70
|
+
ListResourceTemplatesRequestSchema,
|
|
71
|
+
ListToolsRequestSchema,
|
|
72
|
+
LoggingMessageNotificationSchema,
|
|
73
|
+
ReadResourceRequestSchema,
|
|
74
|
+
ResourceUpdatedNotificationSchema,
|
|
75
|
+
SubscribeRequestSchema,
|
|
76
|
+
UnsubscribeRequestSchema
|
|
77
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
78
|
+
var proxyServer = async ({
|
|
79
|
+
client,
|
|
80
|
+
server,
|
|
81
|
+
serverCapabilities
|
|
82
|
+
}) => {
|
|
83
|
+
if (serverCapabilities?.logging) {
|
|
84
|
+
server.setNotificationHandler(
|
|
85
|
+
LoggingMessageNotificationSchema,
|
|
86
|
+
async (args) => {
|
|
87
|
+
return client.notification(args);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
client.setNotificationHandler(
|
|
91
|
+
LoggingMessageNotificationSchema,
|
|
92
|
+
async (args) => {
|
|
93
|
+
return server.notification(args);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (serverCapabilities?.prompts) {
|
|
98
|
+
server.setRequestHandler(GetPromptRequestSchema, async (args) => {
|
|
99
|
+
return client.getPrompt(args.params);
|
|
100
|
+
});
|
|
101
|
+
server.setRequestHandler(ListPromptsRequestSchema, async (args) => {
|
|
102
|
+
return client.listPrompts(args.params);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (serverCapabilities?.resources) {
|
|
106
|
+
server.setRequestHandler(ListResourcesRequestSchema, async (args) => {
|
|
107
|
+
return client.listResources(args.params);
|
|
108
|
+
});
|
|
109
|
+
server.setRequestHandler(
|
|
110
|
+
ListResourceTemplatesRequestSchema,
|
|
111
|
+
async (args) => {
|
|
112
|
+
return client.listResourceTemplates(args.params);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (args) => {
|
|
116
|
+
return client.readResource(args.params);
|
|
117
|
+
});
|
|
118
|
+
if (serverCapabilities?.resources.subscribe) {
|
|
119
|
+
server.setNotificationHandler(
|
|
120
|
+
ResourceUpdatedNotificationSchema,
|
|
121
|
+
async (args) => {
|
|
122
|
+
return client.notification(args);
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
server.setRequestHandler(SubscribeRequestSchema, async (args) => {
|
|
126
|
+
return client.subscribeResource(args.params);
|
|
127
|
+
});
|
|
128
|
+
server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {
|
|
129
|
+
return client.unsubscribeResource(args.params);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (serverCapabilities?.tools) {
|
|
134
|
+
server.setRequestHandler(CallToolRequestSchema, async (args) => {
|
|
135
|
+
return client.callTool(args.params);
|
|
136
|
+
});
|
|
137
|
+
server.setRequestHandler(ListToolsRequestSchema, async (args) => {
|
|
138
|
+
return client.listTools(args.params);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
server.setRequestHandler(CompleteRequestSchema, async (args) => {
|
|
142
|
+
return client.complete(args.params);
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/startHTTPServer.ts
|
|
147
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
148
|
+
import {
|
|
149
|
+
StreamableHTTPServerTransport
|
|
150
|
+
} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
151
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
152
|
+
import http from "http";
|
|
153
|
+
import { randomUUID } from "crypto";
|
|
154
|
+
var getBody = (request) => {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
const bodyParts = [];
|
|
157
|
+
let body;
|
|
158
|
+
request.on("data", (chunk) => {
|
|
159
|
+
bodyParts.push(chunk);
|
|
160
|
+
}).on("end", () => {
|
|
161
|
+
body = Buffer.concat(bodyParts).toString();
|
|
162
|
+
resolve(JSON.parse(body));
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
var handleStreamRequest = async ({
|
|
167
|
+
activeTransports,
|
|
168
|
+
createServer,
|
|
169
|
+
endpoint,
|
|
170
|
+
eventStore,
|
|
171
|
+
onClose,
|
|
172
|
+
onConnect,
|
|
173
|
+
req,
|
|
174
|
+
res
|
|
175
|
+
}) => {
|
|
176
|
+
if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
177
|
+
try {
|
|
178
|
+
const sessionId = Array.isArray(req.headers["mcp-session-id"]) ? req.headers["mcp-session-id"][0] : req.headers["mcp-session-id"];
|
|
179
|
+
let transport;
|
|
180
|
+
let server;
|
|
181
|
+
const body = await getBody(req);
|
|
182
|
+
if (sessionId && activeTransports[sessionId]) {
|
|
183
|
+
transport = activeTransports[sessionId].transport;
|
|
184
|
+
server = activeTransports[sessionId].server;
|
|
185
|
+
} else if (!sessionId && isInitializeRequest(body)) {
|
|
186
|
+
transport = new StreamableHTTPServerTransport({
|
|
187
|
+
eventStore: eventStore || new InMemoryEventStore(),
|
|
188
|
+
onsessioninitialized: (_sessionId) => {
|
|
189
|
+
activeTransports[_sessionId] = {
|
|
190
|
+
server,
|
|
191
|
+
transport
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
sessionIdGenerator: randomUUID
|
|
195
|
+
});
|
|
196
|
+
transport.onclose = async () => {
|
|
197
|
+
const sid = transport.sessionId;
|
|
198
|
+
if (sid && activeTransports[sid]) {
|
|
199
|
+
onClose?.(server);
|
|
200
|
+
try {
|
|
201
|
+
await server.close();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error("Error closing server:", error);
|
|
204
|
+
}
|
|
205
|
+
delete activeTransports[sid];
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
try {
|
|
209
|
+
server = await createServer(req);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof Response) {
|
|
212
|
+
res.writeHead(error.status).end(error.statusText);
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
res.writeHead(500).end("Error creating server");
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
server.connect(transport);
|
|
219
|
+
onConnect?.(server);
|
|
220
|
+
await transport.handleRequest(req, res, body);
|
|
221
|
+
return true;
|
|
222
|
+
} else {
|
|
223
|
+
res.setHeader("Content-Type", "application/json");
|
|
224
|
+
res.writeHead(400).end(
|
|
225
|
+
JSON.stringify({
|
|
226
|
+
error: {
|
|
227
|
+
code: -32e3,
|
|
228
|
+
message: "Bad Request: No valid session ID provided"
|
|
229
|
+
},
|
|
230
|
+
id: null,
|
|
231
|
+
jsonrpc: "2.0"
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
await transport.handleRequest(req, res, body);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
console.error("Error handling request:", error);
|
|
239
|
+
res.setHeader("Content-Type", "application/json");
|
|
240
|
+
res.writeHead(500).end(
|
|
241
|
+
JSON.stringify({
|
|
242
|
+
error: { code: -32603, message: "Internal Server Error" },
|
|
243
|
+
id: null,
|
|
244
|
+
jsonrpc: "2.0"
|
|
245
|
+
})
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
251
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
252
|
+
const activeTransport = sessionId ? activeTransports[sessionId] : void 0;
|
|
253
|
+
if (!sessionId) {
|
|
254
|
+
res.writeHead(400).end("No sessionId");
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (!activeTransport) {
|
|
258
|
+
res.writeHead(400).end("No active transport");
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
const lastEventId = req.headers["last-event-id"];
|
|
262
|
+
if (lastEventId) {
|
|
263
|
+
console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
|
|
264
|
+
} else {
|
|
265
|
+
console.log(`Establishing new SSE stream for session ${sessionId}`);
|
|
266
|
+
}
|
|
267
|
+
await activeTransport.transport.handleRequest(req, res);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
if (req.method === "DELETE" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
271
|
+
console.log("received delete request");
|
|
272
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
273
|
+
if (!sessionId) {
|
|
274
|
+
res.writeHead(400).end("Invalid or missing sessionId");
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
console.log("received delete request for session", sessionId);
|
|
278
|
+
const { server, transport } = activeTransports[sessionId];
|
|
279
|
+
if (!transport) {
|
|
280
|
+
res.writeHead(400).end("No active transport");
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
await transport.handleRequest(req, res);
|
|
285
|
+
onClose?.(server);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.error("Error handling delete request:", error);
|
|
288
|
+
res.writeHead(500).end("Error handling delete request");
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
};
|
|
294
|
+
var handleSSERequest = async ({
|
|
295
|
+
activeTransports,
|
|
296
|
+
createServer,
|
|
297
|
+
endpoint,
|
|
298
|
+
onClose,
|
|
299
|
+
onConnect,
|
|
300
|
+
req,
|
|
301
|
+
res
|
|
302
|
+
}) => {
|
|
303
|
+
if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
304
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
305
|
+
let server;
|
|
306
|
+
try {
|
|
307
|
+
server = await createServer(req);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof Response) {
|
|
310
|
+
res.writeHead(error.status).end(error.statusText);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
res.writeHead(500).end("Error creating server");
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
activeTransports[transport.sessionId] = transport;
|
|
317
|
+
let closed = false;
|
|
318
|
+
res.on("close", async () => {
|
|
319
|
+
closed = true;
|
|
320
|
+
try {
|
|
321
|
+
await server.close();
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error("Error closing server:", error);
|
|
324
|
+
}
|
|
325
|
+
delete activeTransports[transport.sessionId];
|
|
326
|
+
onClose?.(server);
|
|
327
|
+
});
|
|
328
|
+
try {
|
|
329
|
+
await server.connect(transport);
|
|
330
|
+
await transport.send({
|
|
331
|
+
jsonrpc: "2.0",
|
|
332
|
+
method: "sse/connection",
|
|
333
|
+
params: { message: "SSE Connection established" }
|
|
334
|
+
});
|
|
335
|
+
onConnect?.(server);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (!closed) {
|
|
338
|
+
console.error("Error connecting to server:", error);
|
|
339
|
+
res.writeHead(500).end("Error connecting to server");
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
if (req.method === "POST" && req.url?.startsWith("/messages")) {
|
|
345
|
+
const sessionId = new URL(req.url, "https://example.com").searchParams.get(
|
|
346
|
+
"sessionId"
|
|
347
|
+
);
|
|
348
|
+
if (!sessionId) {
|
|
349
|
+
res.writeHead(400).end("No sessionId");
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
const activeTransport = activeTransports[sessionId];
|
|
353
|
+
if (!activeTransport) {
|
|
354
|
+
res.writeHead(400).end("No active transport");
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
await activeTransport.handlePostMessage(req, res);
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
};
|
|
362
|
+
var startHTTPServer = async ({
|
|
363
|
+
createServer,
|
|
364
|
+
eventStore,
|
|
365
|
+
onClose,
|
|
366
|
+
onConnect,
|
|
367
|
+
onUnhandledRequest,
|
|
368
|
+
port
|
|
369
|
+
}) => {
|
|
370
|
+
const activeSSETransports = {};
|
|
371
|
+
const activeStreamTransports = {};
|
|
372
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
373
|
+
if (req.headers.origin) {
|
|
374
|
+
try {
|
|
375
|
+
const origin = new URL(req.headers.origin);
|
|
376
|
+
res.setHeader("Access-Control-Allow-Origin", origin.origin);
|
|
377
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
378
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
379
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error("Error parsing origin:", error);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (req.method === "OPTIONS") {
|
|
385
|
+
res.writeHead(204);
|
|
386
|
+
res.end();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (req.method === "GET" && req.url === `/ping`) {
|
|
390
|
+
res.writeHead(200).end("pong");
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (await handleSSERequest({
|
|
394
|
+
activeTransports: activeSSETransports,
|
|
395
|
+
createServer,
|
|
396
|
+
endpoint: "/sse",
|
|
397
|
+
onClose,
|
|
398
|
+
onConnect,
|
|
399
|
+
req,
|
|
400
|
+
res
|
|
401
|
+
})) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (await handleStreamRequest({
|
|
405
|
+
activeTransports: activeStreamTransports,
|
|
406
|
+
createServer,
|
|
407
|
+
endpoint: "/stream",
|
|
408
|
+
eventStore,
|
|
409
|
+
onClose,
|
|
410
|
+
onConnect,
|
|
411
|
+
req,
|
|
412
|
+
res
|
|
413
|
+
})) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (onUnhandledRequest) {
|
|
417
|
+
await onUnhandledRequest(req, res);
|
|
418
|
+
} else {
|
|
419
|
+
res.writeHead(404).end();
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
await new Promise((resolve) => {
|
|
423
|
+
httpServer.listen(port, "::", () => {
|
|
424
|
+
resolve(void 0);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
return {
|
|
428
|
+
close: async () => {
|
|
429
|
+
for (const transport of Object.values(activeSSETransports)) {
|
|
430
|
+
await transport.close();
|
|
431
|
+
}
|
|
432
|
+
for (const transport of Object.values(activeStreamTransports)) {
|
|
433
|
+
await transport.transport.close();
|
|
434
|
+
}
|
|
435
|
+
return new Promise((resolve, reject) => {
|
|
436
|
+
httpServer.close((error) => {
|
|
437
|
+
if (error) {
|
|
438
|
+
reject(error);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
resolve();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export {
|
|
449
|
+
InMemoryEventStore,
|
|
450
|
+
proxyServer,
|
|
451
|
+
startHTTPServer
|
|
452
|
+
};
|
|
453
|
+
//# sourceMappingURL=chunk-EX66KMYB.js.map
|