@iicp/client 0.7.51 → 0.7.56
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/README.md +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +239 -17
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +60 -1
- package/dist/client.js.map +1 -1
- package/dist/identity.d.ts +7 -0
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +20 -0
- package/dist/identity.js.map +1 -1
- package/dist/node.d.ts +20 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +0 -0
- package/dist/node.js.map +1 -1
- package/dist/relay_session.d.ts +58 -2
- package/dist/relay_session.d.ts.map +1 -1
- package/dist/relay_session.js +140 -3
- package/dist/relay_session.js.map +1 -1
- package/dist/relay_worker_client.d.ts +3 -1
- package/dist/relay_worker_client.d.ts.map +1 -1
- package/dist/relay_worker_client.js +5 -1
- package/dist/relay_worker_client.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -317,6 +317,8 @@ const node = new IicpNode({
|
|
|
317
317
|
});
|
|
318
318
|
```
|
|
319
319
|
|
|
320
|
+
> **Security note**: relay bind authentication is pending ([#510](https://github.com/RobLe3/iicp.network/issues/510)) — only run a relay accept port on networks you trust until the signed-bind mechanism ships.
|
|
321
|
+
|
|
320
322
|
### Opt-out / override
|
|
321
323
|
|
|
322
324
|
```bash
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAyCA,OAAO,EAeL,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAGvB,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iGAAiG;IACjG,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAuaD,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,GAAG,SAAS,CAsB9E;AA+pBD;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CA+D5D;AA8dD,wBAAsB,IAAI,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlF"}
|
package/dist/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ const node_crypto_1 = require("node:crypto");
|
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
27
|
const SDK_VERSION = require("../package.json").version;
|
|
28
28
|
const net = require("node:net");
|
|
29
|
+
const http = require("node:http");
|
|
29
30
|
const node_child_process_1 = require("node:child_process");
|
|
30
31
|
const readline = require("node:readline/promises");
|
|
31
32
|
const node_process_1 = require("node:process");
|
|
@@ -64,6 +65,7 @@ function printHelp() {
|
|
|
64
65
|
` query <prompt> Discover mesh nodes and submit a chat task\n` +
|
|
65
66
|
` credits Show this node's earned / spent / balance credits\n` +
|
|
66
67
|
` proxy Run the local OpenAI/Ollama/Anthropic-compat gateway (loopback; no registration)\n` +
|
|
68
|
+
` mcp-gateway Bridge a local MCP server as an IICP provider node (registers + serves)\n` +
|
|
67
69
|
` operator rename <name> Change your public display_name (signed by your operator key)\n` +
|
|
68
70
|
` operator encrypt Password-encrypt the operator secret at rest ($IICP_OPERATOR_PASSPHRASE)\n` +
|
|
69
71
|
` operator decrypt Remove at-rest encryption of the operator secret\n\n` +
|
|
@@ -90,7 +92,9 @@ function printHelp() {
|
|
|
90
92
|
` --external-ip-probe-url U IICP_EXTERNAL_IP_PROBE_URL — fallback IPv4 probe\n` +
|
|
91
93
|
` --relay-worker-endpoint H IICP_RELAY_WORKER_ENDPOINT — <host>:<port> of a relay node (R2 last-resort)\n` +
|
|
92
94
|
` --relay-capable IICP_RELAY_CAPABLE — advertise as relay server for CGNAT/tier-4 operators\n` +
|
|
93
|
-
` --relay-accept-port N IICP_RELAY_ACCEPT_PORT — TCP port for relay accept server (default 9485)
|
|
95
|
+
` --relay-accept-port N IICP_RELAY_ACCEPT_PORT — TCP port for relay accept server (default 9485).\n` +
|
|
96
|
+
` Note: relay bind authentication is pending (#510) — only run a relay\n` +
|
|
97
|
+
` accept port on networks you trust until the signed-bind mechanism ships.\n` +
|
|
94
98
|
` --log-dir DIR IICP_LOG_DIR — directory for persistent log files (<node_id>.log + events.jsonl)\n` +
|
|
95
99
|
` --with-proxy IICP_WITH_PROXY — also run the loopback compat proxy (127.0.0.1:9483) in-process\n\n` +
|
|
96
100
|
`query optional:\n` +
|
|
@@ -619,6 +623,12 @@ async function runServe(opts) {
|
|
|
619
623
|
let _opDisplayName;
|
|
620
624
|
let _opCreatedAt;
|
|
621
625
|
let _opIntegrityHash;
|
|
626
|
+
const _identityNotice = (0, identity_js_1.noIdentityNotice)(_op);
|
|
627
|
+
if (_identityNotice !== null) {
|
|
628
|
+
// #503 — anonymous registration accrues no founder/recognition standing;
|
|
629
|
+
// say so loudly instead of silently excluding the operator. Non-fatal.
|
|
630
|
+
process.stderr.write(_identityNotice + "\n");
|
|
631
|
+
}
|
|
622
632
|
if (_op && (0, identity_js_1.operatorIsKeyBacked)(_op)) {
|
|
623
633
|
_opDelegation = (0, delegation_js_1.issueDelegation)((0, identity_js_1.operatorSigningKey)(_op), nodeId);
|
|
624
634
|
_opDisplayName = _op.display_name || undefined;
|
|
@@ -1206,13 +1216,22 @@ async function runCredits(argv) {
|
|
|
1206
1216
|
// Multiple nodes have tokens — show all of them.
|
|
1207
1217
|
const dir = directoryUrl ?? process.env["IICP_DIRECTORY_URL"] ?? "https://iicp.network/api";
|
|
1208
1218
|
process.stderr.write(`[iicp-node] no --node given — showing credits for all ${withToken.length} nodes:\n`);
|
|
1219
|
+
// One node failing must not hide the others — show every node,
|
|
1220
|
+
// then exit non-zero if any failed (2026-06-11).
|
|
1221
|
+
let failed = 0;
|
|
1209
1222
|
for (let i = 0; i < withToken.length; i++) {
|
|
1210
1223
|
if (i > 0)
|
|
1211
1224
|
process.stdout.write("\n");
|
|
1212
1225
|
const n = withToken[i];
|
|
1213
1226
|
const rc = await fetchAndDisplayCredits(n.directory_url ?? dir, n.node_id, n.node_token, n.name, Boolean(values["json"]), Boolean(values["verify"]));
|
|
1214
|
-
if (rc !== 0)
|
|
1215
|
-
|
|
1227
|
+
if (rc !== 0) {
|
|
1228
|
+
process.stderr.write(`ERROR: credits fetch failed for node '${n.name}' — continuing with remaining nodes\n`);
|
|
1229
|
+
failed++;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (failed > 0) {
|
|
1233
|
+
process.stderr.write(`ERROR: ${failed}/${withToken.length} node(s) failed\n`);
|
|
1234
|
+
return 1;
|
|
1216
1235
|
}
|
|
1217
1236
|
return 0;
|
|
1218
1237
|
}
|
|
@@ -1252,23 +1271,43 @@ async function runCredits(argv) {
|
|
|
1252
1271
|
/** Shared fetch+display logic for one node's credits summary. */
|
|
1253
1272
|
async function fetchAndDisplayCredits(directoryUrl, nodeId, token, label, asJson, verify) {
|
|
1254
1273
|
const url = `${directoryUrl.replace(/\/+$/, "")}/v1/credits/summary?node_id=${encodeURIComponent(nodeId)}`;
|
|
1274
|
+
// Transient failures (network error, 5xx, undecodable body) get ONE retry
|
|
1275
|
+
// after a short pause — shared-hosting blips and deploy windows otherwise
|
|
1276
|
+
// surface as one-shot CLI errors (observed 2026-06-11).
|
|
1255
1277
|
let resp;
|
|
1256
|
-
try {
|
|
1257
|
-
resp = await fetch(url, {
|
|
1258
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
1259
|
-
signal: AbortSignal.timeout(15000),
|
|
1260
|
-
});
|
|
1261
|
-
}
|
|
1262
|
-
catch (e) {
|
|
1263
|
-
process.stderr.write(`ERROR: request failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
1264
|
-
return 1;
|
|
1265
|
-
}
|
|
1266
1278
|
let body;
|
|
1267
|
-
|
|
1268
|
-
|
|
1279
|
+
let lastErr = "";
|
|
1280
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
1281
|
+
resp = undefined;
|
|
1282
|
+
body = undefined;
|
|
1283
|
+
try {
|
|
1284
|
+
resp = await fetch(url, {
|
|
1285
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1286
|
+
signal: AbortSignal.timeout(15000),
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
catch (e) {
|
|
1290
|
+
lastErr = `request failed: ${e instanceof Error ? e.message : String(e)}`;
|
|
1291
|
+
}
|
|
1292
|
+
if (resp) {
|
|
1293
|
+
try {
|
|
1294
|
+
body = (await resp.json());
|
|
1295
|
+
}
|
|
1296
|
+
catch {
|
|
1297
|
+
lastErr = `bad response (HTTP ${resp.status})`;
|
|
1298
|
+
}
|
|
1299
|
+
if (body !== undefined && resp.status < 500)
|
|
1300
|
+
break; // success or definitive 4xx
|
|
1301
|
+
if (body !== undefined) {
|
|
1302
|
+
const err = body["error"];
|
|
1303
|
+
lastErr = `HTTP ${resp.status}: ${err?.message ?? "request rejected"}`;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (attempt === 1)
|
|
1307
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
1269
1308
|
}
|
|
1270
|
-
|
|
1271
|
-
process.stderr.write(`ERROR:
|
|
1309
|
+
if (!resp || body === undefined || resp.status >= 500) {
|
|
1310
|
+
process.stderr.write(`ERROR: ${lastErr}\n`);
|
|
1272
1311
|
return 1;
|
|
1273
1312
|
}
|
|
1274
1313
|
if (!resp.ok) {
|
|
@@ -1556,6 +1595,187 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1556
1595
|
throw exc;
|
|
1557
1596
|
}
|
|
1558
1597
|
}
|
|
1598
|
+
// ── mcp-gateway (#512) ───────────────────────────────────────────────────────
|
|
1599
|
+
const _MCP_DANGEROUS = new Set(["bash", "shell", "exec", "run_command", "eval"]);
|
|
1600
|
+
function _toolToIntent(name) {
|
|
1601
|
+
const safe = name.toLowerCase().replace(/[^a-z0-9_]/g, "_");
|
|
1602
|
+
return `urn:iicp:intent:mcp:${safe}:v1`;
|
|
1603
|
+
}
|
|
1604
|
+
async function runMcpGateway(argv) {
|
|
1605
|
+
const helpFlag = argv.includes("--help") || argv.includes("-h");
|
|
1606
|
+
if (helpFlag) {
|
|
1607
|
+
process.stdout.write(`usage: iicp-node mcp-gateway [options]\n\n` +
|
|
1608
|
+
`Bridge a local MCP server into the IICP mesh as a registered provider node.\n\n` +
|
|
1609
|
+
`Options:\n` +
|
|
1610
|
+
` --mcp-url URL IICP_MCP_URL — MCP server base URL (default http://localhost:8001)\n` +
|
|
1611
|
+
` --tools NAMES IICP_MCP_TOOLS — comma-separated tool names to advertise (required)\n` +
|
|
1612
|
+
` --node-id ID IICP_NODE_ID — auto-generated if absent\n` +
|
|
1613
|
+
` --public-endpoint U IICP_PUBLIC_ENDPOINT — externally reachable URL of this gateway\n` +
|
|
1614
|
+
` --directory-url URL IICP_DIRECTORY_URL (default https://iicp.network/api/v1)\n` +
|
|
1615
|
+
` --region REGION IICP_REGION (default local)\n` +
|
|
1616
|
+
` --port N IICP_PORT (default 9484)\n` +
|
|
1617
|
+
` --host HOST IICP_HOST (default ::)\n`);
|
|
1618
|
+
return 0;
|
|
1619
|
+
}
|
|
1620
|
+
const { values } = safeParseArgs({
|
|
1621
|
+
args: argv,
|
|
1622
|
+
options: {
|
|
1623
|
+
"mcp-url": { type: "string" },
|
|
1624
|
+
tools: { type: "string" },
|
|
1625
|
+
"node-id": { type: "string" },
|
|
1626
|
+
"public-endpoint": { type: "string" },
|
|
1627
|
+
"directory-url": { type: "string" },
|
|
1628
|
+
region: { type: "string" },
|
|
1629
|
+
port: { type: "string" },
|
|
1630
|
+
host: { type: "string" },
|
|
1631
|
+
},
|
|
1632
|
+
allowPositionals: false,
|
|
1633
|
+
});
|
|
1634
|
+
const mcpUrl = (values["mcp-url"] ?? envOr("IICP_MCP_URL") ?? "http://localhost:8001").replace(/\/$/, "");
|
|
1635
|
+
const rawTools = (values["tools"] ?? envOr("IICP_MCP_TOOLS") ?? "")
|
|
1636
|
+
.split(",").map((t) => t.trim()).filter(Boolean);
|
|
1637
|
+
const activeTools = rawTools.filter((t) => !_MCP_DANGEROUS.has(t.toLowerCase()));
|
|
1638
|
+
if (activeTools.length === 0) {
|
|
1639
|
+
process.stderr.write(`ERROR: --tools is required. Provide a comma-separated list of MCP tool names.\n` +
|
|
1640
|
+
` Example: iicp-node mcp-gateway --tools read_file,list_dir --mcp-url http://localhost:8001\n`);
|
|
1641
|
+
return 2;
|
|
1642
|
+
}
|
|
1643
|
+
const nodeId = values["node-id"] ?? envOr("IICP_NODE_ID") ?? `gateway-mcp-${(0, node_crypto_1.randomBytes)(4).toString("hex")}`;
|
|
1644
|
+
const directoryUrl = (values["directory-url"] ?? envOr("IICP_DIRECTORY_URL") ?? "https://iicp.network/api/v1").replace(/\/$/, "");
|
|
1645
|
+
const region = values["region"] ?? envOr("IICP_REGION") ?? "local";
|
|
1646
|
+
const port = parsePort(values["port"], 9484);
|
|
1647
|
+
const host = values["host"] ?? envOr("IICP_HOST") ?? "::";
|
|
1648
|
+
const publicEndpoint = values["public-endpoint"] ?? envOr("IICP_PUBLIC_ENDPOINT") ?? `http://localhost:${port}`;
|
|
1649
|
+
const intents = activeTools.map(_toolToIntent);
|
|
1650
|
+
let nodeToken = envOr("IICP_NODE_TOKEN") ?? "";
|
|
1651
|
+
async function doRegister() {
|
|
1652
|
+
const payload = { node_id: nodeId, region, endpoint: publicEndpoint, intents, mcp_tools: activeTools, protocol_version: "1.0" };
|
|
1653
|
+
const headers = { "Content-Type": "application/json" };
|
|
1654
|
+
if (nodeToken)
|
|
1655
|
+
headers["Authorization"] = `Bearer ${nodeToken}`;
|
|
1656
|
+
const resp = await fetch(`${directoryUrl}/register`, { method: "POST", headers, body: JSON.stringify(payload), signal: AbortSignal.timeout(10_000) });
|
|
1657
|
+
if (!resp.ok)
|
|
1658
|
+
throw new Error(`register ${resp.status}`);
|
|
1659
|
+
const data = await resp.json();
|
|
1660
|
+
return data["node_token"] ?? nodeToken;
|
|
1661
|
+
}
|
|
1662
|
+
async function doHeartbeat() {
|
|
1663
|
+
const payload = { node_id: nodeId, intents, load: 0.0, status: "active" };
|
|
1664
|
+
try {
|
|
1665
|
+
await fetch(`${directoryUrl}/heartbeat`, {
|
|
1666
|
+
method: "POST",
|
|
1667
|
+
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${nodeToken}` },
|
|
1668
|
+
body: JSON.stringify(payload),
|
|
1669
|
+
signal: AbortSignal.timeout(10_000),
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
catch {
|
|
1673
|
+
// heartbeat failures are non-fatal
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
let mcpRpcId = 0;
|
|
1677
|
+
async function callMcp(toolName, args) {
|
|
1678
|
+
mcpRpcId += 1;
|
|
1679
|
+
const rpc = { jsonrpc: "2.0", id: mcpRpcId, method: "tools/call", params: { name: toolName, arguments: args } };
|
|
1680
|
+
const resp = await fetch(`${mcpUrl}/mcp`, {
|
|
1681
|
+
method: "POST",
|
|
1682
|
+
headers: { "Content-Type": "application/json" },
|
|
1683
|
+
body: JSON.stringify(rpc),
|
|
1684
|
+
signal: AbortSignal.timeout(30_000),
|
|
1685
|
+
});
|
|
1686
|
+
if (!resp.ok)
|
|
1687
|
+
throw new Error(`MCP server unreachable: ${resp.status}`);
|
|
1688
|
+
const data = await resp.json();
|
|
1689
|
+
if (data["error"])
|
|
1690
|
+
throw new Error(data["error"]["message"] ?? "MCP error");
|
|
1691
|
+
return data["result"];
|
|
1692
|
+
}
|
|
1693
|
+
try {
|
|
1694
|
+
nodeToken = await doRegister();
|
|
1695
|
+
process.stdout.write(`iicp-node mcp-gateway registered as '${nodeId}' with ${activeTools.length} tool(s): ${activeTools.join(", ")}\n` +
|
|
1696
|
+
` IICP endpoint: ${publicEndpoint}\n MCP server: ${mcpUrl}\n`);
|
|
1697
|
+
}
|
|
1698
|
+
catch (err) {
|
|
1699
|
+
process.stderr.write(`WARNING: directory registration failed (${err.message}) — running without listing\n`);
|
|
1700
|
+
}
|
|
1701
|
+
const hbInterval = setInterval(() => { void doHeartbeat(); }, 30_000);
|
|
1702
|
+
const server = http.createServer(async (req, res) => {
|
|
1703
|
+
if (req.method === "GET" && req.url === "/iicp/health") {
|
|
1704
|
+
const body = JSON.stringify({ status: "ok", node_id: nodeId, active_tools: activeTools, mcp_server: mcpUrl, timestamp: Math.floor(Date.now() / 1000) });
|
|
1705
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1706
|
+
res.end(body);
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
if (req.method === "POST" && req.url === "/v1/task") {
|
|
1710
|
+
const auth = req.headers["authorization"] ?? "";
|
|
1711
|
+
if (!nodeToken || auth !== `Bearer ${nodeToken}`) {
|
|
1712
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
1713
|
+
res.end(JSON.stringify({ error: "Unauthorized" }));
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const chunks = [];
|
|
1717
|
+
for await (const chunk of req)
|
|
1718
|
+
chunks.push(chunk);
|
|
1719
|
+
let body;
|
|
1720
|
+
try {
|
|
1721
|
+
body = JSON.parse(Buffer.concat(chunks).toString());
|
|
1722
|
+
}
|
|
1723
|
+
catch {
|
|
1724
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1725
|
+
res.end(JSON.stringify({ error: "invalid JSON" }));
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
const payload = (body["payload"] ?? {});
|
|
1729
|
+
let toolName = payload["tool_name"] ?? "";
|
|
1730
|
+
if (!toolName) {
|
|
1731
|
+
const m = /urn:iicp:intent:mcp:([^:]+):v1/.exec(body["intent"] ?? "");
|
|
1732
|
+
if (m)
|
|
1733
|
+
toolName = m[1];
|
|
1734
|
+
}
|
|
1735
|
+
if (!toolName) {
|
|
1736
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1737
|
+
res.end(JSON.stringify({ error: "Cannot determine tool name" }));
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (_MCP_DANGEROUS.has(toolName.toLowerCase())) {
|
|
1741
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1742
|
+
res.end(JSON.stringify({ error: "Tool not permitted" }));
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
if (activeTools.length && !activeTools.includes(toolName)) {
|
|
1746
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1747
|
+
res.end(JSON.stringify({ error: "Tool not available" }));
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const taskId = body["task_id"] ?? (0, node_crypto_1.randomUUID)();
|
|
1751
|
+
try {
|
|
1752
|
+
const result = await callMcp(toolName, (payload["arguments"] ?? {}));
|
|
1753
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1754
|
+
res.end(JSON.stringify({ task_id: taskId, status: "completed", result }));
|
|
1755
|
+
}
|
|
1756
|
+
catch (err) {
|
|
1757
|
+
const msg = err.message ?? "error";
|
|
1758
|
+
const code = msg.includes("unreachable") ? 502 : 422;
|
|
1759
|
+
res.writeHead(code, { "Content-Type": "application/json" });
|
|
1760
|
+
res.end(JSON.stringify({ error: msg }));
|
|
1761
|
+
}
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1765
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
1766
|
+
});
|
|
1767
|
+
await new Promise((resolve) => {
|
|
1768
|
+
server.listen(port, host === "::" ? "::" : host, () => {
|
|
1769
|
+
process.stdout.write(` Listening on ${host}:${port}\n`);
|
|
1770
|
+
resolve();
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
await new Promise((resolve) => {
|
|
1774
|
+
process.on("SIGINT", () => { clearInterval(hbInterval); server.close(); resolve(); });
|
|
1775
|
+
process.on("SIGTERM", () => { clearInterval(hbInterval); server.close(); resolve(); });
|
|
1776
|
+
});
|
|
1777
|
+
return 0;
|
|
1778
|
+
}
|
|
1559
1779
|
/** Command dispatch — separated so main() can wrap parse failures as clean CliError output. */
|
|
1560
1780
|
async function dispatch(argv) {
|
|
1561
1781
|
const cmd = argv[0];
|
|
@@ -1571,6 +1791,8 @@ async function dispatch(argv) {
|
|
|
1571
1791
|
return runOperator(argv.slice(1));
|
|
1572
1792
|
if (cmd === "proxy")
|
|
1573
1793
|
return runProxyCmd(argv.slice(1));
|
|
1794
|
+
if (cmd === "mcp-gateway")
|
|
1795
|
+
return runMcpGateway(argv.slice(1));
|
|
1574
1796
|
if (cmd !== "serve") {
|
|
1575
1797
|
process.stderr.write(`unknown command: ${cmd}\n`);
|
|
1576
1798
|
printHelp();
|