@ai-ecoverse/mcpcat 0.7.0 → 0.8.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 +559 -25
- package/dist/kernel/mcpcat.mjs +559 -25
- package/package.json +1 -1
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -6817,16 +6817,16 @@ async function createStatelessConnection(options) {
|
|
|
6817
6817
|
}
|
|
6818
6818
|
if (res.status >= 400) {
|
|
6819
6819
|
const text = await res.text();
|
|
6820
|
-
let
|
|
6820
|
+
let rpcError3;
|
|
6821
6821
|
try {
|
|
6822
6822
|
const parsed = JSON.parse(text);
|
|
6823
6823
|
if (parsed.error && typeof parsed.error.code === "number") {
|
|
6824
|
-
|
|
6824
|
+
rpcError3 = parsed.error;
|
|
6825
6825
|
}
|
|
6826
6826
|
} catch {
|
|
6827
6827
|
}
|
|
6828
|
-
if (
|
|
6829
|
-
throw new StatelessRpcError(
|
|
6828
|
+
if (rpcError3) {
|
|
6829
|
+
throw new StatelessRpcError(rpcError3.code, rpcError3.message, rpcError3.data);
|
|
6830
6830
|
}
|
|
6831
6831
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
6832
6832
|
}
|
|
@@ -9539,6 +9539,208 @@ function checkHost(host, boundLoopback) {
|
|
|
9539
9539
|
return { ok: false, reason: `host ${host} is not loopback` };
|
|
9540
9540
|
}
|
|
9541
9541
|
|
|
9542
|
+
// src/serve/proxy.ts
|
|
9543
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
9544
|
+
"initialize",
|
|
9545
|
+
"server/discover",
|
|
9546
|
+
"tools/list",
|
|
9547
|
+
"resources/list",
|
|
9548
|
+
"prompts/list",
|
|
9549
|
+
"ping"
|
|
9550
|
+
]);
|
|
9551
|
+
var PROTOCOL_VERSIONS2 = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
9552
|
+
var JSON_CT2 = "application/json; charset=utf-8";
|
|
9553
|
+
function readId2(value) {
|
|
9554
|
+
const id = Object.getOwnPropertyDescriptor(value, "id")?.value;
|
|
9555
|
+
if (typeof id === "string" || typeof id === "number" || id === null) return id;
|
|
9556
|
+
return null;
|
|
9557
|
+
}
|
|
9558
|
+
function readString2(value, key) {
|
|
9559
|
+
const found = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
9560
|
+
return typeof found === "string" ? found : void 0;
|
|
9561
|
+
}
|
|
9562
|
+
function hasOwn2(value, key) {
|
|
9563
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
9564
|
+
}
|
|
9565
|
+
function rpcOk2(id, result) {
|
|
9566
|
+
return {
|
|
9567
|
+
status: 200,
|
|
9568
|
+
contentType: JSON_CT2,
|
|
9569
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, result })
|
|
9570
|
+
};
|
|
9571
|
+
}
|
|
9572
|
+
function rpcError2(id, code2, message) {
|
|
9573
|
+
return {
|
|
9574
|
+
status: 200,
|
|
9575
|
+
contentType: JSON_CT2,
|
|
9576
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, error: { code: code2, message } })
|
|
9577
|
+
};
|
|
9578
|
+
}
|
|
9579
|
+
function scrubValue(value, secrets) {
|
|
9580
|
+
if (typeof value === "string") return scrubString(value, secrets);
|
|
9581
|
+
if (Array.isArray(value)) return value.map((v) => scrubValue(v, secrets));
|
|
9582
|
+
if (value && typeof value === "object") {
|
|
9583
|
+
const out = {};
|
|
9584
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9585
|
+
out[k] = scrubValue(v, secrets);
|
|
9586
|
+
}
|
|
9587
|
+
return out;
|
|
9588
|
+
}
|
|
9589
|
+
return value;
|
|
9590
|
+
}
|
|
9591
|
+
function scrubString(s, secrets) {
|
|
9592
|
+
let result = s;
|
|
9593
|
+
for (const h of secrets) {
|
|
9594
|
+
if (result.includes(h)) {
|
|
9595
|
+
result = result.replaceAll(h, "***");
|
|
9596
|
+
}
|
|
9597
|
+
}
|
|
9598
|
+
return result;
|
|
9599
|
+
}
|
|
9600
|
+
function translateCapabilities(info) {
|
|
9601
|
+
const caps = {};
|
|
9602
|
+
if (info.capabilities.tools) caps.tools = { listChanged: false };
|
|
9603
|
+
if (info.capabilities.resources) caps.resources = { listChanged: false };
|
|
9604
|
+
if (info.capabilities.prompts) caps.prompts = { listChanged: false };
|
|
9605
|
+
return caps;
|
|
9606
|
+
}
|
|
9607
|
+
function buildServerResult(info, protocolVersion) {
|
|
9608
|
+
return {
|
|
9609
|
+
protocolVersion,
|
|
9610
|
+
capabilities: translateCapabilities(info),
|
|
9611
|
+
serverInfo: {
|
|
9612
|
+
name: info.name ?? "mcpcat-proxy",
|
|
9613
|
+
version: info.version ?? "0.0.0",
|
|
9614
|
+
...info.title ? { title: info.title } : {}
|
|
9615
|
+
},
|
|
9616
|
+
...info.instructions ? { instructions: info.instructions } : {}
|
|
9617
|
+
};
|
|
9618
|
+
}
|
|
9619
|
+
function createProxyHandler(options) {
|
|
9620
|
+
let conn = null;
|
|
9621
|
+
let cachedInfo = null;
|
|
9622
|
+
let connecting = null;
|
|
9623
|
+
const secrets = options.hiddenHeaders;
|
|
9624
|
+
async function doConnect() {
|
|
9625
|
+
const c = await connect(options.platform, options.connectOpts);
|
|
9626
|
+
cachedInfo = await c.info();
|
|
9627
|
+
return c;
|
|
9628
|
+
}
|
|
9629
|
+
async function getConnection() {
|
|
9630
|
+
if (conn) return conn;
|
|
9631
|
+
if (connecting !== null) return connecting;
|
|
9632
|
+
connecting = doConnect();
|
|
9633
|
+
try {
|
|
9634
|
+
conn = await connecting;
|
|
9635
|
+
return conn;
|
|
9636
|
+
} finally {
|
|
9637
|
+
connecting = null;
|
|
9638
|
+
}
|
|
9639
|
+
}
|
|
9640
|
+
async function reconnect() {
|
|
9641
|
+
if (conn) {
|
|
9642
|
+
try {
|
|
9643
|
+
await conn.close();
|
|
9644
|
+
} catch {
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
conn = null;
|
|
9648
|
+
cachedInfo = null;
|
|
9649
|
+
return getConnection();
|
|
9650
|
+
}
|
|
9651
|
+
function scrubOk(id, result) {
|
|
9652
|
+
return rpcOk2(id, scrubValue(result, secrets));
|
|
9653
|
+
}
|
|
9654
|
+
function scrubErr(id, code2, message) {
|
|
9655
|
+
return rpcError2(id, code2, scrubString(message, secrets));
|
|
9656
|
+
}
|
|
9657
|
+
function handleInitialize(id, params) {
|
|
9658
|
+
const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
|
|
9659
|
+
const pv = PROTOCOL_VERSIONS2.find((v) => v === requested) ?? PROTOCOL_VERSIONS2[0];
|
|
9660
|
+
return scrubOk(id, buildServerResult(cachedInfo, pv));
|
|
9661
|
+
}
|
|
9662
|
+
function handleDiscover(id) {
|
|
9663
|
+
return scrubOk(id, {
|
|
9664
|
+
...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
|
|
9665
|
+
supportedVersions: PROTOCOL_VERSIONS2
|
|
9666
|
+
});
|
|
9667
|
+
}
|
|
9668
|
+
async function forwardWithRetry(method, doForward, id) {
|
|
9669
|
+
try {
|
|
9670
|
+
return scrubOk(id, await doForward());
|
|
9671
|
+
} catch (err) {
|
|
9672
|
+
if (!IDEMPOTENT_METHODS.has(method)) {
|
|
9673
|
+
return scrubErr(id, -32e3, err.message);
|
|
9674
|
+
}
|
|
9675
|
+
try {
|
|
9676
|
+
await reconnect();
|
|
9677
|
+
return scrubOk(id, await doForward());
|
|
9678
|
+
} catch (retryErr) {
|
|
9679
|
+
return scrubErr(id, -32e3, retryErr.message);
|
|
9680
|
+
}
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
async function handleCall(id, params, c) {
|
|
9684
|
+
const name = params && typeof params === "object" ? readString2(params, "name") : void 0;
|
|
9685
|
+
if (!name) return scrubErr(id, -32602, "tools/call requires a name");
|
|
9686
|
+
const args2 = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
9687
|
+
try {
|
|
9688
|
+
return scrubOk(id, await c.callTool(name, args2));
|
|
9689
|
+
} catch (err) {
|
|
9690
|
+
return scrubErr(id, -32e3, err.message);
|
|
9691
|
+
}
|
|
9692
|
+
}
|
|
9693
|
+
async function dispatch(method, id, params) {
|
|
9694
|
+
try {
|
|
9695
|
+
await getConnection();
|
|
9696
|
+
} catch (err) {
|
|
9697
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
9698
|
+
return scrubErr(id, -32e3, `upstream connection failed: ${err.message}`);
|
|
9699
|
+
}
|
|
9700
|
+
if (method === "initialize") return handleInitialize(id, params);
|
|
9701
|
+
if (method === "server/discover") return handleDiscover(id);
|
|
9702
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
9703
|
+
if (method === "tools/list") {
|
|
9704
|
+
return forwardWithRetry(
|
|
9705
|
+
method,
|
|
9706
|
+
async () => {
|
|
9707
|
+
const tools = await (await getConnection()).listTools();
|
|
9708
|
+
return { tools };
|
|
9709
|
+
},
|
|
9710
|
+
id
|
|
9711
|
+
);
|
|
9712
|
+
}
|
|
9713
|
+
if (method === "tools/call") return handleCall(id, params, conn);
|
|
9714
|
+
return scrubErr(id, -32601, `Method not found: ${method}`);
|
|
9715
|
+
}
|
|
9716
|
+
async function handle(raw) {
|
|
9717
|
+
let parsed;
|
|
9718
|
+
try {
|
|
9719
|
+
parsed = JSON.parse(raw);
|
|
9720
|
+
} catch {
|
|
9721
|
+
return rpcError2(null, -32700, "Parse error");
|
|
9722
|
+
}
|
|
9723
|
+
if (Array.isArray(parsed)) return rpcError2(null, -32600, "Batch requests are not supported");
|
|
9724
|
+
if (!parsed || typeof parsed !== "object") return rpcError2(null, -32600, "Invalid request");
|
|
9725
|
+
const method = readString2(parsed, "method");
|
|
9726
|
+
if (!method) return rpcError2(readId2(parsed), -32600, "Invalid request");
|
|
9727
|
+
if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
9728
|
+
const id = readId2(parsed);
|
|
9729
|
+
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
9730
|
+
return dispatch(method, id, params);
|
|
9731
|
+
}
|
|
9732
|
+
async function shutdown() {
|
|
9733
|
+
if (conn) {
|
|
9734
|
+
try {
|
|
9735
|
+
await conn.close();
|
|
9736
|
+
} catch {
|
|
9737
|
+
}
|
|
9738
|
+
conn = null;
|
|
9739
|
+
}
|
|
9740
|
+
}
|
|
9741
|
+
return { handle, shutdown };
|
|
9742
|
+
}
|
|
9743
|
+
|
|
9542
9744
|
// src/serve/runtime.ts
|
|
9543
9745
|
var CALL_TIMEOUT_MS = 12e4;
|
|
9544
9746
|
var MAX_OUTPUT_DEFAULT = 1048576;
|
|
@@ -9569,26 +9771,57 @@ function parseSingleFlag(args2, i, opts) {
|
|
|
9569
9771
|
opts.host = parsed.host;
|
|
9570
9772
|
opts.port = parsed.port;
|
|
9571
9773
|
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
9774
|
} else if (arg === "--json") {
|
|
9589
9775
|
opts.json = true;
|
|
9590
9776
|
} else if (!arg.startsWith("-")) {
|
|
9591
9777
|
opts.commands.push(parseCommandTarget(arg));
|
|
9778
|
+
} else {
|
|
9779
|
+
return parseValueFlag(args2, i, opts);
|
|
9780
|
+
}
|
|
9781
|
+
return i;
|
|
9782
|
+
}
|
|
9783
|
+
function parseValueFlag(args2, i, opts) {
|
|
9784
|
+
const arg = args2[i];
|
|
9785
|
+
if (!args2[i + 1]) return i;
|
|
9786
|
+
switch (arg) {
|
|
9787
|
+
case "--proxy":
|
|
9788
|
+
opts.proxy = args2[++i];
|
|
9789
|
+
break;
|
|
9790
|
+
case "--bearer":
|
|
9791
|
+
opts.bearer = args2[++i];
|
|
9792
|
+
break;
|
|
9793
|
+
case "--allow-origin":
|
|
9794
|
+
opts.allowOrigins.push(args2[++i]);
|
|
9795
|
+
break;
|
|
9796
|
+
case "--env":
|
|
9797
|
+
opts.envAllow.push(args2[++i]);
|
|
9798
|
+
break;
|
|
9799
|
+
case "--tool":
|
|
9800
|
+
opts.toolTemplates.push(args2[++i]);
|
|
9801
|
+
break;
|
|
9802
|
+
case "--call-timeout":
|
|
9803
|
+
opts.callTimeout = Number(args2[++i]) * 1e3;
|
|
9804
|
+
break;
|
|
9805
|
+
case "--concurrency":
|
|
9806
|
+
opts.concurrency = Number(args2[++i]);
|
|
9807
|
+
break;
|
|
9808
|
+
case "--max-output":
|
|
9809
|
+
opts.maxOutput = Number(args2[++i]);
|
|
9810
|
+
break;
|
|
9811
|
+
case "--cwd":
|
|
9812
|
+
opts.cwd = args2[++i];
|
|
9813
|
+
break;
|
|
9814
|
+
case "-H":
|
|
9815
|
+
case "--header": {
|
|
9816
|
+
const hdr = args2[++i];
|
|
9817
|
+
const colonIdx = hdr.indexOf(":");
|
|
9818
|
+
if (colonIdx > 0) {
|
|
9819
|
+
opts.proxyHeaders.push([hdr.slice(0, colonIdx).trim(), hdr.slice(colonIdx + 1).trim()]);
|
|
9820
|
+
}
|
|
9821
|
+
break;
|
|
9822
|
+
}
|
|
9823
|
+
default:
|
|
9824
|
+
break;
|
|
9592
9825
|
}
|
|
9593
9826
|
return i;
|
|
9594
9827
|
}
|
|
@@ -9606,7 +9839,9 @@ function parseServeFlags(args2) {
|
|
|
9606
9839
|
maxOutput: MAX_OUTPUT_DEFAULT,
|
|
9607
9840
|
cwd: void 0,
|
|
9608
9841
|
json: false,
|
|
9609
|
-
commands: []
|
|
9842
|
+
commands: [],
|
|
9843
|
+
proxy: void 0,
|
|
9844
|
+
proxyHeaders: []
|
|
9610
9845
|
};
|
|
9611
9846
|
for (let i = 0; i < args2.length; i++) {
|
|
9612
9847
|
i = parseSingleFlag(args2, i, opts);
|
|
@@ -9933,6 +10168,9 @@ async function runServe(platform2, options, version) {
|
|
|
9933
10168
|
}
|
|
9934
10169
|
resolvedBearer = expanded;
|
|
9935
10170
|
}
|
|
10171
|
+
if (options.proxy) {
|
|
10172
|
+
return runProxyServe(platform2, options, version, resolvedBearer);
|
|
10173
|
+
}
|
|
9936
10174
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
9937
10175
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
9938
10176
|
const clis = [];
|
|
@@ -9971,6 +10209,240 @@ async function runServe(platform2, options, version) {
|
|
|
9971
10209
|
resolvedBearer
|
|
9972
10210
|
);
|
|
9973
10211
|
}
|
|
10212
|
+
function resolveProxyStdio(target, version) {
|
|
10213
|
+
const rest = target.slice(6);
|
|
10214
|
+
const parts = rest.split(/\s+/).filter(Boolean);
|
|
10215
|
+
return { command: parts[0], args: parts.slice(1), version };
|
|
10216
|
+
}
|
|
10217
|
+
function resolveProxyUrl(target, headers, version) {
|
|
10218
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
10219
|
+
return { url, headers, version };
|
|
10220
|
+
}
|
|
10221
|
+
async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
|
|
10222
|
+
const config = await MergedConfigSource.load(platform2);
|
|
10223
|
+
const entry = await config.get(target);
|
|
10224
|
+
if (!entry) return `unknown target '${target}'`;
|
|
10225
|
+
if ("url" in entry && entry.url) {
|
|
10226
|
+
const merged = { ...entry.headers, ...connectHeaders };
|
|
10227
|
+
if (entry.headers) {
|
|
10228
|
+
for (const v of Object.values(entry.headers)) hiddenValues.add(v);
|
|
10229
|
+
}
|
|
10230
|
+
return { url: entry.url, headers: merged, version };
|
|
10231
|
+
}
|
|
10232
|
+
if ("command" in entry && entry.command) {
|
|
10233
|
+
return {
|
|
10234
|
+
command: entry.command,
|
|
10235
|
+
args: entry.args,
|
|
10236
|
+
env: entry.env,
|
|
10237
|
+
cwd: entry.cwd,
|
|
10238
|
+
version
|
|
10239
|
+
};
|
|
10240
|
+
}
|
|
10241
|
+
return `target '${target}' has no url or command`;
|
|
10242
|
+
}
|
|
10243
|
+
async function buildProxyConnectOpts(platform2, options, version) {
|
|
10244
|
+
const target = options.proxy;
|
|
10245
|
+
const connectHeaders = {};
|
|
10246
|
+
const hiddenValues = /* @__PURE__ */ new Set();
|
|
10247
|
+
for (const [k, v] of options.proxyHeaders) {
|
|
10248
|
+
const expanded = expandPlaceholdersStrict(v, platform2.env);
|
|
10249
|
+
if (expanded === void 0) return { error: `unset variable in header '${k}'` };
|
|
10250
|
+
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
10251
|
+
connectHeaders[key] = expanded;
|
|
10252
|
+
hiddenValues.add(expanded);
|
|
10253
|
+
}
|
|
10254
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
10255
|
+
let connectOpts;
|
|
10256
|
+
if (options.proxyCommand && options.proxyCommand.length > 0) {
|
|
10257
|
+
connectOpts = {
|
|
10258
|
+
command: options.proxyCommand[0],
|
|
10259
|
+
args: options.proxyCommand.slice(1),
|
|
10260
|
+
version
|
|
10261
|
+
};
|
|
10262
|
+
} else if (target.startsWith("stdio:")) {
|
|
10263
|
+
connectOpts = resolveProxyStdio(target, version);
|
|
10264
|
+
} else if (isUrl) {
|
|
10265
|
+
connectOpts = resolveProxyUrl(target, connectHeaders, version);
|
|
10266
|
+
} else {
|
|
10267
|
+
const result = await resolveProxyConfig(
|
|
10268
|
+
platform2,
|
|
10269
|
+
target,
|
|
10270
|
+
connectHeaders,
|
|
10271
|
+
hiddenValues,
|
|
10272
|
+
version
|
|
10273
|
+
);
|
|
10274
|
+
if (typeof result === "string") return { error: result };
|
|
10275
|
+
connectOpts = result;
|
|
10276
|
+
}
|
|
10277
|
+
if (connectOpts.headers) {
|
|
10278
|
+
for (const v of Object.values(connectOpts.headers)) {
|
|
10279
|
+
if (v) hiddenValues.add(v);
|
|
10280
|
+
}
|
|
10281
|
+
}
|
|
10282
|
+
return { connectOpts, hiddenValues };
|
|
10283
|
+
}
|
|
10284
|
+
async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
10285
|
+
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
10286
|
+
if ("error" in built) {
|
|
10287
|
+
await platform2.write(2, `mcpcat serve --proxy: ${built.error}
|
|
10288
|
+
`);
|
|
10289
|
+
return 2;
|
|
10290
|
+
}
|
|
10291
|
+
const { connectOpts, hiddenValues } = built;
|
|
10292
|
+
if (resolvedBearer) hiddenValues.add(resolvedBearer);
|
|
10293
|
+
const proxy = createProxyHandler({
|
|
10294
|
+
target: options.proxy,
|
|
10295
|
+
connectOpts,
|
|
10296
|
+
platform: platform2,
|
|
10297
|
+
hiddenHeaders: hiddenValues
|
|
10298
|
+
});
|
|
10299
|
+
if (options.transport === "stdio") {
|
|
10300
|
+
return runProxyStdio(platform2, proxy);
|
|
10301
|
+
}
|
|
10302
|
+
return runProxyHttp(platform2, options, proxy, resolvedBearer);
|
|
10303
|
+
}
|
|
10304
|
+
async function runProxyStdio(platform2, proxy) {
|
|
10305
|
+
const reader = platform2.stdin.getReader();
|
|
10306
|
+
let buffer = "";
|
|
10307
|
+
const dec = new TextDecoder();
|
|
10308
|
+
const enc = new TextEncoder();
|
|
10309
|
+
try {
|
|
10310
|
+
for (; ; ) {
|
|
10311
|
+
const { done, value } = await reader.read();
|
|
10312
|
+
if (done) break;
|
|
10313
|
+
buffer += dec.decode(value, { stream: true });
|
|
10314
|
+
let nlIdx;
|
|
10315
|
+
while ((nlIdx = buffer.indexOf("\n")) >= 0) {
|
|
10316
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
10317
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
10318
|
+
if (!line) continue;
|
|
10319
|
+
const outcome = await proxy.handle(line);
|
|
10320
|
+
if (outcome.status === 202) continue;
|
|
10321
|
+
const writer = platform2.stdout.getWriter();
|
|
10322
|
+
await writer.write(enc.encode(`${outcome.body}
|
|
10323
|
+
`));
|
|
10324
|
+
writer.releaseLock();
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
} finally {
|
|
10328
|
+
reader.releaseLock();
|
|
10329
|
+
await proxy.shutdown();
|
|
10330
|
+
}
|
|
10331
|
+
return 0;
|
|
10332
|
+
}
|
|
10333
|
+
async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
10334
|
+
if (!platform2.listen) {
|
|
10335
|
+
await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
|
|
10336
|
+
return 3;
|
|
10337
|
+
}
|
|
10338
|
+
const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
|
|
10339
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
10340
|
+
await platform2.write(
|
|
10341
|
+
2,
|
|
10342
|
+
"mcpcat serve --proxy: --bearer is required when binding off loopback\n"
|
|
10343
|
+
);
|
|
10344
|
+
return 2;
|
|
10345
|
+
}
|
|
10346
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
10347
|
+
const listener = await platform2.listen({
|
|
10348
|
+
host: options.host,
|
|
10349
|
+
port: options.port
|
|
10350
|
+
});
|
|
10351
|
+
const actualPort = listener.port;
|
|
10352
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
10353
|
+
if (options.json) {
|
|
10354
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
10355
|
+
`);
|
|
10356
|
+
}
|
|
10357
|
+
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
10358
|
+
`);
|
|
10359
|
+
if (options.onListening) {
|
|
10360
|
+
await options.onListening(actualPort);
|
|
10361
|
+
}
|
|
10362
|
+
let shutdownRequested = false;
|
|
10363
|
+
const shutdown = () => {
|
|
10364
|
+
if (shutdownRequested) return;
|
|
10365
|
+
shutdownRequested = true;
|
|
10366
|
+
listener.close();
|
|
10367
|
+
void proxy.shutdown();
|
|
10368
|
+
};
|
|
10369
|
+
if (platform2.onSignal) {
|
|
10370
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
10371
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
10372
|
+
}
|
|
10373
|
+
const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
|
|
10374
|
+
try {
|
|
10375
|
+
for await (const connection of listener) {
|
|
10376
|
+
if (shutdownRequested) break;
|
|
10377
|
+
const conn = connection;
|
|
10378
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
10379
|
+
}).finally(() => {
|
|
10380
|
+
try {
|
|
10381
|
+
conn.close();
|
|
10382
|
+
} catch {
|
|
10383
|
+
}
|
|
10384
|
+
});
|
|
10385
|
+
}
|
|
10386
|
+
} catch {
|
|
10387
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
10388
|
+
}
|
|
10389
|
+
return 0;
|
|
10390
|
+
}
|
|
10391
|
+
async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer) {
|
|
10392
|
+
if (resolvedBearer) {
|
|
10393
|
+
const authHeader = request.headers.authorization;
|
|
10394
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
10395
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
10396
|
+
return {
|
|
10397
|
+
status: 401,
|
|
10398
|
+
reason: "Unauthorized",
|
|
10399
|
+
headers: { "content-type": "text/plain" },
|
|
10400
|
+
body: "Unauthorized"
|
|
10401
|
+
};
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
10405
|
+
if (!originCheck.ok) {
|
|
10406
|
+
return {
|
|
10407
|
+
status: 403,
|
|
10408
|
+
reason: "Forbidden",
|
|
10409
|
+
headers: { "content-type": "text/plain" },
|
|
10410
|
+
body: originCheck.reason ?? "Forbidden"
|
|
10411
|
+
};
|
|
10412
|
+
}
|
|
10413
|
+
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
10414
|
+
if (!hostCheck.ok) {
|
|
10415
|
+
return {
|
|
10416
|
+
status: 403,
|
|
10417
|
+
reason: "Forbidden",
|
|
10418
|
+
headers: { "content-type": "text/plain" },
|
|
10419
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
10420
|
+
};
|
|
10421
|
+
}
|
|
10422
|
+
if (request.target !== "/mcp") {
|
|
10423
|
+
return {
|
|
10424
|
+
status: 404,
|
|
10425
|
+
reason: "Not Found",
|
|
10426
|
+
headers: { "content-type": "text/plain" },
|
|
10427
|
+
body: "Not Found"
|
|
10428
|
+
};
|
|
10429
|
+
}
|
|
10430
|
+
if (request.method !== "POST") {
|
|
10431
|
+
return {
|
|
10432
|
+
status: 405,
|
|
10433
|
+
reason: "Method Not Allowed",
|
|
10434
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
10435
|
+
body: "Method Not Allowed"
|
|
10436
|
+
};
|
|
10437
|
+
}
|
|
10438
|
+
const outcome = await proxy.handle(request.body);
|
|
10439
|
+
return {
|
|
10440
|
+
status: outcome.status,
|
|
10441
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
10442
|
+
headers: { "content-type": outcome.contentType },
|
|
10443
|
+
body: outcome.body
|
|
10444
|
+
};
|
|
10445
|
+
}
|
|
9974
10446
|
async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
9975
10447
|
const reader = platform2.stdin.getReader();
|
|
9976
10448
|
let buffer = "";
|
|
@@ -10477,6 +10949,14 @@ function parseAddFlagAt(flagArgs, i, flags) {
|
|
|
10477
10949
|
flags.noProbe = true;
|
|
10478
10950
|
return { next: i + 1 };
|
|
10479
10951
|
}
|
|
10952
|
+
if (arg === "--bridge") {
|
|
10953
|
+
flags.bridge = true;
|
|
10954
|
+
return { next: i + 1 };
|
|
10955
|
+
}
|
|
10956
|
+
if (arg === "--bearer" && i + 1 < flagArgs.length) {
|
|
10957
|
+
flags.bearer = flagArgs[i + 1];
|
|
10958
|
+
return { next: i + 2 };
|
|
10959
|
+
}
|
|
10480
10960
|
if (arg === "--config" && i + 1 < flagArgs.length) {
|
|
10481
10961
|
flags.configFile = flagArgs[i + 1];
|
|
10482
10962
|
return { next: i + 2 };
|
|
@@ -10513,6 +10993,7 @@ function parseAddFlags(args2) {
|
|
|
10513
10993
|
const flags = {
|
|
10514
10994
|
local: false,
|
|
10515
10995
|
noProbe: false,
|
|
10996
|
+
bridge: false,
|
|
10516
10997
|
headers: [],
|
|
10517
10998
|
envPairs: [],
|
|
10518
10999
|
positional: [],
|
|
@@ -10875,6 +11356,54 @@ async function runImport(platform2, args2) {
|
|
|
10875
11356
|
}
|
|
10876
11357
|
return 0;
|
|
10877
11358
|
}
|
|
11359
|
+
async function runBridge(platform2, args2, version) {
|
|
11360
|
+
if (platform2.kind === "host") {
|
|
11361
|
+
await platform2.write(
|
|
11362
|
+
2,
|
|
11363
|
+
"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"
|
|
11364
|
+
);
|
|
11365
|
+
return 2;
|
|
11366
|
+
}
|
|
11367
|
+
const flags = parseAddFlags(args2);
|
|
11368
|
+
if (typeof flags === "string") {
|
|
11369
|
+
await platform2.write(2, `mcpcat add --bridge: ${flags}
|
|
11370
|
+
`);
|
|
11371
|
+
return 2;
|
|
11372
|
+
}
|
|
11373
|
+
const name = flags.positional[0];
|
|
11374
|
+
if (!name) {
|
|
11375
|
+
await platform2.write(2, "mcpcat add --bridge: requires a server name\n");
|
|
11376
|
+
return 2;
|
|
11377
|
+
}
|
|
11378
|
+
if (flags.cmdArgs.length === 0) {
|
|
11379
|
+
await platform2.write(2, "mcpcat add --bridge: requires -- <command> [args...]\n");
|
|
11380
|
+
return 2;
|
|
11381
|
+
}
|
|
11382
|
+
if (flags.bearer) {
|
|
11383
|
+
await platform2.write(
|
|
11384
|
+
2,
|
|
11385
|
+
"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"
|
|
11386
|
+
);
|
|
11387
|
+
return 2;
|
|
11388
|
+
}
|
|
11389
|
+
const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
|
|
11390
|
+
serveOptions.proxyCommand = flags.cmdArgs;
|
|
11391
|
+
const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
|
|
11392
|
+
serveOptions.onListening = async (actualPort) => {
|
|
11393
|
+
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
11394
|
+
const httpConfig = { url: bridgeUrl };
|
|
11395
|
+
if (flags.description) httpConfig.description = flags.description;
|
|
11396
|
+
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
11397
|
+
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
11398
|
+
await platform2.write(
|
|
11399
|
+
1,
|
|
11400
|
+
`${replaced ? "replaced" : "added"} ${name} in ${path}
|
|
11401
|
+
bridge does not survive a reload; runs in the foreground (start with &)
|
|
11402
|
+
`
|
|
11403
|
+
);
|
|
11404
|
+
};
|
|
11405
|
+
return runServe(platform2, serveOptions, version);
|
|
11406
|
+
}
|
|
10878
11407
|
|
|
10879
11408
|
// src/cli/main.ts
|
|
10880
11409
|
var VERBS = [
|
|
@@ -10939,6 +11468,9 @@ Client verbs:
|
|
|
10939
11468
|
|
|
10940
11469
|
Configuration verbs:
|
|
10941
11470
|
add <name> <url> Add a server to mcp.json
|
|
11471
|
+
add <name> --bridge --
|
|
11472
|
+
Bridge a stdio server to HTTP (kernel only,
|
|
11473
|
+
runs in foreground, does not survive reload)
|
|
10942
11474
|
rm <name> Remove a server
|
|
10943
11475
|
list List configured servers
|
|
10944
11476
|
enable|disable <name> Toggle a server
|
|
@@ -10948,7 +11480,7 @@ Configuration verbs:
|
|
|
10948
11480
|
logout <target> Remove tokens (not implemented yet)
|
|
10949
11481
|
|
|
10950
11482
|
Server and code verbs:
|
|
10951
|
-
serve [--stdio|--http] \u2026 Expose commands as MCP server
|
|
11483
|
+
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
10952
11484
|
code [-e src|file.js] Run a codemode script
|
|
10953
11485
|
grep <pattern> [target\u2026] Search tools by name or description
|
|
10954
11486
|
discover <url> Find MCP servers at a URL
|
|
@@ -11301,10 +11833,11 @@ async function probeServer(_name, config, platform2) {
|
|
|
11301
11833
|
return err.message;
|
|
11302
11834
|
}
|
|
11303
11835
|
}
|
|
11304
|
-
async function dispatchConfigVerb(platform2, verb, args2) {
|
|
11836
|
+
async function dispatchConfigVerb(platform2, verb, args2, version) {
|
|
11305
11837
|
const rest = args2.slice(1);
|
|
11306
11838
|
switch (verb) {
|
|
11307
11839
|
case "add":
|
|
11840
|
+
if (rest.includes("--bridge")) return runBridge(platform2, rest, version);
|
|
11308
11841
|
return runAdd(platform2, rest, probeServer);
|
|
11309
11842
|
case "rm":
|
|
11310
11843
|
return runRm(platform2, rest);
|
|
@@ -11347,7 +11880,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args2) {
|
|
|
11347
11880
|
async function dispatchServeVerb(platform2, args2, version) {
|
|
11348
11881
|
const serveArgs = args2.slice(args2.indexOf("serve") + 1);
|
|
11349
11882
|
const serveOptions = parseServeFlags(serveArgs);
|
|
11350
|
-
if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
11883
|
+
if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
11351
11884
|
await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
|
|
11352
11885
|
return 2;
|
|
11353
11886
|
}
|
|
@@ -11511,7 +12044,7 @@ async function run(platform2, version, args2) {
|
|
|
11511
12044
|
if (parsed.verb in CONFIG_VERBS) {
|
|
11512
12045
|
const disc = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
11513
12046
|
if (disc !== void 0) return disc;
|
|
11514
|
-
return dispatchConfigVerb(platform2, parsed.verb, args2);
|
|
12047
|
+
return dispatchConfigVerb(platform2, parsed.verb, args2, version);
|
|
11515
12048
|
}
|
|
11516
12049
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
|
|
11517
12050
|
if (discResult !== void 0) return discResult;
|
|
@@ -11754,6 +12287,7 @@ function nodeListen(options) {
|
|
|
11754
12287
|
function createHostPlatform() {
|
|
11755
12288
|
const encoder2 = new TextEncoder();
|
|
11756
12289
|
return {
|
|
12290
|
+
kind: "host",
|
|
11757
12291
|
fetch: globalThis.fetch,
|
|
11758
12292
|
spawn: nodeSpawn,
|
|
11759
12293
|
listen: nodeListen,
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -6418,16 +6418,16 @@ async function createStatelessConnection(options) {
|
|
|
6418
6418
|
}
|
|
6419
6419
|
if (res.status >= 400) {
|
|
6420
6420
|
const text = await res.text();
|
|
6421
|
-
let
|
|
6421
|
+
let rpcError3;
|
|
6422
6422
|
try {
|
|
6423
6423
|
const parsed = JSON.parse(text);
|
|
6424
6424
|
if (parsed.error && typeof parsed.error.code === "number") {
|
|
6425
|
-
|
|
6425
|
+
rpcError3 = parsed.error;
|
|
6426
6426
|
}
|
|
6427
6427
|
} catch {
|
|
6428
6428
|
}
|
|
6429
|
-
if (
|
|
6430
|
-
throw new StatelessRpcError(
|
|
6429
|
+
if (rpcError3) {
|
|
6430
|
+
throw new StatelessRpcError(rpcError3.code, rpcError3.message, rpcError3.data);
|
|
6431
6431
|
}
|
|
6432
6432
|
throw new StatelessHttpError(res.status, res.statusText, method, text.slice(0, 512));
|
|
6433
6433
|
}
|
|
@@ -9140,6 +9140,208 @@ function checkHost(host, boundLoopback) {
|
|
|
9140
9140
|
return { ok: false, reason: `host ${host} is not loopback` };
|
|
9141
9141
|
}
|
|
9142
9142
|
|
|
9143
|
+
// src/serve/proxy.ts
|
|
9144
|
+
var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
9145
|
+
"initialize",
|
|
9146
|
+
"server/discover",
|
|
9147
|
+
"tools/list",
|
|
9148
|
+
"resources/list",
|
|
9149
|
+
"prompts/list",
|
|
9150
|
+
"ping"
|
|
9151
|
+
]);
|
|
9152
|
+
var PROTOCOL_VERSIONS2 = ["2026-07-28", "2025-11-25", "2025-06-18"];
|
|
9153
|
+
var JSON_CT2 = "application/json; charset=utf-8";
|
|
9154
|
+
function readId2(value) {
|
|
9155
|
+
const id = Object.getOwnPropertyDescriptor(value, "id")?.value;
|
|
9156
|
+
if (typeof id === "string" || typeof id === "number" || id === null) return id;
|
|
9157
|
+
return null;
|
|
9158
|
+
}
|
|
9159
|
+
function readString2(value, key) {
|
|
9160
|
+
const found = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
9161
|
+
return typeof found === "string" ? found : void 0;
|
|
9162
|
+
}
|
|
9163
|
+
function hasOwn2(value, key) {
|
|
9164
|
+
return Object.prototype.hasOwnProperty.call(value, key);
|
|
9165
|
+
}
|
|
9166
|
+
function rpcOk2(id, result) {
|
|
9167
|
+
return {
|
|
9168
|
+
status: 200,
|
|
9169
|
+
contentType: JSON_CT2,
|
|
9170
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, result })
|
|
9171
|
+
};
|
|
9172
|
+
}
|
|
9173
|
+
function rpcError2(id, code, message) {
|
|
9174
|
+
return {
|
|
9175
|
+
status: 200,
|
|
9176
|
+
contentType: JSON_CT2,
|
|
9177
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } })
|
|
9178
|
+
};
|
|
9179
|
+
}
|
|
9180
|
+
function scrubValue(value, secrets) {
|
|
9181
|
+
if (typeof value === "string") return scrubString(value, secrets);
|
|
9182
|
+
if (Array.isArray(value)) return value.map((v) => scrubValue(v, secrets));
|
|
9183
|
+
if (value && typeof value === "object") {
|
|
9184
|
+
const out = {};
|
|
9185
|
+
for (const [k, v] of Object.entries(value)) {
|
|
9186
|
+
out[k] = scrubValue(v, secrets);
|
|
9187
|
+
}
|
|
9188
|
+
return out;
|
|
9189
|
+
}
|
|
9190
|
+
return value;
|
|
9191
|
+
}
|
|
9192
|
+
function scrubString(s, secrets) {
|
|
9193
|
+
let result = s;
|
|
9194
|
+
for (const h of secrets) {
|
|
9195
|
+
if (result.includes(h)) {
|
|
9196
|
+
result = result.replaceAll(h, "***");
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
return result;
|
|
9200
|
+
}
|
|
9201
|
+
function translateCapabilities(info) {
|
|
9202
|
+
const caps = {};
|
|
9203
|
+
if (info.capabilities.tools) caps.tools = { listChanged: false };
|
|
9204
|
+
if (info.capabilities.resources) caps.resources = { listChanged: false };
|
|
9205
|
+
if (info.capabilities.prompts) caps.prompts = { listChanged: false };
|
|
9206
|
+
return caps;
|
|
9207
|
+
}
|
|
9208
|
+
function buildServerResult(info, protocolVersion) {
|
|
9209
|
+
return {
|
|
9210
|
+
protocolVersion,
|
|
9211
|
+
capabilities: translateCapabilities(info),
|
|
9212
|
+
serverInfo: {
|
|
9213
|
+
name: info.name ?? "mcpcat-proxy",
|
|
9214
|
+
version: info.version ?? "0.0.0",
|
|
9215
|
+
...info.title ? { title: info.title } : {}
|
|
9216
|
+
},
|
|
9217
|
+
...info.instructions ? { instructions: info.instructions } : {}
|
|
9218
|
+
};
|
|
9219
|
+
}
|
|
9220
|
+
function createProxyHandler(options) {
|
|
9221
|
+
let conn = null;
|
|
9222
|
+
let cachedInfo = null;
|
|
9223
|
+
let connecting = null;
|
|
9224
|
+
const secrets = options.hiddenHeaders;
|
|
9225
|
+
async function doConnect() {
|
|
9226
|
+
const c = await connect(options.platform, options.connectOpts);
|
|
9227
|
+
cachedInfo = await c.info();
|
|
9228
|
+
return c;
|
|
9229
|
+
}
|
|
9230
|
+
async function getConnection() {
|
|
9231
|
+
if (conn) return conn;
|
|
9232
|
+
if (connecting !== null) return connecting;
|
|
9233
|
+
connecting = doConnect();
|
|
9234
|
+
try {
|
|
9235
|
+
conn = await connecting;
|
|
9236
|
+
return conn;
|
|
9237
|
+
} finally {
|
|
9238
|
+
connecting = null;
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9241
|
+
async function reconnect() {
|
|
9242
|
+
if (conn) {
|
|
9243
|
+
try {
|
|
9244
|
+
await conn.close();
|
|
9245
|
+
} catch {
|
|
9246
|
+
}
|
|
9247
|
+
}
|
|
9248
|
+
conn = null;
|
|
9249
|
+
cachedInfo = null;
|
|
9250
|
+
return getConnection();
|
|
9251
|
+
}
|
|
9252
|
+
function scrubOk(id, result) {
|
|
9253
|
+
return rpcOk2(id, scrubValue(result, secrets));
|
|
9254
|
+
}
|
|
9255
|
+
function scrubErr(id, code, message) {
|
|
9256
|
+
return rpcError2(id, code, scrubString(message, secrets));
|
|
9257
|
+
}
|
|
9258
|
+
function handleInitialize(id, params) {
|
|
9259
|
+
const requested = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "protocolVersion")?.value : void 0;
|
|
9260
|
+
const pv = PROTOCOL_VERSIONS2.find((v) => v === requested) ?? PROTOCOL_VERSIONS2[0];
|
|
9261
|
+
return scrubOk(id, buildServerResult(cachedInfo, pv));
|
|
9262
|
+
}
|
|
9263
|
+
function handleDiscover(id) {
|
|
9264
|
+
return scrubOk(id, {
|
|
9265
|
+
...buildServerResult(cachedInfo, PROTOCOL_VERSIONS2[0]),
|
|
9266
|
+
supportedVersions: PROTOCOL_VERSIONS2
|
|
9267
|
+
});
|
|
9268
|
+
}
|
|
9269
|
+
async function forwardWithRetry(method, doForward, id) {
|
|
9270
|
+
try {
|
|
9271
|
+
return scrubOk(id, await doForward());
|
|
9272
|
+
} catch (err) {
|
|
9273
|
+
if (!IDEMPOTENT_METHODS.has(method)) {
|
|
9274
|
+
return scrubErr(id, -32e3, err.message);
|
|
9275
|
+
}
|
|
9276
|
+
try {
|
|
9277
|
+
await reconnect();
|
|
9278
|
+
return scrubOk(id, await doForward());
|
|
9279
|
+
} catch (retryErr) {
|
|
9280
|
+
return scrubErr(id, -32e3, retryErr.message);
|
|
9281
|
+
}
|
|
9282
|
+
}
|
|
9283
|
+
}
|
|
9284
|
+
async function handleCall(id, params, c) {
|
|
9285
|
+
const name = params && typeof params === "object" ? readString2(params, "name") : void 0;
|
|
9286
|
+
if (!name) return scrubErr(id, -32602, "tools/call requires a name");
|
|
9287
|
+
const args = params && typeof params === "object" ? Object.getOwnPropertyDescriptor(params, "arguments")?.value : void 0;
|
|
9288
|
+
try {
|
|
9289
|
+
return scrubOk(id, await c.callTool(name, args));
|
|
9290
|
+
} catch (err) {
|
|
9291
|
+
return scrubErr(id, -32e3, err.message);
|
|
9292
|
+
}
|
|
9293
|
+
}
|
|
9294
|
+
async function dispatch(method, id, params) {
|
|
9295
|
+
try {
|
|
9296
|
+
await getConnection();
|
|
9297
|
+
} catch (err) {
|
|
9298
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
9299
|
+
return scrubErr(id, -32e3, `upstream connection failed: ${err.message}`);
|
|
9300
|
+
}
|
|
9301
|
+
if (method === "initialize") return handleInitialize(id, params);
|
|
9302
|
+
if (method === "server/discover") return handleDiscover(id);
|
|
9303
|
+
if (method === "ping") return rpcOk2(id, {});
|
|
9304
|
+
if (method === "tools/list") {
|
|
9305
|
+
return forwardWithRetry(
|
|
9306
|
+
method,
|
|
9307
|
+
async () => {
|
|
9308
|
+
const tools = await (await getConnection()).listTools();
|
|
9309
|
+
return { tools };
|
|
9310
|
+
},
|
|
9311
|
+
id
|
|
9312
|
+
);
|
|
9313
|
+
}
|
|
9314
|
+
if (method === "tools/call") return handleCall(id, params, conn);
|
|
9315
|
+
return scrubErr(id, -32601, `Method not found: ${method}`);
|
|
9316
|
+
}
|
|
9317
|
+
async function handle(raw) {
|
|
9318
|
+
let parsed;
|
|
9319
|
+
try {
|
|
9320
|
+
parsed = JSON.parse(raw);
|
|
9321
|
+
} catch {
|
|
9322
|
+
return rpcError2(null, -32700, "Parse error");
|
|
9323
|
+
}
|
|
9324
|
+
if (Array.isArray(parsed)) return rpcError2(null, -32600, "Batch requests are not supported");
|
|
9325
|
+
if (!parsed || typeof parsed !== "object") return rpcError2(null, -32600, "Invalid request");
|
|
9326
|
+
const method = readString2(parsed, "method");
|
|
9327
|
+
if (!method) return rpcError2(readId2(parsed), -32600, "Invalid request");
|
|
9328
|
+
if (!hasOwn2(parsed, "id")) return { status: 202, contentType: "text/plain", body: "" };
|
|
9329
|
+
const id = readId2(parsed);
|
|
9330
|
+
const params = Object.getOwnPropertyDescriptor(parsed, "params")?.value;
|
|
9331
|
+
return dispatch(method, id, params);
|
|
9332
|
+
}
|
|
9333
|
+
async function shutdown() {
|
|
9334
|
+
if (conn) {
|
|
9335
|
+
try {
|
|
9336
|
+
await conn.close();
|
|
9337
|
+
} catch {
|
|
9338
|
+
}
|
|
9339
|
+
conn = null;
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9342
|
+
return { handle, shutdown };
|
|
9343
|
+
}
|
|
9344
|
+
|
|
9143
9345
|
// src/serve/runtime.ts
|
|
9144
9346
|
var CALL_TIMEOUT_MS = 12e4;
|
|
9145
9347
|
var MAX_OUTPUT_DEFAULT = 1048576;
|
|
@@ -9170,26 +9372,57 @@ function parseSingleFlag(args, i, opts) {
|
|
|
9170
9372
|
opts.host = parsed.host;
|
|
9171
9373
|
opts.port = parsed.port;
|
|
9172
9374
|
return parsed.next;
|
|
9173
|
-
} else if (arg === "--bearer" && args[i + 1]) {
|
|
9174
|
-
opts.bearer = args[++i];
|
|
9175
|
-
} else if (arg === "--allow-origin" && args[i + 1]) {
|
|
9176
|
-
opts.allowOrigins.push(args[++i]);
|
|
9177
|
-
} else if (arg === "--env" && args[i + 1]) {
|
|
9178
|
-
opts.envAllow.push(args[++i]);
|
|
9179
|
-
} else if (arg === "--tool" && args[i + 1]) {
|
|
9180
|
-
opts.toolTemplates.push(args[++i]);
|
|
9181
|
-
} else if (arg === "--call-timeout" && args[i + 1]) {
|
|
9182
|
-
opts.callTimeout = Number(args[++i]) * 1e3;
|
|
9183
|
-
} else if (arg === "--concurrency" && args[i + 1]) {
|
|
9184
|
-
opts.concurrency = Number(args[++i]);
|
|
9185
|
-
} else if (arg === "--max-output" && args[i + 1]) {
|
|
9186
|
-
opts.maxOutput = Number(args[++i]);
|
|
9187
|
-
} else if (arg === "--cwd" && args[i + 1]) {
|
|
9188
|
-
opts.cwd = args[++i];
|
|
9189
9375
|
} else if (arg === "--json") {
|
|
9190
9376
|
opts.json = true;
|
|
9191
9377
|
} else if (!arg.startsWith("-")) {
|
|
9192
9378
|
opts.commands.push(parseCommandTarget(arg));
|
|
9379
|
+
} else {
|
|
9380
|
+
return parseValueFlag(args, i, opts);
|
|
9381
|
+
}
|
|
9382
|
+
return i;
|
|
9383
|
+
}
|
|
9384
|
+
function parseValueFlag(args, i, opts) {
|
|
9385
|
+
const arg = args[i];
|
|
9386
|
+
if (!args[i + 1]) return i;
|
|
9387
|
+
switch (arg) {
|
|
9388
|
+
case "--proxy":
|
|
9389
|
+
opts.proxy = args[++i];
|
|
9390
|
+
break;
|
|
9391
|
+
case "--bearer":
|
|
9392
|
+
opts.bearer = args[++i];
|
|
9393
|
+
break;
|
|
9394
|
+
case "--allow-origin":
|
|
9395
|
+
opts.allowOrigins.push(args[++i]);
|
|
9396
|
+
break;
|
|
9397
|
+
case "--env":
|
|
9398
|
+
opts.envAllow.push(args[++i]);
|
|
9399
|
+
break;
|
|
9400
|
+
case "--tool":
|
|
9401
|
+
opts.toolTemplates.push(args[++i]);
|
|
9402
|
+
break;
|
|
9403
|
+
case "--call-timeout":
|
|
9404
|
+
opts.callTimeout = Number(args[++i]) * 1e3;
|
|
9405
|
+
break;
|
|
9406
|
+
case "--concurrency":
|
|
9407
|
+
opts.concurrency = Number(args[++i]);
|
|
9408
|
+
break;
|
|
9409
|
+
case "--max-output":
|
|
9410
|
+
opts.maxOutput = Number(args[++i]);
|
|
9411
|
+
break;
|
|
9412
|
+
case "--cwd":
|
|
9413
|
+
opts.cwd = args[++i];
|
|
9414
|
+
break;
|
|
9415
|
+
case "-H":
|
|
9416
|
+
case "--header": {
|
|
9417
|
+
const hdr = args[++i];
|
|
9418
|
+
const colonIdx = hdr.indexOf(":");
|
|
9419
|
+
if (colonIdx > 0) {
|
|
9420
|
+
opts.proxyHeaders.push([hdr.slice(0, colonIdx).trim(), hdr.slice(colonIdx + 1).trim()]);
|
|
9421
|
+
}
|
|
9422
|
+
break;
|
|
9423
|
+
}
|
|
9424
|
+
default:
|
|
9425
|
+
break;
|
|
9193
9426
|
}
|
|
9194
9427
|
return i;
|
|
9195
9428
|
}
|
|
@@ -9207,7 +9440,9 @@ function parseServeFlags(args) {
|
|
|
9207
9440
|
maxOutput: MAX_OUTPUT_DEFAULT,
|
|
9208
9441
|
cwd: void 0,
|
|
9209
9442
|
json: false,
|
|
9210
|
-
commands: []
|
|
9443
|
+
commands: [],
|
|
9444
|
+
proxy: void 0,
|
|
9445
|
+
proxyHeaders: []
|
|
9211
9446
|
};
|
|
9212
9447
|
for (let i = 0; i < args.length; i++) {
|
|
9213
9448
|
i = parseSingleFlag(args, i, opts);
|
|
@@ -9534,6 +9769,9 @@ async function runServe(platform2, options, version) {
|
|
|
9534
9769
|
}
|
|
9535
9770
|
resolvedBearer = expanded;
|
|
9536
9771
|
}
|
|
9772
|
+
if (options.proxy) {
|
|
9773
|
+
return runProxyServe(platform2, options, version, resolvedBearer);
|
|
9774
|
+
}
|
|
9537
9775
|
const serveCwd = options.cwd ?? platform2.cwd;
|
|
9538
9776
|
const childEnv = buildChildEnv(platform2, options.envAllow);
|
|
9539
9777
|
const clis = [];
|
|
@@ -9572,6 +9810,240 @@ async function runServe(platform2, options, version) {
|
|
|
9572
9810
|
resolvedBearer
|
|
9573
9811
|
);
|
|
9574
9812
|
}
|
|
9813
|
+
function resolveProxyStdio(target, version) {
|
|
9814
|
+
const rest = target.slice(6);
|
|
9815
|
+
const parts = rest.split(/\s+/).filter(Boolean);
|
|
9816
|
+
return { command: parts[0], args: parts.slice(1), version };
|
|
9817
|
+
}
|
|
9818
|
+
function resolveProxyUrl(target, headers, version) {
|
|
9819
|
+
const url = target.startsWith("http") ? target : `https://${target}`;
|
|
9820
|
+
return { url, headers, version };
|
|
9821
|
+
}
|
|
9822
|
+
async function resolveProxyConfig(platform2, target, connectHeaders, hiddenValues, version) {
|
|
9823
|
+
const config = await MergedConfigSource.load(platform2);
|
|
9824
|
+
const entry = await config.get(target);
|
|
9825
|
+
if (!entry) return `unknown target '${target}'`;
|
|
9826
|
+
if ("url" in entry && entry.url) {
|
|
9827
|
+
const merged = { ...entry.headers, ...connectHeaders };
|
|
9828
|
+
if (entry.headers) {
|
|
9829
|
+
for (const v of Object.values(entry.headers)) hiddenValues.add(v);
|
|
9830
|
+
}
|
|
9831
|
+
return { url: entry.url, headers: merged, version };
|
|
9832
|
+
}
|
|
9833
|
+
if ("command" in entry && entry.command) {
|
|
9834
|
+
return {
|
|
9835
|
+
command: entry.command,
|
|
9836
|
+
args: entry.args,
|
|
9837
|
+
env: entry.env,
|
|
9838
|
+
cwd: entry.cwd,
|
|
9839
|
+
version
|
|
9840
|
+
};
|
|
9841
|
+
}
|
|
9842
|
+
return `target '${target}' has no url or command`;
|
|
9843
|
+
}
|
|
9844
|
+
async function buildProxyConnectOpts(platform2, options, version) {
|
|
9845
|
+
const target = options.proxy;
|
|
9846
|
+
const connectHeaders = {};
|
|
9847
|
+
const hiddenValues = /* @__PURE__ */ new Set();
|
|
9848
|
+
for (const [k, v] of options.proxyHeaders) {
|
|
9849
|
+
const expanded = expandPlaceholdersStrict(v, platform2.env);
|
|
9850
|
+
if (expanded === void 0) return { error: `unset variable in header '${k}'` };
|
|
9851
|
+
const key = k.toLowerCase() === "authorization" ? "Authorization" : k;
|
|
9852
|
+
connectHeaders[key] = expanded;
|
|
9853
|
+
hiddenValues.add(expanded);
|
|
9854
|
+
}
|
|
9855
|
+
const isUrl = target.startsWith("http://") || target.startsWith("https://") || target.includes(".");
|
|
9856
|
+
let connectOpts;
|
|
9857
|
+
if (options.proxyCommand && options.proxyCommand.length > 0) {
|
|
9858
|
+
connectOpts = {
|
|
9859
|
+
command: options.proxyCommand[0],
|
|
9860
|
+
args: options.proxyCommand.slice(1),
|
|
9861
|
+
version
|
|
9862
|
+
};
|
|
9863
|
+
} else if (target.startsWith("stdio:")) {
|
|
9864
|
+
connectOpts = resolveProxyStdio(target, version);
|
|
9865
|
+
} else if (isUrl) {
|
|
9866
|
+
connectOpts = resolveProxyUrl(target, connectHeaders, version);
|
|
9867
|
+
} else {
|
|
9868
|
+
const result = await resolveProxyConfig(
|
|
9869
|
+
platform2,
|
|
9870
|
+
target,
|
|
9871
|
+
connectHeaders,
|
|
9872
|
+
hiddenValues,
|
|
9873
|
+
version
|
|
9874
|
+
);
|
|
9875
|
+
if (typeof result === "string") return { error: result };
|
|
9876
|
+
connectOpts = result;
|
|
9877
|
+
}
|
|
9878
|
+
if (connectOpts.headers) {
|
|
9879
|
+
for (const v of Object.values(connectOpts.headers)) {
|
|
9880
|
+
if (v) hiddenValues.add(v);
|
|
9881
|
+
}
|
|
9882
|
+
}
|
|
9883
|
+
return { connectOpts, hiddenValues };
|
|
9884
|
+
}
|
|
9885
|
+
async function runProxyServe(platform2, options, version, resolvedBearer) {
|
|
9886
|
+
const built = await buildProxyConnectOpts(platform2, options, version);
|
|
9887
|
+
if ("error" in built) {
|
|
9888
|
+
await platform2.write(2, `mcpcat serve --proxy: ${built.error}
|
|
9889
|
+
`);
|
|
9890
|
+
return 2;
|
|
9891
|
+
}
|
|
9892
|
+
const { connectOpts, hiddenValues } = built;
|
|
9893
|
+
if (resolvedBearer) hiddenValues.add(resolvedBearer);
|
|
9894
|
+
const proxy = createProxyHandler({
|
|
9895
|
+
target: options.proxy,
|
|
9896
|
+
connectOpts,
|
|
9897
|
+
platform: platform2,
|
|
9898
|
+
hiddenHeaders: hiddenValues
|
|
9899
|
+
});
|
|
9900
|
+
if (options.transport === "stdio") {
|
|
9901
|
+
return runProxyStdio(platform2, proxy);
|
|
9902
|
+
}
|
|
9903
|
+
return runProxyHttp(platform2, options, proxy, resolvedBearer);
|
|
9904
|
+
}
|
|
9905
|
+
async function runProxyStdio(platform2, proxy) {
|
|
9906
|
+
const reader = platform2.stdin.getReader();
|
|
9907
|
+
let buffer = "";
|
|
9908
|
+
const dec = new TextDecoder();
|
|
9909
|
+
const enc = new TextEncoder();
|
|
9910
|
+
try {
|
|
9911
|
+
for (; ; ) {
|
|
9912
|
+
const { done, value } = await reader.read();
|
|
9913
|
+
if (done) break;
|
|
9914
|
+
buffer += dec.decode(value, { stream: true });
|
|
9915
|
+
let nlIdx;
|
|
9916
|
+
while ((nlIdx = buffer.indexOf("\n")) >= 0) {
|
|
9917
|
+
const line = buffer.slice(0, nlIdx).trim();
|
|
9918
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
9919
|
+
if (!line) continue;
|
|
9920
|
+
const outcome = await proxy.handle(line);
|
|
9921
|
+
if (outcome.status === 202) continue;
|
|
9922
|
+
const writer = platform2.stdout.getWriter();
|
|
9923
|
+
await writer.write(enc.encode(`${outcome.body}
|
|
9924
|
+
`));
|
|
9925
|
+
writer.releaseLock();
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
} finally {
|
|
9929
|
+
reader.releaseLock();
|
|
9930
|
+
await proxy.shutdown();
|
|
9931
|
+
}
|
|
9932
|
+
return 0;
|
|
9933
|
+
}
|
|
9934
|
+
async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
|
|
9935
|
+
if (!platform2.listen) {
|
|
9936
|
+
await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
|
|
9937
|
+
return 3;
|
|
9938
|
+
}
|
|
9939
|
+
const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
|
|
9940
|
+
if (!boundLoopback && !resolvedBearer) {
|
|
9941
|
+
await platform2.write(
|
|
9942
|
+
2,
|
|
9943
|
+
"mcpcat serve --proxy: --bearer is required when binding off loopback\n"
|
|
9944
|
+
);
|
|
9945
|
+
return 2;
|
|
9946
|
+
}
|
|
9947
|
+
const allowedOrigins = new Set(options.allowOrigins);
|
|
9948
|
+
const listener = await platform2.listen({
|
|
9949
|
+
host: options.host,
|
|
9950
|
+
port: options.port
|
|
9951
|
+
});
|
|
9952
|
+
const actualPort = listener.port;
|
|
9953
|
+
const listenUrl = `http://${options.host}:${actualPort}/mcp`;
|
|
9954
|
+
if (options.json) {
|
|
9955
|
+
await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
|
|
9956
|
+
`);
|
|
9957
|
+
}
|
|
9958
|
+
await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
|
|
9959
|
+
`);
|
|
9960
|
+
if (options.onListening) {
|
|
9961
|
+
await options.onListening(actualPort);
|
|
9962
|
+
}
|
|
9963
|
+
let shutdownRequested = false;
|
|
9964
|
+
const shutdown = () => {
|
|
9965
|
+
if (shutdownRequested) return;
|
|
9966
|
+
shutdownRequested = true;
|
|
9967
|
+
listener.close();
|
|
9968
|
+
void proxy.shutdown();
|
|
9969
|
+
};
|
|
9970
|
+
if (platform2.onSignal) {
|
|
9971
|
+
platform2.onSignal("SIGINT", shutdown);
|
|
9972
|
+
platform2.onSignal("SIGTERM", shutdown);
|
|
9973
|
+
}
|
|
9974
|
+
const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
|
|
9975
|
+
try {
|
|
9976
|
+
for await (const connection of listener) {
|
|
9977
|
+
if (shutdownRequested) break;
|
|
9978
|
+
const conn = connection;
|
|
9979
|
+
handleConnection(conn.readable, conn.writable, handler).catch(() => {
|
|
9980
|
+
}).finally(() => {
|
|
9981
|
+
try {
|
|
9982
|
+
conn.close();
|
|
9983
|
+
} catch {
|
|
9984
|
+
}
|
|
9985
|
+
});
|
|
9986
|
+
}
|
|
9987
|
+
} catch {
|
|
9988
|
+
if (!shutdownRequested) throw new Error("listener closed unexpectedly");
|
|
9989
|
+
}
|
|
9990
|
+
return 0;
|
|
9991
|
+
}
|
|
9992
|
+
async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer) {
|
|
9993
|
+
if (resolvedBearer) {
|
|
9994
|
+
const authHeader = request.headers.authorization;
|
|
9995
|
+
const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
9996
|
+
if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
|
|
9997
|
+
return {
|
|
9998
|
+
status: 401,
|
|
9999
|
+
reason: "Unauthorized",
|
|
10000
|
+
headers: { "content-type": "text/plain" },
|
|
10001
|
+
body: "Unauthorized"
|
|
10002
|
+
};
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
|
|
10006
|
+
if (!originCheck.ok) {
|
|
10007
|
+
return {
|
|
10008
|
+
status: 403,
|
|
10009
|
+
reason: "Forbidden",
|
|
10010
|
+
headers: { "content-type": "text/plain" },
|
|
10011
|
+
body: originCheck.reason ?? "Forbidden"
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
10014
|
+
const hostCheck = checkHost(request.headers.host, boundLoopback);
|
|
10015
|
+
if (!hostCheck.ok) {
|
|
10016
|
+
return {
|
|
10017
|
+
status: 403,
|
|
10018
|
+
reason: "Forbidden",
|
|
10019
|
+
headers: { "content-type": "text/plain" },
|
|
10020
|
+
body: hostCheck.reason ?? "Forbidden"
|
|
10021
|
+
};
|
|
10022
|
+
}
|
|
10023
|
+
if (request.target !== "/mcp") {
|
|
10024
|
+
return {
|
|
10025
|
+
status: 404,
|
|
10026
|
+
reason: "Not Found",
|
|
10027
|
+
headers: { "content-type": "text/plain" },
|
|
10028
|
+
body: "Not Found"
|
|
10029
|
+
};
|
|
10030
|
+
}
|
|
10031
|
+
if (request.method !== "POST") {
|
|
10032
|
+
return {
|
|
10033
|
+
status: 405,
|
|
10034
|
+
reason: "Method Not Allowed",
|
|
10035
|
+
headers: { "content-type": "text/plain", allow: "POST" },
|
|
10036
|
+
body: "Method Not Allowed"
|
|
10037
|
+
};
|
|
10038
|
+
}
|
|
10039
|
+
const outcome = await proxy.handle(request.body);
|
|
10040
|
+
return {
|
|
10041
|
+
status: outcome.status,
|
|
10042
|
+
reason: outcome.status === 200 ? "OK" : "Accepted",
|
|
10043
|
+
headers: { "content-type": outcome.contentType },
|
|
10044
|
+
body: outcome.body
|
|
10045
|
+
};
|
|
10046
|
+
}
|
|
9575
10047
|
async function runStdioServer(platform2, tools, call, serverName, version) {
|
|
9576
10048
|
const reader = platform2.stdin.getReader();
|
|
9577
10049
|
let buffer = "";
|
|
@@ -10078,6 +10550,14 @@ function parseAddFlagAt(flagArgs, i, flags) {
|
|
|
10078
10550
|
flags.noProbe = true;
|
|
10079
10551
|
return { next: i + 1 };
|
|
10080
10552
|
}
|
|
10553
|
+
if (arg === "--bridge") {
|
|
10554
|
+
flags.bridge = true;
|
|
10555
|
+
return { next: i + 1 };
|
|
10556
|
+
}
|
|
10557
|
+
if (arg === "--bearer" && i + 1 < flagArgs.length) {
|
|
10558
|
+
flags.bearer = flagArgs[i + 1];
|
|
10559
|
+
return { next: i + 2 };
|
|
10560
|
+
}
|
|
10081
10561
|
if (arg === "--config" && i + 1 < flagArgs.length) {
|
|
10082
10562
|
flags.configFile = flagArgs[i + 1];
|
|
10083
10563
|
return { next: i + 2 };
|
|
@@ -10114,6 +10594,7 @@ function parseAddFlags(args) {
|
|
|
10114
10594
|
const flags = {
|
|
10115
10595
|
local: false,
|
|
10116
10596
|
noProbe: false,
|
|
10597
|
+
bridge: false,
|
|
10117
10598
|
headers: [],
|
|
10118
10599
|
envPairs: [],
|
|
10119
10600
|
positional: [],
|
|
@@ -10476,6 +10957,54 @@ async function runImport(platform2, args) {
|
|
|
10476
10957
|
}
|
|
10477
10958
|
return 0;
|
|
10478
10959
|
}
|
|
10960
|
+
async function runBridge(platform2, args, version) {
|
|
10961
|
+
if (platform2.kind === "host") {
|
|
10962
|
+
await platform2.write(
|
|
10963
|
+
2,
|
|
10964
|
+
"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"
|
|
10965
|
+
);
|
|
10966
|
+
return 2;
|
|
10967
|
+
}
|
|
10968
|
+
const flags = parseAddFlags(args);
|
|
10969
|
+
if (typeof flags === "string") {
|
|
10970
|
+
await platform2.write(2, `mcpcat add --bridge: ${flags}
|
|
10971
|
+
`);
|
|
10972
|
+
return 2;
|
|
10973
|
+
}
|
|
10974
|
+
const name = flags.positional[0];
|
|
10975
|
+
if (!name) {
|
|
10976
|
+
await platform2.write(2, "mcpcat add --bridge: requires a server name\n");
|
|
10977
|
+
return 2;
|
|
10978
|
+
}
|
|
10979
|
+
if (flags.cmdArgs.length === 0) {
|
|
10980
|
+
await platform2.write(2, "mcpcat add --bridge: requires -- <command> [args...]\n");
|
|
10981
|
+
return 2;
|
|
10982
|
+
}
|
|
10983
|
+
if (flags.bearer) {
|
|
10984
|
+
await platform2.write(
|
|
10985
|
+
2,
|
|
10986
|
+
"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"
|
|
10987
|
+
);
|
|
10988
|
+
return 2;
|
|
10989
|
+
}
|
|
10990
|
+
const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
|
|
10991
|
+
serveOptions.proxyCommand = flags.cmdArgs;
|
|
10992
|
+
const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
|
|
10993
|
+
serveOptions.onListening = async (actualPort) => {
|
|
10994
|
+
const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
|
|
10995
|
+
const httpConfig = { url: bridgeUrl };
|
|
10996
|
+
if (flags.description) httpConfig.description = flags.description;
|
|
10997
|
+
if (flags.exposure) httpConfig.exposure = flags.exposure;
|
|
10998
|
+
const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
|
|
10999
|
+
await platform2.write(
|
|
11000
|
+
1,
|
|
11001
|
+
`${replaced ? "replaced" : "added"} ${name} in ${path}
|
|
11002
|
+
bridge does not survive a reload; runs in the foreground (start with &)
|
|
11003
|
+
`
|
|
11004
|
+
);
|
|
11005
|
+
};
|
|
11006
|
+
return runServe(platform2, serveOptions, version);
|
|
11007
|
+
}
|
|
10479
11008
|
|
|
10480
11009
|
// src/cli/main.ts
|
|
10481
11010
|
var VERBS = [
|
|
@@ -10540,6 +11069,9 @@ Client verbs:
|
|
|
10540
11069
|
|
|
10541
11070
|
Configuration verbs:
|
|
10542
11071
|
add <name> <url> Add a server to mcp.json
|
|
11072
|
+
add <name> --bridge --
|
|
11073
|
+
Bridge a stdio server to HTTP (kernel only,
|
|
11074
|
+
runs in foreground, does not survive reload)
|
|
10543
11075
|
rm <name> Remove a server
|
|
10544
11076
|
list List configured servers
|
|
10545
11077
|
enable|disable <name> Toggle a server
|
|
@@ -10549,7 +11081,7 @@ Configuration verbs:
|
|
|
10549
11081
|
logout <target> Remove tokens (not implemented yet)
|
|
10550
11082
|
|
|
10551
11083
|
Server and code verbs:
|
|
10552
|
-
serve [--stdio|--http] \u2026 Expose commands as MCP server
|
|
11084
|
+
serve [--stdio|--http] \u2026 Expose commands or proxy upstream as MCP server
|
|
10553
11085
|
code [-e src|file.js] Run a codemode script
|
|
10554
11086
|
grep <pattern> [target\u2026] Search tools by name or description
|
|
10555
11087
|
discover <url> Find MCP servers at a URL
|
|
@@ -10902,10 +11434,11 @@ async function probeServer(_name, config, platform2) {
|
|
|
10902
11434
|
return err.message;
|
|
10903
11435
|
}
|
|
10904
11436
|
}
|
|
10905
|
-
async function dispatchConfigVerb(platform2, verb, args) {
|
|
11437
|
+
async function dispatchConfigVerb(platform2, verb, args, version) {
|
|
10906
11438
|
const rest = args.slice(1);
|
|
10907
11439
|
switch (verb) {
|
|
10908
11440
|
case "add":
|
|
11441
|
+
if (rest.includes("--bridge")) return runBridge(platform2, rest, version);
|
|
10909
11442
|
return runAdd(platform2, rest, probeServer);
|
|
10910
11443
|
case "rm":
|
|
10911
11444
|
return runRm(platform2, rest);
|
|
@@ -10948,7 +11481,7 @@ async function dispatchDiscoveryVerb(platform2, parsed, args) {
|
|
|
10948
11481
|
async function dispatchServeVerb(platform2, args, version) {
|
|
10949
11482
|
const serveArgs = args.slice(args.indexOf("serve") + 1);
|
|
10950
11483
|
const serveOptions = parseServeFlags(serveArgs);
|
|
10951
|
-
if (serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
11484
|
+
if (!serveOptions.proxy && serveOptions.commands.length === 0 && serveOptions.toolTemplates.length === 0) {
|
|
10952
11485
|
await platform2.write(2, "mcpcat serve: no commands or --tool templates given\n");
|
|
10953
11486
|
return 2;
|
|
10954
11487
|
}
|
|
@@ -11112,7 +11645,7 @@ async function run(platform2, version, args) {
|
|
|
11112
11645
|
if (parsed.verb in CONFIG_VERBS) {
|
|
11113
11646
|
const disc = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
11114
11647
|
if (disc !== void 0) return disc;
|
|
11115
|
-
return dispatchConfigVerb(platform2, parsed.verb, args);
|
|
11648
|
+
return dispatchConfigVerb(platform2, parsed.verb, args, version);
|
|
11116
11649
|
}
|
|
11117
11650
|
const discResult = await dispatchDiscoveryVerb(platform2, parsed, args);
|
|
11118
11651
|
if (discResult !== void 0) return discResult;
|
|
@@ -11272,6 +11805,7 @@ async function kernelListen(ctx, options) {
|
|
|
11272
11805
|
function createKernelPlatform(ctx, options) {
|
|
11273
11806
|
const encoder2 = new TextEncoder();
|
|
11274
11807
|
return {
|
|
11808
|
+
kind: "kernel",
|
|
11275
11809
|
fetch: ctx.fetch,
|
|
11276
11810
|
spawn: (options2) => kernelSpawn(ctx, options2),
|
|
11277
11811
|
listen: (options2) => kernelListen(ctx, options2),
|