@clawops/cli 1.7.7 → 1.7.8
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 +27 -0
- package/dist/{server-U7C2RQWL.js → chunk-QQNKBTYZ.js} +92 -19
- package/dist/{chunk-4AJ4F2PJ.js → chunk-RAAONHW3.js} +1 -1
- package/dist/cli.js +53 -25
- package/dist/{generate-PAGV574G.js → generate-ELD7QF5Z.js} +1 -1
- package/dist/mcp-wire-ZMIVFAUC.js +72 -0
- package/dist/{package-DHSANJT6.js → package-QYZFN3FP.js} +2 -1
- package/dist/server-FWTOSSZP.js +32 -0
- package/package.json +2 -1
- package/dist/mcp-wire-FR3W2CB6.js +0 -46
package/README.md
CHANGED
|
@@ -12,6 +12,33 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
## What's new in v1.7.8
|
|
16
|
+
|
|
17
|
+
**`clawops mcp wire` never wired anything.** It wrote `gateway.mcpClients` — a key OpenClaw
|
|
18
|
+
does not have, checked against the config schemas of `2026.4.5`, `2026.7.1-2` and `2026.9.2`.
|
|
19
|
+
The real key is top-level `mcp.servers`. Nothing on this line validated the write, so clawops
|
|
20
|
+
stored a key nothing read, restarted your gateway, and reported success. The entry also used
|
|
21
|
+
stdio with `command: "clawops"`, which spawns inside the gateway container where clawops is
|
|
22
|
+
not installed — so even the right key would not have worked.
|
|
23
|
+
|
|
24
|
+
It delegates to `openclaw mcp add` now, which probes the server before saving: "wired" means
|
|
25
|
+
the gateway connected. You run the server yourself, since clawops is not on the gateway host:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
clawops mcp serve --http 18790 --bind 0.0.0.0 --token "$(openssl rand -hex 16)"
|
|
29
|
+
clawops mcp wire --stack prod --token <same token>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**`clawops mcp serve --http` now requires a token off loopback — this may stop your server.**
|
|
33
|
+
It had no authentication at all while exposing every tool, `clawops_destroy` included. It now
|
|
34
|
+
requires a bearer token and refuses to start when bound off-loopback without one. If you run
|
|
35
|
+
it with `--bind 0.0.0.0`, pass `--token` (or set `CLAWOPS_MCP_TOKEN`) before upgrading.
|
|
36
|
+
|
|
37
|
+
**And it only ever served one client.** One transport for the whole process meant the first
|
|
38
|
+
client to connect claimed it; every later one got `"Server already initialized"`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
15
42
|
## What's new in v1.7.3
|
|
16
43
|
|
|
17
44
|
**README maintenance.** The release-notes section had grown to four versions and was missing
|
|
@@ -2,14 +2,12 @@
|
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
4
|
} from "./chunk-35WVQY3X.js";
|
|
5
|
-
import "./chunk-6ZFIFDBJ.js";
|
|
6
|
-
import "./chunk-BOPSG2LI.js";
|
|
7
5
|
import {
|
|
8
6
|
extractBaseOutputs
|
|
9
7
|
} from "./chunk-3QJBNAHW.js";
|
|
10
8
|
import {
|
|
11
9
|
generatePlan
|
|
12
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-RAAONHW3.js";
|
|
13
11
|
import {
|
|
14
12
|
validatePlan
|
|
15
13
|
} from "./chunk-6X45DDGH.js";
|
|
@@ -37,11 +35,6 @@ import {
|
|
|
37
35
|
import {
|
|
38
36
|
buildContext
|
|
39
37
|
} from "./chunk-P5LKWRPL.js";
|
|
40
|
-
import "./chunk-EI7CQQ7D.js";
|
|
41
|
-
import "./chunk-PVUVI35Y.js";
|
|
42
|
-
import "./chunk-3XK66DKK.js";
|
|
43
|
-
import "./chunk-Q7NQY5HV.js";
|
|
44
|
-
import "./chunk-A2I76FTA.js";
|
|
45
38
|
import {
|
|
46
39
|
getConfig,
|
|
47
40
|
getConfigDir
|
|
@@ -53,7 +46,7 @@ import {
|
|
|
53
46
|
|
|
54
47
|
// src/mcp/server.ts
|
|
55
48
|
import { createServer } from "http";
|
|
56
|
-
import { randomUUID as randomUUID5 } from "crypto";
|
|
49
|
+
import { randomUUID as randomUUID5, timingSafeEqual } from "crypto";
|
|
57
50
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
58
51
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
59
52
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
@@ -1308,27 +1301,58 @@ If none of the above resolves the issue:
|
|
|
1308
1301
|
}
|
|
1309
1302
|
|
|
1310
1303
|
// src/mcp/server.ts
|
|
1304
|
+
var MCP_HTTP_PORT = 18790;
|
|
1305
|
+
var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "::1", "localhost"]);
|
|
1306
|
+
function isLoopbackBind(bind) {
|
|
1307
|
+
return LOOPBACK.has(bind);
|
|
1308
|
+
}
|
|
1309
|
+
function authRequirement(bind, token) {
|
|
1310
|
+
if (isLoopbackBind(bind) || token) return { ok: true };
|
|
1311
|
+
return {
|
|
1312
|
+
ok: false,
|
|
1313
|
+
error: `Refusing to serve MCP on ${bind} without a token. This server exposes every clawops tool, including destructive ones, and has no other authentication. Pass --token <value> (or set CLAWOPS_MCP_TOKEN), or bind to 127.0.0.1 and reach it through an SSH tunnel.`
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function tokenMatches(expected, presented) {
|
|
1317
|
+
const a = Buffer.from(expected);
|
|
1318
|
+
const b = Buffer.from(presented);
|
|
1319
|
+
if (a.length !== b.length) return false;
|
|
1320
|
+
return timingSafeEqual(a, b);
|
|
1321
|
+
}
|
|
1322
|
+
function bearerFrom(header) {
|
|
1323
|
+
if (!header) return void 0;
|
|
1324
|
+
const match = /^Bearer[ ]+(.+)$/i.exec(header.trim());
|
|
1325
|
+
return match?.[1]?.trim();
|
|
1326
|
+
}
|
|
1311
1327
|
async function serveMcp(opts) {
|
|
1312
|
-
const { version } = await import("./package-
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1328
|
+
const { version } = await import("./package-QYZFN3FP.js");
|
|
1329
|
+
const makeServer = () => {
|
|
1330
|
+
const server = new McpServer({ name: "clawops", version });
|
|
1331
|
+
registerTools(server, opts);
|
|
1332
|
+
registerResources(server);
|
|
1333
|
+
registerPrompts(server);
|
|
1334
|
+
return server;
|
|
1335
|
+
};
|
|
1317
1336
|
if (opts.port) {
|
|
1318
1337
|
const bind = opts.bind ?? "127.0.0.1";
|
|
1319
|
-
const
|
|
1338
|
+
const token = opts.token ?? process.env["CLAWOPS_MCP_TOKEN"];
|
|
1339
|
+
const allowed = authRequirement(bind, token);
|
|
1340
|
+
if (!allowed.ok) throw new Error(allowed.error);
|
|
1341
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1320
1342
|
const httpServer = createServer((req, res) => {
|
|
1321
|
-
void
|
|
1343
|
+
void handleHttp(req, res, sessions, makeServer, token);
|
|
1322
1344
|
});
|
|
1323
1345
|
await new Promise((resolve, reject) => {
|
|
1324
1346
|
httpServer.once("error", reject);
|
|
1325
1347
|
httpServer.listen(opts.port, bind, resolve);
|
|
1326
1348
|
});
|
|
1327
|
-
process.stderr.write(
|
|
1328
|
-
`)
|
|
1329
|
-
|
|
1349
|
+
process.stderr.write(
|
|
1350
|
+
`[clawops] MCP HTTP server listening on ${bind}:${opts.port}${token ? " (bearer token required)" : " (loopback, no token)"}
|
|
1351
|
+
`
|
|
1352
|
+
);
|
|
1330
1353
|
await new Promise((resolve) => httpServer.once("close", resolve));
|
|
1331
1354
|
} else {
|
|
1355
|
+
const server = makeServer();
|
|
1332
1356
|
const transport = new StdioServerTransport();
|
|
1333
1357
|
await server.connect(transport);
|
|
1334
1358
|
await new Promise((resolve) => {
|
|
@@ -1336,6 +1360,55 @@ async function serveMcp(opts) {
|
|
|
1336
1360
|
});
|
|
1337
1361
|
}
|
|
1338
1362
|
}
|
|
1363
|
+
async function handleHttp(req, res, sessions, makeServer, token) {
|
|
1364
|
+
if (token) {
|
|
1365
|
+
const presented = bearerFrom(req.headers["authorization"]);
|
|
1366
|
+
if (!presented || !tokenMatches(token, presented)) {
|
|
1367
|
+
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" });
|
|
1368
|
+
res.end(
|
|
1369
|
+
JSON.stringify({
|
|
1370
|
+
jsonrpc: "2.0",
|
|
1371
|
+
error: { code: -32001, message: "Unauthorized" },
|
|
1372
|
+
id: null
|
|
1373
|
+
})
|
|
1374
|
+
);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
1379
|
+
const existing = typeof sessionId === "string" ? sessions.get(sessionId) : void 0;
|
|
1380
|
+
if (existing) {
|
|
1381
|
+
await existing.handleRequest(req, res);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (typeof sessionId === "string") {
|
|
1385
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
1386
|
+
res.end(
|
|
1387
|
+
JSON.stringify({
|
|
1388
|
+
jsonrpc: "2.0",
|
|
1389
|
+
error: { code: -32001, message: "Unknown session" },
|
|
1390
|
+
id: null
|
|
1391
|
+
})
|
|
1392
|
+
);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const transport = new StreamableHTTPServerTransport({
|
|
1396
|
+
sessionIdGenerator: () => randomUUID5(),
|
|
1397
|
+
onsessioninitialized: (id) => {
|
|
1398
|
+
sessions.set(id, transport);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
transport.onclose = () => {
|
|
1402
|
+
if (transport.sessionId) sessions.delete(transport.sessionId);
|
|
1403
|
+
};
|
|
1404
|
+
await makeServer().connect(transport);
|
|
1405
|
+
await transport.handleRequest(req, res);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1339
1408
|
export {
|
|
1409
|
+
MCP_HTTP_PORT,
|
|
1410
|
+
isLoopbackBind,
|
|
1411
|
+
authRequirement,
|
|
1412
|
+
bearerFrom,
|
|
1340
1413
|
serveMcp
|
|
1341
1414
|
};
|
|
@@ -45,7 +45,7 @@ async function generatePlan(intent, _opts) {
|
|
|
45
45
|
"plan/apply is not supported for the local provider. Use `clawops up` directly."
|
|
46
46
|
);
|
|
47
47
|
}
|
|
48
|
-
const { version } = await import("./package-
|
|
48
|
+
const { version } = await import("./package-QYZFN3FP.js");
|
|
49
49
|
const config = getConfig();
|
|
50
50
|
const instanceType = intent.instanceType ?? "small";
|
|
51
51
|
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-VUCNZXQS.js");
|
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
monitor_default,
|
|
4
|
-
validateOpenclawConfig
|
|
5
|
-
} from "./chunk-FNS56UQY.js";
|
|
6
2
|
import {
|
|
7
3
|
MCP_APPS,
|
|
8
4
|
buildMcpEntry,
|
|
9
5
|
writeAppConfigs
|
|
10
6
|
} from "./chunk-OIGTOLB3.js";
|
|
7
|
+
import {
|
|
8
|
+
monitor_default,
|
|
9
|
+
validateOpenclawConfig
|
|
10
|
+
} from "./chunk-FNS56UQY.js";
|
|
11
11
|
import "./chunk-DQDAWNGB.js";
|
|
12
12
|
import {
|
|
13
13
|
IMAGE_INSPECT_CMD,
|
|
@@ -501,7 +501,7 @@ var plan_default = defineCommand5({
|
|
|
501
501
|
},
|
|
502
502
|
async run({ args }) {
|
|
503
503
|
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
504
|
-
const { generatePlan } = await import("./generate-
|
|
504
|
+
const { generatePlan } = await import("./generate-ELD7QF5Z.js");
|
|
505
505
|
const ctx = buildContext(args);
|
|
506
506
|
if (ctx.adapter.name === "local") {
|
|
507
507
|
throw new UsageError(
|
|
@@ -1895,17 +1895,19 @@ var serveCmd = defineCommand17({
|
|
|
1895
1895
|
},
|
|
1896
1896
|
args: {
|
|
1897
1897
|
http: { type: "string", description: "HTTP port for standalone mode" },
|
|
1898
|
-
bind: { type: "string", description: "Bind address for HTTP mode" },
|
|
1898
|
+
bind: { type: "string", description: "Bind address for HTTP mode (default 127.0.0.1)" },
|
|
1899
|
+
token: { type: "string", description: "Bearer token required on every HTTP request. Required unless bound to loopback; also read from CLAWOPS_MCP_TOKEN" },
|
|
1899
1900
|
"read-only": { type: "boolean", description: "Only register read toolset" },
|
|
1900
1901
|
"no-destructive": { type: "boolean", description: "Filter out destructive tools" },
|
|
1901
1902
|
toolsets: { type: "string", description: "Comma-separated toolsets to enable" },
|
|
1902
1903
|
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1903
1904
|
},
|
|
1904
1905
|
async run({ args }) {
|
|
1905
|
-
const { serveMcp } = await import("./server-
|
|
1906
|
+
const { serveMcp } = await import("./server-FWTOSSZP.js");
|
|
1906
1907
|
await serveMcp({
|
|
1907
1908
|
port: args.http ? Number(args.http) : void 0,
|
|
1908
1909
|
bind: args.bind,
|
|
1910
|
+
token: args.token,
|
|
1909
1911
|
readOnly: Boolean(args["read-only"]),
|
|
1910
1912
|
noDestructive: Boolean(args["no-destructive"]),
|
|
1911
1913
|
toolsets: args.toolsets ? args.toolsets.split(",").map((s) => s.trim()) : void 0,
|
|
@@ -1974,13 +1976,16 @@ var wireCmd = defineCommand17({
|
|
|
1974
1976
|
},
|
|
1975
1977
|
args: {
|
|
1976
1978
|
stack: { type: "string", description: "Target stack name" },
|
|
1977
|
-
|
|
1979
|
+
url: { type: "string", description: "Where the gateway should reach clawops (default http://host.docker.internal:18790/)" },
|
|
1980
|
+
token: { type: "string", description: "Bearer token the clawops MCP server requires" },
|
|
1981
|
+
rewire: { type: "boolean", description: "Replace an existing clawops entry" }
|
|
1978
1982
|
},
|
|
1979
1983
|
async run({ args }) {
|
|
1980
1984
|
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
1981
1985
|
const { acquireSession, drainPool } = await import("./pool-QGFQRMGU.js");
|
|
1982
1986
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1983
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
1987
|
+
const { wireGatewayMcp } = await import("./mcp-wire-ZMIVFAUC.js");
|
|
1988
|
+
const { MCP_HTTP_PORT } = await import("./server-FWTOSSZP.js");
|
|
1984
1989
|
const ac = new AbortController();
|
|
1985
1990
|
process19.on("SIGINT", () => {
|
|
1986
1991
|
ac.abort();
|
|
@@ -2021,21 +2026,37 @@ var wireCmd = defineCommand17({
|
|
|
2021
2026
|
const spin = spinner(`Connecting to ${conn.host}...`);
|
|
2022
2027
|
const { session, release } = await acquireSession({ ...conn, signal: ac.signal });
|
|
2023
2028
|
try {
|
|
2024
|
-
spin.text = "
|
|
2025
|
-
const result = await wireGatewayMcp(session, ac.signal, {
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2029
|
+
spin.text = "Asking the gateway to connect to clawops...";
|
|
2030
|
+
const result = await wireGatewayMcp(session, ac.signal, {
|
|
2031
|
+
url: typeof args.url === "string" ? args.url : void 0,
|
|
2032
|
+
token: typeof args.token === "string" ? args.token : void 0,
|
|
2033
|
+
rewire: Boolean(args.rewire)
|
|
2034
|
+
});
|
|
2035
|
+
if (result.status === "unsupported") {
|
|
2036
|
+
spin.fail("This gateway has no `openclaw mcp add`, so clawops cannot wire it.");
|
|
2037
|
+
info("OpenClaw 2026.4.5 ships `openclaw mcp` with only `list` and `serve`.");
|
|
2038
|
+
info("Upgrade the gateway to 2026.7.1-2 or later, then re-run this command.");
|
|
2030
2039
|
process19.exit(1);
|
|
2031
2040
|
}
|
|
2032
|
-
if (result.
|
|
2033
|
-
spin.
|
|
2034
|
-
|
|
2035
|
-
|
|
2041
|
+
if (result.status === "exists") {
|
|
2042
|
+
spin.info("The gateway already has a clawops MCP server configured.");
|
|
2043
|
+
info(`Pointing at ${result.url}. Replace it with: clawops mcp wire --rewire`);
|
|
2044
|
+
return;
|
|
2036
2045
|
}
|
|
2046
|
+
if (result.status === "probe-failed") {
|
|
2047
|
+
spin.fail("The gateway could not connect to clawops. Nothing was changed.");
|
|
2048
|
+
failure(result.error);
|
|
2049
|
+
warn("clawops does not run on the gateway host. Start it where the gateway can reach it:");
|
|
2050
|
+
info(` clawops mcp serve --http ${MCP_HTTP_PORT} --bind 0.0.0.0 --token <token>`);
|
|
2051
|
+
info(`Then re-run: clawops mcp wire --url ${result.url} --token <token>`);
|
|
2052
|
+
process19.exit(1);
|
|
2053
|
+
}
|
|
2054
|
+
spin.succeed(
|
|
2055
|
+
result.rewired ? "Re-wired the gateway to clawops \u2014 previous entry replaced." : "Gateway wired to clawops, and the connection was verified."
|
|
2056
|
+
);
|
|
2037
2057
|
success("The gateway's AI can now run clawops commands.");
|
|
2038
2058
|
info('Try asking it: "check if my stack is healthy"');
|
|
2059
|
+
warn("The MCP server exposes destructive tools. Keep it bound where only the gateway can reach it.");
|
|
2039
2060
|
} catch (err) {
|
|
2040
2061
|
spin.fail("Failed to wire gateway MCP client.");
|
|
2041
2062
|
failure(err instanceof Error ? err.message : String(err));
|
|
@@ -3004,7 +3025,7 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3004
3025
|
if (!wireGateway) return;
|
|
3005
3026
|
const { connect } = await import("./ssh-CNJNLM3E.js");
|
|
3006
3027
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
3007
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
3028
|
+
const { wireGatewayMcp } = await import("./mcp-wire-ZMIVFAUC.js");
|
|
3008
3029
|
const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
|
|
3009
3030
|
const spin = spinner("Wiring gateway MCP client...");
|
|
3010
3031
|
const session = await connect({
|
|
@@ -3017,9 +3038,16 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3017
3038
|
});
|
|
3018
3039
|
try {
|
|
3019
3040
|
const result = await wireGatewayMcp(session, opts.signal ?? AbortSignal.timeout(3e4));
|
|
3020
|
-
if (result.status === "
|
|
3021
|
-
spin.warn(
|
|
3022
|
-
info("
|
|
3041
|
+
if (result.status === "unsupported") {
|
|
3042
|
+
spin.warn("This gateway has no `openclaw mcp add`, so nothing was wired.");
|
|
3043
|
+
info("OpenClaw 2026.4.5 ships `openclaw mcp` with only `list` and `serve`.");
|
|
3044
|
+
info("Upgrade to 2026.7.1-2 and run: clawops mcp wire --stack " + opts.stackName);
|
|
3045
|
+
} else if (result.status === "probe-failed") {
|
|
3046
|
+
spin.warn("The gateway could not reach clawops, so nothing was wired.");
|
|
3047
|
+
info("clawops does not run on the gateway host. Start it where the gateway can reach it,");
|
|
3048
|
+
info("then run: clawops mcp wire --stack " + opts.stackName + " --url <url> --token <token>");
|
|
3049
|
+
} else if (result.status === "exists") {
|
|
3050
|
+
spin.info("The gateway is already wired to clawops.");
|
|
3023
3051
|
} else {
|
|
3024
3052
|
spin.succeed(result.rewired ? "Gateway MCP client re-wired." : "Gateway MCP client wired.");
|
|
3025
3053
|
success("The gateway's AI can now run clawops commands.");
|
|
@@ -3522,7 +3550,7 @@ import { accessSync as accessSync2, constants as constants2 } from "fs";
|
|
|
3522
3550
|
import { execSync as execSync2 } from "child_process";
|
|
3523
3551
|
var ISSUES_URL = `${REPO_URL}/issues/new`;
|
|
3524
3552
|
var BODY_CAP = 2e3;
|
|
3525
|
-
var VERSION = true ? "1.7.
|
|
3553
|
+
var VERSION = true ? "1.7.8" : "dev";
|
|
3526
3554
|
async function collectContext() {
|
|
3527
3555
|
const nodeVersion = process22.version;
|
|
3528
3556
|
const os3 = `${process22.platform} ${process22.arch}`;
|
|
@@ -3762,7 +3790,7 @@ function handleError(err) {
|
|
|
3762
3790
|
}
|
|
3763
3791
|
|
|
3764
3792
|
// src/cli/index.ts
|
|
3765
|
-
var CLI_VERSION = true ? "1.7.
|
|
3793
|
+
var CLI_VERSION = true ? "1.7.8" : "dev";
|
|
3766
3794
|
var main = defineCommand23({
|
|
3767
3795
|
meta: {
|
|
3768
3796
|
name: "clawops",
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCP_HTTP_PORT
|
|
4
|
+
} from "./chunk-QQNKBTYZ.js";
|
|
5
|
+
import "./chunk-35WVQY3X.js";
|
|
6
|
+
import "./chunk-6ZFIFDBJ.js";
|
|
7
|
+
import "./chunk-BOPSG2LI.js";
|
|
8
|
+
import "./chunk-3QJBNAHW.js";
|
|
9
|
+
import "./chunk-RAAONHW3.js";
|
|
10
|
+
import "./chunk-6X45DDGH.js";
|
|
11
|
+
import "./chunk-FNS56UQY.js";
|
|
12
|
+
import "./chunk-DQDAWNGB.js";
|
|
13
|
+
import "./chunk-SEZMVVAS.js";
|
|
14
|
+
import "./chunk-P5LKWRPL.js";
|
|
15
|
+
import "./chunk-EI7CQQ7D.js";
|
|
16
|
+
import "./chunk-PVUVI35Y.js";
|
|
17
|
+
import "./chunk-3XK66DKK.js";
|
|
18
|
+
import "./chunk-Q7NQY5HV.js";
|
|
19
|
+
import "./chunk-A2I76FTA.js";
|
|
20
|
+
import "./chunk-CX5SL5HP.js";
|
|
21
|
+
import "./chunk-KGXPLI7W.js";
|
|
22
|
+
|
|
23
|
+
// src/cli/mcp-wire.ts
|
|
24
|
+
var GATEWAY_MCP_NAME = "clawops";
|
|
25
|
+
function defaultGatewayMcpUrl(port = MCP_HTTP_PORT) {
|
|
26
|
+
return `http://host.docker.internal:${port}/`;
|
|
27
|
+
}
|
|
28
|
+
function shellQuote(value) {
|
|
29
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
30
|
+
}
|
|
31
|
+
var OC = "docker exec openclaw openclaw mcp";
|
|
32
|
+
async function supportsMcpAdd(session, signal) {
|
|
33
|
+
const help = await session.exec(`${OC} add --help`, signal);
|
|
34
|
+
return help.code === 0;
|
|
35
|
+
}
|
|
36
|
+
async function wireGatewayMcp(session, signal, opts = {}) {
|
|
37
|
+
const url = opts.url ?? defaultGatewayMcpUrl();
|
|
38
|
+
if (!await supportsMcpAdd(session, signal)) return { status: "unsupported", url };
|
|
39
|
+
const existing = await session.exec(`${OC} show ${GATEWAY_MCP_NAME}`, signal);
|
|
40
|
+
const alreadyWired = existing.code === 0;
|
|
41
|
+
if (alreadyWired && !opts.rewire) return { status: "exists", url };
|
|
42
|
+
if (alreadyWired) {
|
|
43
|
+
const removed = await session.exec(`${OC} unset ${GATEWAY_MCP_NAME}`, signal);
|
|
44
|
+
if (removed.code !== 0) {
|
|
45
|
+
return {
|
|
46
|
+
status: "probe-failed",
|
|
47
|
+
error: `Could not remove the existing entry: ${removed.stderr.trim() || removed.stdout.trim()}`,
|
|
48
|
+
url
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const flags = [
|
|
53
|
+
`--transport streamable-http`,
|
|
54
|
+
`--url ${shellQuote(url)}`,
|
|
55
|
+
...opts.token ? [`--header ${shellQuote(`Authorization=Bearer ${opts.token}`)}`] : []
|
|
56
|
+
].join(" ");
|
|
57
|
+
const added = await session.exec(`${OC} add ${GATEWAY_MCP_NAME} ${flags}`, signal);
|
|
58
|
+
if (added.code !== 0) {
|
|
59
|
+
return {
|
|
60
|
+
status: "probe-failed",
|
|
61
|
+
error: added.stderr.trim() || added.stdout.trim() || `exit ${added.code}`,
|
|
62
|
+
url
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
await session.exec(`${OC} reload`, signal);
|
|
66
|
+
return { status: "wired", rewired: alreadyWired, url };
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
GATEWAY_MCP_NAME,
|
|
70
|
+
defaultGatewayMcpUrl,
|
|
71
|
+
wireGatewayMcp
|
|
72
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// package.json
|
|
4
4
|
var name = "@clawops/cli";
|
|
5
|
-
var version = "1.7.
|
|
5
|
+
var version = "1.7.8";
|
|
6
6
|
var description = "Deploy and manage self-hosted OpenClaw instances across clouds";
|
|
7
7
|
var type = "module";
|
|
8
8
|
var bin = {
|
|
@@ -35,6 +35,7 @@ var scripts = {
|
|
|
35
35
|
test: "vitest run",
|
|
36
36
|
"test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
|
|
37
37
|
"test:changed": "vitest --changed",
|
|
38
|
+
"test:mutation": "node scripts/dev/mutation-check.mjs",
|
|
38
39
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
39
40
|
typecheck: "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
|
|
40
41
|
lint: "eslint --max-warnings=0 src tests scripts",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
MCP_HTTP_PORT,
|
|
4
|
+
authRequirement,
|
|
5
|
+
bearerFrom,
|
|
6
|
+
isLoopbackBind,
|
|
7
|
+
serveMcp
|
|
8
|
+
} from "./chunk-QQNKBTYZ.js";
|
|
9
|
+
import "./chunk-35WVQY3X.js";
|
|
10
|
+
import "./chunk-6ZFIFDBJ.js";
|
|
11
|
+
import "./chunk-BOPSG2LI.js";
|
|
12
|
+
import "./chunk-3QJBNAHW.js";
|
|
13
|
+
import "./chunk-RAAONHW3.js";
|
|
14
|
+
import "./chunk-6X45DDGH.js";
|
|
15
|
+
import "./chunk-FNS56UQY.js";
|
|
16
|
+
import "./chunk-DQDAWNGB.js";
|
|
17
|
+
import "./chunk-SEZMVVAS.js";
|
|
18
|
+
import "./chunk-P5LKWRPL.js";
|
|
19
|
+
import "./chunk-EI7CQQ7D.js";
|
|
20
|
+
import "./chunk-PVUVI35Y.js";
|
|
21
|
+
import "./chunk-3XK66DKK.js";
|
|
22
|
+
import "./chunk-Q7NQY5HV.js";
|
|
23
|
+
import "./chunk-A2I76FTA.js";
|
|
24
|
+
import "./chunk-CX5SL5HP.js";
|
|
25
|
+
import "./chunk-KGXPLI7W.js";
|
|
26
|
+
export {
|
|
27
|
+
MCP_HTTP_PORT,
|
|
28
|
+
authRequirement,
|
|
29
|
+
bearerFrom,
|
|
30
|
+
isLoopbackBind,
|
|
31
|
+
serveMcp
|
|
32
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawops/cli",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"test": "vitest run",
|
|
33
33
|
"test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
|
|
34
34
|
"test:changed": "vitest --changed",
|
|
35
|
+
"test:mutation": "node scripts/dev/mutation-check.mjs",
|
|
35
36
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
36
37
|
"typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
|
|
37
38
|
"lint": "eslint --max-warnings=0 src tests scripts",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
atomicWriteConfig,
|
|
4
|
-
deepMerge,
|
|
5
|
-
readRemoteConfig,
|
|
6
|
-
restartGateway
|
|
7
|
-
} from "./chunk-SEZMVVAS.js";
|
|
8
|
-
import "./chunk-EI7CQQ7D.js";
|
|
9
|
-
|
|
10
|
-
// src/cli/mcp-wire.ts
|
|
11
|
-
var GATEWAY_MCP_MIN_VERSION = "2026.4";
|
|
12
|
-
var GATEWAY_MCP_ENTRY = {
|
|
13
|
-
command: "clawops",
|
|
14
|
-
args: ["mcp", "serve"],
|
|
15
|
-
transport: "stdio"
|
|
16
|
-
};
|
|
17
|
-
function versionAtLeast(version, min) {
|
|
18
|
-
const [vy = 0, vm = 0] = version.split(".").map(Number);
|
|
19
|
-
const [my = 0, mm = 0] = min.split(".").map(Number);
|
|
20
|
-
if (vy !== my) return vy > my;
|
|
21
|
-
return vm >= mm;
|
|
22
|
-
}
|
|
23
|
-
async function wireGatewayMcp(session, signal, opts = {}) {
|
|
24
|
-
const cfg = await readRemoteConfig(session, signal);
|
|
25
|
-
const version = cfg["meta"]?.["lastTouchedVersion"];
|
|
26
|
-
if (version && !versionAtLeast(version, GATEWAY_MCP_MIN_VERSION) && !opts.force) {
|
|
27
|
-
return { status: "version-blocked", version };
|
|
28
|
-
}
|
|
29
|
-
const mcpClients = cfg["gateway"]?.["mcpClients"];
|
|
30
|
-
const rewired = !!mcpClients?.["clawops"];
|
|
31
|
-
const updated = deepMerge(cfg, {
|
|
32
|
-
gateway: {
|
|
33
|
-
mcpClients: {
|
|
34
|
-
clawops: GATEWAY_MCP_ENTRY
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
await atomicWriteConfig(session, updated, signal);
|
|
39
|
-
await restartGateway(session, signal);
|
|
40
|
-
return { status: "wired", rewired };
|
|
41
|
-
}
|
|
42
|
-
export {
|
|
43
|
-
GATEWAY_MCP_ENTRY,
|
|
44
|
-
GATEWAY_MCP_MIN_VERSION,
|
|
45
|
-
wireGatewayMcp
|
|
46
|
-
};
|