@ai-ecoverse/mcpcat 0.20.1 → 0.20.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/host/mcpcat.mjs +347 -37
- package/dist/kernel/mcpcat.mjs +319 -37
- package/package.json +2 -2
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -8485,8 +8485,19 @@ function buildServerInfo(discoverResult2) {
|
|
|
8485
8485
|
instructions: discoverResult2?.instructions
|
|
8486
8486
|
};
|
|
8487
8487
|
}
|
|
8488
|
+
function readHiddenTools(result) {
|
|
8489
|
+
const meta = result?._meta;
|
|
8490
|
+
const hidden = meta?.["io.mcpcat/hidden"];
|
|
8491
|
+
const out = {};
|
|
8492
|
+
if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
|
|
8493
|
+
for (const [name, why] of Object.entries(hidden)) {
|
|
8494
|
+
if (typeof why === "string") out[name] = why;
|
|
8495
|
+
}
|
|
8496
|
+
return out;
|
|
8497
|
+
}
|
|
8488
8498
|
function buildStatelessMethods(requestFn, discoverResult2) {
|
|
8489
8499
|
let serverInfoCache;
|
|
8500
|
+
let hidden = {};
|
|
8490
8501
|
return {
|
|
8491
8502
|
async info() {
|
|
8492
8503
|
if (serverInfoCache) return serverInfoCache;
|
|
@@ -8494,7 +8505,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
|
|
|
8494
8505
|
return serverInfoCache;
|
|
8495
8506
|
},
|
|
8496
8507
|
async listTools() {
|
|
8497
|
-
|
|
8508
|
+
const result = await requestFn("tools/list", {});
|
|
8509
|
+
hidden = readHiddenTools(result);
|
|
8510
|
+
return result?.tools ?? [];
|
|
8511
|
+
},
|
|
8512
|
+
hiddenTool(name) {
|
|
8513
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8498
8514
|
},
|
|
8499
8515
|
async callTool(name, args2, _callOptions) {
|
|
8500
8516
|
const params = {
|
|
@@ -8934,12 +8950,18 @@ function buildServerInfo2(discoverResult2) {
|
|
|
8934
8950
|
};
|
|
8935
8951
|
}
|
|
8936
8952
|
function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
8953
|
+
let hidden = {};
|
|
8937
8954
|
return {
|
|
8938
8955
|
async info() {
|
|
8939
8956
|
return buildServerInfo2(discoverResult2);
|
|
8940
8957
|
},
|
|
8941
8958
|
async listTools() {
|
|
8942
|
-
|
|
8959
|
+
const result = await rpc.request("tools/list", {});
|
|
8960
|
+
hidden = readHiddenTools(result);
|
|
8961
|
+
return result?.tools ?? [];
|
|
8962
|
+
},
|
|
8963
|
+
hiddenTool(name) {
|
|
8964
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8943
8965
|
},
|
|
8944
8966
|
async callTool(name, args2, callOptions) {
|
|
8945
8967
|
const params = { name, arguments: args2 ?? {} };
|
|
@@ -9141,7 +9163,7 @@ function readLine(platform2) {
|
|
|
9141
9163
|
return new Promise((resolve, reject) => {
|
|
9142
9164
|
const reader = platform2.stdin.getReader();
|
|
9143
9165
|
const chunks = [];
|
|
9144
|
-
function
|
|
9166
|
+
function pump2() {
|
|
9145
9167
|
reader.read().then(({ done, value }) => {
|
|
9146
9168
|
if (done) {
|
|
9147
9169
|
reader.releaseLock();
|
|
@@ -9155,10 +9177,10 @@ function readLine(platform2) {
|
|
|
9155
9177
|
resolve(text.split("\n")[0]);
|
|
9156
9178
|
return;
|
|
9157
9179
|
}
|
|
9158
|
-
|
|
9180
|
+
pump2();
|
|
9159
9181
|
}, reject);
|
|
9160
9182
|
}
|
|
9161
|
-
|
|
9183
|
+
pump2();
|
|
9162
9184
|
});
|
|
9163
9185
|
}
|
|
9164
9186
|
function concatChunks(chunks) {
|
|
@@ -12037,7 +12059,7 @@ async function writeResponse(writer, response) {
|
|
|
12037
12059
|
var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
12038
12060
|
var OUTPUT_CAP = 1048576;
|
|
12039
12061
|
var JSON_CT = "application/json; charset=utf-8";
|
|
12040
|
-
async function handleJsonRpc(raw,
|
|
12062
|
+
async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
|
|
12041
12063
|
let parsed;
|
|
12042
12064
|
try {
|
|
12043
12065
|
parsed = JSON.parse(raw);
|
|
@@ -12054,16 +12076,26 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
|
|
|
12054
12076
|
if (method === "initialize")
|
|
12055
12077
|
return rpcOk(id, initializeResult(params, serverName, serverVersion));
|
|
12056
12078
|
if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
|
|
12057
|
-
if (method === "tools/list") return rpcOk(id, { tools });
|
|
12058
12079
|
if (method === "ping") return rpcOk(id, {});
|
|
12059
|
-
if (method === "tools/
|
|
12080
|
+
if (method === "tools/list" || method === "tools/call") {
|
|
12081
|
+
const listing = Array.isArray(source) ? { tools: source } : await source();
|
|
12082
|
+
if (listing.error) return rpcError(id, -32e3, listing.error);
|
|
12083
|
+
if (method === "tools/call") return toolCall(id, params, listing, call);
|
|
12084
|
+
const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
|
|
12085
|
+
return rpcOk(id, {
|
|
12086
|
+
tools: listing.tools,
|
|
12087
|
+
...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
|
|
12088
|
+
});
|
|
12089
|
+
}
|
|
12060
12090
|
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
12061
12091
|
}
|
|
12062
|
-
async function toolCall(id, params, tools, call) {
|
|
12092
|
+
async function toolCall(id, params, { tools, hidden }, call) {
|
|
12063
12093
|
const name = params && typeof params === "object" ? readString(params, "name") : void 0;
|
|
12064
12094
|
if (!name) return rpcError(id, -32602, "tools/call requires a name");
|
|
12065
|
-
if (!tools.some((tool) => tool.name === name))
|
|
12066
|
-
|
|
12095
|
+
if (!tools.some((tool) => tool.name === name)) {
|
|
12096
|
+
const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
12097
|
+
return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
|
|
12098
|
+
}
|
|
12067
12099
|
const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
12068
12100
|
try {
|
|
12069
12101
|
const result = await call(name, args2);
|
|
@@ -12427,7 +12459,149 @@ ${tail}`,
|
|
|
12427
12459
|
}
|
|
12428
12460
|
};
|
|
12429
12461
|
}
|
|
12430
|
-
|
|
12462
|
+
var DIALOG_POLL_MS = 250;
|
|
12463
|
+
var DIALOG_READY_MS = 1e3;
|
|
12464
|
+
var DIALOG_STALL_MS = 1e3;
|
|
12465
|
+
function watchLoop(watch) {
|
|
12466
|
+
let timer;
|
|
12467
|
+
const result = new Promise((resolve) => {
|
|
12468
|
+
timer = setInterval(() => {
|
|
12469
|
+
const found = watch();
|
|
12470
|
+
if (found) {
|
|
12471
|
+
clearInterval(timer);
|
|
12472
|
+
resolve(found);
|
|
12473
|
+
}
|
|
12474
|
+
}, DIALOG_POLL_MS);
|
|
12475
|
+
});
|
|
12476
|
+
return {
|
|
12477
|
+
result,
|
|
12478
|
+
stop() {
|
|
12479
|
+
clearInterval(timer);
|
|
12480
|
+
}
|
|
12481
|
+
};
|
|
12482
|
+
}
|
|
12483
|
+
async function browserSocketUrl(platform2, cdp) {
|
|
12484
|
+
if (/^wss?:\/\//i.test(cdp)) return cdp;
|
|
12485
|
+
if (!/^https?:\/\//i.test(cdp)) return void 0;
|
|
12486
|
+
const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
|
|
12487
|
+
signal: AbortSignal.timeout(DIALOG_READY_MS)
|
|
12488
|
+
});
|
|
12489
|
+
const body = await res.json();
|
|
12490
|
+
return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
|
|
12491
|
+
}
|
|
12492
|
+
function parseCdp(data) {
|
|
12493
|
+
try {
|
|
12494
|
+
return JSON.parse(
|
|
12495
|
+
typeof data === "string" ? data : new TextDecoder().decode(data)
|
|
12496
|
+
);
|
|
12497
|
+
} catch {
|
|
12498
|
+
return void 0;
|
|
12499
|
+
}
|
|
12500
|
+
}
|
|
12501
|
+
function applyCdp(state, channel, send, msg, settle) {
|
|
12502
|
+
if (msg.method === "Page.javascriptDialogOpening") {
|
|
12503
|
+
state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
|
|
12504
|
+
} else if (msg.method === "Page.javascriptDialogClosed") {
|
|
12505
|
+
state.open = void 0;
|
|
12506
|
+
} else if (msg.method === "Target.detachedFromTarget") {
|
|
12507
|
+
channel.close();
|
|
12508
|
+
} else if (msg.id === 1) {
|
|
12509
|
+
const sessionId = msg.result?.sessionId;
|
|
12510
|
+
if (typeof sessionId !== "string") {
|
|
12511
|
+
channel.close();
|
|
12512
|
+
return;
|
|
12513
|
+
}
|
|
12514
|
+
state.enableSentAt = Date.now();
|
|
12515
|
+
send({ id: 2, sessionId, method: "Page.enable" });
|
|
12516
|
+
} else if (msg.id === 2) {
|
|
12517
|
+
state.enableSentAt = void 0;
|
|
12518
|
+
settle();
|
|
12519
|
+
}
|
|
12520
|
+
}
|
|
12521
|
+
async function pump(state, channel, send, settle) {
|
|
12522
|
+
const reader = channel.readable.getReader();
|
|
12523
|
+
try {
|
|
12524
|
+
for (; ; ) {
|
|
12525
|
+
const { done, value } = await reader.read();
|
|
12526
|
+
if (done) return;
|
|
12527
|
+
const msg = parseCdp(value);
|
|
12528
|
+
if (msg) applyCdp(state, channel, send, msg, settle);
|
|
12529
|
+
}
|
|
12530
|
+
} catch {
|
|
12531
|
+
return;
|
|
12532
|
+
}
|
|
12533
|
+
}
|
|
12534
|
+
async function attachSocket(platform2, state, url, targetId) {
|
|
12535
|
+
const channel = await platform2.websocket(url).catch(() => void 0);
|
|
12536
|
+
if (!channel) return;
|
|
12537
|
+
state.channel = channel;
|
|
12538
|
+
const writer = channel.writable.getWriter();
|
|
12539
|
+
const send = (message) => {
|
|
12540
|
+
writer.write(JSON.stringify(message)).catch(() => channel.close());
|
|
12541
|
+
};
|
|
12542
|
+
let settle;
|
|
12543
|
+
const settled = new Promise((resolve) => {
|
|
12544
|
+
settle = resolve;
|
|
12545
|
+
});
|
|
12546
|
+
void pump(state, channel, send, settle).then(() => {
|
|
12547
|
+
if (state.channel === channel) {
|
|
12548
|
+
state.channel = void 0;
|
|
12549
|
+
state.open = void 0;
|
|
12550
|
+
state.enableSentAt = void 0;
|
|
12551
|
+
}
|
|
12552
|
+
channel.close();
|
|
12553
|
+
settle();
|
|
12554
|
+
});
|
|
12555
|
+
send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
|
|
12556
|
+
await settled;
|
|
12557
|
+
}
|
|
12558
|
+
function watchJsDialogs(platform2, cdp, targetId) {
|
|
12559
|
+
const state = {};
|
|
12560
|
+
let connecting;
|
|
12561
|
+
let closed = false;
|
|
12562
|
+
const connect2 = async () => {
|
|
12563
|
+
if (!platform2.websocket) return;
|
|
12564
|
+
const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
|
|
12565
|
+
if (!url || closed) return;
|
|
12566
|
+
await attachSocket(platform2, state, url, targetId);
|
|
12567
|
+
};
|
|
12568
|
+
return {
|
|
12569
|
+
async ready() {
|
|
12570
|
+
if (closed || state.channel) return;
|
|
12571
|
+
connecting ??= connect2().finally(() => {
|
|
12572
|
+
connecting = void 0;
|
|
12573
|
+
});
|
|
12574
|
+
let timer;
|
|
12575
|
+
await Promise.race([
|
|
12576
|
+
connecting,
|
|
12577
|
+
new Promise((resolve) => {
|
|
12578
|
+
timer = setTimeout(resolve, DIALOG_READY_MS);
|
|
12579
|
+
})
|
|
12580
|
+
]);
|
|
12581
|
+
clearTimeout(timer);
|
|
12582
|
+
},
|
|
12583
|
+
dialog: () => state.open,
|
|
12584
|
+
stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
|
|
12585
|
+
close() {
|
|
12586
|
+
closed = true;
|
|
12587
|
+
state.channel?.close();
|
|
12588
|
+
}
|
|
12589
|
+
};
|
|
12590
|
+
}
|
|
12591
|
+
function dialogMessage(tab, type) {
|
|
12592
|
+
return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
|
|
12593
|
+
}
|
|
12594
|
+
function timeoutMessage(verb, timeoutMs, tab, watcher) {
|
|
12595
|
+
const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
|
|
12596
|
+
if (!watcher) {
|
|
12597
|
+
return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
|
|
12598
|
+
}
|
|
12599
|
+
if (watcher.stalled()) {
|
|
12600
|
+
return `${head}; tab ${tab} does not answer the DevTools protocol, so a JavaScript dialog opened before mcpcat attached may block it; answer it in the browser, then retry`;
|
|
12601
|
+
}
|
|
12602
|
+
return head;
|
|
12603
|
+
}
|
|
12604
|
+
async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
|
|
12431
12605
|
let proc;
|
|
12432
12606
|
try {
|
|
12433
12607
|
proc = await platform2.spawn({ argv });
|
|
@@ -12471,12 +12645,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12471
12645
|
readStream3(proc.stderr, err),
|
|
12472
12646
|
proc.wait()
|
|
12473
12647
|
]);
|
|
12474
|
-
const
|
|
12648
|
+
const watcher = watch ? watchLoop(watch) : void 0;
|
|
12649
|
+
const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
|
|
12650
|
+
});
|
|
12651
|
+
const result = await Promise.race([work, timer, blocked]);
|
|
12475
12652
|
clearTimeout(timeoutId);
|
|
12653
|
+
watcher?.stop();
|
|
12476
12654
|
if (result === "timeout") {
|
|
12477
12655
|
await proc.kill("SIGTERM");
|
|
12478
12656
|
return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
|
|
12479
12657
|
}
|
|
12658
|
+
if (!Array.isArray(result)) {
|
|
12659
|
+
await proc.kill("SIGTERM");
|
|
12660
|
+
return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
|
|
12661
|
+
}
|
|
12480
12662
|
const [, , exitResult] = result;
|
|
12481
12663
|
const stdout = out.finish();
|
|
12482
12664
|
const stderr = err.finish();
|
|
@@ -12487,7 +12669,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12487
12669
|
cut: stdout.cut
|
|
12488
12670
|
};
|
|
12489
12671
|
}
|
|
12490
|
-
async function
|
|
12672
|
+
async function listTabs(platform2, options) {
|
|
12491
12673
|
const argv = [options.playwrightCli, "tab-list"];
|
|
12492
12674
|
if (options.cdp) argv.push("--cdp", options.cdp);
|
|
12493
12675
|
const result = await runProcess(
|
|
@@ -12514,6 +12696,13 @@ async function resolveWebMcpTab(platform2, options) {
|
|
|
12514
12696
|
if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
|
|
12515
12697
|
return { error: "could not read playwright-cli tab-list" };
|
|
12516
12698
|
}
|
|
12699
|
+
return { rows };
|
|
12700
|
+
}
|
|
12701
|
+
async function resolveWebMcpTab(platform2, options) {
|
|
12702
|
+
const listed = await listTabs(platform2, options);
|
|
12703
|
+
if ("error" in listed) return listed;
|
|
12704
|
+
const { rows } = listed;
|
|
12705
|
+
const active = rows.filter((r) => r.active);
|
|
12517
12706
|
if (active.length === 1) return { tab: active[0].id };
|
|
12518
12707
|
if (rows.length === 1) return { tab: rows[0].id };
|
|
12519
12708
|
return {
|
|
@@ -12576,20 +12765,57 @@ function ambiguityError(name, peers) {
|
|
|
12576
12765
|
`tool "${name}" is ambiguous: ${peers.length} tools on the page match it (${origins}), and playwright-cli runs the first tool by name and cannot select one by origin, so mcpcat refuses the call`
|
|
12577
12766
|
);
|
|
12578
12767
|
}
|
|
12768
|
+
async function blockedNow(watcher, tab) {
|
|
12769
|
+
if (!watcher) return void 0;
|
|
12770
|
+
await watcher.ready();
|
|
12771
|
+
const open2 = watcher.dialog();
|
|
12772
|
+
return open2 ? dialogMessage(tab, open2) : void 0;
|
|
12773
|
+
}
|
|
12774
|
+
async function tabGone(platform2, options) {
|
|
12775
|
+
const { tab } = options;
|
|
12776
|
+
if (!tab) return void 0;
|
|
12777
|
+
const listed = await listTabs(platform2, options);
|
|
12778
|
+
if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
|
|
12779
|
+
return `tab ${tab} is gone (the browser restarted or the tab closed); restart mcpcat serve --webmcp, or pass --tab <targetId> (see playwright-cli tab-list)`;
|
|
12780
|
+
}
|
|
12781
|
+
function hiddenToolMessage(name) {
|
|
12782
|
+
return `tool ${name} exists on the page but is hidden because it is consequential (consequentialHint); restart with mcpcat serve --webmcp --allow-consequential to expose it`;
|
|
12783
|
+
}
|
|
12579
12784
|
function createWebMcpHandler(platform2, options) {
|
|
12580
12785
|
let cache;
|
|
12581
12786
|
const baseArgs = buildBaseArgs(options);
|
|
12582
12787
|
const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
|
|
12788
|
+
const { cdp, tab } = options;
|
|
12789
|
+
const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
|
|
12790
|
+
const watch = watcher ? () => watcher.dialog() : void 0;
|
|
12583
12791
|
async function listRaw(fresh = false) {
|
|
12792
|
+
const blocked = await blockedNow(watcher, tab);
|
|
12793
|
+
if (blocked) return { tools: [], error: blocked };
|
|
12584
12794
|
const now = Date.now();
|
|
12585
12795
|
if (!fresh && cache && now < cache.expiresAt) {
|
|
12586
12796
|
return { tools: cache.tools };
|
|
12587
12797
|
}
|
|
12588
12798
|
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12589
|
-
const result = await runProcess(
|
|
12799
|
+
const result = await runProcess(
|
|
12800
|
+
platform2,
|
|
12801
|
+
argv,
|
|
12802
|
+
void 0,
|
|
12803
|
+
options.callTimeoutMs,
|
|
12804
|
+
listLimit,
|
|
12805
|
+
watch
|
|
12806
|
+
);
|
|
12590
12807
|
if (result.exitCode === -1) {
|
|
12591
12808
|
return { tools: [], error: result.stderr, notFound: true };
|
|
12592
12809
|
}
|
|
12810
|
+
if (result.blocked) {
|
|
12811
|
+
return { tools: [], error: dialogMessage(tab, result.blocked) };
|
|
12812
|
+
}
|
|
12813
|
+
if (result.exitCode === 124) {
|
|
12814
|
+
return {
|
|
12815
|
+
tools: [],
|
|
12816
|
+
error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
|
|
12817
|
+
};
|
|
12818
|
+
}
|
|
12593
12819
|
if (result.exitCode !== 0) {
|
|
12594
12820
|
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12595
12821
|
}
|
|
@@ -12605,6 +12831,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12605
12831
|
if (!Array.isArray(parsed)) {
|
|
12606
12832
|
return { tools: [], error: "webmcp-list did not return an array" };
|
|
12607
12833
|
}
|
|
12834
|
+
if (parsed.length === 0) {
|
|
12835
|
+
const gone2 = await tabGone(platform2, options);
|
|
12836
|
+
if (gone2) return { tools: [], error: gone2 };
|
|
12837
|
+
}
|
|
12608
12838
|
const tools = parsed;
|
|
12609
12839
|
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12610
12840
|
return { tools };
|
|
@@ -12612,11 +12842,13 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12612
12842
|
async function getRpcTools() {
|
|
12613
12843
|
const { tools, error, notFound } = await listRaw();
|
|
12614
12844
|
if (notFound) return null;
|
|
12615
|
-
if (error) return [];
|
|
12845
|
+
if (error) return { tools: [], error };
|
|
12616
12846
|
const rpcTools = [];
|
|
12847
|
+
const hidden = {};
|
|
12617
12848
|
for (const { name, tool, peers } of exposedTools(tools)) {
|
|
12618
12849
|
if (peers.length > 1) continue;
|
|
12619
12850
|
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12851
|
+
hidden[name] = hiddenToolMessage(name);
|
|
12620
12852
|
continue;
|
|
12621
12853
|
}
|
|
12622
12854
|
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
@@ -12627,7 +12859,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12627
12859
|
annotations: mapped
|
|
12628
12860
|
});
|
|
12629
12861
|
}
|
|
12630
|
-
return rpcTools;
|
|
12862
|
+
return { tools: rpcTools, hidden };
|
|
12631
12863
|
}
|
|
12632
12864
|
async function callTool(name, args2) {
|
|
12633
12865
|
const { tools, error } = await listRaw(true);
|
|
@@ -12652,11 +12884,18 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12652
12884
|
argv,
|
|
12653
12885
|
stdinData,
|
|
12654
12886
|
options.callTimeoutMs,
|
|
12655
|
-
options.maxOutput
|
|
12887
|
+
options.maxOutput,
|
|
12888
|
+
watch
|
|
12656
12889
|
);
|
|
12657
12890
|
if (result.exitCode === -1) {
|
|
12658
12891
|
throw new Error(result.stderr || "playwright-cli not found");
|
|
12659
12892
|
}
|
|
12893
|
+
if (result.blocked) {
|
|
12894
|
+
throw new Error(dialogMessage(tab, result.blocked));
|
|
12895
|
+
}
|
|
12896
|
+
if (result.exitCode === 124) {
|
|
12897
|
+
throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
|
|
12898
|
+
}
|
|
12660
12899
|
if (result.exitCode === 2) {
|
|
12661
12900
|
throw new Error(result.stderr || "webmcp-call error");
|
|
12662
12901
|
}
|
|
@@ -12678,7 +12917,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12678
12917
|
function invalidateCache() {
|
|
12679
12918
|
cache = void 0;
|
|
12680
12919
|
}
|
|
12681
|
-
|
|
12920
|
+
function close() {
|
|
12921
|
+
watcher?.close();
|
|
12922
|
+
}
|
|
12923
|
+
return { getRpcTools, callTool, invalidateCache, close };
|
|
12682
12924
|
}
|
|
12683
12925
|
|
|
12684
12926
|
// src/serve/runtime.ts
|
|
@@ -13441,23 +13683,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13441
13683
|
}
|
|
13442
13684
|
return { connectOpts, hiddenValues };
|
|
13443
13685
|
}
|
|
13444
|
-
function resolvePlaywrightCli(platform2, explicit) {
|
|
13686
|
+
async function resolvePlaywrightCli(platform2, explicit) {
|
|
13445
13687
|
if (explicit) return explicit;
|
|
13446
13688
|
if (platform2.kind === "kernel") return "playwright-cli";
|
|
13447
13689
|
const pathEnv = platform2.env.PATH ?? "";
|
|
13448
|
-
const
|
|
13449
|
-
|
|
13690
|
+
const windows = pathEnv.includes(";");
|
|
13691
|
+
const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
|
|
13692
|
+
for (const dir of pathEnv.split(windows ? ";" : ":")) {
|
|
13450
13693
|
if (!dir) continue;
|
|
13451
|
-
|
|
13694
|
+
for (const name of names) {
|
|
13695
|
+
const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
|
|
13696
|
+
if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
|
|
13697
|
+
}
|
|
13452
13698
|
}
|
|
13453
13699
|
return void 0;
|
|
13454
13700
|
}
|
|
13701
|
+
async function listenOrReport(platform2, options, label) {
|
|
13702
|
+
try {
|
|
13703
|
+
return await platform2.listen({ host: options.host, port: options.port });
|
|
13704
|
+
} catch (err) {
|
|
13705
|
+
const { code: code2, message } = err;
|
|
13706
|
+
const reason = code2 === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
|
|
13707
|
+
const host = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
13708
|
+
await platform2.write(
|
|
13709
|
+
2,
|
|
13710
|
+
sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
|
|
13711
|
+
`)
|
|
13712
|
+
);
|
|
13713
|
+
return 3;
|
|
13714
|
+
}
|
|
13715
|
+
}
|
|
13455
13716
|
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
13456
|
-
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13717
|
+
const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13457
13718
|
if (!cliPath) {
|
|
13458
13719
|
await platform2.write(
|
|
13459
13720
|
2,
|
|
13460
|
-
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13721
|
+
"mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13461
13722
|
);
|
|
13462
13723
|
return 3;
|
|
13463
13724
|
}
|
|
@@ -13492,6 +13753,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13492
13753
|
callTimeoutMs: options.callTimeout,
|
|
13493
13754
|
maxOutput: options.maxOutput
|
|
13494
13755
|
});
|
|
13756
|
+
try {
|
|
13757
|
+
return await serveWebMcpHandler(
|
|
13758
|
+
platform2,
|
|
13759
|
+
options,
|
|
13760
|
+
handler,
|
|
13761
|
+
version,
|
|
13762
|
+
resolvedBearer,
|
|
13763
|
+
generatedBearer
|
|
13764
|
+
);
|
|
13765
|
+
} finally {
|
|
13766
|
+
handler.close();
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
|
|
13495
13770
|
const probe = await handler.getRpcTools();
|
|
13496
13771
|
if (probe === null) {
|
|
13497
13772
|
await platform2.write(
|
|
@@ -13501,7 +13776,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13501
13776
|
return 3;
|
|
13502
13777
|
}
|
|
13503
13778
|
const serverName = "mcpcat-webmcp";
|
|
13504
|
-
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
13779
|
+
const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
|
|
13505
13780
|
const callTool = (name, args2) => handler.callTool(name, args2);
|
|
13506
13781
|
if (options.transport === "stdio") {
|
|
13507
13782
|
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
@@ -13532,8 +13807,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
|
|
|
13532
13807
|
const line = buffer.slice(0, nlIdx).trim();
|
|
13533
13808
|
buffer = buffer.slice(nlIdx + 1);
|
|
13534
13809
|
if (!line) continue;
|
|
13535
|
-
const
|
|
13536
|
-
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
13810
|
+
const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
|
|
13537
13811
|
if (outcome.status === 202) continue;
|
|
13538
13812
|
const writer = platform2.stdout.getWriter();
|
|
13539
13813
|
await writer.write(enc.encode(`${outcome.body}
|
|
@@ -13560,7 +13834,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13560
13834
|
return 2;
|
|
13561
13835
|
}
|
|
13562
13836
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13563
|
-
const listener = await platform2
|
|
13837
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
|
|
13838
|
+
if (typeof listener === "number") return listener;
|
|
13564
13839
|
const actualPort = listener.port;
|
|
13565
13840
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13566
13841
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13666,8 +13941,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13666
13941
|
};
|
|
13667
13942
|
}
|
|
13668
13943
|
}
|
|
13669
|
-
const
|
|
13670
|
-
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
13944
|
+
const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
|
|
13671
13945
|
return {
|
|
13672
13946
|
status: outcome.status,
|
|
13673
13947
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
@@ -13755,10 +14029,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13755
14029
|
return 2;
|
|
13756
14030
|
}
|
|
13757
14031
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13758
|
-
const listener = await platform2
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
14032
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
|
|
14033
|
+
if (typeof listener === "number") {
|
|
14034
|
+
await proxy.shutdown();
|
|
14035
|
+
return listener;
|
|
14036
|
+
}
|
|
13762
14037
|
const actualPort = listener.port;
|
|
13763
14038
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13764
14039
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13939,7 +14214,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13939
14214
|
return 2;
|
|
13940
14215
|
}
|
|
13941
14216
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13942
|
-
const listener = await platform2
|
|
14217
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve");
|
|
14218
|
+
if (typeof listener === "number") return listener;
|
|
13943
14219
|
const actualPort = listener.port;
|
|
13944
14220
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13945
14221
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -16071,6 +16347,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
16071
16347
|
if (toolArgs.length > 0) {
|
|
16072
16348
|
const tools = await conn.listTools();
|
|
16073
16349
|
const tool = tools.find((t) => t.name === toolName);
|
|
16350
|
+
const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
|
|
16351
|
+
if (hidden) {
|
|
16352
|
+
await writeErr(platform2, opts.json ?? false, "usage", hidden);
|
|
16353
|
+
return EXIT_USAGE;
|
|
16354
|
+
}
|
|
16074
16355
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
16075
16356
|
const skipRequired = opts.noValidate || !!dataArg;
|
|
16076
16357
|
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
@@ -17225,6 +17506,34 @@ function nodeListen(options) {
|
|
|
17225
17506
|
});
|
|
17226
17507
|
});
|
|
17227
17508
|
}
|
|
17509
|
+
function hostWebSocket(url) {
|
|
17510
|
+
return new Promise((resolve, reject) => {
|
|
17511
|
+
const ws = new WebSocket(url);
|
|
17512
|
+
ws.binaryType = "arraybuffer";
|
|
17513
|
+
let feed;
|
|
17514
|
+
const readable = new ReadableStream({
|
|
17515
|
+
start(controller) {
|
|
17516
|
+
feed = controller;
|
|
17517
|
+
}
|
|
17518
|
+
});
|
|
17519
|
+
ws.onclose = () => feed.close();
|
|
17520
|
+
ws.onerror = () => reject(new Error(`websocket ${url} failed`));
|
|
17521
|
+
ws.onmessage = (event) => {
|
|
17522
|
+
feed.enqueue(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
|
|
17523
|
+
};
|
|
17524
|
+
ws.onopen = () => {
|
|
17525
|
+
resolve({
|
|
17526
|
+
readable,
|
|
17527
|
+
writable: new WritableStream({
|
|
17528
|
+
write(chunk) {
|
|
17529
|
+
ws.send(chunk);
|
|
17530
|
+
}
|
|
17531
|
+
}),
|
|
17532
|
+
close: () => ws.close()
|
|
17533
|
+
});
|
|
17534
|
+
};
|
|
17535
|
+
});
|
|
17536
|
+
}
|
|
17228
17537
|
function createHostPlatform() {
|
|
17229
17538
|
const encoder2 = new TextEncoder();
|
|
17230
17539
|
return {
|
|
@@ -17232,6 +17541,7 @@ function createHostPlatform() {
|
|
|
17232
17541
|
fetch: globalThis.fetch,
|
|
17233
17542
|
spawn: nodeSpawn,
|
|
17234
17543
|
listen: nodeListen,
|
|
17544
|
+
websocket: hostWebSocket,
|
|
17235
17545
|
fs: hostFs,
|
|
17236
17546
|
env: process.env,
|
|
17237
17547
|
cwd: process.cwd(),
|
|
@@ -17269,5 +17579,5 @@ try {
|
|
|
17269
17579
|
});
|
|
17270
17580
|
} catch {
|
|
17271
17581
|
}
|
|
17272
|
-
var code = await run(platform, "0.20.
|
|
17582
|
+
var code = await run(platform, "0.20.3", args);
|
|
17273
17583
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -8106,8 +8106,19 @@ function buildServerInfo(discoverResult2) {
|
|
|
8106
8106
|
instructions: discoverResult2?.instructions
|
|
8107
8107
|
};
|
|
8108
8108
|
}
|
|
8109
|
+
function readHiddenTools(result) {
|
|
8110
|
+
const meta = result?._meta;
|
|
8111
|
+
const hidden = meta?.["io.mcpcat/hidden"];
|
|
8112
|
+
const out = {};
|
|
8113
|
+
if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
|
|
8114
|
+
for (const [name, why] of Object.entries(hidden)) {
|
|
8115
|
+
if (typeof why === "string") out[name] = why;
|
|
8116
|
+
}
|
|
8117
|
+
return out;
|
|
8118
|
+
}
|
|
8109
8119
|
function buildStatelessMethods(requestFn, discoverResult2) {
|
|
8110
8120
|
let serverInfoCache;
|
|
8121
|
+
let hidden = {};
|
|
8111
8122
|
return {
|
|
8112
8123
|
async info() {
|
|
8113
8124
|
if (serverInfoCache) return serverInfoCache;
|
|
@@ -8115,7 +8126,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
|
|
|
8115
8126
|
return serverInfoCache;
|
|
8116
8127
|
},
|
|
8117
8128
|
async listTools() {
|
|
8118
|
-
|
|
8129
|
+
const result = await requestFn("tools/list", {});
|
|
8130
|
+
hidden = readHiddenTools(result);
|
|
8131
|
+
return result?.tools ?? [];
|
|
8132
|
+
},
|
|
8133
|
+
hiddenTool(name) {
|
|
8134
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8119
8135
|
},
|
|
8120
8136
|
async callTool(name, args, _callOptions) {
|
|
8121
8137
|
const params = {
|
|
@@ -8555,12 +8571,18 @@ function buildServerInfo2(discoverResult2) {
|
|
|
8555
8571
|
};
|
|
8556
8572
|
}
|
|
8557
8573
|
function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
8574
|
+
let hidden = {};
|
|
8558
8575
|
return {
|
|
8559
8576
|
async info() {
|
|
8560
8577
|
return buildServerInfo2(discoverResult2);
|
|
8561
8578
|
},
|
|
8562
8579
|
async listTools() {
|
|
8563
|
-
|
|
8580
|
+
const result = await rpc.request("tools/list", {});
|
|
8581
|
+
hidden = readHiddenTools(result);
|
|
8582
|
+
return result?.tools ?? [];
|
|
8583
|
+
},
|
|
8584
|
+
hiddenTool(name) {
|
|
8585
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8564
8586
|
},
|
|
8565
8587
|
async callTool(name, args, callOptions) {
|
|
8566
8588
|
const params = { name, arguments: args ?? {} };
|
|
@@ -8762,7 +8784,7 @@ function readLine(platform2) {
|
|
|
8762
8784
|
return new Promise((resolve2, reject) => {
|
|
8763
8785
|
const reader = platform2.stdin.getReader();
|
|
8764
8786
|
const chunks = [];
|
|
8765
|
-
function
|
|
8787
|
+
function pump2() {
|
|
8766
8788
|
reader.read().then(({ done, value }) => {
|
|
8767
8789
|
if (done) {
|
|
8768
8790
|
reader.releaseLock();
|
|
@@ -8776,10 +8798,10 @@ function readLine(platform2) {
|
|
|
8776
8798
|
resolve2(text.split("\n")[0]);
|
|
8777
8799
|
return;
|
|
8778
8800
|
}
|
|
8779
|
-
|
|
8801
|
+
pump2();
|
|
8780
8802
|
}, reject);
|
|
8781
8803
|
}
|
|
8782
|
-
|
|
8804
|
+
pump2();
|
|
8783
8805
|
});
|
|
8784
8806
|
}
|
|
8785
8807
|
function concatChunks(chunks) {
|
|
@@ -11658,7 +11680,7 @@ async function writeResponse(writer, response) {
|
|
|
11658
11680
|
var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
11659
11681
|
var OUTPUT_CAP = 1048576;
|
|
11660
11682
|
var JSON_CT = "application/json; charset=utf-8";
|
|
11661
|
-
async function handleJsonRpc(raw,
|
|
11683
|
+
async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
|
|
11662
11684
|
let parsed;
|
|
11663
11685
|
try {
|
|
11664
11686
|
parsed = JSON.parse(raw);
|
|
@@ -11675,16 +11697,26 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
|
|
|
11675
11697
|
if (method === "initialize")
|
|
11676
11698
|
return rpcOk(id, initializeResult(params, serverName, serverVersion));
|
|
11677
11699
|
if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
|
|
11678
|
-
if (method === "tools/list") return rpcOk(id, { tools });
|
|
11679
11700
|
if (method === "ping") return rpcOk(id, {});
|
|
11680
|
-
if (method === "tools/
|
|
11701
|
+
if (method === "tools/list" || method === "tools/call") {
|
|
11702
|
+
const listing = Array.isArray(source) ? { tools: source } : await source();
|
|
11703
|
+
if (listing.error) return rpcError(id, -32e3, listing.error);
|
|
11704
|
+
if (method === "tools/call") return toolCall(id, params, listing, call);
|
|
11705
|
+
const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
|
|
11706
|
+
return rpcOk(id, {
|
|
11707
|
+
tools: listing.tools,
|
|
11708
|
+
...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
|
|
11709
|
+
});
|
|
11710
|
+
}
|
|
11681
11711
|
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
11682
11712
|
}
|
|
11683
|
-
async function toolCall(id, params, tools, call) {
|
|
11713
|
+
async function toolCall(id, params, { tools, hidden }, call) {
|
|
11684
11714
|
const name = params && typeof params === "object" ? readString(params, "name") : void 0;
|
|
11685
11715
|
if (!name) return rpcError(id, -32602, "tools/call requires a name");
|
|
11686
|
-
if (!tools.some((tool) => tool.name === name))
|
|
11687
|
-
|
|
11716
|
+
if (!tools.some((tool) => tool.name === name)) {
|
|
11717
|
+
const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
11718
|
+
return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
|
|
11719
|
+
}
|
|
11688
11720
|
const args = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
11689
11721
|
try {
|
|
11690
11722
|
const result = await call(name, args);
|
|
@@ -12048,7 +12080,149 @@ ${tail}`,
|
|
|
12048
12080
|
}
|
|
12049
12081
|
};
|
|
12050
12082
|
}
|
|
12051
|
-
|
|
12083
|
+
var DIALOG_POLL_MS = 250;
|
|
12084
|
+
var DIALOG_READY_MS = 1e3;
|
|
12085
|
+
var DIALOG_STALL_MS = 1e3;
|
|
12086
|
+
function watchLoop(watch) {
|
|
12087
|
+
let timer;
|
|
12088
|
+
const result = new Promise((resolve2) => {
|
|
12089
|
+
timer = setInterval(() => {
|
|
12090
|
+
const found = watch();
|
|
12091
|
+
if (found) {
|
|
12092
|
+
clearInterval(timer);
|
|
12093
|
+
resolve2(found);
|
|
12094
|
+
}
|
|
12095
|
+
}, DIALOG_POLL_MS);
|
|
12096
|
+
});
|
|
12097
|
+
return {
|
|
12098
|
+
result,
|
|
12099
|
+
stop() {
|
|
12100
|
+
clearInterval(timer);
|
|
12101
|
+
}
|
|
12102
|
+
};
|
|
12103
|
+
}
|
|
12104
|
+
async function browserSocketUrl(platform2, cdp) {
|
|
12105
|
+
if (/^wss?:\/\//i.test(cdp)) return cdp;
|
|
12106
|
+
if (!/^https?:\/\//i.test(cdp)) return void 0;
|
|
12107
|
+
const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
|
|
12108
|
+
signal: AbortSignal.timeout(DIALOG_READY_MS)
|
|
12109
|
+
});
|
|
12110
|
+
const body = await res.json();
|
|
12111
|
+
return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
|
|
12112
|
+
}
|
|
12113
|
+
function parseCdp(data) {
|
|
12114
|
+
try {
|
|
12115
|
+
return JSON.parse(
|
|
12116
|
+
typeof data === "string" ? data : new TextDecoder().decode(data)
|
|
12117
|
+
);
|
|
12118
|
+
} catch {
|
|
12119
|
+
return void 0;
|
|
12120
|
+
}
|
|
12121
|
+
}
|
|
12122
|
+
function applyCdp(state, channel, send, msg, settle) {
|
|
12123
|
+
if (msg.method === "Page.javascriptDialogOpening") {
|
|
12124
|
+
state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
|
|
12125
|
+
} else if (msg.method === "Page.javascriptDialogClosed") {
|
|
12126
|
+
state.open = void 0;
|
|
12127
|
+
} else if (msg.method === "Target.detachedFromTarget") {
|
|
12128
|
+
channel.close();
|
|
12129
|
+
} else if (msg.id === 1) {
|
|
12130
|
+
const sessionId = msg.result?.sessionId;
|
|
12131
|
+
if (typeof sessionId !== "string") {
|
|
12132
|
+
channel.close();
|
|
12133
|
+
return;
|
|
12134
|
+
}
|
|
12135
|
+
state.enableSentAt = Date.now();
|
|
12136
|
+
send({ id: 2, sessionId, method: "Page.enable" });
|
|
12137
|
+
} else if (msg.id === 2) {
|
|
12138
|
+
state.enableSentAt = void 0;
|
|
12139
|
+
settle();
|
|
12140
|
+
}
|
|
12141
|
+
}
|
|
12142
|
+
async function pump(state, channel, send, settle) {
|
|
12143
|
+
const reader = channel.readable.getReader();
|
|
12144
|
+
try {
|
|
12145
|
+
for (; ; ) {
|
|
12146
|
+
const { done, value } = await reader.read();
|
|
12147
|
+
if (done) return;
|
|
12148
|
+
const msg = parseCdp(value);
|
|
12149
|
+
if (msg) applyCdp(state, channel, send, msg, settle);
|
|
12150
|
+
}
|
|
12151
|
+
} catch {
|
|
12152
|
+
return;
|
|
12153
|
+
}
|
|
12154
|
+
}
|
|
12155
|
+
async function attachSocket(platform2, state, url, targetId) {
|
|
12156
|
+
const channel = await platform2.websocket(url).catch(() => void 0);
|
|
12157
|
+
if (!channel) return;
|
|
12158
|
+
state.channel = channel;
|
|
12159
|
+
const writer = channel.writable.getWriter();
|
|
12160
|
+
const send = (message) => {
|
|
12161
|
+
writer.write(JSON.stringify(message)).catch(() => channel.close());
|
|
12162
|
+
};
|
|
12163
|
+
let settle;
|
|
12164
|
+
const settled = new Promise((resolve2) => {
|
|
12165
|
+
settle = resolve2;
|
|
12166
|
+
});
|
|
12167
|
+
void pump(state, channel, send, settle).then(() => {
|
|
12168
|
+
if (state.channel === channel) {
|
|
12169
|
+
state.channel = void 0;
|
|
12170
|
+
state.open = void 0;
|
|
12171
|
+
state.enableSentAt = void 0;
|
|
12172
|
+
}
|
|
12173
|
+
channel.close();
|
|
12174
|
+
settle();
|
|
12175
|
+
});
|
|
12176
|
+
send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
|
|
12177
|
+
await settled;
|
|
12178
|
+
}
|
|
12179
|
+
function watchJsDialogs(platform2, cdp, targetId) {
|
|
12180
|
+
const state = {};
|
|
12181
|
+
let connecting;
|
|
12182
|
+
let closed = false;
|
|
12183
|
+
const connect2 = async () => {
|
|
12184
|
+
if (!platform2.websocket) return;
|
|
12185
|
+
const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
|
|
12186
|
+
if (!url || closed) return;
|
|
12187
|
+
await attachSocket(platform2, state, url, targetId);
|
|
12188
|
+
};
|
|
12189
|
+
return {
|
|
12190
|
+
async ready() {
|
|
12191
|
+
if (closed || state.channel) return;
|
|
12192
|
+
connecting ??= connect2().finally(() => {
|
|
12193
|
+
connecting = void 0;
|
|
12194
|
+
});
|
|
12195
|
+
let timer;
|
|
12196
|
+
await Promise.race([
|
|
12197
|
+
connecting,
|
|
12198
|
+
new Promise((resolve2) => {
|
|
12199
|
+
timer = setTimeout(resolve2, DIALOG_READY_MS);
|
|
12200
|
+
})
|
|
12201
|
+
]);
|
|
12202
|
+
clearTimeout(timer);
|
|
12203
|
+
},
|
|
12204
|
+
dialog: () => state.open,
|
|
12205
|
+
stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
|
|
12206
|
+
close() {
|
|
12207
|
+
closed = true;
|
|
12208
|
+
state.channel?.close();
|
|
12209
|
+
}
|
|
12210
|
+
};
|
|
12211
|
+
}
|
|
12212
|
+
function dialogMessage(tab, type) {
|
|
12213
|
+
return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
|
|
12214
|
+
}
|
|
12215
|
+
function timeoutMessage(verb, timeoutMs, tab, watcher) {
|
|
12216
|
+
const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
|
|
12217
|
+
if (!watcher) {
|
|
12218
|
+
return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
|
|
12219
|
+
}
|
|
12220
|
+
if (watcher.stalled()) {
|
|
12221
|
+
return `${head}; tab ${tab} does not answer the DevTools protocol, so a JavaScript dialog opened before mcpcat attached may block it; answer it in the browser, then retry`;
|
|
12222
|
+
}
|
|
12223
|
+
return head;
|
|
12224
|
+
}
|
|
12225
|
+
async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
|
|
12052
12226
|
let proc;
|
|
12053
12227
|
try {
|
|
12054
12228
|
proc = await platform2.spawn({ argv });
|
|
@@ -12092,12 +12266,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12092
12266
|
readStream3(proc.stderr, err),
|
|
12093
12267
|
proc.wait()
|
|
12094
12268
|
]);
|
|
12095
|
-
const
|
|
12269
|
+
const watcher = watch ? watchLoop(watch) : void 0;
|
|
12270
|
+
const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
|
|
12271
|
+
});
|
|
12272
|
+
const result = await Promise.race([work, timer, blocked]);
|
|
12096
12273
|
clearTimeout(timeoutId);
|
|
12274
|
+
watcher?.stop();
|
|
12097
12275
|
if (result === "timeout") {
|
|
12098
12276
|
await proc.kill("SIGTERM");
|
|
12099
12277
|
return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
|
|
12100
12278
|
}
|
|
12279
|
+
if (!Array.isArray(result)) {
|
|
12280
|
+
await proc.kill("SIGTERM");
|
|
12281
|
+
return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
|
|
12282
|
+
}
|
|
12101
12283
|
const [, , exitResult] = result;
|
|
12102
12284
|
const stdout = out.finish();
|
|
12103
12285
|
const stderr = err.finish();
|
|
@@ -12108,7 +12290,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12108
12290
|
cut: stdout.cut
|
|
12109
12291
|
};
|
|
12110
12292
|
}
|
|
12111
|
-
async function
|
|
12293
|
+
async function listTabs(platform2, options) {
|
|
12112
12294
|
const argv = [options.playwrightCli, "tab-list"];
|
|
12113
12295
|
if (options.cdp) argv.push("--cdp", options.cdp);
|
|
12114
12296
|
const result = await runProcess(
|
|
@@ -12135,6 +12317,13 @@ async function resolveWebMcpTab(platform2, options) {
|
|
|
12135
12317
|
if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
|
|
12136
12318
|
return { error: "could not read playwright-cli tab-list" };
|
|
12137
12319
|
}
|
|
12320
|
+
return { rows };
|
|
12321
|
+
}
|
|
12322
|
+
async function resolveWebMcpTab(platform2, options) {
|
|
12323
|
+
const listed = await listTabs(platform2, options);
|
|
12324
|
+
if ("error" in listed) return listed;
|
|
12325
|
+
const { rows } = listed;
|
|
12326
|
+
const active = rows.filter((r) => r.active);
|
|
12138
12327
|
if (active.length === 1) return { tab: active[0].id };
|
|
12139
12328
|
if (rows.length === 1) return { tab: rows[0].id };
|
|
12140
12329
|
return {
|
|
@@ -12197,20 +12386,57 @@ function ambiguityError(name, peers) {
|
|
|
12197
12386
|
`tool "${name}" is ambiguous: ${peers.length} tools on the page match it (${origins}), and playwright-cli runs the first tool by name and cannot select one by origin, so mcpcat refuses the call`
|
|
12198
12387
|
);
|
|
12199
12388
|
}
|
|
12389
|
+
async function blockedNow(watcher, tab) {
|
|
12390
|
+
if (!watcher) return void 0;
|
|
12391
|
+
await watcher.ready();
|
|
12392
|
+
const open = watcher.dialog();
|
|
12393
|
+
return open ? dialogMessage(tab, open) : void 0;
|
|
12394
|
+
}
|
|
12395
|
+
async function tabGone(platform2, options) {
|
|
12396
|
+
const { tab } = options;
|
|
12397
|
+
if (!tab) return void 0;
|
|
12398
|
+
const listed = await listTabs(platform2, options);
|
|
12399
|
+
if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
|
|
12400
|
+
return `tab ${tab} is gone (the browser restarted or the tab closed); restart mcpcat serve --webmcp, or pass --tab <targetId> (see playwright-cli tab-list)`;
|
|
12401
|
+
}
|
|
12402
|
+
function hiddenToolMessage(name) {
|
|
12403
|
+
return `tool ${name} exists on the page but is hidden because it is consequential (consequentialHint); restart with mcpcat serve --webmcp --allow-consequential to expose it`;
|
|
12404
|
+
}
|
|
12200
12405
|
function createWebMcpHandler(platform2, options) {
|
|
12201
12406
|
let cache;
|
|
12202
12407
|
const baseArgs = buildBaseArgs(options);
|
|
12203
12408
|
const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
|
|
12409
|
+
const { cdp, tab } = options;
|
|
12410
|
+
const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
|
|
12411
|
+
const watch = watcher ? () => watcher.dialog() : void 0;
|
|
12204
12412
|
async function listRaw(fresh = false) {
|
|
12413
|
+
const blocked = await blockedNow(watcher, tab);
|
|
12414
|
+
if (blocked) return { tools: [], error: blocked };
|
|
12205
12415
|
const now = Date.now();
|
|
12206
12416
|
if (!fresh && cache && now < cache.expiresAt) {
|
|
12207
12417
|
return { tools: cache.tools };
|
|
12208
12418
|
}
|
|
12209
12419
|
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12210
|
-
const result = await runProcess(
|
|
12420
|
+
const result = await runProcess(
|
|
12421
|
+
platform2,
|
|
12422
|
+
argv,
|
|
12423
|
+
void 0,
|
|
12424
|
+
options.callTimeoutMs,
|
|
12425
|
+
listLimit,
|
|
12426
|
+
watch
|
|
12427
|
+
);
|
|
12211
12428
|
if (result.exitCode === -1) {
|
|
12212
12429
|
return { tools: [], error: result.stderr, notFound: true };
|
|
12213
12430
|
}
|
|
12431
|
+
if (result.blocked) {
|
|
12432
|
+
return { tools: [], error: dialogMessage(tab, result.blocked) };
|
|
12433
|
+
}
|
|
12434
|
+
if (result.exitCode === 124) {
|
|
12435
|
+
return {
|
|
12436
|
+
tools: [],
|
|
12437
|
+
error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
|
|
12438
|
+
};
|
|
12439
|
+
}
|
|
12214
12440
|
if (result.exitCode !== 0) {
|
|
12215
12441
|
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12216
12442
|
}
|
|
@@ -12226,6 +12452,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12226
12452
|
if (!Array.isArray(parsed)) {
|
|
12227
12453
|
return { tools: [], error: "webmcp-list did not return an array" };
|
|
12228
12454
|
}
|
|
12455
|
+
if (parsed.length === 0) {
|
|
12456
|
+
const gone2 = await tabGone(platform2, options);
|
|
12457
|
+
if (gone2) return { tools: [], error: gone2 };
|
|
12458
|
+
}
|
|
12229
12459
|
const tools = parsed;
|
|
12230
12460
|
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12231
12461
|
return { tools };
|
|
@@ -12233,11 +12463,13 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12233
12463
|
async function getRpcTools() {
|
|
12234
12464
|
const { tools, error, notFound } = await listRaw();
|
|
12235
12465
|
if (notFound) return null;
|
|
12236
|
-
if (error) return [];
|
|
12466
|
+
if (error) return { tools: [], error };
|
|
12237
12467
|
const rpcTools = [];
|
|
12468
|
+
const hidden = {};
|
|
12238
12469
|
for (const { name, tool, peers } of exposedTools(tools)) {
|
|
12239
12470
|
if (peers.length > 1) continue;
|
|
12240
12471
|
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12472
|
+
hidden[name] = hiddenToolMessage(name);
|
|
12241
12473
|
continue;
|
|
12242
12474
|
}
|
|
12243
12475
|
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
@@ -12248,7 +12480,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12248
12480
|
annotations: mapped
|
|
12249
12481
|
});
|
|
12250
12482
|
}
|
|
12251
|
-
return rpcTools;
|
|
12483
|
+
return { tools: rpcTools, hidden };
|
|
12252
12484
|
}
|
|
12253
12485
|
async function callTool(name, args) {
|
|
12254
12486
|
const { tools, error } = await listRaw(true);
|
|
@@ -12273,11 +12505,18 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12273
12505
|
argv,
|
|
12274
12506
|
stdinData,
|
|
12275
12507
|
options.callTimeoutMs,
|
|
12276
|
-
options.maxOutput
|
|
12508
|
+
options.maxOutput,
|
|
12509
|
+
watch
|
|
12277
12510
|
);
|
|
12278
12511
|
if (result.exitCode === -1) {
|
|
12279
12512
|
throw new Error(result.stderr || "playwright-cli not found");
|
|
12280
12513
|
}
|
|
12514
|
+
if (result.blocked) {
|
|
12515
|
+
throw new Error(dialogMessage(tab, result.blocked));
|
|
12516
|
+
}
|
|
12517
|
+
if (result.exitCode === 124) {
|
|
12518
|
+
throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
|
|
12519
|
+
}
|
|
12281
12520
|
if (result.exitCode === 2) {
|
|
12282
12521
|
throw new Error(result.stderr || "webmcp-call error");
|
|
12283
12522
|
}
|
|
@@ -12299,7 +12538,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12299
12538
|
function invalidateCache() {
|
|
12300
12539
|
cache = void 0;
|
|
12301
12540
|
}
|
|
12302
|
-
|
|
12541
|
+
function close() {
|
|
12542
|
+
watcher?.close();
|
|
12543
|
+
}
|
|
12544
|
+
return { getRpcTools, callTool, invalidateCache, close };
|
|
12303
12545
|
}
|
|
12304
12546
|
|
|
12305
12547
|
// src/serve/runtime.ts
|
|
@@ -13062,23 +13304,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13062
13304
|
}
|
|
13063
13305
|
return { connectOpts, hiddenValues };
|
|
13064
13306
|
}
|
|
13065
|
-
function resolvePlaywrightCli(platform2, explicit) {
|
|
13307
|
+
async function resolvePlaywrightCli(platform2, explicit) {
|
|
13066
13308
|
if (explicit) return explicit;
|
|
13067
13309
|
if (platform2.kind === "kernel") return "playwright-cli";
|
|
13068
13310
|
const pathEnv = platform2.env.PATH ?? "";
|
|
13069
|
-
const
|
|
13070
|
-
|
|
13311
|
+
const windows = pathEnv.includes(";");
|
|
13312
|
+
const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
|
|
13313
|
+
for (const dir of pathEnv.split(windows ? ";" : ":")) {
|
|
13071
13314
|
if (!dir) continue;
|
|
13072
|
-
|
|
13315
|
+
for (const name of names) {
|
|
13316
|
+
const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
|
|
13317
|
+
if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
|
|
13318
|
+
}
|
|
13073
13319
|
}
|
|
13074
13320
|
return void 0;
|
|
13075
13321
|
}
|
|
13322
|
+
async function listenOrReport(platform2, options, label) {
|
|
13323
|
+
try {
|
|
13324
|
+
return await platform2.listen({ host: options.host, port: options.port });
|
|
13325
|
+
} catch (err) {
|
|
13326
|
+
const { code, message } = err;
|
|
13327
|
+
const reason = code === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
|
|
13328
|
+
const host = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
13329
|
+
await platform2.write(
|
|
13330
|
+
2,
|
|
13331
|
+
sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
|
|
13332
|
+
`)
|
|
13333
|
+
);
|
|
13334
|
+
return 3;
|
|
13335
|
+
}
|
|
13336
|
+
}
|
|
13076
13337
|
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
13077
|
-
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13338
|
+
const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13078
13339
|
if (!cliPath) {
|
|
13079
13340
|
await platform2.write(
|
|
13080
13341
|
2,
|
|
13081
|
-
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13342
|
+
"mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13082
13343
|
);
|
|
13083
13344
|
return 3;
|
|
13084
13345
|
}
|
|
@@ -13113,6 +13374,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13113
13374
|
callTimeoutMs: options.callTimeout,
|
|
13114
13375
|
maxOutput: options.maxOutput
|
|
13115
13376
|
});
|
|
13377
|
+
try {
|
|
13378
|
+
return await serveWebMcpHandler(
|
|
13379
|
+
platform2,
|
|
13380
|
+
options,
|
|
13381
|
+
handler,
|
|
13382
|
+
version,
|
|
13383
|
+
resolvedBearer,
|
|
13384
|
+
generatedBearer
|
|
13385
|
+
);
|
|
13386
|
+
} finally {
|
|
13387
|
+
handler.close();
|
|
13388
|
+
}
|
|
13389
|
+
}
|
|
13390
|
+
async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
|
|
13116
13391
|
const probe = await handler.getRpcTools();
|
|
13117
13392
|
if (probe === null) {
|
|
13118
13393
|
await platform2.write(
|
|
@@ -13122,7 +13397,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13122
13397
|
return 3;
|
|
13123
13398
|
}
|
|
13124
13399
|
const serverName = "mcpcat-webmcp";
|
|
13125
|
-
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
13400
|
+
const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
|
|
13126
13401
|
const callTool = (name, args) => handler.callTool(name, args);
|
|
13127
13402
|
if (options.transport === "stdio") {
|
|
13128
13403
|
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
@@ -13153,8 +13428,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
|
|
|
13153
13428
|
const line = buffer.slice(0, nlIdx).trim();
|
|
13154
13429
|
buffer = buffer.slice(nlIdx + 1);
|
|
13155
13430
|
if (!line) continue;
|
|
13156
|
-
const
|
|
13157
|
-
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
13431
|
+
const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
|
|
13158
13432
|
if (outcome.status === 202) continue;
|
|
13159
13433
|
const writer = platform2.stdout.getWriter();
|
|
13160
13434
|
await writer.write(enc.encode(`${outcome.body}
|
|
@@ -13181,7 +13455,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13181
13455
|
return 2;
|
|
13182
13456
|
}
|
|
13183
13457
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13184
|
-
const listener = await platform2
|
|
13458
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
|
|
13459
|
+
if (typeof listener === "number") return listener;
|
|
13185
13460
|
const actualPort = listener.port;
|
|
13186
13461
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13187
13462
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13287,8 +13562,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13287
13562
|
};
|
|
13288
13563
|
}
|
|
13289
13564
|
}
|
|
13290
|
-
const
|
|
13291
|
-
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
13565
|
+
const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
|
|
13292
13566
|
return {
|
|
13293
13567
|
status: outcome.status,
|
|
13294
13568
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
@@ -13376,10 +13650,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13376
13650
|
return 2;
|
|
13377
13651
|
}
|
|
13378
13652
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13379
|
-
const listener = await platform2
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13653
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
|
|
13654
|
+
if (typeof listener === "number") {
|
|
13655
|
+
await proxy.shutdown();
|
|
13656
|
+
return listener;
|
|
13657
|
+
}
|
|
13383
13658
|
const actualPort = listener.port;
|
|
13384
13659
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13385
13660
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13560,7 +13835,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13560
13835
|
return 2;
|
|
13561
13836
|
}
|
|
13562
13837
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13563
|
-
const listener = await platform2
|
|
13838
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve");
|
|
13839
|
+
if (typeof listener === "number") return listener;
|
|
13564
13840
|
const actualPort = listener.port;
|
|
13565
13841
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13566
13842
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -15692,6 +15968,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
15692
15968
|
if (toolArgs.length > 0) {
|
|
15693
15969
|
const tools = await conn.listTools();
|
|
15694
15970
|
const tool = tools.find((t) => t.name === toolName);
|
|
15971
|
+
const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
|
|
15972
|
+
if (hidden) {
|
|
15973
|
+
await writeErr(platform2, opts.json ?? false, "usage", hidden);
|
|
15974
|
+
return EXIT_USAGE;
|
|
15975
|
+
}
|
|
15695
15976
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
15696
15977
|
const skipRequired = opts.noValidate || !!dataArg;
|
|
15697
15978
|
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
@@ -16956,6 +17237,7 @@ function createKernelPlatform(ctx, options) {
|
|
|
16956
17237
|
fetch: createKernelFetch(ctx),
|
|
16957
17238
|
spawn: (options2) => kernelSpawn(ctx, options2),
|
|
16958
17239
|
listen: (options2) => kernelListen(ctx, options2),
|
|
17240
|
+
...ctx.websocket ? { websocket: (url) => ctx.websocket(url) } : {},
|
|
16959
17241
|
fs: kernelFs(ctx),
|
|
16960
17242
|
env: ctx.env,
|
|
16961
17243
|
cwd: ctx.cwd,
|
|
@@ -16991,7 +17273,7 @@ async function main(ctx) {
|
|
|
16991
17273
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16992
17274
|
});
|
|
16993
17275
|
const args = ctx.argv.slice(1);
|
|
16994
|
-
return run(platform2, "0.20.
|
|
17276
|
+
return run(platform2, "0.20.3", args);
|
|
16995
17277
|
}
|
|
16996
17278
|
export {
|
|
16997
17279
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.3",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@ai-ecoverse/slicc-kernel": "1.146.3",
|
|
62
|
-
"@ai-ecoverse/slicc-shared-web": "2.
|
|
62
|
+
"@ai-ecoverse/slicc-shared-web": "2.10.2",
|
|
63
63
|
"@biomejs/biome": "2.5.15",
|
|
64
64
|
"@earendil-works/pi-codemode": "1.2.0",
|
|
65
65
|
"@earendil-works/pi-mcp": "1.2.0",
|