@ai-ecoverse/mcpcat 0.15.0 → 0.15.2
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 +43 -5
- package/dist/kernel/mcpcat.mjs +63 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Or via npm:
|
|
|
22
22
|
npm install -g @ai-ecoverse/mcpcat
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
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.
|
|
25
|
+
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. A SIGKILLed mcpcat runs no handler. On slicc-kernel ≥ 1.88.0, mcpcat spawns stdio servers and served commands with a parent death signal (like Linux `PR_SET_PDEATHSIG`), so the kernel SIGKILLs each server's process group when mcpcat dies. On older kernels, and on the host, where there is no portable equivalent, servers that exit on stdin EOF (most MCP servers) still stop, but servers that ignore it keep running.
|
|
26
26
|
|
|
27
27
|
## Quick start
|
|
28
28
|
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -8177,7 +8177,8 @@ var PlatformStdioTransport = class {
|
|
|
8177
8177
|
this.process = await this.options.platform.spawn({
|
|
8178
8178
|
argv: [this.options.command, ...this.options.args ?? []],
|
|
8179
8179
|
env,
|
|
8180
|
-
cwd: this.options.cwd
|
|
8180
|
+
cwd: this.options.cwd,
|
|
8181
|
+
dieWithParent: true
|
|
8181
8182
|
});
|
|
8182
8183
|
trackStdio(this.process);
|
|
8183
8184
|
}
|
|
@@ -8343,6 +8344,16 @@ var McpAuthError = class extends Error {
|
|
|
8343
8344
|
this.target = target;
|
|
8344
8345
|
}
|
|
8345
8346
|
};
|
|
8347
|
+
var TransportRefusalError = class extends Error {
|
|
8348
|
+
status;
|
|
8349
|
+
reason;
|
|
8350
|
+
constructor(status, reason) {
|
|
8351
|
+
super(reason);
|
|
8352
|
+
this.name = "TransportRefusalError";
|
|
8353
|
+
this.status = status;
|
|
8354
|
+
this.reason = reason;
|
|
8355
|
+
}
|
|
8356
|
+
};
|
|
8346
8357
|
var AuthRetryableError = class extends Error {
|
|
8347
8358
|
response;
|
|
8348
8359
|
constructor(response) {
|
|
@@ -8485,6 +8496,15 @@ async function createStatelessConnection(options) {
|
|
|
8485
8496
|
return h;
|
|
8486
8497
|
}
|
|
8487
8498
|
async function handleErrorStatus(res, method) {
|
|
8499
|
+
if (res.status === 403) {
|
|
8500
|
+
const wwwAuth = res.headers.get("www-authenticate") ?? "";
|
|
8501
|
+
if (!wwwAuth) {
|
|
8502
|
+
const body = await res.text();
|
|
8503
|
+
if (body && !body.includes("insufficient_scope")) {
|
|
8504
|
+
throw new TransportRefusalError(403, body.split("\n")[0].slice(0, 200));
|
|
8505
|
+
}
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8488
8508
|
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
8489
8509
|
throw new AuthRetryableError(res);
|
|
8490
8510
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
@@ -8892,7 +8912,8 @@ async function probeStdioStateless(options) {
|
|
|
8892
8912
|
const proc = await options.platform.spawn({
|
|
8893
8913
|
argv: [options.command, ...options.args ?? []],
|
|
8894
8914
|
env: inheritedEnv,
|
|
8895
|
-
cwd: options.cwd
|
|
8915
|
+
cwd: options.cwd,
|
|
8916
|
+
dieWithParent: true
|
|
8896
8917
|
});
|
|
8897
8918
|
trackStdio(proc);
|
|
8898
8919
|
const rpc = createStdioJsonRpc(proc, options.platform, {
|
|
@@ -9018,6 +9039,7 @@ async function connect(platform2, options) {
|
|
|
9018
9039
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
9019
9040
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
9020
9041
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
9042
|
+
if (err instanceof TransportRefusalError) throw err;
|
|
9021
9043
|
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
9022
9044
|
if (isMethodNotFound) {
|
|
9023
9045
|
return createPiConnection({
|
|
@@ -12509,7 +12531,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
12509
12531
|
proc = await platform2.spawn({
|
|
12510
12532
|
argv: [cmdPath, ...argv],
|
|
12511
12533
|
env: childEnv,
|
|
12512
|
-
cwd
|
|
12534
|
+
cwd,
|
|
12535
|
+
dieWithParent: true
|
|
12513
12536
|
});
|
|
12514
12537
|
} catch (err) {
|
|
12515
12538
|
return { stdout: "", stderr: `spawn failed: ${err.message}`, exitCode: 127 };
|
|
@@ -14449,7 +14472,10 @@ async function sealSecret(platform2, name, value, options) {
|
|
|
14449
14472
|
}
|
|
14450
14473
|
const response = JSON.parse(responseText);
|
|
14451
14474
|
if (response.error) {
|
|
14452
|
-
|
|
14475
|
+
const isStructured = typeof response.error === "object" && response.error !== null && response.error.code;
|
|
14476
|
+
const errno = isStructured ? response.error.code : response.errno ?? "EACCES";
|
|
14477
|
+
const message = isStructured ? response.error.message : response.error;
|
|
14478
|
+
throw Object.assign(new Error(String(message)), { sealErrno: errno });
|
|
14453
14479
|
}
|
|
14454
14480
|
return { placeholder: response.placeholder };
|
|
14455
14481
|
}
|
|
@@ -14461,6 +14487,7 @@ function sealErrorMessage(errno, name) {
|
|
|
14461
14487
|
if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
|
|
14462
14488
|
if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
|
|
14463
14489
|
if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
|
|
14490
|
+
if (errno === "ETIMEDOUT") return "vault did not respond in time";
|
|
14464
14491
|
return `vault error: ${errno}`;
|
|
14465
14492
|
}
|
|
14466
14493
|
|
|
@@ -16088,6 +16115,9 @@ function classifyError(err) {
|
|
|
16088
16115
|
if (err instanceof InputRequiredError) {
|
|
16089
16116
|
return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
|
|
16090
16117
|
}
|
|
16118
|
+
if (err instanceof TransportRefusalError) {
|
|
16119
|
+
return { code: EXIT_CONNECTION, kind: "connection", msg: err.reason };
|
|
16120
|
+
}
|
|
16091
16121
|
if (err instanceof McpTimeoutError2) {
|
|
16092
16122
|
return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
|
|
16093
16123
|
}
|
|
@@ -16144,6 +16174,14 @@ async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config
|
|
|
16144
16174
|
return { provider, credentialSecrets };
|
|
16145
16175
|
}
|
|
16146
16176
|
function classifyConnectionError(err, target, interrupted) {
|
|
16177
|
+
if (err instanceof TransportRefusalError) {
|
|
16178
|
+
const hint = "in seven, reach the host via http://host.slicc.internal:<port>; in tests, enable the transport's hostLoopback";
|
|
16179
|
+
return {
|
|
16180
|
+
code: EXIT_CONNECTION,
|
|
16181
|
+
kind: "connection",
|
|
16182
|
+
msg: `${err.reason} (${hint})`
|
|
16183
|
+
};
|
|
16184
|
+
}
|
|
16147
16185
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
|
|
16148
16186
|
return classifyError(err);
|
|
16149
16187
|
}
|
|
@@ -16649,5 +16687,5 @@ try {
|
|
|
16649
16687
|
});
|
|
16650
16688
|
} catch {
|
|
16651
16689
|
}
|
|
16652
|
-
var code = await run(platform, "0.15.
|
|
16690
|
+
var code = await run(platform, "0.15.2", args);
|
|
16653
16691
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -7798,7 +7798,8 @@ var PlatformStdioTransport = class {
|
|
|
7798
7798
|
this.process = await this.options.platform.spawn({
|
|
7799
7799
|
argv: [this.options.command, ...this.options.args ?? []],
|
|
7800
7800
|
env: env2,
|
|
7801
|
-
cwd: this.options.cwd
|
|
7801
|
+
cwd: this.options.cwd,
|
|
7802
|
+
dieWithParent: true
|
|
7802
7803
|
});
|
|
7803
7804
|
trackStdio(this.process);
|
|
7804
7805
|
}
|
|
@@ -7964,6 +7965,16 @@ var McpAuthError = class extends Error {
|
|
|
7964
7965
|
this.target = target;
|
|
7965
7966
|
}
|
|
7966
7967
|
};
|
|
7968
|
+
var TransportRefusalError = class extends Error {
|
|
7969
|
+
status;
|
|
7970
|
+
reason;
|
|
7971
|
+
constructor(status, reason) {
|
|
7972
|
+
super(reason);
|
|
7973
|
+
this.name = "TransportRefusalError";
|
|
7974
|
+
this.status = status;
|
|
7975
|
+
this.reason = reason;
|
|
7976
|
+
}
|
|
7977
|
+
};
|
|
7967
7978
|
var AuthRetryableError = class extends Error {
|
|
7968
7979
|
response;
|
|
7969
7980
|
constructor(response) {
|
|
@@ -8106,6 +8117,15 @@ async function createStatelessConnection(options) {
|
|
|
8106
8117
|
return h;
|
|
8107
8118
|
}
|
|
8108
8119
|
async function handleErrorStatus(res, method) {
|
|
8120
|
+
if (res.status === 403) {
|
|
8121
|
+
const wwwAuth = res.headers.get("www-authenticate") ?? "";
|
|
8122
|
+
if (!wwwAuth) {
|
|
8123
|
+
const body = await res.text();
|
|
8124
|
+
if (body && !body.includes("insufficient_scope")) {
|
|
8125
|
+
throw new TransportRefusalError(403, body.split("\n")[0].slice(0, 200));
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
}
|
|
8109
8129
|
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
8110
8130
|
throw new AuthRetryableError(res);
|
|
8111
8131
|
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
@@ -8513,7 +8533,8 @@ async function probeStdioStateless(options) {
|
|
|
8513
8533
|
const proc = await options.platform.spawn({
|
|
8514
8534
|
argv: [options.command, ...options.args ?? []],
|
|
8515
8535
|
env: inheritedEnv,
|
|
8516
|
-
cwd: options.cwd
|
|
8536
|
+
cwd: options.cwd,
|
|
8537
|
+
dieWithParent: true
|
|
8517
8538
|
});
|
|
8518
8539
|
trackStdio(proc);
|
|
8519
8540
|
const rpc = createStdioJsonRpc(proc, options.platform, {
|
|
@@ -8639,6 +8660,7 @@ async function connect(platform2, options) {
|
|
|
8639
8660
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
8640
8661
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
8641
8662
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
8663
|
+
if (err instanceof TransportRefusalError) throw err;
|
|
8642
8664
|
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
8643
8665
|
if (isMethodNotFound) {
|
|
8644
8666
|
return createPiConnection({
|
|
@@ -12130,7 +12152,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
|
|
|
12130
12152
|
proc = await platform2.spawn({
|
|
12131
12153
|
argv: [cmdPath, ...argv],
|
|
12132
12154
|
env: childEnv,
|
|
12133
|
-
cwd
|
|
12155
|
+
cwd,
|
|
12156
|
+
dieWithParent: true
|
|
12134
12157
|
});
|
|
12135
12158
|
} catch (err) {
|
|
12136
12159
|
return { stdout: "", stderr: `spawn failed: ${err.message}`, exitCode: 127 };
|
|
@@ -14070,7 +14093,10 @@ async function sealSecret(platform2, name, value, options) {
|
|
|
14070
14093
|
}
|
|
14071
14094
|
const response = JSON.parse(responseText);
|
|
14072
14095
|
if (response.error) {
|
|
14073
|
-
|
|
14096
|
+
const isStructured = typeof response.error === "object" && response.error !== null && response.error.code;
|
|
14097
|
+
const errno = isStructured ? response.error.code : response.errno ?? "EACCES";
|
|
14098
|
+
const message = isStructured ? response.error.message : response.error;
|
|
14099
|
+
throw Object.assign(new Error(String(message)), { sealErrno: errno });
|
|
14074
14100
|
}
|
|
14075
14101
|
return { placeholder: response.placeholder };
|
|
14076
14102
|
}
|
|
@@ -14082,6 +14108,7 @@ function sealErrorMessage(errno, name) {
|
|
|
14082
14108
|
if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
|
|
14083
14109
|
if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
|
|
14084
14110
|
if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
|
|
14111
|
+
if (errno === "ETIMEDOUT") return "vault did not respond in time";
|
|
14085
14112
|
return `vault error: ${errno}`;
|
|
14086
14113
|
}
|
|
14087
14114
|
|
|
@@ -15709,6 +15736,9 @@ function classifyError(err) {
|
|
|
15709
15736
|
if (err instanceof InputRequiredError) {
|
|
15710
15737
|
return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
|
|
15711
15738
|
}
|
|
15739
|
+
if (err instanceof TransportRefusalError) {
|
|
15740
|
+
return { code: EXIT_CONNECTION, kind: "connection", msg: err.reason };
|
|
15741
|
+
}
|
|
15712
15742
|
if (err instanceof McpTimeoutError2) {
|
|
15713
15743
|
return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
|
|
15714
15744
|
}
|
|
@@ -15765,6 +15795,14 @@ async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config
|
|
|
15765
15795
|
return { provider, credentialSecrets };
|
|
15766
15796
|
}
|
|
15767
15797
|
function classifyConnectionError(err, target, interrupted) {
|
|
15798
|
+
if (err instanceof TransportRefusalError) {
|
|
15799
|
+
const hint = "in seven, reach the host via http://host.slicc.internal:<port>; in tests, enable the transport's hostLoopback";
|
|
15800
|
+
return {
|
|
15801
|
+
code: EXIT_CONNECTION,
|
|
15802
|
+
kind: "connection",
|
|
15803
|
+
msg: `${err.reason} (${hint})`
|
|
15804
|
+
};
|
|
15805
|
+
}
|
|
15768
15806
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
|
|
15769
15807
|
return classifyError(err);
|
|
15770
15808
|
}
|
|
@@ -16059,9 +16097,14 @@ function kernelFs(ctx) {
|
|
|
16059
16097
|
};
|
|
16060
16098
|
}
|
|
16061
16099
|
var groupSpawnSupported;
|
|
16100
|
+
var deathSignalSupported;
|
|
16101
|
+
var PARENT_DEATH_SIGNAL = "SIGKILL";
|
|
16102
|
+
function isInvalid(err) {
|
|
16103
|
+
return err?.code === "EINVAL";
|
|
16104
|
+
}
|
|
16062
16105
|
async function kernelSpawn(ctx, options) {
|
|
16063
16106
|
const tryGroup = groupSpawnSupported !== false;
|
|
16064
|
-
const
|
|
16107
|
+
const base = {
|
|
16065
16108
|
argv: options.argv,
|
|
16066
16109
|
env: options.env,
|
|
16067
16110
|
cwd: options.cwd,
|
|
@@ -16069,7 +16112,20 @@ async function kernelSpawn(ctx, options) {
|
|
|
16069
16112
|
stdout: "pipe",
|
|
16070
16113
|
stderr: "pipe",
|
|
16071
16114
|
...tryGroup ? { pgid: 0 } : {}
|
|
16072
|
-
}
|
|
16115
|
+
};
|
|
16116
|
+
const tryDeath = options.dieWithParent === true && deathSignalSupported !== false;
|
|
16117
|
+
let result;
|
|
16118
|
+
if (tryDeath) {
|
|
16119
|
+
try {
|
|
16120
|
+
result = await ctx.spawn({ ...base, parentDeathSignal: PARENT_DEATH_SIGNAL });
|
|
16121
|
+
} catch (err) {
|
|
16122
|
+
if (!isInvalid(err)) throw err;
|
|
16123
|
+
deathSignalSupported = false;
|
|
16124
|
+
result = await ctx.spawn(base);
|
|
16125
|
+
}
|
|
16126
|
+
} else {
|
|
16127
|
+
result = await ctx.spawn(base);
|
|
16128
|
+
}
|
|
16073
16129
|
const childPid = result.pid;
|
|
16074
16130
|
if (groupSpawnSupported === void 0) {
|
|
16075
16131
|
try {
|
|
@@ -16313,7 +16369,7 @@ async function main(ctx) {
|
|
|
16313
16369
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16314
16370
|
});
|
|
16315
16371
|
const args = ctx.argv.slice(1);
|
|
16316
|
-
return run(platform2, "0.15.
|
|
16372
|
+
return run(platform2, "0.15.2", args);
|
|
16317
16373
|
}
|
|
16318
16374
|
export {
|
|
16319
16375
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
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.88.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",
|