@ai-ecoverse/mcpcat 0.15.5 → 0.15.7
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 +359 -147
- package/dist/kernel/mcpcat.mjs +371 -156
- package/package.json +4 -3
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -1854,6 +1854,16 @@ var init_version = __esm({
|
|
|
1854
1854
|
});
|
|
1855
1855
|
|
|
1856
1856
|
// node_modules/quickjs-wasi/dist/index.js
|
|
1857
|
+
var dist_exports = {};
|
|
1858
|
+
__export(dist_exports, {
|
|
1859
|
+
CompileFlags: () => CompileFlags,
|
|
1860
|
+
EvalFlags: () => EvalFlags,
|
|
1861
|
+
Intrinsics: () => Intrinsics,
|
|
1862
|
+
JSException: () => JSException,
|
|
1863
|
+
JSValueHandle: () => JSValueHandle,
|
|
1864
|
+
MAX_STACK_SIZE: () => MAX_STACK_SIZE,
|
|
1865
|
+
QuickJS: () => QuickJS
|
|
1866
|
+
});
|
|
1857
1867
|
function stringKeyNeedsValuePath(key) {
|
|
1858
1868
|
return key.includes("\0") || LONE_SURROGATE_RE.test(key);
|
|
1859
1869
|
}
|
|
@@ -5329,8 +5339,8 @@ function parseCodemodeSource(input) {
|
|
|
5329
5339
|
throw new CodemodeSourceError(`The ${CODEMODE_OPTIONS_PREFIX} line must be the first line of the input; found one on line ${misplaced + 1}`);
|
|
5330
5340
|
}
|
|
5331
5341
|
const newline = input.indexOf("\n");
|
|
5332
|
-
const
|
|
5333
|
-
const trimmed =
|
|
5342
|
+
const firstLine2 = lines[0].replace(/\r$/, "");
|
|
5343
|
+
const trimmed = firstLine2.trimStart();
|
|
5334
5344
|
if (!trimmed.startsWith(CODEMODE_OPTIONS_PREFIX))
|
|
5335
5345
|
return { code: input, options: {} };
|
|
5336
5346
|
const code2 = newline === -1 ? "" : input.slice(newline);
|
|
@@ -5365,8 +5375,8 @@ SOURCE: /[\s\S]+/
|
|
|
5365
5375
|
});
|
|
5366
5376
|
|
|
5367
5377
|
// node_modules/@earendil-works/pi-codemode/dist/index.js
|
|
5368
|
-
var
|
|
5369
|
-
__export(
|
|
5378
|
+
var dist_exports2 = {};
|
|
5379
|
+
__export(dist_exports2, {
|
|
5370
5380
|
CODEMODE_OPTIONS_PREFIX: () => CODEMODE_OPTIONS_PREFIX,
|
|
5371
5381
|
CODEMODE_SOURCE_GRAMMAR: () => CODEMODE_SOURCE_GRAMMAR,
|
|
5372
5382
|
CodemodeSandbox: () => CodemodeSandbox,
|
|
@@ -8369,8 +8379,9 @@ var McpTimeoutError2 = class extends Error {
|
|
|
8369
8379
|
var McpAuthError = class extends Error {
|
|
8370
8380
|
status;
|
|
8371
8381
|
target;
|
|
8372
|
-
constructor(status, target) {
|
|
8373
|
-
|
|
8382
|
+
constructor(status, target, reason) {
|
|
8383
|
+
const why = reason ? ` (${reason})` : "";
|
|
8384
|
+
super(`HTTP ${status}: authentication required${why}. Run: mcpcat login ${target}`);
|
|
8374
8385
|
this.name = "McpAuthError";
|
|
8375
8386
|
this.status = status;
|
|
8376
8387
|
this.target = target;
|
|
@@ -8388,12 +8399,23 @@ var TransportRefusalError = class extends Error {
|
|
|
8388
8399
|
};
|
|
8389
8400
|
var AuthRetryableError = class extends Error {
|
|
8390
8401
|
response;
|
|
8391
|
-
|
|
8402
|
+
reason;
|
|
8403
|
+
constructor(response, reason) {
|
|
8392
8404
|
super(`HTTP ${response.status}: auth retry`);
|
|
8393
8405
|
this.name = "AuthRetryableError";
|
|
8394
8406
|
this.response = response;
|
|
8407
|
+
this.reason = reason;
|
|
8395
8408
|
}
|
|
8396
8409
|
};
|
|
8410
|
+
var KERNEL_REFUSAL = "nodeTransport refuses ";
|
|
8411
|
+
function firstLine(text) {
|
|
8412
|
+
return text.trim().split("\n")[0].trim().slice(0, 200);
|
|
8413
|
+
}
|
|
8414
|
+
async function authReason(res) {
|
|
8415
|
+
const type = res.headers.get("content-type") ?? "";
|
|
8416
|
+
if (!type.toLowerCase().startsWith("text/plain")) return void 0;
|
|
8417
|
+
return firstLine(await res.text()) || void 0;
|
|
8418
|
+
}
|
|
8397
8419
|
function selectSseFrame(body, targetId) {
|
|
8398
8420
|
const text = new TextDecoder().decode(body);
|
|
8399
8421
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -8510,7 +8532,8 @@ async function createStatelessConnection(options) {
|
|
|
8510
8532
|
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
8511
8533
|
signal: externalSignal,
|
|
8512
8534
|
verbose,
|
|
8513
|
-
authProvider
|
|
8535
|
+
authProvider,
|
|
8536
|
+
transportRefusals
|
|
8514
8537
|
} = options;
|
|
8515
8538
|
let nextId2 = 1;
|
|
8516
8539
|
let negotiatedVersion;
|
|
@@ -8528,18 +8551,14 @@ async function createStatelessConnection(options) {
|
|
|
8528
8551
|
return h;
|
|
8529
8552
|
}
|
|
8530
8553
|
async function handleErrorStatus(res, method) {
|
|
8531
|
-
if (res.status === 403) {
|
|
8532
|
-
const
|
|
8533
|
-
if (!
|
|
8534
|
-
|
|
8535
|
-
if (body && !body.includes("insufficient_scope")) {
|
|
8536
|
-
throw new TransportRefusalError(403, body.split("\n")[0].slice(0, 200));
|
|
8537
|
-
}
|
|
8554
|
+
if (res.status === 401 || res.status === 403) {
|
|
8555
|
+
const reason = await authReason(res);
|
|
8556
|
+
if (transportRefusals && res.status === 403 && !res.headers.has("www-authenticate") && reason?.startsWith(KERNEL_REFUSAL)) {
|
|
8557
|
+
throw new TransportRefusalError(403, reason);
|
|
8538
8558
|
}
|
|
8559
|
+
if (authProvider) throw new AuthRetryableError(res, reason);
|
|
8560
|
+
throw new McpAuthError(res.status, url, reason);
|
|
8539
8561
|
}
|
|
8540
|
-
if ((res.status === 401 || res.status === 403) && authProvider)
|
|
8541
|
-
throw new AuthRetryableError(res);
|
|
8542
|
-
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
8543
8562
|
const text = await res.text();
|
|
8544
8563
|
let rpc;
|
|
8545
8564
|
try {
|
|
@@ -8594,7 +8613,7 @@ async function createStatelessConnection(options) {
|
|
|
8594
8613
|
return frame.result;
|
|
8595
8614
|
}
|
|
8596
8615
|
async function handleAuthError(err, token) {
|
|
8597
|
-
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url);
|
|
8616
|
+
if (!authProvider?.onUnauthorized) throw new McpAuthError(err.response.status, url, err.reason);
|
|
8598
8617
|
try {
|
|
8599
8618
|
await authProvider.onUnauthorized({
|
|
8600
8619
|
response: err.response,
|
|
@@ -8604,7 +8623,7 @@ async function createStatelessConnection(options) {
|
|
|
8604
8623
|
});
|
|
8605
8624
|
} catch (authErr) {
|
|
8606
8625
|
if (authErr instanceof AuthStepUpError || authErr instanceof JerkTimeoutError) throw authErr;
|
|
8607
|
-
throw new McpAuthError(err.response.status, url);
|
|
8626
|
+
throw new McpAuthError(err.response.status, url, err.reason);
|
|
8608
8627
|
}
|
|
8609
8628
|
}
|
|
8610
8629
|
async function request(method, params) {
|
|
@@ -9065,7 +9084,8 @@ async function connect(platform2, options) {
|
|
|
9065
9084
|
timeoutMs: options.timeoutMs,
|
|
9066
9085
|
signal: options.signal,
|
|
9067
9086
|
verbose: options.verbose,
|
|
9068
|
-
authProvider: options.authProvider
|
|
9087
|
+
authProvider: options.authProvider,
|
|
9088
|
+
transportRefusals: platform2.kind === "kernel"
|
|
9069
9089
|
});
|
|
9070
9090
|
} catch (err) {
|
|
9071
9091
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
@@ -9357,7 +9377,7 @@ function toolDeclaration(tool) {
|
|
|
9357
9377
|
};
|
|
9358
9378
|
}
|
|
9359
9379
|
async function runDescribeTs(platform2, targets, budget) {
|
|
9360
|
-
const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(),
|
|
9380
|
+
const { renderDeclarations: renderDeclarations2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
9361
9381
|
const allTools = [];
|
|
9362
9382
|
for (const target of targets) {
|
|
9363
9383
|
const tools = await target.conn.listTools();
|
|
@@ -9428,6 +9448,39 @@ async function runGrep(platform2, pattern, targets, options) {
|
|
|
9428
9448
|
// src/codemode/run.ts
|
|
9429
9449
|
init_dist2();
|
|
9430
9450
|
init_source();
|
|
9451
|
+
|
|
9452
|
+
// src/codemode/deadline.ts
|
|
9453
|
+
var expiries = /* @__PURE__ */ new WeakMap();
|
|
9454
|
+
var hooked = /* @__PURE__ */ new WeakSet();
|
|
9455
|
+
function hookDeadlines(quickjs) {
|
|
9456
|
+
if (hooked.has(quickjs)) return;
|
|
9457
|
+
hooked.add(quickjs);
|
|
9458
|
+
const create = quickjs.create.bind(quickjs);
|
|
9459
|
+
quickjs.create = (options) => {
|
|
9460
|
+
const expired = options.wasm ? expiries.get(options.wasm) : void 0;
|
|
9461
|
+
const inner = options.interruptHandler;
|
|
9462
|
+
if (!expired || !inner) return create(options);
|
|
9463
|
+
return create({ ...options, interruptHandler: () => expired() || inner() });
|
|
9464
|
+
};
|
|
9465
|
+
}
|
|
9466
|
+
function bindDeadline(wasm, timeoutMs, now = Date.now) {
|
|
9467
|
+
let endsAt = Number.POSITIVE_INFINITY;
|
|
9468
|
+
let tripped = false;
|
|
9469
|
+
expiries.set(wasm, () => {
|
|
9470
|
+
if (!tripped && now() >= endsAt) tripped = true;
|
|
9471
|
+
return tripped;
|
|
9472
|
+
});
|
|
9473
|
+
return {
|
|
9474
|
+
start() {
|
|
9475
|
+
endsAt = now() + timeoutMs;
|
|
9476
|
+
},
|
|
9477
|
+
get tripped() {
|
|
9478
|
+
return tripped;
|
|
9479
|
+
}
|
|
9480
|
+
};
|
|
9481
|
+
}
|
|
9482
|
+
|
|
9483
|
+
// src/codemode/run.ts
|
|
9431
9484
|
async function loadStore(platform2, stateDir2) {
|
|
9432
9485
|
const storePath = `${stateDir2}/codemode-store.json`;
|
|
9433
9486
|
try {
|
|
@@ -9613,9 +9666,23 @@ async function runCode(targets, source, options) {
|
|
|
9613
9666
|
...buildDiscoveryGlobals(allDeclaredTools, namespaces, searchDocs),
|
|
9614
9667
|
...options.allowShell ? [buildShellGlobal(platform2)] : []
|
|
9615
9668
|
];
|
|
9616
|
-
const sandbox = await createSandbox(
|
|
9669
|
+
const { sandbox, deadline } = await createSandbox(
|
|
9670
|
+
platform2,
|
|
9671
|
+
toolImpls,
|
|
9672
|
+
globals,
|
|
9673
|
+
timeoutMs,
|
|
9674
|
+
memoryLimitBytes
|
|
9675
|
+
);
|
|
9617
9676
|
try {
|
|
9618
|
-
|
|
9677
|
+
deadline?.start();
|
|
9678
|
+
const executed = await sandbox.execute(code2, { store });
|
|
9679
|
+
const result = deadline?.tripped && !executed.ok ? {
|
|
9680
|
+
...executed,
|
|
9681
|
+
error: {
|
|
9682
|
+
kind: "timeout",
|
|
9683
|
+
message: `Execution timed out after ${timeoutMs} ms`
|
|
9684
|
+
}
|
|
9685
|
+
} : executed;
|
|
9619
9686
|
if (result.ok) {
|
|
9620
9687
|
const writes = result.storeWrites;
|
|
9621
9688
|
if (Object.keys(writes.set).length > 0 || writes.delete.length > 0) {
|
|
@@ -9661,12 +9728,23 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
|
|
|
9661
9728
|
const isKernel2 = platform2.packageDir !== void 0;
|
|
9662
9729
|
if (isKernel2) {
|
|
9663
9730
|
const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
|
|
9731
|
+
const { QuickJS: QuickJS2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
|
|
9732
|
+
hookDeadlines(QuickJS2);
|
|
9664
9733
|
const wasm = await loadWasmKernel(platform2);
|
|
9665
|
-
|
|
9734
|
+
const deadline = bindDeadline(wasm, timeoutMs);
|
|
9735
|
+
const sandbox2 = new InlineCodemodeSandbox2({
|
|
9736
|
+
tools,
|
|
9737
|
+
globals,
|
|
9738
|
+
timeoutMs,
|
|
9739
|
+
wasm,
|
|
9740
|
+
memoryLimitBytes,
|
|
9741
|
+
interruptBudget: Number.POSITIVE_INFINITY
|
|
9742
|
+
});
|
|
9743
|
+
return { sandbox: sandbox2, deadline };
|
|
9666
9744
|
}
|
|
9667
|
-
const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(),
|
|
9745
|
+
const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports2));
|
|
9668
9746
|
const wasmPath = await resolveWasmPath(platform2);
|
|
9669
|
-
|
|
9747
|
+
const sandbox = new CodemodeSandbox2({
|
|
9670
9748
|
tools,
|
|
9671
9749
|
globals,
|
|
9672
9750
|
timeoutMs,
|
|
@@ -9674,6 +9752,7 @@ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBy
|
|
|
9674
9752
|
wasm: loadQuickJSWasm2(wasmPath),
|
|
9675
9753
|
workerUrl: new URL("./codemode-worker.mjs", import.meta.url)
|
|
9676
9754
|
});
|
|
9755
|
+
return { sandbox };
|
|
9677
9756
|
}
|
|
9678
9757
|
async function resolveWasmPath(platform2) {
|
|
9679
9758
|
const candidates = [
|
|
@@ -12183,16 +12262,45 @@ function wrapUntrusted(content) {
|
|
|
12183
12262
|
${neutralised}
|
|
12184
12263
|
${closeTag}`;
|
|
12185
12264
|
}
|
|
12186
|
-
|
|
12265
|
+
var WEBMCP_LIST_FLOOR = 1048576;
|
|
12266
|
+
function capCollector(limit) {
|
|
12267
|
+
const dec = new TextDecoder();
|
|
12268
|
+
const half = Math.floor(limit / 2);
|
|
12269
|
+
let head = "";
|
|
12270
|
+
let tail = "";
|
|
12271
|
+
let total = 0;
|
|
12272
|
+
const take = (piece) => {
|
|
12273
|
+
if (!piece) return;
|
|
12274
|
+
total += piece.length;
|
|
12275
|
+
if (head.length < limit) head += piece.slice(0, limit - head.length);
|
|
12276
|
+
tail = half > 0 ? (tail + piece).slice(-half) : "";
|
|
12277
|
+
};
|
|
12278
|
+
return {
|
|
12279
|
+
push(chunk) {
|
|
12280
|
+
take(dec.decode(chunk, { stream: true }));
|
|
12281
|
+
},
|
|
12282
|
+
finish() {
|
|
12283
|
+
take(dec.decode());
|
|
12284
|
+
if (total <= limit) return { text: head, cut: false };
|
|
12285
|
+
return {
|
|
12286
|
+
text: `${head.slice(0, half)}
|
|
12287
|
+
\u2026 ${total - limit} characters cut \u2026
|
|
12288
|
+
${tail}`,
|
|
12289
|
+
cut: true
|
|
12290
|
+
};
|
|
12291
|
+
}
|
|
12292
|
+
};
|
|
12293
|
+
}
|
|
12294
|
+
async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
12187
12295
|
let proc;
|
|
12188
12296
|
try {
|
|
12189
12297
|
proc = await platform2.spawn({ argv });
|
|
12190
|
-
} catch (
|
|
12191
|
-
const code2 =
|
|
12298
|
+
} catch (err2) {
|
|
12299
|
+
const code2 = err2.code;
|
|
12192
12300
|
if (code2 === "ENOENT" || code2 === "EACCES") {
|
|
12193
|
-
return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1 };
|
|
12301
|
+
return { stdout: "", stderr: `command not found: ${argv[0]}`, exitCode: -1, cut: false };
|
|
12194
12302
|
}
|
|
12195
|
-
throw
|
|
12303
|
+
throw err2;
|
|
12196
12304
|
}
|
|
12197
12305
|
if (stdin !== void 0) {
|
|
12198
12306
|
const writer = proc.stdin.getWriter();
|
|
@@ -12204,8 +12312,8 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
|
|
|
12204
12312
|
} catch {
|
|
12205
12313
|
}
|
|
12206
12314
|
}
|
|
12207
|
-
const
|
|
12208
|
-
const
|
|
12315
|
+
const out = capCollector(limit);
|
|
12316
|
+
const err = capCollector(limit);
|
|
12209
12317
|
const readStream3 = async (stream, into) => {
|
|
12210
12318
|
const r = stream.getReader();
|
|
12211
12319
|
try {
|
|
@@ -12218,53 +12326,136 @@ async function runProcess(platform2, argv, stdin, timeoutMs) {
|
|
|
12218
12326
|
r.releaseLock();
|
|
12219
12327
|
}
|
|
12220
12328
|
};
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12329
|
+
let timeoutId;
|
|
12330
|
+
const timer = new Promise((resolve) => {
|
|
12331
|
+
timeoutId = setTimeout(() => resolve("timeout"), timeoutMs);
|
|
12332
|
+
});
|
|
12224
12333
|
const work = Promise.all([
|
|
12225
|
-
readStream3(proc.stdout,
|
|
12226
|
-
readStream3(proc.stderr,
|
|
12334
|
+
readStream3(proc.stdout, out),
|
|
12335
|
+
readStream3(proc.stderr, err),
|
|
12227
12336
|
proc.wait()
|
|
12228
12337
|
]);
|
|
12229
12338
|
const result = await Promise.race([work, timer]);
|
|
12339
|
+
clearTimeout(timeoutId);
|
|
12230
12340
|
if (result === "timeout") {
|
|
12231
12341
|
await proc.kill("SIGTERM");
|
|
12232
|
-
return { stdout: "", stderr: "timed out", exitCode: 124 };
|
|
12342
|
+
return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
|
|
12233
12343
|
}
|
|
12234
12344
|
const [, , exitResult] = result;
|
|
12235
|
-
const
|
|
12236
|
-
const
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
offset += c.length;
|
|
12243
|
-
}
|
|
12244
|
-
return out;
|
|
12345
|
+
const stdout = out.finish();
|
|
12346
|
+
const stderr = err.finish();
|
|
12347
|
+
return {
|
|
12348
|
+
stdout: stdout.text,
|
|
12349
|
+
stderr: stderr.text,
|
|
12350
|
+
exitCode: exitResult.status,
|
|
12351
|
+
cut: stdout.cut
|
|
12245
12352
|
};
|
|
12353
|
+
}
|
|
12354
|
+
async function resolveWebMcpTab(platform2, options) {
|
|
12355
|
+
const argv = [options.playwrightCli, "tab-list"];
|
|
12356
|
+
if (options.cdp) argv.push("--cdp", options.cdp);
|
|
12357
|
+
const result = await runProcess(
|
|
12358
|
+
platform2,
|
|
12359
|
+
argv,
|
|
12360
|
+
void 0,
|
|
12361
|
+
options.callTimeoutMs,
|
|
12362
|
+
WEBMCP_LIST_FLOOR
|
|
12363
|
+
);
|
|
12364
|
+
if (result.exitCode === -1) return { error: result.stderr, notFound: true };
|
|
12365
|
+
if (result.exitCode !== 0) {
|
|
12366
|
+
return { error: result.stderr.trim() || `tab-list exited ${result.exitCode}` };
|
|
12367
|
+
}
|
|
12368
|
+
const rows = [];
|
|
12369
|
+
for (const line of result.stdout.split("\n")) {
|
|
12370
|
+
const match = /^(\d+): \[([^\]\s]+)\] /.exec(line);
|
|
12371
|
+
if (!match) continue;
|
|
12372
|
+
if (Number(match[1]) !== rows.length + 1) {
|
|
12373
|
+
return { error: "could not read playwright-cli tab-list" };
|
|
12374
|
+
}
|
|
12375
|
+
rows.push({ id: match[2], active: line.endsWith('" (active)') });
|
|
12376
|
+
}
|
|
12377
|
+
const active = rows.filter((r) => r.active);
|
|
12378
|
+
if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
|
|
12379
|
+
return { error: "could not read playwright-cli tab-list" };
|
|
12380
|
+
}
|
|
12381
|
+
if (active.length === 1) return { tab: active[0].id };
|
|
12382
|
+
if (rows.length === 1) return { tab: rows[0].id };
|
|
12246
12383
|
return {
|
|
12247
|
-
|
|
12248
|
-
stderr: dec.decode(concat2(stderrChunks)),
|
|
12249
|
-
exitCode: exitResult.status
|
|
12384
|
+
error: rows.length === 0 ? "no browser tab is open; open one or pass --tab <targetId>" : "no current playwright-cli tab; pass --tab <targetId> (see playwright-cli tab-list)"
|
|
12250
12385
|
};
|
|
12251
12386
|
}
|
|
12387
|
+
function mapDescriptionAnnotations(ann) {
|
|
12388
|
+
const mapped = { openWorldHint: true };
|
|
12389
|
+
if (!ann) return mapped;
|
|
12390
|
+
if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
|
|
12391
|
+
if (ann.consequentialHint) mapped.destructiveHint = true;
|
|
12392
|
+
return mapped;
|
|
12393
|
+
}
|
|
12394
|
+
function originPrefix(tools) {
|
|
12395
|
+
const origins = new Set(tools.map((t) => t.origin));
|
|
12396
|
+
return origins.size > 1;
|
|
12397
|
+
}
|
|
12398
|
+
function hostFromOrigin(origin) {
|
|
12399
|
+
try {
|
|
12400
|
+
return new URL(origin).host;
|
|
12401
|
+
} catch {
|
|
12402
|
+
return origin;
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
function buildDescription(tool) {
|
|
12406
|
+
const parts = [];
|
|
12407
|
+
if (tool.description) parts.push(tool.description);
|
|
12408
|
+
parts.push(`[origin: ${tool.origin}]`);
|
|
12409
|
+
return parts.join("\n");
|
|
12410
|
+
}
|
|
12411
|
+
function buildToolName(tool, prefix) {
|
|
12412
|
+
const base = tool.name;
|
|
12413
|
+
if (!prefix) return base;
|
|
12414
|
+
const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
|
|
12415
|
+
return `${host}__${base}`;
|
|
12416
|
+
}
|
|
12417
|
+
function exposedTools(tools) {
|
|
12418
|
+
const needPrefix = originPrefix(tools);
|
|
12419
|
+
const byBase = /* @__PURE__ */ new Map();
|
|
12420
|
+
const byName = /* @__PURE__ */ new Map();
|
|
12421
|
+
for (const tool of tools) {
|
|
12422
|
+
const name = buildToolName(tool, needPrefix);
|
|
12423
|
+
byBase.set(tool.name, [...byBase.get(tool.name) ?? [], tool]);
|
|
12424
|
+
byName.set(name, [...byName.get(name) ?? [], tool]);
|
|
12425
|
+
}
|
|
12426
|
+
return tools.map((tool) => {
|
|
12427
|
+
const name = buildToolName(tool, needPrefix);
|
|
12428
|
+
const sameBase = byBase.get(tool.name);
|
|
12429
|
+
const sameName = byName.get(name);
|
|
12430
|
+
return { name, tool, peers: sameBase.length > 1 ? sameBase : sameName };
|
|
12431
|
+
});
|
|
12432
|
+
}
|
|
12433
|
+
function ambiguityError(name, peers) {
|
|
12434
|
+
const origins = [...new Set(peers.map((t) => String(t.origin)))].join(", ");
|
|
12435
|
+
return new Error(
|
|
12436
|
+
`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`
|
|
12437
|
+
);
|
|
12438
|
+
}
|
|
12252
12439
|
function createWebMcpHandler(platform2, options) {
|
|
12253
12440
|
let cache;
|
|
12254
12441
|
const baseArgs = buildBaseArgs(options);
|
|
12255
|
-
|
|
12442
|
+
const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
|
|
12443
|
+
async function listRaw(fresh = false) {
|
|
12256
12444
|
const now = Date.now();
|
|
12257
|
-
if (cache && now < cache.expiresAt) {
|
|
12445
|
+
if (!fresh && cache && now < cache.expiresAt) {
|
|
12258
12446
|
return { tools: cache.tools };
|
|
12259
12447
|
}
|
|
12260
12448
|
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12261
|
-
const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs);
|
|
12449
|
+
const result = await runProcess(platform2, argv, void 0, options.callTimeoutMs, listLimit);
|
|
12262
12450
|
if (result.exitCode === -1) {
|
|
12263
12451
|
return { tools: [], error: result.stderr, notFound: true };
|
|
12264
12452
|
}
|
|
12265
12453
|
if (result.exitCode !== 0) {
|
|
12266
12454
|
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12267
12455
|
}
|
|
12456
|
+
if (result.cut) {
|
|
12457
|
+
return { tools: [], error: `webmcp-list output exceeds ${listLimit} characters` };
|
|
12458
|
+
}
|
|
12268
12459
|
let parsed;
|
|
12269
12460
|
try {
|
|
12270
12461
|
parsed = JSON.parse(result.stdout);
|
|
@@ -12278,49 +12469,19 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12278
12469
|
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12279
12470
|
return { tools };
|
|
12280
12471
|
}
|
|
12281
|
-
function mapDescriptionAnnotations(ann) {
|
|
12282
|
-
const mapped = { openWorldHint: true };
|
|
12283
|
-
if (!ann) return mapped;
|
|
12284
|
-
if (ann.readOnlyHint !== void 0) mapped.readOnlyHint = ann.readOnlyHint;
|
|
12285
|
-
if (ann.consequentialHint) mapped.destructiveHint = true;
|
|
12286
|
-
return mapped;
|
|
12287
|
-
}
|
|
12288
|
-
function originPrefix(tools) {
|
|
12289
|
-
const origins = new Set(tools.map((t) => t.origin));
|
|
12290
|
-
return origins.size > 1;
|
|
12291
|
-
}
|
|
12292
|
-
function hostFromOrigin(origin) {
|
|
12293
|
-
try {
|
|
12294
|
-
return new URL(origin).host;
|
|
12295
|
-
} catch {
|
|
12296
|
-
return origin;
|
|
12297
|
-
}
|
|
12298
|
-
}
|
|
12299
|
-
function buildDescription(tool) {
|
|
12300
|
-
const parts = [];
|
|
12301
|
-
if (tool.description) parts.push(tool.description);
|
|
12302
|
-
parts.push(`[origin: ${tool.origin}]`);
|
|
12303
|
-
return parts.join("\n");
|
|
12304
|
-
}
|
|
12305
|
-
function buildToolName(tool, prefix) {
|
|
12306
|
-
const base = tool.name;
|
|
12307
|
-
if (!prefix) return base;
|
|
12308
|
-
const host = hostFromOrigin(tool.origin).replace(/[^a-zA-Z0-9_]/g, "_");
|
|
12309
|
-
return `${host}__${base}`;
|
|
12310
|
-
}
|
|
12311
12472
|
async function getRpcTools() {
|
|
12312
12473
|
const { tools, error, notFound } = await listRaw();
|
|
12313
12474
|
if (notFound) return null;
|
|
12314
12475
|
if (error) return [];
|
|
12315
|
-
const needPrefix = originPrefix(tools);
|
|
12316
12476
|
const rpcTools = [];
|
|
12317
|
-
for (const tool of tools) {
|
|
12477
|
+
for (const { name, tool, peers } of exposedTools(tools)) {
|
|
12478
|
+
if (peers.length > 1) continue;
|
|
12318
12479
|
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12319
12480
|
continue;
|
|
12320
12481
|
}
|
|
12321
12482
|
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
12322
12483
|
rpcTools.push({
|
|
12323
|
-
name
|
|
12484
|
+
name,
|
|
12324
12485
|
description: buildDescription(tool),
|
|
12325
12486
|
inputSchema: tool.inputSchema,
|
|
12326
12487
|
annotations: mapped
|
|
@@ -12329,21 +12490,30 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12329
12490
|
return rpcTools;
|
|
12330
12491
|
}
|
|
12331
12492
|
async function callTool(name, args2) {
|
|
12332
|
-
const { tools, error } = await listRaw();
|
|
12493
|
+
const { tools, error } = await listRaw(true);
|
|
12333
12494
|
if (error) {
|
|
12334
12495
|
return { stdout: "", stderr: error, exitCode: 2 };
|
|
12335
12496
|
}
|
|
12336
|
-
const
|
|
12337
|
-
|
|
12338
|
-
if (!tool) {
|
|
12497
|
+
const match = exposedTools(tools).find((t) => t.name === name);
|
|
12498
|
+
if (!match) {
|
|
12339
12499
|
return { stdout: "", stderr: `unknown tool: ${name}`, exitCode: 2 };
|
|
12340
12500
|
}
|
|
12501
|
+
if (match.peers.length > 1) {
|
|
12502
|
+
throw ambiguityError(name, match.peers);
|
|
12503
|
+
}
|
|
12504
|
+
const { tool } = match;
|
|
12341
12505
|
const argv = [options.playwrightCli, "webmcp-call", tool.name, "-", ...baseArgs];
|
|
12342
12506
|
if (options.allowConsequential) {
|
|
12343
12507
|
argv.push("--allow-consequential");
|
|
12344
12508
|
}
|
|
12345
12509
|
const stdinData = JSON.stringify(args2 ?? {});
|
|
12346
|
-
const result = await runProcess(
|
|
12510
|
+
const result = await runProcess(
|
|
12511
|
+
platform2,
|
|
12512
|
+
argv,
|
|
12513
|
+
stdinData,
|
|
12514
|
+
options.callTimeoutMs,
|
|
12515
|
+
options.maxOutput
|
|
12516
|
+
);
|
|
12347
12517
|
if (result.exitCode === -1) {
|
|
12348
12518
|
throw new Error(result.stderr || "playwright-cli not found");
|
|
12349
12519
|
}
|
|
@@ -12869,6 +13039,7 @@ async function discoverWithSignal(platform2, commands, childEnv, cwd) {
|
|
|
12869
13039
|
};
|
|
12870
13040
|
platform2.onSignal?.("SIGTERM", onShutdown);
|
|
12871
13041
|
platform2.onSignal?.("SIGINT", onShutdown);
|
|
13042
|
+
platform2.onSignal?.("SIGHUP", onShutdown);
|
|
12872
13043
|
for (const cmd of commands) {
|
|
12873
13044
|
try {
|
|
12874
13045
|
const cli = await discoverCli(platform2, cmd.path, cmd.name, childEnv, cwd, earlyAbort.signal);
|
|
@@ -13094,9 +13265,30 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13094
13265
|
return 3;
|
|
13095
13266
|
}
|
|
13096
13267
|
const cdp = options.webmcpCdp ?? platform2.env.SLICC_CDP_URL;
|
|
13268
|
+
let tab = options.webmcpTab;
|
|
13269
|
+
if (!tab) {
|
|
13270
|
+
const resolved = await resolveWebMcpTab(platform2, {
|
|
13271
|
+
playwrightCli: cliPath,
|
|
13272
|
+
cdp,
|
|
13273
|
+
callTimeoutMs: options.callTimeout
|
|
13274
|
+
});
|
|
13275
|
+
if (resolved.notFound) {
|
|
13276
|
+
await platform2.write(
|
|
13277
|
+
2,
|
|
13278
|
+
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13279
|
+
);
|
|
13280
|
+
return 3;
|
|
13281
|
+
}
|
|
13282
|
+
if (!resolved.tab) {
|
|
13283
|
+
await platform2.write(2, `mcpcat serve --webmcp: ${resolved.error}
|
|
13284
|
+
`);
|
|
13285
|
+
return 3;
|
|
13286
|
+
}
|
|
13287
|
+
tab = resolved.tab;
|
|
13288
|
+
}
|
|
13097
13289
|
const handler = createWebMcpHandler(platform2, {
|
|
13098
13290
|
playwrightCli: cliPath,
|
|
13099
|
-
tab
|
|
13291
|
+
tab,
|
|
13100
13292
|
cdp,
|
|
13101
13293
|
allowConsequential: options.webmcpAllowConsequential,
|
|
13102
13294
|
ttlMs: options.webmcpTtl,
|
|
@@ -13178,6 +13370,21 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13178
13370
|
const address = serveAddress(platform2, options.host, actualPort);
|
|
13179
13371
|
const listenUrl = address.url;
|
|
13180
13372
|
let tokenFilePath;
|
|
13373
|
+
let shutdownRequested = false;
|
|
13374
|
+
const shutdown = () => {
|
|
13375
|
+
if (shutdownRequested) return;
|
|
13376
|
+
shutdownRequested = true;
|
|
13377
|
+
listener.close();
|
|
13378
|
+
if (tokenFilePath) {
|
|
13379
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13380
|
+
});
|
|
13381
|
+
}
|
|
13382
|
+
};
|
|
13383
|
+
if (platform2.onSignal) {
|
|
13384
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
13385
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
13386
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
13387
|
+
}
|
|
13181
13388
|
if (generatedBearer && resolvedBearer) {
|
|
13182
13389
|
tokenFilePath = await publishBearer(
|
|
13183
13390
|
platform2,
|
|
@@ -13186,31 +13393,22 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13186
13393
|
address,
|
|
13187
13394
|
options.json
|
|
13188
13395
|
);
|
|
13396
|
+
if (shutdownRequested && tokenFilePath) {
|
|
13397
|
+
await platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13398
|
+
});
|
|
13399
|
+
}
|
|
13189
13400
|
} else {
|
|
13190
13401
|
if (options.json) {
|
|
13191
13402
|
await platform2.write(1, `${JSON.stringify(address)}
|
|
13192
13403
|
`);
|
|
13193
13404
|
}
|
|
13194
13405
|
}
|
|
13195
|
-
|
|
13406
|
+
if (!shutdownRequested) {
|
|
13407
|
+
await platform2.write(2, `mcpcat serve --webmcp: listening on ${listenUrl}
|
|
13196
13408
|
`);
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
}
|
|
13200
|
-
let shutdownRequested = false;
|
|
13201
|
-
const shutdown = () => {
|
|
13202
|
-
if (shutdownRequested) return;
|
|
13203
|
-
shutdownRequested = true;
|
|
13204
|
-
listener.close();
|
|
13205
|
-
if (tokenFilePath) {
|
|
13206
|
-
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13207
|
-
});
|
|
13409
|
+
if (options.onListening) {
|
|
13410
|
+
await options.onListening(actualPort);
|
|
13208
13411
|
}
|
|
13209
|
-
};
|
|
13210
|
-
if (platform2.onSignal) {
|
|
13211
|
-
platform2.onSignal("SIGINT", shutdown);
|
|
13212
|
-
platform2.onSignal("SIGTERM", shutdown);
|
|
13213
|
-
platform2.onSignal("SIGHUP", shutdown);
|
|
13214
13412
|
}
|
|
13215
13413
|
const handler = async (request) => {
|
|
13216
13414
|
if (request.target !== "/mcp") {
|
|
@@ -13374,6 +13572,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13374
13572
|
const address = serveAddress(platform2, options.host, actualPort);
|
|
13375
13573
|
const listenUrl = address.url;
|
|
13376
13574
|
let tokenFilePath;
|
|
13575
|
+
let shutdownRequested = false;
|
|
13576
|
+
let proxyDone;
|
|
13577
|
+
const shutdown = () => {
|
|
13578
|
+
if (shutdownRequested) return;
|
|
13579
|
+
shutdownRequested = true;
|
|
13580
|
+
listener.close();
|
|
13581
|
+
proxyDone = proxy.shutdown();
|
|
13582
|
+
if (tokenFilePath) {
|
|
13583
|
+
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13584
|
+
});
|
|
13585
|
+
}
|
|
13586
|
+
};
|
|
13587
|
+
if (platform2.onSignal) {
|
|
13588
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
13589
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
13590
|
+
platform2.onSignal("SIGHUP", shutdown);
|
|
13591
|
+
}
|
|
13377
13592
|
if (generatedBearer && resolvedBearer) {
|
|
13378
13593
|
tokenFilePath = await publishBearer(
|
|
13379
13594
|
platform2,
|
|
@@ -13382,32 +13597,22 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13382
13597
|
address,
|
|
13383
13598
|
options.json
|
|
13384
13599
|
);
|
|
13600
|
+
if (shutdownRequested && tokenFilePath) {
|
|
13601
|
+
await platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13602
|
+
});
|
|
13603
|
+
}
|
|
13385
13604
|
} else {
|
|
13386
13605
|
if (options.json) {
|
|
13387
13606
|
await platform2.write(1, `${JSON.stringify(address)}
|
|
13388
13607
|
`);
|
|
13389
13608
|
}
|
|
13390
13609
|
}
|
|
13391
|
-
|
|
13610
|
+
if (!shutdownRequested) {
|
|
13611
|
+
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
13392
13612
|
`);
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
}
|
|
13396
|
-
let shutdownRequested = false;
|
|
13397
|
-
const shutdown = () => {
|
|
13398
|
-
if (shutdownRequested) return;
|
|
13399
|
-
shutdownRequested = true;
|
|
13400
|
-
listener.close();
|
|
13401
|
-
void proxy.shutdown();
|
|
13402
|
-
if (tokenFilePath) {
|
|
13403
|
-
platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13404
|
-
});
|
|
13613
|
+
if (options.onListening) {
|
|
13614
|
+
await options.onListening(actualPort);
|
|
13405
13615
|
}
|
|
13406
|
-
};
|
|
13407
|
-
if (platform2.onSignal) {
|
|
13408
|
-
platform2.onSignal("SIGINT", shutdown);
|
|
13409
|
-
platform2.onSignal("SIGTERM", shutdown);
|
|
13410
|
-
platform2.onSignal("SIGHUP", shutdown);
|
|
13411
13616
|
}
|
|
13412
13617
|
const handler = (request) => handleProxyMcpRequest(
|
|
13413
13618
|
request,
|
|
@@ -13432,6 +13637,7 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13432
13637
|
} catch {
|
|
13433
13638
|
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
13434
13639
|
}
|
|
13640
|
+
await proxyDone;
|
|
13435
13641
|
return 0;
|
|
13436
13642
|
}
|
|
13437
13643
|
async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
|
|
@@ -13551,22 +13757,6 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13551
13757
|
const address = serveAddress(platform2, options.host, actualPort);
|
|
13552
13758
|
const listenUrl = address.url;
|
|
13553
13759
|
let tokenFilePath;
|
|
13554
|
-
if (generatedBearer && resolvedBearer) {
|
|
13555
|
-
tokenFilePath = await publishBearer(
|
|
13556
|
-
platform2,
|
|
13557
|
-
resolvedBearer,
|
|
13558
|
-
actualPort,
|
|
13559
|
-
address,
|
|
13560
|
-
options.json
|
|
13561
|
-
);
|
|
13562
|
-
} else {
|
|
13563
|
-
if (options.json) {
|
|
13564
|
-
await platform2.write(1, `${JSON.stringify(address)}
|
|
13565
|
-
`);
|
|
13566
|
-
}
|
|
13567
|
-
}
|
|
13568
|
-
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
13569
|
-
`);
|
|
13570
13760
|
let shutdownRequested = false;
|
|
13571
13761
|
let inflightDone;
|
|
13572
13762
|
const shutdown = () => {
|
|
@@ -13584,6 +13774,28 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13584
13774
|
platform2.onSignal("SIGTERM", shutdown);
|
|
13585
13775
|
platform2.onSignal("SIGHUP", shutdown);
|
|
13586
13776
|
}
|
|
13777
|
+
if (generatedBearer && resolvedBearer) {
|
|
13778
|
+
tokenFilePath = await publishBearer(
|
|
13779
|
+
platform2,
|
|
13780
|
+
resolvedBearer,
|
|
13781
|
+
actualPort,
|
|
13782
|
+
address,
|
|
13783
|
+
options.json
|
|
13784
|
+
);
|
|
13785
|
+
if (shutdownRequested && tokenFilePath) {
|
|
13786
|
+
await platform2.fs.unlink?.(tokenFilePath).catch(() => {
|
|
13787
|
+
});
|
|
13788
|
+
}
|
|
13789
|
+
} else {
|
|
13790
|
+
if (options.json) {
|
|
13791
|
+
await platform2.write(1, `${JSON.stringify(address)}
|
|
13792
|
+
`);
|
|
13793
|
+
}
|
|
13794
|
+
}
|
|
13795
|
+
if (!shutdownRequested) {
|
|
13796
|
+
await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
|
|
13797
|
+
`);
|
|
13798
|
+
}
|
|
13587
13799
|
const handler = (request) => handleMcpRequest(
|
|
13588
13800
|
request,
|
|
13589
13801
|
tools,
|
|
@@ -16734,5 +16946,5 @@ try {
|
|
|
16734
16946
|
});
|
|
16735
16947
|
} catch {
|
|
16736
16948
|
}
|
|
16737
|
-
var code = await run(platform, "0.15.
|
|
16949
|
+
var code = await run(platform, "0.15.7", args);
|
|
16738
16950
|
process.exit(code);
|