@fractaal/pi-mcp 1.4.1 → 1.6.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/CHANGELOG.md +21 -0
- package/README.md +60 -5
- package/dist/config.d.ts +117 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +153 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/input-schema.d.ts +7 -0
- package/dist/input-schema.d.ts.map +1 -0
- package/dist/input-schema.js +13 -0
- package/dist/input-schema.js.map +1 -0
- package/dist/naming.d.ts +18 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +29 -0
- package/dist/naming.js.map +1 -0
- package/dist/oauth/auth-provider.d.ts +52 -0
- package/dist/oauth/auth-provider.d.ts.map +1 -0
- package/dist/oauth/auth-provider.js +97 -0
- package/dist/oauth/auth-provider.js.map +1 -0
- package/dist/oauth/callback.d.ts.map +1 -1
- package/dist/oauth/callback.js +5 -1
- package/dist/oauth/callback.js.map +1 -1
- package/dist/oauth/client-settings.d.ts +107 -0
- package/dist/oauth/client-settings.d.ts.map +1 -0
- package/dist/oauth/client-settings.js +114 -0
- package/dist/oauth/client-settings.js.map +1 -0
- package/dist/oauth/discovery.js +1 -1
- package/dist/oauth/discovery.js.map +1 -1
- package/dist/oauth/errors.d.ts +7 -0
- package/dist/oauth/errors.d.ts.map +1 -1
- package/dist/oauth/errors.js +10 -0
- package/dist/oauth/errors.js.map +1 -1
- package/dist/oauth/flow.d.ts +10 -0
- package/dist/oauth/flow.d.ts.map +1 -1
- package/dist/oauth/flow.js +31 -8
- package/dist/oauth/flow.js.map +1 -1
- package/dist/oauth/index.d.ts +5 -1
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +4 -1
- package/dist/oauth/index.js.map +1 -1
- package/dist/oauth/loopback.d.ts +51 -0
- package/dist/oauth/loopback.d.ts.map +1 -0
- package/dist/oauth/loopback.js +98 -0
- package/dist/oauth/loopback.js.map +1 -0
- package/dist/oauth/sign-in.d.ts +73 -0
- package/dist/oauth/sign-in.d.ts.map +1 -0
- package/dist/oauth/sign-in.js +182 -0
- package/dist/oauth/sign-in.js.map +1 -0
- package/dist/oauth/types.js +1 -1
- package/dist/oauth/types.js.map +1 -1
- package/dist/output-limit.d.ts +24 -0
- package/dist/output-limit.d.ts.map +1 -0
- package/dist/output-limit.js +58 -0
- package/dist/output-limit.js.map +1 -0
- package/dist/transports/lines.d.ts +34 -0
- package/dist/transports/lines.d.ts.map +1 -0
- package/dist/transports/lines.js +85 -0
- package/dist/transports/lines.js.map +1 -0
- package/dist/transports/pipe.d.ts +49 -0
- package/dist/transports/pipe.d.ts.map +1 -0
- package/dist/transports/pipe.js +96 -0
- package/dist/transports/pipe.js.map +1 -0
- package/dist/transports/stdio.d.ts +1 -4
- package/dist/transports/stdio.d.ts.map +1 -1
- package/dist/transports/stdio.js +20 -51
- package/dist/transports/stdio.js.map +1 -1
- package/dist/transports/streamable-http.d.ts.map +1 -1
- package/dist/transports/streamable-http.js +77 -12
- package/dist/transports/streamable-http.js.map +1 -1
- package/package.json +3 -4
- /package/{LICENSES → THIRD_PARTY_NOTICES}/modelcontextprotocol-typescript-sdk.txt +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type JsonRpcMessage } from "../protocol/jsonrpc.ts";
|
|
2
|
+
import { type McpTransport, TransportEvents } from "./transport.ts";
|
|
3
|
+
/** A running program's stdio, as `@earendil-works/pi-env`'s `RemoteProcess` provides it. */
|
|
4
|
+
export interface McpPipe {
|
|
5
|
+
write(data: Uint8Array | string): void;
|
|
6
|
+
closeStdin(): void;
|
|
7
|
+
/** Kill the program (and what it started). The pipe owns cleaning up what the program started. */
|
|
8
|
+
close(): void;
|
|
9
|
+
/** Settles when the program has ended, whatever the reason; it must also settle after `close()`. */
|
|
10
|
+
readonly exited: Promise<unknown>;
|
|
11
|
+
}
|
|
12
|
+
/** Handlers `PipeTransportOptions.open` connects to the program's output. */
|
|
13
|
+
export interface McpPipeHandlers {
|
|
14
|
+
onStdout(bytes: Uint8Array): void;
|
|
15
|
+
onStderr(bytes: Uint8Array): void;
|
|
16
|
+
}
|
|
17
|
+
export interface PipeTransportOptions {
|
|
18
|
+
/** Start the program; `onStdout`/`onStderr` receive its output from then on. */
|
|
19
|
+
open(handlers: McpPipeHandlers): Promise<McpPipe>;
|
|
20
|
+
maxMessageBytes?: number;
|
|
21
|
+
maxStderrBytes?: number;
|
|
22
|
+
onStderr?: (chunk: string) => void;
|
|
23
|
+
/** After closing stdin, how long the program may take to exit before `close()` kills it. Default 2000. */
|
|
24
|
+
closeTimeoutMs?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* MCP's stdio transport over a program that something else runs, for example on another machine through pi-env.
|
|
28
|
+
* The transport closes when the program ends; a rejected `exited` that `close()` did not cause is reported as an
|
|
29
|
+
* error first.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PipeTransport extends TransportEvents implements McpTransport {
|
|
32
|
+
readonly options: Readonly<PipeTransportOptions>;
|
|
33
|
+
private readonly lines;
|
|
34
|
+
private pipe;
|
|
35
|
+
private started;
|
|
36
|
+
private closed;
|
|
37
|
+
private ended;
|
|
38
|
+
constructor(options: PipeTransportOptions);
|
|
39
|
+
/** The last `maxStderrBytes` (default 64 KiB) of the program's stderr. */
|
|
40
|
+
get stderr(): string;
|
|
41
|
+
start(): Promise<void>;
|
|
42
|
+
send(message: JsonRpcMessage): Promise<void>;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
/** The program ended: report an unfinished message, then close. */
|
|
45
|
+
private exit;
|
|
46
|
+
/** `exited` rejected: an error unless `close()` caused it, then the same as an exit. */
|
|
47
|
+
private fail;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=pipe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../../src/transports/pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAA4B,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIpE,4FAA4F;AAC5F,MAAM,WAAW,OAAO;IACvB,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,UAAU,IAAI,IAAI,CAAC;IACnB,kGAAkG;IAClG,KAAK,IAAI,IAAI,CAAC;IACd,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;CAClC;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACpC,gFAAgF;IAChF,IAAI,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,0GAA0G;IAC1G,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,eAAgB,YAAW,YAAY;IACzE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACjD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IAEtB,YAAY,OAAO,EAAE,oBAAoB,EAUxC;IAED,0EAA0E;IAC1E,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB3B;IAEK,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAyB3B;IAED,mEAAmE;IACnE,OAAO,CAAC,IAAI;IAMZ,wFAAwF;IACxF,OAAO,CAAC,IAAI;CAIZ"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { McpConnectionClosedError } from "../protocol/jsonrpc.js";
|
|
2
|
+
import { JsonRpcLineReader } from "./lines.js";
|
|
3
|
+
import { TransportEvents } from "./transport.js";
|
|
4
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 2_000;
|
|
5
|
+
/**
|
|
6
|
+
* MCP's stdio transport over a program that something else runs, for example on another machine through pi-env.
|
|
7
|
+
* The transport closes when the program ends; a rejected `exited` that `close()` did not cause is reported as an
|
|
8
|
+
* error first.
|
|
9
|
+
*/
|
|
10
|
+
export class PipeTransport extends TransportEvents {
|
|
11
|
+
options;
|
|
12
|
+
lines;
|
|
13
|
+
pipe;
|
|
14
|
+
started = false;
|
|
15
|
+
closed = false;
|
|
16
|
+
ended = false;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
super();
|
|
19
|
+
this.options = Object.freeze({ ...options });
|
|
20
|
+
this.lines = new JsonRpcLineReader({
|
|
21
|
+
maxMessageBytes: this.options.maxMessageBytes,
|
|
22
|
+
maxStderrBytes: this.options.maxStderrBytes,
|
|
23
|
+
onStderr: this.options.onStderr,
|
|
24
|
+
onMessage: (message) => this.emitMessage(message),
|
|
25
|
+
onError: (error) => this.emitError(error),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** The last `maxStderrBytes` (default 64 KiB) of the program's stderr. */
|
|
29
|
+
get stderr() {
|
|
30
|
+
return this.lines.stderr;
|
|
31
|
+
}
|
|
32
|
+
async start() {
|
|
33
|
+
if (this.started)
|
|
34
|
+
throw new Error("MCP pipe transport already started");
|
|
35
|
+
if (this.closed)
|
|
36
|
+
throw new McpConnectionClosedError();
|
|
37
|
+
this.started = true;
|
|
38
|
+
const pipe = await this.options.open({
|
|
39
|
+
onStdout: (bytes) => this.lines.readStdout(bytes),
|
|
40
|
+
onStderr: (bytes) => this.lines.readStderr(bytes),
|
|
41
|
+
});
|
|
42
|
+
if (this.closed) {
|
|
43
|
+
// close() ran while the program was starting and found nothing to shut down. Nobody else awaits
|
|
44
|
+
// `exited`, and a pipe may reject it after a kill.
|
|
45
|
+
pipe.exited.catch(() => { });
|
|
46
|
+
pipe.close();
|
|
47
|
+
throw new McpConnectionClosedError();
|
|
48
|
+
}
|
|
49
|
+
this.pipe = pipe;
|
|
50
|
+
pipe.exited.then(() => this.exit(), (error) => this.fail(error));
|
|
51
|
+
}
|
|
52
|
+
async send(message) {
|
|
53
|
+
if (!this.pipe || this.closed || this.ended)
|
|
54
|
+
throw new McpConnectionClosedError();
|
|
55
|
+
this.pipe.write(`${JSON.stringify(message)}\n`);
|
|
56
|
+
}
|
|
57
|
+
async close() {
|
|
58
|
+
if (this.closed)
|
|
59
|
+
return;
|
|
60
|
+
this.closed = true;
|
|
61
|
+
const pipe = this.pipe;
|
|
62
|
+
if (!pipe) {
|
|
63
|
+
this.emitClose();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (this.ended)
|
|
67
|
+
return;
|
|
68
|
+
// Shutdown per the spec: close stdin and let the program exit, then kill it.
|
|
69
|
+
pipe.closeStdin();
|
|
70
|
+
let timer;
|
|
71
|
+
const exitedInTime = await Promise.race([
|
|
72
|
+
pipe.exited.then(() => true, () => true),
|
|
73
|
+
new Promise((resolve) => {
|
|
74
|
+
timer = setTimeout(() => resolve(false), this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS);
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
if (exitedInTime)
|
|
79
|
+
return;
|
|
80
|
+
pipe.close();
|
|
81
|
+
await pipe.exited.catch(() => { });
|
|
82
|
+
}
|
|
83
|
+
/** The program ended: report an unfinished message, then close. */
|
|
84
|
+
exit() {
|
|
85
|
+
this.ended = true;
|
|
86
|
+
this.lines.end();
|
|
87
|
+
this.emitClose();
|
|
88
|
+
}
|
|
89
|
+
/** `exited` rejected: an error unless `close()` caused it, then the same as an exit. */
|
|
90
|
+
fail(error) {
|
|
91
|
+
if (!this.closed)
|
|
92
|
+
this.emitError(error);
|
|
93
|
+
this.exit();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=pipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe.js","sourceRoot":"","sources":["../../src/transports/pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAqB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,MAAM,wBAAwB,GAAG,KAAK,CAAC;AA4BvC;;;;GAIG;AACH,MAAM,OAAO,aAAc,SAAQ,eAAe;IACxC,OAAO,CAAiC;IAChC,KAAK,CAAoB;IAClC,IAAI,CAAsB;IAC1B,OAAO,GAAG,KAAK,CAAC;IAChB,MAAM,GAAG,KAAK,CAAC;IACf,KAAK,GAAG,KAAK,CAAC;IAEtB,YAAY,OAA6B;QACxC,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,iBAAiB,CAAC;YAClC,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;YAC7C,cAAc,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc;YAC3C,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ;YAC/B,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;YACjD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzC,CAAC,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxE,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,wBAAwB,EAAE,CAAC;QACtD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC;YACjD,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC;SACjD,CAAC,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,gGAAgG;YAChG,mDAAmD;YACnD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,wBAAwB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,CAAC,IAAI,CACf,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EACjB,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CACpC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAuB;QACjC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,wBAAwB,EAAE,CAAC;QAClF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,KAAK;QACV,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,6EAA6E;QAC7E,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,KAAgD,CAAC;QACrD,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CACf,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,IAAI,CACV;YACD,IAAI,OAAO,CAAQ,CAAC,OAAO,EAAE,EAAE;gBAC9B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,cAAc,IAAI,wBAAwB,CAAC,CAAC;YACnG,CAAC,CAAC;SACF,CAAC,CAAC;QACH,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,IAAI,YAAY;YAAE,OAAO;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,mEAAmE;IAC3D,IAAI;QACX,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACjB,IAAI,CAAC,SAAS,EAAE,CAAC;IAClB,CAAC;IAED,wFAAwF;IAChF,IAAI,CAAC,KAAc;QAC1B,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;CACD","sourcesContent":["import { type JsonRpcMessage, McpConnectionClosedError } from \"../protocol/jsonrpc.ts\";\nimport { JsonRpcLineReader } from \"./lines.ts\";\nimport { type McpTransport, TransportEvents } from \"./transport.ts\";\n\nconst DEFAULT_CLOSE_TIMEOUT_MS = 2_000;\n\n/** A running program's stdio, as `@earendil-works/pi-env`'s `RemoteProcess` provides it. */\nexport interface McpPipe {\n\twrite(data: Uint8Array | string): void;\n\tcloseStdin(): void;\n\t/** Kill the program (and what it started). The pipe owns cleaning up what the program started. */\n\tclose(): void;\n\t/** Settles when the program has ended, whatever the reason; it must also settle after `close()`. */\n\treadonly exited: Promise<unknown>;\n}\n\n/** Handlers `PipeTransportOptions.open` connects to the program's output. */\nexport interface McpPipeHandlers {\n\tonStdout(bytes: Uint8Array): void;\n\tonStderr(bytes: Uint8Array): void;\n}\n\nexport interface PipeTransportOptions {\n\t/** Start the program; `onStdout`/`onStderr` receive its output from then on. */\n\topen(handlers: McpPipeHandlers): Promise<McpPipe>;\n\tmaxMessageBytes?: number;\n\tmaxStderrBytes?: number;\n\tonStderr?: (chunk: string) => void;\n\t/** After closing stdin, how long the program may take to exit before `close()` kills it. Default 2000. */\n\tcloseTimeoutMs?: number;\n}\n\n/**\n * MCP's stdio transport over a program that something else runs, for example on another machine through pi-env.\n * The transport closes when the program ends; a rejected `exited` that `close()` did not cause is reported as an\n * error first.\n */\nexport class PipeTransport extends TransportEvents implements McpTransport {\n\treadonly options: Readonly<PipeTransportOptions>;\n\tprivate readonly lines: JsonRpcLineReader;\n\tprivate pipe: McpPipe | undefined;\n\tprivate started = false;\n\tprivate closed = false;\n\tprivate ended = false;\n\n\tconstructor(options: PipeTransportOptions) {\n\t\tsuper();\n\t\tthis.options = Object.freeze({ ...options });\n\t\tthis.lines = new JsonRpcLineReader({\n\t\t\tmaxMessageBytes: this.options.maxMessageBytes,\n\t\t\tmaxStderrBytes: this.options.maxStderrBytes,\n\t\t\tonStderr: this.options.onStderr,\n\t\t\tonMessage: (message) => this.emitMessage(message),\n\t\t\tonError: (error) => this.emitError(error),\n\t\t});\n\t}\n\n\t/** The last `maxStderrBytes` (default 64 KiB) of the program's stderr. */\n\tget stderr(): string {\n\t\treturn this.lines.stderr;\n\t}\n\n\tasync start(): Promise<void> {\n\t\tif (this.started) throw new Error(\"MCP pipe transport already started\");\n\t\tif (this.closed) throw new McpConnectionClosedError();\n\t\tthis.started = true;\n\t\tconst pipe = await this.options.open({\n\t\t\tonStdout: (bytes) => this.lines.readStdout(bytes),\n\t\t\tonStderr: (bytes) => this.lines.readStderr(bytes),\n\t\t});\n\t\tif (this.closed) {\n\t\t\t// close() ran while the program was starting and found nothing to shut down. Nobody else awaits\n\t\t\t// `exited`, and a pipe may reject it after a kill.\n\t\t\tpipe.exited.catch(() => {});\n\t\t\tpipe.close();\n\t\t\tthrow new McpConnectionClosedError();\n\t\t}\n\t\tthis.pipe = pipe;\n\t\tpipe.exited.then(\n\t\t\t() => this.exit(),\n\t\t\t(error: unknown) => this.fail(error),\n\t\t);\n\t}\n\n\tasync send(message: JsonRpcMessage): Promise<void> {\n\t\tif (!this.pipe || this.closed || this.ended) throw new McpConnectionClosedError();\n\t\tthis.pipe.write(`${JSON.stringify(message)}\\n`);\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tconst pipe = this.pipe;\n\t\tif (!pipe) {\n\t\t\tthis.emitClose();\n\t\t\treturn;\n\t\t}\n\t\tif (this.ended) return;\n\t\t// Shutdown per the spec: close stdin and let the program exit, then kill it.\n\t\tpipe.closeStdin();\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst exitedInTime = await Promise.race([\n\t\t\tpipe.exited.then(\n\t\t\t\t() => true,\n\t\t\t\t() => true,\n\t\t\t),\n\t\t\tnew Promise<false>((resolve) => {\n\t\t\t\ttimer = setTimeout(() => resolve(false), this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS);\n\t\t\t}),\n\t\t]);\n\t\tclearTimeout(timer);\n\t\tif (exitedInTime) return;\n\t\tpipe.close();\n\t\tawait pipe.exited.catch(() => {});\n\t}\n\n\t/** The program ended: report an unfinished message, then close. */\n\tprivate exit(): void {\n\t\tthis.ended = true;\n\t\tthis.lines.end();\n\t\tthis.emitClose();\n\t}\n\n\t/** `exited` rejected: an error unless `close()` caused it, then the same as an exit. */\n\tprivate fail(error: unknown): void {\n\t\tif (!this.closed) this.emitError(error);\n\t\tthis.exit();\n\t}\n}\n"]}
|
|
@@ -16,8 +16,7 @@ export interface StdioTransportOptions {
|
|
|
16
16
|
export declare class StdioTransport extends TransportEvents implements McpTransport {
|
|
17
17
|
readonly options: Readonly<StdioTransportOptions>;
|
|
18
18
|
private child;
|
|
19
|
-
private
|
|
20
|
-
private stderrBuffer;
|
|
19
|
+
private readonly lines;
|
|
21
20
|
private started;
|
|
22
21
|
private closed;
|
|
23
22
|
constructor(options: StdioTransportOptions);
|
|
@@ -26,7 +25,5 @@ export declare class StdioTransport extends TransportEvents implements McpTransp
|
|
|
26
25
|
start(): Promise<void>;
|
|
27
26
|
send(message: JsonRpcMessage): Promise<void>;
|
|
28
27
|
close(): Promise<void>;
|
|
29
|
-
private handleStdout;
|
|
30
|
-
private handleStderr;
|
|
31
28
|
}
|
|
32
29
|
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAA4B,MAAM,wBAAwB,CAAC;AAEvF,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAiDpE,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,cAAe,SAAQ,eAAgB,YAAW,YAAY;IAC1E,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,CAA2B;IACxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,OAAO,EAAE,qBAAqB,EAUzC;IAED,IAAI,GAAG,IAAI,MAAM,GAAG,SAAS,CAE5B;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAiD3B;IAEK,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAOjD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CA6B3B;CACD"}
|
package/dist/transports/stdio.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import crossSpawn from "cross-spawn";
|
|
4
|
-
import { McpConnectionClosedError
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { McpConnectionClosedError } from "../protocol/jsonrpc.js";
|
|
5
|
+
import { JsonRpcLineReader } from "./lines.js";
|
|
6
|
+
import { TransportEvents } from "./transport.js";
|
|
7
7
|
const DEFAULT_CLOSE_TIMEOUT_MS = 2_000;
|
|
8
8
|
/** How long a server gets to exit on its own after stdin closes, before it is sent SIGTERM. */
|
|
9
9
|
const STDIN_CLOSE_GRACE_MS = 500;
|
|
@@ -52,19 +52,25 @@ function installExitHook() {
|
|
|
52
52
|
export class StdioTransport extends TransportEvents {
|
|
53
53
|
options;
|
|
54
54
|
child;
|
|
55
|
-
|
|
56
|
-
stderrBuffer = Buffer.alloc(0);
|
|
55
|
+
lines;
|
|
57
56
|
started = false;
|
|
58
57
|
closed = false;
|
|
59
58
|
constructor(options) {
|
|
60
59
|
super();
|
|
61
60
|
this.options = Object.freeze({ ...options, args: options.args ? [...options.args] : undefined });
|
|
61
|
+
this.lines = new JsonRpcLineReader({
|
|
62
|
+
maxMessageBytes: this.options.maxMessageBytes,
|
|
63
|
+
maxStderrBytes: this.options.maxStderrBytes,
|
|
64
|
+
onStderr: this.options.onStderr,
|
|
65
|
+
onMessage: (message) => this.emitMessage(message),
|
|
66
|
+
onError: (error) => this.emitError(error),
|
|
67
|
+
});
|
|
62
68
|
}
|
|
63
69
|
get pid() {
|
|
64
70
|
return this.child?.pid;
|
|
65
71
|
}
|
|
66
72
|
get stderr() {
|
|
67
|
-
return this.
|
|
73
|
+
return this.lines.stderr;
|
|
68
74
|
}
|
|
69
75
|
async start() {
|
|
70
76
|
if (this.started)
|
|
@@ -86,22 +92,20 @@ export class StdioTransport extends TransportEvents {
|
|
|
86
92
|
if (USE_PROCESS_GROUPS && pid !== undefined) {
|
|
87
93
|
installExitHook();
|
|
88
94
|
liveProcessGroups.add(pid);
|
|
89
|
-
|
|
95
|
+
// Not on "exit": children of the server can outlive it while still holding its pipes.
|
|
96
|
+
child.once("close", () => liveProcessGroups.delete(pid));
|
|
90
97
|
}
|
|
91
|
-
child.stdout?.on("data", (chunk) => this.
|
|
98
|
+
child.stdout?.on("data", (chunk) => this.lines.readStdout(chunk));
|
|
92
99
|
child.stdout?.on("error", (error) => this.emitError(error));
|
|
93
100
|
child.stdin?.on("error", (error) => {
|
|
94
101
|
if (!this.closed)
|
|
95
102
|
this.emitError(error);
|
|
96
103
|
});
|
|
97
|
-
child.stderr?.on("data", (chunk) => this.
|
|
104
|
+
child.stderr?.on("data", (chunk) => this.lines.readStderr(chunk));
|
|
98
105
|
child.stderr?.on("error", (error) => this.emitError(error));
|
|
99
106
|
child.on("close", () => {
|
|
100
107
|
this.child = undefined;
|
|
101
|
-
|
|
102
|
-
this.emitError(new Error("MCP stdio server closed with an incomplete JSON-RPC message"));
|
|
103
|
-
}
|
|
104
|
-
this.stdoutBuffer = Buffer.alloc(0);
|
|
108
|
+
this.lines.end();
|
|
105
109
|
this.emitClose();
|
|
106
110
|
});
|
|
107
111
|
await new Promise((resolve, reject) => {
|
|
@@ -139,7 +143,9 @@ export class StdioTransport extends TransportEvents {
|
|
|
139
143
|
this.emitClose();
|
|
140
144
|
return;
|
|
141
145
|
}
|
|
142
|
-
|
|
146
|
+
// With process groups, a server that already exited can leave children holding its pipes; the
|
|
147
|
+
// group cleanup below still has to run for them.
|
|
148
|
+
if (!USE_PROCESS_GROUPS && (child.exitCode !== null || child.signalCode !== null)) {
|
|
143
149
|
child.stdin?.end();
|
|
144
150
|
return;
|
|
145
151
|
}
|
|
@@ -160,42 +166,5 @@ export class StdioTransport extends TransportEvents {
|
|
|
160
166
|
child.stdin?.end();
|
|
161
167
|
});
|
|
162
168
|
}
|
|
163
|
-
handleStdout(chunk) {
|
|
164
|
-
this.stdoutBuffer = Buffer.concat([this.stdoutBuffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
165
|
-
const maxMessageBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
166
|
-
while (true) {
|
|
167
|
-
const newline = this.stdoutBuffer.indexOf(0x0a);
|
|
168
|
-
if (newline < 0) {
|
|
169
|
-
if (this.stdoutBuffer.length > maxMessageBytes) {
|
|
170
|
-
this.stdoutBuffer = Buffer.alloc(0);
|
|
171
|
-
this.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));
|
|
172
|
-
}
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const line = this.stdoutBuffer.subarray(0, newline);
|
|
176
|
-
this.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);
|
|
177
|
-
if (line.length > maxMessageBytes) {
|
|
178
|
-
this.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
const text = line.toString("utf8").replace(/\r$/, "");
|
|
182
|
-
if (!text.trim())
|
|
183
|
-
continue;
|
|
184
|
-
try {
|
|
185
|
-
this.emitMessage(parseJsonRpcMessage(JSON.parse(text)));
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
this.emitError(error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
handleStderr(chunk) {
|
|
193
|
-
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
194
|
-
const maxStderrBytes = this.options.maxStderrBytes ?? DEFAULT_MAX_STDERR_BYTES;
|
|
195
|
-
this.stderrBuffer = Buffer.concat([this.stderrBuffer, buffer]);
|
|
196
|
-
if (this.stderrBuffer.length > maxStderrBytes)
|
|
197
|
-
this.stderrBuffer = this.stderrBuffer.subarray(-maxStderrBytes);
|
|
198
|
-
this.options.onStderr?.(buffer.toString("utf8"));
|
|
199
|
-
}
|
|
200
169
|
}
|
|
201
170
|
//# sourceMappingURL=stdio.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport crossSpawn from \"cross-spawn\";\nimport { type JsonRpcMessage, McpConnectionClosedError, parseJsonRpcMessage } from \"../protocol/jsonrpc.ts\";\nimport { DEFAULT_MAX_MESSAGE_BYTES, type McpTransport, TransportEvents } from \"./transport.ts\";\n\nconst DEFAULT_MAX_STDERR_BYTES = 64 * 1024;\nconst DEFAULT_CLOSE_TIMEOUT_MS = 2_000;\n/** How long a server gets to exit on its own after stdin closes, before it is sent SIGTERM. */\nconst STDIN_CLOSE_GRACE_MS = 500;\nconst USE_PROCESS_GROUPS = process.platform !== \"win32\";\n\n/** Process groups of running servers, killed if the host exits without closing them. */\nconst liveProcessGroups = new Set<number>();\nlet exitHookInstalled = false;\n\nfunction killProcessTree(child: ChildProcess, signal: NodeJS.Signals): void {\n\tconst pid = child.pid;\n\tif (process.platform === \"win32\" && pid !== undefined) {\n\t\tif (child.exitCode !== null) return;\n\t\t// Windows has no graceful signals, and cross-spawn runs `.cmd` shims through cmd.exe; killing only\n\t\t// that would leave the server running.\n\t\tspawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true }).on(\n\t\t\t\"error\",\n\t\t\t() => {},\n\t\t);\n\t\treturn;\n\t}\n\tif (USE_PROCESS_GROUPS && pid !== undefined) {\n\t\ttry {\n\t\t\t// Negative pid: the whole group, so wrappers like `npx` or `uvx` do not leave the server behind.\n\t\t\tprocess.kill(-pid, signal);\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// The group is gone or was never created; fall back to the direct child.\n\t\t}\n\t}\n\ttry {\n\t\tchild.kill(signal);\n\t} catch {}\n}\n\nfunction installExitHook(): void {\n\tif (exitHookInstalled) return;\n\texitHookInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const pid of liveProcessGroups) {\n\t\t\ttry {\n\t\t\t\tprocess.kill(-pid, \"SIGTERM\");\n\t\t\t} catch {}\n\t\t}\n\t});\n}\n\nexport interface StdioTransportOptions {\n\tcommand: string;\n\targs?: readonly string[];\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tinheritEnv?: boolean;\n\tstderr?: \"pipe\" | \"inherit\";\n\tonStderr?: (chunk: string) => void;\n\tmaxMessageBytes?: number;\n\tmaxStderrBytes?: number;\n\t/** Time to wait for the server to exit after SIGTERM before sending SIGKILL. Default: 2000. */\n\tcloseTimeoutMs?: number;\n}\n\nexport class StdioTransport extends TransportEvents implements McpTransport {\n\treadonly options: Readonly<StdioTransportOptions>;\n\tprivate child: ChildProcess | undefined;\n\tprivate stdoutBuffer = Buffer.alloc(0);\n\tprivate stderrBuffer = Buffer.alloc(0);\n\tprivate started = false;\n\tprivate closed = false;\n\n\tconstructor(options: StdioTransportOptions) {\n\t\tsuper();\n\t\tthis.options = Object.freeze({ ...options, args: options.args ? [...options.args] : undefined });\n\t}\n\n\tget pid(): number | undefined {\n\t\treturn this.child?.pid;\n\t}\n\n\tget stderr(): string {\n\t\treturn this.stderrBuffer.toString(\"utf8\");\n\t}\n\n\tasync start(): Promise<void> {\n\t\tif (this.started) throw new Error(\"MCP stdio transport already started\");\n\t\tif (this.closed) throw new McpConnectionClosedError();\n\t\tthis.started = true;\n\t\tconst env = this.options.inheritEnv === false ? { ...this.options.env } : { ...process.env, ...this.options.env };\n\t\tconst child = crossSpawn(this.options.command, this.options.args ?? [], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv,\n\t\t\tstdio: [\"pipe\", \"pipe\", this.options.stderr === \"inherit\" ? \"inherit\" : \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t\t// Own process group, so closing the transport can terminate the server's children too.\n\t\t\tdetached: USE_PROCESS_GROUPS,\n\t\t});\n\t\tthis.child = child;\n\t\tconst pid = child.pid;\n\t\tif (USE_PROCESS_GROUPS && pid !== undefined) {\n\t\t\tinstallExitHook();\n\t\t\tliveProcessGroups.add(pid);\n\t\t\tchild.once(\"exit\", () => liveProcessGroups.delete(pid));\n\t\t}\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer | string) => this.handleStdout(chunk));\n\t\tchild.stdout?.on(\"error\", (error) => this.emitError(error));\n\t\tchild.stdin?.on(\"error\", (error) => {\n\t\t\tif (!this.closed) this.emitError(error);\n\t\t});\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer | string) => this.handleStderr(chunk));\n\t\tchild.stderr?.on(\"error\", (error) => this.emitError(error));\n\t\tchild.on(\"close\", () => {\n\t\t\tthis.child = undefined;\n\t\t\tif (this.stdoutBuffer.toString(\"utf8\").trim()) {\n\t\t\t\tthis.emitError(new Error(\"MCP stdio server closed with an incomplete JSON-RPC message\"));\n\t\t\t}\n\t\t\tthis.stdoutBuffer = Buffer.alloc(0);\n\t\t\tthis.emitClose();\n\t\t});\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst onSpawn = () => {\n\t\t\t\tchild.off(\"error\", onError);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\tchild.off(\"spawn\", onSpawn);\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tchild.once(\"spawn\", onSpawn);\n\t\t\tchild.once(\"error\", onError);\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tif (!this.closed) this.emitError(error);\n\t\t});\n\t}\n\n\tasync send(message: JsonRpcMessage): Promise<void> {\n\t\tconst stdin = this.child?.stdin;\n\t\tif (!this.started || this.closed || !stdin?.writable) throw new McpConnectionClosedError();\n\t\tconst payload = `${JSON.stringify(message)}\\n`;\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tstdin.write(payload, (error) => (error ? reject(error) : resolve()));\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tconst child = this.child;\n\t\tif (!child) {\n\t\t\tthis.emitClose();\n\t\t\treturn;\n\t\t}\n\t\tif (child.exitCode !== null || child.signalCode !== null) {\n\t\t\tchild.stdin?.end();\n\t\t\treturn;\n\t\t}\n\t\t// Shutdown per the spec: close stdin and let the server exit, then SIGTERM, then SIGKILL.\n\t\tconst closeTimeoutMs = this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timers: ReturnType<typeof setTimeout>[] = [];\n\t\t\tchild.once(\"close\", () => {\n\t\t\t\tfor (const timer of timers) clearTimeout(timer);\n\t\t\t\t// Children of the server that ignored stdin closing would otherwise outlive it.\n\t\t\t\tkillProcessTree(child, \"SIGTERM\");\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tconst grace = Math.min(STDIN_CLOSE_GRACE_MS, closeTimeoutMs);\n\t\t\ttimers.push(setTimeout(() => killProcessTree(child, \"SIGTERM\"), grace));\n\t\t\ttimers.push(setTimeout(() => killProcessTree(child, \"SIGKILL\"), grace + closeTimeoutMs));\n\t\t\tchild.stdin?.end();\n\t\t});\n\t}\n\n\tprivate handleStdout(chunk: Buffer | string): void {\n\t\tthis.stdoutBuffer = Buffer.concat([this.stdoutBuffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);\n\t\tconst maxMessageBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;\n\t\twhile (true) {\n\t\t\tconst newline = this.stdoutBuffer.indexOf(0x0a);\n\t\t\tif (newline < 0) {\n\t\t\t\tif (this.stdoutBuffer.length > maxMessageBytes) {\n\t\t\t\t\tthis.stdoutBuffer = Buffer.alloc(0);\n\t\t\t\t\tthis.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst line = this.stdoutBuffer.subarray(0, newline);\n\t\t\tthis.stdoutBuffer = this.stdoutBuffer.subarray(newline + 1);\n\t\t\tif (line.length > maxMessageBytes) {\n\t\t\t\tthis.emitError(new Error(`MCP stdio message exceeds ${maxMessageBytes} bytes`));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst text = line.toString(\"utf8\").replace(/\\r$/, \"\");\n\t\t\tif (!text.trim()) continue;\n\t\t\ttry {\n\t\t\t\tthis.emitMessage(parseJsonRpcMessage(JSON.parse(text)));\n\t\t\t} catch (error) {\n\t\t\t\tthis.emitError(error);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate handleStderr(chunk: Buffer | string): void {\n\t\tconst buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n\t\tconst maxStderrBytes = this.options.maxStderrBytes ?? DEFAULT_MAX_STDERR_BYTES;\n\t\tthis.stderrBuffer = Buffer.concat([this.stderrBuffer, buffer]);\n\t\tif (this.stderrBuffer.length > maxStderrBytes) this.stderrBuffer = this.stderrBuffer.subarray(-maxStderrBytes);\n\t\tthis.options.onStderr?.(buffer.toString(\"utf8\"));\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcess, spawn } from \"node:child_process\";\nimport process from \"node:process\";\nimport crossSpawn from \"cross-spawn\";\nimport { type JsonRpcMessage, McpConnectionClosedError } from \"../protocol/jsonrpc.ts\";\nimport { JsonRpcLineReader } from \"./lines.ts\";\nimport { type McpTransport, TransportEvents } from \"./transport.ts\";\n\nconst DEFAULT_CLOSE_TIMEOUT_MS = 2_000;\n/** How long a server gets to exit on its own after stdin closes, before it is sent SIGTERM. */\nconst STDIN_CLOSE_GRACE_MS = 500;\nconst USE_PROCESS_GROUPS = process.platform !== \"win32\";\n\n/** Process groups of running servers, killed if the host exits without closing them. */\nconst liveProcessGroups = new Set<number>();\nlet exitHookInstalled = false;\n\nfunction killProcessTree(child: ChildProcess, signal: NodeJS.Signals): void {\n\tconst pid = child.pid;\n\tif (process.platform === \"win32\" && pid !== undefined) {\n\t\tif (child.exitCode !== null) return;\n\t\t// Windows has no graceful signals, and cross-spawn runs `.cmd` shims through cmd.exe; killing only\n\t\t// that would leave the server running.\n\t\tspawn(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { stdio: \"ignore\", windowsHide: true }).on(\n\t\t\t\"error\",\n\t\t\t() => {},\n\t\t);\n\t\treturn;\n\t}\n\tif (USE_PROCESS_GROUPS && pid !== undefined) {\n\t\ttry {\n\t\t\t// Negative pid: the whole group, so wrappers like `npx` or `uvx` do not leave the server behind.\n\t\t\tprocess.kill(-pid, signal);\n\t\t\treturn;\n\t\t} catch {\n\t\t\t// The group is gone or was never created; fall back to the direct child.\n\t\t}\n\t}\n\ttry {\n\t\tchild.kill(signal);\n\t} catch {}\n}\n\nfunction installExitHook(): void {\n\tif (exitHookInstalled) return;\n\texitHookInstalled = true;\n\tprocess.once(\"exit\", () => {\n\t\tfor (const pid of liveProcessGroups) {\n\t\t\ttry {\n\t\t\t\tprocess.kill(-pid, \"SIGTERM\");\n\t\t\t} catch {}\n\t\t}\n\t});\n}\n\nexport interface StdioTransportOptions {\n\tcommand: string;\n\targs?: readonly string[];\n\tcwd?: string;\n\tenv?: Record<string, string>;\n\tinheritEnv?: boolean;\n\tstderr?: \"pipe\" | \"inherit\";\n\tonStderr?: (chunk: string) => void;\n\tmaxMessageBytes?: number;\n\tmaxStderrBytes?: number;\n\t/** Time to wait for the server to exit after SIGTERM before sending SIGKILL. Default: 2000. */\n\tcloseTimeoutMs?: number;\n}\n\nexport class StdioTransport extends TransportEvents implements McpTransport {\n\treadonly options: Readonly<StdioTransportOptions>;\n\tprivate child: ChildProcess | undefined;\n\tprivate readonly lines: JsonRpcLineReader;\n\tprivate started = false;\n\tprivate closed = false;\n\n\tconstructor(options: StdioTransportOptions) {\n\t\tsuper();\n\t\tthis.options = Object.freeze({ ...options, args: options.args ? [...options.args] : undefined });\n\t\tthis.lines = new JsonRpcLineReader({\n\t\t\tmaxMessageBytes: this.options.maxMessageBytes,\n\t\t\tmaxStderrBytes: this.options.maxStderrBytes,\n\t\t\tonStderr: this.options.onStderr,\n\t\t\tonMessage: (message) => this.emitMessage(message),\n\t\t\tonError: (error) => this.emitError(error),\n\t\t});\n\t}\n\n\tget pid(): number | undefined {\n\t\treturn this.child?.pid;\n\t}\n\n\tget stderr(): string {\n\t\treturn this.lines.stderr;\n\t}\n\n\tasync start(): Promise<void> {\n\t\tif (this.started) throw new Error(\"MCP stdio transport already started\");\n\t\tif (this.closed) throw new McpConnectionClosedError();\n\t\tthis.started = true;\n\t\tconst env = this.options.inheritEnv === false ? { ...this.options.env } : { ...process.env, ...this.options.env };\n\t\tconst child = crossSpawn(this.options.command, this.options.args ?? [], {\n\t\t\tcwd: this.options.cwd,\n\t\t\tenv,\n\t\t\tstdio: [\"pipe\", \"pipe\", this.options.stderr === \"inherit\" ? \"inherit\" : \"pipe\"],\n\t\t\twindowsHide: true,\n\t\t\t// Own process group, so closing the transport can terminate the server's children too.\n\t\t\tdetached: USE_PROCESS_GROUPS,\n\t\t});\n\t\tthis.child = child;\n\t\tconst pid = child.pid;\n\t\tif (USE_PROCESS_GROUPS && pid !== undefined) {\n\t\t\tinstallExitHook();\n\t\t\tliveProcessGroups.add(pid);\n\t\t\t// Not on \"exit\": children of the server can outlive it while still holding its pipes.\n\t\t\tchild.once(\"close\", () => liveProcessGroups.delete(pid));\n\t\t}\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => this.lines.readStdout(chunk));\n\t\tchild.stdout?.on(\"error\", (error) => this.emitError(error));\n\t\tchild.stdin?.on(\"error\", (error) => {\n\t\t\tif (!this.closed) this.emitError(error);\n\t\t});\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => this.lines.readStderr(chunk));\n\t\tchild.stderr?.on(\"error\", (error) => this.emitError(error));\n\t\tchild.on(\"close\", () => {\n\t\t\tthis.child = undefined;\n\t\t\tthis.lines.end();\n\t\t\tthis.emitClose();\n\t\t});\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst onSpawn = () => {\n\t\t\t\tchild.off(\"error\", onError);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst onError = (error: Error) => {\n\t\t\t\tchild.off(\"spawn\", onSpawn);\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tchild.once(\"spawn\", onSpawn);\n\t\t\tchild.once(\"error\", onError);\n\t\t});\n\t\tchild.on(\"error\", (error) => {\n\t\t\tif (!this.closed) this.emitError(error);\n\t\t});\n\t}\n\n\tasync send(message: JsonRpcMessage): Promise<void> {\n\t\tconst stdin = this.child?.stdin;\n\t\tif (!this.started || this.closed || !stdin?.writable) throw new McpConnectionClosedError();\n\t\tconst payload = `${JSON.stringify(message)}\\n`;\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tstdin.write(payload, (error) => (error ? reject(error) : resolve()));\n\t\t});\n\t}\n\n\tasync close(): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tconst child = this.child;\n\t\tif (!child) {\n\t\t\tthis.emitClose();\n\t\t\treturn;\n\t\t}\n\t\t// With process groups, a server that already exited can leave children holding its pipes; the\n\t\t// group cleanup below still has to run for them.\n\t\tif (!USE_PROCESS_GROUPS && (child.exitCode !== null || child.signalCode !== null)) {\n\t\t\tchild.stdin?.end();\n\t\t\treturn;\n\t\t}\n\t\t// Shutdown per the spec: close stdin and let the server exit, then SIGTERM, then SIGKILL.\n\t\tconst closeTimeoutMs = this.options.closeTimeoutMs ?? DEFAULT_CLOSE_TIMEOUT_MS;\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst timers: ReturnType<typeof setTimeout>[] = [];\n\t\t\tchild.once(\"close\", () => {\n\t\t\t\tfor (const timer of timers) clearTimeout(timer);\n\t\t\t\t// Children of the server that ignored stdin closing would otherwise outlive it.\n\t\t\t\tkillProcessTree(child, \"SIGTERM\");\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tconst grace = Math.min(STDIN_CLOSE_GRACE_MS, closeTimeoutMs);\n\t\t\ttimers.push(setTimeout(() => killProcessTree(child, \"SIGTERM\"), grace));\n\t\t\ttimers.push(setTimeout(() => killProcessTree(child, \"SIGKILL\"), grace + closeTimeoutMs));\n\t\t\tchild.stdin?.end();\n\t\t});\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamable-http.d.ts","sourceRoot":"","sources":["../../src/transports/streamable-http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAKN,KAAK,cAAc,EAKnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,eAAe,EACf,MAAM,gBAAgB,CAAC;AAQxB,MAAM,WAAW,QAAQ;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/B,0GAA0G;IAC1G,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"streamable-http.d.ts","sourceRoot":"","sources":["../../src/transports/streamable-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,8 +29,7 @@ export async function consumeSseStream(stream, options) {
|
|
|
29
29
|
dataLines = [];
|
|
30
30
|
dataBytes = 0;
|
|
31
31
|
};
|
|
32
|
-
const processLine = (
|
|
33
|
-
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
32
|
+
const processLine = (line) => {
|
|
34
33
|
if (line === "") {
|
|
35
34
|
dispatch();
|
|
36
35
|
return;
|
|
@@ -57,26 +56,59 @@ export async function consumeSseStream(stream, options) {
|
|
|
57
56
|
else if (field === "retry" && /^\d+$/.test(value))
|
|
58
57
|
options.onRetry?.(Number(value));
|
|
59
58
|
};
|
|
59
|
+
// SSE lines end with CRLF, LF, or a lone CR.
|
|
60
|
+
let skipLeadingLf = false;
|
|
61
|
+
const consumeLines = () => {
|
|
62
|
+
let start = 0;
|
|
63
|
+
for (let i = 0; i < buffered.length; i++) {
|
|
64
|
+
const char = buffered[i];
|
|
65
|
+
if (char !== "\r" && char !== "\n")
|
|
66
|
+
continue;
|
|
67
|
+
processLine(buffered.slice(start, i));
|
|
68
|
+
if (char === "\r") {
|
|
69
|
+
if (i + 1 < buffered.length) {
|
|
70
|
+
if (buffered[i + 1] === "\n")
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
skipLeadingLf = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
start = i + 1;
|
|
78
|
+
}
|
|
79
|
+
buffered = buffered.slice(start);
|
|
80
|
+
};
|
|
60
81
|
try {
|
|
61
82
|
while (true) {
|
|
62
83
|
const { done, value } = await reader.read();
|
|
63
84
|
if (done)
|
|
64
85
|
break;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
let text = decoder.decode(value, { stream: true });
|
|
87
|
+
// A CRLF split across chunks: the CR already ended the line, so drop the LF.
|
|
88
|
+
if (skipLeadingLf && text.length > 0) {
|
|
89
|
+
if (text.startsWith("\n"))
|
|
90
|
+
text = text.slice(1);
|
|
91
|
+
skipLeadingLf = false;
|
|
71
92
|
}
|
|
93
|
+
buffered += text;
|
|
94
|
+
consumeLines();
|
|
72
95
|
if (Buffer.byteLength(buffered) > maxEventBytes)
|
|
73
96
|
throw new Error(`MCP SSE event exceeds ${maxEventBytes} bytes`);
|
|
74
97
|
}
|
|
75
|
-
|
|
98
|
+
let tail = decoder.decode();
|
|
99
|
+
if (skipLeadingLf && tail.startsWith("\n"))
|
|
100
|
+
tail = tail.slice(1);
|
|
101
|
+
buffered += tail;
|
|
102
|
+
consumeLines();
|
|
76
103
|
if (buffered)
|
|
77
104
|
processLine(buffered);
|
|
78
105
|
dispatch();
|
|
79
106
|
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
// Releasing the lock alone leaves the response open; the original error wins over a failed cancel.
|
|
109
|
+
await reader.cancel().catch(() => { });
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
80
112
|
finally {
|
|
81
113
|
reader.releaseLock();
|
|
82
114
|
}
|
|
@@ -123,6 +155,35 @@ function isTransientStatus(status) {
|
|
|
123
155
|
function discard(response) {
|
|
124
156
|
return response.body?.cancel().catch(() => { }) ?? Promise.resolve();
|
|
125
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Reads a response body as text, decoding chunks as they arrive. Once the body exceeds `maxBytes`, the rest is
|
|
160
|
+
* cancelled and the text holds only the first `maxBytes` bytes.
|
|
161
|
+
*/
|
|
162
|
+
async function readBodyText(response, maxBytes) {
|
|
163
|
+
if (!response.body)
|
|
164
|
+
return { text: "", truncated: false };
|
|
165
|
+
const reader = response.body.getReader();
|
|
166
|
+
const decoder = new TextDecoder();
|
|
167
|
+
let text = "";
|
|
168
|
+
let remaining = maxBytes;
|
|
169
|
+
try {
|
|
170
|
+
while (true) {
|
|
171
|
+
const { done, value } = await reader.read();
|
|
172
|
+
if (done)
|
|
173
|
+
return { text: text + decoder.decode(), truncated: false };
|
|
174
|
+
if (value.byteLength > remaining) {
|
|
175
|
+
await reader.cancel().catch(() => { });
|
|
176
|
+
// Decoding a subarray copies only the kept bytes, so the result never references the dropped rest.
|
|
177
|
+
return { text: text + decoder.decode(value.subarray(0, remaining), { stream: true }), truncated: true };
|
|
178
|
+
}
|
|
179
|
+
remaining -= value.byteLength;
|
|
180
|
+
text += decoder.decode(value, { stream: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
reader.releaseLock();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
126
187
|
function describeHttpFailure(status, body) {
|
|
127
188
|
const text = body.trim();
|
|
128
189
|
const snippet = text.length > ERROR_MESSAGE_BODY_CHARS ? `${text.slice(0, ERROR_MESSAGE_BODY_CHARS - 3)}...` : text;
|
|
@@ -184,7 +245,11 @@ export class StreamableHttpTransport extends TransportEvents {
|
|
|
184
245
|
}
|
|
185
246
|
const type = contentType(response);
|
|
186
247
|
if (type === "application/json") {
|
|
187
|
-
const
|
|
248
|
+
const maxBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
249
|
+
const { text, truncated } = await readBodyText(response, maxBytes);
|
|
250
|
+
if (truncated)
|
|
251
|
+
throw new Error(`MCP JSON response exceeds ${maxBytes} bytes`);
|
|
252
|
+
const body = JSON.parse(text);
|
|
188
253
|
for (const item of Array.isArray(body) ? body : [body])
|
|
189
254
|
this.emitMessage(parseJsonRpcMessage(item));
|
|
190
255
|
return;
|
|
@@ -271,8 +336,8 @@ export class StreamableHttpTransport extends TransportEvents {
|
|
|
271
336
|
async checkResponse(response) {
|
|
272
337
|
if (response.ok)
|
|
273
338
|
return;
|
|
274
|
-
//
|
|
275
|
-
const body =
|
|
339
|
+
// Reads only the excerpt the error keeps and cancels the rest, so a huge error body is never buffered.
|
|
340
|
+
const body = (await readBodyText(response, MAX_ERROR_BODY_BYTES).catch(() => ({ text: "" }))).text;
|
|
276
341
|
if (response.status === 401)
|
|
277
342
|
throw new McpAuthRequiredError(response, body);
|
|
278
343
|
if (response.status === 404 && this.sessionIdValue)
|