@ai-ecoverse/mcpcat 0.14.2 → 0.14.3
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 +1 -1
- package/dist/host/mcpcat.mjs +216 -76
- package/dist/kernel/mcpcat.mjs +236 -82
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Or via npm:
|
|
|
14
14
|
npm install -g @ai-ecoverse/mcpcat
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Sliccy Seven as a kernel program, with the same flags, the same output and the same config file in both places.
|
|
17
|
+
mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Sliccy Seven as a kernel program, with the same flags, the same output and the same config file in both places. Orphan-free shutdown of stdio server trees in the kernel needs slicc-kernel ≥ 1.74.0; on older kernels only the direct child is signalled.
|
|
18
18
|
|
|
19
19
|
## Quick start
|
|
20
20
|
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -8011,6 +8011,40 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
8011
8011
|
}
|
|
8012
8012
|
};
|
|
8013
8013
|
|
|
8014
|
+
// src/client/stdio-registry.ts
|
|
8015
|
+
var GRACE_MS = 2e3;
|
|
8016
|
+
var registry = /* @__PURE__ */ new Set();
|
|
8017
|
+
function trackStdio(proc) {
|
|
8018
|
+
registry.add(proc);
|
|
8019
|
+
void proc.wait().then(
|
|
8020
|
+
() => registry.delete(proc),
|
|
8021
|
+
() => registry.delete(proc)
|
|
8022
|
+
);
|
|
8023
|
+
}
|
|
8024
|
+
async function killAllStdio() {
|
|
8025
|
+
const procs = [...registry];
|
|
8026
|
+
if (procs.length === 0) return;
|
|
8027
|
+
await Promise.all(
|
|
8028
|
+
procs.map(async (proc) => {
|
|
8029
|
+
try {
|
|
8030
|
+
await proc.kill("SIGTERM");
|
|
8031
|
+
} catch {
|
|
8032
|
+
}
|
|
8033
|
+
const exited = await Promise.race([
|
|
8034
|
+
proc.wait().then(() => true),
|
|
8035
|
+
new Promise((r) => setTimeout(() => r(false), GRACE_MS))
|
|
8036
|
+
]);
|
|
8037
|
+
if (!exited) {
|
|
8038
|
+
try {
|
|
8039
|
+
await proc.kill("SIGKILL");
|
|
8040
|
+
} catch {
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
})
|
|
8044
|
+
);
|
|
8045
|
+
registry.clear();
|
|
8046
|
+
}
|
|
8047
|
+
|
|
8014
8048
|
// src/client/platform-stdio-transport.ts
|
|
8015
8049
|
var DEFAULT_MAX_MESSAGE_BYTES2 = 4 * 1024 * 1024;
|
|
8016
8050
|
var DEFAULT_MAX_STDERR_BYTES2 = 64 * 1024;
|
|
@@ -8067,16 +8101,21 @@ var PlatformStdioTransport = class {
|
|
|
8067
8101
|
if (this.started) throw new Error("MCP stdio transport already started");
|
|
8068
8102
|
if (this.closed) throw new Error("MCP connection closed");
|
|
8069
8103
|
this.started = true;
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8076
|
-
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8104
|
+
if (this.options.existingProcess) {
|
|
8105
|
+
this.process = this.options.existingProcess;
|
|
8106
|
+
} else {
|
|
8107
|
+
const env = this.options.inheritEnv === false ? { ...this.options.env ?? {} } : {
|
|
8108
|
+
...this.options.platform.env,
|
|
8109
|
+
...this.options.env
|
|
8110
|
+
};
|
|
8111
|
+
this.process = await this.options.platform.spawn({
|
|
8112
|
+
argv: [this.options.command, ...this.options.args ?? []],
|
|
8113
|
+
env,
|
|
8114
|
+
cwd: this.options.cwd
|
|
8115
|
+
});
|
|
8116
|
+
trackStdio(this.process);
|
|
8117
|
+
}
|
|
8118
|
+
void this.readStdout(this.options.pendingStdout);
|
|
8080
8119
|
void this.readStderr();
|
|
8081
8120
|
void this.waitForExit();
|
|
8082
8121
|
}
|
|
@@ -8109,22 +8148,22 @@ var PlatformStdioTransport = class {
|
|
|
8109
8148
|
}
|
|
8110
8149
|
const exited = await Promise.race([done, delay(grace)]);
|
|
8111
8150
|
if (!exited) {
|
|
8112
|
-
proc.kill("SIGTERM");
|
|
8151
|
+
await proc.kill("SIGTERM");
|
|
8113
8152
|
const exitedAfterTerm = await Promise.race([done, delay(closeTimeoutMs)]);
|
|
8114
8153
|
if (!exitedAfterTerm) {
|
|
8115
|
-
proc.kill("SIGKILL");
|
|
8154
|
+
await proc.kill("SIGKILL");
|
|
8116
8155
|
await done;
|
|
8117
8156
|
}
|
|
8118
8157
|
}
|
|
8119
|
-
proc.kill("SIGTERM");
|
|
8158
|
+
await proc.kill("SIGTERM");
|
|
8120
8159
|
this.emitClose();
|
|
8121
8160
|
}
|
|
8122
|
-
async readStdout() {
|
|
8161
|
+
async readStdout(pending) {
|
|
8123
8162
|
const proc = this.process;
|
|
8124
8163
|
if (!proc) return;
|
|
8125
8164
|
const reader = proc.stdout.getReader();
|
|
8126
8165
|
const maxBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES2;
|
|
8127
|
-
let buffer = "";
|
|
8166
|
+
let buffer = pending ?? "";
|
|
8128
8167
|
try {
|
|
8129
8168
|
for (; ; ) {
|
|
8130
8169
|
const { done, value } = await reader.read();
|
|
@@ -8184,6 +8223,10 @@ var PlatformStdioTransport = class {
|
|
|
8184
8223
|
await proc.wait();
|
|
8185
8224
|
} catch {
|
|
8186
8225
|
}
|
|
8226
|
+
try {
|
|
8227
|
+
await proc.kill("SIGTERM");
|
|
8228
|
+
} catch {
|
|
8229
|
+
}
|
|
8187
8230
|
if (!this.closed) {
|
|
8188
8231
|
this.emitClose();
|
|
8189
8232
|
}
|
|
@@ -8525,7 +8568,9 @@ function buildTransport(options) {
|
|
|
8525
8568
|
cwd: options.cwd,
|
|
8526
8569
|
inheritEnv: true,
|
|
8527
8570
|
platform: options.platform,
|
|
8528
|
-
onStderr: buildStderrHandler(options)
|
|
8571
|
+
onStderr: buildStderrHandler(options),
|
|
8572
|
+
existingProcess: options.existingProcess?.proc,
|
|
8573
|
+
pendingStdout: options.existingProcess?.pendingStdout
|
|
8529
8574
|
});
|
|
8530
8575
|
}
|
|
8531
8576
|
return new StdioTransport({
|
|
@@ -8712,6 +8757,9 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8712
8757
|
reader.releaseLock();
|
|
8713
8758
|
} catch {
|
|
8714
8759
|
}
|
|
8760
|
+
},
|
|
8761
|
+
pendingBuffer() {
|
|
8762
|
+
return readBuffer;
|
|
8715
8763
|
}
|
|
8716
8764
|
};
|
|
8717
8765
|
}
|
|
@@ -8764,13 +8812,13 @@ function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
|
8764
8812
|
async close() {
|
|
8765
8813
|
rpc.close();
|
|
8766
8814
|
try {
|
|
8767
|
-
proc.kill("SIGTERM");
|
|
8815
|
+
await proc.kill("SIGTERM");
|
|
8768
8816
|
} catch {
|
|
8769
8817
|
}
|
|
8770
8818
|
}
|
|
8771
8819
|
};
|
|
8772
8820
|
}
|
|
8773
|
-
async function
|
|
8821
|
+
async function probeStdioStateless(options) {
|
|
8774
8822
|
const inheritedEnv = {
|
|
8775
8823
|
...options.platform.env,
|
|
8776
8824
|
...options.env
|
|
@@ -8780,22 +8828,30 @@ async function createStdioStatelessConnection(options) {
|
|
|
8780
8828
|
env: inheritedEnv,
|
|
8781
8829
|
cwd: options.cwd
|
|
8782
8830
|
});
|
|
8783
|
-
|
|
8831
|
+
trackStdio(proc);
|
|
8784
8832
|
const rpc = createStdioJsonRpc(proc, options.platform, {
|
|
8785
8833
|
version: options.version,
|
|
8786
8834
|
timeoutMs: options.timeoutMs,
|
|
8787
8835
|
signal: options.signal,
|
|
8788
8836
|
verbose: options.verbose
|
|
8789
8837
|
});
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8838
|
+
let discoverRaw;
|
|
8839
|
+
try {
|
|
8840
|
+
discoverRaw = await rpc.request("server/discover", {
|
|
8841
|
+
protocolVersion: PROTOCOL_VERSION
|
|
8842
|
+
});
|
|
8843
|
+
} catch (err) {
|
|
8844
|
+
const pending = rpc.pendingBuffer();
|
|
8845
|
+
rpc.close();
|
|
8846
|
+
return { proc, pendingStdout: pending, error: err };
|
|
8847
|
+
}
|
|
8848
|
+
void readStderr(proc, options.verbose);
|
|
8793
8849
|
const discoverResult2 = discoverRaw;
|
|
8794
8850
|
const supported = (discoverResult2?.supportedVersions ?? []).filter(
|
|
8795
8851
|
(v) => typeof v === "string"
|
|
8796
8852
|
);
|
|
8797
8853
|
if (!supported.includes(PROTOCOL_VERSION)) {
|
|
8798
|
-
proc.kill("SIGTERM");
|
|
8854
|
+
await proc.kill("SIGTERM");
|
|
8799
8855
|
throw new Error(
|
|
8800
8856
|
`Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
8801
8857
|
);
|
|
@@ -8829,25 +8885,37 @@ async function connect(platform2, options) {
|
|
|
8829
8885
|
platform: platform2
|
|
8830
8886
|
};
|
|
8831
8887
|
if (options.command) {
|
|
8888
|
+
let fallbackProcess;
|
|
8832
8889
|
if (!options.protocolVersion || options.protocolVersion.startsWith("2026")) {
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8890
|
+
const result = await probeStdioStateless({
|
|
8891
|
+
command: options.command,
|
|
8892
|
+
args: options.args,
|
|
8893
|
+
env: options.env,
|
|
8894
|
+
cwd: options.cwd,
|
|
8895
|
+
platform: platform2,
|
|
8896
|
+
version: options.version,
|
|
8897
|
+
timeoutMs: options.timeoutMs,
|
|
8898
|
+
signal: options.signal,
|
|
8899
|
+
verbose: options.verbose
|
|
8900
|
+
});
|
|
8901
|
+
if (!("proc" in result)) return result;
|
|
8902
|
+
const isMethodNotFound = result.error instanceof StatelessRpcError && result.error.code === -32601;
|
|
8903
|
+
const isUnsupported = result.error.message.includes("does not support");
|
|
8904
|
+
if (options.protocolVersion?.startsWith("2026")) {
|
|
8905
|
+
try {
|
|
8906
|
+
await result.proc.kill("SIGTERM");
|
|
8907
|
+
} catch {
|
|
8908
|
+
}
|
|
8909
|
+
throw result.error;
|
|
8910
|
+
}
|
|
8911
|
+
if (!isMethodNotFound && !isUnsupported) {
|
|
8912
|
+
try {
|
|
8913
|
+
await result.proc.kill("SIGTERM");
|
|
8914
|
+
} catch {
|
|
8915
|
+
}
|
|
8916
|
+
throw result.error;
|
|
8850
8917
|
}
|
|
8918
|
+
fallbackProcess = { proc: result.proc, pendingStdout: result.pendingStdout };
|
|
8851
8919
|
}
|
|
8852
8920
|
return createPiConnection({
|
|
8853
8921
|
command: options.command,
|
|
@@ -8855,6 +8923,7 @@ async function connect(platform2, options) {
|
|
|
8855
8923
|
env: options.env,
|
|
8856
8924
|
cwd: options.cwd,
|
|
8857
8925
|
roots: options.roots,
|
|
8926
|
+
existingProcess: fallbackProcess,
|
|
8858
8927
|
...shared
|
|
8859
8928
|
});
|
|
8860
8929
|
}
|
|
@@ -12129,7 +12198,7 @@ function capOutput(value, limit) {
|
|
|
12129
12198
|
${value.slice(value.length - half)}`;
|
|
12130
12199
|
}
|
|
12131
12200
|
var inflightProcs = /* @__PURE__ */ new Set();
|
|
12132
|
-
async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs, maxOutput, cwd) {
|
|
12201
|
+
async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs, maxOutput, cwd, signal) {
|
|
12133
12202
|
let proc;
|
|
12134
12203
|
try {
|
|
12135
12204
|
proc = await platform2.spawn({
|
|
@@ -12168,23 +12237,41 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
12168
12237
|
const timer = new Promise(
|
|
12169
12238
|
(resolve) => setTimeout(() => resolve("timeout"), timeoutMs)
|
|
12170
12239
|
);
|
|
12240
|
+
const abortPromise = signal ? new Promise((resolve) => {
|
|
12241
|
+
if (signal.aborted) {
|
|
12242
|
+
resolve("timeout");
|
|
12243
|
+
return;
|
|
12244
|
+
}
|
|
12245
|
+
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
|
|
12246
|
+
}) : null;
|
|
12171
12247
|
const work = Promise.all([
|
|
12172
12248
|
readStream3(proc.stdout, stdoutChunks),
|
|
12173
12249
|
readStream3(proc.stderr, stderrChunks),
|
|
12174
12250
|
proc.wait()
|
|
12175
12251
|
]);
|
|
12176
|
-
const
|
|
12252
|
+
const racers = [
|
|
12253
|
+
work,
|
|
12254
|
+
timer
|
|
12255
|
+
];
|
|
12256
|
+
if (abortPromise !== null) racers.push(abortPromise);
|
|
12257
|
+
const result = await Promise.race(racers);
|
|
12177
12258
|
if (result === "timeout") {
|
|
12178
|
-
proc.kill("SIGTERM");
|
|
12179
|
-
|
|
12180
|
-
|
|
12181
|
-
|
|
12182
|
-
|
|
12259
|
+
await proc.kill("SIGTERM");
|
|
12260
|
+
if (!signal?.aborted) {
|
|
12261
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
12262
|
+
try {
|
|
12263
|
+
await proc.kill("SIGKILL");
|
|
12264
|
+
} catch {
|
|
12265
|
+
}
|
|
12183
12266
|
}
|
|
12184
12267
|
inflightProcs.delete(proc);
|
|
12185
12268
|
return { stdout: "", stderr: "timed out", exitCode: 124 };
|
|
12186
12269
|
}
|
|
12187
12270
|
inflightProcs.delete(proc);
|
|
12271
|
+
try {
|
|
12272
|
+
await proc.kill("SIGTERM");
|
|
12273
|
+
} catch {
|
|
12274
|
+
}
|
|
12188
12275
|
const [, , exitResult] = result;
|
|
12189
12276
|
const dec = new TextDecoder();
|
|
12190
12277
|
return {
|
|
@@ -12203,25 +12290,31 @@ function concat(chunks) {
|
|
|
12203
12290
|
}
|
|
12204
12291
|
return out;
|
|
12205
12292
|
}
|
|
12293
|
+
var KILL_GRACE_MS = 2e3;
|
|
12206
12294
|
async function killInflight() {
|
|
12207
|
-
|
|
12208
|
-
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
}
|
|
12212
|
-
}
|
|
12213
|
-
if (inflightProcs.size > 0) {
|
|
12214
|
-
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
12215
|
-
for (const proc of inflightProcs) {
|
|
12295
|
+
const procs = [...inflightProcs];
|
|
12296
|
+
if (procs.length === 0) return;
|
|
12297
|
+
await Promise.all(
|
|
12298
|
+
procs.map(async (proc) => {
|
|
12216
12299
|
try {
|
|
12217
|
-
proc.kill("
|
|
12300
|
+
await proc.kill("SIGTERM");
|
|
12218
12301
|
} catch {
|
|
12219
12302
|
}
|
|
12220
|
-
|
|
12221
|
-
|
|
12303
|
+
const exited = await Promise.race([
|
|
12304
|
+
proc.wait().then(() => true),
|
|
12305
|
+
new Promise((r) => setTimeout(() => r(false), KILL_GRACE_MS))
|
|
12306
|
+
]);
|
|
12307
|
+
if (!exited) {
|
|
12308
|
+
try {
|
|
12309
|
+
await proc.kill("SIGKILL");
|
|
12310
|
+
} catch {
|
|
12311
|
+
}
|
|
12312
|
+
}
|
|
12313
|
+
})
|
|
12314
|
+
);
|
|
12222
12315
|
inflightProcs.clear();
|
|
12223
12316
|
}
|
|
12224
|
-
async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
12317
|
+
async function runHelpForCli(platform2, filePath, childEnv, cwd, signal) {
|
|
12225
12318
|
const dashed = await runCommand(
|
|
12226
12319
|
platform2,
|
|
12227
12320
|
filePath,
|
|
@@ -12230,9 +12323,11 @@ async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
|
12230
12323
|
void 0,
|
|
12231
12324
|
1e4,
|
|
12232
12325
|
MAX_OUTPUT_DEFAULT,
|
|
12233
|
-
cwd
|
|
12326
|
+
cwd,
|
|
12327
|
+
signal
|
|
12234
12328
|
);
|
|
12235
12329
|
if (dashed.exitCode === 0 && dashed.stdout.trim()) return dashed.stdout;
|
|
12330
|
+
if (signal?.aborted) return "";
|
|
12236
12331
|
const plain = await runCommand(
|
|
12237
12332
|
platform2,
|
|
12238
12333
|
filePath,
|
|
@@ -12241,7 +12336,8 @@ async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
|
12241
12336
|
void 0,
|
|
12242
12337
|
1e4,
|
|
12243
12338
|
MAX_OUTPUT_DEFAULT,
|
|
12244
|
-
cwd
|
|
12339
|
+
cwd,
|
|
12340
|
+
signal
|
|
12245
12341
|
);
|
|
12246
12342
|
if (plain.exitCode === 0 && plain.stdout.trim()) return plain.stdout;
|
|
12247
12343
|
return dashed.stdout.trim() ? dashed.stdout : "";
|
|
@@ -12268,7 +12364,7 @@ async function expandGroupCommands(platform2, filePath, commands, childEnv, cwd)
|
|
|
12268
12364
|
}
|
|
12269
12365
|
return current;
|
|
12270
12366
|
}
|
|
12271
|
-
async function discoverCli(platform2, filePath, explicitName, childEnv, cwd) {
|
|
12367
|
+
async function discoverCli(platform2, filePath, explicitName, childEnv, cwd, signal) {
|
|
12272
12368
|
const mcp = await runCommand(
|
|
12273
12369
|
platform2,
|
|
12274
12370
|
filePath,
|
|
@@ -12277,10 +12373,11 @@ async function discoverCli(platform2, filePath, explicitName, childEnv, cwd) {
|
|
|
12277
12373
|
void 0,
|
|
12278
12374
|
1e4,
|
|
12279
12375
|
MAX_OUTPUT_DEFAULT,
|
|
12280
|
-
cwd
|
|
12376
|
+
cwd,
|
|
12377
|
+
signal
|
|
12281
12378
|
);
|
|
12282
12379
|
const override = mcp.exitCode === 0 ? parseMcpOverride(mcp.stdout) : null;
|
|
12283
|
-
const helpText2 = override ? "" : await runHelpForCli(platform2, filePath, childEnv, cwd);
|
|
12380
|
+
const helpText2 = override || signal?.aborted ? "" : await runHelpForCli(platform2, filePath, childEnv, cwd, signal);
|
|
12284
12381
|
if (!override && !helpText2.trim()) {
|
|
12285
12382
|
throw new Error(`${filePath} did not print help or --mcp descriptor`);
|
|
12286
12383
|
}
|
|
@@ -12400,6 +12497,32 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
|
|
|
12400
12497
|
}
|
|
12401
12498
|
return { invoke, rpcTools };
|
|
12402
12499
|
}
|
|
12500
|
+
async function discoverWithSignal(platform2, commands, childEnv, cwd) {
|
|
12501
|
+
const clis = [];
|
|
12502
|
+
let earlyShutdown = false;
|
|
12503
|
+
let inflightDone;
|
|
12504
|
+
const earlyAbort = new AbortController();
|
|
12505
|
+
const onShutdown = () => {
|
|
12506
|
+
earlyShutdown = true;
|
|
12507
|
+
earlyAbort.abort();
|
|
12508
|
+
inflightDone = killInflight();
|
|
12509
|
+
};
|
|
12510
|
+
platform2.onSignal?.("SIGTERM", onShutdown);
|
|
12511
|
+
platform2.onSignal?.("SIGINT", onShutdown);
|
|
12512
|
+
for (const cmd of commands) {
|
|
12513
|
+
try {
|
|
12514
|
+
const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, cwd, earlyAbort.signal);
|
|
12515
|
+
clis.push(cli);
|
|
12516
|
+
} catch (err) {
|
|
12517
|
+
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12518
|
+
await platform2.write(2, `mcpcat serve: ${err.message}
|
|
12519
|
+
`);
|
|
12520
|
+
return { exitCode: 2 };
|
|
12521
|
+
}
|
|
12522
|
+
}
|
|
12523
|
+
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12524
|
+
return { clis };
|
|
12525
|
+
}
|
|
12403
12526
|
async function runServe(platform2, options, version) {
|
|
12404
12527
|
let resolvedBearer;
|
|
12405
12528
|
let generatedBearer = false;
|
|
@@ -12443,17 +12566,17 @@ async function runServe(platform2, options, version) {
|
|
|
12443
12566
|
}
|
|
12444
12567
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
12445
12568
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
12446
|
-
const
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
`);
|
|
12454
|
-
return 2;
|
|
12569
|
+
const discovery = await discoverWithSignal(platform2, options.commands, childEnv, serveCwd);
|
|
12570
|
+
if ("exitCode" in discovery) {
|
|
12571
|
+
if (discovery.inflightDone !== void 0) {
|
|
12572
|
+
try {
|
|
12573
|
+
await discovery.inflightDone;
|
|
12574
|
+
} catch {
|
|
12575
|
+
}
|
|
12455
12576
|
}
|
|
12577
|
+
return discovery.exitCode;
|
|
12456
12578
|
}
|
|
12579
|
+
const { clis } = discovery;
|
|
12457
12580
|
const allTools = clis.flatMap((cli) => buildTools(cli.name, cli.commands));
|
|
12458
12581
|
for (const spec of options.toolTemplates) {
|
|
12459
12582
|
const tool = parseToolTemplate(spec);
|
|
@@ -12679,6 +12802,7 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
12679
12802
|
if (platform2.onSignal) {
|
|
12680
12803
|
platform2.onSignal("SIGINT", shutdown);
|
|
12681
12804
|
platform2.onSignal("SIGTERM", shutdown);
|
|
12805
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
12682
12806
|
}
|
|
12683
12807
|
const handler = (request) => handleProxyMcpRequest(
|
|
12684
12808
|
request,
|
|
@@ -12838,11 +12962,12 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12838
12962
|
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
12839
12963
|
`);
|
|
12840
12964
|
let shutdownRequested = false;
|
|
12965
|
+
let inflightDone;
|
|
12841
12966
|
const shutdown = () => {
|
|
12842
12967
|
if (shutdownRequested) return;
|
|
12843
12968
|
shutdownRequested = true;
|
|
12844
12969
|
listener.close();
|
|
12845
|
-
|
|
12970
|
+
inflightDone = killInflight();
|
|
12846
12971
|
if (tokenFilePath) {
|
|
12847
12972
|
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12848
12973
|
});
|
|
@@ -12851,6 +12976,7 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12851
12976
|
if (platform2.onSignal) {
|
|
12852
12977
|
platform2.onSignal("SIGINT", shutdown);
|
|
12853
12978
|
platform2.onSignal("SIGTERM", shutdown);
|
|
12979
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
12854
12980
|
}
|
|
12855
12981
|
const handler = (request) => handleMcpRequest(
|
|
12856
12982
|
request,
|
|
@@ -12878,6 +13004,12 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12878
13004
|
} catch {
|
|
12879
13005
|
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
12880
13006
|
}
|
|
13007
|
+
if (inflightDone !== void 0) {
|
|
13008
|
+
try {
|
|
13009
|
+
await inflightDone;
|
|
13010
|
+
} catch {
|
|
13011
|
+
}
|
|
13012
|
+
}
|
|
12881
13013
|
return 0;
|
|
12882
13014
|
}
|
|
12883
13015
|
async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
@@ -15207,6 +15339,10 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
15207
15339
|
} catch {
|
|
15208
15340
|
}
|
|
15209
15341
|
}
|
|
15342
|
+
try {
|
|
15343
|
+
await Promise.race([killAllStdio(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
15344
|
+
} catch {
|
|
15345
|
+
}
|
|
15210
15346
|
platform2.exit(exitCode);
|
|
15211
15347
|
}
|
|
15212
15348
|
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
@@ -15545,6 +15681,10 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
15545
15681
|
} catch {
|
|
15546
15682
|
}
|
|
15547
15683
|
}
|
|
15684
|
+
try {
|
|
15685
|
+
await Promise.race([killAllStdio(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
15686
|
+
} catch {
|
|
15687
|
+
}
|
|
15548
15688
|
platform2.exit(exitCode);
|
|
15549
15689
|
}
|
|
15550
15690
|
platform2.onSignal?.("SIGINT", () => {
|
|
@@ -15942,5 +16082,5 @@ try {
|
|
|
15942
16082
|
});
|
|
15943
16083
|
} catch {
|
|
15944
16084
|
}
|
|
15945
|
-
var code = await run(platform, "0.14.
|
|
16085
|
+
var code = await run(platform, "0.14.3", args);
|
|
15946
16086
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -7632,6 +7632,40 @@ var StreamableHttpTransport = class extends TransportEvents {
|
|
|
7632
7632
|
}
|
|
7633
7633
|
};
|
|
7634
7634
|
|
|
7635
|
+
// src/client/stdio-registry.ts
|
|
7636
|
+
var GRACE_MS = 2e3;
|
|
7637
|
+
var registry = /* @__PURE__ */ new Set();
|
|
7638
|
+
function trackStdio(proc) {
|
|
7639
|
+
registry.add(proc);
|
|
7640
|
+
void proc.wait().then(
|
|
7641
|
+
() => registry.delete(proc),
|
|
7642
|
+
() => registry.delete(proc)
|
|
7643
|
+
);
|
|
7644
|
+
}
|
|
7645
|
+
async function killAllStdio() {
|
|
7646
|
+
const procs = [...registry];
|
|
7647
|
+
if (procs.length === 0) return;
|
|
7648
|
+
await Promise.all(
|
|
7649
|
+
procs.map(async (proc) => {
|
|
7650
|
+
try {
|
|
7651
|
+
await proc.kill("SIGTERM");
|
|
7652
|
+
} catch {
|
|
7653
|
+
}
|
|
7654
|
+
const exited = await Promise.race([
|
|
7655
|
+
proc.wait().then(() => true),
|
|
7656
|
+
new Promise((r) => setTimeout(() => r(false), GRACE_MS))
|
|
7657
|
+
]);
|
|
7658
|
+
if (!exited) {
|
|
7659
|
+
try {
|
|
7660
|
+
await proc.kill("SIGKILL");
|
|
7661
|
+
} catch {
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
})
|
|
7665
|
+
);
|
|
7666
|
+
registry.clear();
|
|
7667
|
+
}
|
|
7668
|
+
|
|
7635
7669
|
// src/client/platform-stdio-transport.ts
|
|
7636
7670
|
var DEFAULT_MAX_MESSAGE_BYTES2 = 4 * 1024 * 1024;
|
|
7637
7671
|
var DEFAULT_MAX_STDERR_BYTES2 = 64 * 1024;
|
|
@@ -7688,16 +7722,21 @@ var PlatformStdioTransport = class {
|
|
|
7688
7722
|
if (this.started) throw new Error("MCP stdio transport already started");
|
|
7689
7723
|
if (this.closed) throw new Error("MCP connection closed");
|
|
7690
7724
|
this.started = true;
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7725
|
+
if (this.options.existingProcess) {
|
|
7726
|
+
this.process = this.options.existingProcess;
|
|
7727
|
+
} else {
|
|
7728
|
+
const env2 = this.options.inheritEnv === false ? { ...this.options.env ?? {} } : {
|
|
7729
|
+
...this.options.platform.env,
|
|
7730
|
+
...this.options.env
|
|
7731
|
+
};
|
|
7732
|
+
this.process = await this.options.platform.spawn({
|
|
7733
|
+
argv: [this.options.command, ...this.options.args ?? []],
|
|
7734
|
+
env: env2,
|
|
7735
|
+
cwd: this.options.cwd
|
|
7736
|
+
});
|
|
7737
|
+
trackStdio(this.process);
|
|
7738
|
+
}
|
|
7739
|
+
void this.readStdout(this.options.pendingStdout);
|
|
7701
7740
|
void this.readStderr();
|
|
7702
7741
|
void this.waitForExit();
|
|
7703
7742
|
}
|
|
@@ -7730,22 +7769,22 @@ var PlatformStdioTransport = class {
|
|
|
7730
7769
|
}
|
|
7731
7770
|
const exited = await Promise.race([done, delay(grace)]);
|
|
7732
7771
|
if (!exited) {
|
|
7733
|
-
proc.kill("SIGTERM");
|
|
7772
|
+
await proc.kill("SIGTERM");
|
|
7734
7773
|
const exitedAfterTerm = await Promise.race([done, delay(closeTimeoutMs)]);
|
|
7735
7774
|
if (!exitedAfterTerm) {
|
|
7736
|
-
proc.kill("SIGKILL");
|
|
7775
|
+
await proc.kill("SIGKILL");
|
|
7737
7776
|
await done;
|
|
7738
7777
|
}
|
|
7739
7778
|
}
|
|
7740
|
-
proc.kill("SIGTERM");
|
|
7779
|
+
await proc.kill("SIGTERM");
|
|
7741
7780
|
this.emitClose();
|
|
7742
7781
|
}
|
|
7743
|
-
async readStdout() {
|
|
7782
|
+
async readStdout(pending) {
|
|
7744
7783
|
const proc = this.process;
|
|
7745
7784
|
if (!proc) return;
|
|
7746
7785
|
const reader = proc.stdout.getReader();
|
|
7747
7786
|
const maxBytes = this.options.maxMessageBytes ?? DEFAULT_MAX_MESSAGE_BYTES2;
|
|
7748
|
-
let buffer = "";
|
|
7787
|
+
let buffer = pending ?? "";
|
|
7749
7788
|
try {
|
|
7750
7789
|
for (; ; ) {
|
|
7751
7790
|
const { done, value } = await reader.read();
|
|
@@ -7805,6 +7844,10 @@ var PlatformStdioTransport = class {
|
|
|
7805
7844
|
await proc.wait();
|
|
7806
7845
|
} catch {
|
|
7807
7846
|
}
|
|
7847
|
+
try {
|
|
7848
|
+
await proc.kill("SIGTERM");
|
|
7849
|
+
} catch {
|
|
7850
|
+
}
|
|
7808
7851
|
if (!this.closed) {
|
|
7809
7852
|
this.emitClose();
|
|
7810
7853
|
}
|
|
@@ -8146,7 +8189,9 @@ function buildTransport(options) {
|
|
|
8146
8189
|
cwd: options.cwd,
|
|
8147
8190
|
inheritEnv: true,
|
|
8148
8191
|
platform: options.platform,
|
|
8149
|
-
onStderr: buildStderrHandler(options)
|
|
8192
|
+
onStderr: buildStderrHandler(options),
|
|
8193
|
+
existingProcess: options.existingProcess?.proc,
|
|
8194
|
+
pendingStdout: options.existingProcess?.pendingStdout
|
|
8150
8195
|
});
|
|
8151
8196
|
}
|
|
8152
8197
|
return new StdioTransport({
|
|
@@ -8333,6 +8378,9 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8333
8378
|
reader.releaseLock();
|
|
8334
8379
|
} catch {
|
|
8335
8380
|
}
|
|
8381
|
+
},
|
|
8382
|
+
pendingBuffer() {
|
|
8383
|
+
return readBuffer;
|
|
8336
8384
|
}
|
|
8337
8385
|
};
|
|
8338
8386
|
}
|
|
@@ -8385,13 +8433,13 @@ function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
|
8385
8433
|
async close() {
|
|
8386
8434
|
rpc.close();
|
|
8387
8435
|
try {
|
|
8388
|
-
proc.kill("SIGTERM");
|
|
8436
|
+
await proc.kill("SIGTERM");
|
|
8389
8437
|
} catch {
|
|
8390
8438
|
}
|
|
8391
8439
|
}
|
|
8392
8440
|
};
|
|
8393
8441
|
}
|
|
8394
|
-
async function
|
|
8442
|
+
async function probeStdioStateless(options) {
|
|
8395
8443
|
const inheritedEnv = {
|
|
8396
8444
|
...options.platform.env,
|
|
8397
8445
|
...options.env
|
|
@@ -8401,22 +8449,30 @@ async function createStdioStatelessConnection(options) {
|
|
|
8401
8449
|
env: inheritedEnv,
|
|
8402
8450
|
cwd: options.cwd
|
|
8403
8451
|
});
|
|
8404
|
-
|
|
8452
|
+
trackStdio(proc);
|
|
8405
8453
|
const rpc = createStdioJsonRpc(proc, options.platform, {
|
|
8406
8454
|
version: options.version,
|
|
8407
8455
|
timeoutMs: options.timeoutMs,
|
|
8408
8456
|
signal: options.signal,
|
|
8409
8457
|
verbose: options.verbose
|
|
8410
8458
|
});
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8459
|
+
let discoverRaw;
|
|
8460
|
+
try {
|
|
8461
|
+
discoverRaw = await rpc.request("server/discover", {
|
|
8462
|
+
protocolVersion: PROTOCOL_VERSION
|
|
8463
|
+
});
|
|
8464
|
+
} catch (err) {
|
|
8465
|
+
const pending = rpc.pendingBuffer();
|
|
8466
|
+
rpc.close();
|
|
8467
|
+
return { proc, pendingStdout: pending, error: err };
|
|
8468
|
+
}
|
|
8469
|
+
void readStderr(proc, options.verbose);
|
|
8414
8470
|
const discoverResult2 = discoverRaw;
|
|
8415
8471
|
const supported = (discoverResult2?.supportedVersions ?? []).filter(
|
|
8416
8472
|
(v) => typeof v === "string"
|
|
8417
8473
|
);
|
|
8418
8474
|
if (!supported.includes(PROTOCOL_VERSION)) {
|
|
8419
|
-
proc.kill("SIGTERM");
|
|
8475
|
+
await proc.kill("SIGTERM");
|
|
8420
8476
|
throw new Error(
|
|
8421
8477
|
`Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
8422
8478
|
);
|
|
@@ -8450,25 +8506,37 @@ async function connect(platform2, options) {
|
|
|
8450
8506
|
platform: platform2
|
|
8451
8507
|
};
|
|
8452
8508
|
if (options.command) {
|
|
8509
|
+
let fallbackProcess;
|
|
8453
8510
|
if (!options.protocolVersion || options.protocolVersion.startsWith("2026")) {
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8511
|
+
const result = await probeStdioStateless({
|
|
8512
|
+
command: options.command,
|
|
8513
|
+
args: options.args,
|
|
8514
|
+
env: options.env,
|
|
8515
|
+
cwd: options.cwd,
|
|
8516
|
+
platform: platform2,
|
|
8517
|
+
version: options.version,
|
|
8518
|
+
timeoutMs: options.timeoutMs,
|
|
8519
|
+
signal: options.signal,
|
|
8520
|
+
verbose: options.verbose
|
|
8521
|
+
});
|
|
8522
|
+
if (!("proc" in result)) return result;
|
|
8523
|
+
const isMethodNotFound = result.error instanceof StatelessRpcError && result.error.code === -32601;
|
|
8524
|
+
const isUnsupported = result.error.message.includes("does not support");
|
|
8525
|
+
if (options.protocolVersion?.startsWith("2026")) {
|
|
8526
|
+
try {
|
|
8527
|
+
await result.proc.kill("SIGTERM");
|
|
8528
|
+
} catch {
|
|
8529
|
+
}
|
|
8530
|
+
throw result.error;
|
|
8531
|
+
}
|
|
8532
|
+
if (!isMethodNotFound && !isUnsupported) {
|
|
8533
|
+
try {
|
|
8534
|
+
await result.proc.kill("SIGTERM");
|
|
8535
|
+
} catch {
|
|
8536
|
+
}
|
|
8537
|
+
throw result.error;
|
|
8471
8538
|
}
|
|
8539
|
+
fallbackProcess = { proc: result.proc, pendingStdout: result.pendingStdout };
|
|
8472
8540
|
}
|
|
8473
8541
|
return createPiConnection({
|
|
8474
8542
|
command: options.command,
|
|
@@ -8476,6 +8544,7 @@ async function connect(platform2, options) {
|
|
|
8476
8544
|
env: options.env,
|
|
8477
8545
|
cwd: options.cwd,
|
|
8478
8546
|
roots: options.roots,
|
|
8547
|
+
existingProcess: fallbackProcess,
|
|
8479
8548
|
...shared
|
|
8480
8549
|
});
|
|
8481
8550
|
}
|
|
@@ -11750,7 +11819,7 @@ function capOutput(value, limit) {
|
|
|
11750
11819
|
${value.slice(value.length - half)}`;
|
|
11751
11820
|
}
|
|
11752
11821
|
var inflightProcs = /* @__PURE__ */ new Set();
|
|
11753
|
-
async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs, maxOutput, cwd) {
|
|
11822
|
+
async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs, maxOutput, cwd, signal) {
|
|
11754
11823
|
let proc;
|
|
11755
11824
|
try {
|
|
11756
11825
|
proc = await platform2.spawn({
|
|
@@ -11789,23 +11858,41 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
11789
11858
|
const timer = new Promise(
|
|
11790
11859
|
(resolve2) => setTimeout(() => resolve2("timeout"), timeoutMs)
|
|
11791
11860
|
);
|
|
11861
|
+
const abortPromise = signal ? new Promise((resolve2) => {
|
|
11862
|
+
if (signal.aborted) {
|
|
11863
|
+
resolve2("timeout");
|
|
11864
|
+
return;
|
|
11865
|
+
}
|
|
11866
|
+
signal.addEventListener("abort", () => resolve2("timeout"), { once: true });
|
|
11867
|
+
}) : null;
|
|
11792
11868
|
const work = Promise.all([
|
|
11793
11869
|
readStream3(proc.stdout, stdoutChunks),
|
|
11794
11870
|
readStream3(proc.stderr, stderrChunks),
|
|
11795
11871
|
proc.wait()
|
|
11796
11872
|
]);
|
|
11797
|
-
const
|
|
11873
|
+
const racers = [
|
|
11874
|
+
work,
|
|
11875
|
+
timer
|
|
11876
|
+
];
|
|
11877
|
+
if (abortPromise !== null) racers.push(abortPromise);
|
|
11878
|
+
const result = await Promise.race(racers);
|
|
11798
11879
|
if (result === "timeout") {
|
|
11799
|
-
proc.kill("SIGTERM");
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11880
|
+
await proc.kill("SIGTERM");
|
|
11881
|
+
if (!signal?.aborted) {
|
|
11882
|
+
await new Promise((resolve2) => setTimeout(resolve2, 5e3));
|
|
11883
|
+
try {
|
|
11884
|
+
await proc.kill("SIGKILL");
|
|
11885
|
+
} catch {
|
|
11886
|
+
}
|
|
11804
11887
|
}
|
|
11805
11888
|
inflightProcs.delete(proc);
|
|
11806
11889
|
return { stdout: "", stderr: "timed out", exitCode: 124 };
|
|
11807
11890
|
}
|
|
11808
11891
|
inflightProcs.delete(proc);
|
|
11892
|
+
try {
|
|
11893
|
+
await proc.kill("SIGTERM");
|
|
11894
|
+
} catch {
|
|
11895
|
+
}
|
|
11809
11896
|
const [, , exitResult] = result;
|
|
11810
11897
|
const dec = new TextDecoder();
|
|
11811
11898
|
return {
|
|
@@ -11824,25 +11911,31 @@ function concat(chunks) {
|
|
|
11824
11911
|
}
|
|
11825
11912
|
return out;
|
|
11826
11913
|
}
|
|
11914
|
+
var KILL_GRACE_MS = 2e3;
|
|
11827
11915
|
async function killInflight() {
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
}
|
|
11833
|
-
}
|
|
11834
|
-
if (inflightProcs.size > 0) {
|
|
11835
|
-
await new Promise((resolve2) => setTimeout(resolve2, 5e3));
|
|
11836
|
-
for (const proc of inflightProcs) {
|
|
11916
|
+
const procs = [...inflightProcs];
|
|
11917
|
+
if (procs.length === 0) return;
|
|
11918
|
+
await Promise.all(
|
|
11919
|
+
procs.map(async (proc) => {
|
|
11837
11920
|
try {
|
|
11838
|
-
proc.kill("
|
|
11921
|
+
await proc.kill("SIGTERM");
|
|
11839
11922
|
} catch {
|
|
11840
11923
|
}
|
|
11841
|
-
|
|
11842
|
-
|
|
11924
|
+
const exited = await Promise.race([
|
|
11925
|
+
proc.wait().then(() => true),
|
|
11926
|
+
new Promise((r) => setTimeout(() => r(false), KILL_GRACE_MS))
|
|
11927
|
+
]);
|
|
11928
|
+
if (!exited) {
|
|
11929
|
+
try {
|
|
11930
|
+
await proc.kill("SIGKILL");
|
|
11931
|
+
} catch {
|
|
11932
|
+
}
|
|
11933
|
+
}
|
|
11934
|
+
})
|
|
11935
|
+
);
|
|
11843
11936
|
inflightProcs.clear();
|
|
11844
11937
|
}
|
|
11845
|
-
async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
11938
|
+
async function runHelpForCli(platform2, filePath, childEnv, cwd, signal) {
|
|
11846
11939
|
const dashed = await runCommand(
|
|
11847
11940
|
platform2,
|
|
11848
11941
|
filePath,
|
|
@@ -11851,9 +11944,11 @@ async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
|
11851
11944
|
void 0,
|
|
11852
11945
|
1e4,
|
|
11853
11946
|
MAX_OUTPUT_DEFAULT,
|
|
11854
|
-
cwd
|
|
11947
|
+
cwd,
|
|
11948
|
+
signal
|
|
11855
11949
|
);
|
|
11856
11950
|
if (dashed.exitCode === 0 && dashed.stdout.trim()) return dashed.stdout;
|
|
11951
|
+
if (signal?.aborted) return "";
|
|
11857
11952
|
const plain = await runCommand(
|
|
11858
11953
|
platform2,
|
|
11859
11954
|
filePath,
|
|
@@ -11862,7 +11957,8 @@ async function runHelpForCli(platform2, filePath, childEnv, cwd) {
|
|
|
11862
11957
|
void 0,
|
|
11863
11958
|
1e4,
|
|
11864
11959
|
MAX_OUTPUT_DEFAULT,
|
|
11865
|
-
cwd
|
|
11960
|
+
cwd,
|
|
11961
|
+
signal
|
|
11866
11962
|
);
|
|
11867
11963
|
if (plain.exitCode === 0 && plain.stdout.trim()) return plain.stdout;
|
|
11868
11964
|
return dashed.stdout.trim() ? dashed.stdout : "";
|
|
@@ -11889,7 +11985,7 @@ async function expandGroupCommands(platform2, filePath, commands, childEnv, cwd)
|
|
|
11889
11985
|
}
|
|
11890
11986
|
return current;
|
|
11891
11987
|
}
|
|
11892
|
-
async function discoverCli(platform2, filePath, explicitName, childEnv, cwd) {
|
|
11988
|
+
async function discoverCli(platform2, filePath, explicitName, childEnv, cwd, signal) {
|
|
11893
11989
|
const mcp = await runCommand(
|
|
11894
11990
|
platform2,
|
|
11895
11991
|
filePath,
|
|
@@ -11898,10 +11994,11 @@ async function discoverCli(platform2, filePath, explicitName, childEnv, cwd) {
|
|
|
11898
11994
|
void 0,
|
|
11899
11995
|
1e4,
|
|
11900
11996
|
MAX_OUTPUT_DEFAULT,
|
|
11901
|
-
cwd
|
|
11997
|
+
cwd,
|
|
11998
|
+
signal
|
|
11902
11999
|
);
|
|
11903
12000
|
const override = mcp.exitCode === 0 ? parseMcpOverride(mcp.stdout) : null;
|
|
11904
|
-
const helpText2 = override ? "" : await runHelpForCli(platform2, filePath, childEnv, cwd);
|
|
12001
|
+
const helpText2 = override || signal?.aborted ? "" : await runHelpForCli(platform2, filePath, childEnv, cwd, signal);
|
|
11905
12002
|
if (!override && !helpText2.trim()) {
|
|
11906
12003
|
throw new Error(`${filePath} did not print help or --mcp descriptor`);
|
|
11907
12004
|
}
|
|
@@ -12021,6 +12118,32 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
|
|
|
12021
12118
|
}
|
|
12022
12119
|
return { invoke, rpcTools };
|
|
12023
12120
|
}
|
|
12121
|
+
async function discoverWithSignal(platform2, commands, childEnv, cwd) {
|
|
12122
|
+
const clis = [];
|
|
12123
|
+
let earlyShutdown = false;
|
|
12124
|
+
let inflightDone;
|
|
12125
|
+
const earlyAbort = new AbortController();
|
|
12126
|
+
const onShutdown = () => {
|
|
12127
|
+
earlyShutdown = true;
|
|
12128
|
+
earlyAbort.abort();
|
|
12129
|
+
inflightDone = killInflight();
|
|
12130
|
+
};
|
|
12131
|
+
platform2.onSignal?.("SIGTERM", onShutdown);
|
|
12132
|
+
platform2.onSignal?.("SIGINT", onShutdown);
|
|
12133
|
+
for (const cmd of commands) {
|
|
12134
|
+
try {
|
|
12135
|
+
const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, cwd, earlyAbort.signal);
|
|
12136
|
+
clis.push(cli);
|
|
12137
|
+
} catch (err) {
|
|
12138
|
+
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12139
|
+
await platform2.write(2, `mcpcat serve: ${err.message}
|
|
12140
|
+
`);
|
|
12141
|
+
return { exitCode: 2 };
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
if (earlyShutdown) return { exitCode: 143, inflightDone };
|
|
12145
|
+
return { clis };
|
|
12146
|
+
}
|
|
12024
12147
|
async function runServe(platform2, options, version) {
|
|
12025
12148
|
let resolvedBearer;
|
|
12026
12149
|
let generatedBearer = false;
|
|
@@ -12064,17 +12187,17 @@ async function runServe(platform2, options, version) {
|
|
|
12064
12187
|
}
|
|
12065
12188
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
12066
12189
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
12067
|
-
const
|
|
12068
|
-
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12074
|
-
`);
|
|
12075
|
-
return 2;
|
|
12190
|
+
const discovery = await discoverWithSignal(platform2, options.commands, childEnv, serveCwd);
|
|
12191
|
+
if ("exitCode" in discovery) {
|
|
12192
|
+
if (discovery.inflightDone !== void 0) {
|
|
12193
|
+
try {
|
|
12194
|
+
await discovery.inflightDone;
|
|
12195
|
+
} catch {
|
|
12196
|
+
}
|
|
12076
12197
|
}
|
|
12198
|
+
return discovery.exitCode;
|
|
12077
12199
|
}
|
|
12200
|
+
const { clis } = discovery;
|
|
12078
12201
|
const allTools = clis.flatMap((cli) => buildTools(cli.name, cli.commands));
|
|
12079
12202
|
for (const spec of options.toolTemplates) {
|
|
12080
12203
|
const tool = parseToolTemplate(spec);
|
|
@@ -12300,6 +12423,7 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
12300
12423
|
if (platform2.onSignal) {
|
|
12301
12424
|
platform2.onSignal("SIGINT", shutdown);
|
|
12302
12425
|
platform2.onSignal("SIGTERM", shutdown);
|
|
12426
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
12303
12427
|
}
|
|
12304
12428
|
const handler = (request) => handleProxyMcpRequest(
|
|
12305
12429
|
request,
|
|
@@ -12459,11 +12583,12 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12459
12583
|
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
12460
12584
|
`);
|
|
12461
12585
|
let shutdownRequested = false;
|
|
12586
|
+
let inflightDone;
|
|
12462
12587
|
const shutdown = () => {
|
|
12463
12588
|
if (shutdownRequested) return;
|
|
12464
12589
|
shutdownRequested = true;
|
|
12465
12590
|
listener.close();
|
|
12466
|
-
|
|
12591
|
+
inflightDone = killInflight();
|
|
12467
12592
|
if (tokenFilePath) {
|
|
12468
12593
|
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
12469
12594
|
});
|
|
@@ -12472,6 +12597,7 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12472
12597
|
if (platform2.onSignal) {
|
|
12473
12598
|
platform2.onSignal("SIGINT", shutdown);
|
|
12474
12599
|
platform2.onSignal("SIGTERM", shutdown);
|
|
12600
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
12475
12601
|
}
|
|
12476
12602
|
const handler = (request) => handleMcpRequest(
|
|
12477
12603
|
request,
|
|
@@ -12499,6 +12625,12 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
12499
12625
|
} catch {
|
|
12500
12626
|
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
12501
12627
|
}
|
|
12628
|
+
if (inflightDone !== void 0) {
|
|
12629
|
+
try {
|
|
12630
|
+
await inflightDone;
|
|
12631
|
+
} catch {
|
|
12632
|
+
}
|
|
12633
|
+
}
|
|
12502
12634
|
return 0;
|
|
12503
12635
|
}
|
|
12504
12636
|
async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
@@ -14828,6 +14960,10 @@ async function dispatchMultiTarget(platform2, parsed, version) {
|
|
|
14828
14960
|
} catch {
|
|
14829
14961
|
}
|
|
14830
14962
|
}
|
|
14963
|
+
try {
|
|
14964
|
+
await Promise.race([killAllStdio(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
14965
|
+
} catch {
|
|
14966
|
+
}
|
|
14831
14967
|
platform2.exit(exitCode);
|
|
14832
14968
|
}
|
|
14833
14969
|
platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
|
|
@@ -15166,6 +15302,10 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
15166
15302
|
} catch {
|
|
15167
15303
|
}
|
|
15168
15304
|
}
|
|
15305
|
+
try {
|
|
15306
|
+
await Promise.race([killAllStdio(), new Promise((r) => setTimeout(r, 3e3))]);
|
|
15307
|
+
} catch {
|
|
15308
|
+
}
|
|
15169
15309
|
platform2.exit(exitCode);
|
|
15170
15310
|
}
|
|
15171
15311
|
platform2.onSignal?.("SIGINT", () => {
|
|
@@ -15351,28 +15491,42 @@ function kernelFs(ctx) {
|
|
|
15351
15491
|
chmod: ctx.fs.chmod ? (path, mode) => ctx.fs.chmod(path, mode) : void 0
|
|
15352
15492
|
};
|
|
15353
15493
|
}
|
|
15494
|
+
var groupSpawnSupported;
|
|
15354
15495
|
async function kernelSpawn(ctx, options) {
|
|
15496
|
+
const tryGroup = groupSpawnSupported !== false;
|
|
15355
15497
|
const result = await ctx.spawn({
|
|
15356
15498
|
argv: options.argv,
|
|
15357
15499
|
env: options.env,
|
|
15358
15500
|
cwd: options.cwd,
|
|
15359
15501
|
stdin: "pipe",
|
|
15360
15502
|
stdout: "pipe",
|
|
15361
|
-
stderr: "pipe"
|
|
15503
|
+
stderr: "pipe",
|
|
15504
|
+
...tryGroup ? { pgid: 0 } : {}
|
|
15362
15505
|
});
|
|
15363
15506
|
const childPid = result.pid;
|
|
15507
|
+
if (groupSpawnSupported === void 0) {
|
|
15508
|
+
try {
|
|
15509
|
+
await ctx.kill(-childPid, "SIGCONT");
|
|
15510
|
+
groupSpawnSupported = true;
|
|
15511
|
+
} catch {
|
|
15512
|
+
groupSpawnSupported = false;
|
|
15513
|
+
}
|
|
15514
|
+
}
|
|
15515
|
+
const useGroupKill = groupSpawnSupported;
|
|
15364
15516
|
return {
|
|
15365
15517
|
stdin: result.stdin,
|
|
15366
15518
|
stdout: result.stdout,
|
|
15367
15519
|
stderr: result.stderr,
|
|
15368
15520
|
wait: () => result.wait(),
|
|
15369
|
-
kill: (signal) => {
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15521
|
+
kill: async (signal) => {
|
|
15522
|
+
if (useGroupKill) {
|
|
15523
|
+
try {
|
|
15524
|
+
await ctx.kill(-childPid, signal);
|
|
15525
|
+
} catch {
|
|
15526
|
+
}
|
|
15373
15527
|
}
|
|
15374
15528
|
try {
|
|
15375
|
-
ctx.kill(childPid, signal);
|
|
15529
|
+
await ctx.kill(childPid, signal);
|
|
15376
15530
|
} catch {
|
|
15377
15531
|
}
|
|
15378
15532
|
}
|
|
@@ -15592,7 +15746,7 @@ async function main(ctx) {
|
|
|
15592
15746
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
15593
15747
|
});
|
|
15594
15748
|
const args = ctx.argv.slice(1);
|
|
15595
|
-
return run(platform2, "0.14.
|
|
15749
|
+
return run(platform2, "0.14.3", args);
|
|
15596
15750
|
}
|
|
15597
15751
|
export {
|
|
15598
15752
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
59
|
+
"@ai-ecoverse/slicc-kernel": "1.76.0",
|
|
60
60
|
"@ai-ecoverse/slicc-shared-web": "1.13.1",
|
|
61
61
|
"@biomejs/biome": "2.5.15",
|
|
62
62
|
"@earendil-works/pi-codemode": "1.2.0",
|