@ai-ecoverse/mcpcat 0.31.16 → 0.31.18
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/dist/host/mcpcat.mjs +260 -59
- package/dist/kernel/mcpcat.mjs +263 -60
- package/package.json +5 -5
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -6558,6 +6558,12 @@ function agentIgnoresConfig(platform2, path) {
|
|
|
6558
6558
|
function projectConfigPath(platform2) {
|
|
6559
6559
|
return `${platform2.cwd}/.pi/mcp.json`;
|
|
6560
6560
|
}
|
|
6561
|
+
function cacheDir(platform2) {
|
|
6562
|
+
if (platform2.env.XDG_CACHE_HOME) return `${platform2.env.XDG_CACHE_HOME}/mcpcat`;
|
|
6563
|
+
const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
|
|
6564
|
+
if (platform2.env.LOCALAPPDATA) return `${platform2.env.LOCALAPPDATA}\\mcpcat\\cache`;
|
|
6565
|
+
return `${home}/Library/Caches/mcpcat`;
|
|
6566
|
+
}
|
|
6561
6567
|
function stateDir(platform2) {
|
|
6562
6568
|
if (platform2.env.XDG_STATE_HOME) return `${platform2.env.XDG_STATE_HOME}/mcpcat`;
|
|
6563
6569
|
const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
|
|
@@ -14287,10 +14293,30 @@ var HOLDER_RESTART_MAX_MS = 3e4;
|
|
|
14287
14293
|
var HOLDER_STABLE_MS = 3e4;
|
|
14288
14294
|
var HOLDER_STOP_GRACE_MS = 2e3;
|
|
14289
14295
|
var HOLDER_READY_MS = 2e4;
|
|
14296
|
+
var FALLBACK_UNVERIFIED = "webmcp: other-extension fallback could not verify the reloaded document; tools are unavailable";
|
|
14290
14297
|
var LABEL = "mcpcat serve --webmcp";
|
|
14291
|
-
var
|
|
14298
|
+
var TERMINAL_REFUSALS = /* @__PURE__ */ new Set([
|
|
14299
|
+
"slicc: refused: target is on seven's origin",
|
|
14300
|
+
"slicc: refused: seven\u2019s own pages and the vault are out of reach of browser automation",
|
|
14301
|
+
"slicc: refused: breakpoints could run in seven\u2019s pages or the vault, so the guard doesn\u2019t offer the Debugger domains",
|
|
14302
|
+
"slicc: refused: update the browser guard and client",
|
|
14303
|
+
"slicc: refused: WebMCP installation could not verify the document; update the browser guard and client",
|
|
14304
|
+
"slicc: refused: WebMCP installation could not verify a usable guard and document; update the browser guard and client",
|
|
14305
|
+
"slicc: refused: WebMCP instrumentation is unavailable; update the browser guard and client",
|
|
14306
|
+
"webmcp: no tools are available through the other-extension fallback; page startup or parse-time checks may have happened before installation"
|
|
14307
|
+
]);
|
|
14308
|
+
var FALLBACK_REFUSALS = /* @__PURE__ */ new Set([
|
|
14309
|
+
"other-extension fallback could not verify the reloaded document; tools are unavailable",
|
|
14310
|
+
"other-extension fallback could not verify the document"
|
|
14311
|
+
]);
|
|
14312
|
+
function holderReason(line) {
|
|
14313
|
+
return line.trim().replace(/^playwright-cli webmcp-polyfill: /, "").replace(/^webmcp-polyfill: /, "").replace(/^[A-Z][A-Za-z]*\.[a-z][A-Za-z]*: /, "");
|
|
14314
|
+
}
|
|
14315
|
+
function isFallbackUnavailable(line) {
|
|
14316
|
+
return FALLBACK_REFUSALS.has(holderReason(line));
|
|
14317
|
+
}
|
|
14292
14318
|
function isGuardRefusal(line) {
|
|
14293
|
-
return
|
|
14319
|
+
return TERMINAL_REFUSALS.has(holderReason(line)) || isFallbackUnavailable(line);
|
|
14294
14320
|
}
|
|
14295
14321
|
function compareVersions(a, b) {
|
|
14296
14322
|
const pa = a.split(".").map(Number);
|
|
@@ -14418,6 +14444,9 @@ function startHolder(platform2, options) {
|
|
|
14418
14444
|
return;
|
|
14419
14445
|
}
|
|
14420
14446
|
proc = child;
|
|
14447
|
+
state.fallbackUnavailable = false;
|
|
14448
|
+
if (Number.isSafeInteger(child.pid) && child.pid > 0)
|
|
14449
|
+
report(`holder-pid ${child.pid} tab ${options.tab}`);
|
|
14421
14450
|
writer = child.stdin.getWriter();
|
|
14422
14451
|
let lastError;
|
|
14423
14452
|
const out = drain(child.stdout, onEvent);
|
|
@@ -14425,7 +14454,8 @@ function startHolder(platform2, options) {
|
|
|
14425
14454
|
const text = line.trim();
|
|
14426
14455
|
if (!text) return;
|
|
14427
14456
|
lastError = text;
|
|
14428
|
-
if (
|
|
14457
|
+
if (isFallbackUnavailable(text)) state.fallbackUnavailable = true;
|
|
14458
|
+
if (isGuardRefusal(text)) refusal ??= text;
|
|
14429
14459
|
else relay(text);
|
|
14430
14460
|
});
|
|
14431
14461
|
exited = Promise.all([out, err, child.wait()]).then(([, , result]) => {
|
|
@@ -14749,6 +14779,12 @@ function startDialogHolder(platform2, options) {
|
|
|
14749
14779
|
|
|
14750
14780
|
// src/serve/webmcp.ts
|
|
14751
14781
|
var UNAVAILABLE_RE = /WebMCP is unavailable on this page/;
|
|
14782
|
+
function emptyFallbackOrError(error, holderHint) {
|
|
14783
|
+
const hint = error.trim();
|
|
14784
|
+
if (hint === "webmcp: unverified isolated context" && holderHint === FALLBACK_UNVERIFIED)
|
|
14785
|
+
return { tools: [], hint: holderHint };
|
|
14786
|
+
return hint === "webmcp: no tools are available through the other-extension fallback; page startup or parse-time checks may have happened before installation" ? { tools: [], hint } : { tools: [], error };
|
|
14787
|
+
}
|
|
14752
14788
|
function buildBaseArgs(options) {
|
|
14753
14789
|
const args2 = [];
|
|
14754
14790
|
if (options.cdp) {
|
|
@@ -15317,7 +15353,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
15317
15353
|
async function getRpcTools() {
|
|
15318
15354
|
const { tools, error, notFound, unavailable } = await listRaw();
|
|
15319
15355
|
if (notFound) return null;
|
|
15320
|
-
if (error) return
|
|
15356
|
+
if (error) return emptyFallbackOrError(error, options.emptyHint?.(false));
|
|
15321
15357
|
const late = options.late?.() ?? false;
|
|
15322
15358
|
const rpcTools = [];
|
|
15323
15359
|
const hidden = {};
|
|
@@ -16479,6 +16515,7 @@ function emptyToolsHint(options, polyfill, unavailable) {
|
|
|
16479
16515
|
return `the page shows no WebMCP tools, and the WebMCP polyfill needs playwright-cli ${POLYFILL_MIN_VERSION} or newer (found ${polyfill.oldVersion})`;
|
|
16480
16516
|
}
|
|
16481
16517
|
const status = polyfill.holder?.status();
|
|
16518
|
+
if (status?.refused && status.fallbackUnavailable) return FALLBACK_UNVERIFIED;
|
|
16482
16519
|
if (isLate(polyfill.holder)) {
|
|
16483
16520
|
return "the page may check for WebMCP only when it loads, before the polyfill was injected; reload the tab, or restart with --webmcp-reload (it reloads the tab)";
|
|
16484
16521
|
}
|
|
@@ -17410,6 +17447,142 @@ function parseLauncher(text) {
|
|
|
17410
17447
|
return m ? { node: m[1], entry: m[2] } : void 0;
|
|
17411
17448
|
}
|
|
17412
17449
|
|
|
17450
|
+
// src/auth/callback-port.ts
|
|
17451
|
+
function hasFixedCallback(oauth) {
|
|
17452
|
+
if (oauth?.callbackPort !== void 0 && oauth.callbackPort > 0) return true;
|
|
17453
|
+
if (!oauth?.callbackUrl) return false;
|
|
17454
|
+
try {
|
|
17455
|
+
const port = Number(new URL(oauth.callbackUrl).port);
|
|
17456
|
+
return port > 0;
|
|
17457
|
+
} catch {
|
|
17458
|
+
return false;
|
|
17459
|
+
}
|
|
17460
|
+
}
|
|
17461
|
+
function needsInjectedCallbackPort(options) {
|
|
17462
|
+
return options.namedEntry && !hasFixedCallback(options.oauth);
|
|
17463
|
+
}
|
|
17464
|
+
async function withInjectedCallbackPortIfNeeded(platform2, name, options, fn) {
|
|
17465
|
+
if (!needsInjectedCallbackPort(options)) return fn(void 0);
|
|
17466
|
+
const port = await allocateCallbackPort();
|
|
17467
|
+
return withTempCallbackPortConfig(platform2, name, port, fn);
|
|
17468
|
+
}
|
|
17469
|
+
async function allocateCallbackPort() {
|
|
17470
|
+
const callback = await OAuthCallbackServer.listen({
|
|
17471
|
+
host: "127.0.0.1",
|
|
17472
|
+
port: 0,
|
|
17473
|
+
path: "/callback"
|
|
17474
|
+
});
|
|
17475
|
+
try {
|
|
17476
|
+
return Number(new URL(callback.redirectUrl).port);
|
|
17477
|
+
} finally {
|
|
17478
|
+
await callback.close();
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
function isRecord7(value) {
|
|
17482
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17483
|
+
}
|
|
17484
|
+
function hostEnv(platform2) {
|
|
17485
|
+
const out = {};
|
|
17486
|
+
for (const [key, value] of Object.entries(platform2.env)) {
|
|
17487
|
+
if (value !== void 0) out[key] = value;
|
|
17488
|
+
}
|
|
17489
|
+
return out;
|
|
17490
|
+
}
|
|
17491
|
+
async function makeTempDir(platform2) {
|
|
17492
|
+
const base = `${cacheDir(platform2)}/mcp-cb`;
|
|
17493
|
+
await platform2.fs.mkdir(base, { mode: 448 });
|
|
17494
|
+
for (let i = 0; i < 32; i++) {
|
|
17495
|
+
const dir = `${base}/${Date.now().toString(36)}-${i.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
17496
|
+
try {
|
|
17497
|
+
await platform2.fs.mkdirExclusive(dir);
|
|
17498
|
+
return dir;
|
|
17499
|
+
} catch {
|
|
17500
|
+
}
|
|
17501
|
+
}
|
|
17502
|
+
throw new Error("unable to create temporary mcp config directory");
|
|
17503
|
+
}
|
|
17504
|
+
async function removeTempDir(platform2, dir) {
|
|
17505
|
+
try {
|
|
17506
|
+
if (platform2.fs.unlink) await platform2.fs.unlink(`${dir}/mcp.json`);
|
|
17507
|
+
} catch {
|
|
17508
|
+
}
|
|
17509
|
+
try {
|
|
17510
|
+
await platform2.fs.rmdir(dir);
|
|
17511
|
+
} catch {
|
|
17512
|
+
}
|
|
17513
|
+
}
|
|
17514
|
+
async function withTempCallbackPortConfig(platform2, name, port, fn) {
|
|
17515
|
+
const path = userConfigPath(platform2);
|
|
17516
|
+
let raw;
|
|
17517
|
+
try {
|
|
17518
|
+
raw = new TextDecoder().decode(await platform2.fs.readFile(path));
|
|
17519
|
+
} catch {
|
|
17520
|
+
return fn(void 0);
|
|
17521
|
+
}
|
|
17522
|
+
let parsed;
|
|
17523
|
+
try {
|
|
17524
|
+
parsed = JSON.parse(raw);
|
|
17525
|
+
} catch {
|
|
17526
|
+
return fn(void 0);
|
|
17527
|
+
}
|
|
17528
|
+
if (!isRecord7(parsed) || !isRecord7(parsed.mcpServers) || !isRecord7(parsed.mcpServers[name])) {
|
|
17529
|
+
return fn(void 0);
|
|
17530
|
+
}
|
|
17531
|
+
const entry = { ...parsed.mcpServers[name] };
|
|
17532
|
+
const prevOauth = isRecord7(entry.oauth) ? { ...entry.oauth } : {};
|
|
17533
|
+
const oauth = { ...prevOauth, callbackPort: port };
|
|
17534
|
+
if (typeof oauth.callbackUrl === "string") {
|
|
17535
|
+
try {
|
|
17536
|
+
const url = new URL(oauth.callbackUrl);
|
|
17537
|
+
if (!(Number(url.port) > 0)) {
|
|
17538
|
+
url.port = String(port);
|
|
17539
|
+
oauth.callbackUrl = url.href;
|
|
17540
|
+
}
|
|
17541
|
+
} catch {
|
|
17542
|
+
}
|
|
17543
|
+
}
|
|
17544
|
+
entry.oauth = oauth;
|
|
17545
|
+
const tempServers = { ...parsed.mcpServers, [name]: entry };
|
|
17546
|
+
const merged = { ...parsed, mcpServers: tempServers };
|
|
17547
|
+
const tempDir = await makeTempDir(platform2);
|
|
17548
|
+
const tempPath = `${tempDir}/mcp.json`;
|
|
17549
|
+
await platform2.fs.writeFile(tempPath, `${JSON.stringify(merged, null, 2)}
|
|
17550
|
+
`, { mode: 384 });
|
|
17551
|
+
const env = { ...hostEnv(platform2), PI_MCP_CONFIG: tempPath };
|
|
17552
|
+
let cleaned = false;
|
|
17553
|
+
const cleanup = async () => {
|
|
17554
|
+
if (cleaned) return;
|
|
17555
|
+
cleaned = true;
|
|
17556
|
+
await removeTempDir(platform2, tempDir);
|
|
17557
|
+
};
|
|
17558
|
+
const exitFor = (signal) => signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129;
|
|
17559
|
+
let signalCode;
|
|
17560
|
+
let wake;
|
|
17561
|
+
const stopped = new Promise((resolve) => {
|
|
17562
|
+
wake = resolve;
|
|
17563
|
+
});
|
|
17564
|
+
if (platform2.onSignal) {
|
|
17565
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
17566
|
+
await platform2.onSignal(signal, () => {
|
|
17567
|
+
void cleanup().then(() => {
|
|
17568
|
+
signalCode = exitFor(signal);
|
|
17569
|
+
wake?.();
|
|
17570
|
+
});
|
|
17571
|
+
});
|
|
17572
|
+
}
|
|
17573
|
+
}
|
|
17574
|
+
try {
|
|
17575
|
+
const result = await Promise.race([
|
|
17576
|
+
fn(env).then((value) => ({ ok: true, value })),
|
|
17577
|
+
stopped.then(() => ({ ok: false }))
|
|
17578
|
+
]);
|
|
17579
|
+
if (!result.ok) platform2.exit(signalCode ?? 130);
|
|
17580
|
+
return result.value;
|
|
17581
|
+
} finally {
|
|
17582
|
+
await cleanup();
|
|
17583
|
+
}
|
|
17584
|
+
}
|
|
17585
|
+
|
|
17413
17586
|
// src/auth/oauth.ts
|
|
17414
17587
|
var CALLBACK_HOST2 = "127.0.0.1";
|
|
17415
17588
|
var CALLBACK_PATH2 = "/callback";
|
|
@@ -17425,14 +17598,23 @@ function timedFetch2(fetchImpl) {
|
|
|
17425
17598
|
});
|
|
17426
17599
|
};
|
|
17427
17600
|
}
|
|
17601
|
+
function nonzeroPort(value) {
|
|
17602
|
+
return value !== void 0 && value > 0 ? value : void 0;
|
|
17603
|
+
}
|
|
17604
|
+
function preferredPortFromRegistration(configured, registered) {
|
|
17605
|
+
if (configured !== void 0) return configured;
|
|
17606
|
+
if (!registered[0]) return void 0;
|
|
17607
|
+
return nonzeroPort(Number(new URL(registered[0]).port));
|
|
17608
|
+
}
|
|
17428
17609
|
function callbackSettings(oauth) {
|
|
17429
17610
|
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
17430
17611
|
const url = new URL(raw);
|
|
17431
17612
|
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
17432
|
-
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
17613
|
+
const port = nonzeroPort(url.port ? Number(url.port) : oauth?.callbackPort);
|
|
17433
17614
|
let fixedRedirectUrl;
|
|
17434
|
-
if (url.port)
|
|
17435
|
-
|
|
17615
|
+
if (nonzeroPort(url.port ? Number(url.port) : void 0) !== void 0 && oauth?.callbackUrl) {
|
|
17616
|
+
fixedRedirectUrl = oauth.callbackUrl;
|
|
17617
|
+
} else if (port !== void 0) {
|
|
17436
17618
|
url.port = String(port);
|
|
17437
17619
|
fixedRedirectUrl = url.href;
|
|
17438
17620
|
}
|
|
@@ -17444,6 +17626,24 @@ function callbackSettings(oauth) {
|
|
|
17444
17626
|
fixedRedirectUrl
|
|
17445
17627
|
};
|
|
17446
17628
|
}
|
|
17629
|
+
async function listenCallback(cbSettings, preferredPort) {
|
|
17630
|
+
try {
|
|
17631
|
+
return await OAuthCallbackServer.listen({
|
|
17632
|
+
host: cbSettings.host,
|
|
17633
|
+
redirectHost: cbSettings.redirectHost,
|
|
17634
|
+
port: preferredPort ?? 0,
|
|
17635
|
+
path: cbSettings.path
|
|
17636
|
+
});
|
|
17637
|
+
} catch (err) {
|
|
17638
|
+
if (preferredPort === void 0 || cbSettings.fixedRedirectUrl) throw err;
|
|
17639
|
+
return OAuthCallbackServer.listen({
|
|
17640
|
+
host: cbSettings.host,
|
|
17641
|
+
redirectHost: cbSettings.redirectHost,
|
|
17642
|
+
port: 0,
|
|
17643
|
+
path: cbSettings.path
|
|
17644
|
+
});
|
|
17645
|
+
}
|
|
17646
|
+
}
|
|
17447
17647
|
function cimdForServer(metadata, redirectUrl) {
|
|
17448
17648
|
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
17449
17649
|
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
@@ -17548,24 +17748,17 @@ async function signIn(options) {
|
|
|
17548
17748
|
const stored = await store.load();
|
|
17549
17749
|
const cbSettings = callbackSettings(oauth);
|
|
17550
17750
|
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
17551
|
-
const preferredPort = cbSettings.port
|
|
17552
|
-
const callback = await
|
|
17553
|
-
host: cbSettings.host,
|
|
17554
|
-
redirectHost: cbSettings.redirectHost,
|
|
17555
|
-
port: preferredPort ?? 0,
|
|
17556
|
-
path: cbSettings.path
|
|
17557
|
-
});
|
|
17751
|
+
const preferredPort = preferredPortFromRegistration(cbSettings.port, registered);
|
|
17752
|
+
const callback = await listenCallback(cbSettings, preferredPort);
|
|
17558
17753
|
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
17559
17754
|
try {
|
|
17560
17755
|
if (stored) {
|
|
17561
17756
|
const next = { ...stored };
|
|
17562
17757
|
delete next.oauthState;
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
if (!
|
|
17758
|
+
delete next.tokens;
|
|
17759
|
+
delete next.tokensExpireAt;
|
|
17760
|
+
if (!oauth?.clientId) {
|
|
17566
17761
|
delete next.clientInformation;
|
|
17567
|
-
delete next.tokens;
|
|
17568
|
-
delete next.tokensExpireAt;
|
|
17569
17762
|
}
|
|
17570
17763
|
await store.save(next);
|
|
17571
17764
|
}
|
|
@@ -17648,13 +17841,14 @@ async function resolveLoginTarget(platform2, target) {
|
|
|
17648
17841
|
return {
|
|
17649
17842
|
name: target,
|
|
17650
17843
|
serverUrl: httpConfig.url,
|
|
17651
|
-
oauth: httpConfig.oauth
|
|
17844
|
+
oauth: httpConfig.oauth,
|
|
17845
|
+
namedEntry: true
|
|
17652
17846
|
};
|
|
17653
17847
|
}
|
|
17654
17848
|
const resolved = resolveServerName(target);
|
|
17655
17849
|
if (resolved.isUrl) {
|
|
17656
17850
|
const url = withScheme(target);
|
|
17657
|
-
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
17851
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0, namedEntry: false };
|
|
17658
17852
|
}
|
|
17659
17853
|
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
17660
17854
|
}
|
|
@@ -17685,24 +17879,31 @@ async function jerkLogin(platform2, target, resolved, args2) {
|
|
|
17685
17879
|
if (args2.includes("--no-browser")) {
|
|
17686
17880
|
argv.push("--no-browser");
|
|
17687
17881
|
}
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
|
|
17882
|
+
return withInjectedCallbackPortIfNeeded(
|
|
17883
|
+
platform2,
|
|
17884
|
+
resolved.name,
|
|
17885
|
+
{ namedEntry: resolved.namedEntry, oauth: resolved.oauth },
|
|
17886
|
+
async (env) => {
|
|
17887
|
+
const proc = await platform2.spawn({ argv, env });
|
|
17888
|
+
proc.stdin.close().catch(() => {
|
|
17889
|
+
});
|
|
17890
|
+
const [, errText, result] = await Promise.all([
|
|
17891
|
+
readStream2(proc.stdout),
|
|
17892
|
+
readStream2(proc.stderr),
|
|
17893
|
+
proc.wait()
|
|
17894
|
+
]);
|
|
17895
|
+
if (result.status === 0) {
|
|
17896
|
+
await platform2.write(1, `Signed in to ${target}
|
|
17698
17897
|
`);
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
17898
|
+
return 0;
|
|
17899
|
+
}
|
|
17900
|
+
if (errText.trim()) {
|
|
17901
|
+
await platform2.write(2, sanitizeTerminal(errText));
|
|
17902
|
+
}
|
|
17903
|
+
if (result.status === JERK_EXIT_SIGN_IN) return 4;
|
|
17904
|
+
return result.status;
|
|
17905
|
+
}
|
|
17906
|
+
);
|
|
17706
17907
|
}
|
|
17707
17908
|
async function jerkLogout(platform2, target, resolved, source) {
|
|
17708
17909
|
const id = jerkId(resolved.name, resolved.serverUrl);
|
|
@@ -17879,11 +18080,11 @@ function concatUint8Arrays(arrays) {
|
|
|
17879
18080
|
}
|
|
17880
18081
|
|
|
17881
18082
|
// src/config/import.ts
|
|
17882
|
-
function
|
|
18083
|
+
function isRecord8(value) {
|
|
17883
18084
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17884
18085
|
}
|
|
17885
18086
|
function isStringRecord2(value) {
|
|
17886
|
-
return
|
|
18087
|
+
return isRecord8(value) && Object.values(value).every((v) => typeof v === "string");
|
|
17887
18088
|
}
|
|
17888
18089
|
function tryParse(text) {
|
|
17889
18090
|
try {
|
|
@@ -17913,17 +18114,17 @@ function importClaude(text, names) {
|
|
|
17913
18114
|
result.errors.push(error);
|
|
17914
18115
|
return result;
|
|
17915
18116
|
}
|
|
17916
|
-
if (!
|
|
18117
|
+
if (!isRecord8(parsed)) {
|
|
17917
18118
|
result.errors.push("expected an object");
|
|
17918
18119
|
return result;
|
|
17919
18120
|
}
|
|
17920
18121
|
const mcpServers = parsed.mcpServers;
|
|
17921
|
-
if (!
|
|
18122
|
+
if (!isRecord8(mcpServers)) {
|
|
17922
18123
|
result.errors.push('no "mcpServers" object');
|
|
17923
18124
|
return result;
|
|
17924
18125
|
}
|
|
17925
18126
|
for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
|
|
17926
|
-
if (!
|
|
18127
|
+
if (!isRecord8(entry)) {
|
|
17927
18128
|
result.errors.push(`${name}: not an object`);
|
|
17928
18129
|
continue;
|
|
17929
18130
|
}
|
|
@@ -17938,17 +18139,17 @@ function importVSCode(text, names) {
|
|
|
17938
18139
|
result.errors.push(error);
|
|
17939
18140
|
return result;
|
|
17940
18141
|
}
|
|
17941
|
-
if (!
|
|
18142
|
+
if (!isRecord8(parsed)) {
|
|
17942
18143
|
result.errors.push("expected an object");
|
|
17943
18144
|
return result;
|
|
17944
18145
|
}
|
|
17945
18146
|
const servers = parsed.servers ?? parsed.mcpServers;
|
|
17946
|
-
if (!
|
|
18147
|
+
if (!isRecord8(servers)) {
|
|
17947
18148
|
result.errors.push('no "servers" or "mcpServers" object');
|
|
17948
18149
|
return result;
|
|
17949
18150
|
}
|
|
17950
18151
|
for (const [name, entry] of filterNames(Object.entries(servers), names)) {
|
|
17951
|
-
if (!
|
|
18152
|
+
if (!isRecord8(entry)) {
|
|
17952
18153
|
result.errors.push(`${name}: not an object`);
|
|
17953
18154
|
continue;
|
|
17954
18155
|
}
|
|
@@ -17982,17 +18183,17 @@ function importSixServers(text, names) {
|
|
|
17982
18183
|
result.errors.push(error);
|
|
17983
18184
|
return result;
|
|
17984
18185
|
}
|
|
17985
|
-
if (!
|
|
18186
|
+
if (!isRecord8(parsed)) {
|
|
17986
18187
|
result.errors.push("expected an object");
|
|
17987
18188
|
return result;
|
|
17988
18189
|
}
|
|
17989
18190
|
const servers = parsed.servers;
|
|
17990
|
-
if (!
|
|
18191
|
+
if (!isRecord8(servers)) {
|
|
17991
18192
|
result.errors.push('no "servers" object');
|
|
17992
18193
|
return result;
|
|
17993
18194
|
}
|
|
17994
18195
|
for (const [name, entry] of filterNames(Object.entries(servers), names)) {
|
|
17995
|
-
if (!
|
|
18196
|
+
if (!isRecord8(entry)) {
|
|
17996
18197
|
result.errors.push(`${name}: not an object`);
|
|
17997
18198
|
continue;
|
|
17998
18199
|
}
|
|
@@ -18000,7 +18201,7 @@ function importSixServers(text, names) {
|
|
|
18000
18201
|
if (typeof entry.url === "string") config.url = entry.url;
|
|
18001
18202
|
if (isStringRecord2(entry.headers)) config.headers = entry.headers;
|
|
18002
18203
|
if (typeof entry.exposure === "string") config.exposure = entry.exposure;
|
|
18003
|
-
if (
|
|
18204
|
+
if (isRecord8(entry.toolExposure)) config.toolExposure = entry.toolExposure;
|
|
18004
18205
|
result.servers.push({ name, config });
|
|
18005
18206
|
}
|
|
18006
18207
|
return result;
|
|
@@ -18017,7 +18218,7 @@ function importCodexToml(text, names) {
|
|
|
18017
18218
|
if (fields.command) config.command = fields.command;
|
|
18018
18219
|
if (fields.args) config.args = fields.args;
|
|
18019
18220
|
if (fields.url) config.url = fields.url;
|
|
18020
|
-
if (fields.env &&
|
|
18221
|
+
if (fields.env && isRecord8(fields.env)) config.env = fields.env;
|
|
18021
18222
|
result.servers.push({ name, config });
|
|
18022
18223
|
}
|
|
18023
18224
|
return result;
|
|
@@ -18069,7 +18270,7 @@ function convertOpenCodeLocal(entry) {
|
|
|
18069
18270
|
} else if (typeof entry.command === "string") {
|
|
18070
18271
|
config.command = entry.command;
|
|
18071
18272
|
}
|
|
18072
|
-
if (
|
|
18273
|
+
if (isRecord8(entry.environment)) {
|
|
18073
18274
|
const env = {};
|
|
18074
18275
|
for (const [k, v] of Object.entries(entry.environment)) {
|
|
18075
18276
|
if (typeof v === "string")
|
|
@@ -18092,17 +18293,17 @@ function importOpenCode(text, names) {
|
|
|
18092
18293
|
result.errors.push(error);
|
|
18093
18294
|
return result;
|
|
18094
18295
|
}
|
|
18095
|
-
if (!
|
|
18296
|
+
if (!isRecord8(parsed)) {
|
|
18096
18297
|
result.errors.push("expected an object");
|
|
18097
18298
|
return result;
|
|
18098
18299
|
}
|
|
18099
18300
|
const mcpServers = parsed.mcpServers ?? parsed.mcp;
|
|
18100
|
-
if (!
|
|
18301
|
+
if (!isRecord8(mcpServers)) {
|
|
18101
18302
|
result.errors.push("no mcp servers found");
|
|
18102
18303
|
return result;
|
|
18103
18304
|
}
|
|
18104
18305
|
for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
|
|
18105
|
-
if (!
|
|
18306
|
+
if (!isRecord8(entry)) {
|
|
18106
18307
|
result.errors.push(`${name}: not an object`);
|
|
18107
18308
|
continue;
|
|
18108
18309
|
}
|
|
@@ -18118,9 +18319,9 @@ function detectFormat(text, filename) {
|
|
|
18118
18319
|
if (filename.includes(".vscode")) return "vscode";
|
|
18119
18320
|
try {
|
|
18120
18321
|
const parsed = JSON.parse(text);
|
|
18121
|
-
if (
|
|
18122
|
-
if (parsed.servers &&
|
|
18123
|
-
if (parsed.servers &&
|
|
18322
|
+
if (isRecord8(parsed)) {
|
|
18323
|
+
if (parsed.servers && isRecord8(parsed.servers) && parsed.version !== void 0) return "six";
|
|
18324
|
+
if (parsed.servers && isRecord8(parsed.servers)) return "vscode";
|
|
18124
18325
|
if (parsed.mcpServers) return "claude";
|
|
18125
18326
|
if (parsed.mcp) return "opencode";
|
|
18126
18327
|
}
|
|
@@ -22284,5 +22485,5 @@ try {
|
|
|
22284
22485
|
});
|
|
22285
22486
|
} catch {
|
|
22286
22487
|
}
|
|
22287
|
-
var code = await run(platform, "0.31.
|
|
22488
|
+
var code = await run(platform, "0.31.18", args);
|
|
22288
22489
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -6179,6 +6179,12 @@ function agentIgnoresConfig(platform2, path) {
|
|
|
6179
6179
|
function projectConfigPath(platform2) {
|
|
6180
6180
|
return `${platform2.cwd}/.pi/mcp.json`;
|
|
6181
6181
|
}
|
|
6182
|
+
function cacheDir(platform2) {
|
|
6183
|
+
if (platform2.env.XDG_CACHE_HOME) return `${platform2.env.XDG_CACHE_HOME}/mcpcat`;
|
|
6184
|
+
const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
|
|
6185
|
+
if (platform2.env.LOCALAPPDATA) return `${platform2.env.LOCALAPPDATA}\\mcpcat\\cache`;
|
|
6186
|
+
return `${home}/Library/Caches/mcpcat`;
|
|
6187
|
+
}
|
|
6182
6188
|
function stateDir(platform2) {
|
|
6183
6189
|
if (platform2.env.XDG_STATE_HOME) return `${platform2.env.XDG_STATE_HOME}/mcpcat`;
|
|
6184
6190
|
const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
|
|
@@ -6572,6 +6578,7 @@ var HEADER_REJECTED = "The server rejected the explicit Authorization header.";
|
|
|
6572
6578
|
var PLACEHOLDER_NOT_ACCEPTED = "The request carried the signed-in credential's placeholder and the server still answered 401. Seven's vault fills the placeholder in when the request is forwarded, so either that forwarding is not active in this seven yet or the server rejected the credential. Try again from the Settings page's Connect, or ask whoever runs seven.";
|
|
6573
6579
|
var VAULT_NO_ANSWER = "Seven's vault did not answer, so the request carried no credential. Try again, or ask whoever runs seven.";
|
|
6574
6580
|
var PLACEHOLDER_REFUSED = "Seven's vault fills a placeholder in when the request is forwarded, so either that forwarding is not active in this seven yet or it failed. Try again, or ask whoever runs seven.";
|
|
6581
|
+
var CAUSED = /\brefused \([a-z][a-z-]*\): \S/;
|
|
6575
6582
|
var NO_PLACEHOLDER = `This user gets no placeholder for this server. With no sign-in yet: ${SETTINGS_SIGN_IN} After a sign-in, the agent already has this server's tools, because a Settings sign-in gives it the server as native tools, and a human can use the server from a panel terminal as root.`;
|
|
6576
6583
|
function placeholderRefusal(err) {
|
|
6577
6584
|
if (!(err instanceof TypeError)) return void 0;
|
|
@@ -6579,7 +6586,7 @@ function placeholderRefusal(err) {
|
|
|
6579
6586
|
if (code !== "EACCES" || typeof detail !== "string") return void 0;
|
|
6580
6587
|
if (!detail.includes("sealed placeholder")) return void 0;
|
|
6581
6588
|
const reason = detail.replace(/fnox_proxy_[0-9a-f]{24}/g, "[placeholder]");
|
|
6582
|
-
return authError(`${reason}. ${PLACEHOLDER_REFUSED}`);
|
|
6589
|
+
return authError(CAUSED.test(reason) ? `${reason}.` : `${reason}. ${PLACEHOLDER_REFUSED}`);
|
|
6583
6590
|
}
|
|
6584
6591
|
var SettingsSignInError = class extends McpcatError {
|
|
6585
6592
|
advice;
|
|
@@ -13917,10 +13924,30 @@ var HOLDER_RESTART_MAX_MS = 3e4;
|
|
|
13917
13924
|
var HOLDER_STABLE_MS = 3e4;
|
|
13918
13925
|
var HOLDER_STOP_GRACE_MS = 2e3;
|
|
13919
13926
|
var HOLDER_READY_MS = 2e4;
|
|
13927
|
+
var FALLBACK_UNVERIFIED = "webmcp: other-extension fallback could not verify the reloaded document; tools are unavailable";
|
|
13920
13928
|
var LABEL = "mcpcat serve --webmcp";
|
|
13921
|
-
var
|
|
13929
|
+
var TERMINAL_REFUSALS = /* @__PURE__ */ new Set([
|
|
13930
|
+
"slicc: refused: target is on seven's origin",
|
|
13931
|
+
"slicc: refused: seven\u2019s own pages and the vault are out of reach of browser automation",
|
|
13932
|
+
"slicc: refused: breakpoints could run in seven\u2019s pages or the vault, so the guard doesn\u2019t offer the Debugger domains",
|
|
13933
|
+
"slicc: refused: update the browser guard and client",
|
|
13934
|
+
"slicc: refused: WebMCP installation could not verify the document; update the browser guard and client",
|
|
13935
|
+
"slicc: refused: WebMCP installation could not verify a usable guard and document; update the browser guard and client",
|
|
13936
|
+
"slicc: refused: WebMCP instrumentation is unavailable; update the browser guard and client",
|
|
13937
|
+
"webmcp: no tools are available through the other-extension fallback; page startup or parse-time checks may have happened before installation"
|
|
13938
|
+
]);
|
|
13939
|
+
var FALLBACK_REFUSALS = /* @__PURE__ */ new Set([
|
|
13940
|
+
"other-extension fallback could not verify the reloaded document; tools are unavailable",
|
|
13941
|
+
"other-extension fallback could not verify the document"
|
|
13942
|
+
]);
|
|
13943
|
+
function holderReason(line) {
|
|
13944
|
+
return line.trim().replace(/^playwright-cli webmcp-polyfill: /, "").replace(/^webmcp-polyfill: /, "").replace(/^[A-Z][A-Za-z]*\.[a-z][A-Za-z]*: /, "");
|
|
13945
|
+
}
|
|
13946
|
+
function isFallbackUnavailable(line) {
|
|
13947
|
+
return FALLBACK_REFUSALS.has(holderReason(line));
|
|
13948
|
+
}
|
|
13922
13949
|
function isGuardRefusal(line) {
|
|
13923
|
-
return
|
|
13950
|
+
return TERMINAL_REFUSALS.has(holderReason(line)) || isFallbackUnavailable(line);
|
|
13924
13951
|
}
|
|
13925
13952
|
function compareVersions(a, b) {
|
|
13926
13953
|
const pa = a.split(".").map(Number);
|
|
@@ -14048,6 +14075,9 @@ function startHolder(platform2, options) {
|
|
|
14048
14075
|
return;
|
|
14049
14076
|
}
|
|
14050
14077
|
proc = child;
|
|
14078
|
+
state.fallbackUnavailable = false;
|
|
14079
|
+
if (Number.isSafeInteger(child.pid) && child.pid > 0)
|
|
14080
|
+
report(`holder-pid ${child.pid} tab ${options.tab}`);
|
|
14051
14081
|
writer = child.stdin.getWriter();
|
|
14052
14082
|
let lastError;
|
|
14053
14083
|
const out = drain(child.stdout, onEvent);
|
|
@@ -14055,7 +14085,8 @@ function startHolder(platform2, options) {
|
|
|
14055
14085
|
const text = line.trim();
|
|
14056
14086
|
if (!text) return;
|
|
14057
14087
|
lastError = text;
|
|
14058
|
-
if (
|
|
14088
|
+
if (isFallbackUnavailable(text)) state.fallbackUnavailable = true;
|
|
14089
|
+
if (isGuardRefusal(text)) refusal ??= text;
|
|
14059
14090
|
else relay(text);
|
|
14060
14091
|
});
|
|
14061
14092
|
exited = Promise.all([out, err, child.wait()]).then(([, , result]) => {
|
|
@@ -14379,6 +14410,12 @@ function startDialogHolder(platform2, options) {
|
|
|
14379
14410
|
|
|
14380
14411
|
// src/serve/webmcp.ts
|
|
14381
14412
|
var UNAVAILABLE_RE = /WebMCP is unavailable on this page/;
|
|
14413
|
+
function emptyFallbackOrError(error, holderHint) {
|
|
14414
|
+
const hint = error.trim();
|
|
14415
|
+
if (hint === "webmcp: unverified isolated context" && holderHint === FALLBACK_UNVERIFIED)
|
|
14416
|
+
return { tools: [], hint: holderHint };
|
|
14417
|
+
return hint === "webmcp: no tools are available through the other-extension fallback; page startup or parse-time checks may have happened before installation" ? { tools: [], hint } : { tools: [], error };
|
|
14418
|
+
}
|
|
14382
14419
|
function buildBaseArgs(options) {
|
|
14383
14420
|
const args = [];
|
|
14384
14421
|
if (options.cdp) {
|
|
@@ -14947,7 +14984,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
14947
14984
|
async function getRpcTools() {
|
|
14948
14985
|
const { tools, error, notFound, unavailable } = await listRaw();
|
|
14949
14986
|
if (notFound) return null;
|
|
14950
|
-
if (error) return
|
|
14987
|
+
if (error) return emptyFallbackOrError(error, options.emptyHint?.(false));
|
|
14951
14988
|
const late = options.late?.() ?? false;
|
|
14952
14989
|
const rpcTools = [];
|
|
14953
14990
|
const hidden = {};
|
|
@@ -16109,6 +16146,7 @@ function emptyToolsHint(options, polyfill, unavailable) {
|
|
|
16109
16146
|
return `the page shows no WebMCP tools, and the WebMCP polyfill needs playwright-cli ${POLYFILL_MIN_VERSION} or newer (found ${polyfill.oldVersion})`;
|
|
16110
16147
|
}
|
|
16111
16148
|
const status = polyfill.holder?.status();
|
|
16149
|
+
if (status?.refused && status.fallbackUnavailable) return FALLBACK_UNVERIFIED;
|
|
16112
16150
|
if (isLate(polyfill.holder)) {
|
|
16113
16151
|
return "the page may check for WebMCP only when it loads, before the polyfill was injected; reload the tab, or restart with --webmcp-reload (it reloads the tab)";
|
|
16114
16152
|
}
|
|
@@ -16936,6 +16974,142 @@ function parseUpgradeArgs(args) {
|
|
|
16936
16974
|
return { kind: "run", check, force };
|
|
16937
16975
|
}
|
|
16938
16976
|
|
|
16977
|
+
// src/auth/callback-port.ts
|
|
16978
|
+
function hasFixedCallback(oauth) {
|
|
16979
|
+
if (oauth?.callbackPort !== void 0 && oauth.callbackPort > 0) return true;
|
|
16980
|
+
if (!oauth?.callbackUrl) return false;
|
|
16981
|
+
try {
|
|
16982
|
+
const port = Number(new URL(oauth.callbackUrl).port);
|
|
16983
|
+
return port > 0;
|
|
16984
|
+
} catch {
|
|
16985
|
+
return false;
|
|
16986
|
+
}
|
|
16987
|
+
}
|
|
16988
|
+
function needsInjectedCallbackPort(options) {
|
|
16989
|
+
return options.namedEntry && !hasFixedCallback(options.oauth);
|
|
16990
|
+
}
|
|
16991
|
+
async function withInjectedCallbackPortIfNeeded(platform2, name, options, fn) {
|
|
16992
|
+
if (!needsInjectedCallbackPort(options)) return fn(void 0);
|
|
16993
|
+
const port = await allocateCallbackPort();
|
|
16994
|
+
return withTempCallbackPortConfig(platform2, name, port, fn);
|
|
16995
|
+
}
|
|
16996
|
+
async function allocateCallbackPort() {
|
|
16997
|
+
const callback = await OAuthCallbackServer.listen({
|
|
16998
|
+
host: "127.0.0.1",
|
|
16999
|
+
port: 0,
|
|
17000
|
+
path: "/callback"
|
|
17001
|
+
});
|
|
17002
|
+
try {
|
|
17003
|
+
return Number(new URL(callback.redirectUrl).port);
|
|
17004
|
+
} finally {
|
|
17005
|
+
await callback.close();
|
|
17006
|
+
}
|
|
17007
|
+
}
|
|
17008
|
+
function isRecord7(value) {
|
|
17009
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17010
|
+
}
|
|
17011
|
+
function hostEnv(platform2) {
|
|
17012
|
+
const out = {};
|
|
17013
|
+
for (const [key, value] of Object.entries(platform2.env)) {
|
|
17014
|
+
if (value !== void 0) out[key] = value;
|
|
17015
|
+
}
|
|
17016
|
+
return out;
|
|
17017
|
+
}
|
|
17018
|
+
async function makeTempDir(platform2) {
|
|
17019
|
+
const base = `${cacheDir(platform2)}/mcp-cb`;
|
|
17020
|
+
await platform2.fs.mkdir(base, { mode: 448 });
|
|
17021
|
+
for (let i = 0; i < 32; i++) {
|
|
17022
|
+
const dir = `${base}/${Date.now().toString(36)}-${i.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
17023
|
+
try {
|
|
17024
|
+
await platform2.fs.mkdirExclusive(dir);
|
|
17025
|
+
return dir;
|
|
17026
|
+
} catch {
|
|
17027
|
+
}
|
|
17028
|
+
}
|
|
17029
|
+
throw new Error("unable to create temporary mcp config directory");
|
|
17030
|
+
}
|
|
17031
|
+
async function removeTempDir(platform2, dir) {
|
|
17032
|
+
try {
|
|
17033
|
+
if (platform2.fs.unlink) await platform2.fs.unlink(`${dir}/mcp.json`);
|
|
17034
|
+
} catch {
|
|
17035
|
+
}
|
|
17036
|
+
try {
|
|
17037
|
+
await platform2.fs.rmdir(dir);
|
|
17038
|
+
} catch {
|
|
17039
|
+
}
|
|
17040
|
+
}
|
|
17041
|
+
async function withTempCallbackPortConfig(platform2, name, port, fn) {
|
|
17042
|
+
const path = userConfigPath(platform2);
|
|
17043
|
+
let raw;
|
|
17044
|
+
try {
|
|
17045
|
+
raw = new TextDecoder().decode(await platform2.fs.readFile(path));
|
|
17046
|
+
} catch {
|
|
17047
|
+
return fn(void 0);
|
|
17048
|
+
}
|
|
17049
|
+
let parsed;
|
|
17050
|
+
try {
|
|
17051
|
+
parsed = JSON.parse(raw);
|
|
17052
|
+
} catch {
|
|
17053
|
+
return fn(void 0);
|
|
17054
|
+
}
|
|
17055
|
+
if (!isRecord7(parsed) || !isRecord7(parsed.mcpServers) || !isRecord7(parsed.mcpServers[name])) {
|
|
17056
|
+
return fn(void 0);
|
|
17057
|
+
}
|
|
17058
|
+
const entry = { ...parsed.mcpServers[name] };
|
|
17059
|
+
const prevOauth = isRecord7(entry.oauth) ? { ...entry.oauth } : {};
|
|
17060
|
+
const oauth = { ...prevOauth, callbackPort: port };
|
|
17061
|
+
if (typeof oauth.callbackUrl === "string") {
|
|
17062
|
+
try {
|
|
17063
|
+
const url = new URL(oauth.callbackUrl);
|
|
17064
|
+
if (!(Number(url.port) > 0)) {
|
|
17065
|
+
url.port = String(port);
|
|
17066
|
+
oauth.callbackUrl = url.href;
|
|
17067
|
+
}
|
|
17068
|
+
} catch {
|
|
17069
|
+
}
|
|
17070
|
+
}
|
|
17071
|
+
entry.oauth = oauth;
|
|
17072
|
+
const tempServers = { ...parsed.mcpServers, [name]: entry };
|
|
17073
|
+
const merged = { ...parsed, mcpServers: tempServers };
|
|
17074
|
+
const tempDir = await makeTempDir(platform2);
|
|
17075
|
+
const tempPath = `${tempDir}/mcp.json`;
|
|
17076
|
+
await platform2.fs.writeFile(tempPath, `${JSON.stringify(merged, null, 2)}
|
|
17077
|
+
`, { mode: 384 });
|
|
17078
|
+
const env2 = { ...hostEnv(platform2), PI_MCP_CONFIG: tempPath };
|
|
17079
|
+
let cleaned = false;
|
|
17080
|
+
const cleanup = async () => {
|
|
17081
|
+
if (cleaned) return;
|
|
17082
|
+
cleaned = true;
|
|
17083
|
+
await removeTempDir(platform2, tempDir);
|
|
17084
|
+
};
|
|
17085
|
+
const exitFor = (signal) => signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129;
|
|
17086
|
+
let signalCode;
|
|
17087
|
+
let wake;
|
|
17088
|
+
const stopped = new Promise((resolve2) => {
|
|
17089
|
+
wake = resolve2;
|
|
17090
|
+
});
|
|
17091
|
+
if (platform2.onSignal) {
|
|
17092
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
17093
|
+
await platform2.onSignal(signal, () => {
|
|
17094
|
+
void cleanup().then(() => {
|
|
17095
|
+
signalCode = exitFor(signal);
|
|
17096
|
+
wake?.();
|
|
17097
|
+
});
|
|
17098
|
+
});
|
|
17099
|
+
}
|
|
17100
|
+
}
|
|
17101
|
+
try {
|
|
17102
|
+
const result = await Promise.race([
|
|
17103
|
+
fn(env2).then((value) => ({ ok: true, value })),
|
|
17104
|
+
stopped.then(() => ({ ok: false }))
|
|
17105
|
+
]);
|
|
17106
|
+
if (!result.ok) platform2.exit(signalCode ?? 130);
|
|
17107
|
+
return result.value;
|
|
17108
|
+
} finally {
|
|
17109
|
+
await cleanup();
|
|
17110
|
+
}
|
|
17111
|
+
}
|
|
17112
|
+
|
|
16939
17113
|
// src/auth/oauth.ts
|
|
16940
17114
|
var CALLBACK_HOST2 = "127.0.0.1";
|
|
16941
17115
|
var CALLBACK_PATH2 = "/callback";
|
|
@@ -16951,14 +17125,23 @@ function timedFetch2(fetchImpl) {
|
|
|
16951
17125
|
});
|
|
16952
17126
|
};
|
|
16953
17127
|
}
|
|
17128
|
+
function nonzeroPort(value) {
|
|
17129
|
+
return value !== void 0 && value > 0 ? value : void 0;
|
|
17130
|
+
}
|
|
17131
|
+
function preferredPortFromRegistration(configured, registered) {
|
|
17132
|
+
if (configured !== void 0) return configured;
|
|
17133
|
+
if (!registered[0]) return void 0;
|
|
17134
|
+
return nonzeroPort(Number(new URL(registered[0]).port));
|
|
17135
|
+
}
|
|
16954
17136
|
function callbackSettings(oauth) {
|
|
16955
17137
|
const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
|
|
16956
17138
|
const url = new URL(raw);
|
|
16957
17139
|
const address = url.hostname.replace(/^\[|\]$/g, "");
|
|
16958
|
-
const port = url.port ? Number(url.port) : oauth?.callbackPort;
|
|
17140
|
+
const port = nonzeroPort(url.port ? Number(url.port) : oauth?.callbackPort);
|
|
16959
17141
|
let fixedRedirectUrl;
|
|
16960
|
-
if (url.port)
|
|
16961
|
-
|
|
17142
|
+
if (nonzeroPort(url.port ? Number(url.port) : void 0) !== void 0 && oauth?.callbackUrl) {
|
|
17143
|
+
fixedRedirectUrl = oauth.callbackUrl;
|
|
17144
|
+
} else if (port !== void 0) {
|
|
16962
17145
|
url.port = String(port);
|
|
16963
17146
|
fixedRedirectUrl = url.href;
|
|
16964
17147
|
}
|
|
@@ -16970,6 +17153,24 @@ function callbackSettings(oauth) {
|
|
|
16970
17153
|
fixedRedirectUrl
|
|
16971
17154
|
};
|
|
16972
17155
|
}
|
|
17156
|
+
async function listenCallback(cbSettings, preferredPort) {
|
|
17157
|
+
try {
|
|
17158
|
+
return await OAuthCallbackServer.listen({
|
|
17159
|
+
host: cbSettings.host,
|
|
17160
|
+
redirectHost: cbSettings.redirectHost,
|
|
17161
|
+
port: preferredPort ?? 0,
|
|
17162
|
+
path: cbSettings.path
|
|
17163
|
+
});
|
|
17164
|
+
} catch (err) {
|
|
17165
|
+
if (preferredPort === void 0 || cbSettings.fixedRedirectUrl) throw err;
|
|
17166
|
+
return OAuthCallbackServer.listen({
|
|
17167
|
+
host: cbSettings.host,
|
|
17168
|
+
redirectHost: cbSettings.redirectHost,
|
|
17169
|
+
port: 0,
|
|
17170
|
+
path: cbSettings.path
|
|
17171
|
+
});
|
|
17172
|
+
}
|
|
17173
|
+
}
|
|
16973
17174
|
function cimdForServer(metadata, redirectUrl) {
|
|
16974
17175
|
if (!metadata?.client_id_metadata_document_supported) return void 0;
|
|
16975
17176
|
if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
|
|
@@ -17074,24 +17275,17 @@ async function signIn(options) {
|
|
|
17074
17275
|
const stored = await store.load();
|
|
17075
17276
|
const cbSettings = callbackSettings(oauth);
|
|
17076
17277
|
const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
|
|
17077
|
-
const preferredPort = cbSettings.port
|
|
17078
|
-
const callback = await
|
|
17079
|
-
host: cbSettings.host,
|
|
17080
|
-
redirectHost: cbSettings.redirectHost,
|
|
17081
|
-
port: preferredPort ?? 0,
|
|
17082
|
-
path: cbSettings.path
|
|
17083
|
-
});
|
|
17278
|
+
const preferredPort = preferredPortFromRegistration(cbSettings.port, registered);
|
|
17279
|
+
const callback = await listenCallback(cbSettings, preferredPort);
|
|
17084
17280
|
const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
|
|
17085
17281
|
try {
|
|
17086
17282
|
if (stored) {
|
|
17087
17283
|
const next = { ...stored };
|
|
17088
17284
|
delete next.oauthState;
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
if (!
|
|
17285
|
+
delete next.tokens;
|
|
17286
|
+
delete next.tokensExpireAt;
|
|
17287
|
+
if (!oauth?.clientId) {
|
|
17092
17288
|
delete next.clientInformation;
|
|
17093
|
-
delete next.tokens;
|
|
17094
|
-
delete next.tokensExpireAt;
|
|
17095
17289
|
}
|
|
17096
17290
|
await store.save(next);
|
|
17097
17291
|
}
|
|
@@ -17174,13 +17368,14 @@ async function resolveLoginTarget(platform2, target) {
|
|
|
17174
17368
|
return {
|
|
17175
17369
|
name: target,
|
|
17176
17370
|
serverUrl: httpConfig.url,
|
|
17177
|
-
oauth: httpConfig.oauth
|
|
17371
|
+
oauth: httpConfig.oauth,
|
|
17372
|
+
namedEntry: true
|
|
17178
17373
|
};
|
|
17179
17374
|
}
|
|
17180
17375
|
const resolved = resolveServerName(target);
|
|
17181
17376
|
if (resolved.isUrl) {
|
|
17182
17377
|
const url = withScheme(target);
|
|
17183
|
-
return { name: resolved.name, serverUrl: url, oauth: void 0 };
|
|
17378
|
+
return { name: resolved.name, serverUrl: url, oauth: void 0, namedEntry: false };
|
|
17184
17379
|
}
|
|
17185
17380
|
throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
|
|
17186
17381
|
}
|
|
@@ -17211,24 +17406,31 @@ async function jerkLogin(platform2, target, resolved, args) {
|
|
|
17211
17406
|
if (args.includes("--no-browser")) {
|
|
17212
17407
|
argv.push("--no-browser");
|
|
17213
17408
|
}
|
|
17214
|
-
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
|
|
17218
|
-
|
|
17219
|
-
|
|
17220
|
-
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17409
|
+
return withInjectedCallbackPortIfNeeded(
|
|
17410
|
+
platform2,
|
|
17411
|
+
resolved.name,
|
|
17412
|
+
{ namedEntry: resolved.namedEntry, oauth: resolved.oauth },
|
|
17413
|
+
async (env2) => {
|
|
17414
|
+
const proc = await platform2.spawn({ argv, env: env2 });
|
|
17415
|
+
proc.stdin.close().catch(() => {
|
|
17416
|
+
});
|
|
17417
|
+
const [, errText, result] = await Promise.all([
|
|
17418
|
+
readStream2(proc.stdout),
|
|
17419
|
+
readStream2(proc.stderr),
|
|
17420
|
+
proc.wait()
|
|
17421
|
+
]);
|
|
17422
|
+
if (result.status === 0) {
|
|
17423
|
+
await platform2.write(1, `Signed in to ${target}
|
|
17224
17424
|
`);
|
|
17225
|
-
|
|
17226
|
-
|
|
17227
|
-
|
|
17228
|
-
|
|
17229
|
-
|
|
17230
|
-
|
|
17231
|
-
|
|
17425
|
+
return 0;
|
|
17426
|
+
}
|
|
17427
|
+
if (errText.trim()) {
|
|
17428
|
+
await platform2.write(2, sanitizeTerminal(errText));
|
|
17429
|
+
}
|
|
17430
|
+
if (result.status === JERK_EXIT_SIGN_IN) return 4;
|
|
17431
|
+
return result.status;
|
|
17432
|
+
}
|
|
17433
|
+
);
|
|
17232
17434
|
}
|
|
17233
17435
|
async function jerkLogout(platform2, target, resolved, source) {
|
|
17234
17436
|
const id = jerkId(resolved.name, resolved.serverUrl);
|
|
@@ -17405,11 +17607,11 @@ function concatUint8Arrays(arrays) {
|
|
|
17405
17607
|
}
|
|
17406
17608
|
|
|
17407
17609
|
// src/config/import.ts
|
|
17408
|
-
function
|
|
17610
|
+
function isRecord8(value) {
|
|
17409
17611
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17410
17612
|
}
|
|
17411
17613
|
function isStringRecord2(value) {
|
|
17412
|
-
return
|
|
17614
|
+
return isRecord8(value) && Object.values(value).every((v) => typeof v === "string");
|
|
17413
17615
|
}
|
|
17414
17616
|
function tryParse(text) {
|
|
17415
17617
|
try {
|
|
@@ -17439,17 +17641,17 @@ function importClaude(text, names) {
|
|
|
17439
17641
|
result.errors.push(error);
|
|
17440
17642
|
return result;
|
|
17441
17643
|
}
|
|
17442
|
-
if (!
|
|
17644
|
+
if (!isRecord8(parsed)) {
|
|
17443
17645
|
result.errors.push("expected an object");
|
|
17444
17646
|
return result;
|
|
17445
17647
|
}
|
|
17446
17648
|
const mcpServers = parsed.mcpServers;
|
|
17447
|
-
if (!
|
|
17649
|
+
if (!isRecord8(mcpServers)) {
|
|
17448
17650
|
result.errors.push('no "mcpServers" object');
|
|
17449
17651
|
return result;
|
|
17450
17652
|
}
|
|
17451
17653
|
for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
|
|
17452
|
-
if (!
|
|
17654
|
+
if (!isRecord8(entry)) {
|
|
17453
17655
|
result.errors.push(`${name}: not an object`);
|
|
17454
17656
|
continue;
|
|
17455
17657
|
}
|
|
@@ -17464,17 +17666,17 @@ function importVSCode(text, names) {
|
|
|
17464
17666
|
result.errors.push(error);
|
|
17465
17667
|
return result;
|
|
17466
17668
|
}
|
|
17467
|
-
if (!
|
|
17669
|
+
if (!isRecord8(parsed)) {
|
|
17468
17670
|
result.errors.push("expected an object");
|
|
17469
17671
|
return result;
|
|
17470
17672
|
}
|
|
17471
17673
|
const servers = parsed.servers ?? parsed.mcpServers;
|
|
17472
|
-
if (!
|
|
17674
|
+
if (!isRecord8(servers)) {
|
|
17473
17675
|
result.errors.push('no "servers" or "mcpServers" object');
|
|
17474
17676
|
return result;
|
|
17475
17677
|
}
|
|
17476
17678
|
for (const [name, entry] of filterNames(Object.entries(servers), names)) {
|
|
17477
|
-
if (!
|
|
17679
|
+
if (!isRecord8(entry)) {
|
|
17478
17680
|
result.errors.push(`${name}: not an object`);
|
|
17479
17681
|
continue;
|
|
17480
17682
|
}
|
|
@@ -17508,17 +17710,17 @@ function importSixServers(text, names) {
|
|
|
17508
17710
|
result.errors.push(error);
|
|
17509
17711
|
return result;
|
|
17510
17712
|
}
|
|
17511
|
-
if (!
|
|
17713
|
+
if (!isRecord8(parsed)) {
|
|
17512
17714
|
result.errors.push("expected an object");
|
|
17513
17715
|
return result;
|
|
17514
17716
|
}
|
|
17515
17717
|
const servers = parsed.servers;
|
|
17516
|
-
if (!
|
|
17718
|
+
if (!isRecord8(servers)) {
|
|
17517
17719
|
result.errors.push('no "servers" object');
|
|
17518
17720
|
return result;
|
|
17519
17721
|
}
|
|
17520
17722
|
for (const [name, entry] of filterNames(Object.entries(servers), names)) {
|
|
17521
|
-
if (!
|
|
17723
|
+
if (!isRecord8(entry)) {
|
|
17522
17724
|
result.errors.push(`${name}: not an object`);
|
|
17523
17725
|
continue;
|
|
17524
17726
|
}
|
|
@@ -17526,7 +17728,7 @@ function importSixServers(text, names) {
|
|
|
17526
17728
|
if (typeof entry.url === "string") config.url = entry.url;
|
|
17527
17729
|
if (isStringRecord2(entry.headers)) config.headers = entry.headers;
|
|
17528
17730
|
if (typeof entry.exposure === "string") config.exposure = entry.exposure;
|
|
17529
|
-
if (
|
|
17731
|
+
if (isRecord8(entry.toolExposure)) config.toolExposure = entry.toolExposure;
|
|
17530
17732
|
result.servers.push({ name, config });
|
|
17531
17733
|
}
|
|
17532
17734
|
return result;
|
|
@@ -17543,7 +17745,7 @@ function importCodexToml(text, names) {
|
|
|
17543
17745
|
if (fields.command) config.command = fields.command;
|
|
17544
17746
|
if (fields.args) config.args = fields.args;
|
|
17545
17747
|
if (fields.url) config.url = fields.url;
|
|
17546
|
-
if (fields.env &&
|
|
17748
|
+
if (fields.env && isRecord8(fields.env)) config.env = fields.env;
|
|
17547
17749
|
result.servers.push({ name, config });
|
|
17548
17750
|
}
|
|
17549
17751
|
return result;
|
|
@@ -17595,7 +17797,7 @@ function convertOpenCodeLocal(entry) {
|
|
|
17595
17797
|
} else if (typeof entry.command === "string") {
|
|
17596
17798
|
config.command = entry.command;
|
|
17597
17799
|
}
|
|
17598
|
-
if (
|
|
17800
|
+
if (isRecord8(entry.environment)) {
|
|
17599
17801
|
const env2 = {};
|
|
17600
17802
|
for (const [k, v] of Object.entries(entry.environment)) {
|
|
17601
17803
|
if (typeof v === "string")
|
|
@@ -17618,17 +17820,17 @@ function importOpenCode(text, names) {
|
|
|
17618
17820
|
result.errors.push(error);
|
|
17619
17821
|
return result;
|
|
17620
17822
|
}
|
|
17621
|
-
if (!
|
|
17823
|
+
if (!isRecord8(parsed)) {
|
|
17622
17824
|
result.errors.push("expected an object");
|
|
17623
17825
|
return result;
|
|
17624
17826
|
}
|
|
17625
17827
|
const mcpServers = parsed.mcpServers ?? parsed.mcp;
|
|
17626
|
-
if (!
|
|
17828
|
+
if (!isRecord8(mcpServers)) {
|
|
17627
17829
|
result.errors.push("no mcp servers found");
|
|
17628
17830
|
return result;
|
|
17629
17831
|
}
|
|
17630
17832
|
for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
|
|
17631
|
-
if (!
|
|
17833
|
+
if (!isRecord8(entry)) {
|
|
17632
17834
|
result.errors.push(`${name}: not an object`);
|
|
17633
17835
|
continue;
|
|
17634
17836
|
}
|
|
@@ -17644,9 +17846,9 @@ function detectFormat(text, filename) {
|
|
|
17644
17846
|
if (filename.includes(".vscode")) return "vscode";
|
|
17645
17847
|
try {
|
|
17646
17848
|
const parsed = JSON.parse(text);
|
|
17647
|
-
if (
|
|
17648
|
-
if (parsed.servers &&
|
|
17649
|
-
if (parsed.servers &&
|
|
17849
|
+
if (isRecord8(parsed)) {
|
|
17850
|
+
if (parsed.servers && isRecord8(parsed.servers) && parsed.version !== void 0) return "six";
|
|
17851
|
+
if (parsed.servers && isRecord8(parsed.servers)) return "vscode";
|
|
17650
17852
|
if (parsed.mcpServers) return "claude";
|
|
17651
17853
|
if (parsed.mcp) return "opencode";
|
|
17652
17854
|
}
|
|
@@ -21238,6 +21440,7 @@ async function kernelSpawn(ctx, options) {
|
|
|
21238
21440
|
const useGroupKill = groupSpawnSupported;
|
|
21239
21441
|
let waited;
|
|
21240
21442
|
return {
|
|
21443
|
+
pid: childPid,
|
|
21241
21444
|
stdin: result.stdin ?? new WritableStream(),
|
|
21242
21445
|
stdout: result.stdout,
|
|
21243
21446
|
stderr: result.stderr,
|
|
@@ -21651,7 +21854,7 @@ async function main(ctx) {
|
|
|
21651
21854
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
21652
21855
|
});
|
|
21653
21856
|
const args = ctx.argv.slice(1);
|
|
21654
|
-
return run(platform2, "0.31.
|
|
21857
|
+
return run(platform2, "0.31.18", args);
|
|
21655
21858
|
}
|
|
21656
21859
|
export {
|
|
21657
21860
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.31.
|
|
3
|
+
"version": "0.31.18",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -58,16 +58,16 @@
|
|
|
58
58
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
62
|
-
"@ai-ecoverse/slicc-shared-web": "2.13.
|
|
63
|
-
"@ai-ecoverse/slicc-vault": "1.
|
|
61
|
+
"@ai-ecoverse/slicc-kernel": "1.174.0",
|
|
62
|
+
"@ai-ecoverse/slicc-shared-web": "2.13.10",
|
|
63
|
+
"@ai-ecoverse/slicc-vault": "1.22.0",
|
|
64
64
|
"@biomejs/biome": "2.6.3",
|
|
65
65
|
"@earendil-works/pi-codemode": "1.4.0",
|
|
66
66
|
"@earendil-works/pi-mcp": "1.4.0",
|
|
67
67
|
"@types/node": "26.6.5",
|
|
68
68
|
"esbuild": "0.28.2",
|
|
69
69
|
"lefthook": "2.2.2",
|
|
70
|
-
"monocart-coverage-reports": "2.
|
|
70
|
+
"monocart-coverage-reports": "3.2.0",
|
|
71
71
|
"proper-lockfile": "4.1.2",
|
|
72
72
|
"quickjs-wasi": "3.6.3",
|
|
73
73
|
"semantic-release": "25.0.9",
|