mcp-proxy 2.12.2 → 2.13.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 +28 -1
- package/dist/bin/mcp-proxy.js +37 -20
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/chunk-BLDWVJ5G.js +461 -0
- package/dist/chunk-BLDWVJ5G.js.map +1 -0
- package/dist/index.d.ts +71 -19
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/eslint.config.js +8 -1
- package/package.json +1 -1
- package/src/InMemoryEventStore.ts +91 -0
- package/src/bin/mcp-proxy.ts +38 -20
- package/src/index.ts +3 -1
- package/src/startHTTPStreamServer.test.ts +127 -0
- package/src/startHTTPStreamServer.ts +280 -0
- package/dist/chunk-HTMOSQ6H.js +0 -202
- package/dist/chunk-HTMOSQ6H.js.map +0 -1
package/README.md
CHANGED
|
@@ -25,7 +25,14 @@ npm install mcp-proxy
|
|
|
25
25
|
npx mcp-proxy --port 8080 --endpoint /sse tsx server.js
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
This starts
|
|
28
|
+
This starts a server and `stdio` server (`tsx server.js`). The server listens on port 8080 and endpoint `/sse` by default, and forwards messages to the `stdio` server.
|
|
29
|
+
|
|
30
|
+
options:
|
|
31
|
+
|
|
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
|
+
- `--debug`: Enable debug logging
|
|
29
36
|
|
|
30
37
|
### Node.js SDK
|
|
31
38
|
|
|
@@ -71,6 +78,26 @@ const { close } = await startSSEServer({
|
|
|
71
78
|
close();
|
|
72
79
|
```
|
|
73
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({
|
|
90
|
+
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
|
+
});
|
|
97
|
+
|
|
98
|
+
close();
|
|
99
|
+
```
|
|
100
|
+
|
|
74
101
|
#### `tapTransport`
|
|
75
102
|
|
|
76
103
|
Taps into a transport and logs events.
|
package/dist/bin/mcp-proxy.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
InMemoryEventStore,
|
|
3
4
|
proxyServer,
|
|
5
|
+
startHTTPStreamServer,
|
|
4
6
|
startSSEServer
|
|
5
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-BLDWVJ5G.js";
|
|
6
8
|
|
|
7
9
|
// src/bin/mcp-proxy.ts
|
|
8
10
|
import yargs from "yargs";
|
|
@@ -168,13 +170,18 @@ var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$
|
|
|
168
170
|
},
|
|
169
171
|
endpoint: {
|
|
170
172
|
type: "string",
|
|
171
|
-
describe: "The endpoint to listen on
|
|
172
|
-
default: "/sse"
|
|
173
|
+
describe: "The endpoint to listen on"
|
|
173
174
|
},
|
|
174
175
|
port: {
|
|
175
176
|
type: "number",
|
|
176
|
-
describe: "The port to listen on
|
|
177
|
+
describe: "The port to listen on",
|
|
177
178
|
default: 8080
|
|
179
|
+
},
|
|
180
|
+
server: {
|
|
181
|
+
type: "string",
|
|
182
|
+
describe: "The server type to use (sse or stream)",
|
|
183
|
+
choices: ["sse", "stream"],
|
|
184
|
+
default: "sse"
|
|
178
185
|
}
|
|
179
186
|
}).help().parseAsync();
|
|
180
187
|
var connect = async (client) => {
|
|
@@ -204,22 +211,32 @@ var proxy = async () => {
|
|
|
204
211
|
await connect(client);
|
|
205
212
|
const serverVersion = client.getServerVersion();
|
|
206
213
|
const serverCapabilities = client.getServerCapabilities();
|
|
207
|
-
console.info("starting the
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
console.info("starting the %s server on port %d", argv.server, argv.port);
|
|
215
|
+
const createServer = async () => {
|
|
216
|
+
const server = new Server(serverVersion, {
|
|
217
|
+
capabilities: serverCapabilities
|
|
218
|
+
});
|
|
219
|
+
proxyServer({
|
|
220
|
+
server,
|
|
221
|
+
client,
|
|
222
|
+
serverCapabilities
|
|
223
|
+
});
|
|
224
|
+
return server;
|
|
225
|
+
};
|
|
226
|
+
if (argv.server === "sse") {
|
|
227
|
+
await startSSEServer({
|
|
228
|
+
createServer,
|
|
229
|
+
port: argv.port,
|
|
230
|
+
endpoint: argv.endpoint || "/sse"
|
|
231
|
+
});
|
|
232
|
+
} else {
|
|
233
|
+
await startHTTPStreamServer({
|
|
234
|
+
createServer,
|
|
235
|
+
port: argv.port,
|
|
236
|
+
endpoint: argv.endpoint || "/stream",
|
|
237
|
+
eventStore: new InMemoryEventStore()
|
|
238
|
+
});
|
|
239
|
+
}
|
|
223
240
|
};
|
|
224
241
|
var main = async () => {
|
|
225
242
|
process.on("SIGINT", () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/bin/mcp-proxy.ts","../../src/StdioClientTransport.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\";\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 .env(\"MCP_PROXY\")\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 if (argv.debug) {\n console.debug(\"transport event\", event);\n }\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 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\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"],"mappings":";;;;;;;AAEA,OAAO,WAAW;AAClB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,kBAAkB;;;ACH3B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAGP,SAA+B,aAAa;AA+DrC,IAAM,uBAAN,MAAgD;AAAA,EAC7C;AAAA,EACA,kBAAmC,IAAI,gBAAgB;AAAA,EACvD,aAAyB,IAAI,WAAW;AAAA,EACxC;AAAA,EACA;AAAA,EAER;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,QAA+B;AACzC,SAAK,eAAe;AACpB,SAAK,UAAU,OAAO;AAAA,EACxB;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,OAAO,CAAC,QAAQ,QAAQ,KAAK,aAAa,UAAU,SAAS;AAAA,UAC7D,OAAO;AAAA,UACP,QAAQ,KAAK,gBAAgB;AAAA,UAC7B,KAAK,KAAK,aAAa;AAAA,QACzB;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;AAED,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,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,aAAK,UAAU,KAAK;AAAA,MACtB,CAAC;AAED,WAAK,QAAQ,QAAQ,GAAG,QAAQ,CAAC,UAAU;AACzC,aAAK,UAAU;AAAA,UACb,MAAM;AAAA,UACN,OAAO,MAAM,SAAS;AAAA,QACxB,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,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,aAAK,UAAU,KAAK;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,SAAwB;AAC1B,WAAO,KAAK,SAAS,UAAU;AAAA,EACjC;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,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,aAAK,YAAY,OAAO;AAAA,MAC1B,SAAS,OAAO;AACd,aAAK,UAAU;AAAA,UACb,MAAM;AAAA,UACN;AAAA,QACF,CAAC;AAED,aAAK,UAAU,KAAc;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;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;AACF;;;ADxNA,OAAO,UAAU;AAIjB,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,MAAM;AAAA,EACN,UAAU;AAAA,EACV,cAAc;AAChB,CAAC,EACA,WAAW,QAAQ;AAAA,EAClB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,UAAU;AACZ,CAAC,EACA,IAAI,WAAW,EACf,QAAQ;AAAA,EACP,OAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,UAAU;AAAA,IACR,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AACF,CAAC,EACA,KAAK,EACL,WAAW;AAEd,IAAM,UAAU,OAAO,WAAmB;AACxC,QAAM,YAAY,IAAI,qBAAqB;AAAA,IACzC,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,IACX,KAAK,QAAQ;AAAA,IACb,QAAQ;AAAA,IACR,SAAS,CAAC,UAAU;AAClB,UAAI,KAAK,OAAO;AACd,gBAAQ,MAAM,mBAAmB,KAAK;AAAA,MACxC;AAAA,IACF;AAAA,EACF,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;AAExD,UAAQ,KAAK,sCAAsC,KAAK,IAAI;AAE5D,QAAM,eAAe;AAAA,IACnB,cAAc,YAAY;AACxB,YAAM,SAAS,IAAI,OAAO,eAAe;AAAA,QACvC,cAAc;AAAA,MAChB,CAAC;AAED,kBAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,aAAO;AAAA,IACT;AAAA,IACA,MAAM,KAAK;AAAA,IACX,UAAU,KAAK;AAAA,EACjB,CAAC;AACH;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 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\";\nimport { StdioClientTransport } from \"../StdioClientTransport.js\";\nimport util from \"node:util\";\nimport { startSSEServer } from \"../startSSEServer.js\";\nimport { startHTTPStreamServer } from \"../startHTTPStreamServer.js\";\nimport { proxyServer } from \"../proxyServer.js\";\nimport { InMemoryEventStore } from \"../InMemoryEventStore.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 .env(\"MCP_PROXY\")\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\",\n },\n port: {\n type: \"number\",\n describe: \"The port to listen on\",\n default: 8080,\n },\n server: {\n type: \"string\",\n describe: \"The server type to use (sse or stream)\",\n choices: [\"sse\", \"stream\"],\n default: \"sse\",\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 if (argv.debug) {\n console.debug(\"transport event\", event);\n }\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 %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 server,\n client,\n serverCapabilities,\n });\n\n return server;\n };\n\n if (argv.server === \"sse\") {\n await startSSEServer({\n createServer,\n port: argv.port,\n endpoint: argv.endpoint || (\"/sse\" as `/${string}`),\n });\n } else {\n await startHTTPStreamServer({\n createServer,\n port: argv.port,\n endpoint: argv.endpoint || (\"/stream\" as `/${string}`),\n eventStore: new InMemoryEventStore(),\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\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"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// src/InMemoryEventStore.ts
|
|
2
|
+
var InMemoryEventStore = class {
|
|
3
|
+
events = /* @__PURE__ */ new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Generates a unique event ID for a given stream ID
|
|
6
|
+
*/
|
|
7
|
+
generateEventId(streamId) {
|
|
8
|
+
return `${streamId}_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Extracts the stream ID from an event ID
|
|
12
|
+
*/
|
|
13
|
+
getStreamIdFromEventId(eventId) {
|
|
14
|
+
const parts = eventId.split("_");
|
|
15
|
+
return parts.length > 0 ? parts[0] : "";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Stores an event with a generated event ID
|
|
19
|
+
* Implements EventStore.storeEvent
|
|
20
|
+
*/
|
|
21
|
+
async storeEvent(streamId, message) {
|
|
22
|
+
const eventId = this.generateEventId(streamId);
|
|
23
|
+
this.events.set(eventId, { streamId, message });
|
|
24
|
+
return eventId;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Replays events that occurred after a specific event ID
|
|
28
|
+
* Implements EventStore.replayEventsAfter
|
|
29
|
+
*/
|
|
30
|
+
async replayEventsAfter(lastEventId, {
|
|
31
|
+
send
|
|
32
|
+
}) {
|
|
33
|
+
if (!lastEventId || !this.events.has(lastEventId)) {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
const streamId = this.getStreamIdFromEventId(lastEventId);
|
|
37
|
+
if (!streamId) {
|
|
38
|
+
return "";
|
|
39
|
+
}
|
|
40
|
+
let foundLastEvent = false;
|
|
41
|
+
const sortedEvents = [...this.events.entries()].sort(
|
|
42
|
+
(a, b) => a[0].localeCompare(b[0])
|
|
43
|
+
);
|
|
44
|
+
for (const [
|
|
45
|
+
eventId,
|
|
46
|
+
{ streamId: eventStreamId, message }
|
|
47
|
+
] of sortedEvents) {
|
|
48
|
+
if (eventStreamId !== streamId) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (eventId === lastEventId) {
|
|
52
|
+
foundLastEvent = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (foundLastEvent) {
|
|
56
|
+
await send(eventId, message);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return streamId;
|
|
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
|
+
SubscribeRequestSchema,
|
|
75
|
+
UnsubscribeRequestSchema,
|
|
76
|
+
ResourceUpdatedNotificationSchema
|
|
77
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
78
|
+
var proxyServer = async ({
|
|
79
|
+
server,
|
|
80
|
+
client,
|
|
81
|
+
serverCapabilities
|
|
82
|
+
}) => {
|
|
83
|
+
if (serverCapabilities?.logging) {
|
|
84
|
+
server.setNotificationHandler(
|
|
85
|
+
LoggingMessageNotificationSchema,
|
|
86
|
+
async (args) => {
|
|
87
|
+
return client.notification(args);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (serverCapabilities?.prompts) {
|
|
92
|
+
server.setRequestHandler(GetPromptRequestSchema, async (args) => {
|
|
93
|
+
return client.getPrompt(args.params);
|
|
94
|
+
});
|
|
95
|
+
server.setRequestHandler(ListPromptsRequestSchema, async (args) => {
|
|
96
|
+
return client.listPrompts(args.params);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (serverCapabilities?.resources) {
|
|
100
|
+
server.setRequestHandler(ListResourcesRequestSchema, async (args) => {
|
|
101
|
+
return client.listResources(args.params);
|
|
102
|
+
});
|
|
103
|
+
server.setRequestHandler(
|
|
104
|
+
ListResourceTemplatesRequestSchema,
|
|
105
|
+
async (args) => {
|
|
106
|
+
return client.listResourceTemplates(args.params);
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (args) => {
|
|
110
|
+
return client.readResource(args.params);
|
|
111
|
+
});
|
|
112
|
+
if (serverCapabilities?.resources.subscribe) {
|
|
113
|
+
server.setNotificationHandler(
|
|
114
|
+
ResourceUpdatedNotificationSchema,
|
|
115
|
+
async (args) => {
|
|
116
|
+
return client.notification(args);
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
server.setRequestHandler(SubscribeRequestSchema, async (args) => {
|
|
120
|
+
return client.subscribeResource(args.params);
|
|
121
|
+
});
|
|
122
|
+
server.setRequestHandler(UnsubscribeRequestSchema, async (args) => {
|
|
123
|
+
return client.unsubscribeResource(args.params);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (serverCapabilities?.tools) {
|
|
128
|
+
server.setRequestHandler(CallToolRequestSchema, async (args) => {
|
|
129
|
+
return client.callTool(args.params);
|
|
130
|
+
});
|
|
131
|
+
server.setRequestHandler(ListToolsRequestSchema, async (args) => {
|
|
132
|
+
return client.listTools(args.params);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
server.setRequestHandler(CompleteRequestSchema, async (args) => {
|
|
136
|
+
return client.complete(args.params);
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// src/startHTTPStreamServer.ts
|
|
141
|
+
import {
|
|
142
|
+
StreamableHTTPServerTransport
|
|
143
|
+
} from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
144
|
+
import { randomUUID } from "node:crypto";
|
|
145
|
+
import http from "http";
|
|
146
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
147
|
+
var startHTTPStreamServer = async ({
|
|
148
|
+
port,
|
|
149
|
+
createServer,
|
|
150
|
+
endpoint,
|
|
151
|
+
eventStore,
|
|
152
|
+
onConnect,
|
|
153
|
+
onClose,
|
|
154
|
+
onUnhandledRequest
|
|
155
|
+
}) => {
|
|
156
|
+
const activeTransports = {};
|
|
157
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
158
|
+
if (req.headers.origin) {
|
|
159
|
+
try {
|
|
160
|
+
const origin = new URL(req.headers.origin);
|
|
161
|
+
res.setHeader("Access-Control-Allow-Origin", origin.origin);
|
|
162
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
163
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
164
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error("Error parsing origin:", error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (req.method === "OPTIONS") {
|
|
170
|
+
res.writeHead(204);
|
|
171
|
+
res.end();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (req.method === "GET" && req.url === `/ping`) {
|
|
175
|
+
res.writeHead(200).end("pong");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (req.method === "POST" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
179
|
+
try {
|
|
180
|
+
const sessionId = Array.isArray(req.headers["mcp-session-id"]) ? req.headers["mcp-session-id"][0] : req.headers["mcp-session-id"];
|
|
181
|
+
let transport;
|
|
182
|
+
let server;
|
|
183
|
+
const body = await getBody(req);
|
|
184
|
+
if (sessionId && activeTransports[sessionId]) {
|
|
185
|
+
transport = activeTransports[sessionId].transport;
|
|
186
|
+
server = activeTransports[sessionId].server;
|
|
187
|
+
} else if (!sessionId && isInitializeRequest(body)) {
|
|
188
|
+
transport = new StreamableHTTPServerTransport({
|
|
189
|
+
sessionIdGenerator: randomUUID,
|
|
190
|
+
eventStore: eventStore || new InMemoryEventStore(),
|
|
191
|
+
onsessioninitialized: (_sessionId) => {
|
|
192
|
+
activeTransports[_sessionId] = {
|
|
193
|
+
transport,
|
|
194
|
+
server
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
transport.onclose = async () => {
|
|
199
|
+
const sid = transport.sessionId;
|
|
200
|
+
if (sid && activeTransports[sid]) {
|
|
201
|
+
onClose?.(server);
|
|
202
|
+
try {
|
|
203
|
+
await server.close();
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error("Error closing server:", error);
|
|
206
|
+
}
|
|
207
|
+
delete activeTransports[sid];
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
try {
|
|
211
|
+
server = await createServer(req);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof Response) {
|
|
214
|
+
res.writeHead(error.status).end(error.statusText);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
res.writeHead(500).end("Error creating server");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
server.connect(transport);
|
|
221
|
+
onConnect?.(server);
|
|
222
|
+
await transport.handleRequest(req, res, body);
|
|
223
|
+
return;
|
|
224
|
+
} else {
|
|
225
|
+
res.setHeader("Content-Type", "application/json");
|
|
226
|
+
res.writeHead(400).end(
|
|
227
|
+
JSON.stringify({
|
|
228
|
+
jsonrpc: "2.0",
|
|
229
|
+
error: {
|
|
230
|
+
code: -32e3,
|
|
231
|
+
message: "Bad Request: No valid session ID provided"
|
|
232
|
+
},
|
|
233
|
+
id: null
|
|
234
|
+
})
|
|
235
|
+
);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
await transport.handleRequest(req, res, body);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error("Error handling request:", error);
|
|
241
|
+
res.setHeader("Content-Type", "application/json");
|
|
242
|
+
res.writeHead(500).end(
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
jsonrpc: "2.0",
|
|
245
|
+
error: { code: -32603, message: "Internal Server Error" },
|
|
246
|
+
id: null
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
253
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
254
|
+
const activeTransport = sessionId ? activeTransports[sessionId] : void 0;
|
|
255
|
+
if (!sessionId) {
|
|
256
|
+
res.writeHead(400).end("No sessionId");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!activeTransport) {
|
|
260
|
+
res.writeHead(400).end("No active transport");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const lastEventId = req.headers["last-event-id"];
|
|
264
|
+
if (lastEventId) {
|
|
265
|
+
console.log(`Client reconnecting with Last-Event-ID: ${lastEventId}`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log(`Establishing new SSE stream for session ${sessionId}`);
|
|
268
|
+
}
|
|
269
|
+
await activeTransport.transport.handleRequest(req, res);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (req.method === "DELETE" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
273
|
+
console.log("received delete request");
|
|
274
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
275
|
+
if (!sessionId) {
|
|
276
|
+
res.writeHead(400).end("Invalid or missing sessionId");
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
console.log("received delete request for session", sessionId);
|
|
280
|
+
const { transport, server } = activeTransports[sessionId];
|
|
281
|
+
if (!transport) {
|
|
282
|
+
res.writeHead(400).end("No active transport");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
await transport.handleRequest(req, res);
|
|
287
|
+
onClose?.(server);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error("Error handling delete request:", error);
|
|
290
|
+
res.writeHead(500).end("Error handling delete request");
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (onUnhandledRequest) {
|
|
295
|
+
await onUnhandledRequest(req, res);
|
|
296
|
+
} else {
|
|
297
|
+
res.writeHead(404).end();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await new Promise((resolve) => {
|
|
301
|
+
httpServer.listen(port, "::", () => {
|
|
302
|
+
resolve(void 0);
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
return {
|
|
306
|
+
close: async () => {
|
|
307
|
+
for (const transport of Object.values(activeTransports)) {
|
|
308
|
+
await transport.transport.close();
|
|
309
|
+
}
|
|
310
|
+
return new Promise((resolve, reject) => {
|
|
311
|
+
httpServer.close((error) => {
|
|
312
|
+
if (error) {
|
|
313
|
+
reject(error);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
resolve();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
function getBody(request) {
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
const bodyParts = [];
|
|
325
|
+
let body;
|
|
326
|
+
request.on("data", (chunk) => {
|
|
327
|
+
bodyParts.push(chunk);
|
|
328
|
+
}).on("end", () => {
|
|
329
|
+
body = Buffer.concat(bodyParts).toString();
|
|
330
|
+
resolve(JSON.parse(body));
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/startSSEServer.ts
|
|
336
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
337
|
+
import http2 from "http";
|
|
338
|
+
var startSSEServer = async ({
|
|
339
|
+
port,
|
|
340
|
+
createServer,
|
|
341
|
+
endpoint,
|
|
342
|
+
onConnect,
|
|
343
|
+
onClose,
|
|
344
|
+
onUnhandledRequest
|
|
345
|
+
}) => {
|
|
346
|
+
const activeTransports = {};
|
|
347
|
+
const httpServer = http2.createServer(async (req, res) => {
|
|
348
|
+
if (req.headers.origin) {
|
|
349
|
+
try {
|
|
350
|
+
const origin = new URL(req.headers.origin);
|
|
351
|
+
res.setHeader("Access-Control-Allow-Origin", origin.origin);
|
|
352
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
353
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
354
|
+
res.setHeader("Access-Control-Allow-Headers", "*");
|
|
355
|
+
} catch (error) {
|
|
356
|
+
console.error("Error parsing origin:", error);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (req.method === "OPTIONS") {
|
|
360
|
+
res.writeHead(204);
|
|
361
|
+
res.end();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
if (req.method === "GET" && req.url === `/ping`) {
|
|
365
|
+
res.writeHead(200).end("pong");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (req.method === "GET" && new URL(req.url, "http://localhost").pathname === endpoint) {
|
|
369
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
370
|
+
let server;
|
|
371
|
+
try {
|
|
372
|
+
server = await createServer(req);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error instanceof Response) {
|
|
375
|
+
res.writeHead(error.status).end(error.statusText);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
res.writeHead(500).end("Error creating server");
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
activeTransports[transport.sessionId] = transport;
|
|
382
|
+
let closed = false;
|
|
383
|
+
res.on("close", async () => {
|
|
384
|
+
closed = true;
|
|
385
|
+
try {
|
|
386
|
+
await server.close();
|
|
387
|
+
} catch (error) {
|
|
388
|
+
console.error("Error closing server:", error);
|
|
389
|
+
}
|
|
390
|
+
delete activeTransports[transport.sessionId];
|
|
391
|
+
onClose?.(server);
|
|
392
|
+
});
|
|
393
|
+
try {
|
|
394
|
+
await server.connect(transport);
|
|
395
|
+
await transport.send({
|
|
396
|
+
jsonrpc: "2.0",
|
|
397
|
+
method: "sse/connection",
|
|
398
|
+
params: { message: "SSE Connection established" }
|
|
399
|
+
});
|
|
400
|
+
onConnect?.(server);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
if (!closed) {
|
|
403
|
+
console.error("Error connecting to server:", error);
|
|
404
|
+
res.writeHead(500).end("Error connecting to server");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (req.method === "POST" && req.url?.startsWith("/messages")) {
|
|
410
|
+
const sessionId = new URL(
|
|
411
|
+
req.url,
|
|
412
|
+
"https://example.com"
|
|
413
|
+
).searchParams.get("sessionId");
|
|
414
|
+
if (!sessionId) {
|
|
415
|
+
res.writeHead(400).end("No sessionId");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const activeTransport = activeTransports[sessionId];
|
|
419
|
+
if (!activeTransport) {
|
|
420
|
+
res.writeHead(400).end("No active transport");
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
await activeTransport.handlePostMessage(req, res);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (onUnhandledRequest) {
|
|
427
|
+
await onUnhandledRequest(req, res);
|
|
428
|
+
} else {
|
|
429
|
+
res.writeHead(404).end();
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
await new Promise((resolve) => {
|
|
433
|
+
httpServer.listen(port, "::", () => {
|
|
434
|
+
resolve(void 0);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
return {
|
|
438
|
+
close: async () => {
|
|
439
|
+
for (const transport of Object.values(activeTransports)) {
|
|
440
|
+
await transport.close();
|
|
441
|
+
}
|
|
442
|
+
return new Promise((resolve, reject) => {
|
|
443
|
+
httpServer.close((error) => {
|
|
444
|
+
if (error) {
|
|
445
|
+
reject(error);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
resolve();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
export {
|
|
456
|
+
InMemoryEventStore,
|
|
457
|
+
proxyServer,
|
|
458
|
+
startHTTPStreamServer,
|
|
459
|
+
startSSEServer
|
|
460
|
+
};
|
|
461
|
+
//# sourceMappingURL=chunk-BLDWVJ5G.js.map
|