@ai-ecoverse/mcpcat 0.17.8 → 0.17.10
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 -3
- package/dist/host/mcpcat.mjs +64 -38
- package/dist/kernel/mcpcat.mjs +64 -38
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,9 +28,7 @@ mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Slicc
|
|
|
28
28
|
|
|
29
29
|
Every change that alters what mcpcat ships is certified before it reaches `main`. A pull request that touches `src/`, the build, the dependencies or the release workflows gets mcpcat's cert-review on a pack of its head, and again in the merge queue on the exact tree that lands on `main`; a FAIL blocks the merge. Docs-, test- and CI-only changes skip it as "not behaviour". A draft pull request is not reviewed: its `cert-review` is red with `draft: not reviewed, mark it ready for review`, and marking it ready starts the review (converted_to_draft cancels a running one). Mark ready = reviewed, and merges on green; a pull request held for anything stays a draft. For a PR that needs the coordinator's security read, the order is: the coordinator reads the draft, it is marked ready, the cert runs, then the queue. Each merge to `main` is then released to npm as `latest` in one step by semantic-release, with provenance, so `npm install @ai-ecoverse/mcpcat` and the installer always get certified code. The verdict line is a comment on the pull request and the `cert-review` commit status. It reads `certified …`, `cert-ci: FAIL …` when the reviewer or a check in code found a defect (status `failure`), or `cert-ci: INCOMPLETE …` when the review couldn't decide: the review job didn't succeed or was skipped, no verdict could be read, or the artifact or the runner's own `gh` was missing (status `error`). A fork's change to shipped behaviour also reads INCOMPLETE, since it gets no review. INCOMPLETE never certifies; re-run the failed jobs. A run that ends without `record` posting its status (a crashed plan, record or verdict job) still posts one on the head: `error` with its INCOMPLETE line, or `failure` for a FAIL. So the required `cert-review` check never waits forever. The reviewer's verdict can add defects but never clear one found in code: `record` re-reads the suites' evidence (`stage.json`, `suites.json`), uploaded before the reviewer runs and bound by the artifact id and digest the review job output. A suite or security row that failed on the host or a pinned kernel, a changed test that fails on the head, an import in the kernel bundle or an unexplained rebuild difference is a FAIL. Missing suites, kernels or rows are INCOMPLETE. The NEGATIVE runs each changed test on the base's code: the subject's tests, helpers and fixtures with the base's `dist`, `scripts/` and `.github/workflows/`, all from the base sha, so a test of the cert tooling runs against main's tooling. The record names both shas. A changed test that also passes on the base is named in the line as `gaps: no-negative <file>`. Every artifact a later job reads (the tarball, the verdict, the suites' evidence) is downloaded by the artifact id its producer output, after `bind.mjs` checks the artifact's name, run and digest against that producer's outputs. Any other artifact from this attempt of the run under `mcpcat-tarball-`, `mcpcat-npm-tarball-` or `cert-review-` makes the line INCOMPLETE. An earlier attempt's leftovers, created before this attempt started, are never read.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Silence never passes: every gate in the workflows prints a verdict line, and its caller requires that line, not just an exit code. These are `all-green: PASS|FAIL …`, the cert job's `cert: kernel <v> — N kernel tests passed`, shipped.mjs's `behaviour:`/`not behaviour:`, release verify's `verified …`, and the installer tests' `All installer tests passed.`. A gate that exits 0 without its line fails.
|
|
31
|
+
Silence never passes: every gate in the workflows prints a verdict line, and its caller requires that line, not just an exit code. These are `all-green: PASS|FAIL …`, the cert job's `cert: kernel <v> — N kernel tests passed`, shipped.mjs's `behaviour:`/`not behaviour:`, and the installer tests' `All installer tests passed.`. A gate that exits 0 without its line fails.
|
|
34
32
|
|
|
35
33
|
## Quick start
|
|
36
34
|
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -8421,10 +8421,11 @@ var KERNEL_REFUSAL = "nodeTransport refuses ";
|
|
|
8421
8421
|
function firstLine(text) {
|
|
8422
8422
|
return text.trim().split("\n")[0].trim().slice(0, 200);
|
|
8423
8423
|
}
|
|
8424
|
-
async function authReason(res) {
|
|
8424
|
+
async function authReason(res, redact2) {
|
|
8425
8425
|
const type = res.headers.get("content-type") ?? "";
|
|
8426
|
-
if (!type.toLowerCase().startsWith("text/plain")) return
|
|
8427
|
-
|
|
8426
|
+
if (!type.toLowerCase().startsWith("text/plain")) return {};
|
|
8427
|
+
const text = await res.text();
|
|
8428
|
+
return { raw: firstLine(text), reason: firstLine(redact2(text)) || void 0 };
|
|
8428
8429
|
}
|
|
8429
8430
|
function selectSseFrame(body, targetId) {
|
|
8430
8431
|
const text = new TextDecoder().decode(body);
|
|
@@ -8541,7 +8542,8 @@ async function createStatelessConnection(options) {
|
|
|
8541
8542
|
signal: externalSignal,
|
|
8542
8543
|
verbose,
|
|
8543
8544
|
authProvider,
|
|
8544
|
-
transportRefusals
|
|
8545
|
+
transportRefusals,
|
|
8546
|
+
redact: redact2 = (text) => text
|
|
8545
8547
|
} = options;
|
|
8546
8548
|
let nextId2 = 1;
|
|
8547
8549
|
let negotiatedVersion;
|
|
@@ -8560,9 +8562,9 @@ async function createStatelessConnection(options) {
|
|
|
8560
8562
|
}
|
|
8561
8563
|
async function handleErrorStatus(res, method) {
|
|
8562
8564
|
if (res.status === 401 || res.status === 403) {
|
|
8563
|
-
const reason = await authReason(res);
|
|
8564
|
-
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") &&
|
|
8565
|
-
throw new TransportRefusalError(403, reason);
|
|
8565
|
+
const { raw, reason } = await authReason(res, redact2);
|
|
8566
|
+
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && raw?.startsWith(KERNEL_REFUSAL)) {
|
|
8567
|
+
throw new TransportRefusalError(403, reason ?? "");
|
|
8566
8568
|
}
|
|
8567
8569
|
if (authProvider) throw new AuthRetryableError(res, reason);
|
|
8568
8570
|
throw new McpAuthError(res.status, url, reason);
|
|
@@ -8575,7 +8577,7 @@ async function createStatelessConnection(options) {
|
|
|
8575
8577
|
} catch {
|
|
8576
8578
|
}
|
|
8577
8579
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8578
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
8580
|
+
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8579
8581
|
}
|
|
8580
8582
|
async function doFetch(method, params, body) {
|
|
8581
8583
|
const token = await authProvider?.token();
|
|
@@ -9103,7 +9105,8 @@ async function connect(platform2, options) {
|
|
|
9103
9105
|
signal: options.signal,
|
|
9104
9106
|
verbose: options.verbose,
|
|
9105
9107
|
authProvider: options.authProvider,
|
|
9106
|
-
transportRefusals: platform2.kind === "kernel"
|
|
9108
|
+
transportRefusals: platform2.kind === "kernel",
|
|
9109
|
+
redact: options.redact
|
|
9107
9110
|
});
|
|
9108
9111
|
} catch (err) {
|
|
9109
9112
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
@@ -11172,11 +11175,30 @@ function redactWrites(platform2, redact2) {
|
|
|
11172
11175
|
write: (fd, data) => platform2.write(fd, typeof data === "string" ? redact2(data) : data)
|
|
11173
11176
|
});
|
|
11174
11177
|
}
|
|
11178
|
+
function secretMasker(secrets, mask) {
|
|
11179
|
+
const ordered = [...new Set(secrets)].filter((s) => s.length > 0);
|
|
11180
|
+
if (ordered.length === 0) return (text) => text;
|
|
11181
|
+
return (text) => {
|
|
11182
|
+
const hidden = new Uint8Array(text.length);
|
|
11183
|
+
for (const secret of ordered) {
|
|
11184
|
+
for (let at = text.indexOf(secret); at >= 0; at = text.indexOf(secret, at + 1)) {
|
|
11185
|
+
hidden.fill(1, at, at + secret.length);
|
|
11186
|
+
}
|
|
11187
|
+
}
|
|
11188
|
+
let out = "";
|
|
11189
|
+
let start = 0;
|
|
11190
|
+
while (start < text.length) {
|
|
11191
|
+
let end = start;
|
|
11192
|
+
const masked = hidden[start] === 1;
|
|
11193
|
+
while (end < text.length && hidden[end] === 1 === masked) end++;
|
|
11194
|
+
out += masked ? mask : text.slice(start, end);
|
|
11195
|
+
start = end;
|
|
11196
|
+
}
|
|
11197
|
+
return out;
|
|
11198
|
+
};
|
|
11199
|
+
}
|
|
11175
11200
|
function redactSecrets(text, secrets) {
|
|
11176
|
-
|
|
11177
|
-
let result = text;
|
|
11178
|
-
for (const secret of ordered) result = result.replaceAll(secret, "[REDACTED]");
|
|
11179
|
-
return result;
|
|
11201
|
+
return secretMasker(secrets, "[REDACTED]")(text);
|
|
11180
11202
|
}
|
|
11181
11203
|
|
|
11182
11204
|
// src/render/result.ts
|
|
@@ -12153,27 +12175,18 @@ function rpcError2(id, code2, message) {
|
|
|
12153
12175
|
body: JSON.stringify({ jsonrpc: "2.0", id, error: { code: code2, message } })
|
|
12154
12176
|
};
|
|
12155
12177
|
}
|
|
12156
|
-
function scrubValue(value,
|
|
12157
|
-
if (typeof value === "string") return
|
|
12158
|
-
if (Array.isArray(value)) return value.map((v) => scrubValue(v,
|
|
12178
|
+
function scrubValue(value, scrub) {
|
|
12179
|
+
if (typeof value === "string") return scrub(value);
|
|
12180
|
+
if (Array.isArray(value)) return value.map((v) => scrubValue(v, scrub));
|
|
12159
12181
|
if (value && typeof value === "object") {
|
|
12160
12182
|
const out = {};
|
|
12161
12183
|
for (const [k, v] of Object.entries(value)) {
|
|
12162
|
-
out[k] = scrubValue(v,
|
|
12184
|
+
out[k] = scrubValue(v, scrub);
|
|
12163
12185
|
}
|
|
12164
12186
|
return out;
|
|
12165
12187
|
}
|
|
12166
12188
|
return value;
|
|
12167
12189
|
}
|
|
12168
|
-
function scrubString(s, secrets) {
|
|
12169
|
-
let result = s;
|
|
12170
|
-
for (const h of secrets) {
|
|
12171
|
-
if (result.includes(h)) {
|
|
12172
|
-
result = result.replaceAll(h, "***");
|
|
12173
|
-
}
|
|
12174
|
-
}
|
|
12175
|
-
return result;
|
|
12176
|
-
}
|
|
12177
12190
|
function translateCapabilities(info) {
|
|
12178
12191
|
const caps = {};
|
|
12179
12192
|
if (info.capabilities.tools) caps.tools = { listChanged: false };
|
|
@@ -12197,9 +12210,9 @@ function createProxyHandler(options) {
|
|
|
12197
12210
|
let conn = null;
|
|
12198
12211
|
let cachedInfo = null;
|
|
12199
12212
|
let connecting = null;
|
|
12200
|
-
const
|
|
12213
|
+
const scrub = secretMasker(options.hiddenHeaders, "***");
|
|
12201
12214
|
async function doConnect() {
|
|
12202
|
-
const c = await connect(options.platform, options.connectOpts);
|
|
12215
|
+
const c = await connect(options.platform, { ...options.connectOpts, redact: scrub });
|
|
12203
12216
|
cachedInfo = await c.info();
|
|
12204
12217
|
return c;
|
|
12205
12218
|
}
|
|
@@ -12226,10 +12239,10 @@ function createProxyHandler(options) {
|
|
|
12226
12239
|
return getConnection();
|
|
12227
12240
|
}
|
|
12228
12241
|
function scrubOk(id, result) {
|
|
12229
|
-
return rpcOk2(id, scrubValue(result,
|
|
12242
|
+
return rpcOk2(id, scrubValue(result, scrub));
|
|
12230
12243
|
}
|
|
12231
12244
|
function scrubErr(id, code2, message) {
|
|
12232
|
-
return rpcError2(id, code2,
|
|
12245
|
+
return rpcError2(id, code2, scrub(message));
|
|
12233
12246
|
}
|
|
12234
12247
|
function handleInitialize(id, params) {
|
|
12235
12248
|
const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
|
|
@@ -13276,9 +13289,17 @@ async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValue
|
|
|
13276
13289
|
const entry = await config.get(target);
|
|
13277
13290
|
if (!entry) return `unknown target '${target}'`;
|
|
13278
13291
|
if ("url" in entry && entry.url) {
|
|
13279
|
-
const
|
|
13292
|
+
const expanded = expandHeaders(entry.headers, platform2.env, (msg) => {
|
|
13293
|
+
platform2.write(2, `mcpcat serve --proxy: warning: ${msg}
|
|
13294
|
+
`).catch(() => {
|
|
13295
|
+
});
|
|
13296
|
+
});
|
|
13297
|
+
const merged = { ...expanded, ...connectHeaders };
|
|
13280
13298
|
if (entry.headers) {
|
|
13281
|
-
for (const v of Object.values(entry.headers))
|
|
13299
|
+
for (const v of Object.values(entry.headers)) {
|
|
13300
|
+
hiddenValues.add(v);
|
|
13301
|
+
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
13302
|
+
}
|
|
13282
13303
|
}
|
|
13283
13304
|
return { url: entry.url, headers: merged, version };
|
|
13284
13305
|
}
|
|
@@ -13303,6 +13324,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13303
13324
|
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
13304
13325
|
connectHeaders[key] = expanded;
|
|
13305
13326
|
hiddenValues.add(expanded);
|
|
13327
|
+
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
13306
13328
|
}
|
|
13307
13329
|
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
13308
13330
|
let connectOpts;
|
|
@@ -16100,13 +16122,15 @@ async function runVerb(platform2, parsed, conn, onProgress, respondData) {
|
|
|
16100
16122
|
}
|
|
16101
16123
|
}
|
|
16102
16124
|
async function probeServer(_name, config, platform2) {
|
|
16125
|
+
let secrets = [];
|
|
16103
16126
|
try {
|
|
16104
16127
|
const opts = { version: "probe" };
|
|
16105
16128
|
if ("url" in config && config.url) {
|
|
16106
16129
|
opts.url = config.url;
|
|
16107
|
-
if ("headers" in config) {
|
|
16108
|
-
const
|
|
16109
|
-
|
|
16130
|
+
if ("headers" in config && config.headers) {
|
|
16131
|
+
const collected = expandAndCollect(config.headers, platform2.env);
|
|
16132
|
+
secrets = collected.secrets;
|
|
16133
|
+
if (Object.keys(collected.expanded).length > 0) opts.headers = collected.expanded;
|
|
16110
16134
|
}
|
|
16111
16135
|
} else if ("command" in config && config.command) {
|
|
16112
16136
|
opts.command = config.command;
|
|
@@ -16116,6 +16140,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
16116
16140
|
} else {
|
|
16117
16141
|
return void 0;
|
|
16118
16142
|
}
|
|
16143
|
+
opts.redact = (text) => redactSecrets(text, secrets);
|
|
16119
16144
|
const conn = await connect(platform2, opts);
|
|
16120
16145
|
try {
|
|
16121
16146
|
await conn.info();
|
|
@@ -16127,7 +16152,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
16127
16152
|
}
|
|
16128
16153
|
return void 0;
|
|
16129
16154
|
} catch (err) {
|
|
16130
|
-
return err.message;
|
|
16155
|
+
return redactSecrets(err.message, secrets);
|
|
16131
16156
|
}
|
|
16132
16157
|
}
|
|
16133
16158
|
async function dispatchConfigVerb(platform2, verb, args2, version) {
|
|
@@ -16709,7 +16734,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16709
16734
|
signal: sigintController.signal,
|
|
16710
16735
|
verbose,
|
|
16711
16736
|
authProvider,
|
|
16712
|
-
roots
|
|
16737
|
+
roots,
|
|
16738
|
+
redact: redact2
|
|
16713
16739
|
});
|
|
16714
16740
|
activeConn = conn;
|
|
16715
16741
|
} catch (err) {
|
|
@@ -17046,5 +17072,5 @@ try {
|
|
|
17046
17072
|
});
|
|
17047
17073
|
} catch {
|
|
17048
17074
|
}
|
|
17049
|
-
var code = await run(platform, "0.17.
|
|
17075
|
+
var code = await run(platform, "0.17.10", args);
|
|
17050
17076
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -8042,10 +8042,11 @@ var KERNEL_REFUSAL = "nodeTransport refuses ";
|
|
|
8042
8042
|
function firstLine(text) {
|
|
8043
8043
|
return text.trim().split("\n")[0].trim().slice(0, 200);
|
|
8044
8044
|
}
|
|
8045
|
-
async function authReason(res) {
|
|
8045
|
+
async function authReason(res, redact2) {
|
|
8046
8046
|
const type = res.headers.get("content-type") ?? "";
|
|
8047
|
-
if (!type.toLowerCase().startsWith("text/plain")) return
|
|
8048
|
-
|
|
8047
|
+
if (!type.toLowerCase().startsWith("text/plain")) return {};
|
|
8048
|
+
const text = await res.text();
|
|
8049
|
+
return { raw: firstLine(text), reason: firstLine(redact2(text)) || void 0 };
|
|
8049
8050
|
}
|
|
8050
8051
|
function selectSseFrame(body, targetId) {
|
|
8051
8052
|
const text = new TextDecoder().decode(body);
|
|
@@ -8162,7 +8163,8 @@ async function createStatelessConnection(options) {
|
|
|
8162
8163
|
signal: externalSignal,
|
|
8163
8164
|
verbose,
|
|
8164
8165
|
authProvider,
|
|
8165
|
-
transportRefusals
|
|
8166
|
+
transportRefusals,
|
|
8167
|
+
redact: redact2 = (text) => text
|
|
8166
8168
|
} = options;
|
|
8167
8169
|
let nextId2 = 1;
|
|
8168
8170
|
let negotiatedVersion;
|
|
@@ -8181,9 +8183,9 @@ async function createStatelessConnection(options) {
|
|
|
8181
8183
|
}
|
|
8182
8184
|
async function handleErrorStatus(res, method) {
|
|
8183
8185
|
if (res.status === 401 || res.status === 403) {
|
|
8184
|
-
const reason = await authReason(res);
|
|
8185
|
-
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") &&
|
|
8186
|
-
throw new TransportRefusalError(403, reason);
|
|
8186
|
+
const { raw, reason } = await authReason(res, redact2);
|
|
8187
|
+
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && raw?.startsWith(KERNEL_REFUSAL)) {
|
|
8188
|
+
throw new TransportRefusalError(403, reason ?? "");
|
|
8187
8189
|
}
|
|
8188
8190
|
if (authProvider) throw new AuthRetryableError(res, reason);
|
|
8189
8191
|
throw new McpAuthError(res.status, url, reason);
|
|
@@ -8196,7 +8198,7 @@ async function createStatelessConnection(options) {
|
|
|
8196
8198
|
} catch {
|
|
8197
8199
|
}
|
|
8198
8200
|
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8199
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
8201
|
+
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8200
8202
|
}
|
|
8201
8203
|
async function doFetch(method, params, body) {
|
|
8202
8204
|
const token = await authProvider?.token();
|
|
@@ -8724,7 +8726,8 @@ async function connect(platform2, options) {
|
|
|
8724
8726
|
signal: options.signal,
|
|
8725
8727
|
verbose: options.verbose,
|
|
8726
8728
|
authProvider: options.authProvider,
|
|
8727
|
-
transportRefusals: platform2.kind === "kernel"
|
|
8729
|
+
transportRefusals: platform2.kind === "kernel",
|
|
8730
|
+
redact: options.redact
|
|
8728
8731
|
});
|
|
8729
8732
|
} catch (err) {
|
|
8730
8733
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
@@ -10793,11 +10796,30 @@ function redactWrites(platform2, redact2) {
|
|
|
10793
10796
|
write: (fd, data) => platform2.write(fd, typeof data === "string" ? redact2(data) : data)
|
|
10794
10797
|
});
|
|
10795
10798
|
}
|
|
10799
|
+
function secretMasker(secrets, mask) {
|
|
10800
|
+
const ordered = [...new Set(secrets)].filter((s) => s.length > 0);
|
|
10801
|
+
if (ordered.length === 0) return (text) => text;
|
|
10802
|
+
return (text) => {
|
|
10803
|
+
const hidden = new Uint8Array(text.length);
|
|
10804
|
+
for (const secret of ordered) {
|
|
10805
|
+
for (let at = text.indexOf(secret); at >= 0; at = text.indexOf(secret, at + 1)) {
|
|
10806
|
+
hidden.fill(1, at, at + secret.length);
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
let out = "";
|
|
10810
|
+
let start = 0;
|
|
10811
|
+
while (start < text.length) {
|
|
10812
|
+
let end = start;
|
|
10813
|
+
const masked = hidden[start] === 1;
|
|
10814
|
+
while (end < text.length && hidden[end] === 1 === masked) end++;
|
|
10815
|
+
out += masked ? mask : text.slice(start, end);
|
|
10816
|
+
start = end;
|
|
10817
|
+
}
|
|
10818
|
+
return out;
|
|
10819
|
+
};
|
|
10820
|
+
}
|
|
10796
10821
|
function redactSecrets(text, secrets) {
|
|
10797
|
-
|
|
10798
|
-
let result = text;
|
|
10799
|
-
for (const secret of ordered) result = result.replaceAll(secret, "[REDACTED]");
|
|
10800
|
-
return result;
|
|
10822
|
+
return secretMasker(secrets, "[REDACTED]")(text);
|
|
10801
10823
|
}
|
|
10802
10824
|
|
|
10803
10825
|
// src/render/result.ts
|
|
@@ -11774,27 +11796,18 @@ function rpcError2(id, code, message) {
|
|
|
11774
11796
|
body: JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })
|
|
11775
11797
|
};
|
|
11776
11798
|
}
|
|
11777
|
-
function scrubValue(value,
|
|
11778
|
-
if (typeof value === "string") return
|
|
11779
|
-
if (Array.isArray(value)) return value.map((v) => scrubValue(v,
|
|
11799
|
+
function scrubValue(value, scrub) {
|
|
11800
|
+
if (typeof value === "string") return scrub(value);
|
|
11801
|
+
if (Array.isArray(value)) return value.map((v) => scrubValue(v, scrub));
|
|
11780
11802
|
if (value && typeof value === "object") {
|
|
11781
11803
|
const out = {};
|
|
11782
11804
|
for (const [k, v] of Object.entries(value)) {
|
|
11783
|
-
out[k] = scrubValue(v,
|
|
11805
|
+
out[k] = scrubValue(v, scrub);
|
|
11784
11806
|
}
|
|
11785
11807
|
return out;
|
|
11786
11808
|
}
|
|
11787
11809
|
return value;
|
|
11788
11810
|
}
|
|
11789
|
-
function scrubString(s, secrets) {
|
|
11790
|
-
let result = s;
|
|
11791
|
-
for (const h of secrets) {
|
|
11792
|
-
if (result.includes(h)) {
|
|
11793
|
-
result = result.replaceAll(h, "***");
|
|
11794
|
-
}
|
|
11795
|
-
}
|
|
11796
|
-
return result;
|
|
11797
|
-
}
|
|
11798
11811
|
function translateCapabilities(info) {
|
|
11799
11812
|
const caps = {};
|
|
11800
11813
|
if (info.capabilities.tools) caps.tools = { listChanged: false };
|
|
@@ -11818,9 +11831,9 @@ function createProxyHandler(options) {
|
|
|
11818
11831
|
let conn = null;
|
|
11819
11832
|
let cachedInfo = null;
|
|
11820
11833
|
let connecting = null;
|
|
11821
|
-
const
|
|
11834
|
+
const scrub = secretMasker(options.hiddenHeaders, "***");
|
|
11822
11835
|
async function doConnect() {
|
|
11823
|
-
const c = await connect(options.platform, options.connectOpts);
|
|
11836
|
+
const c = await connect(options.platform, { ...options.connectOpts, redact: scrub });
|
|
11824
11837
|
cachedInfo = await c.info();
|
|
11825
11838
|
return c;
|
|
11826
11839
|
}
|
|
@@ -11847,10 +11860,10 @@ function createProxyHandler(options) {
|
|
|
11847
11860
|
return getConnection();
|
|
11848
11861
|
}
|
|
11849
11862
|
function scrubOk(id, result) {
|
|
11850
|
-
return rpcOk2(id, scrubValue(result,
|
|
11863
|
+
return rpcOk2(id, scrubValue(result, scrub));
|
|
11851
11864
|
}
|
|
11852
11865
|
function scrubErr(id, code, message) {
|
|
11853
|
-
return rpcError2(id, code,
|
|
11866
|
+
return rpcError2(id, code, scrub(message));
|
|
11854
11867
|
}
|
|
11855
11868
|
function handleInitialize(id, params) {
|
|
11856
11869
|
const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
|
|
@@ -12897,9 +12910,17 @@ async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValue
|
|
|
12897
12910
|
const entry = await config.get(target);
|
|
12898
12911
|
if (!entry) return `unknown target '${target}'`;
|
|
12899
12912
|
if ("url" in entry && entry.url) {
|
|
12900
|
-
const
|
|
12913
|
+
const expanded = expandHeaders(entry.headers, platform2.env, (msg) => {
|
|
12914
|
+
platform2.write(2, `mcpcat serve --proxy: warning: ${msg}
|
|
12915
|
+
`).catch(() => {
|
|
12916
|
+
});
|
|
12917
|
+
});
|
|
12918
|
+
const merged = { ...expanded, ...connectHeaders };
|
|
12901
12919
|
if (entry.headers) {
|
|
12902
|
-
for (const v of Object.values(entry.headers))
|
|
12920
|
+
for (const v of Object.values(entry.headers)) {
|
|
12921
|
+
hiddenValues.add(v);
|
|
12922
|
+
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
12923
|
+
}
|
|
12903
12924
|
}
|
|
12904
12925
|
return { url: entry.url, headers: merged, version };
|
|
12905
12926
|
}
|
|
@@ -12924,6 +12945,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
12924
12945
|
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
12925
12946
|
connectHeaders[key] = expanded;
|
|
12926
12947
|
hiddenValues.add(expanded);
|
|
12948
|
+
for (const p of placeholderValues(v, platform2.env)) hiddenValues.add(p);
|
|
12927
12949
|
}
|
|
12928
12950
|
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
12929
12951
|
let connectOpts;
|
|
@@ -15721,13 +15743,15 @@ async function runVerb(platform2, parsed, conn, onProgress, respondData) {
|
|
|
15721
15743
|
}
|
|
15722
15744
|
}
|
|
15723
15745
|
async function probeServer(_name, config, platform2) {
|
|
15746
|
+
let secrets = [];
|
|
15724
15747
|
try {
|
|
15725
15748
|
const opts = { version: "probe" };
|
|
15726
15749
|
if ("url" in config && config.url) {
|
|
15727
15750
|
opts.url = config.url;
|
|
15728
|
-
if ("headers" in config) {
|
|
15729
|
-
const
|
|
15730
|
-
|
|
15751
|
+
if ("headers" in config && config.headers) {
|
|
15752
|
+
const collected = expandAndCollect(config.headers, platform2.env);
|
|
15753
|
+
secrets = collected.secrets;
|
|
15754
|
+
if (Object.keys(collected.expanded).length > 0) opts.headers = collected.expanded;
|
|
15731
15755
|
}
|
|
15732
15756
|
} else if ("command" in config && config.command) {
|
|
15733
15757
|
opts.command = config.command;
|
|
@@ -15737,6 +15761,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
15737
15761
|
} else {
|
|
15738
15762
|
return void 0;
|
|
15739
15763
|
}
|
|
15764
|
+
opts.redact = (text) => redactSecrets(text, secrets);
|
|
15740
15765
|
const conn = await connect(platform2, opts);
|
|
15741
15766
|
try {
|
|
15742
15767
|
await conn.info();
|
|
@@ -15748,7 +15773,7 @@ async function probeServer(_name, config, platform2) {
|
|
|
15748
15773
|
}
|
|
15749
15774
|
return void 0;
|
|
15750
15775
|
} catch (err) {
|
|
15751
|
-
return err.message;
|
|
15776
|
+
return redactSecrets(err.message, secrets);
|
|
15752
15777
|
}
|
|
15753
15778
|
}
|
|
15754
15779
|
async function dispatchConfigVerb(platform2, verb, args, version) {
|
|
@@ -16330,7 +16355,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16330
16355
|
signal: sigintController.signal,
|
|
16331
16356
|
verbose,
|
|
16332
16357
|
authProvider,
|
|
16333
|
-
roots
|
|
16358
|
+
roots,
|
|
16359
|
+
redact: redact2
|
|
16334
16360
|
});
|
|
16335
16361
|
activeConn = conn;
|
|
16336
16362
|
} catch (err) {
|
|
@@ -16768,7 +16794,7 @@ async function main(ctx) {
|
|
|
16768
16794
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16769
16795
|
});
|
|
16770
16796
|
const args = ctx.argv.slice(1);
|
|
16771
|
-
return run(platform2, "0.17.
|
|
16797
|
+
return run(platform2, "0.17.10", args);
|
|
16772
16798
|
}
|
|
16773
16799
|
export {
|
|
16774
16800
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.10",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@ai-ecoverse/slicc-bios": "1.
|
|
64
|
+
"@ai-ecoverse/slicc-bios": "1.38.0",
|
|
65
65
|
"@ai-ecoverse/slicc-kernel": "1.142.1",
|
|
66
|
-
"@ai-ecoverse/slicc-shared-web": "1.29.
|
|
66
|
+
"@ai-ecoverse/slicc-shared-web": "1.29.13",
|
|
67
67
|
"@biomejs/biome": "2.5.15",
|
|
68
68
|
"@earendil-works/pi-codemode": "1.2.0",
|
|
69
69
|
"@earendil-works/pi-mcp": "1.2.0",
|