@ai-ecoverse/mcpcat 0.20.2 → 0.20.4
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 +550 -136
- package/dist/kernel/mcpcat.mjs +522 -136
- package/package.json +3 -3
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -7691,7 +7691,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7691
7691
|
let dataLines = [];
|
|
7692
7692
|
let dataBytes = 0;
|
|
7693
7693
|
const maxEventBytes = options.maxEventBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
7694
|
-
const
|
|
7694
|
+
const dispatch2 = () => {
|
|
7695
7695
|
if (dataLines.length === 0) {
|
|
7696
7696
|
eventName = void 0;
|
|
7697
7697
|
eventId = void 0;
|
|
@@ -7707,7 +7707,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7707
7707
|
const processLine = (rawLine) => {
|
|
7708
7708
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
7709
7709
|
if (line === "") {
|
|
7710
|
-
|
|
7710
|
+
dispatch2();
|
|
7711
7711
|
return;
|
|
7712
7712
|
}
|
|
7713
7713
|
if (line.startsWith(":"))
|
|
@@ -7748,7 +7748,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7748
7748
|
buffered += decoder3.decode();
|
|
7749
7749
|
if (buffered)
|
|
7750
7750
|
processLine(buffered);
|
|
7751
|
-
|
|
7751
|
+
dispatch2();
|
|
7752
7752
|
} finally {
|
|
7753
7753
|
reader.releaseLock();
|
|
7754
7754
|
}
|
|
@@ -8352,18 +8352,118 @@ var PlatformStdioTransport = class {
|
|
|
8352
8352
|
}
|
|
8353
8353
|
};
|
|
8354
8354
|
|
|
8355
|
+
// src/client/modern.ts
|
|
8356
|
+
var MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
8357
|
+
var META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
8358
|
+
var META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
|
|
8359
|
+
var META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
8360
|
+
var META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
|
|
8361
|
+
var HEADER_MISMATCH = -32020;
|
|
8362
|
+
var MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
|
|
8363
|
+
var UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
8364
|
+
var MODERN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
8365
|
+
HEADER_MISMATCH,
|
|
8366
|
+
MISSING_REQUIRED_CLIENT_CAPABILITY,
|
|
8367
|
+
UNSUPPORTED_PROTOCOL_VERSION
|
|
8368
|
+
]);
|
|
8369
|
+
function isModernErrorCode(code2) {
|
|
8370
|
+
return MODERN_ERROR_CODES.has(code2);
|
|
8371
|
+
}
|
|
8372
|
+
function modernRequestMeta(protocolVersion, clientInfo) {
|
|
8373
|
+
return {
|
|
8374
|
+
[META_PROTOCOL_VERSION]: protocolVersion,
|
|
8375
|
+
[META_CLIENT_INFO]: clientInfo,
|
|
8376
|
+
[META_CLIENT_CAPABILITIES]: {}
|
|
8377
|
+
};
|
|
8378
|
+
}
|
|
8379
|
+
function modernRequestParams(params, protocolVersion, clientInfo) {
|
|
8380
|
+
const { _inputResponses, ...rest } = params;
|
|
8381
|
+
const out = { ...rest };
|
|
8382
|
+
if (_inputResponses) out.inputResponses = _inputResponses;
|
|
8383
|
+
out._meta = modernRequestMeta(protocolVersion, clientInfo);
|
|
8384
|
+
return out;
|
|
8385
|
+
}
|
|
8386
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8387
|
+
function base64Utf8(value) {
|
|
8388
|
+
const bytes = new TextEncoder().encode(value);
|
|
8389
|
+
let out = "";
|
|
8390
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
8391
|
+
const a = bytes[i];
|
|
8392
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
8393
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
8394
|
+
const n = a << 16 | b << 8 | c;
|
|
8395
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63];
|
|
8396
|
+
out += i + 1 < bytes.length ? B64[n >> 6 & 63] : "=";
|
|
8397
|
+
out += i + 2 < bytes.length ? B64[n & 63] : "=";
|
|
8398
|
+
}
|
|
8399
|
+
return out;
|
|
8400
|
+
}
|
|
8401
|
+
function encodeHeaderValue(value) {
|
|
8402
|
+
const safe = /^[\x20-\x7e\t]*$/.test(value) && value.trim() === value && !(value.startsWith("=?base64?") && value.endsWith("?="));
|
|
8403
|
+
return safe ? value : `=?base64?${base64Utf8(value)}?=`;
|
|
8404
|
+
}
|
|
8405
|
+
function isModernRequest(params) {
|
|
8406
|
+
if (!params || typeof params !== "object") return false;
|
|
8407
|
+
const meta = Object.getOwnPropertyDescriptor(params, "_meta")?.value;
|
|
8408
|
+
if (!meta || typeof meta !== "object") return false;
|
|
8409
|
+
return typeof Object.getOwnPropertyDescriptor(meta, META_PROTOCOL_VERSION)?.value === "string";
|
|
8410
|
+
}
|
|
8411
|
+
function completeModernOutcome(outcome, params) {
|
|
8412
|
+
if (outcome.status !== 200 || !isModernRequest(params)) return outcome;
|
|
8413
|
+
const frame = JSON.parse(outcome.body);
|
|
8414
|
+
const result = frame.result;
|
|
8415
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return outcome;
|
|
8416
|
+
if (Object.hasOwn(result, "resultType")) return outcome;
|
|
8417
|
+
frame.result = { resultType: "complete", ...result };
|
|
8418
|
+
return { ...outcome, body: JSON.stringify(frame) };
|
|
8419
|
+
}
|
|
8420
|
+
function modernDiscoverResult(supportedVersions, capabilities, serverInfo, instructions) {
|
|
8421
|
+
return {
|
|
8422
|
+
resultType: "complete",
|
|
8423
|
+
supportedVersions,
|
|
8424
|
+
capabilities,
|
|
8425
|
+
serverInfo,
|
|
8426
|
+
...instructions ? { instructions } : {},
|
|
8427
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
8428
|
+
};
|
|
8429
|
+
}
|
|
8430
|
+
|
|
8355
8431
|
// src/client/stateless-connection.ts
|
|
8356
|
-
var STATELESS_PROTOCOL_VERSION =
|
|
8432
|
+
var STATELESS_PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
|
|
8357
8433
|
var StatelessRpcError = class extends Error {
|
|
8358
8434
|
code;
|
|
8359
8435
|
data;
|
|
8360
|
-
|
|
8436
|
+
httpStatus;
|
|
8437
|
+
constructor(code2, message, data, httpStatus) {
|
|
8361
8438
|
super(`MCP RPC error ${code2}: ${message}`);
|
|
8362
8439
|
this.name = "StatelessRpcError";
|
|
8363
8440
|
this.code = code2;
|
|
8364
8441
|
this.data = data;
|
|
8442
|
+
this.httpStatus = httpStatus;
|
|
8443
|
+
}
|
|
8444
|
+
};
|
|
8445
|
+
var UnsupportedModernVersionError = class extends Error {
|
|
8446
|
+
constructor(version, supported) {
|
|
8447
|
+
super(`Server does not support ${version} (advertised: ${supported.join(", ") || "none"})`);
|
|
8448
|
+
this.name = "UnsupportedModernVersionError";
|
|
8365
8449
|
}
|
|
8366
8450
|
};
|
|
8451
|
+
function supportedList(data) {
|
|
8452
|
+
const list = data?.supported;
|
|
8453
|
+
return Array.isArray(list) ? list.filter((v) => typeof v === "string") : void 0;
|
|
8454
|
+
}
|
|
8455
|
+
function indicatesLegacyServer(err, http) {
|
|
8456
|
+
if (err instanceof UnsupportedModernVersionError) return true;
|
|
8457
|
+
if (http && err instanceof StatelessHttpError) return err.status >= 400 && err.status < 500;
|
|
8458
|
+
if (!(err instanceof StatelessRpcError)) return false;
|
|
8459
|
+
if (err.code === UNSUPPORTED_PROTOCOL_VERSION) {
|
|
8460
|
+
const supported = supportedList(err.data);
|
|
8461
|
+
return !!supported && !supported.includes(STATELESS_PROTOCOL_VERSION);
|
|
8462
|
+
}
|
|
8463
|
+
if (isModernErrorCode(err.code)) return false;
|
|
8464
|
+
if (!http || err.code === -32601) return true;
|
|
8465
|
+
return err.httpStatus !== void 0 && err.httpStatus >= 400 && err.httpStatus < 500;
|
|
8466
|
+
}
|
|
8367
8467
|
var StatelessHttpError = class extends Error {
|
|
8368
8468
|
status;
|
|
8369
8469
|
constructor(status, statusText, method, snippet) {
|
|
@@ -8475,18 +8575,34 @@ function logHeaders(verbose, dir, entries) {
|
|
|
8475
8575
|
else verbose.headerIn(k, v);
|
|
8476
8576
|
}
|
|
8477
8577
|
}
|
|
8578
|
+
function discoverServerInfo(discoverResult2) {
|
|
8579
|
+
const meta = discoverResult2?._meta;
|
|
8580
|
+
return meta?.[META_SERVER_INFO] ?? discoverResult2?.serverInfo;
|
|
8581
|
+
}
|
|
8478
8582
|
function buildServerInfo(discoverResult2) {
|
|
8583
|
+
const serverInfo = discoverServerInfo(discoverResult2);
|
|
8479
8584
|
return {
|
|
8480
|
-
name:
|
|
8481
|
-
version:
|
|
8482
|
-
title:
|
|
8585
|
+
name: serverInfo?.name,
|
|
8586
|
+
version: serverInfo?.version,
|
|
8587
|
+
title: serverInfo?.title,
|
|
8483
8588
|
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
8484
8589
|
capabilities: discoverResult2?.capabilities ?? {},
|
|
8485
8590
|
instructions: discoverResult2?.instructions
|
|
8486
8591
|
};
|
|
8487
8592
|
}
|
|
8593
|
+
function readHiddenTools(result) {
|
|
8594
|
+
const meta = result?._meta;
|
|
8595
|
+
const hidden = meta?.["io.mcpcat/hidden"];
|
|
8596
|
+
const out = {};
|
|
8597
|
+
if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
|
|
8598
|
+
for (const [name, why] of Object.entries(hidden)) {
|
|
8599
|
+
if (typeof why === "string") out[name] = why;
|
|
8600
|
+
}
|
|
8601
|
+
return out;
|
|
8602
|
+
}
|
|
8488
8603
|
function buildStatelessMethods(requestFn, discoverResult2) {
|
|
8489
8604
|
let serverInfoCache;
|
|
8605
|
+
let hidden = {};
|
|
8490
8606
|
return {
|
|
8491
8607
|
async info() {
|
|
8492
8608
|
if (serverInfoCache) return serverInfoCache;
|
|
@@ -8494,7 +8610,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
|
|
|
8494
8610
|
return serverInfoCache;
|
|
8495
8611
|
},
|
|
8496
8612
|
async listTools() {
|
|
8497
|
-
|
|
8613
|
+
const result = await requestFn("tools/list", {});
|
|
8614
|
+
hidden = readHiddenTools(result);
|
|
8615
|
+
return result?.tools ?? [];
|
|
8616
|
+
},
|
|
8617
|
+
hiddenTool(name) {
|
|
8618
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8498
8619
|
},
|
|
8499
8620
|
async callTool(name, args2, _callOptions) {
|
|
8500
8621
|
const params = {
|
|
@@ -8546,17 +8667,17 @@ async function createStatelessConnection(options) {
|
|
|
8546
8667
|
redact: redact2 = (text) => text
|
|
8547
8668
|
} = options;
|
|
8548
8669
|
let nextId2 = 1;
|
|
8549
|
-
|
|
8670
|
+
const protocolVersion = STATELESS_PROTOCOL_VERSION;
|
|
8550
8671
|
function buildHeaders(method, params, token) {
|
|
8551
8672
|
const h = {
|
|
8552
8673
|
"Content-Type": "application/json",
|
|
8553
8674
|
Accept: "application/json, text/event-stream",
|
|
8675
|
+
"MCP-Protocol-Version": protocolVersion,
|
|
8554
8676
|
"Mcp-Method": method,
|
|
8555
8677
|
...extraHeaders
|
|
8556
8678
|
};
|
|
8557
8679
|
const n = mcpNameForMethod(method, params);
|
|
8558
|
-
if (n) h["Mcp-Name"] = n;
|
|
8559
|
-
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
8680
|
+
if (n) h["Mcp-Name"] = encodeHeaderValue(n);
|
|
8560
8681
|
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
8561
8682
|
return h;
|
|
8562
8683
|
}
|
|
@@ -8576,7 +8697,7 @@ async function createStatelessConnection(options) {
|
|
|
8576
8697
|
if (p.error && typeof p.error.code === "number") rpc = p.error;
|
|
8577
8698
|
} catch {
|
|
8578
8699
|
}
|
|
8579
|
-
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8700
|
+
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data, res.status);
|
|
8580
8701
|
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8581
8702
|
}
|
|
8582
8703
|
async function doFetch(method, params, body) {
|
|
@@ -8619,7 +8740,12 @@ async function createStatelessConnection(options) {
|
|
|
8619
8740
|
}
|
|
8620
8741
|
if (verbose?.enabled) verbose.response(JSON.stringify(frame));
|
|
8621
8742
|
if (frame.error) {
|
|
8622
|
-
throw new StatelessRpcError(
|
|
8743
|
+
throw new StatelessRpcError(
|
|
8744
|
+
frame.error.code,
|
|
8745
|
+
frame.error.message,
|
|
8746
|
+
frame.error.data,
|
|
8747
|
+
res.status
|
|
8748
|
+
);
|
|
8623
8749
|
}
|
|
8624
8750
|
return frame.result;
|
|
8625
8751
|
}
|
|
@@ -8638,23 +8764,10 @@ async function createStatelessConnection(options) {
|
|
|
8638
8764
|
}
|
|
8639
8765
|
}
|
|
8640
8766
|
async function request(method, params) {
|
|
8641
|
-
const
|
|
8642
|
-
const meta = {
|
|
8643
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
8644
|
-
clientInfo
|
|
8645
|
-
};
|
|
8646
|
-
const cleanParams = { ...params };
|
|
8647
|
-
if (cleanParams._inputResponses) {
|
|
8648
|
-
meta.inputResponses = cleanParams._inputResponses;
|
|
8649
|
-
delete cleanParams._inputResponses;
|
|
8650
|
-
}
|
|
8651
|
-
const body = JSON.stringify({
|
|
8652
|
-
jsonrpc: "2.0",
|
|
8653
|
-
id,
|
|
8654
|
-
method,
|
|
8655
|
-
params: { ...cleanParams, _meta: meta }
|
|
8656
|
-
});
|
|
8767
|
+
const wireParams = modernRequestParams(params, protocolVersion, clientInfo);
|
|
8657
8768
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8769
|
+
const id = nextId2++;
|
|
8770
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: wireParams });
|
|
8658
8771
|
const { res, token, detach } = await doFetch(method, params, body);
|
|
8659
8772
|
let retry;
|
|
8660
8773
|
try {
|
|
@@ -8674,16 +8787,11 @@ async function createStatelessConnection(options) {
|
|
|
8674
8787
|
}
|
|
8675
8788
|
throw new McpAuthError(401, url);
|
|
8676
8789
|
}
|
|
8677
|
-
const discoverResult2 = await request("server/discover", {
|
|
8678
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
8679
|
-
});
|
|
8790
|
+
const discoverResult2 = await request("server/discover", {});
|
|
8680
8791
|
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
8681
8792
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
8682
|
-
throw new
|
|
8683
|
-
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
8684
|
-
);
|
|
8793
|
+
throw new UnsupportedModernVersionError(STATELESS_PROTOCOL_VERSION, supported);
|
|
8685
8794
|
}
|
|
8686
|
-
negotiatedVersion = STATELESS_PROTOCOL_VERSION;
|
|
8687
8795
|
const extra = buildStatelessMethods(request, discoverResult2);
|
|
8688
8796
|
return {
|
|
8689
8797
|
...extra,
|
|
@@ -8830,14 +8938,13 @@ async function createPiConnection(options) {
|
|
|
8830
8938
|
}
|
|
8831
8939
|
|
|
8832
8940
|
// src/client/stdio-stateless.ts
|
|
8833
|
-
var PROTOCOL_VERSION =
|
|
8941
|
+
var PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
|
|
8834
8942
|
function createStdioJsonRpc(proc, platform2, options) {
|
|
8835
8943
|
const encoder2 = new TextEncoder();
|
|
8836
8944
|
const decoder3 = new TextDecoder();
|
|
8837
8945
|
let nextId2 = 1;
|
|
8838
8946
|
let readBuffer = "";
|
|
8839
8947
|
const reader = proc.stdout.getReader();
|
|
8840
|
-
let negotiatedVersion;
|
|
8841
8948
|
const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
|
|
8842
8949
|
async function readLine2() {
|
|
8843
8950
|
while (true) {
|
|
@@ -8877,20 +8984,11 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8877
8984
|
}
|
|
8878
8985
|
async function request(method, params) {
|
|
8879
8986
|
const id = nextId2++;
|
|
8880
|
-
const meta = {
|
|
8881
|
-
protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
|
|
8882
|
-
clientInfo: { name: "mcpcat", version }
|
|
8883
|
-
};
|
|
8884
|
-
const cleanParams = { ...params };
|
|
8885
|
-
if (cleanParams._inputResponses) {
|
|
8886
|
-
meta.inputResponses = cleanParams._inputResponses;
|
|
8887
|
-
delete cleanParams._inputResponses;
|
|
8888
|
-
}
|
|
8889
8987
|
const body = JSON.stringify({
|
|
8890
8988
|
jsonrpc: "2.0",
|
|
8891
8989
|
id,
|
|
8892
8990
|
method,
|
|
8893
|
-
params:
|
|
8991
|
+
params: modernRequestParams(params, PROTOCOL_VERSION, { name: "mcpcat", version })
|
|
8894
8992
|
});
|
|
8895
8993
|
if (verbose?.enabled) verbose.request(method, body);
|
|
8896
8994
|
await send(body);
|
|
@@ -8924,22 +9022,29 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8924
9022
|
};
|
|
8925
9023
|
}
|
|
8926
9024
|
function buildServerInfo2(discoverResult2) {
|
|
9025
|
+
const serverInfo = discoverServerInfo(discoverResult2);
|
|
8927
9026
|
return {
|
|
8928
|
-
name:
|
|
8929
|
-
version:
|
|
8930
|
-
title:
|
|
9027
|
+
name: serverInfo?.name,
|
|
9028
|
+
version: serverInfo?.version,
|
|
9029
|
+
title: serverInfo?.title,
|
|
8931
9030
|
protocolVersion: PROTOCOL_VERSION,
|
|
8932
9031
|
capabilities: discoverResult2?.capabilities ?? {},
|
|
8933
9032
|
instructions: discoverResult2?.instructions
|
|
8934
9033
|
};
|
|
8935
9034
|
}
|
|
8936
9035
|
function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
9036
|
+
let hidden = {};
|
|
8937
9037
|
return {
|
|
8938
9038
|
async info() {
|
|
8939
9039
|
return buildServerInfo2(discoverResult2);
|
|
8940
9040
|
},
|
|
8941
9041
|
async listTools() {
|
|
8942
|
-
|
|
9042
|
+
const result = await rpc.request("tools/list", {});
|
|
9043
|
+
hidden = readHiddenTools(result);
|
|
9044
|
+
return result?.tools ?? [];
|
|
9045
|
+
},
|
|
9046
|
+
hiddenTool(name) {
|
|
9047
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8943
9048
|
},
|
|
8944
9049
|
async callTool(name, args2, callOptions) {
|
|
8945
9050
|
const params = { name, arguments: args2 ?? {} };
|
|
@@ -8995,25 +9100,27 @@ async function probeStdioStateless(options) {
|
|
|
8995
9100
|
});
|
|
8996
9101
|
let discoverRaw;
|
|
8997
9102
|
try {
|
|
8998
|
-
discoverRaw = await rpc.request("server/discover", {
|
|
8999
|
-
protocolVersion: PROTOCOL_VERSION
|
|
9000
|
-
});
|
|
9103
|
+
discoverRaw = await rpc.request("server/discover", {});
|
|
9001
9104
|
} catch (err) {
|
|
9002
9105
|
const pending = rpc.pendingBuffer();
|
|
9003
9106
|
rpc.close();
|
|
9004
9107
|
return { proc, pendingStdout: pending, error: err };
|
|
9005
9108
|
}
|
|
9006
|
-
void readStderr(proc, options.verbose);
|
|
9007
9109
|
const discoverResult2 = discoverRaw;
|
|
9008
|
-
const
|
|
9110
|
+
const advertised = discoverResult2?.supportedVersions;
|
|
9111
|
+
const supported = (Array.isArray(advertised) ? advertised : []).filter(
|
|
9009
9112
|
(v) => typeof v === "string"
|
|
9010
9113
|
);
|
|
9011
9114
|
if (!supported.includes(PROTOCOL_VERSION)) {
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9115
|
+
const pending = rpc.pendingBuffer();
|
|
9116
|
+
rpc.close();
|
|
9117
|
+
return {
|
|
9118
|
+
proc,
|
|
9119
|
+
pendingStdout: pending,
|
|
9120
|
+
error: new UnsupportedModernVersionError(PROTOCOL_VERSION, supported)
|
|
9121
|
+
};
|
|
9016
9122
|
}
|
|
9123
|
+
void readStderr(proc, options.verbose);
|
|
9017
9124
|
return buildConnectionMethods(rpc, discoverResult2, proc);
|
|
9018
9125
|
}
|
|
9019
9126
|
async function readStderr(proc, verbose) {
|
|
@@ -9057,16 +9164,7 @@ async function connect(platform2, options) {
|
|
|
9057
9164
|
verbose: options.verbose
|
|
9058
9165
|
});
|
|
9059
9166
|
if (!("proc" in result)) return result;
|
|
9060
|
-
|
|
9061
|
-
const isUnsupported = result.error.message.includes("does not support");
|
|
9062
|
-
if (options.protocolVersion?.startsWith("2026")) {
|
|
9063
|
-
try {
|
|
9064
|
-
await result.proc.kill("SIGTERM");
|
|
9065
|
-
} catch {
|
|
9066
|
-
}
|
|
9067
|
-
throw result.error;
|
|
9068
|
-
}
|
|
9069
|
-
if (!isMethodNotFound && !isUnsupported) {
|
|
9167
|
+
if (options.protocolVersion?.startsWith("2026") || !indicatesLegacyServer(result.error, false)) {
|
|
9070
9168
|
try {
|
|
9071
9169
|
await result.proc.kill("SIGTERM");
|
|
9072
9170
|
} catch {
|
|
@@ -9109,12 +9207,11 @@ async function connect(platform2, options) {
|
|
|
9109
9207
|
redact: options.redact
|
|
9110
9208
|
});
|
|
9111
9209
|
} catch (err) {
|
|
9112
|
-
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
9113
9210
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
9114
9211
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
9115
9212
|
if (err instanceof TransportRefusalError) throw err;
|
|
9116
9213
|
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
9117
|
-
if (
|
|
9214
|
+
if (indicatesLegacyServer(err, true)) {
|
|
9118
9215
|
return createPiConnection({
|
|
9119
9216
|
url: options.url,
|
|
9120
9217
|
headers: options.headers,
|
|
@@ -9141,7 +9238,7 @@ function readLine(platform2) {
|
|
|
9141
9238
|
return new Promise((resolve, reject) => {
|
|
9142
9239
|
const reader = platform2.stdin.getReader();
|
|
9143
9240
|
const chunks = [];
|
|
9144
|
-
function
|
|
9241
|
+
function pump2() {
|
|
9145
9242
|
reader.read().then(({ done, value }) => {
|
|
9146
9243
|
if (done) {
|
|
9147
9244
|
reader.releaseLock();
|
|
@@ -9155,10 +9252,10 @@ function readLine(platform2) {
|
|
|
9155
9252
|
resolve(text.split("\n")[0]);
|
|
9156
9253
|
return;
|
|
9157
9254
|
}
|
|
9158
|
-
|
|
9255
|
+
pump2();
|
|
9159
9256
|
}, reject);
|
|
9160
9257
|
}
|
|
9161
|
-
|
|
9258
|
+
pump2();
|
|
9162
9259
|
});
|
|
9163
9260
|
}
|
|
9164
9261
|
function concatChunks(chunks) {
|
|
@@ -10865,11 +10962,21 @@ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPriva
|
|
|
10865
10962
|
}
|
|
10866
10963
|
return events;
|
|
10867
10964
|
}
|
|
10965
|
+
function sseResponse(text, id) {
|
|
10966
|
+
for (const block of text.replace(/\r\n?/g, "\n").split("\n\n")) {
|
|
10967
|
+
const data = block.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
10968
|
+
const parsed = tryParseJson(data);
|
|
10969
|
+
if (isRecord4(parsed) && parsed.id === id) return parsed;
|
|
10970
|
+
}
|
|
10971
|
+
return void 0;
|
|
10972
|
+
}
|
|
10868
10973
|
function mcpJsonRpc(method, params, id = 1) {
|
|
10869
10974
|
return JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
10870
10975
|
}
|
|
10871
10976
|
function parseServerInfo(result) {
|
|
10872
|
-
const
|
|
10977
|
+
const meta = isRecord4(result._meta) ? result._meta : void 0;
|
|
10978
|
+
const fromMeta = meta && isRecord4(meta[META_SERVER_INFO]) ? meta[META_SERVER_INFO] : void 0;
|
|
10979
|
+
const si = fromMeta ?? (isRecord4(result.serverInfo) ? result.serverInfo : void 0);
|
|
10873
10980
|
if (!si) return void 0;
|
|
10874
10981
|
return {
|
|
10875
10982
|
name: typeof si.name === "string" ? si.name : void 0,
|
|
@@ -10885,7 +10992,7 @@ function checkAuthResponse(resp) {
|
|
|
10885
10992
|
async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
10886
10993
|
const headers = {
|
|
10887
10994
|
"Content-Type": "application/json",
|
|
10888
|
-
Accept: "application/json",
|
|
10995
|
+
Accept: "application/json, text/event-stream",
|
|
10889
10996
|
...extraHeaders
|
|
10890
10997
|
};
|
|
10891
10998
|
try {
|
|
@@ -10900,7 +11007,8 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
|
10900
11007
|
if (authSignal) return { url, trust: [authSignal] };
|
|
10901
11008
|
if (resp.status === 200) {
|
|
10902
11009
|
const text = await resp.text();
|
|
10903
|
-
const
|
|
11010
|
+
const sse = (resp.headers.get("content-type") ?? "").includes("text/event-stream");
|
|
11011
|
+
const parsed = sse ? sseResponse(text, tryParseJson(body)?.id) : tryParseJson(text);
|
|
10904
11012
|
if (isRecord4(parsed) && isRecord4(parsed.result)) {
|
|
10905
11013
|
return { url, serverInfo: parseServerInfo(parsed.result), trust: [] };
|
|
10906
11014
|
}
|
|
@@ -10911,18 +11019,22 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
|
10911
11019
|
}
|
|
10912
11020
|
async function probeMcpEndpoint(fetcher, url) {
|
|
10913
11021
|
const discoverBody = mcpJsonRpc("server/discover", {
|
|
10914
|
-
|
|
10915
|
-
clientInfo: { name: "mcpcat", version: "0.0.0" }
|
|
11022
|
+
_meta: modernRequestMeta(MODERN_PROTOCOL_VERSION, { name: "mcpcat", version: "0.0.0" })
|
|
10916
11023
|
});
|
|
10917
11024
|
const hit = await tryMcpPost(fetcher, url, discoverBody, {
|
|
11025
|
+
"MCP-Protocol-Version": MODERN_PROTOCOL_VERSION,
|
|
10918
11026
|
"Mcp-Method": "server/discover"
|
|
10919
11027
|
});
|
|
10920
11028
|
if (hit) return hit;
|
|
10921
|
-
const initBody = mcpJsonRpc(
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
11029
|
+
const initBody = mcpJsonRpc(
|
|
11030
|
+
"initialize",
|
|
11031
|
+
{
|
|
11032
|
+
protocolVersion: "2025-11-25",
|
|
11033
|
+
clientInfo: { name: "mcpcat", version: "0.0.0" },
|
|
11034
|
+
capabilities: {}
|
|
11035
|
+
},
|
|
11036
|
+
2
|
|
11037
|
+
);
|
|
10926
11038
|
return tryMcpPost(fetcher, url, initBody);
|
|
10927
11039
|
}
|
|
10928
11040
|
async function probeMcp(fetcher, origin, page, targetUrl) {
|
|
@@ -12037,7 +12149,7 @@ async function writeResponse(writer, response) {
|
|
|
12037
12149
|
var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
12038
12150
|
var OUTPUT_CAP = 1048576;
|
|
12039
12151
|
var JSON_CT = "application/json; charset=utf-8";
|
|
12040
|
-
async function handleJsonRpc(raw,
|
|
12152
|
+
async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
|
|
12041
12153
|
let parsed;
|
|
12042
12154
|
try {
|
|
12043
12155
|
parsed = JSON.parse(raw);
|
|
@@ -12051,19 +12163,35 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
|
|
|
12051
12163
|
if (!hasOwn(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
12052
12164
|
const id = readId(parsed);
|
|
12053
12165
|
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
12166
|
+
return completeModernOutcome(
|
|
12167
|
+
await dispatch(id, method, params, source, call, serverName, serverVersion),
|
|
12168
|
+
params
|
|
12169
|
+
);
|
|
12170
|
+
}
|
|
12171
|
+
async function dispatch(id, method, params, source, call, serverName, serverVersion) {
|
|
12054
12172
|
if (method === "initialize")
|
|
12055
12173
|
return rpcOk(id, initializeResult(params, serverName, serverVersion));
|
|
12056
12174
|
if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
|
|
12057
|
-
if (method === "tools/list") return rpcOk(id, { tools });
|
|
12058
12175
|
if (method === "ping") return rpcOk(id, {});
|
|
12059
|
-
if (method === "tools/
|
|
12176
|
+
if (method === "tools/list" || method === "tools/call") {
|
|
12177
|
+
const listing = Array.isArray(source) ? { tools: source } : await source();
|
|
12178
|
+
if (listing.error) return rpcError(id, -32e3, listing.error);
|
|
12179
|
+
if (method === "tools/call") return toolCall(id, params, listing, call);
|
|
12180
|
+
const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
|
|
12181
|
+
return rpcOk(id, {
|
|
12182
|
+
tools: listing.tools,
|
|
12183
|
+
...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
|
|
12184
|
+
});
|
|
12185
|
+
}
|
|
12060
12186
|
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
12061
12187
|
}
|
|
12062
|
-
async function toolCall(id, params, tools, call) {
|
|
12188
|
+
async function toolCall(id, params, { tools, hidden }, call) {
|
|
12063
12189
|
const name = params && typeof params === "object" ? readString(params, "name") : void 0;
|
|
12064
12190
|
if (!name) return rpcError(id, -32602, "tools/call requires a name");
|
|
12065
|
-
if (!tools.some((tool) => tool.name === name))
|
|
12066
|
-
|
|
12191
|
+
if (!tools.some((tool) => tool.name === name)) {
|
|
12192
|
+
const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
12193
|
+
return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
|
|
12194
|
+
}
|
|
12067
12195
|
const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
12068
12196
|
try {
|
|
12069
12197
|
const result = await call(name, args2);
|
|
@@ -12095,12 +12223,11 @@ function initializeResult(params, serverName, serverVersion) {
|
|
|
12095
12223
|
};
|
|
12096
12224
|
}
|
|
12097
12225
|
function discoverResult(serverName, serverVersion) {
|
|
12098
|
-
return
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
};
|
|
12226
|
+
return modernDiscoverResult(
|
|
12227
|
+
PROTOCOL_VERSIONS,
|
|
12228
|
+
{ tools: { listChanged: false } },
|
|
12229
|
+
{ name: serverName, version: serverVersion }
|
|
12230
|
+
);
|
|
12104
12231
|
}
|
|
12105
12232
|
function cap(value) {
|
|
12106
12233
|
if (value.length <= OUTPUT_CAP) return value;
|
|
@@ -12236,15 +12363,18 @@ function translateCapabilities(info) {
|
|
|
12236
12363
|
if (info.capabilities.prompts) caps.prompts = { listChanged: false };
|
|
12237
12364
|
return caps;
|
|
12238
12365
|
}
|
|
12366
|
+
function proxyServerInfo(info) {
|
|
12367
|
+
return {
|
|
12368
|
+
name: info.name ?? "mcpcat-proxy",
|
|
12369
|
+
version: info.version ?? "0.0.0",
|
|
12370
|
+
...info.title ? { title: info.title } : {}
|
|
12371
|
+
};
|
|
12372
|
+
}
|
|
12239
12373
|
function buildServerResult(info, protocolVersion) {
|
|
12240
12374
|
return {
|
|
12241
12375
|
protocolVersion,
|
|
12242
12376
|
capabilities: translateCapabilities(info),
|
|
12243
|
-
serverInfo:
|
|
12244
|
-
name: info.name ?? "mcpcat-proxy",
|
|
12245
|
-
version: info.version ?? "0.0.0",
|
|
12246
|
-
...info.title ? { title: info.title } : {}
|
|
12247
|
-
},
|
|
12377
|
+
serverInfo: proxyServerInfo(info),
|
|
12248
12378
|
...info.instructions ? { instructions: info.instructions } : {}
|
|
12249
12379
|
};
|
|
12250
12380
|
}
|
|
@@ -12292,10 +12422,16 @@ function createProxyHandler(options) {
|
|
|
12292
12422
|
return scrubOk(id, buildServerResult(cachedInfo, pv));
|
|
12293
12423
|
}
|
|
12294
12424
|
function handleDiscover(id) {
|
|
12295
|
-
|
|
12296
|
-
|
|
12297
|
-
|
|
12298
|
-
|
|
12425
|
+
const info = cachedInfo;
|
|
12426
|
+
return scrubOk(
|
|
12427
|
+
id,
|
|
12428
|
+
modernDiscoverResult(
|
|
12429
|
+
PROTOCOL_VERSIONS2,
|
|
12430
|
+
translateCapabilities(info),
|
|
12431
|
+
proxyServerInfo(info),
|
|
12432
|
+
info.instructions
|
|
12433
|
+
)
|
|
12434
|
+
);
|
|
12299
12435
|
}
|
|
12300
12436
|
async function forwardWithRetry(method, doForward, id) {
|
|
12301
12437
|
try {
|
|
@@ -12322,7 +12458,7 @@ function createProxyHandler(options) {
|
|
|
12322
12458
|
return scrubErr(id, -32e3, err.message);
|
|
12323
12459
|
}
|
|
12324
12460
|
}
|
|
12325
|
-
async function
|
|
12461
|
+
async function dispatch2(method, id, params) {
|
|
12326
12462
|
try {
|
|
12327
12463
|
await getConnection();
|
|
12328
12464
|
} catch (err) {
|
|
@@ -12359,7 +12495,7 @@ function createProxyHandler(options) {
|
|
|
12359
12495
|
if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
12360
12496
|
const id = readId2(parsed);
|
|
12361
12497
|
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
12362
|
-
return
|
|
12498
|
+
return completeModernOutcome(await dispatch2(method, id, params), params);
|
|
12363
12499
|
}
|
|
12364
12500
|
async function shutdown() {
|
|
12365
12501
|
if (conn) {
|
|
@@ -12427,7 +12563,149 @@ ${tail}`,
|
|
|
12427
12563
|
}
|
|
12428
12564
|
};
|
|
12429
12565
|
}
|
|
12430
|
-
|
|
12566
|
+
var DIALOG_POLL_MS = 250;
|
|
12567
|
+
var DIALOG_READY_MS = 1e3;
|
|
12568
|
+
var DIALOG_STALL_MS = 1e3;
|
|
12569
|
+
function watchLoop(watch) {
|
|
12570
|
+
let timer;
|
|
12571
|
+
const result = new Promise((resolve) => {
|
|
12572
|
+
timer = setInterval(() => {
|
|
12573
|
+
const found = watch();
|
|
12574
|
+
if (found) {
|
|
12575
|
+
clearInterval(timer);
|
|
12576
|
+
resolve(found);
|
|
12577
|
+
}
|
|
12578
|
+
}, DIALOG_POLL_MS);
|
|
12579
|
+
});
|
|
12580
|
+
return {
|
|
12581
|
+
result,
|
|
12582
|
+
stop() {
|
|
12583
|
+
clearInterval(timer);
|
|
12584
|
+
}
|
|
12585
|
+
};
|
|
12586
|
+
}
|
|
12587
|
+
async function browserSocketUrl(platform2, cdp) {
|
|
12588
|
+
if (/^wss?:\/\//i.test(cdp)) return cdp;
|
|
12589
|
+
if (!/^https?:\/\//i.test(cdp)) return void 0;
|
|
12590
|
+
const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
|
|
12591
|
+
signal: AbortSignal.timeout(DIALOG_READY_MS)
|
|
12592
|
+
});
|
|
12593
|
+
const body = await res.json();
|
|
12594
|
+
return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
|
|
12595
|
+
}
|
|
12596
|
+
function parseCdp(data) {
|
|
12597
|
+
try {
|
|
12598
|
+
return JSON.parse(
|
|
12599
|
+
typeof data === "string" ? data : new TextDecoder().decode(data)
|
|
12600
|
+
);
|
|
12601
|
+
} catch {
|
|
12602
|
+
return void 0;
|
|
12603
|
+
}
|
|
12604
|
+
}
|
|
12605
|
+
function applyCdp(state, channel, send, msg, settle) {
|
|
12606
|
+
if (msg.method === "Page.javascriptDialogOpening") {
|
|
12607
|
+
state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
|
|
12608
|
+
} else if (msg.method === "Page.javascriptDialogClosed") {
|
|
12609
|
+
state.open = void 0;
|
|
12610
|
+
} else if (msg.method === "Target.detachedFromTarget") {
|
|
12611
|
+
channel.close();
|
|
12612
|
+
} else if (msg.id === 1) {
|
|
12613
|
+
const sessionId = msg.result?.sessionId;
|
|
12614
|
+
if (typeof sessionId !== "string") {
|
|
12615
|
+
channel.close();
|
|
12616
|
+
return;
|
|
12617
|
+
}
|
|
12618
|
+
state.enableSentAt = Date.now();
|
|
12619
|
+
send({ id: 2, sessionId, method: "Page.enable" });
|
|
12620
|
+
} else if (msg.id === 2) {
|
|
12621
|
+
state.enableSentAt = void 0;
|
|
12622
|
+
settle();
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
async function pump(state, channel, send, settle) {
|
|
12626
|
+
const reader = channel.readable.getReader();
|
|
12627
|
+
try {
|
|
12628
|
+
for (; ; ) {
|
|
12629
|
+
const { done, value } = await reader.read();
|
|
12630
|
+
if (done) return;
|
|
12631
|
+
const msg = parseCdp(value);
|
|
12632
|
+
if (msg) applyCdp(state, channel, send, msg, settle);
|
|
12633
|
+
}
|
|
12634
|
+
} catch {
|
|
12635
|
+
return;
|
|
12636
|
+
}
|
|
12637
|
+
}
|
|
12638
|
+
async function attachSocket(platform2, state, url, targetId) {
|
|
12639
|
+
const channel = await platform2.websocket(url).catch(() => void 0);
|
|
12640
|
+
if (!channel) return;
|
|
12641
|
+
state.channel = channel;
|
|
12642
|
+
const writer = channel.writable.getWriter();
|
|
12643
|
+
const send = (message) => {
|
|
12644
|
+
writer.write(JSON.stringify(message)).catch(() => channel.close());
|
|
12645
|
+
};
|
|
12646
|
+
let settle;
|
|
12647
|
+
const settled = new Promise((resolve) => {
|
|
12648
|
+
settle = resolve;
|
|
12649
|
+
});
|
|
12650
|
+
void pump(state, channel, send, settle).then(() => {
|
|
12651
|
+
if (state.channel === channel) {
|
|
12652
|
+
state.channel = void 0;
|
|
12653
|
+
state.open = void 0;
|
|
12654
|
+
state.enableSentAt = void 0;
|
|
12655
|
+
}
|
|
12656
|
+
channel.close();
|
|
12657
|
+
settle();
|
|
12658
|
+
});
|
|
12659
|
+
send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
|
|
12660
|
+
await settled;
|
|
12661
|
+
}
|
|
12662
|
+
function watchJsDialogs(platform2, cdp, targetId) {
|
|
12663
|
+
const state = {};
|
|
12664
|
+
let connecting;
|
|
12665
|
+
let closed = false;
|
|
12666
|
+
const connect2 = async () => {
|
|
12667
|
+
if (!platform2.websocket) return;
|
|
12668
|
+
const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
|
|
12669
|
+
if (!url || closed) return;
|
|
12670
|
+
await attachSocket(platform2, state, url, targetId);
|
|
12671
|
+
};
|
|
12672
|
+
return {
|
|
12673
|
+
async ready() {
|
|
12674
|
+
if (closed || state.channel) return;
|
|
12675
|
+
connecting ??= connect2().finally(() => {
|
|
12676
|
+
connecting = void 0;
|
|
12677
|
+
});
|
|
12678
|
+
let timer;
|
|
12679
|
+
await Promise.race([
|
|
12680
|
+
connecting,
|
|
12681
|
+
new Promise((resolve) => {
|
|
12682
|
+
timer = setTimeout(resolve, DIALOG_READY_MS);
|
|
12683
|
+
})
|
|
12684
|
+
]);
|
|
12685
|
+
clearTimeout(timer);
|
|
12686
|
+
},
|
|
12687
|
+
dialog: () => state.open,
|
|
12688
|
+
stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
|
|
12689
|
+
close() {
|
|
12690
|
+
closed = true;
|
|
12691
|
+
state.channel?.close();
|
|
12692
|
+
}
|
|
12693
|
+
};
|
|
12694
|
+
}
|
|
12695
|
+
function dialogMessage(tab, type) {
|
|
12696
|
+
return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
|
|
12697
|
+
}
|
|
12698
|
+
function timeoutMessage(verb, timeoutMs, tab, watcher) {
|
|
12699
|
+
const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
|
|
12700
|
+
if (!watcher) {
|
|
12701
|
+
return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
|
|
12702
|
+
}
|
|
12703
|
+
if (watcher.stalled()) {
|
|
12704
|
+
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`;
|
|
12705
|
+
}
|
|
12706
|
+
return head;
|
|
12707
|
+
}
|
|
12708
|
+
async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
|
|
12431
12709
|
let proc;
|
|
12432
12710
|
try {
|
|
12433
12711
|
proc = await platform2.spawn({ argv });
|
|
@@ -12471,12 +12749,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12471
12749
|
readStream3(proc.stderr, err),
|
|
12472
12750
|
proc.wait()
|
|
12473
12751
|
]);
|
|
12474
|
-
const
|
|
12752
|
+
const watcher = watch ? watchLoop(watch) : void 0;
|
|
12753
|
+
const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
|
|
12754
|
+
});
|
|
12755
|
+
const result = await Promise.race([work, timer, blocked]);
|
|
12475
12756
|
clearTimeout(timeoutId);
|
|
12757
|
+
watcher?.stop();
|
|
12476
12758
|
if (result === "timeout") {
|
|
12477
12759
|
await proc.kill("SIGTERM");
|
|
12478
12760
|
return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
|
|
12479
12761
|
}
|
|
12762
|
+
if (!Array.isArray(result)) {
|
|
12763
|
+
await proc.kill("SIGTERM");
|
|
12764
|
+
return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
|
|
12765
|
+
}
|
|
12480
12766
|
const [, , exitResult] = result;
|
|
12481
12767
|
const stdout = out.finish();
|
|
12482
12768
|
const stderr = err.finish();
|
|
@@ -12487,7 +12773,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12487
12773
|
cut: stdout.cut
|
|
12488
12774
|
};
|
|
12489
12775
|
}
|
|
12490
|
-
async function
|
|
12776
|
+
async function listTabs(platform2, options) {
|
|
12491
12777
|
const argv = [options.playwrightCli, "tab-list"];
|
|
12492
12778
|
if (options.cdp) argv.push("--cdp", options.cdp);
|
|
12493
12779
|
const result = await runProcess(
|
|
@@ -12514,6 +12800,13 @@ async function resolveWebMcpTab(platform2, options) {
|
|
|
12514
12800
|
if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
|
|
12515
12801
|
return { error: "could not read playwright-cli tab-list" };
|
|
12516
12802
|
}
|
|
12803
|
+
return { rows };
|
|
12804
|
+
}
|
|
12805
|
+
async function resolveWebMcpTab(platform2, options) {
|
|
12806
|
+
const listed = await listTabs(platform2, options);
|
|
12807
|
+
if ("error" in listed) return listed;
|
|
12808
|
+
const { rows } = listed;
|
|
12809
|
+
const active = rows.filter((r) => r.active);
|
|
12517
12810
|
if (active.length === 1) return { tab: active[0].id };
|
|
12518
12811
|
if (rows.length === 1) return { tab: rows[0].id };
|
|
12519
12812
|
return {
|
|
@@ -12576,20 +12869,57 @@ function ambiguityError(name, peers) {
|
|
|
12576
12869
|
`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
12870
|
);
|
|
12578
12871
|
}
|
|
12872
|
+
async function blockedNow(watcher, tab) {
|
|
12873
|
+
if (!watcher) return void 0;
|
|
12874
|
+
await watcher.ready();
|
|
12875
|
+
const open2 = watcher.dialog();
|
|
12876
|
+
return open2 ? dialogMessage(tab, open2) : void 0;
|
|
12877
|
+
}
|
|
12878
|
+
async function tabGone(platform2, options) {
|
|
12879
|
+
const { tab } = options;
|
|
12880
|
+
if (!tab) return void 0;
|
|
12881
|
+
const listed = await listTabs(platform2, options);
|
|
12882
|
+
if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
|
|
12883
|
+
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)`;
|
|
12884
|
+
}
|
|
12885
|
+
function hiddenToolMessage(name) {
|
|
12886
|
+
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`;
|
|
12887
|
+
}
|
|
12579
12888
|
function createWebMcpHandler(platform2, options) {
|
|
12580
12889
|
let cache;
|
|
12581
12890
|
const baseArgs = buildBaseArgs(options);
|
|
12582
12891
|
const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
|
|
12892
|
+
const { cdp, tab } = options;
|
|
12893
|
+
const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
|
|
12894
|
+
const watch = watcher ? () => watcher.dialog() : void 0;
|
|
12583
12895
|
async function listRaw(fresh = false) {
|
|
12896
|
+
const blocked = await blockedNow(watcher, tab);
|
|
12897
|
+
if (blocked) return { tools: [], error: blocked };
|
|
12584
12898
|
const now = Date.now();
|
|
12585
12899
|
if (!fresh && cache && now < cache.expiresAt) {
|
|
12586
12900
|
return { tools: cache.tools };
|
|
12587
12901
|
}
|
|
12588
12902
|
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12589
|
-
const result = await runProcess(
|
|
12903
|
+
const result = await runProcess(
|
|
12904
|
+
platform2,
|
|
12905
|
+
argv,
|
|
12906
|
+
void 0,
|
|
12907
|
+
options.callTimeoutMs,
|
|
12908
|
+
listLimit,
|
|
12909
|
+
watch
|
|
12910
|
+
);
|
|
12590
12911
|
if (result.exitCode === -1) {
|
|
12591
12912
|
return { tools: [], error: result.stderr, notFound: true };
|
|
12592
12913
|
}
|
|
12914
|
+
if (result.blocked) {
|
|
12915
|
+
return { tools: [], error: dialogMessage(tab, result.blocked) };
|
|
12916
|
+
}
|
|
12917
|
+
if (result.exitCode === 124) {
|
|
12918
|
+
return {
|
|
12919
|
+
tools: [],
|
|
12920
|
+
error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
|
|
12921
|
+
};
|
|
12922
|
+
}
|
|
12593
12923
|
if (result.exitCode !== 0) {
|
|
12594
12924
|
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12595
12925
|
}
|
|
@@ -12605,6 +12935,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12605
12935
|
if (!Array.isArray(parsed)) {
|
|
12606
12936
|
return { tools: [], error: "webmcp-list did not return an array" };
|
|
12607
12937
|
}
|
|
12938
|
+
if (parsed.length === 0) {
|
|
12939
|
+
const gone2 = await tabGone(platform2, options);
|
|
12940
|
+
if (gone2) return { tools: [], error: gone2 };
|
|
12941
|
+
}
|
|
12608
12942
|
const tools = parsed;
|
|
12609
12943
|
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12610
12944
|
return { tools };
|
|
@@ -12612,11 +12946,13 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12612
12946
|
async function getRpcTools() {
|
|
12613
12947
|
const { tools, error, notFound } = await listRaw();
|
|
12614
12948
|
if (notFound) return null;
|
|
12615
|
-
if (error) return [];
|
|
12949
|
+
if (error) return { tools: [], error };
|
|
12616
12950
|
const rpcTools = [];
|
|
12951
|
+
const hidden = {};
|
|
12617
12952
|
for (const { name, tool, peers } of exposedTools(tools)) {
|
|
12618
12953
|
if (peers.length > 1) continue;
|
|
12619
12954
|
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12955
|
+
hidden[name] = hiddenToolMessage(name);
|
|
12620
12956
|
continue;
|
|
12621
12957
|
}
|
|
12622
12958
|
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
@@ -12627,7 +12963,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12627
12963
|
annotations: mapped
|
|
12628
12964
|
});
|
|
12629
12965
|
}
|
|
12630
|
-
return rpcTools;
|
|
12966
|
+
return { tools: rpcTools, hidden };
|
|
12631
12967
|
}
|
|
12632
12968
|
async function callTool(name, args2) {
|
|
12633
12969
|
const { tools, error } = await listRaw(true);
|
|
@@ -12652,11 +12988,18 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12652
12988
|
argv,
|
|
12653
12989
|
stdinData,
|
|
12654
12990
|
options.callTimeoutMs,
|
|
12655
|
-
options.maxOutput
|
|
12991
|
+
options.maxOutput,
|
|
12992
|
+
watch
|
|
12656
12993
|
);
|
|
12657
12994
|
if (result.exitCode === -1) {
|
|
12658
12995
|
throw new Error(result.stderr || "playwright-cli not found");
|
|
12659
12996
|
}
|
|
12997
|
+
if (result.blocked) {
|
|
12998
|
+
throw new Error(dialogMessage(tab, result.blocked));
|
|
12999
|
+
}
|
|
13000
|
+
if (result.exitCode === 124) {
|
|
13001
|
+
throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
|
|
13002
|
+
}
|
|
12660
13003
|
if (result.exitCode === 2) {
|
|
12661
13004
|
throw new Error(result.stderr || "webmcp-call error");
|
|
12662
13005
|
}
|
|
@@ -12678,7 +13021,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12678
13021
|
function invalidateCache() {
|
|
12679
13022
|
cache = void 0;
|
|
12680
13023
|
}
|
|
12681
|
-
|
|
13024
|
+
function close() {
|
|
13025
|
+
watcher?.close();
|
|
13026
|
+
}
|
|
13027
|
+
return { getRpcTools, callTool, invalidateCache, close };
|
|
12682
13028
|
}
|
|
12683
13029
|
|
|
12684
13030
|
// src/serve/runtime.ts
|
|
@@ -13441,23 +13787,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13441
13787
|
}
|
|
13442
13788
|
return { connectOpts, hiddenValues };
|
|
13443
13789
|
}
|
|
13444
|
-
function resolvePlaywrightCli(platform2, explicit) {
|
|
13790
|
+
async function resolvePlaywrightCli(platform2, explicit) {
|
|
13445
13791
|
if (explicit) return explicit;
|
|
13446
13792
|
if (platform2.kind === "kernel") return "playwright-cli";
|
|
13447
13793
|
const pathEnv = platform2.env.PATH ?? "";
|
|
13448
|
-
const
|
|
13449
|
-
|
|
13794
|
+
const windows = pathEnv.includes(";");
|
|
13795
|
+
const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
|
|
13796
|
+
for (const dir of pathEnv.split(windows ? ";" : ":")) {
|
|
13450
13797
|
if (!dir) continue;
|
|
13451
|
-
|
|
13798
|
+
for (const name of names) {
|
|
13799
|
+
const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
|
|
13800
|
+
if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
|
|
13801
|
+
}
|
|
13452
13802
|
}
|
|
13453
13803
|
return void 0;
|
|
13454
13804
|
}
|
|
13805
|
+
async function listenOrReport(platform2, options, label) {
|
|
13806
|
+
try {
|
|
13807
|
+
return await platform2.listen({ host: options.host, port: options.port });
|
|
13808
|
+
} catch (err) {
|
|
13809
|
+
const { code: code2, message } = err;
|
|
13810
|
+
const reason = code2 === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
|
|
13811
|
+
const host = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
13812
|
+
await platform2.write(
|
|
13813
|
+
2,
|
|
13814
|
+
sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
|
|
13815
|
+
`)
|
|
13816
|
+
);
|
|
13817
|
+
return 3;
|
|
13818
|
+
}
|
|
13819
|
+
}
|
|
13455
13820
|
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
13456
|
-
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13821
|
+
const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13457
13822
|
if (!cliPath) {
|
|
13458
13823
|
await platform2.write(
|
|
13459
13824
|
2,
|
|
13460
|
-
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13825
|
+
"mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13461
13826
|
);
|
|
13462
13827
|
return 3;
|
|
13463
13828
|
}
|
|
@@ -13492,6 +13857,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13492
13857
|
callTimeoutMs: options.callTimeout,
|
|
13493
13858
|
maxOutput: options.maxOutput
|
|
13494
13859
|
});
|
|
13860
|
+
try {
|
|
13861
|
+
return await serveWebMcpHandler(
|
|
13862
|
+
platform2,
|
|
13863
|
+
options,
|
|
13864
|
+
handler,
|
|
13865
|
+
version,
|
|
13866
|
+
resolvedBearer,
|
|
13867
|
+
generatedBearer
|
|
13868
|
+
);
|
|
13869
|
+
} finally {
|
|
13870
|
+
handler.close();
|
|
13871
|
+
}
|
|
13872
|
+
}
|
|
13873
|
+
async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
|
|
13495
13874
|
const probe = await handler.getRpcTools();
|
|
13496
13875
|
if (probe === null) {
|
|
13497
13876
|
await platform2.write(
|
|
@@ -13501,7 +13880,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13501
13880
|
return 3;
|
|
13502
13881
|
}
|
|
13503
13882
|
const serverName = "mcpcat-webmcp";
|
|
13504
|
-
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
13883
|
+
const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
|
|
13505
13884
|
const callTool = (name, args2) => handler.callTool(name, args2);
|
|
13506
13885
|
if (options.transport === "stdio") {
|
|
13507
13886
|
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
@@ -13532,8 +13911,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
|
|
|
13532
13911
|
const line = buffer.slice(0, nlIdx).trim();
|
|
13533
13912
|
buffer = buffer.slice(nlIdx + 1);
|
|
13534
13913
|
if (!line) continue;
|
|
13535
|
-
const
|
|
13536
|
-
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
13914
|
+
const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
|
|
13537
13915
|
if (outcome.status === 202) continue;
|
|
13538
13916
|
const writer = platform2.stdout.getWriter();
|
|
13539
13917
|
await writer.write(enc.encode(`${outcome.body}
|
|
@@ -13560,7 +13938,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13560
13938
|
return 2;
|
|
13561
13939
|
}
|
|
13562
13940
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13563
|
-
const listener = await platform2
|
|
13941
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
|
|
13942
|
+
if (typeof listener === "number") return listener;
|
|
13564
13943
|
const actualPort = listener.port;
|
|
13565
13944
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13566
13945
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13666,8 +14045,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13666
14045
|
};
|
|
13667
14046
|
}
|
|
13668
14047
|
}
|
|
13669
|
-
const
|
|
13670
|
-
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
14048
|
+
const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
|
|
13671
14049
|
return {
|
|
13672
14050
|
status: outcome.status,
|
|
13673
14051
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
@@ -13755,10 +14133,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13755
14133
|
return 2;
|
|
13756
14134
|
}
|
|
13757
14135
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13758
|
-
const listener = await platform2
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
14136
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
|
|
14137
|
+
if (typeof listener === "number") {
|
|
14138
|
+
await proxy.shutdown();
|
|
14139
|
+
return listener;
|
|
14140
|
+
}
|
|
13762
14141
|
const actualPort = listener.port;
|
|
13763
14142
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13764
14143
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13939,7 +14318,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13939
14318
|
return 2;
|
|
13940
14319
|
}
|
|
13941
14320
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13942
|
-
const listener = await platform2
|
|
14321
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve");
|
|
14322
|
+
if (typeof listener === "number") return listener;
|
|
13943
14323
|
const actualPort = listener.port;
|
|
13944
14324
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13945
14325
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -16071,6 +16451,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
16071
16451
|
if (toolArgs.length > 0) {
|
|
16072
16452
|
const tools = await conn.listTools();
|
|
16073
16453
|
const tool = tools.find((t) => t.name === toolName);
|
|
16454
|
+
const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
|
|
16455
|
+
if (hidden) {
|
|
16456
|
+
await writeErr(platform2, opts.json ?? false, "usage", hidden);
|
|
16457
|
+
return EXIT_USAGE;
|
|
16458
|
+
}
|
|
16074
16459
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
16075
16460
|
const skipRequired = opts.noValidate || !!dataArg;
|
|
16076
16461
|
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
@@ -17225,6 +17610,34 @@ function nodeListen(options) {
|
|
|
17225
17610
|
});
|
|
17226
17611
|
});
|
|
17227
17612
|
}
|
|
17613
|
+
function hostWebSocket(url) {
|
|
17614
|
+
return new Promise((resolve, reject) => {
|
|
17615
|
+
const ws = new WebSocket(url);
|
|
17616
|
+
ws.binaryType = "arraybuffer";
|
|
17617
|
+
let feed;
|
|
17618
|
+
const readable = new ReadableStream({
|
|
17619
|
+
start(controller) {
|
|
17620
|
+
feed = controller;
|
|
17621
|
+
}
|
|
17622
|
+
});
|
|
17623
|
+
ws.onclose = () => feed.close();
|
|
17624
|
+
ws.onerror = () => reject(new Error(`websocket ${url} failed`));
|
|
17625
|
+
ws.onmessage = (event) => {
|
|
17626
|
+
feed.enqueue(typeof event.data === "string" ? event.data : new Uint8Array(event.data));
|
|
17627
|
+
};
|
|
17628
|
+
ws.onopen = () => {
|
|
17629
|
+
resolve({
|
|
17630
|
+
readable,
|
|
17631
|
+
writable: new WritableStream({
|
|
17632
|
+
write(chunk) {
|
|
17633
|
+
ws.send(chunk);
|
|
17634
|
+
}
|
|
17635
|
+
}),
|
|
17636
|
+
close: () => ws.close()
|
|
17637
|
+
});
|
|
17638
|
+
};
|
|
17639
|
+
});
|
|
17640
|
+
}
|
|
17228
17641
|
function createHostPlatform() {
|
|
17229
17642
|
const encoder2 = new TextEncoder();
|
|
17230
17643
|
return {
|
|
@@ -17232,6 +17645,7 @@ function createHostPlatform() {
|
|
|
17232
17645
|
fetch: globalThis.fetch,
|
|
17233
17646
|
spawn: nodeSpawn,
|
|
17234
17647
|
listen: nodeListen,
|
|
17648
|
+
websocket: hostWebSocket,
|
|
17235
17649
|
fs: hostFs,
|
|
17236
17650
|
env: process.env,
|
|
17237
17651
|
cwd: process.cwd(),
|
|
@@ -17269,5 +17683,5 @@ try {
|
|
|
17269
17683
|
});
|
|
17270
17684
|
} catch {
|
|
17271
17685
|
}
|
|
17272
|
-
var code = await run(platform, "0.20.
|
|
17686
|
+
var code = await run(platform, "0.20.4", args);
|
|
17273
17687
|
process.exit(code);
|