mcp-proxy 2.12.2 → 2.13.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 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 an SSE server and `stdio` server (`tsx server.js`). The SSE server listens on port 8080 and endpoint `/sse`, and forwards messages to the `stdio` server.
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.
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ InMemoryEventStore,
3
4
  proxyServer,
5
+ startHTTPStreamServer,
4
6
  startSSEServer
5
- } from "../chunk-HTMOSQ6H.js";
7
+ } from "../chunk-F2LFKNGG.js";
6
8
 
7
9
  // src/bin/mcp-proxy.ts
8
- import yargs from "yargs";
9
- import { hideBin } from "yargs/helpers";
10
10
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
12
  import { EventSource } from "eventsource";
13
13
  import { setTimeout } from "node:timers";
14
+ import util from "node:util";
15
+ import yargs from "yargs";
16
+ import { hideBin } from "yargs/helpers";
14
17
 
15
18
  // src/StdioClientTransport.ts
16
19
  import {
@@ -19,18 +22,47 @@ import {
19
22
  } from "@modelcontextprotocol/sdk/shared/stdio.js";
20
23
  import { spawn } from "node:child_process";
21
24
  var StdioClientTransport = class {
22
- process;
23
- abortController = new AbortController();
24
- readBuffer = new ReadBuffer();
25
- serverParams;
26
- onEvent;
27
25
  onclose;
28
26
  onerror;
29
27
  onmessage;
28
+ /**
29
+ * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
30
+ *
31
+ * This is only available after the process has been started.
32
+ */
33
+ get stderr() {
34
+ return this.process?.stderr ?? null;
35
+ }
36
+ abortController = new AbortController();
37
+ onEvent;
38
+ process;
39
+ readBuffer = new ReadBuffer();
40
+ serverParams;
30
41
  constructor(server) {
31
42
  this.serverParams = server;
32
43
  this.onEvent = server.onEvent;
33
44
  }
45
+ async close() {
46
+ this.onEvent?.({
47
+ type: "close"
48
+ });
49
+ this.abortController.abort();
50
+ this.process = void 0;
51
+ this.readBuffer.clear();
52
+ }
53
+ send(message) {
54
+ return new Promise((resolve) => {
55
+ if (!this.process?.stdin) {
56
+ throw new Error("Not connected");
57
+ }
58
+ const json = serializeMessage(message);
59
+ if (this.process.stdin.write(json)) {
60
+ resolve();
61
+ } else {
62
+ this.process.stdin.once("drain", resolve);
63
+ }
64
+ });
65
+ }
34
66
  /**
35
67
  * Starts the server process and prepares to communicate with it.
36
68
  */
@@ -45,11 +77,11 @@ var StdioClientTransport = class {
45
77
  this.serverParams.command,
46
78
  this.serverParams.args ?? [],
47
79
  {
80
+ cwd: this.serverParams.cwd,
48
81
  env: this.serverParams.env,
49
- stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"],
50
82
  shell: false,
51
83
  signal: this.abortController.signal,
52
- cwd: this.serverParams.cwd
84
+ stdio: ["pipe", "pipe", this.serverParams.stderr ?? "inherit"]
53
85
  }
54
86
  );
55
87
  this.process.on("error", (error) => {
@@ -72,36 +104,28 @@ var StdioClientTransport = class {
72
104
  });
73
105
  this.process.stdin?.on("error", (error) => {
74
106
  this.onEvent?.({
75
- type: "error",
76
- error
107
+ error,
108
+ type: "error"
77
109
  });
78
110
  this.onerror?.(error);
79
111
  });
80
112
  this.process.stdout?.on("data", (chunk) => {
81
113
  this.onEvent?.({
82
- type: "data",
83
- chunk: chunk.toString()
114
+ chunk: chunk.toString(),
115
+ type: "data"
84
116
  });
85
117
  this.readBuffer.append(chunk);
86
118
  this.processReadBuffer();
87
119
  });
88
120
  this.process.stdout?.on("error", (error) => {
89
121
  this.onEvent?.({
90
- type: "error",
91
- error
122
+ error,
123
+ type: "error"
92
124
  });
93
125
  this.onerror?.(error);
94
126
  });
95
127
  });
96
128
  }
97
- /**
98
- * The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
99
- *
100
- * This is only available after the process has been started.
101
- */
102
- get stderr() {
103
- return this.process?.stderr ?? null;
104
- }
105
129
  processReadBuffer() {
106
130
  while (true) {
107
131
  try {
@@ -110,84 +134,67 @@ var StdioClientTransport = class {
110
134
  break;
111
135
  }
112
136
  this.onEvent?.({
113
- type: "message",
114
- message
137
+ message,
138
+ type: "message"
115
139
  });
116
140
  this.onmessage?.(message);
117
141
  } catch (error) {
118
142
  this.onEvent?.({
119
- type: "error",
120
- error
143
+ error,
144
+ type: "error"
121
145
  });
122
146
  this.onerror?.(error);
123
147
  }
124
148
  }
125
149
  }
126
- async close() {
127
- this.onEvent?.({
128
- type: "close"
129
- });
130
- this.abortController.abort();
131
- this.process = void 0;
132
- this.readBuffer.clear();
133
- }
134
- send(message) {
135
- return new Promise((resolve) => {
136
- if (!this.process?.stdin) {
137
- throw new Error("Not connected");
138
- }
139
- const json = serializeMessage(message);
140
- if (this.process.stdin.write(json)) {
141
- resolve();
142
- } else {
143
- this.process.stdin.once("drain", resolve);
144
- }
145
- });
146
- }
147
150
  };
148
151
 
149
152
  // src/bin/mcp-proxy.ts
150
- import util from "node:util";
151
153
  util.inspect.defaultOptions.depth = 8;
152
154
  if (!("EventSource" in global)) {
153
155
  global.EventSource = EventSource;
154
156
  }
155
157
  var argv = await yargs(hideBin(process.argv)).scriptName("mcp-proxy").command("$0 <command> [args...]", "Run a command with MCP arguments").positional("command", {
156
- type: "string",
158
+ demandOption: true,
157
159
  describe: "The command to run",
158
- demandOption: true
160
+ type: "string"
159
161
  }).positional("args", {
160
- type: "string",
161
162
  array: true,
162
- describe: "The arguments to pass to the command"
163
+ describe: "The arguments to pass to the command",
164
+ type: "string"
163
165
  }).env("MCP_PROXY").options({
164
166
  debug: {
165
- type: "boolean",
167
+ default: false,
166
168
  describe: "Enable debug logging",
167
- default: false
169
+ type: "boolean"
168
170
  },
169
171
  endpoint: {
170
- type: "string",
171
- describe: "The endpoint to listen on for SSE",
172
- default: "/sse"
172
+ describe: "The endpoint to listen on",
173
+ type: "string"
173
174
  },
174
175
  port: {
175
- type: "number",
176
- describe: "The port to listen on for SSE",
177
- default: 8080
176
+ default: 8080,
177
+ describe: "The port to listen on",
178
+ type: "number"
179
+ },
180
+ server: {
181
+ choices: ["sse", "stream"],
182
+ default: "sse",
183
+ describe: "The server type to use (sse or stream)",
184
+ type: "string"
178
185
  }
179
186
  }).help().parseAsync();
180
187
  var connect = async (client) => {
181
188
  const transport = new StdioClientTransport({
182
- command: argv.command,
183
189
  args: argv.args,
190
+ command: argv.command,
184
191
  env: process.env,
185
- stderr: "pipe",
186
192
  onEvent: (event) => {
187
193
  if (argv.debug) {
188
194
  console.debug("transport event", event);
189
195
  }
190
- }
196
+ },
197
+ stderr: "pipe"
191
198
  });
192
199
  await client.connect(transport);
193
200
  };
@@ -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 SSE server on port %d", argv.port);
208
- await startSSEServer({
209
- createServer: async () => {
210
- const server = new Server(serverVersion, {
211
- capabilities: serverCapabilities
212
- });
213
- proxyServer({
214
- server,
215
- client,
216
- serverCapabilities
217
- });
218
- return server;
219
- },
220
- port: argv.port,
221
- endpoint: argv.endpoint
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
+ client,
221
+ server,
222
+ serverCapabilities
223
+ });
224
+ return server;
225
+ };
226
+ if (argv.server === "sse") {
227
+ await startSSEServer({
228
+ createServer,
229
+ endpoint: argv.endpoint || "/sse",
230
+ port: argv.port
231
+ });
232
+ } else {
233
+ await startHTTPStreamServer({
234
+ createServer,
235
+ endpoint: argv.endpoint || "/stream",
236
+ eventStore: new InMemoryEventStore(),
237
+ port: argv.port
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":"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","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 { 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":"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","names":[]}