Squashed 'packages/mcp-typescript/' content from commit 2cc7dd1
git-subtree-dir: packages/mcp-typescript git-subtree-split: 2cc7dd104307d48bab8d27760f16c63c119d8a88
This commit is contained in:
121
src/client/stdio.ts
Normal file
121
src/client/stdio.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { ChildProcess, spawn } from "node:child_process";
|
||||
import { ReadBuffer, serializeMessage } from "../shared/stdio.js";
|
||||
import { JSONRPCMessage } from "../types.js";
|
||||
import { Transport } from "../shared/transport.js";
|
||||
|
||||
export type StdioServerParameters = {
|
||||
/**
|
||||
* The executable to run to start the server.
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Command line arguments to pass to the executable.
|
||||
*/
|
||||
args?: string[];
|
||||
|
||||
/**
|
||||
* The environment to use when spawning the process.
|
||||
*
|
||||
* The environment is NOT inherited from the parent process by default.
|
||||
*/
|
||||
env?: object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout.
|
||||
*
|
||||
* This transport is only available in Node.js environments.
|
||||
*/
|
||||
export class StdioClientTransport implements Transport {
|
||||
private _process?: ChildProcess;
|
||||
private _abortController: AbortController = new AbortController();
|
||||
private _readBuffer: ReadBuffer = new ReadBuffer();
|
||||
|
||||
onclose?: () => void;
|
||||
onerror?: (error: Error) => void;
|
||||
onmessage?: (message: JSONRPCMessage) => void;
|
||||
|
||||
/**
|
||||
* Spawns the server process and prepare to communicate with it.
|
||||
*/
|
||||
spawn(server: StdioServerParameters): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._process = spawn(server.command, server.args ?? [], {
|
||||
// The parent process may have sensitive secrets in its env, so don't inherit it automatically.
|
||||
env: server.env === undefined ? {} : { ...server.env },
|
||||
stdio: ["pipe", "pipe", "inherit"],
|
||||
signal: this._abortController.signal,
|
||||
});
|
||||
|
||||
this._process.on("error", (error) => {
|
||||
if (error.name === "AbortError") {
|
||||
// Expected when close() is called.
|
||||
this.onclose?.();
|
||||
return;
|
||||
}
|
||||
|
||||
reject(error);
|
||||
this.onerror?.(error);
|
||||
});
|
||||
|
||||
this._process.on("spawn", () => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
this._process.on("close", (_code) => {
|
||||
this._process = undefined;
|
||||
this.onclose?.();
|
||||
});
|
||||
|
||||
this._process.stdin?.on("error", (error) => {
|
||||
this.onerror?.(error);
|
||||
});
|
||||
|
||||
this._process.stdout?.on("data", (chunk) => {
|
||||
this._readBuffer.append(chunk);
|
||||
this.processReadBuffer();
|
||||
});
|
||||
|
||||
this._process.stdout?.on("error", (error) => {
|
||||
this.onerror?.(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private processReadBuffer() {
|
||||
while (true) {
|
||||
try {
|
||||
const message = this._readBuffer.readMessage();
|
||||
if (message === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
this.onmessage?.(message);
|
||||
} catch (error) {
|
||||
this.onerror?.(error as Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
this._abortController.abort();
|
||||
this._process = undefined;
|
||||
this._readBuffer.clear();
|
||||
}
|
||||
|
||||
send(message: JSONRPCMessage): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
if (!this._process?.stdin) {
|
||||
throw new Error("Not connected");
|
||||
}
|
||||
|
||||
const json = serializeMessage(message);
|
||||
if (this._process.stdin.write(json)) {
|
||||
resolve();
|
||||
} else {
|
||||
this._process.stdin.once("drain", resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user