@ai-ecoverse/mcpcat 0.7.0 → 0.9.0
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 +1337 -249
- package/dist/kernel/mcpcat.mjs +1336 -248
- package/package.json +2 -2
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -5298,6 +5298,197 @@ var init_portable = __esm({
|
|
|
5298
5298
|
}
|
|
5299
5299
|
});
|
|
5300
5300
|
|
|
5301
|
+
// src/args/coerce.ts
|
|
5302
|
+
function asSchemaObject(schema) {
|
|
5303
|
+
return schema ?? {};
|
|
5304
|
+
}
|
|
5305
|
+
function emptyArgObject() {
|
|
5306
|
+
return /* @__PURE__ */ Object.create(null);
|
|
5307
|
+
}
|
|
5308
|
+
function isUnsafeObjectKey(key) {
|
|
5309
|
+
return key === "__proto__" || key === "prototype" || key === "constructor";
|
|
5310
|
+
}
|
|
5311
|
+
function isPlainObject(value) {
|
|
5312
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value !== Object.prototype;
|
|
5313
|
+
}
|
|
5314
|
+
function splitFlag(a) {
|
|
5315
|
+
const eq = a.indexOf("=");
|
|
5316
|
+
if (eq > 2) return { key: a.slice(2, eq), inlineValue: a.slice(eq + 1) };
|
|
5317
|
+
return { key: a.slice(2), inlineValue: void 0 };
|
|
5318
|
+
}
|
|
5319
|
+
function coerceScalar(raw, type) {
|
|
5320
|
+
switch (type) {
|
|
5321
|
+
case "integer": {
|
|
5322
|
+
if (!/^-?\d+$/.test(raw)) return { ok: false, error: `expected integer, got "${raw}"` };
|
|
5323
|
+
return { ok: true, value: Number(raw) };
|
|
5324
|
+
}
|
|
5325
|
+
case "number": {
|
|
5326
|
+
const n = Number(raw);
|
|
5327
|
+
if (!Number.isFinite(n)) return { ok: false, error: `expected number, got "${raw}"` };
|
|
5328
|
+
return { ok: true, value: n };
|
|
5329
|
+
}
|
|
5330
|
+
case "boolean": {
|
|
5331
|
+
if (raw === "true" || raw === "1" || raw === "yes") return { ok: true, value: true };
|
|
5332
|
+
if (raw === "false" || raw === "0" || raw === "no") return { ok: true, value: false };
|
|
5333
|
+
return { ok: false, error: `expected boolean, got "${raw}"` };
|
|
5334
|
+
}
|
|
5335
|
+
case "object":
|
|
5336
|
+
return coerceJsonObject(raw);
|
|
5337
|
+
default:
|
|
5338
|
+
return { ok: true, value: raw };
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
function coerceJsonObject(raw) {
|
|
5342
|
+
let parsed;
|
|
5343
|
+
try {
|
|
5344
|
+
parsed = JSON.parse(raw);
|
|
5345
|
+
} catch {
|
|
5346
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
5347
|
+
}
|
|
5348
|
+
if (!isPlainObject(parsed)) {
|
|
5349
|
+
return { ok: false, error: `expected object JSON, got "${raw}"` };
|
|
5350
|
+
}
|
|
5351
|
+
return { ok: true, value: parsed };
|
|
5352
|
+
}
|
|
5353
|
+
function resolveToolFlag(key, properties) {
|
|
5354
|
+
if (Object.hasOwn(properties, key)) {
|
|
5355
|
+
return { ok: true, rootKey: key, dottedPath: null, meta: properties[key] ?? {} };
|
|
5356
|
+
}
|
|
5357
|
+
const dot = key.indexOf(".");
|
|
5358
|
+
if (dot <= 0) return { ok: false, error: `unknown flag: --${key}` };
|
|
5359
|
+
const rootKey = key.slice(0, dot);
|
|
5360
|
+
const rest = key.slice(dot + 1);
|
|
5361
|
+
if (!Object.hasOwn(properties, rootKey) || rest.length === 0) {
|
|
5362
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5363
|
+
}
|
|
5364
|
+
const parts = rest.split(".");
|
|
5365
|
+
if (isUnsafeObjectKey(rootKey) || parts.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
5366
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5367
|
+
}
|
|
5368
|
+
return { ok: true, rootKey, dottedPath: parts, meta: properties[rootKey] ?? {} };
|
|
5369
|
+
}
|
|
5370
|
+
function readFlagRaw(args2, i, key, inlineValue, type, dottedPath) {
|
|
5371
|
+
if (inlineValue !== void 0) {
|
|
5372
|
+
return { ok: true, raw: inlineValue, nextIndex: i + 1, bareBoolean: false };
|
|
5373
|
+
}
|
|
5374
|
+
const next = args2[i + 1];
|
|
5375
|
+
if (!dottedPath && type === "boolean" && (next === void 0 || next.startsWith("--"))) {
|
|
5376
|
+
return { ok: true, raw: "", nextIndex: i + 1, bareBoolean: true };
|
|
5377
|
+
}
|
|
5378
|
+
if (next === void 0) {
|
|
5379
|
+
return { ok: false, error: `flag --${key} requires a value` };
|
|
5380
|
+
}
|
|
5381
|
+
return { ok: true, raw: next, nextIndex: i + 2, bareBoolean: false };
|
|
5382
|
+
}
|
|
5383
|
+
function setDotted(out, rootKey, path, value) {
|
|
5384
|
+
if (isUnsafeObjectKey(rootKey) || path.some((p) => p.length === 0 || isUnsafeObjectKey(p))) {
|
|
5385
|
+
return { ok: false, error: `unknown flag: --${rootKey}.${path.join(".")}` };
|
|
5386
|
+
}
|
|
5387
|
+
const existing = Object.hasOwn(out, rootKey) ? out[rootKey] : void 0;
|
|
5388
|
+
if (existing === void 0) {
|
|
5389
|
+
out[rootKey] = emptyArgObject();
|
|
5390
|
+
} else if (!isPlainObject(existing)) {
|
|
5391
|
+
return { ok: false, error: `--${rootKey}: cannot nest into a ${typeof existing} value` };
|
|
5392
|
+
}
|
|
5393
|
+
let cursor = out[rootKey];
|
|
5394
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
5395
|
+
const seg = path[i];
|
|
5396
|
+
const next = Object.hasOwn(cursor, seg) ? cursor[seg] : void 0;
|
|
5397
|
+
if (next === void 0) {
|
|
5398
|
+
cursor[seg] = emptyArgObject();
|
|
5399
|
+
} else if (!isPlainObject(next)) {
|
|
5400
|
+
return {
|
|
5401
|
+
ok: false,
|
|
5402
|
+
error: `--${rootKey}.${path.slice(0, i + 1).join(".")}: cannot nest into a ${typeof next} value`
|
|
5403
|
+
};
|
|
5404
|
+
}
|
|
5405
|
+
cursor = cursor[seg];
|
|
5406
|
+
}
|
|
5407
|
+
cursor[path[path.length - 1]] = value;
|
|
5408
|
+
return { ok: true };
|
|
5409
|
+
}
|
|
5410
|
+
function assignFlagValue(out, rootKey, dottedPath, value, isArray) {
|
|
5411
|
+
if (dottedPath) return setDotted(out, rootKey, dottedPath, value);
|
|
5412
|
+
if (isArray) {
|
|
5413
|
+
const prev = out[rootKey];
|
|
5414
|
+
if (Array.isArray(prev)) prev.push(value);
|
|
5415
|
+
else out[rootKey] = [value];
|
|
5416
|
+
return { ok: true };
|
|
5417
|
+
}
|
|
5418
|
+
const existing = out[rootKey];
|
|
5419
|
+
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
5420
|
+
Object.assign(existing, value);
|
|
5421
|
+
return { ok: true };
|
|
5422
|
+
}
|
|
5423
|
+
out[rootKey] = value;
|
|
5424
|
+
return { ok: true };
|
|
5425
|
+
}
|
|
5426
|
+
function parseOneFlag(args2, i, properties, out) {
|
|
5427
|
+
const a = args2[i];
|
|
5428
|
+
if (a === "--") return { ok: true, nextIndex: args2.length };
|
|
5429
|
+
if (a.startsWith("--no-") && a.length > 5) {
|
|
5430
|
+
const key2 = a.slice(5);
|
|
5431
|
+
if (Object.hasOwn(properties, key2) && properties[key2]?.type === "boolean") {
|
|
5432
|
+
out[key2] = false;
|
|
5433
|
+
return { ok: true, nextIndex: i + 1 };
|
|
5434
|
+
}
|
|
5435
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
5436
|
+
}
|
|
5437
|
+
if (!a.startsWith("--")) {
|
|
5438
|
+
return { ok: false, error: `unexpected positional argument "${a}"` };
|
|
5439
|
+
}
|
|
5440
|
+
const { key, inlineValue } = splitFlag(a);
|
|
5441
|
+
const resolved = resolveToolFlag(key, properties);
|
|
5442
|
+
if (!resolved.ok) return resolved;
|
|
5443
|
+
const { rootKey, dottedPath, meta } = resolved;
|
|
5444
|
+
const declaredType = meta.type;
|
|
5445
|
+
const type = declaredType ?? "string";
|
|
5446
|
+
const isArray = type === "array";
|
|
5447
|
+
const itemType = (isArray ? meta.items?.type : void 0) ?? "string";
|
|
5448
|
+
if (dottedPath && declaredType !== void 0 && declaredType !== "object") {
|
|
5449
|
+
return { ok: false, error: `unknown flag: --${key}` };
|
|
5450
|
+
}
|
|
5451
|
+
const taken = readFlagRaw(args2, i, key, inlineValue, type, dottedPath);
|
|
5452
|
+
if (!taken.ok) return taken;
|
|
5453
|
+
if (taken.bareBoolean) {
|
|
5454
|
+
out[rootKey] = true;
|
|
5455
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5456
|
+
}
|
|
5457
|
+
const valueType = dottedPath ? "string" : isArray ? itemType : type;
|
|
5458
|
+
const coerced = coerceScalar(taken.raw, valueType);
|
|
5459
|
+
if (!coerced.ok) return { ok: false, error: `--${key}: ${coerced.error}` };
|
|
5460
|
+
const assigned = assignFlagValue(out, rootKey, dottedPath, coerced.value, isArray);
|
|
5461
|
+
if (!assigned.ok) return assigned;
|
|
5462
|
+
return { ok: true, nextIndex: taken.nextIndex };
|
|
5463
|
+
}
|
|
5464
|
+
function coerceArgsBySchema(args2, schema, options) {
|
|
5465
|
+
const s = asSchemaObject(schema);
|
|
5466
|
+
const properties = s.properties ?? {};
|
|
5467
|
+
const required = new Set(Array.isArray(s.required) ? s.required : []);
|
|
5468
|
+
const out = emptyArgObject();
|
|
5469
|
+
let i = 0;
|
|
5470
|
+
while (i < args2.length) {
|
|
5471
|
+
const result = parseOneFlag(args2, i, properties, out);
|
|
5472
|
+
if (!result.ok) return result;
|
|
5473
|
+
i = result.nextIndex;
|
|
5474
|
+
}
|
|
5475
|
+
if (!options?.noValidate) {
|
|
5476
|
+
for (const r of required) {
|
|
5477
|
+
if (!Object.hasOwn(out, r)) {
|
|
5478
|
+
return { ok: false, error: `missing required flag --${r}` };
|
|
5479
|
+
}
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
return { ok: true, value: out };
|
|
5483
|
+
}
|
|
5484
|
+
function mergeArgs(base, flags) {
|
|
5485
|
+
const result = { ...base };
|
|
5486
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
5487
|
+
result[k] = v;
|
|
5488
|
+
}
|
|
5489
|
+
return result;
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5301
5492
|
// node_modules/@earendil-works/pi-mcp/dist/protocol/jsonrpc.js
|
|
5302
5493
|
var JSON_RPC_ERROR_CODES = {
|
|
5303
5494
|
parseError: -32700,
|
|
@@ -6651,67 +6842,6 @@ var PlatformStdioTransport = class {
|
|
|
6651
6842
|
}
|
|
6652
6843
|
};
|
|
6653
6844
|
|
|
6654
|
-
// src/client/pi-connection.ts
|
|
6655
|
-
async function createPiConnection(options) {
|
|
6656
|
-
const clientOptions = {
|
|
6657
|
-
name: "mcpcat",
|
|
6658
|
-
version: options.version ?? "0.0.0"
|
|
6659
|
-
};
|
|
6660
|
-
const client = new McpClient(clientOptions);
|
|
6661
|
-
let transport;
|
|
6662
|
-
if (options.url) {
|
|
6663
|
-
transport = new StreamableHttpTransport({
|
|
6664
|
-
url: options.url,
|
|
6665
|
-
headers: options.headers,
|
|
6666
|
-
fetch: options.fetchImpl
|
|
6667
|
-
});
|
|
6668
|
-
} else if (options.command) {
|
|
6669
|
-
if (options.platform) {
|
|
6670
|
-
transport = new PlatformStdioTransport({
|
|
6671
|
-
command: options.command,
|
|
6672
|
-
args: options.args,
|
|
6673
|
-
env: options.env,
|
|
6674
|
-
cwd: options.cwd,
|
|
6675
|
-
inheritEnv: true,
|
|
6676
|
-
platform: options.platform
|
|
6677
|
-
});
|
|
6678
|
-
} else {
|
|
6679
|
-
transport = new StdioTransport({
|
|
6680
|
-
command: options.command,
|
|
6681
|
-
args: options.args,
|
|
6682
|
-
env: options.env,
|
|
6683
|
-
cwd: options.cwd,
|
|
6684
|
-
inheritEnv: true,
|
|
6685
|
-
stderr: "pipe"
|
|
6686
|
-
});
|
|
6687
|
-
}
|
|
6688
|
-
} else {
|
|
6689
|
-
throw new Error("PiConnection requires either a url or a command");
|
|
6690
|
-
}
|
|
6691
|
-
const initResult = await client.connect(transport);
|
|
6692
|
-
return {
|
|
6693
|
-
async info() {
|
|
6694
|
-
return {
|
|
6695
|
-
name: initResult.serverInfo?.name,
|
|
6696
|
-
version: initResult.serverInfo?.version,
|
|
6697
|
-
title: initResult.serverInfo?.title,
|
|
6698
|
-
protocolVersion: initResult.protocolVersion,
|
|
6699
|
-
capabilities: initResult.capabilities,
|
|
6700
|
-
instructions: initResult.instructions
|
|
6701
|
-
};
|
|
6702
|
-
},
|
|
6703
|
-
async listTools() {
|
|
6704
|
-
return client.listTools();
|
|
6705
|
-
},
|
|
6706
|
-
async callTool(name, args2) {
|
|
6707
|
-
return client.callTool(name, args2);
|
|
6708
|
-
},
|
|
6709
|
-
async close() {
|
|
6710
|
-
await client.close();
|
|
6711
|
-
}
|
|
6712
|
-
};
|
|
6713
|
-
}
|
|
6714
|
-
|
|
6715
6845
|
// src/client/stateless-connection.ts
|
|
6716
6846
|
var STATELESS_PROTOCOL_VERSION = "2026-07-28";
|
|
6717
6847
|
var StatelessRpcError = class extends Error {
|
|
@@ -6740,6 +6870,22 @@ var InputRequiredError = class extends Error {
|
|
|
6740
6870
|
this.requestJson = requestJson;
|
|
6741
6871
|
}
|
|
6742
6872
|
};
|
|
6873
|
+
var McpTimeoutError2 = class extends Error {
|
|
6874
|
+
constructor(method, ms) {
|
|
6875
|
+
super(`timeout after ${ms}ms waiting for ${method}`);
|
|
6876
|
+
this.name = "McpTimeoutError";
|
|
6877
|
+
}
|
|
6878
|
+
};
|
|
6879
|
+
var McpAuthError = class extends Error {
|
|
6880
|
+
status;
|
|
6881
|
+
target;
|
|
6882
|
+
constructor(status, target) {
|
|
6883
|
+
super(`HTTP ${status}: authentication required. Run: mcpcat login ${target}`);
|
|
6884
|
+
this.name = "McpAuthError";
|
|
6885
|
+
this.status = status;
|
|
6886
|
+
this.target = target;
|
|
6887
|
+
}
|
|
6888
|
+
};
|
|
6743
6889
|
function selectSseFrame(body, targetId) {
|
|
6744
6890
|
const text = new TextDecoder().decode(body);
|
|
6745
6891
|
const blocks = text.replace(/\r\n/g, "\n").split(/\n\n/);
|
|
@@ -6770,25 +6916,66 @@ async function createStatelessConnection(options) {
|
|
|
6770
6916
|
headers: extraHeaders,
|
|
6771
6917
|
fetchImpl,
|
|
6772
6918
|
timeoutMs = 6e4,
|
|
6773
|
-
clientInfo = { name: "mcpcat", version: "0.0.0" }
|
|
6919
|
+
clientInfo = { name: "mcpcat", version: "0.0.0" },
|
|
6920
|
+
signal: externalSignal,
|
|
6921
|
+
verbose
|
|
6774
6922
|
} = options;
|
|
6775
6923
|
let nextId2 = 1;
|
|
6776
6924
|
let negotiatedVersion;
|
|
6777
|
-
|
|
6778
|
-
const
|
|
6779
|
-
const headers = {
|
|
6925
|
+
function buildHeaders(method, params) {
|
|
6926
|
+
const h = {
|
|
6780
6927
|
"Content-Type": "application/json",
|
|
6781
6928
|
Accept: "application/json, text/event-stream",
|
|
6782
6929
|
"Mcp-Method": method,
|
|
6783
6930
|
...extraHeaders
|
|
6784
6931
|
};
|
|
6785
|
-
const
|
|
6786
|
-
if (
|
|
6787
|
-
|
|
6932
|
+
const n = mcpNameForMethod(method, params);
|
|
6933
|
+
if (n) h["Mcp-Name"] = n;
|
|
6934
|
+
if (negotiatedVersion) h["Mcp-Protocol-Version"] = negotiatedVersion;
|
|
6935
|
+
return h;
|
|
6936
|
+
}
|
|
6937
|
+
function makeAbort() {
|
|
6938
|
+
const controller = new AbortController();
|
|
6939
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
6940
|
+
if (externalSignal) {
|
|
6941
|
+
if (externalSignal.aborted) {
|
|
6942
|
+
clearTimeout(timer);
|
|
6943
|
+
controller.abort();
|
|
6944
|
+
} else {
|
|
6945
|
+
externalSignal.addEventListener(
|
|
6946
|
+
"abort",
|
|
6947
|
+
() => {
|
|
6948
|
+
clearTimeout(timer);
|
|
6949
|
+
controller.abort();
|
|
6950
|
+
},
|
|
6951
|
+
{ once: true }
|
|
6952
|
+
);
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6955
|
+
return { controller, timer };
|
|
6956
|
+
}
|
|
6957
|
+
function logHeaders(dir, entries) {
|
|
6958
|
+
if (!verbose?.enabled) return;
|
|
6959
|
+
for (const [k, v] of entries) {
|
|
6960
|
+
if (dir === ">") verbose.headerOut(k, v);
|
|
6961
|
+
else verbose.headerIn(k, v);
|
|
6788
6962
|
}
|
|
6789
|
-
|
|
6790
|
-
|
|
6963
|
+
}
|
|
6964
|
+
async function handleErrorStatus(res, method) {
|
|
6965
|
+
if (res.status === 401 || res.status === 403) throw new McpAuthError(res.status, url);
|
|
6966
|
+
const text = await res.text();
|
|
6967
|
+
let rpc;
|
|
6968
|
+
try {
|
|
6969
|
+
const p = JSON.parse(text);
|
|
6970
|
+
if (p.error && typeof p.error.code === "number") rpc = p.error;
|
|
6971
|
+
} catch {
|
|
6791
6972
|
}
|
|
6973
|
+
if (rpc) throw new StatelessRpcError(rpc.code, rpc.message, rpc.data);
|
|
6974
|
+
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
6975
|
+
}
|
|
6976
|
+
async function request(method, params) {
|
|
6977
|
+
const id = nextId2++;
|
|
6978
|
+
const headers = buildHeaders(method, params);
|
|
6792
6979
|
const meta = {
|
|
6793
6980
|
protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
|
|
6794
6981
|
clientInfo
|
|
@@ -6799,8 +6986,11 @@ async function createStatelessConnection(options) {
|
|
|
6799
6986
|
method,
|
|
6800
6987
|
params: { ...params, _meta: meta }
|
|
6801
6988
|
});
|
|
6802
|
-
|
|
6803
|
-
|
|
6989
|
+
if (verbose?.enabled) {
|
|
6990
|
+
verbose.request(method, body);
|
|
6991
|
+
logHeaders(">", Object.entries(headers));
|
|
6992
|
+
}
|
|
6993
|
+
const { controller, timer } = makeAbort();
|
|
6804
6994
|
let res;
|
|
6805
6995
|
try {
|
|
6806
6996
|
res = await fetchImpl(url, {
|
|
@@ -6809,35 +6999,28 @@ async function createStatelessConnection(options) {
|
|
|
6809
6999
|
body,
|
|
6810
7000
|
signal: controller.signal
|
|
6811
7001
|
});
|
|
7002
|
+
} catch (err) {
|
|
7003
|
+
clearTimeout(timer);
|
|
7004
|
+
if (controller.signal.aborted && !externalSignal?.aborted) {
|
|
7005
|
+
throw new McpTimeoutError2(method, timeoutMs);
|
|
7006
|
+
}
|
|
7007
|
+
throw err;
|
|
6812
7008
|
} finally {
|
|
6813
7009
|
clearTimeout(timer);
|
|
6814
7010
|
}
|
|
6815
|
-
if (
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
if (res.status >= 400) {
|
|
6819
|
-
const text = await res.text();
|
|
6820
|
-
let rpcError2;
|
|
6821
|
-
try {
|
|
6822
|
-
const parsed = JSON.parse(text);
|
|
6823
|
-
if (parsed.error && typeof parsed.error.code === "number") {
|
|
6824
|
-
rpcError2 = parsed.error;
|
|
6825
|
-
}
|
|
6826
|
-
} catch {
|
|
6827
|
-
}
|
|
6828
|
-
if (rpcError2) {
|
|
6829
|
-
throw new StatelessRpcError(rpcError2.code, rpcError2.message, rpcError2.data);
|
|
6830
|
-
}
|
|
6831
|
-
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
7011
|
+
if (verbose?.enabled) {
|
|
7012
|
+
verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
|
|
7013
|
+
logHeaders("<", res.headers.entries());
|
|
6832
7014
|
}
|
|
7015
|
+
if (res.status >= 400) await handleErrorStatus(res, method);
|
|
6833
7016
|
const ct = res.headers.get("content-type") ?? "";
|
|
6834
7017
|
let frame;
|
|
6835
7018
|
if (ct.toLowerCase().includes("text/event-stream")) {
|
|
6836
|
-
|
|
6837
|
-
frame = selectSseFrame(bytes, id);
|
|
7019
|
+
frame = selectSseFrame(new Uint8Array(await res.arrayBuffer()), id);
|
|
6838
7020
|
} else {
|
|
6839
7021
|
frame = await res.json();
|
|
6840
7022
|
}
|
|
7023
|
+
if (verbose?.enabled) verbose.response(JSON.stringify(frame));
|
|
6841
7024
|
if (frame.error) {
|
|
6842
7025
|
throw new StatelessRpcError(frame.error.code, frame.error.message, frame.error.data);
|
|
6843
7026
|
}
|
|
@@ -6857,28 +7040,26 @@ async function createStatelessConnection(options) {
|
|
|
6857
7040
|
return {
|
|
6858
7041
|
async info() {
|
|
6859
7042
|
if (serverInfoCache) return serverInfoCache;
|
|
6860
|
-
const
|
|
7043
|
+
const r = discoverResult2;
|
|
6861
7044
|
serverInfoCache = {
|
|
6862
|
-
name:
|
|
6863
|
-
version:
|
|
6864
|
-
title:
|
|
7045
|
+
name: r?.serverInfo?.name,
|
|
7046
|
+
version: r?.serverInfo?.version,
|
|
7047
|
+
title: r?.serverInfo?.title,
|
|
6865
7048
|
protocolVersion: STATELESS_PROTOCOL_VERSION,
|
|
6866
|
-
capabilities:
|
|
6867
|
-
instructions:
|
|
7049
|
+
capabilities: r?.capabilities ?? {},
|
|
7050
|
+
instructions: r?.instructions
|
|
6868
7051
|
};
|
|
6869
7052
|
return serverInfoCache;
|
|
6870
7053
|
},
|
|
6871
7054
|
async listTools() {
|
|
6872
|
-
|
|
6873
|
-
return result?.tools ?? [];
|
|
7055
|
+
return (await request("tools/list", {}))?.tools ?? [];
|
|
6874
7056
|
},
|
|
6875
|
-
async callTool(name, args2) {
|
|
7057
|
+
async callTool(name, args2, _callOptions) {
|
|
6876
7058
|
const result = await request("tools/call", {
|
|
6877
7059
|
name,
|
|
6878
7060
|
arguments: args2 ?? {}
|
|
6879
7061
|
});
|
|
6880
|
-
|
|
6881
|
-
if (resultType === "input_required") {
|
|
7062
|
+
if ((result.resultType ?? "complete") === "input_required") {
|
|
6882
7063
|
throw new InputRequiredError(
|
|
6883
7064
|
JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
|
|
6884
7065
|
);
|
|
@@ -6890,28 +7071,138 @@ async function createStatelessConnection(options) {
|
|
|
6890
7071
|
};
|
|
6891
7072
|
}
|
|
6892
7073
|
|
|
7074
|
+
// src/client/pi-connection.ts
|
|
7075
|
+
function buildStderrHandler(options) {
|
|
7076
|
+
if (!options.verbose?.enabled) return void 0;
|
|
7077
|
+
return (chunk) => {
|
|
7078
|
+
for (const l of chunk.split("\n")) {
|
|
7079
|
+
if (l.trim()) options.verbose.server(l);
|
|
7080
|
+
}
|
|
7081
|
+
};
|
|
7082
|
+
}
|
|
7083
|
+
function buildTransport(options) {
|
|
7084
|
+
if (options.url) {
|
|
7085
|
+
return new StreamableHttpTransport({
|
|
7086
|
+
url: options.url,
|
|
7087
|
+
headers: options.headers,
|
|
7088
|
+
fetch: options.fetchImpl
|
|
7089
|
+
});
|
|
7090
|
+
}
|
|
7091
|
+
if (!options.command) throw new Error("PiConnection requires either a url or a command");
|
|
7092
|
+
if (options.platform) {
|
|
7093
|
+
return new PlatformStdioTransport({
|
|
7094
|
+
command: options.command,
|
|
7095
|
+
args: options.args,
|
|
7096
|
+
env: options.env,
|
|
7097
|
+
cwd: options.cwd,
|
|
7098
|
+
inheritEnv: true,
|
|
7099
|
+
platform: options.platform,
|
|
7100
|
+
onStderr: buildStderrHandler(options)
|
|
7101
|
+
});
|
|
7102
|
+
}
|
|
7103
|
+
return new StdioTransport({
|
|
7104
|
+
command: options.command,
|
|
7105
|
+
args: options.args,
|
|
7106
|
+
env: options.env,
|
|
7107
|
+
cwd: options.cwd,
|
|
7108
|
+
inheritEnv: true,
|
|
7109
|
+
stderr: "pipe",
|
|
7110
|
+
onStderr: buildStderrHandler(options)
|
|
7111
|
+
});
|
|
7112
|
+
}
|
|
7113
|
+
async function createPiConnection(options) {
|
|
7114
|
+
const clientOptions = {
|
|
7115
|
+
name: "mcpcat",
|
|
7116
|
+
version: options.version ?? "0.0.0",
|
|
7117
|
+
requestTimeoutMs: options.timeoutMs
|
|
7118
|
+
};
|
|
7119
|
+
const client = new McpClient(clientOptions);
|
|
7120
|
+
const transport = buildTransport(options);
|
|
7121
|
+
if (options.verbose?.enabled) {
|
|
7122
|
+
transport.onMessage((msg) => {
|
|
7123
|
+
options.verbose.response(JSON.stringify(msg));
|
|
7124
|
+
});
|
|
7125
|
+
}
|
|
7126
|
+
try {
|
|
7127
|
+
let reqOpts2 = function() {
|
|
7128
|
+
const o = {};
|
|
7129
|
+
if (options.signal) o.signal = options.signal;
|
|
7130
|
+
if (options.verbose?.enabled) {
|
|
7131
|
+
o.onProgress = (p) => {
|
|
7132
|
+
options.verbose.progress(`progress: ${p.message ?? ""}`);
|
|
7133
|
+
};
|
|
7134
|
+
}
|
|
7135
|
+
return o;
|
|
7136
|
+
};
|
|
7137
|
+
var reqOpts = reqOpts2;
|
|
7138
|
+
const initResult = await client.connect(transport);
|
|
7139
|
+
return {
|
|
7140
|
+
async info() {
|
|
7141
|
+
return {
|
|
7142
|
+
name: initResult.serverInfo?.name,
|
|
7143
|
+
version: initResult.serverInfo?.version,
|
|
7144
|
+
title: initResult.serverInfo?.title,
|
|
7145
|
+
protocolVersion: initResult.protocolVersion,
|
|
7146
|
+
capabilities: initResult.capabilities,
|
|
7147
|
+
instructions: initResult.instructions
|
|
7148
|
+
};
|
|
7149
|
+
},
|
|
7150
|
+
async listTools() {
|
|
7151
|
+
return client.listTools(reqOpts2());
|
|
7152
|
+
},
|
|
7153
|
+
async callTool(name, args2, callOptions) {
|
|
7154
|
+
const o = reqOpts2();
|
|
7155
|
+
if (callOptions?.onProgress) {
|
|
7156
|
+
const outer = callOptions.onProgress;
|
|
7157
|
+
const inner = o.onProgress;
|
|
7158
|
+
o.onProgress = (p) => {
|
|
7159
|
+
outer(p.progress, p.total, p.message);
|
|
7160
|
+
inner?.(p);
|
|
7161
|
+
};
|
|
7162
|
+
}
|
|
7163
|
+
return client.callTool(name, args2, o);
|
|
7164
|
+
},
|
|
7165
|
+
async close() {
|
|
7166
|
+
await client.close();
|
|
7167
|
+
}
|
|
7168
|
+
};
|
|
7169
|
+
} catch (err) {
|
|
7170
|
+
const msg = err.message ?? "";
|
|
7171
|
+
if (msg.includes("401") || msg.includes("403") || msg.includes("Unauthorized")) {
|
|
7172
|
+
throw new McpAuthError(401, options.url ?? options.command ?? "");
|
|
7173
|
+
}
|
|
7174
|
+
if (msg.includes("timeout") || msg.includes("Timeout") || msg.includes("ETIMEDOUT")) {
|
|
7175
|
+
throw new McpTimeoutError2("connect", options.timeoutMs ?? 6e4);
|
|
7176
|
+
}
|
|
7177
|
+
throw err;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
|
|
6893
7181
|
// src/client/connect.ts
|
|
6894
7182
|
async function connect(platform2, options) {
|
|
7183
|
+
const shared = {
|
|
7184
|
+
version: options.version,
|
|
7185
|
+
timeoutMs: options.timeoutMs,
|
|
7186
|
+
signal: options.signal,
|
|
7187
|
+
verbose: options.verbose,
|
|
7188
|
+
platform: platform2
|
|
7189
|
+
};
|
|
6895
7190
|
if (options.command) {
|
|
6896
7191
|
return createPiConnection({
|
|
6897
7192
|
command: options.command,
|
|
6898
7193
|
args: options.args,
|
|
6899
7194
|
env: options.env,
|
|
6900
7195
|
cwd: options.cwd,
|
|
6901
|
-
|
|
6902
|
-
platform: platform2
|
|
7196
|
+
...shared
|
|
6903
7197
|
});
|
|
6904
7198
|
}
|
|
6905
|
-
if (!options.url)
|
|
6906
|
-
throw new Error("connect requires either a url or a command");
|
|
6907
|
-
}
|
|
7199
|
+
if (!options.url) throw new Error("connect requires either a url or a command");
|
|
6908
7200
|
if (options.protocolVersion && !options.protocolVersion.startsWith("2026")) {
|
|
6909
7201
|
return createPiConnection({
|
|
6910
7202
|
url: options.url,
|
|
6911
7203
|
headers: options.headers,
|
|
6912
|
-
version: options.version,
|
|
6913
7204
|
fetchImpl: platform2.fetch,
|
|
6914
|
-
|
|
7205
|
+
...shared
|
|
6915
7206
|
});
|
|
6916
7207
|
}
|
|
6917
7208
|
try {
|
|
@@ -6919,20 +7210,20 @@ async function connect(platform2, options) {
|
|
|
6919
7210
|
url: options.url,
|
|
6920
7211
|
headers: options.headers,
|
|
6921
7212
|
fetchImpl: platform2.fetch,
|
|
6922
|
-
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" }
|
|
7213
|
+
clientInfo: { name: "mcpcat", version: options.version ?? "0.0.0" },
|
|
7214
|
+
timeoutMs: options.timeoutMs,
|
|
7215
|
+
signal: options.signal,
|
|
7216
|
+
verbose: options.verbose
|
|
6923
7217
|
});
|
|
6924
7218
|
} catch (err) {
|
|
6925
7219
|
const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601 || err instanceof StatelessHttpError && err.status >= 400 && err.status < 500;
|
|
6926
|
-
if (options.protocolVersion?.startsWith("2026"))
|
|
6927
|
-
throw err;
|
|
6928
|
-
}
|
|
7220
|
+
if (options.protocolVersion?.startsWith("2026")) throw err;
|
|
6929
7221
|
if (isMethodNotFound) {
|
|
6930
7222
|
return createPiConnection({
|
|
6931
7223
|
url: options.url,
|
|
6932
7224
|
headers: options.headers,
|
|
6933
|
-
version: options.version,
|
|
6934
7225
|
fetchImpl: platform2.fetch,
|
|
6935
|
-
|
|
7226
|
+
...shared
|
|
6936
7227
|
});
|
|
6937
7228
|
}
|
|
6938
7229
|
throw err;
|
|
@@ -7484,6 +7775,28 @@ async function readStream(stream) {
|
|
|
7484
7775
|
return new TextDecoder().decode(merged);
|
|
7485
7776
|
}
|
|
7486
7777
|
|
|
7778
|
+
// src/config/expand.ts
|
|
7779
|
+
var PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
7780
|
+
var PLACEHOLDER_TEST = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
|
|
7781
|
+
function containsPlaceholders(value) {
|
|
7782
|
+
return PLACEHOLDER_TEST.test(value);
|
|
7783
|
+
}
|
|
7784
|
+
function expandPlaceholders(value, env) {
|
|
7785
|
+
return value.replace(PLACEHOLDER, (_, name) => env[name] ?? "");
|
|
7786
|
+
}
|
|
7787
|
+
function expandPlaceholdersStrict(value, env) {
|
|
7788
|
+
let hasUndefined = false;
|
|
7789
|
+
const result = value.replace(PLACEHOLDER, (_, name) => {
|
|
7790
|
+
if (env[name] === void 0) {
|
|
7791
|
+
hasUndefined = true;
|
|
7792
|
+
return "";
|
|
7793
|
+
}
|
|
7794
|
+
return env[name];
|
|
7795
|
+
});
|
|
7796
|
+
if (hasUndefined) return void 0;
|
|
7797
|
+
return result;
|
|
7798
|
+
}
|
|
7799
|
+
|
|
7487
7800
|
// src/config/parse.ts
|
|
7488
7801
|
var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
|
|
7489
7802
|
var EXPOSURES = ["codemode", "deferred", "direct", "hidden"];
|
|
@@ -8720,23 +9033,98 @@ async function runDiscover(platform2, args2) {
|
|
|
8720
9033
|
return 0;
|
|
8721
9034
|
}
|
|
8722
9035
|
|
|
8723
|
-
// src/
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
return
|
|
9036
|
+
// src/render/result.ts
|
|
9037
|
+
function formatBytes(n) {
|
|
9038
|
+
if (n < 1024) return `${n} bytes`;
|
|
9039
|
+
const kb = n / 1024;
|
|
9040
|
+
if (kb < 1024) return `${kb.toFixed(1)} KB`;
|
|
9041
|
+
return `${(kb / 1024).toFixed(1)} MB`;
|
|
9042
|
+
}
|
|
9043
|
+
function truncateMiddle(text, max) {
|
|
9044
|
+
if (text.length <= max) return text;
|
|
9045
|
+
const half = Math.floor((max - 5) / 2);
|
|
9046
|
+
return `${text.slice(0, half)}
|
|
9047
|
+
[\u2026]
|
|
9048
|
+
${text.slice(-half)}`;
|
|
9049
|
+
}
|
|
9050
|
+
function renderContentBlock(block) {
|
|
9051
|
+
if (block.type === "text") return block.text;
|
|
9052
|
+
if (block.type === "image") {
|
|
9053
|
+
const size = formatBytes(Math.ceil(block.data.length * 3 / 4));
|
|
9054
|
+
return `[image: ${block.mimeType}, ${size}]`;
|
|
9055
|
+
}
|
|
9056
|
+
if (block.type === "audio") {
|
|
9057
|
+
const audioBlock = block;
|
|
9058
|
+
const size = formatBytes(Math.ceil(audioBlock.data.length * 3 / 4));
|
|
9059
|
+
return `[audio: ${audioBlock.mimeType}, ${size}]`;
|
|
9060
|
+
}
|
|
9061
|
+
if (block.type === "resource_link") return `[resource: ${block.uri}]`;
|
|
9062
|
+
if (block.type === "resource") {
|
|
9063
|
+
return "text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`;
|
|
9064
|
+
}
|
|
9065
|
+
return void 0;
|
|
8728
9066
|
}
|
|
8729
|
-
function
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
9067
|
+
function renderCallResultText(result, options) {
|
|
9068
|
+
const parts = [];
|
|
9069
|
+
if (result.content) {
|
|
9070
|
+
for (const block of result.content) {
|
|
9071
|
+
const rendered = renderContentBlock(block);
|
|
9072
|
+
if (rendered !== void 0) parts.push(rendered);
|
|
8735
9073
|
}
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
9074
|
+
}
|
|
9075
|
+
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
9076
|
+
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
9077
|
+
}
|
|
9078
|
+
let text = parts.join("\n");
|
|
9079
|
+
if (options?.maxChars && text.length > options.maxChars) {
|
|
9080
|
+
text = truncateMiddle(text, options.maxChars);
|
|
9081
|
+
}
|
|
9082
|
+
return text;
|
|
9083
|
+
}
|
|
9084
|
+
function renderCallResultJson(result) {
|
|
9085
|
+
return JSON.stringify(result, null, 2);
|
|
9086
|
+
}
|
|
9087
|
+
async function renderCallResult(platform2, result, options) {
|
|
9088
|
+
if (options?.json) {
|
|
9089
|
+
const json = renderCallResultJson(result);
|
|
9090
|
+
if (result.isError) {
|
|
9091
|
+
await platform2.write(
|
|
9092
|
+
2,
|
|
9093
|
+
`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
|
|
9094
|
+
`
|
|
9095
|
+
);
|
|
9096
|
+
return 1;
|
|
9097
|
+
}
|
|
9098
|
+
await platform2.write(1, `${json}
|
|
9099
|
+
`);
|
|
9100
|
+
return 0;
|
|
9101
|
+
}
|
|
9102
|
+
const text = renderCallResultText(result, options);
|
|
9103
|
+
if (result.isError) {
|
|
9104
|
+
await platform2.write(2, `${text || "(tool reported error)"}
|
|
9105
|
+
`);
|
|
9106
|
+
return 1;
|
|
9107
|
+
}
|
|
9108
|
+
if (text) {
|
|
9109
|
+
await platform2.write(1, `${text}
|
|
9110
|
+
`);
|
|
9111
|
+
}
|
|
9112
|
+
return 0;
|
|
9113
|
+
}
|
|
9114
|
+
async function renderToolsJson(platform2, tools) {
|
|
9115
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
9116
|
+
`);
|
|
9117
|
+
}
|
|
9118
|
+
async function renderInfoJson(platform2, info) {
|
|
9119
|
+
await platform2.write(1, `${JSON.stringify(info, null, 2)}
|
|
9120
|
+
`);
|
|
9121
|
+
}
|
|
9122
|
+
async function renderErrorJson(platform2, kind, message, code2, data) {
|
|
9123
|
+
const errorObj = { kind, message };
|
|
9124
|
+
if (code2 !== void 0) errorObj.code = code2;
|
|
9125
|
+
if (data !== void 0) errorObj.data = data;
|
|
9126
|
+
await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
|
|
9127
|
+
`);
|
|
8740
9128
|
}
|
|
8741
9129
|
|
|
8742
9130
|
// src/serve/catalog.ts
|
|
@@ -9539,29 +9927,231 @@ function checkHost(host, boundLoopback) {
|
|
|
9539
9927
|
return { ok: false, reason: `host ${host} is not loopback` };
|
|
9540
9928
|
}
|
|
9541
9929
|
|
|
9542
|
-
// src/serve/
|
|
9543
|
-
var
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9930
|
+
// src/serve/proxy.ts
|
|
9931
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
9932
|
+
"initialize",
|
|
9933
|
+
"server/discover",
|
|
9934
|
+
"tools/list",
|
|
9935
|
+
"resources/list",
|
|
9936
|
+
"prompts/list",
|
|
9937
|
+
"ping"
|
|
9938
|
+
]);
|
|
9939
|
+
var PROTOCOL_VERSIONS2 = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
9940
|
+
var JSON_CT2 = "application/json; charset=utf-8";
|
|
9941
|
+
function readId2(value) {
|
|
9942
|
+
const id = Object.getOwnPropertyDescriptor(value, "id")?.value;
|
|
9943
|
+
if (typeof id === "string" || typeof id === "number" || id === null) return id;
|
|
9944
|
+
return null;
|
|
9554
9945
|
}
|
|
9555
|
-
function
|
|
9556
|
-
const
|
|
9557
|
-
|
|
9558
|
-
return { name: arg.slice(0, eqIdx), path: arg.slice(eqIdx + 1) };
|
|
9559
|
-
}
|
|
9560
|
-
return { path: arg };
|
|
9946
|
+
function readString2(value, key) {
|
|
9947
|
+
const found = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
9948
|
+
return typeof found === "string" ? found : void 0;
|
|
9561
9949
|
}
|
|
9562
|
-
function
|
|
9563
|
-
|
|
9564
|
-
|
|
9950
|
+
function hasOwn2(value, key) {
|
|
9951
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
9952
|
+
}
|
|
9953
|
+
function rpcOk2(id, result) {
|
|
9954
|
+
return {
|
|
9955
|
+
status: 200,
|
|
9956
|
+
contentType: JSON_CT2,
|
|
9957
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, result })
|
|
9958
|
+
};
|
|
9959
|
+
}
|
|
9960
|
+
function rpcError2(id, code2, message) {
|
|
9961
|
+
return {
|
|
9962
|
+
status: 200,
|
|
9963
|
+
contentType: JSON_CT2,
|
|
9964
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, error: { code: code2, message } })
|
|
9965
|
+
};
|
|
9966
|
+
}
|
|
9967
|
+
function scrubValue(value, secrets) {
|
|
9968
|
+
if (typeof value === "string") return scrubString(value, secrets);
|
|
9969
|
+
if (Array.isArray(value)) return value.map((v) => scrubValue(v, secrets));
|
|
9970
|
+
if (value && typeof value === "object") {
|
|
9971
|
+
const out = {};
|
|
9972
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9973
|
+
out[k] = scrubValue(v, secrets);
|
|
9974
|
+
}
|
|
9975
|
+
return out;
|
|
9976
|
+
}
|
|
9977
|
+
return value;
|
|
9978
|
+
}
|
|
9979
|
+
function scrubString(s, secrets) {
|
|
9980
|
+
let result = s;
|
|
9981
|
+
for (const h of secrets) {
|
|
9982
|
+
if (result.includes(h)) {
|
|
9983
|
+
result = result.replaceAll(h, "***");
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
return result;
|
|
9987
|
+
}
|
|
9988
|
+
function translateCapabilities(info) {
|
|
9989
|
+
const caps = {};
|
|
9990
|
+
if (info.capabilities.tools) caps.tools = { listChanged: false };
|
|
9991
|
+
if (info.capabilities.resources) caps.resources = { listChanged: false };
|
|
9992
|
+
if (info.capabilities.prompts) caps.prompts = { listChanged: false };
|
|
9993
|
+
return caps;
|
|
9994
|
+
}
|
|
9995
|
+
function buildServerResult(info, protocolVersion) {
|
|
9996
|
+
return {
|
|
9997
|
+
protocolVersion,
|
|
9998
|
+
capabilities: translateCapabilities(info),
|
|
9999
|
+
serverInfo: {
|
|
10000
|
+
name: info.name ?? "mcpcat-proxy",
|
|
10001
|
+
version: info.version ?? "0.0.0",
|
|
10002
|
+
...info.title ? { title: info.title } : {}
|
|
10003
|
+
},
|
|
10004
|
+
...info.instructions ? { instructions: info.instructions } : {}
|
|
10005
|
+
};
|
|
10006
|
+
}
|
|
10007
|
+
function createProxyHandler(options) {
|
|
10008
|
+
let conn = null;
|
|
10009
|
+
let cachedInfo = null;
|
|
10010
|
+
let connecting = null;
|
|
10011
|
+
const secrets = options.hiddenHeaders;
|
|
10012
|
+
async function doConnect() {
|
|
10013
|
+
const c = await connect(options.platform, options.connectOpts);
|
|
10014
|
+
cachedInfo = await c.info();
|
|
10015
|
+
return c;
|
|
10016
|
+
}
|
|
10017
|
+
async function getConnection() {
|
|
10018
|
+
if (conn) return conn;
|
|
10019
|
+
if (connecting !== null) return connecting;
|
|
10020
|
+
connecting = doConnect();
|
|
10021
|
+
try {
|
|
10022
|
+
conn = await connecting;
|
|
10023
|
+
return conn;
|
|
10024
|
+
} finally {
|
|
10025
|
+
connecting = null;
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
async function reconnect() {
|
|
10029
|
+
if (conn) {
|
|
10030
|
+
try {
|
|
10031
|
+
await conn.close();
|
|
10032
|
+
} catch {
|
|
10033
|
+
}
|
|
10034
|
+
}
|
|
10035
|
+
conn = null;
|
|
10036
|
+
cachedInfo = null;
|
|
10037
|
+
return getConnection();
|
|
10038
|
+
}
|
|
10039
|
+
function scrubOk(id, result) {
|
|
10040
|
+
return rpcOk2(id, scrubValue(result, secrets));
|
|
10041
|
+
}
|
|
10042
|
+
function scrubErr(id, code2, message) {
|
|
10043
|
+
return rpcError2(id, code2, scrubString(message, secrets));
|
|
10044
|
+
}
|
|
10045
|
+
function handleInitialize(id, params) {
|
|
10046
|
+
const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
|
|
10047
|
+
const pv = PROTOCOL_VERSIONS2.find((v) => v === requested) ?? PROTOCOL_VERSIONS2[0];
|
|
10048
|
+
return scrubOk(id, buildServerResult(cachedInfo, pv));
|
|
10049
|
+
}
|
|
10050
|
+
function handleDiscover(id) {
|
|
10051
|
+
return scrubOk(id, {
|
|
10052
|
+
...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
|
|
10053
|
+
supportedVersions: PROTOCOL_VERSIONS2
|
|
10054
|
+
});
|
|
10055
|
+
}
|
|
10056
|
+
async function forwardWithRetry(method, doForward, id) {
|
|
10057
|
+
try {
|
|
10058
|
+
return scrubOk(id, await doForward());
|
|
10059
|
+
} catch (err) {
|
|
10060
|
+
if (!IDEMPOTENT_METHODS.has(method)) {
|
|
10061
|
+
return scrubErr(id, -32e3, err.message);
|
|
10062
|
+
}
|
|
10063
|
+
try {
|
|
10064
|
+
await reconnect();
|
|
10065
|
+
return scrubOk(id, await doForward());
|
|
10066
|
+
} catch (retryErr) {
|
|
10067
|
+
return scrubErr(id, -32e3, retryErr.message);
|
|
10068
|
+
}
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
async function handleCall(id, params, c) {
|
|
10072
|
+
const name = params && typeof params === "object" ? readString2(params, "name") : void 0;
|
|
10073
|
+
if (!name) return scrubErr(id, -32602, "tools/call requires a name");
|
|
10074
|
+
const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
10075
|
+
try {
|
|
10076
|
+
return scrubOk(id, await c.callTool(name, args2));
|
|
10077
|
+
} catch (err) {
|
|
10078
|
+
return scrubErr(id, -32e3, err.message);
|
|
10079
|
+
}
|
|
10080
|
+
}
|
|
10081
|
+
async function dispatch(method, id, params) {
|
|
10082
|
+
try {
|
|
10083
|
+
await getConnection();
|
|
10084
|
+
} catch (err) {
|
|
10085
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
10086
|
+
return scrubErr(id, -32e3, `upstream connection failed: ${err.message}`);
|
|
10087
|
+
}
|
|
10088
|
+
if (method === "initialize") return handleInitialize(id, params);
|
|
10089
|
+
if (method === "server/discover") return handleDiscover(id);
|
|
10090
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
10091
|
+
if (method === "tools/list") {
|
|
10092
|
+
return forwardWithRetry(
|
|
10093
|
+
method,
|
|
10094
|
+
async () => {
|
|
10095
|
+
const tools = await (await getConnection()).listTools();
|
|
10096
|
+
return { tools };
|
|
10097
|
+
},
|
|
10098
|
+
id
|
|
10099
|
+
);
|
|
10100
|
+
}
|
|
10101
|
+
if (method === "tools/call") return handleCall(id, params, conn);
|
|
10102
|
+
return scrubErr(id, -32601, `Method not found: ${method}`);
|
|
10103
|
+
}
|
|
10104
|
+
async function handle(raw) {
|
|
10105
|
+
let parsed;
|
|
10106
|
+
try {
|
|
10107
|
+
parsed = JSON.parse(raw);
|
|
10108
|
+
} catch {
|
|
10109
|
+
return rpcError2(null, -32700, "Parse error");
|
|
10110
|
+
}
|
|
10111
|
+
if (Array.isArray(parsed)) return rpcError2(null, -32600, "Batch requests are not supported");
|
|
10112
|
+
if (!parsed || typeof parsed !== "object") return rpcError2(null, -32600, "Invalid request");
|
|
10113
|
+
const method = readString2(parsed, "method");
|
|
10114
|
+
if (!method) return rpcError2(readId2(parsed), -32600, "Invalid request");
|
|
10115
|
+
if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
10116
|
+
const id = readId2(parsed);
|
|
10117
|
+
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
10118
|
+
return dispatch(method, id, params);
|
|
10119
|
+
}
|
|
10120
|
+
async function shutdown() {
|
|
10121
|
+
if (conn) {
|
|
10122
|
+
try {
|
|
10123
|
+
await conn.close();
|
|
10124
|
+
} catch {
|
|
10125
|
+
}
|
|
10126
|
+
conn = null;
|
|
10127
|
+
}
|
|
10128
|
+
}
|
|
10129
|
+
return { handle, shutdown };
|
|
10130
|
+
}
|
|
10131
|
+
|
|
10132
|
+
// src/serve/runtime.ts
|
|
10133
|
+
var CALL_TIMEOUT_MS = 12e4;
|
|
10134
|
+
var MAX_OUTPUT_DEFAULT = 1048576;
|
|
10135
|
+
var ALLOWED_ENV_KEYS = /* @__PURE__ */ new Set(["PATH", "HOME", "LANG"]);
|
|
10136
|
+
function parseHttpFlag(args2, i) {
|
|
10137
|
+
const next = args2[i + 1];
|
|
10138
|
+
if (!next || next.startsWith("-")) return { host: "127.0.0.1", port: 0, next: i };
|
|
10139
|
+
const colonIdx = next.lastIndexOf(":");
|
|
10140
|
+
if (colonIdx > 0 && colonIdx < next.length - 1) {
|
|
10141
|
+
return { host: next.slice(0, colonIdx), port: Number(next.slice(colonIdx + 1)), next: i + 1 };
|
|
10142
|
+
}
|
|
10143
|
+
return { host: "127.0.0.1", port: Number(next), next: i + 1 };
|
|
10144
|
+
}
|
|
10145
|
+
function parseCommandTarget(arg) {
|
|
10146
|
+
const eqIdx = arg.indexOf("=");
|
|
10147
|
+
if (eqIdx > 0 && !arg.slice(0, eqIdx).includes("/")) {
|
|
10148
|
+
return { name: arg.slice(0, eqIdx), path: arg.slice(eqIdx + 1) };
|
|
10149
|
+
}
|
|
10150
|
+
return { path: arg };
|
|
10151
|
+
}
|
|
10152
|
+
function parseSingleFlag(args2, i, opts) {
|
|
10153
|
+
const arg = args2[i];
|
|
10154
|
+
if (arg === "--stdio") {
|
|
9565
10155
|
opts.transport = "stdio";
|
|
9566
10156
|
} else if (arg === "--http") {
|
|
9567
10157
|
opts.transport = "http";
|
|
@@ -9569,26 +10159,57 @@ function parseSingleFlag(args2, i, opts) {
|
|
|
9569
10159
|
opts.host = parsed.host;
|
|
9570
10160
|
opts.port = parsed.port;
|
|
9571
10161
|
return parsed.next;
|
|
9572
|
-
} else if (arg === "--bearer" && args2[i + 1]) {
|
|
9573
|
-
opts.bearer = args2[++i];
|
|
9574
|
-
} else if (arg === "--allow-origin" && args2[i + 1]) {
|
|
9575
|
-
opts.allowOrigins.push(args2[++i]);
|
|
9576
|
-
} else if (arg === "--env" && args2[i + 1]) {
|
|
9577
|
-
opts.envAllow.push(args2[++i]);
|
|
9578
|
-
} else if (arg === "--tool" && args2[i + 1]) {
|
|
9579
|
-
opts.toolTemplates.push(args2[++i]);
|
|
9580
|
-
} else if (arg === "--call-timeout" && args2[i + 1]) {
|
|
9581
|
-
opts.callTimeout = Number(args2[++i]) * 1e3;
|
|
9582
|
-
} else if (arg === "--concurrency" && args2[i + 1]) {
|
|
9583
|
-
opts.concurrency = Number(args2[++i]);
|
|
9584
|
-
} else if (arg === "--max-output" && args2[i + 1]) {
|
|
9585
|
-
opts.maxOutput = Number(args2[++i]);
|
|
9586
|
-
} else if (arg === "--cwd" && args2[i + 1]) {
|
|
9587
|
-
opts.cwd = args2[++i];
|
|
9588
10162
|
} else if (arg === "--json") {
|
|
9589
10163
|
opts.json = true;
|
|
9590
10164
|
} else if (!arg.startsWith("-")) {
|
|
9591
10165
|
opts.commands.push(parseCommandTarget(arg));
|
|
10166
|
+
} else {
|
|
10167
|
+
return parseValueFlag(args2, i, opts);
|
|
10168
|
+
}
|
|
10169
|
+
return i;
|
|
10170
|
+
}
|
|
10171
|
+
function parseValueFlag(args2, i, opts) {
|
|
10172
|
+
const arg = args2[i];
|
|
10173
|
+
if (!args2[i + 1]) return i;
|
|
10174
|
+
switch (arg) {
|
|
10175
|
+
case "--proxy":
|
|
10176
|
+
opts.proxy = args2[++i];
|
|
10177
|
+
break;
|
|
10178
|
+
case "--bearer":
|
|
10179
|
+
opts.bearer = args2[++i];
|
|
10180
|
+
break;
|
|
10181
|
+
case "--allow-origin":
|
|
10182
|
+
opts.allowOrigins.push(args2[++i]);
|
|
10183
|
+
break;
|
|
10184
|
+
case "--env":
|
|
10185
|
+
opts.envAllow.push(args2[++i]);
|
|
10186
|
+
break;
|
|
10187
|
+
case "--tool":
|
|
10188
|
+
opts.toolTemplates.push(args2[++i]);
|
|
10189
|
+
break;
|
|
10190
|
+
case "--call-timeout":
|
|
10191
|
+
opts.callTimeout = Number(args2[++i]) * 1e3;
|
|
10192
|
+
break;
|
|
10193
|
+
case "--concurrency":
|
|
10194
|
+
opts.concurrency = Number(args2[++i]);
|
|
10195
|
+
break;
|
|
10196
|
+
case "--max-output":
|
|
10197
|
+
opts.maxOutput = Number(args2[++i]);
|
|
10198
|
+
break;
|
|
10199
|
+
case "--cwd":
|
|
10200
|
+
opts.cwd = args2[++i];
|
|
10201
|
+
break;
|
|
10202
|
+
case "-H":
|
|
10203
|
+
case "--header": {
|
|
10204
|
+
const hdr = args2[++i];
|
|
10205
|
+
const colonIdx = hdr.indexOf(":");
|
|
10206
|
+
if (colonIdx > 0) {
|
|
10207
|
+
opts.proxyHeaders.push([hdr.slice(0, colonIdx).trim(), hdr.slice(colonIdx + 1).trim()]);
|
|
10208
|
+
}
|
|
10209
|
+
break;
|
|
10210
|
+
}
|
|
10211
|
+
default:
|
|
10212
|
+
break;
|
|
9592
10213
|
}
|
|
9593
10214
|
return i;
|
|
9594
10215
|
}
|
|
@@ -9606,7 +10227,9 @@ function parseServeFlags(args2) {
|
|
|
9606
10227
|
maxOutput: MAX_OUTPUT_DEFAULT,
|
|
9607
10228
|
cwd: void 0,
|
|
9608
10229
|
json: false,
|
|
9609
|
-
commands: []
|
|
10230
|
+
commands: [],
|
|
10231
|
+
proxy: void 0,
|
|
10232
|
+
proxyHeaders: []
|
|
9610
10233
|
};
|
|
9611
10234
|
for (let i = 0; i < args2.length; i++) {
|
|
9612
10235
|
i = parseSingleFlag(args2, i, opts);
|
|
@@ -9933,6 +10556,9 @@ async function runServe(platform2, options, version) {
|
|
|
9933
10556
|
}
|
|
9934
10557
|
resolvedBearer = expanded;
|
|
9935
10558
|
}
|
|
10559
|
+
if (options.proxy) {
|
|
10560
|
+
return runProxyServe(platform2, options, version, resolvedBearer);
|
|
10561
|
+
}
|
|
9936
10562
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
9937
10563
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
9938
10564
|
const clis = [];
|
|
@@ -9971,6 +10597,240 @@ async function runServe(platform2, options, version) {
|
|
|
9971
10597
|
resolvedBearer
|
|
9972
10598
|
);
|
|
9973
10599
|
}
|
|
10600
|
+
function resolveProxyStdio(target, version) {
|
|
10601
|
+
const rest = target.slice(6);
|
|
10602
|
+
const parts = rest.split(/\s+/).filter(Boolean);
|
|
10603
|
+
return { command: parts[0], args: parts.slice(1), version };
|
|
10604
|
+
}
|
|
10605
|
+
function resolveProxyUrl(target, headers, version) {
|
|
10606
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
10607
|
+
return { url, headers, version };
|
|
10608
|
+
}
|
|
10609
|
+
async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
|
|
10610
|
+
const config = await MergedConfigSource.load(platform2);
|
|
10611
|
+
const entry = await config.get(target);
|
|
10612
|
+
if (!entry) return `unknown target '${target}'`;
|
|
10613
|
+
if ("url" in entry && entry.url) {
|
|
10614
|
+
const merged = { ...entry.headers, ...connectHeaders };
|
|
10615
|
+
if (entry.headers) {
|
|
10616
|
+
for (const v of Object.values(entry.headers)) hiddenValues.add(v);
|
|
10617
|
+
}
|
|
10618
|
+
return { url: entry.url, headers: merged, version };
|
|
10619
|
+
}
|
|
10620
|
+
if ("command" in entry && entry.command) {
|
|
10621
|
+
return {
|
|
10622
|
+
command: entry.command,
|
|
10623
|
+
args: entry.args,
|
|
10624
|
+
env: entry.env,
|
|
10625
|
+
cwd: entry.cwd,
|
|
10626
|
+
version
|
|
10627
|
+
};
|
|
10628
|
+
}
|
|
10629
|
+
return `target '${target}' has no url or command`;
|
|
10630
|
+
}
|
|
10631
|
+
async function buildProxyConnectOpts(platform2, options, version) {
|
|
10632
|
+
const target = options.proxy;
|
|
10633
|
+
const connectHeaders = {};
|
|
10634
|
+
const hiddenValues = /* @__PURE__ */ new Set();
|
|
10635
|
+
for (const [k, v] of options.proxyHeaders) {
|
|
10636
|
+
const expanded = expandPlaceholdersStrict(v, platform2.env);
|
|
10637
|
+
if (expanded === void 0) return { error: `unset variable in header '${k}'` };
|
|
10638
|
+
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
10639
|
+
connectHeaders[key] = expanded;
|
|
10640
|
+
hiddenValues.add(expanded);
|
|
10641
|
+
}
|
|
10642
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
10643
|
+
let connectOpts;
|
|
10644
|
+
if (options.proxyCommand && options.proxyCommand.length > 0) {
|
|
10645
|
+
connectOpts = {
|
|
10646
|
+
command: options.proxyCommand[0],
|
|
10647
|
+
args: options.proxyCommand.slice(1),
|
|
10648
|
+
version
|
|
10649
|
+
};
|
|
10650
|
+
} else if (target.startsWith("stdio:")) {
|
|
10651
|
+
connectOpts = resolveProxyStdio(target, version);
|
|
10652
|
+
} else if (isUrl) {
|
|
10653
|
+
connectOpts = resolveProxyUrl(target, connectHeaders, version);
|
|
10654
|
+
} else {
|
|
10655
|
+
const result = await resolveProxyConfig(
|
|
10656
|
+
platform2,
|
|
10657
|
+
target,
|
|
10658
|
+
connectHeaders,
|
|
10659
|
+
hiddenValues,
|
|
10660
|
+
version
|
|
10661
|
+
);
|
|
10662
|
+
if (typeof result === "string") return { error: result };
|
|
10663
|
+
connectOpts = result;
|
|
10664
|
+
}
|
|
10665
|
+
if (connectOpts.headers) {
|
|
10666
|
+
for (const v of Object.values(connectOpts.headers)) {
|
|
10667
|
+
if (v) hiddenValues.add(v);
|
|
10668
|
+
}
|
|
10669
|
+
}
|
|
10670
|
+
return { connectOpts, hiddenValues };
|
|
10671
|
+
}
|
|
10672
|
+
async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
10673
|
+
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
10674
|
+
if ("error" in built) {
|
|
10675
|
+
await platform2.write(2, `mcpcat serve --proxy: ${built.error}
|
|
10676
|
+
`);
|
|
10677
|
+
return 2;
|
|
10678
|
+
}
|
|
10679
|
+
const { connectOpts, hiddenValues } = built;
|
|
10680
|
+
if (resolvedBearer) hiddenValues.add(resolvedBearer);
|
|
10681
|
+
const proxy = createProxyHandler({
|
|
10682
|
+
target: options.proxy,
|
|
10683
|
+
connectOpts,
|
|
10684
|
+
platform: platform2,
|
|
10685
|
+
hiddenHeaders: hiddenValues
|
|
10686
|
+
});
|
|
10687
|
+
if (options.transport === "stdio") {
|
|
10688
|
+
return runProxyStdio(platform2, proxy);
|
|
10689
|
+
}
|
|
10690
|
+
return runProxyHttp(platform2, options, proxy, resolvedBearer);
|
|
10691
|
+
}
|
|
10692
|
+
async function runProxyStdio(platform2, proxy) {
|
|
10693
|
+
const reader = platform2.stdin.getReader();
|
|
10694
|
+
let buffer = "";
|
|
10695
|
+
const dec = new TextDecoder();
|
|
10696
|
+
const enc = new TextEncoder();
|
|
10697
|
+
try {
|
|
10698
|
+
for (; ; ) {
|
|
10699
|
+
const { done, value } = await reader.read();
|
|
10700
|
+
if (done) break;
|
|
10701
|
+
buffer += dec.decode(value, { stream: true });
|
|
10702
|
+
let nlIdx;
|
|
10703
|
+
while ((nlIdx = buffer.indexOf("\n")) >= 0) {
|
|
10704
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
10705
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
10706
|
+
if (!line) continue;
|
|
10707
|
+
const outcome = await proxy.handle(line);
|
|
10708
|
+
if (outcome.status === 202) continue;
|
|
10709
|
+
const writer = platform2.stdout.getWriter();
|
|
10710
|
+
await writer.write(enc.encode(`${outcome.body}
|
|
10711
|
+
`));
|
|
10712
|
+
writer.releaseLock();
|
|
10713
|
+
}
|
|
10714
|
+
}
|
|
10715
|
+
} finally {
|
|
10716
|
+
reader.releaseLock();
|
|
10717
|
+
await proxy.shutdown();
|
|
10718
|
+
}
|
|
10719
|
+
return 0;
|
|
10720
|
+
}
|
|
10721
|
+
async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
10722
|
+
if (!platform2.listen) {
|
|
10723
|
+
await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
|
|
10724
|
+
return 3;
|
|
10725
|
+
}
|
|
10726
|
+
const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
|
|
10727
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
10728
|
+
await platform2.write(
|
|
10729
|
+
2,
|
|
10730
|
+
"mcpcat serve --proxy: --bearer is required when binding off loopback\n"
|
|
10731
|
+
);
|
|
10732
|
+
return 2;
|
|
10733
|
+
}
|
|
10734
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
10735
|
+
const listener = await platform2.listen({
|
|
10736
|
+
host: options.host,
|
|
10737
|
+
port: options.port
|
|
10738
|
+
});
|
|
10739
|
+
const actualPort = listener.port;
|
|
10740
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
10741
|
+
if (options.json) {
|
|
10742
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
10743
|
+
`);
|
|
10744
|
+
}
|
|
10745
|
+
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
10746
|
+
`);
|
|
10747
|
+
if (options.onListening) {
|
|
10748
|
+
await options.onListening(actualPort);
|
|
10749
|
+
}
|
|
10750
|
+
let shutdownRequested = false;
|
|
10751
|
+
const shutdown = () => {
|
|
10752
|
+
if (shutdownRequested) return;
|
|
10753
|
+
shutdownRequested = true;
|
|
10754
|
+
listener.close();
|
|
10755
|
+
void proxy.shutdown();
|
|
10756
|
+
};
|
|
10757
|
+
if (platform2.onSignal) {
|
|
10758
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
10759
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
10760
|
+
}
|
|
10761
|
+
const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
|
|
10762
|
+
try {
|
|
10763
|
+
for await (const connection of listener) {
|
|
10764
|
+
if (shutdownRequested) break;
|
|
10765
|
+
const conn = connection;
|
|
10766
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
10767
|
+
}).finally(() => {
|
|
10768
|
+
try {
|
|
10769
|
+
conn.close();
|
|
10770
|
+
} catch {
|
|
10771
|
+
}
|
|
10772
|
+
});
|
|
10773
|
+
}
|
|
10774
|
+
} catch {
|
|
10775
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
10776
|
+
}
|
|
10777
|
+
return 0;
|
|
10778
|
+
}
|
|
10779
|
+
async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer) {
|
|
10780
|
+
if (resolvedBearer) {
|
|
10781
|
+
const authHeader = request.headers.authorization;
|
|
10782
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
10783
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
10784
|
+
return {
|
|
10785
|
+
status: 401,
|
|
10786
|
+
reason: "Unauthorized",
|
|
10787
|
+
headers: { "content-type": "text/plain" },
|
|
10788
|
+
body: "Unauthorized"
|
|
10789
|
+
};
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
10793
|
+
if (!originCheck.ok) {
|
|
10794
|
+
return {
|
|
10795
|
+
status: 403,
|
|
10796
|
+
reason: "Forbidden",
|
|
10797
|
+
headers: { "content-type": "text/plain" },
|
|
10798
|
+
body: originCheck.reason ?? "Forbidden"
|
|
10799
|
+
};
|
|
10800
|
+
}
|
|
10801
|
+
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
10802
|
+
if (!hostCheck.ok) {
|
|
10803
|
+
return {
|
|
10804
|
+
status: 403,
|
|
10805
|
+
reason: "Forbidden",
|
|
10806
|
+
headers: { "content-type": "text/plain" },
|
|
10807
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
10808
|
+
};
|
|
10809
|
+
}
|
|
10810
|
+
if (request.target !== "/mcp") {
|
|
10811
|
+
return {
|
|
10812
|
+
status: 404,
|
|
10813
|
+
reason: "Not Found",
|
|
10814
|
+
headers: { "content-type": "text/plain" },
|
|
10815
|
+
body: "Not Found"
|
|
10816
|
+
};
|
|
10817
|
+
}
|
|
10818
|
+
if (request.method !== "POST") {
|
|
10819
|
+
return {
|
|
10820
|
+
status: 405,
|
|
10821
|
+
reason: "Method Not Allowed",
|
|
10822
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
10823
|
+
body: "Method Not Allowed"
|
|
10824
|
+
};
|
|
10825
|
+
}
|
|
10826
|
+
const outcome = await proxy.handle(request.body);
|
|
10827
|
+
return {
|
|
10828
|
+
status: outcome.status,
|
|
10829
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
10830
|
+
headers: { "content-type": outcome.contentType },
|
|
10831
|
+
body: outcome.body
|
|
10832
|
+
};
|
|
10833
|
+
}
|
|
9974
10834
|
async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
9975
10835
|
const reader = platform2.stdin.getReader();
|
|
9976
10836
|
let buffer = "";
|
|
@@ -10477,6 +11337,14 @@ function parseAddFlagAt(flagArgs, i, flags) {
|
|
|
10477
11337
|
flags.noProbe = true;
|
|
10478
11338
|
return { next: i + 1 };
|
|
10479
11339
|
}
|
|
11340
|
+
if (arg === "--bridge") {
|
|
11341
|
+
flags.bridge = true;
|
|
11342
|
+
return { next: i + 1 };
|
|
11343
|
+
}
|
|
11344
|
+
if (arg === "--bearer" && i + 1 < flagArgs.length) {
|
|
11345
|
+
flags.bearer = flagArgs[i + 1];
|
|
11346
|
+
return { next: i + 2 };
|
|
11347
|
+
}
|
|
10480
11348
|
if (arg === "--config" && i + 1 < flagArgs.length) {
|
|
10481
11349
|
flags.configFile = flagArgs[i + 1];
|
|
10482
11350
|
return { next: i + 2 };
|
|
@@ -10513,6 +11381,7 @@ function parseAddFlags(args2) {
|
|
|
10513
11381
|
const flags = {
|
|
10514
11382
|
local: false,
|
|
10515
11383
|
noProbe: false,
|
|
11384
|
+
bridge: false,
|
|
10516
11385
|
headers: [],
|
|
10517
11386
|
envPairs: [],
|
|
10518
11387
|
positional: [],
|
|
@@ -10875,6 +11744,139 @@ async function runImport(platform2, args2) {
|
|
|
10875
11744
|
}
|
|
10876
11745
|
return 0;
|
|
10877
11746
|
}
|
|
11747
|
+
async function runBridge(platform2, args2, version) {
|
|
11748
|
+
if (platform2.kind === "host") {
|
|
11749
|
+
await platform2.write(
|
|
11750
|
+
2,
|
|
11751
|
+
"mcpcat add --bridge: not available on the host.\nOn the host, add stdio entries directly with: mcpcat add <name> -- <cmd>\nTo expose a stdio server over HTTP: mcpcat serve --proxy <target> --http <port>\n"
|
|
11752
|
+
);
|
|
11753
|
+
return 2;
|
|
11754
|
+
}
|
|
11755
|
+
const flags = parseAddFlags(args2);
|
|
11756
|
+
if (typeof flags === "string") {
|
|
11757
|
+
await platform2.write(2, `mcpcat add --bridge: ${flags}
|
|
11758
|
+
`);
|
|
11759
|
+
return 2;
|
|
11760
|
+
}
|
|
11761
|
+
const name = flags.positional[0];
|
|
11762
|
+
if (!name) {
|
|
11763
|
+
await platform2.write(2, "mcpcat add --bridge: requires a server name\n");
|
|
11764
|
+
return 2;
|
|
11765
|
+
}
|
|
11766
|
+
if (flags.cmdArgs.length === 0) {
|
|
11767
|
+
await platform2.write(2, "mcpcat add --bridge: requires -- <command> [args...]\n");
|
|
11768
|
+
return 2;
|
|
11769
|
+
}
|
|
11770
|
+
if (flags.bearer) {
|
|
11771
|
+
await platform2.write(
|
|
11772
|
+
2,
|
|
11773
|
+
"mcpcat add --bridge: --bearer is not needed on the kernel loopback.\nThe kernel loopback is only reachable from inside the kernel and the agent.\n"
|
|
11774
|
+
);
|
|
11775
|
+
return 2;
|
|
11776
|
+
}
|
|
11777
|
+
const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
|
|
11778
|
+
serveOptions.proxyCommand = flags.cmdArgs;
|
|
11779
|
+
const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
|
|
11780
|
+
serveOptions.onListening = async (actualPort) => {
|
|
11781
|
+
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
11782
|
+
const httpConfig = { url: bridgeUrl };
|
|
11783
|
+
if (flags.description) httpConfig.description = flags.description;
|
|
11784
|
+
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
11785
|
+
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
11786
|
+
await platform2.write(
|
|
11787
|
+
1,
|
|
11788
|
+
`${replaced ? "replaced" : "added"} ${name} in ${path}
|
|
11789
|
+
bridge does not survive a reload; runs in the foreground (start with &)
|
|
11790
|
+
`
|
|
11791
|
+
);
|
|
11792
|
+
};
|
|
11793
|
+
return runServe(platform2, serveOptions, version);
|
|
11794
|
+
}
|
|
11795
|
+
|
|
11796
|
+
// src/cli/exit-codes.ts
|
|
11797
|
+
var EXIT_OK = 0;
|
|
11798
|
+
var EXIT_TOOL_ERROR = 1;
|
|
11799
|
+
var EXIT_USAGE = 2;
|
|
11800
|
+
var EXIT_CONNECTION = 3;
|
|
11801
|
+
var EXIT_AUTH = 4;
|
|
11802
|
+
var EXIT_RPC_ERROR = 5;
|
|
11803
|
+
var EXIT_INPUT_REQUIRED = 6;
|
|
11804
|
+
var EXIT_TIMEOUT = 124;
|
|
11805
|
+
var EXIT_INTERRUPTED = 130;
|
|
11806
|
+
var McpcatError = class extends Error {
|
|
11807
|
+
exitCode;
|
|
11808
|
+
constructor(exitCode, message) {
|
|
11809
|
+
super(message);
|
|
11810
|
+
this.name = "McpcatError";
|
|
11811
|
+
this.exitCode = exitCode;
|
|
11812
|
+
}
|
|
11813
|
+
};
|
|
11814
|
+
|
|
11815
|
+
// src/cli/verbose.ts
|
|
11816
|
+
var SENSITIVE_HEADER_RE = /^(authorization|cookie|proxy-authorization)$/i;
|
|
11817
|
+
var SENSITIVE_NAME_RE = /token|key|secret|password|session/i;
|
|
11818
|
+
function isSensitiveHeader(name) {
|
|
11819
|
+
return SENSITIVE_HEADER_RE.test(name) || SENSITIVE_NAME_RE.test(name);
|
|
11820
|
+
}
|
|
11821
|
+
function redact(text, secrets) {
|
|
11822
|
+
let result = text;
|
|
11823
|
+
for (const secret of secrets) {
|
|
11824
|
+
if (secret.length > 0) {
|
|
11825
|
+
while (result.includes(secret)) {
|
|
11826
|
+
result = result.replace(secret, "[REDACTED]");
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11830
|
+
return result;
|
|
11831
|
+
}
|
|
11832
|
+
function createVerboseLogger(platform2, enabled) {
|
|
11833
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
11834
|
+
function write(line) {
|
|
11835
|
+
const safe = redact(line, secrets);
|
|
11836
|
+
platform2.write(2, `${safe}
|
|
11837
|
+
`).catch(() => {
|
|
11838
|
+
});
|
|
11839
|
+
}
|
|
11840
|
+
return {
|
|
11841
|
+
enabled,
|
|
11842
|
+
secrets,
|
|
11843
|
+
request(method, body) {
|
|
11844
|
+
if (!enabled) return;
|
|
11845
|
+
write(`> ${method}`);
|
|
11846
|
+
for (const line of body.split("\n")) {
|
|
11847
|
+
write(`> ${line}`);
|
|
11848
|
+
}
|
|
11849
|
+
},
|
|
11850
|
+
response(body) {
|
|
11851
|
+
if (!enabled) return;
|
|
11852
|
+
for (const line of body.split("\n")) {
|
|
11853
|
+
write(`< ${line}`);
|
|
11854
|
+
}
|
|
11855
|
+
},
|
|
11856
|
+
http(direction, line) {
|
|
11857
|
+
if (!enabled) return;
|
|
11858
|
+
write(`${direction} ${line}`);
|
|
11859
|
+
},
|
|
11860
|
+
server(line) {
|
|
11861
|
+
if (!enabled) return;
|
|
11862
|
+
write(`[server] ${line}`);
|
|
11863
|
+
},
|
|
11864
|
+
headerOut(name, value) {
|
|
11865
|
+
if (!enabled) return;
|
|
11866
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
11867
|
+
write(`> ${name}: ${display}`);
|
|
11868
|
+
},
|
|
11869
|
+
headerIn(name, value) {
|
|
11870
|
+
if (!enabled) return;
|
|
11871
|
+
const display = isSensitiveHeader(name) ? "[REDACTED]" : redact(value, secrets);
|
|
11872
|
+
write(`< ${name}: ${display}`);
|
|
11873
|
+
},
|
|
11874
|
+
progress(message) {
|
|
11875
|
+
if (!enabled) return;
|
|
11876
|
+
write(`* ${message}`);
|
|
11877
|
+
}
|
|
11878
|
+
};
|
|
11879
|
+
}
|
|
10878
11880
|
|
|
10879
11881
|
// src/cli/main.ts
|
|
10880
11882
|
var VERBS = [
|
|
@@ -10939,6 +11941,9 @@ Client verbs:
|
|
|
10939
11941
|
|
|
10940
11942
|
Configuration verbs:
|
|
10941
11943
|
add <name> <url> Add a server to mcp.json
|
|
11944
|
+
add <name> --bridge --
|
|
11945
|
+
Bridge a stdio server to HTTP (kernel only,
|
|
11946
|
+
runs in foreground, does not survive reload)
|
|
10942
11947
|
rm <name> Remove a server
|
|
10943
11948
|
list List configured servers
|
|
10944
11949
|
enable|disable <name> Toggle a server
|
|
@@ -10948,7 +11953,7 @@ Configuration verbs:
|
|
|
10948
11953
|
logout <target> Remove tokens (not implemented yet)
|
|
10949
11954
|
|
|
10950
11955
|
Server and code verbs:
|
|
10951
|
-
serve [--stdio|--http] \u2026 Expose commands as MCP server
|
|
11956
|
+
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
10952
11957
|
code [-e src|file.js] Run a codemode script
|
|
10953
11958
|
grep <pattern> [target\u2026] Search tools by name or description
|
|
10954
11959
|
discover <url> Find MCP servers at a URL
|
|
@@ -10990,6 +11995,8 @@ function parseFlags(args2) {
|
|
|
10990
11995
|
const serverTargets = [];
|
|
10991
11996
|
let allowShell = false;
|
|
10992
11997
|
let timeoutMs;
|
|
11998
|
+
let verbose = false;
|
|
11999
|
+
let silent = false;
|
|
10993
12000
|
const positional = [];
|
|
10994
12001
|
let trailingCommand;
|
|
10995
12002
|
for (let i = 0; i < args2.length; i++) {
|
|
@@ -11026,6 +12033,10 @@ function parseFlags(args2) {
|
|
|
11026
12033
|
serverTargets.push(args2[++i]);
|
|
11027
12034
|
} else if (arg === "--allow-shell") {
|
|
11028
12035
|
allowShell = true;
|
|
12036
|
+
} else if (arg === "-v" || arg === "--verbose") {
|
|
12037
|
+
verbose = true;
|
|
12038
|
+
} else if (arg === "--silent") {
|
|
12039
|
+
silent = true;
|
|
11029
12040
|
} else if ((arg === "-m" || arg === "--timeout") && args2[i + 1]) {
|
|
11030
12041
|
timeoutMs = Number(args2[++i]) * 1e3;
|
|
11031
12042
|
} else if (!arg.startsWith("-")) {
|
|
@@ -11046,6 +12057,8 @@ function parseFlags(args2) {
|
|
|
11046
12057
|
serverTargets,
|
|
11047
12058
|
allowShell,
|
|
11048
12059
|
timeoutMs,
|
|
12060
|
+
verbose,
|
|
12061
|
+
silent,
|
|
11049
12062
|
positional,
|
|
11050
12063
|
trailingCommand
|
|
11051
12064
|
};
|
|
@@ -11094,7 +12107,9 @@ function parseArgs(args2) {
|
|
|
11094
12107
|
inlineSource: flags.inlineSource,
|
|
11095
12108
|
serverTargets: flags.serverTargets,
|
|
11096
12109
|
allowShell: flags.allowShell,
|
|
11097
|
-
timeoutMs: flags.timeoutMs
|
|
12110
|
+
timeoutMs: flags.timeoutMs,
|
|
12111
|
+
verbose: flags.verbose,
|
|
12112
|
+
silent: flags.silent
|
|
11098
12113
|
};
|
|
11099
12114
|
}
|
|
11100
12115
|
function formatToolLine(tool) {
|
|
@@ -11118,26 +12133,6 @@ function formatToolSchema(tool) {
|
|
|
11118
12133
|
}
|
|
11119
12134
|
return lines.join("\n");
|
|
11120
12135
|
}
|
|
11121
|
-
function renderCallResult(result) {
|
|
11122
|
-
const parts = [];
|
|
11123
|
-
if (result.content) {
|
|
11124
|
-
for (const block of result.content) {
|
|
11125
|
-
if (block.type === "text") parts.push(block.text);
|
|
11126
|
-
else if (block.type === "image")
|
|
11127
|
-
parts.push(`[image: ${block.mimeType}, ${Math.ceil(block.data.length * 3 / 4)} bytes]`);
|
|
11128
|
-
else if (block.type === "resource_link") parts.push(`[resource: ${block.uri}]`);
|
|
11129
|
-
else if (block.type === "resource") {
|
|
11130
|
-
parts.push(
|
|
11131
|
-
"text" in block.resource ? block.resource.text : `[blob: ${block.resource.uri}]`
|
|
11132
|
-
);
|
|
11133
|
-
}
|
|
11134
|
-
}
|
|
11135
|
-
}
|
|
11136
|
-
if (parts.length === 0 && result.structuredContent !== void 0) {
|
|
11137
|
-
parts.push(JSON.stringify(result.structuredContent, null, 2));
|
|
11138
|
-
}
|
|
11139
|
-
return parts.join("\n");
|
|
11140
|
-
}
|
|
11141
12136
|
var NETWORK_HINT = "connection failed (use slicc-node, slicc-swift, or the browser extension for servers without CORS)";
|
|
11142
12137
|
function transportHint(err) {
|
|
11143
12138
|
if (err instanceof TypeError) {
|
|
@@ -11180,8 +12175,12 @@ async function readDataArg(platform2, dataArg) {
|
|
|
11180
12175
|
}
|
|
11181
12176
|
return JSON.parse(raw);
|
|
11182
12177
|
}
|
|
11183
|
-
async function runInfo(platform2, conn) {
|
|
12178
|
+
async function runInfo(platform2, conn, json) {
|
|
11184
12179
|
const info = await conn.info();
|
|
12180
|
+
if (json) {
|
|
12181
|
+
await renderInfoJson(platform2, info);
|
|
12182
|
+
return EXIT_OK;
|
|
12183
|
+
}
|
|
11185
12184
|
const lines = [];
|
|
11186
12185
|
if (info.name) lines.push(`Server: ${info.name}${info.version ? ` ${info.version}` : ""}`);
|
|
11187
12186
|
if (info.title) lines.push(`Title: ${info.title}`);
|
|
@@ -11198,48 +12197,66 @@ async function runInfo(platform2, conn) {
|
|
|
11198
12197
|
}
|
|
11199
12198
|
await platform2.write(1, `${lines.join("\n")}
|
|
11200
12199
|
`);
|
|
11201
|
-
return
|
|
12200
|
+
return EXIT_OK;
|
|
11202
12201
|
}
|
|
11203
|
-
async function runTools(platform2, conn) {
|
|
12202
|
+
async function runTools(platform2, conn, json) {
|
|
11204
12203
|
const tools = await conn.listTools();
|
|
11205
|
-
if (
|
|
11206
|
-
await platform2
|
|
11207
|
-
|
|
11208
|
-
await platform2.write(1, `${tools.map(formatToolLine).join("\n")}
|
|
11209
|
-
`);
|
|
12204
|
+
if (json) {
|
|
12205
|
+
await renderToolsJson(platform2, tools);
|
|
12206
|
+
return EXIT_OK;
|
|
11210
12207
|
}
|
|
11211
|
-
|
|
12208
|
+
await platform2.write(
|
|
12209
|
+
1,
|
|
12210
|
+
tools.length === 0 ? "(no tools)\n" : `${tools.map(formatToolLine).join("\n")}
|
|
12211
|
+
`
|
|
12212
|
+
);
|
|
12213
|
+
return EXIT_OK;
|
|
11212
12214
|
}
|
|
11213
|
-
async function runCall(platform2, conn, toolName, dataArg) {
|
|
11214
|
-
let
|
|
12215
|
+
async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
|
|
12216
|
+
let base = {};
|
|
11215
12217
|
if (dataArg) {
|
|
11216
12218
|
try {
|
|
11217
|
-
|
|
12219
|
+
base = await readDataArg(platform2, dataArg);
|
|
11218
12220
|
} catch (err) {
|
|
11219
|
-
await
|
|
11220
|
-
|
|
11221
|
-
|
|
12221
|
+
await writeErr(
|
|
12222
|
+
platform2,
|
|
12223
|
+
opts.json ?? false,
|
|
12224
|
+
"usage",
|
|
12225
|
+
`invalid -d argument: ${err.message}`
|
|
12226
|
+
);
|
|
12227
|
+
return EXIT_USAGE;
|
|
11222
12228
|
}
|
|
11223
12229
|
}
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11229
|
-
|
|
12230
|
+
let fin = base;
|
|
12231
|
+
if (toolArgs.length > 0) {
|
|
12232
|
+
const tools = await conn.listTools();
|
|
12233
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
12234
|
+
const schema = tool?.inputSchema ?? { type: "object", properties: {} };
|
|
12235
|
+
const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
|
|
12236
|
+
if (!c.ok) {
|
|
12237
|
+
await writeErr(platform2, opts.json ?? false, "usage", c.error);
|
|
12238
|
+
return EXIT_USAGE;
|
|
12239
|
+
}
|
|
12240
|
+
fin = mergeArgs(base, c.value);
|
|
11230
12241
|
}
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
12242
|
+
return renderCallResult(
|
|
12243
|
+
platform2,
|
|
12244
|
+
await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
|
|
12245
|
+
{ json: opts.json }
|
|
12246
|
+
);
|
|
11234
12247
|
}
|
|
11235
|
-
async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
12248
|
+
async function runDescribe(platform2, conn, toolName, showSchema, json) {
|
|
11236
12249
|
const tools = await conn.listTools();
|
|
11237
12250
|
if (toolName) {
|
|
11238
12251
|
const tool = tools.find((t) => t.name === toolName);
|
|
11239
12252
|
if (!tool) {
|
|
11240
|
-
await platform2
|
|
12253
|
+
await writeErr(platform2, json, "usage", `tool '${toolName}' not found`);
|
|
12254
|
+
return EXIT_USAGE;
|
|
12255
|
+
}
|
|
12256
|
+
if (json) {
|
|
12257
|
+
await platform2.write(1, `${JSON.stringify(tool, null, 2)}
|
|
11241
12258
|
`);
|
|
11242
|
-
return
|
|
12259
|
+
return EXIT_OK;
|
|
11243
12260
|
}
|
|
11244
12261
|
if (showSchema && tool.inputSchema) {
|
|
11245
12262
|
await platform2.write(1, `${JSON.stringify(tool.inputSchema, null, 2)}
|
|
@@ -11249,28 +12266,36 @@ async function runDescribe(platform2, conn, toolName, showSchema) {
|
|
|
11249
12266
|
`);
|
|
11250
12267
|
}
|
|
11251
12268
|
} else {
|
|
12269
|
+
if (json) {
|
|
12270
|
+
await platform2.write(1, `${JSON.stringify({ tools }, null, 2)}
|
|
12271
|
+
`);
|
|
12272
|
+
return EXIT_OK;
|
|
12273
|
+
}
|
|
11252
12274
|
for (const tool of tools) {
|
|
11253
12275
|
await platform2.write(1, `${formatToolSchema(tool)}
|
|
11254
12276
|
|
|
11255
12277
|
`);
|
|
11256
12278
|
}
|
|
11257
12279
|
}
|
|
11258
|
-
return
|
|
12280
|
+
return EXIT_OK;
|
|
11259
12281
|
}
|
|
11260
|
-
async function runVerb(platform2, parsed, conn) {
|
|
12282
|
+
async function runVerb(platform2, parsed, conn, onProgress) {
|
|
11261
12283
|
switch (parsed.verb) {
|
|
11262
12284
|
case "info":
|
|
11263
|
-
return runInfo(platform2, conn);
|
|
12285
|
+
return runInfo(platform2, conn, parsed.jsonOutput);
|
|
11264
12286
|
case "tools":
|
|
11265
|
-
return runTools(platform2, conn);
|
|
12287
|
+
return runTools(platform2, conn, parsed.jsonOutput);
|
|
11266
12288
|
case "call":
|
|
11267
|
-
return runCall(platform2, conn, parsed.toolName, parsed.dataArg
|
|
12289
|
+
return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
|
|
12290
|
+
json: parsed.jsonOutput,
|
|
12291
|
+
onProgress
|
|
12292
|
+
});
|
|
11268
12293
|
case "describe":
|
|
11269
|
-
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema);
|
|
12294
|
+
return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
|
|
11270
12295
|
default:
|
|
11271
12296
|
await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
|
|
11272
12297
|
`);
|
|
11273
|
-
return
|
|
12298
|
+
return EXIT_USAGE;
|
|
11274
12299
|
}
|
|
11275
12300
|
}
|
|
11276
12301
|
async function probeServer(_name, config, platform2) {
|
|
@@ -11301,10 +12326,11 @@ async function probeServer(_name, config, platform2) {
|
|
|
11301
12326
|
return err.message;
|
|
11302
12327
|
}
|
|
11303
12328
|
}
|
|
11304
|
-
async function dispatchConfigVerb(platform2, verb, args2) {
|
|
12329
|
+
async function dispatchConfigVerb(platform2, verb, args2, version) {
|
|
11305
12330
|
const rest = args2.slice(1);
|
|
11306
12331
|
switch (verb) {
|
|
11307
12332
|
case "add":
|
|
12333
|
+
if (rest.includes("--bridge")) return runBridge(platform2, rest, version);
|
|
11308
12334
|
return runAdd(platform2, rest, probeServer);
|
|
11309
12335
|
case "rm":
|
|
11310
12336
|
return runRm(platform2, rest);
|
|
@@ -11347,7 +12373,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args2) {
|
|
|
11347
12373
|
async function dispatchServeVerb(platform2, args2, version) {
|
|
11348
12374
|
const serveArgs = args2.slice(args2.indexOf("serve") + 1);
|
|
11349
12375
|
const serveOptions = parseServeFlags(serveArgs);
|
|
11350
|
-
if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
12376
|
+
if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
11351
12377
|
await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
|
|
11352
12378
|
return 2;
|
|
11353
12379
|
}
|
|
@@ -11511,14 +12537,14 @@ async function run(platform2, version, args2) {
|
|
|
11511
12537
|
if (parsed.verb in CONFIG_VERBS) {
|
|
11512
12538
|
const disc = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
11513
12539
|
if (disc !== void 0) return disc;
|
|
11514
|
-
return dispatchConfigVerb(platform2, parsed.verb, args2);
|
|
12540
|
+
return dispatchConfigVerb(platform2, parsed.verb, args2, version);
|
|
11515
12541
|
}
|
|
11516
12542
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
11517
12543
|
if (discResult !== void 0) return discResult;
|
|
11518
12544
|
if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
|
|
11519
12545
|
await platform2.write(2, `mcpcat: '${parsed.verb}' is not implemented yet
|
|
11520
12546
|
`);
|
|
11521
|
-
return
|
|
12547
|
+
return EXIT_TOOL_ERROR;
|
|
11522
12548
|
}
|
|
11523
12549
|
if (parsed.verb === "serve") {
|
|
11524
12550
|
return dispatchServeVerb(platform2, args2, version);
|
|
@@ -11540,44 +12566,100 @@ async function run(platform2, version, args2) {
|
|
|
11540
12566
|
`);
|
|
11541
12567
|
return 2;
|
|
11542
12568
|
}
|
|
12569
|
+
return runClientVerb(platform2, version, parsed);
|
|
12570
|
+
}
|
|
12571
|
+
function expandAndCollect(headers, env) {
|
|
12572
|
+
const secrets = [];
|
|
12573
|
+
const result = {};
|
|
12574
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
12575
|
+
const e = expandPlaceholders(v, env);
|
|
12576
|
+
result[k] = e;
|
|
12577
|
+
if (e !== v) secrets.push(e);
|
|
12578
|
+
if (v.length > 0) secrets.push(v);
|
|
12579
|
+
}
|
|
12580
|
+
return { expanded: result, secrets };
|
|
12581
|
+
}
|
|
12582
|
+
function classifyError(err) {
|
|
12583
|
+
if (err instanceof InputRequiredError) {
|
|
12584
|
+
return { code: EXIT_INPUT_REQUIRED, kind: "input_required", msg: err.requestJson };
|
|
12585
|
+
}
|
|
12586
|
+
if (err instanceof McpTimeoutError2) {
|
|
12587
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg: err.message };
|
|
12588
|
+
}
|
|
12589
|
+
if (err instanceof McpAuthError) {
|
|
12590
|
+
return { code: EXIT_AUTH, kind: "auth", msg: err.message };
|
|
12591
|
+
}
|
|
12592
|
+
if (err instanceof McpcatError) {
|
|
12593
|
+
return { code: err.exitCode, kind: "error", msg: err.message };
|
|
12594
|
+
}
|
|
12595
|
+
const msg = err.message ?? String(err);
|
|
12596
|
+
const name = err.name ?? "";
|
|
12597
|
+
if (name === "McpTimeoutError" || msg.includes("timed out") || msg.includes("timeout") || msg.includes("Timeout")) {
|
|
12598
|
+
return { code: EXIT_TIMEOUT, kind: "timeout", msg };
|
|
12599
|
+
}
|
|
12600
|
+
return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
|
|
12601
|
+
}
|
|
12602
|
+
async function runClientVerb(platform2, version, parsed) {
|
|
12603
|
+
const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
|
|
12604
|
+
const verbose = createVerboseLogger(platform2, parsed.verbose);
|
|
12605
|
+
for (const s of secrets) verbose.secrets.add(s);
|
|
12606
|
+
const timeoutMs = parsed.timeoutMs;
|
|
12607
|
+
const sigintController = new AbortController();
|
|
12608
|
+
let interrupted = false;
|
|
12609
|
+
platform2.onSignal?.("SIGINT", () => {
|
|
12610
|
+
interrupted = true;
|
|
12611
|
+
sigintController.abort();
|
|
12612
|
+
});
|
|
12613
|
+
const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
|
|
12614
|
+
const onProgress = (progress, total, message) => {
|
|
12615
|
+
if (!showProgress) return;
|
|
12616
|
+
const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
|
|
12617
|
+
const msg = message ? ` ${message}` : "";
|
|
12618
|
+
platform2.write(2, `* progress:${pct}${msg}
|
|
12619
|
+
`).catch(() => {
|
|
12620
|
+
});
|
|
12621
|
+
};
|
|
11543
12622
|
const config = await MergedConfigSource.load(platform2);
|
|
12623
|
+
const target = parsed.target === "__trailing__" ? "" : parsed.target;
|
|
11544
12624
|
let resolved;
|
|
11545
12625
|
try {
|
|
11546
|
-
resolved = await resolveTarget(
|
|
11547
|
-
parsed.target === "__trailing__" ? "" : parsed.target,
|
|
11548
|
-
parsed.trailingCommand,
|
|
11549
|
-
config
|
|
11550
|
-
);
|
|
12626
|
+
resolved = await resolveTarget(target, parsed.trailingCommand, config);
|
|
11551
12627
|
} catch (err) {
|
|
11552
|
-
await platform2.
|
|
11553
|
-
|
|
11554
|
-
return 2;
|
|
12628
|
+
await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
|
|
12629
|
+
return EXIT_USAGE;
|
|
11555
12630
|
}
|
|
11556
12631
|
let conn;
|
|
11557
12632
|
try {
|
|
11558
12633
|
conn = await connect(platform2, {
|
|
11559
12634
|
...resolved,
|
|
11560
|
-
headers: { ...resolved.headers, ...
|
|
12635
|
+
headers: { ...resolved.headers, ...expanded },
|
|
11561
12636
|
protocolVersion: parsed.protocolVersion,
|
|
11562
|
-
version
|
|
12637
|
+
version,
|
|
12638
|
+
timeoutMs,
|
|
12639
|
+
signal: sigintController.signal,
|
|
12640
|
+
verbose
|
|
11563
12641
|
});
|
|
11564
12642
|
} catch (err) {
|
|
12643
|
+
if (err instanceof McpAuthError) {
|
|
12644
|
+
await writeErr(platform2, parsed.jsonOutput, "auth", err.message);
|
|
12645
|
+
return EXIT_AUTH;
|
|
12646
|
+
}
|
|
11565
12647
|
const msg = transportHint(err) ?? `connection failed: ${err.message}`;
|
|
11566
|
-
await platform2.
|
|
11567
|
-
|
|
11568
|
-
return 3;
|
|
12648
|
+
await writeErr(platform2, parsed.jsonOutput, "connection", msg);
|
|
12649
|
+
return EXIT_CONNECTION;
|
|
11569
12650
|
}
|
|
11570
12651
|
try {
|
|
11571
|
-
return await runVerb(platform2, parsed, conn);
|
|
12652
|
+
return await runVerb(platform2, parsed, conn, onProgress);
|
|
11572
12653
|
} catch (err) {
|
|
11573
|
-
if (
|
|
11574
|
-
await platform2.write(2,
|
|
11575
|
-
|
|
11576
|
-
return 6;
|
|
12654
|
+
if (interrupted) {
|
|
12655
|
+
await platform2.write(2, "mcpcat: interrupted\n");
|
|
12656
|
+
return EXIT_INTERRUPTED;
|
|
11577
12657
|
}
|
|
11578
|
-
|
|
12658
|
+
const e = classifyError(err);
|
|
12659
|
+
if (err instanceof InputRequiredError) await platform2.write(2, `${e.msg}
|
|
11579
12660
|
`);
|
|
11580
|
-
|
|
12661
|
+
else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
|
|
12662
|
+
return e.code;
|
|
11581
12663
|
} finally {
|
|
11582
12664
|
try {
|
|
11583
12665
|
await conn.close();
|
|
@@ -11585,6 +12667,11 @@ async function run(platform2, version, args2) {
|
|
|
11585
12667
|
}
|
|
11586
12668
|
}
|
|
11587
12669
|
}
|
|
12670
|
+
async function writeErr(p, json, kind, msg) {
|
|
12671
|
+
if (json) await renderErrorJson(p, kind, msg);
|
|
12672
|
+
else await p.write(2, `mcpcat: ${msg}
|
|
12673
|
+
`);
|
|
12674
|
+
}
|
|
11588
12675
|
|
|
11589
12676
|
// src/platform/host.ts
|
|
11590
12677
|
import { spawn as cpSpawn } from "node:child_process";
|
|
@@ -11754,6 +12841,7 @@ function nodeListen(options) {
|
|
|
11754
12841
|
function createHostPlatform() {
|
|
11755
12842
|
const encoder2 = new TextEncoder();
|
|
11756
12843
|
return {
|
|
12844
|
+
kind: "host",
|
|
11757
12845
|
fetch: globalThis.fetch,
|
|
11758
12846
|
spawn: nodeSpawn,
|
|
11759
12847
|
listen: nodeListen,
|
|
@@ -11785,5 +12873,5 @@ function createHostPlatform() {
|
|
|
11785
12873
|
// src/host.ts
|
|
11786
12874
|
var platform = createHostPlatform();
|
|
11787
12875
|
var args = process.argv.slice(2);
|
|
11788
|
-
var code = await run(platform, "0.
|
|
12876
|
+
var code = await run(platform, "0.9.0", args);
|
|
11789
12877
|
process.exit(code);
|