@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/kernel/mcpcat.mjs
CHANGED
|
@@ -7312,7 +7312,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7312
7312
|
let dataLines = [];
|
|
7313
7313
|
let dataBytes = 0;
|
|
7314
7314
|
const maxEventBytes = options.maxEventBytes ?? DEFAULT_MAX_MESSAGE_BYTES;
|
|
7315
|
-
const
|
|
7315
|
+
const dispatch2 = () => {
|
|
7316
7316
|
if (dataLines.length === 0) {
|
|
7317
7317
|
eventName = void 0;
|
|
7318
7318
|
eventId = void 0;
|
|
@@ -7328,7 +7328,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7328
7328
|
const processLine = (rawLine) => {
|
|
7329
7329
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
7330
7330
|
if (line === "") {
|
|
7331
|
-
|
|
7331
|
+
dispatch2();
|
|
7332
7332
|
return;
|
|
7333
7333
|
}
|
|
7334
7334
|
if (line.startsWith(":"))
|
|
@@ -7369,7 +7369,7 @@ async function consumeSseStream(stream, options) {
|
|
|
7369
7369
|
buffered += decoder3.decode();
|
|
7370
7370
|
if (buffered)
|
|
7371
7371
|
processLine(buffered);
|
|
7372
|
-
|
|
7372
|
+
dispatch2();
|
|
7373
7373
|
} finally {
|
|
7374
7374
|
reader.releaseLock();
|
|
7375
7375
|
}
|
|
@@ -7973,18 +7973,118 @@ var PlatformStdioTransport = class {
|
|
|
7973
7973
|
}
|
|
7974
7974
|
};
|
|
7975
7975
|
|
|
7976
|
+
// src/client/modern.ts
|
|
7977
|
+
var MODERN_PROTOCOL_VERSION = "2026-07-28";
|
|
7978
|
+
var META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
|
|
7979
|
+
var META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
|
|
7980
|
+
var META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
|
|
7981
|
+
var META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
|
|
7982
|
+
var HEADER_MISMATCH = -32020;
|
|
7983
|
+
var MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
|
|
7984
|
+
var UNSUPPORTED_PROTOCOL_VERSION = -32022;
|
|
7985
|
+
var MODERN_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
7986
|
+
HEADER_MISMATCH,
|
|
7987
|
+
MISSING_REQUIRED_CLIENT_CAPABILITY,
|
|
7988
|
+
UNSUPPORTED_PROTOCOL_VERSION
|
|
7989
|
+
]);
|
|
7990
|
+
function isModernErrorCode(code) {
|
|
7991
|
+
return MODERN_ERROR_CODES.has(code);
|
|
7992
|
+
}
|
|
7993
|
+
function modernRequestMeta(protocolVersion, clientInfo) {
|
|
7994
|
+
return {
|
|
7995
|
+
[META_PROTOCOL_VERSION]: protocolVersion,
|
|
7996
|
+
[META_CLIENT_INFO]: clientInfo,
|
|
7997
|
+
[META_CLIENT_CAPABILITIES]: {}
|
|
7998
|
+
};
|
|
7999
|
+
}
|
|
8000
|
+
function modernRequestParams(params, protocolVersion, clientInfo) {
|
|
8001
|
+
const { _inputResponses, ...rest } = params;
|
|
8002
|
+
const out = { ...rest };
|
|
8003
|
+
if (_inputResponses) out.inputResponses = _inputResponses;
|
|
8004
|
+
out._meta = modernRequestMeta(protocolVersion, clientInfo);
|
|
8005
|
+
return out;
|
|
8006
|
+
}
|
|
8007
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
8008
|
+
function base64Utf8(value) {
|
|
8009
|
+
const bytes = new TextEncoder().encode(value);
|
|
8010
|
+
let out = "";
|
|
8011
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
8012
|
+
const a = bytes[i];
|
|
8013
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
8014
|
+
const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
|
|
8015
|
+
const n = a << 16 | b << 8 | c;
|
|
8016
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63];
|
|
8017
|
+
out += i + 1 < bytes.length ? B64[n >> 6 & 63] : "=";
|
|
8018
|
+
out += i + 2 < bytes.length ? B64[n & 63] : "=";
|
|
8019
|
+
}
|
|
8020
|
+
return out;
|
|
8021
|
+
}
|
|
8022
|
+
function encodeHeaderValue(value) {
|
|
8023
|
+
const safe = /^[\x20-\x7e\t]*$/.test(value) && value.trim() === value && !(value.startsWith("=?base64?") && value.endsWith("?="));
|
|
8024
|
+
return safe ? value : `=?base64?${base64Utf8(value)}?=`;
|
|
8025
|
+
}
|
|
8026
|
+
function isModernRequest(params) {
|
|
8027
|
+
if (!params || typeof params !== "object") return false;
|
|
8028
|
+
const meta = Object.getOwnPropertyDescriptor(params, "_meta")?.value;
|
|
8029
|
+
if (!meta || typeof meta !== "object") return false;
|
|
8030
|
+
return typeof Object.getOwnPropertyDescriptor(meta, META_PROTOCOL_VERSION)?.value === "string";
|
|
8031
|
+
}
|
|
8032
|
+
function completeModernOutcome(outcome, params) {
|
|
8033
|
+
if (outcome.status !== 200 || !isModernRequest(params)) return outcome;
|
|
8034
|
+
const frame = JSON.parse(outcome.body);
|
|
8035
|
+
const result = frame.result;
|
|
8036
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return outcome;
|
|
8037
|
+
if (Object.hasOwn(result, "resultType")) return outcome;
|
|
8038
|
+
frame.result = { resultType: "complete", ...result };
|
|
8039
|
+
return { ...outcome, body: JSON.stringify(frame) };
|
|
8040
|
+
}
|
|
8041
|
+
function modernDiscoverResult(supportedVersions, capabilities, serverInfo, instructions) {
|
|
8042
|
+
return {
|
|
8043
|
+
resultType: "complete",
|
|
8044
|
+
supportedVersions,
|
|
8045
|
+
capabilities,
|
|
8046
|
+
serverInfo,
|
|
8047
|
+
...instructions ? { instructions } : {},
|
|
8048
|
+
_meta: { [META_SERVER_INFO]: serverInfo }
|
|
8049
|
+
};
|
|
8050
|
+
}
|
|
8051
|
+
|
|
7976
8052
|
// src/client/stateless-connection.ts
|
|
7977
|
-
var STATELESS_PROTOCOL_VERSION =
|
|
8053
|
+
var STATELESS_PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
|
|
7978
8054
|
var StatelessRpcError = class extends Error {
|
|
7979
8055
|
code;
|
|
7980
8056
|
data;
|
|
7981
|
-
|
|
8057
|
+
httpStatus;
|
|
8058
|
+
constructor(code, message, data, httpStatus) {
|
|
7982
8059
|
super(`MCP RPC error ${code}: ${message}`);
|
|
7983
8060
|
this.name = "StatelessRpcError";
|
|
7984
8061
|
this.code = code;
|
|
7985
8062
|
this.data = data;
|
|
8063
|
+
this.httpStatus = httpStatus;
|
|
8064
|
+
}
|
|
8065
|
+
};
|
|
8066
|
+
var UnsupportedModernVersionError = class extends Error {
|
|
8067
|
+
constructor(version, supported) {
|
|
8068
|
+
super(`Server does not support ${version} (advertised: ${supported.join(", ") || "none"})`);
|
|
8069
|
+
this.name = "UnsupportedModernVersionError";
|
|
7986
8070
|
}
|
|
7987
8071
|
};
|
|
8072
|
+
function supportedList(data) {
|
|
8073
|
+
const list = data?.supported;
|
|
8074
|
+
return Array.isArray(list) ? list.filter((v) => typeof v === "string") : void 0;
|
|
8075
|
+
}
|
|
8076
|
+
function indicatesLegacyServer(err, http) {
|
|
8077
|
+
if (err instanceof UnsupportedModernVersionError) return true;
|
|
8078
|
+
if (http && err instanceof StatelessHttpError) return err.status >= 400 && err.status < 500;
|
|
8079
|
+
if (!(err instanceof StatelessRpcError)) return false;
|
|
8080
|
+
if (err.code === UNSUPPORTED_PROTOCOL_VERSION) {
|
|
8081
|
+
const supported = supportedList(err.data);
|
|
8082
|
+
return !!supported && !supported.includes(STATELESS_PROTOCOL_VERSION);
|
|
8083
|
+
}
|
|
8084
|
+
if (isModernErrorCode(err.code)) return false;
|
|
8085
|
+
if (!http || err.code === -32601) return true;
|
|
8086
|
+
return err.httpStatus !== void 0 && err.httpStatus >= 400 && err.httpStatus < 500;
|
|
8087
|
+
}
|
|
7988
8088
|
var StatelessHttpError = class extends Error {
|
|
7989
8089
|
status;
|
|
7990
8090
|
constructor(status, statusText, method, snippet) {
|
|
@@ -8096,18 +8196,34 @@ function logHeaders(verbose, dir, entries) {
|
|
|
8096
8196
|
else verbose.headerIn(k, v);
|
|
8097
8197
|
}
|
|
8098
8198
|
}
|
|
8199
|
+
function discoverServerInfo(discoverResult2) {
|
|
8200
|
+
const meta = discoverResult2?._meta;
|
|
8201
|
+
return meta?.[META_SERVER_INFO] ?? discoverResult2?.serverInfo;
|
|
8202
|
+
}
|
|
8099
8203
|
function buildServerInfo(discoverResult2) {
|
|
8204
|
+
const serverInfo = discoverServerInfo(discoverResult2);
|
|
8100
8205
|
return {
|
|
8101
|
-
name:
|
|
8102
|
-
version:
|
|
8103
|
-
title:
|
|
8206
|
+
name: serverInfo?.name,
|
|
8207
|
+
version: serverInfo?.version,
|
|
8208
|
+
title: serverInfo?.title,
|
|
8104
8209
|
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
8105
8210
|
capabilities: discoverResult2?.capabilities ?? {},
|
|
8106
8211
|
instructions: discoverResult2?.instructions
|
|
8107
8212
|
};
|
|
8108
8213
|
}
|
|
8214
|
+
function readHiddenTools(result) {
|
|
8215
|
+
const meta = result?._meta;
|
|
8216
|
+
const hidden = meta?.["io.mcpcat/hidden"];
|
|
8217
|
+
const out = {};
|
|
8218
|
+
if (!hidden || typeof hidden !== "object" || Array.isArray(hidden)) return out;
|
|
8219
|
+
for (const [name, why] of Object.entries(hidden)) {
|
|
8220
|
+
if (typeof why === "string") out[name] = why;
|
|
8221
|
+
}
|
|
8222
|
+
return out;
|
|
8223
|
+
}
|
|
8109
8224
|
function buildStatelessMethods(requestFn, discoverResult2) {
|
|
8110
8225
|
let serverInfoCache;
|
|
8226
|
+
let hidden = {};
|
|
8111
8227
|
return {
|
|
8112
8228
|
async info() {
|
|
8113
8229
|
if (serverInfoCache) return serverInfoCache;
|
|
@@ -8115,7 +8231,12 @@ function buildStatelessMethods(requestFn, discoverResult2) {
|
|
|
8115
8231
|
return serverInfoCache;
|
|
8116
8232
|
},
|
|
8117
8233
|
async listTools() {
|
|
8118
|
-
|
|
8234
|
+
const result = await requestFn("tools/list", {});
|
|
8235
|
+
hidden = readHiddenTools(result);
|
|
8236
|
+
return result?.tools ?? [];
|
|
8237
|
+
},
|
|
8238
|
+
hiddenTool(name) {
|
|
8239
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8119
8240
|
},
|
|
8120
8241
|
async callTool(name, args, _callOptions) {
|
|
8121
8242
|
const params = {
|
|
@@ -8167,17 +8288,17 @@ async function createStatelessConnection(options) {
|
|
|
8167
8288
|
redact: redact2 = (text) => text
|
|
8168
8289
|
} = options;
|
|
8169
8290
|
let nextId2 = 1;
|
|
8170
|
-
|
|
8291
|
+
const protocolVersion = STATELESS_PROTOCOL_VERSION;
|
|
8171
8292
|
function buildHeaders(method, params, token) {
|
|
8172
8293
|
const h = {
|
|
8173
8294
|
"Content-Type": "application/json",
|
|
8174
8295
|
Accept: "application/json, text/event-stream",
|
|
8296
|
+
"MCP-Protocol-Version": protocolVersion,
|
|
8175
8297
|
"Mcp-Method": method,
|
|
8176
8298
|
...extraHeaders
|
|
8177
8299
|
};
|
|
8178
8300
|
const n = mcpNameForMethod(method, params);
|
|
8179
|
-
if (n) h["Mcp-Name"] = n;
|
|
8180
|
-
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
8301
|
+
if (n) h["Mcp-Name"] = encodeHeaderValue(n);
|
|
8181
8302
|
if (token) h["Authorization"] = `Bearer ${token}`;
|
|
8182
8303
|
return h;
|
|
8183
8304
|
}
|
|
@@ -8197,7 +8318,7 @@ async function createStatelessConnection(options) {
|
|
|
8197
8318
|
if (p.error && typeof p.error.code === "number") rpc = p.error;
|
|
8198
8319
|
} catch {
|
|
8199
8320
|
}
|
|
8200
|
-
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
8321
|
+
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data, res.status);
|
|
8201
8322
|
throw new StatelessHttpError(res.status, res.statusText, method, redact2(text).slice(0, 512));
|
|
8202
8323
|
}
|
|
8203
8324
|
async function doFetch(method, params, body) {
|
|
@@ -8240,7 +8361,12 @@ async function createStatelessConnection(options) {
|
|
|
8240
8361
|
}
|
|
8241
8362
|
if (verbose?.enabled) verbose.response(JSON.stringify(frame));
|
|
8242
8363
|
if (frame.error) {
|
|
8243
|
-
throw new StatelessRpcError(
|
|
8364
|
+
throw new StatelessRpcError(
|
|
8365
|
+
frame.error.code,
|
|
8366
|
+
frame.error.message,
|
|
8367
|
+
frame.error.data,
|
|
8368
|
+
res.status
|
|
8369
|
+
);
|
|
8244
8370
|
}
|
|
8245
8371
|
return frame.result;
|
|
8246
8372
|
}
|
|
@@ -8259,23 +8385,10 @@ async function createStatelessConnection(options) {
|
|
|
8259
8385
|
}
|
|
8260
8386
|
}
|
|
8261
8387
|
async function request(method, params) {
|
|
8262
|
-
const
|
|
8263
|
-
const meta = {
|
|
8264
|
-
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
8265
|
-
clientInfo
|
|
8266
|
-
};
|
|
8267
|
-
const cleanParams = { ...params };
|
|
8268
|
-
if (cleanParams._inputResponses) {
|
|
8269
|
-
meta.inputResponses = cleanParams._inputResponses;
|
|
8270
|
-
delete cleanParams._inputResponses;
|
|
8271
|
-
}
|
|
8272
|
-
const body = JSON.stringify({
|
|
8273
|
-
jsonrpc: "2.0",
|
|
8274
|
-
id,
|
|
8275
|
-
method,
|
|
8276
|
-
params: { ...cleanParams, _meta: meta }
|
|
8277
|
-
});
|
|
8388
|
+
const wireParams = modernRequestParams(params, protocolVersion, clientInfo);
|
|
8278
8389
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
8390
|
+
const id = nextId2++;
|
|
8391
|
+
const body = JSON.stringify({ jsonrpc: "2.0", id, method, params: wireParams });
|
|
8279
8392
|
const { res, token, detach } = await doFetch(method, params, body);
|
|
8280
8393
|
let retry;
|
|
8281
8394
|
try {
|
|
@@ -8295,16 +8408,11 @@ async function createStatelessConnection(options) {
|
|
|
8295
8408
|
}
|
|
8296
8409
|
throw new McpAuthError(401, url);
|
|
8297
8410
|
}
|
|
8298
|
-
const discoverResult2 = await request("server/discover", {
|
|
8299
|
-
protocolVersion: STATELESS_PROTOCOL_VERSION
|
|
8300
|
-
});
|
|
8411
|
+
const discoverResult2 = await request("server/discover", {});
|
|
8301
8412
|
const supported = discoverResult2?.supportedVersions?.filter((v) => typeof v === "string") ?? [];
|
|
8302
8413
|
if (!supported.includes(STATELESS_PROTOCOL_VERSION)) {
|
|
8303
|
-
throw new
|
|
8304
|
-
`Server does not support ${STATELESS_PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
|
|
8305
|
-
);
|
|
8414
|
+
throw new UnsupportedModernVersionError(STATELESS_PROTOCOL_VERSION, supported);
|
|
8306
8415
|
}
|
|
8307
|
-
negotiatedVersion = STATELESS_PROTOCOL_VERSION;
|
|
8308
8416
|
const extra = buildStatelessMethods(request, discoverResult2);
|
|
8309
8417
|
return {
|
|
8310
8418
|
...extra,
|
|
@@ -8451,14 +8559,13 @@ async function createPiConnection(options) {
|
|
|
8451
8559
|
}
|
|
8452
8560
|
|
|
8453
8561
|
// src/client/stdio-stateless.ts
|
|
8454
|
-
var PROTOCOL_VERSION =
|
|
8562
|
+
var PROTOCOL_VERSION = MODERN_PROTOCOL_VERSION;
|
|
8455
8563
|
function createStdioJsonRpc(proc, platform2, options) {
|
|
8456
8564
|
const encoder2 = new TextEncoder();
|
|
8457
8565
|
const decoder3 = new TextDecoder();
|
|
8458
8566
|
let nextId2 = 1;
|
|
8459
8567
|
let readBuffer = "";
|
|
8460
8568
|
const reader = proc.stdout.getReader();
|
|
8461
|
-
let negotiatedVersion;
|
|
8462
8569
|
const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
|
|
8463
8570
|
async function readLine2() {
|
|
8464
8571
|
while (true) {
|
|
@@ -8498,20 +8605,11 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8498
8605
|
}
|
|
8499
8606
|
async function request(method, params) {
|
|
8500
8607
|
const id = nextId2++;
|
|
8501
|
-
const meta = {
|
|
8502
|
-
protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
|
|
8503
|
-
clientInfo: { name: "mcpcat", version }
|
|
8504
|
-
};
|
|
8505
|
-
const cleanParams = { ...params };
|
|
8506
|
-
if (cleanParams._inputResponses) {
|
|
8507
|
-
meta.inputResponses = cleanParams._inputResponses;
|
|
8508
|
-
delete cleanParams._inputResponses;
|
|
8509
|
-
}
|
|
8510
8608
|
const body = JSON.stringify({
|
|
8511
8609
|
jsonrpc: "2.0",
|
|
8512
8610
|
id,
|
|
8513
8611
|
method,
|
|
8514
|
-
params:
|
|
8612
|
+
params: modernRequestParams(params, PROTOCOL_VERSION, { name: "mcpcat", version })
|
|
8515
8613
|
});
|
|
8516
8614
|
if (verbose?.enabled) verbose.request(method, body);
|
|
8517
8615
|
await send(body);
|
|
@@ -8545,22 +8643,29 @@ function createStdioJsonRpc(proc, platform2, options) {
|
|
|
8545
8643
|
};
|
|
8546
8644
|
}
|
|
8547
8645
|
function buildServerInfo2(discoverResult2) {
|
|
8646
|
+
const serverInfo = discoverServerInfo(discoverResult2);
|
|
8548
8647
|
return {
|
|
8549
|
-
name:
|
|
8550
|
-
version:
|
|
8551
|
-
title:
|
|
8648
|
+
name: serverInfo?.name,
|
|
8649
|
+
version: serverInfo?.version,
|
|
8650
|
+
title: serverInfo?.title,
|
|
8552
8651
|
protocolVersion: PROTOCOL_VERSION,
|
|
8553
8652
|
capabilities: discoverResult2?.capabilities ?? {},
|
|
8554
8653
|
instructions: discoverResult2?.instructions
|
|
8555
8654
|
};
|
|
8556
8655
|
}
|
|
8557
8656
|
function buildConnectionMethods(rpc, discoverResult2, proc) {
|
|
8657
|
+
let hidden = {};
|
|
8558
8658
|
return {
|
|
8559
8659
|
async info() {
|
|
8560
8660
|
return buildServerInfo2(discoverResult2);
|
|
8561
8661
|
},
|
|
8562
8662
|
async listTools() {
|
|
8563
|
-
|
|
8663
|
+
const result = await rpc.request("tools/list", {});
|
|
8664
|
+
hidden = readHiddenTools(result);
|
|
8665
|
+
return result?.tools ?? [];
|
|
8666
|
+
},
|
|
8667
|
+
hiddenTool(name) {
|
|
8668
|
+
return Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
8564
8669
|
},
|
|
8565
8670
|
async callTool(name, args, callOptions) {
|
|
8566
8671
|
const params = { name, arguments: args ?? {} };
|
|
@@ -8616,25 +8721,27 @@ async function probeStdioStateless(options) {
|
|
|
8616
8721
|
});
|
|
8617
8722
|
let discoverRaw;
|
|
8618
8723
|
try {
|
|
8619
|
-
discoverRaw = await rpc.request("server/discover", {
|
|
8620
|
-
protocolVersion: PROTOCOL_VERSION
|
|
8621
|
-
});
|
|
8724
|
+
discoverRaw = await rpc.request("server/discover", {});
|
|
8622
8725
|
} catch (err) {
|
|
8623
8726
|
const pending = rpc.pendingBuffer();
|
|
8624
8727
|
rpc.close();
|
|
8625
8728
|
return { proc, pendingStdout: pending, error: err };
|
|
8626
8729
|
}
|
|
8627
|
-
void readStderr(proc, options.verbose);
|
|
8628
8730
|
const discoverResult2 = discoverRaw;
|
|
8629
|
-
const
|
|
8731
|
+
const advertised = discoverResult2?.supportedVersions;
|
|
8732
|
+
const supported = (Array.isArray(advertised) ? advertised : []).filter(
|
|
8630
8733
|
(v) => typeof v === "string"
|
|
8631
8734
|
);
|
|
8632
8735
|
if (!supported.includes(PROTOCOL_VERSION)) {
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8736
|
+
const pending = rpc.pendingBuffer();
|
|
8737
|
+
rpc.close();
|
|
8738
|
+
return {
|
|
8739
|
+
proc,
|
|
8740
|
+
pendingStdout: pending,
|
|
8741
|
+
error: new UnsupportedModernVersionError(PROTOCOL_VERSION, supported)
|
|
8742
|
+
};
|
|
8637
8743
|
}
|
|
8744
|
+
void readStderr(proc, options.verbose);
|
|
8638
8745
|
return buildConnectionMethods(rpc, discoverResult2, proc);
|
|
8639
8746
|
}
|
|
8640
8747
|
async function readStderr(proc, verbose) {
|
|
@@ -8678,16 +8785,7 @@ async function connect(platform2, options) {
|
|
|
8678
8785
|
verbose: options.verbose
|
|
8679
8786
|
});
|
|
8680
8787
|
if (!("proc" in result)) return result;
|
|
8681
|
-
|
|
8682
|
-
const isUnsupported = result.error.message.includes("does not support");
|
|
8683
|
-
if (options.protocolVersion?.startsWith("2026")) {
|
|
8684
|
-
try {
|
|
8685
|
-
await result.proc.kill("SIGTERM");
|
|
8686
|
-
} catch {
|
|
8687
|
-
}
|
|
8688
|
-
throw result.error;
|
|
8689
|
-
}
|
|
8690
|
-
if (!isMethodNotFound && !isUnsupported) {
|
|
8788
|
+
if (options.protocolVersion?.startsWith("2026") || !indicatesLegacyServer(result.error, false)) {
|
|
8691
8789
|
try {
|
|
8692
8790
|
await result.proc.kill("SIGTERM");
|
|
8693
8791
|
} catch {
|
|
@@ -8730,12 +8828,11 @@ async function connect(platform2, options) {
|
|
|
8730
8828
|
redact: options.redact
|
|
8731
8829
|
});
|
|
8732
8830
|
} catch (err) {
|
|
8733
|
-
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
8734
8831
|
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
8735
8832
|
if (err instanceof McpAuthError || err instanceof AuthStepUpError) throw err;
|
|
8736
8833
|
if (err instanceof TransportRefusalError) throw err;
|
|
8737
8834
|
if (err.name === "McpOAuthAuthorizationRequiredError") throw err;
|
|
8738
|
-
if (
|
|
8835
|
+
if (indicatesLegacyServer(err, true)) {
|
|
8739
8836
|
return createPiConnection({
|
|
8740
8837
|
url: options.url,
|
|
8741
8838
|
headers: options.headers,
|
|
@@ -8762,7 +8859,7 @@ function readLine(platform2) {
|
|
|
8762
8859
|
return new Promise((resolve2, reject) => {
|
|
8763
8860
|
const reader = platform2.stdin.getReader();
|
|
8764
8861
|
const chunks = [];
|
|
8765
|
-
function
|
|
8862
|
+
function pump2() {
|
|
8766
8863
|
reader.read().then(({ done, value }) => {
|
|
8767
8864
|
if (done) {
|
|
8768
8865
|
reader.releaseLock();
|
|
@@ -8776,10 +8873,10 @@ function readLine(platform2) {
|
|
|
8776
8873
|
resolve2(text.split("\n")[0]);
|
|
8777
8874
|
return;
|
|
8778
8875
|
}
|
|
8779
|
-
|
|
8876
|
+
pump2();
|
|
8780
8877
|
}, reject);
|
|
8781
8878
|
}
|
|
8782
|
-
|
|
8879
|
+
pump2();
|
|
8783
8880
|
});
|
|
8784
8881
|
}
|
|
8785
8882
|
function concatChunks(chunks) {
|
|
@@ -10486,11 +10583,21 @@ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPriva
|
|
|
10486
10583
|
}
|
|
10487
10584
|
return events;
|
|
10488
10585
|
}
|
|
10586
|
+
function sseResponse(text, id) {
|
|
10587
|
+
for (const block of text.replace(/\r\n?/g, "\n").split("\n\n")) {
|
|
10588
|
+
const data = block.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
|
|
10589
|
+
const parsed = tryParseJson(data);
|
|
10590
|
+
if (isRecord4(parsed) && parsed.id === id) return parsed;
|
|
10591
|
+
}
|
|
10592
|
+
return void 0;
|
|
10593
|
+
}
|
|
10489
10594
|
function mcpJsonRpc(method, params, id = 1) {
|
|
10490
10595
|
return JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
10491
10596
|
}
|
|
10492
10597
|
function parseServerInfo(result) {
|
|
10493
|
-
const
|
|
10598
|
+
const meta = isRecord4(result._meta) ? result._meta : void 0;
|
|
10599
|
+
const fromMeta = meta && isRecord4(meta[META_SERVER_INFO]) ? meta[META_SERVER_INFO] : void 0;
|
|
10600
|
+
const si = fromMeta ?? (isRecord4(result.serverInfo) ? result.serverInfo : void 0);
|
|
10494
10601
|
if (!si) return void 0;
|
|
10495
10602
|
return {
|
|
10496
10603
|
name: typeof si.name === "string" ? si.name : void 0,
|
|
@@ -10506,7 +10613,7 @@ function checkAuthResponse(resp) {
|
|
|
10506
10613
|
async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
10507
10614
|
const headers = {
|
|
10508
10615
|
"Content-Type": "application/json",
|
|
10509
|
-
Accept: "application/json",
|
|
10616
|
+
Accept: "application/json, text/event-stream",
|
|
10510
10617
|
...extraHeaders
|
|
10511
10618
|
};
|
|
10512
10619
|
try {
|
|
@@ -10521,7 +10628,8 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
|
10521
10628
|
if (authSignal) return { url, trust: [authSignal] };
|
|
10522
10629
|
if (resp.status === 200) {
|
|
10523
10630
|
const text = await resp.text();
|
|
10524
|
-
const
|
|
10631
|
+
const sse = (resp.headers.get("content-type") ?? "").includes("text/event-stream");
|
|
10632
|
+
const parsed = sse ? sseResponse(text, tryParseJson(body)?.id) : tryParseJson(text);
|
|
10525
10633
|
if (isRecord4(parsed) && isRecord4(parsed.result)) {
|
|
10526
10634
|
return { url, serverInfo: parseServerInfo(parsed.result), trust: [] };
|
|
10527
10635
|
}
|
|
@@ -10532,18 +10640,22 @@ async function tryMcpPost(fetcher, url, body, extraHeaders) {
|
|
|
10532
10640
|
}
|
|
10533
10641
|
async function probeMcpEndpoint(fetcher, url) {
|
|
10534
10642
|
const discoverBody = mcpJsonRpc("server/discover", {
|
|
10535
|
-
|
|
10536
|
-
clientInfo: { name: "mcpcat", version: "0.0.0" }
|
|
10643
|
+
_meta: modernRequestMeta(MODERN_PROTOCOL_VERSION, { name: "mcpcat", version: "0.0.0" })
|
|
10537
10644
|
});
|
|
10538
10645
|
const hit = await tryMcpPost(fetcher, url, discoverBody, {
|
|
10646
|
+
"MCP-Protocol-Version": MODERN_PROTOCOL_VERSION,
|
|
10539
10647
|
"Mcp-Method": "server/discover"
|
|
10540
10648
|
});
|
|
10541
10649
|
if (hit) return hit;
|
|
10542
|
-
const initBody = mcpJsonRpc(
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10650
|
+
const initBody = mcpJsonRpc(
|
|
10651
|
+
"initialize",
|
|
10652
|
+
{
|
|
10653
|
+
protocolVersion: "2025-11-25",
|
|
10654
|
+
clientInfo: { name: "mcpcat", version: "0.0.0" },
|
|
10655
|
+
capabilities: {}
|
|
10656
|
+
},
|
|
10657
|
+
2
|
|
10658
|
+
);
|
|
10547
10659
|
return tryMcpPost(fetcher, url, initBody);
|
|
10548
10660
|
}
|
|
10549
10661
|
async function probeMcp(fetcher, origin, page, targetUrl) {
|
|
@@ -11658,7 +11770,7 @@ async function writeResponse(writer, response) {
|
|
|
11658
11770
|
var PROTOCOL_VERSIONS = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
11659
11771
|
var OUTPUT_CAP = 1048576;
|
|
11660
11772
|
var JSON_CT = "application/json; charset=utf-8";
|
|
11661
|
-
async function handleJsonRpc(raw,
|
|
11773
|
+
async function handleJsonRpc(raw, source, call, serverName, serverVersion) {
|
|
11662
11774
|
let parsed;
|
|
11663
11775
|
try {
|
|
11664
11776
|
parsed = JSON.parse(raw);
|
|
@@ -11672,19 +11784,35 @@ async function handleJsonRpc(raw, tools, call, serverName, serverVersion) {
|
|
|
11672
11784
|
if (!hasOwn(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
11673
11785
|
const id = readId(parsed);
|
|
11674
11786
|
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
11787
|
+
return completeModernOutcome(
|
|
11788
|
+
await dispatch(id, method, params, source, call, serverName, serverVersion),
|
|
11789
|
+
params
|
|
11790
|
+
);
|
|
11791
|
+
}
|
|
11792
|
+
async function dispatch(id, method, params, source, call, serverName, serverVersion) {
|
|
11675
11793
|
if (method === "initialize")
|
|
11676
11794
|
return rpcOk(id, initializeResult(params, serverName, serverVersion));
|
|
11677
11795
|
if (method === "server/discover") return rpcOk(id, discoverResult(serverName, serverVersion));
|
|
11678
|
-
if (method === "tools/list") return rpcOk(id, { tools });
|
|
11679
11796
|
if (method === "ping") return rpcOk(id, {});
|
|
11680
|
-
if (method === "tools/
|
|
11797
|
+
if (method === "tools/list" || method === "tools/call") {
|
|
11798
|
+
const listing = Array.isArray(source) ? { tools: source } : await source();
|
|
11799
|
+
if (listing.error) return rpcError(id, -32e3, listing.error);
|
|
11800
|
+
if (method === "tools/call") return toolCall(id, params, listing, call);
|
|
11801
|
+
const hidden = listing.hidden && Object.keys(listing.hidden).length > 0 ? listing.hidden : null;
|
|
11802
|
+
return rpcOk(id, {
|
|
11803
|
+
tools: listing.tools,
|
|
11804
|
+
...hidden ? { _meta: { "io.mcpcat/hidden": hidden } } : {}
|
|
11805
|
+
});
|
|
11806
|
+
}
|
|
11681
11807
|
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
11682
11808
|
}
|
|
11683
|
-
async function toolCall(id, params, tools, call) {
|
|
11809
|
+
async function toolCall(id, params, { tools, hidden }, call) {
|
|
11684
11810
|
const name = params && typeof params === "object" ? readString(params, "name") : void 0;
|
|
11685
11811
|
if (!name) return rpcError(id, -32602, "tools/call requires a name");
|
|
11686
|
-
if (!tools.some((tool) => tool.name === name))
|
|
11687
|
-
|
|
11812
|
+
if (!tools.some((tool) => tool.name === name)) {
|
|
11813
|
+
const why = hidden && Object.hasOwn(hidden, name) ? hidden[name] : void 0;
|
|
11814
|
+
return rpcError(id, -32602, why ? `Unknown tool: ${name}: ${why}` : `Unknown tool: ${name}`);
|
|
11815
|
+
}
|
|
11688
11816
|
const args = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
11689
11817
|
try {
|
|
11690
11818
|
const result = await call(name, args);
|
|
@@ -11716,12 +11844,11 @@ function initializeResult(params, serverName, serverVersion) {
|
|
|
11716
11844
|
};
|
|
11717
11845
|
}
|
|
11718
11846
|
function discoverResult(serverName, serverVersion) {
|
|
11719
|
-
return
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
};
|
|
11847
|
+
return modernDiscoverResult(
|
|
11848
|
+
PROTOCOL_VERSIONS,
|
|
11849
|
+
{ tools: { listChanged: false } },
|
|
11850
|
+
{ name: serverName, version: serverVersion }
|
|
11851
|
+
);
|
|
11725
11852
|
}
|
|
11726
11853
|
function cap(value) {
|
|
11727
11854
|
if (value.length <= OUTPUT_CAP) return value;
|
|
@@ -11857,15 +11984,18 @@ function translateCapabilities(info) {
|
|
|
11857
11984
|
if (info.capabilities.prompts) caps.prompts = { listChanged: false };
|
|
11858
11985
|
return caps;
|
|
11859
11986
|
}
|
|
11987
|
+
function proxyServerInfo(info) {
|
|
11988
|
+
return {
|
|
11989
|
+
name: info.name ?? "mcpcat-proxy",
|
|
11990
|
+
version: info.version ?? "0.0.0",
|
|
11991
|
+
...info.title ? { title: info.title } : {}
|
|
11992
|
+
};
|
|
11993
|
+
}
|
|
11860
11994
|
function buildServerResult(info, protocolVersion) {
|
|
11861
11995
|
return {
|
|
11862
11996
|
protocolVersion,
|
|
11863
11997
|
capabilities: translateCapabilities(info),
|
|
11864
|
-
serverInfo:
|
|
11865
|
-
name: info.name ?? "mcpcat-proxy",
|
|
11866
|
-
version: info.version ?? "0.0.0",
|
|
11867
|
-
...info.title ? { title: info.title } : {}
|
|
11868
|
-
},
|
|
11998
|
+
serverInfo: proxyServerInfo(info),
|
|
11869
11999
|
...info.instructions ? { instructions: info.instructions } : {}
|
|
11870
12000
|
};
|
|
11871
12001
|
}
|
|
@@ -11913,10 +12043,16 @@ function createProxyHandler(options) {
|
|
|
11913
12043
|
return scrubOk(id, buildServerResult(cachedInfo, pv));
|
|
11914
12044
|
}
|
|
11915
12045
|
function handleDiscover(id) {
|
|
11916
|
-
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
|
|
12046
|
+
const info = cachedInfo;
|
|
12047
|
+
return scrubOk(
|
|
12048
|
+
id,
|
|
12049
|
+
modernDiscoverResult(
|
|
12050
|
+
PROTOCOL_VERSIONS2,
|
|
12051
|
+
translateCapabilities(info),
|
|
12052
|
+
proxyServerInfo(info),
|
|
12053
|
+
info.instructions
|
|
12054
|
+
)
|
|
12055
|
+
);
|
|
11920
12056
|
}
|
|
11921
12057
|
async function forwardWithRetry(method, doForward, id) {
|
|
11922
12058
|
try {
|
|
@@ -11943,7 +12079,7 @@ function createProxyHandler(options) {
|
|
|
11943
12079
|
return scrubErr(id, -32e3, err.message);
|
|
11944
12080
|
}
|
|
11945
12081
|
}
|
|
11946
|
-
async function
|
|
12082
|
+
async function dispatch2(method, id, params) {
|
|
11947
12083
|
try {
|
|
11948
12084
|
await getConnection();
|
|
11949
12085
|
} catch (err) {
|
|
@@ -11980,7 +12116,7 @@ function createProxyHandler(options) {
|
|
|
11980
12116
|
if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
11981
12117
|
const id = readId2(parsed);
|
|
11982
12118
|
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
11983
|
-
return
|
|
12119
|
+
return completeModernOutcome(await dispatch2(method, id, params), params);
|
|
11984
12120
|
}
|
|
11985
12121
|
async function shutdown() {
|
|
11986
12122
|
if (conn) {
|
|
@@ -12048,7 +12184,149 @@ ${tail}`,
|
|
|
12048
12184
|
}
|
|
12049
12185
|
};
|
|
12050
12186
|
}
|
|
12051
|
-
|
|
12187
|
+
var DIALOG_POLL_MS = 250;
|
|
12188
|
+
var DIALOG_READY_MS = 1e3;
|
|
12189
|
+
var DIALOG_STALL_MS = 1e3;
|
|
12190
|
+
function watchLoop(watch) {
|
|
12191
|
+
let timer;
|
|
12192
|
+
const result = new Promise((resolve2) => {
|
|
12193
|
+
timer = setInterval(() => {
|
|
12194
|
+
const found = watch();
|
|
12195
|
+
if (found) {
|
|
12196
|
+
clearInterval(timer);
|
|
12197
|
+
resolve2(found);
|
|
12198
|
+
}
|
|
12199
|
+
}, DIALOG_POLL_MS);
|
|
12200
|
+
});
|
|
12201
|
+
return {
|
|
12202
|
+
result,
|
|
12203
|
+
stop() {
|
|
12204
|
+
clearInterval(timer);
|
|
12205
|
+
}
|
|
12206
|
+
};
|
|
12207
|
+
}
|
|
12208
|
+
async function browserSocketUrl(platform2, cdp) {
|
|
12209
|
+
if (/^wss?:\/\//i.test(cdp)) return cdp;
|
|
12210
|
+
if (!/^https?:\/\//i.test(cdp)) return void 0;
|
|
12211
|
+
const res = await platform2.fetch(`${cdp.replace(/\/+$/, "")}/json/version`, {
|
|
12212
|
+
signal: AbortSignal.timeout(DIALOG_READY_MS)
|
|
12213
|
+
});
|
|
12214
|
+
const body = await res.json();
|
|
12215
|
+
return typeof body.webSocketDebuggerUrl === "string" ? body.webSocketDebuggerUrl : void 0;
|
|
12216
|
+
}
|
|
12217
|
+
function parseCdp(data) {
|
|
12218
|
+
try {
|
|
12219
|
+
return JSON.parse(
|
|
12220
|
+
typeof data === "string" ? data : new TextDecoder().decode(data)
|
|
12221
|
+
);
|
|
12222
|
+
} catch {
|
|
12223
|
+
return void 0;
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
function applyCdp(state, channel, send, msg, settle) {
|
|
12227
|
+
if (msg.method === "Page.javascriptDialogOpening") {
|
|
12228
|
+
state.open = typeof msg.params?.type === "string" ? msg.params.type : "dialog";
|
|
12229
|
+
} else if (msg.method === "Page.javascriptDialogClosed") {
|
|
12230
|
+
state.open = void 0;
|
|
12231
|
+
} else if (msg.method === "Target.detachedFromTarget") {
|
|
12232
|
+
channel.close();
|
|
12233
|
+
} else if (msg.id === 1) {
|
|
12234
|
+
const sessionId = msg.result?.sessionId;
|
|
12235
|
+
if (typeof sessionId !== "string") {
|
|
12236
|
+
channel.close();
|
|
12237
|
+
return;
|
|
12238
|
+
}
|
|
12239
|
+
state.enableSentAt = Date.now();
|
|
12240
|
+
send({ id: 2, sessionId, method: "Page.enable" });
|
|
12241
|
+
} else if (msg.id === 2) {
|
|
12242
|
+
state.enableSentAt = void 0;
|
|
12243
|
+
settle();
|
|
12244
|
+
}
|
|
12245
|
+
}
|
|
12246
|
+
async function pump(state, channel, send, settle) {
|
|
12247
|
+
const reader = channel.readable.getReader();
|
|
12248
|
+
try {
|
|
12249
|
+
for (; ; ) {
|
|
12250
|
+
const { done, value } = await reader.read();
|
|
12251
|
+
if (done) return;
|
|
12252
|
+
const msg = parseCdp(value);
|
|
12253
|
+
if (msg) applyCdp(state, channel, send, msg, settle);
|
|
12254
|
+
}
|
|
12255
|
+
} catch {
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
12258
|
+
}
|
|
12259
|
+
async function attachSocket(platform2, state, url, targetId) {
|
|
12260
|
+
const channel = await platform2.websocket(url).catch(() => void 0);
|
|
12261
|
+
if (!channel) return;
|
|
12262
|
+
state.channel = channel;
|
|
12263
|
+
const writer = channel.writable.getWriter();
|
|
12264
|
+
const send = (message) => {
|
|
12265
|
+
writer.write(JSON.stringify(message)).catch(() => channel.close());
|
|
12266
|
+
};
|
|
12267
|
+
let settle;
|
|
12268
|
+
const settled = new Promise((resolve2) => {
|
|
12269
|
+
settle = resolve2;
|
|
12270
|
+
});
|
|
12271
|
+
void pump(state, channel, send, settle).then(() => {
|
|
12272
|
+
if (state.channel === channel) {
|
|
12273
|
+
state.channel = void 0;
|
|
12274
|
+
state.open = void 0;
|
|
12275
|
+
state.enableSentAt = void 0;
|
|
12276
|
+
}
|
|
12277
|
+
channel.close();
|
|
12278
|
+
settle();
|
|
12279
|
+
});
|
|
12280
|
+
send({ id: 1, method: "Target.attachToTarget", params: { targetId, flatten: true } });
|
|
12281
|
+
await settled;
|
|
12282
|
+
}
|
|
12283
|
+
function watchJsDialogs(platform2, cdp, targetId) {
|
|
12284
|
+
const state = {};
|
|
12285
|
+
let connecting;
|
|
12286
|
+
let closed = false;
|
|
12287
|
+
const connect2 = async () => {
|
|
12288
|
+
if (!platform2.websocket) return;
|
|
12289
|
+
const url = await browserSocketUrl(platform2, cdp).catch(() => void 0);
|
|
12290
|
+
if (!url || closed) return;
|
|
12291
|
+
await attachSocket(platform2, state, url, targetId);
|
|
12292
|
+
};
|
|
12293
|
+
return {
|
|
12294
|
+
async ready() {
|
|
12295
|
+
if (closed || state.channel) return;
|
|
12296
|
+
connecting ??= connect2().finally(() => {
|
|
12297
|
+
connecting = void 0;
|
|
12298
|
+
});
|
|
12299
|
+
let timer;
|
|
12300
|
+
await Promise.race([
|
|
12301
|
+
connecting,
|
|
12302
|
+
new Promise((resolve2) => {
|
|
12303
|
+
timer = setTimeout(resolve2, DIALOG_READY_MS);
|
|
12304
|
+
})
|
|
12305
|
+
]);
|
|
12306
|
+
clearTimeout(timer);
|
|
12307
|
+
},
|
|
12308
|
+
dialog: () => state.open,
|
|
12309
|
+
stalled: () => state.enableSentAt !== void 0 && Date.now() - state.enableSentAt >= DIALOG_STALL_MS,
|
|
12310
|
+
close() {
|
|
12311
|
+
closed = true;
|
|
12312
|
+
state.channel?.close();
|
|
12313
|
+
}
|
|
12314
|
+
};
|
|
12315
|
+
}
|
|
12316
|
+
function dialogMessage(tab, type) {
|
|
12317
|
+
return `tab ${tab} is blocked by a JavaScript ${type.replace(/[^a-z]/gi, "")} dialog; answer or dismiss it in the browser, then retry`;
|
|
12318
|
+
}
|
|
12319
|
+
function timeoutMessage(verb, timeoutMs, tab, watcher) {
|
|
12320
|
+
const head = `${verb} timed out after ${Math.round(timeoutMs / 1e3)}s`;
|
|
12321
|
+
if (!watcher) {
|
|
12322
|
+
return `${head}; if the page shows a JavaScript dialog (alert, confirm, prompt), answer it, and pass --cdp <url> so mcpcat can detect one`;
|
|
12323
|
+
}
|
|
12324
|
+
if (watcher.stalled()) {
|
|
12325
|
+
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`;
|
|
12326
|
+
}
|
|
12327
|
+
return head;
|
|
12328
|
+
}
|
|
12329
|
+
async function runProcess(platform2, argv, stdin, timeoutMs, limit, watch) {
|
|
12052
12330
|
let proc;
|
|
12053
12331
|
try {
|
|
12054
12332
|
proc = await platform2.spawn({ argv });
|
|
@@ -12092,12 +12370,20 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12092
12370
|
readStream3(proc.stderr, err),
|
|
12093
12371
|
proc.wait()
|
|
12094
12372
|
]);
|
|
12095
|
-
const
|
|
12373
|
+
const watcher = watch ? watchLoop(watch) : void 0;
|
|
12374
|
+
const blocked = watcher ? watcher.result.then((type) => ({ blocked: type })) : new Promise(() => {
|
|
12375
|
+
});
|
|
12376
|
+
const result = await Promise.race([work, timer, blocked]);
|
|
12096
12377
|
clearTimeout(timeoutId);
|
|
12378
|
+
watcher?.stop();
|
|
12097
12379
|
if (result === "timeout") {
|
|
12098
12380
|
await proc.kill("SIGTERM");
|
|
12099
12381
|
return { stdout: "", stderr: "timed out", exitCode: 124, cut: false };
|
|
12100
12382
|
}
|
|
12383
|
+
if (!Array.isArray(result)) {
|
|
12384
|
+
await proc.kill("SIGTERM");
|
|
12385
|
+
return { stdout: "", stderr: "blocked", exitCode: 124, cut: false, blocked: result.blocked };
|
|
12386
|
+
}
|
|
12101
12387
|
const [, , exitResult] = result;
|
|
12102
12388
|
const stdout = out.finish();
|
|
12103
12389
|
const stderr = err.finish();
|
|
@@ -12108,7 +12394,7 @@ async function runProcess(platform2, argv, stdin, timeoutMs, limit) {
|
|
|
12108
12394
|
cut: stdout.cut
|
|
12109
12395
|
};
|
|
12110
12396
|
}
|
|
12111
|
-
async function
|
|
12397
|
+
async function listTabs(platform2, options) {
|
|
12112
12398
|
const argv = [options.playwrightCli, "tab-list"];
|
|
12113
12399
|
if (options.cdp) argv.push("--cdp", options.cdp);
|
|
12114
12400
|
const result = await runProcess(
|
|
@@ -12135,6 +12421,13 @@ async function resolveWebMcpTab(platform2, options) {
|
|
|
12135
12421
|
if (new Set(rows.map((r) => r.id)).size !== rows.length || active.length > 1) {
|
|
12136
12422
|
return { error: "could not read playwright-cli tab-list" };
|
|
12137
12423
|
}
|
|
12424
|
+
return { rows };
|
|
12425
|
+
}
|
|
12426
|
+
async function resolveWebMcpTab(platform2, options) {
|
|
12427
|
+
const listed = await listTabs(platform2, options);
|
|
12428
|
+
if ("error" in listed) return listed;
|
|
12429
|
+
const { rows } = listed;
|
|
12430
|
+
const active = rows.filter((r) => r.active);
|
|
12138
12431
|
if (active.length === 1) return { tab: active[0].id };
|
|
12139
12432
|
if (rows.length === 1) return { tab: rows[0].id };
|
|
12140
12433
|
return {
|
|
@@ -12197,20 +12490,57 @@ function ambiguityError(name, peers) {
|
|
|
12197
12490
|
`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
12491
|
);
|
|
12199
12492
|
}
|
|
12493
|
+
async function blockedNow(watcher, tab) {
|
|
12494
|
+
if (!watcher) return void 0;
|
|
12495
|
+
await watcher.ready();
|
|
12496
|
+
const open = watcher.dialog();
|
|
12497
|
+
return open ? dialogMessage(tab, open) : void 0;
|
|
12498
|
+
}
|
|
12499
|
+
async function tabGone(platform2, options) {
|
|
12500
|
+
const { tab } = options;
|
|
12501
|
+
if (!tab) return void 0;
|
|
12502
|
+
const listed = await listTabs(platform2, options);
|
|
12503
|
+
if ("error" in listed || listed.rows.some((r) => r.id === tab)) return void 0;
|
|
12504
|
+
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)`;
|
|
12505
|
+
}
|
|
12506
|
+
function hiddenToolMessage(name) {
|
|
12507
|
+
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`;
|
|
12508
|
+
}
|
|
12200
12509
|
function createWebMcpHandler(platform2, options) {
|
|
12201
12510
|
let cache;
|
|
12202
12511
|
const baseArgs = buildBaseArgs(options);
|
|
12203
12512
|
const listLimit = Math.max(options.maxOutput, WEBMCP_LIST_FLOOR);
|
|
12513
|
+
const { cdp, tab } = options;
|
|
12514
|
+
const watcher = cdp && tab ? watchJsDialogs(platform2, cdp, tab) : void 0;
|
|
12515
|
+
const watch = watcher ? () => watcher.dialog() : void 0;
|
|
12204
12516
|
async function listRaw(fresh = false) {
|
|
12517
|
+
const blocked = await blockedNow(watcher, tab);
|
|
12518
|
+
if (blocked) return { tools: [], error: blocked };
|
|
12205
12519
|
const now = Date.now();
|
|
12206
12520
|
if (!fresh && cache && now < cache.expiresAt) {
|
|
12207
12521
|
return { tools: cache.tools };
|
|
12208
12522
|
}
|
|
12209
12523
|
const argv = [options.playwrightCli, "webmcp-list", "--json", ...baseArgs];
|
|
12210
|
-
const result = await runProcess(
|
|
12524
|
+
const result = await runProcess(
|
|
12525
|
+
platform2,
|
|
12526
|
+
argv,
|
|
12527
|
+
void 0,
|
|
12528
|
+
options.callTimeoutMs,
|
|
12529
|
+
listLimit,
|
|
12530
|
+
watch
|
|
12531
|
+
);
|
|
12211
12532
|
if (result.exitCode === -1) {
|
|
12212
12533
|
return { tools: [], error: result.stderr, notFound: true };
|
|
12213
12534
|
}
|
|
12535
|
+
if (result.blocked) {
|
|
12536
|
+
return { tools: [], error: dialogMessage(tab, result.blocked) };
|
|
12537
|
+
}
|
|
12538
|
+
if (result.exitCode === 124) {
|
|
12539
|
+
return {
|
|
12540
|
+
tools: [],
|
|
12541
|
+
error: timeoutMessage("webmcp-list", options.callTimeoutMs, tab, watcher)
|
|
12542
|
+
};
|
|
12543
|
+
}
|
|
12214
12544
|
if (result.exitCode !== 0) {
|
|
12215
12545
|
return { tools: [], error: result.stderr || `webmcp-list exited ${result.exitCode}` };
|
|
12216
12546
|
}
|
|
@@ -12226,6 +12556,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12226
12556
|
if (!Array.isArray(parsed)) {
|
|
12227
12557
|
return { tools: [], error: "webmcp-list did not return an array" };
|
|
12228
12558
|
}
|
|
12559
|
+
if (parsed.length === 0) {
|
|
12560
|
+
const gone2 = await tabGone(platform2, options);
|
|
12561
|
+
if (gone2) return { tools: [], error: gone2 };
|
|
12562
|
+
}
|
|
12229
12563
|
const tools = parsed;
|
|
12230
12564
|
cache = { tools, expiresAt: now + options.ttlMs };
|
|
12231
12565
|
return { tools };
|
|
@@ -12233,11 +12567,13 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12233
12567
|
async function getRpcTools() {
|
|
12234
12568
|
const { tools, error, notFound } = await listRaw();
|
|
12235
12569
|
if (notFound) return null;
|
|
12236
|
-
if (error) return [];
|
|
12570
|
+
if (error) return { tools: [], error };
|
|
12237
12571
|
const rpcTools = [];
|
|
12572
|
+
const hidden = {};
|
|
12238
12573
|
for (const { name, tool, peers } of exposedTools(tools)) {
|
|
12239
12574
|
if (peers.length > 1) continue;
|
|
12240
12575
|
if (tool.annotations?.consequentialHint && !options.allowConsequential) {
|
|
12576
|
+
hidden[name] = hiddenToolMessage(name);
|
|
12241
12577
|
continue;
|
|
12242
12578
|
}
|
|
12243
12579
|
const mapped = mapDescriptionAnnotations(tool.annotations);
|
|
@@ -12248,7 +12584,7 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12248
12584
|
annotations: mapped
|
|
12249
12585
|
});
|
|
12250
12586
|
}
|
|
12251
|
-
return rpcTools;
|
|
12587
|
+
return { tools: rpcTools, hidden };
|
|
12252
12588
|
}
|
|
12253
12589
|
async function callTool(name, args) {
|
|
12254
12590
|
const { tools, error } = await listRaw(true);
|
|
@@ -12273,11 +12609,18 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12273
12609
|
argv,
|
|
12274
12610
|
stdinData,
|
|
12275
12611
|
options.callTimeoutMs,
|
|
12276
|
-
options.maxOutput
|
|
12612
|
+
options.maxOutput,
|
|
12613
|
+
watch
|
|
12277
12614
|
);
|
|
12278
12615
|
if (result.exitCode === -1) {
|
|
12279
12616
|
throw new Error(result.stderr || "playwright-cli not found");
|
|
12280
12617
|
}
|
|
12618
|
+
if (result.blocked) {
|
|
12619
|
+
throw new Error(dialogMessage(tab, result.blocked));
|
|
12620
|
+
}
|
|
12621
|
+
if (result.exitCode === 124) {
|
|
12622
|
+
throw new Error(timeoutMessage("webmcp-call", options.callTimeoutMs, tab, watcher));
|
|
12623
|
+
}
|
|
12281
12624
|
if (result.exitCode === 2) {
|
|
12282
12625
|
throw new Error(result.stderr || "webmcp-call error");
|
|
12283
12626
|
}
|
|
@@ -12299,7 +12642,10 @@ function createWebMcpHandler(platform2, options) {
|
|
|
12299
12642
|
function invalidateCache() {
|
|
12300
12643
|
cache = void 0;
|
|
12301
12644
|
}
|
|
12302
|
-
|
|
12645
|
+
function close() {
|
|
12646
|
+
watcher?.close();
|
|
12647
|
+
}
|
|
12648
|
+
return { getRpcTools, callTool, invalidateCache, close };
|
|
12303
12649
|
}
|
|
12304
12650
|
|
|
12305
12651
|
// src/serve/runtime.ts
|
|
@@ -13062,23 +13408,42 @@ async function buildProxyConnectOpts(platform2, options, version) {
|
|
|
13062
13408
|
}
|
|
13063
13409
|
return { connectOpts, hiddenValues };
|
|
13064
13410
|
}
|
|
13065
|
-
function resolvePlaywrightCli(platform2, explicit) {
|
|
13411
|
+
async function resolvePlaywrightCli(platform2, explicit) {
|
|
13066
13412
|
if (explicit) return explicit;
|
|
13067
13413
|
if (platform2.kind === "kernel") return "playwright-cli";
|
|
13068
13414
|
const pathEnv = platform2.env.PATH ?? "";
|
|
13069
|
-
const
|
|
13070
|
-
|
|
13415
|
+
const windows = pathEnv.includes(";");
|
|
13416
|
+
const names = windows ? ["playwright-cli.exe", "playwright-cli.cmd", "playwright-cli"] : ["playwright-cli"];
|
|
13417
|
+
for (const dir of pathEnv.split(windows ? ";" : ":")) {
|
|
13071
13418
|
if (!dir) continue;
|
|
13072
|
-
|
|
13419
|
+
for (const name of names) {
|
|
13420
|
+
const candidate = `${dir.replace(/[\\/]+$/, "")}/${name}`;
|
|
13421
|
+
if (await platform2.fs.exists(candidate).catch(() => false)) return "playwright-cli";
|
|
13422
|
+
}
|
|
13073
13423
|
}
|
|
13074
13424
|
return void 0;
|
|
13075
13425
|
}
|
|
13426
|
+
async function listenOrReport(platform2, options, label) {
|
|
13427
|
+
try {
|
|
13428
|
+
return await platform2.listen({ host: options.host, port: options.port });
|
|
13429
|
+
} catch (err) {
|
|
13430
|
+
const { code, message } = err;
|
|
13431
|
+
const reason = code === "EADDRINUSE" || /EADDRINUSE/.test(String(message)) ? "address already in use" : String(message ?? err);
|
|
13432
|
+
const host = options.host.includes(":") ? `[${options.host}]` : options.host;
|
|
13433
|
+
await platform2.write(
|
|
13434
|
+
2,
|
|
13435
|
+
sanitizeTerminal(`${label}: cannot listen on ${host}:${options.port}: ${reason}
|
|
13436
|
+
`)
|
|
13437
|
+
);
|
|
13438
|
+
return 3;
|
|
13439
|
+
}
|
|
13440
|
+
}
|
|
13076
13441
|
async function runWebMcpServe(platform2, options, version, resolvedBearer, generatedBearer) {
|
|
13077
|
-
const cliPath = resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13442
|
+
const cliPath = await resolvePlaywrightCli(platform2, options.webmcpPlaywrightCli);
|
|
13078
13443
|
if (!cliPath) {
|
|
13079
13444
|
await platform2.write(
|
|
13080
13445
|
2,
|
|
13081
|
-
"mcpcat serve --webmcp: playwright-cli not found. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13446
|
+
"mcpcat serve --webmcp: playwright-cli not found on PATH. Install it with: curl -fsSL https://get.sliccy.ai/playwright-cli | sh\n"
|
|
13082
13447
|
);
|
|
13083
13448
|
return 3;
|
|
13084
13449
|
}
|
|
@@ -13113,6 +13478,20 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13113
13478
|
callTimeoutMs: options.callTimeout,
|
|
13114
13479
|
maxOutput: options.maxOutput
|
|
13115
13480
|
});
|
|
13481
|
+
try {
|
|
13482
|
+
return await serveWebMcpHandler(
|
|
13483
|
+
platform2,
|
|
13484
|
+
options,
|
|
13485
|
+
handler,
|
|
13486
|
+
version,
|
|
13487
|
+
resolvedBearer,
|
|
13488
|
+
generatedBearer
|
|
13489
|
+
);
|
|
13490
|
+
} finally {
|
|
13491
|
+
handler.close();
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13494
|
+
async function serveWebMcpHandler(platform2, options, handler, version, resolvedBearer, generatedBearer) {
|
|
13116
13495
|
const probe = await handler.getRpcTools();
|
|
13117
13496
|
if (probe === null) {
|
|
13118
13497
|
await platform2.write(
|
|
@@ -13122,7 +13501,7 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13122
13501
|
return 3;
|
|
13123
13502
|
}
|
|
13124
13503
|
const serverName = "mcpcat-webmcp";
|
|
13125
|
-
const getTools = async () => await handler.getRpcTools() ?? [];
|
|
13504
|
+
const getTools = async () => await handler.getRpcTools() ?? { tools: [] };
|
|
13126
13505
|
const callTool = (name, args) => handler.callTool(name, args);
|
|
13127
13506
|
if (options.transport === "stdio") {
|
|
13128
13507
|
return runWebMcpStdio(platform2, getTools, callTool, serverName, version);
|
|
@@ -13153,8 +13532,7 @@ async function runWebMcpStdio(platform2, getTools, callTool, serverName, version
|
|
|
13153
13532
|
const line = buffer.slice(0, nlIdx).trim();
|
|
13154
13533
|
buffer = buffer.slice(nlIdx + 1);
|
|
13155
13534
|
if (!line) continue;
|
|
13156
|
-
const
|
|
13157
|
-
const outcome = await handleJsonRpc(line, tools, callTool, serverName, version);
|
|
13535
|
+
const outcome = await handleJsonRpc(line, getTools, callTool, serverName, version);
|
|
13158
13536
|
if (outcome.status === 202) continue;
|
|
13159
13537
|
const writer = platform2.stdout.getWriter();
|
|
13160
13538
|
await writer.write(enc.encode(`${outcome.body}
|
|
@@ -13181,7 +13559,8 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13181
13559
|
return 2;
|
|
13182
13560
|
}
|
|
13183
13561
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13184
|
-
const listener = await platform2
|
|
13562
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --webmcp");
|
|
13563
|
+
if (typeof listener === "number") return listener;
|
|
13185
13564
|
const actualPort = listener.port;
|
|
13186
13565
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13187
13566
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13287,8 +13666,7 @@ async function runWebMcpHttp(platform2, options, getTools, callTool, serverName,
|
|
|
13287
13666
|
};
|
|
13288
13667
|
}
|
|
13289
13668
|
}
|
|
13290
|
-
const
|
|
13291
|
-
const outcome = await handleJsonRpc(request.body, tools, callTool, serverName, version);
|
|
13669
|
+
const outcome = await handleJsonRpc(request.body, getTools, callTool, serverName, version);
|
|
13292
13670
|
return {
|
|
13293
13671
|
status: outcome.status,
|
|
13294
13672
|
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
@@ -13376,10 +13754,11 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generated
|
|
|
13376
13754
|
return 2;
|
|
13377
13755
|
}
|
|
13378
13756
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13379
|
-
const listener = await platform2
|
|
13380
|
-
|
|
13381
|
-
|
|
13382
|
-
|
|
13757
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve --proxy");
|
|
13758
|
+
if (typeof listener === "number") {
|
|
13759
|
+
await proxy.shutdown();
|
|
13760
|
+
return listener;
|
|
13761
|
+
}
|
|
13383
13762
|
const actualPort = listener.port;
|
|
13384
13763
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13385
13764
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -13560,7 +13939,8 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
|
|
|
13560
13939
|
return 2;
|
|
13561
13940
|
}
|
|
13562
13941
|
const allowedOrigins = new Set(options.allowOrigins);
|
|
13563
|
-
const listener = await platform2
|
|
13942
|
+
const listener = await listenOrReport(platform2, options, "mcpcat serve");
|
|
13943
|
+
if (typeof listener === "number") return listener;
|
|
13564
13944
|
const actualPort = listener.port;
|
|
13565
13945
|
const serverOrigins = buildServerOrigins(options.host, actualPort);
|
|
13566
13946
|
const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
|
|
@@ -15692,6 +16072,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
|
15692
16072
|
if (toolArgs.length > 0) {
|
|
15693
16073
|
const tools = await conn.listTools();
|
|
15694
16074
|
const tool = tools.find((t) => t.name === toolName);
|
|
16075
|
+
const hidden = tool ? void 0 : conn.hiddenTool?.(toolName);
|
|
16076
|
+
if (hidden) {
|
|
16077
|
+
await writeErr(platform2, opts.json ?? false, "usage", hidden);
|
|
16078
|
+
return EXIT_USAGE;
|
|
16079
|
+
}
|
|
15695
16080
|
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
15696
16081
|
const skipRequired = opts.noValidate || !!dataArg;
|
|
15697
16082
|
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
|
|
@@ -16956,6 +17341,7 @@ function createKernelPlatform(ctx, options) {
|
|
|
16956
17341
|
fetch: createKernelFetch(ctx),
|
|
16957
17342
|
spawn: (options2) => kernelSpawn(ctx, options2),
|
|
16958
17343
|
listen: (options2) => kernelListen(ctx, options2),
|
|
17344
|
+
...ctx.websocket ? { websocket: (url) => ctx.websocket(url) } : {},
|
|
16959
17345
|
fs: kernelFs(ctx),
|
|
16960
17346
|
env: ctx.env,
|
|
16961
17347
|
cwd: ctx.cwd,
|
|
@@ -16991,7 +17377,7 @@ async function main(ctx) {
|
|
|
16991
17377
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16992
17378
|
});
|
|
16993
17379
|
const args = ctx.argv.slice(1);
|
|
16994
|
-
return run(platform2, "0.20.
|
|
17380
|
+
return run(platform2, "0.20.4", args);
|
|
16995
17381
|
}
|
|
16996
17382
|
export {
|
|
16997
17383
|
main as default
|