@muhaven/mcp 0.1.5 → 0.1.6
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/CHANGELOG.md +76 -0
- package/dist/broker.cjs +168 -5
- package/dist/broker.js +168 -5
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,82 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.1.6] — 2026-05-17
|
|
11
|
+
|
|
12
|
+
Adds `muhaven-broker setup --register HOST` so a fresh install no longer
|
|
13
|
+
requires hand-writing a `.mcp.json` (or equivalent host-config file).
|
|
14
|
+
This closes the last manual step of the install ritual — operators run
|
|
15
|
+
one command end-to-end from `npm install -g @muhaven/mcp` to a working
|
|
16
|
+
MCP server registered with their host.
|
|
17
|
+
|
|
18
|
+
Initial host coverage: **Claude Code** (via `claude mcp add-json`).
|
|
19
|
+
`claude-desktop` and `cursor` are reserved as known host names — they
|
|
20
|
+
parse cleanly today but the registrar declines to act and points the
|
|
21
|
+
operator at the per-host JSON snippet in `docs.muhaven.app/mcp/install`.
|
|
22
|
+
Both ship in a Wave 5 follow-up (file-edit registrars need merge-then-
|
|
23
|
+
write semantics + dedicated tests).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`muhaven-broker setup --register HOST[,HOST...]` flag** — auto-wire
|
|
28
|
+
the MCP server into one or more host configs after the login step:
|
|
29
|
+
- **claude-code** (live): probes `claude --version`, removes any
|
|
30
|
+
existing `muhaven` entry (idempotent), then runs
|
|
31
|
+
`claude mcp add-json muhaven '{"type":"stdio","command":"muhaven-mcp","env":{...}}' --scope <scope>`.
|
|
32
|
+
`env` carries `MUHAVEN_BACKEND_URL`, `MUHAVEN_DASHBOARD_URL`, and
|
|
33
|
+
`MUHAVEN_KEYRING` when set (the broker session key + endpoint stay
|
|
34
|
+
daemon-only — never baked into the host config).
|
|
35
|
+
- **claude-desktop / cursor**: reserved names. Parse cleanly; registrar
|
|
36
|
+
short-circuits with a "not implemented yet" hint pointing at the
|
|
37
|
+
docs snippet. Adding a host is a focused diff: implement the
|
|
38
|
+
registrar + extend `KNOWN_REGISTER_HOSTS`.
|
|
39
|
+
- Accepts comma-separated values (`--register claude-code,cursor`) and
|
|
40
|
+
repeated flags (`--register claude-code --register cursor`). Dedupes
|
|
41
|
+
across both forms.
|
|
42
|
+
- Unknown host names fail fast with exit code 2 + the allowlist in the
|
|
43
|
+
error message.
|
|
44
|
+
|
|
45
|
+
- **`--register-scope user|project|local` flag** — scope for the
|
|
46
|
+
`claude mcp add-json` call. Default `user` (every project on this
|
|
47
|
+
machine sees the server — matches the per-user broker model);
|
|
48
|
+
`project` writes `.mcp.json` at CWD (git-shared if you commit it);
|
|
49
|
+
`local` writes `~/.claude.json` as a per-project user-only entry
|
|
50
|
+
(Claude Code's `claude mcp add` default).
|
|
51
|
+
|
|
52
|
+
- **Pure helpers exported from `src/broker/setup.ts`** for testing +
|
|
53
|
+
third-party reuse: `buildRegisterEnv`, `buildClaudeMcpRegisterJson`,
|
|
54
|
+
`buildClaudeMcpAddJsonArgv`, `buildClaudeMcpRemoveArgv`,
|
|
55
|
+
`registerWithHost`. Plus type exports for `RegisterHost`,
|
|
56
|
+
`RegisterScope`, `RegisterHostOutcome`, `ShellResult`, and the
|
|
57
|
+
`KNOWN_REGISTER_HOSTS` + `KNOWN_REGISTER_SCOPES` constants.
|
|
58
|
+
|
|
59
|
+
- **`SetupDeps.shellOut`** seam — abstracts child-process execution so
|
|
60
|
+
tests can script the host-CLI responses without spawning real
|
|
61
|
+
binaries. Default implementation in `cli.ts` uses `node:child_process`
|
|
62
|
+
`spawn` (argv-safe — no shell interpolation of the JSON payload).
|
|
63
|
+
|
|
64
|
+
### Operator UX
|
|
65
|
+
|
|
66
|
+
- Setup's exit code is **0 on register failure**. The broker daemon and
|
|
67
|
+
JWT (the load-bearing artifacts) are already in place; an opt-in
|
|
68
|
+
registration failure surfaces as a warning on stderr with the exact
|
|
69
|
+
re-run hint and a fallback link to the per-host JSON snippet. This
|
|
70
|
+
matches the existing pattern for `--skip-login` (operator can complete
|
|
71
|
+
the missing step in isolation later).
|
|
72
|
+
|
|
73
|
+
- A `cli_missing` outcome (claude binary not on PATH) is distinct from
|
|
74
|
+
a `failed` outcome (claude ran but errored). The error copy reflects
|
|
75
|
+
which: operators on a machine without Claude Code get an "install
|
|
76
|
+
Claude Code" prompt; operators with Claude Code installed get the
|
|
77
|
+
CLI's actual error message.
|
|
78
|
+
|
|
79
|
+
### Tests
|
|
80
|
+
|
|
81
|
+
- **35 new vitest cases** covering `parseSetupFlags --register /
|
|
82
|
+
--register-scope` (11), pure helpers (12), and the `registerWithHost`
|
|
83
|
+
+ `runSetup` integration (12). Total `__tests__/setup.test.ts` now
|
|
84
|
+
93 cases. Full `@muhaven/mcp` suite: 241 cases passing.
|
|
85
|
+
|
|
10
86
|
## [0.1.5] — 2026-05-17
|
|
11
87
|
|
|
12
88
|
Adds the `muhaven-broker stop` subcommand so operators can cleanly tear
|
package/dist/broker.cjs
CHANGED
|
@@ -1047,6 +1047,12 @@ async function waitForBroker(options) {
|
|
|
1047
1047
|
`muhaven-broker daemon did not become reachable within ${timeoutMs}ms: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
1048
1048
|
);
|
|
1049
1049
|
}
|
|
1050
|
+
var KNOWN_REGISTER_HOSTS = [
|
|
1051
|
+
"claude-code",
|
|
1052
|
+
"claude-desktop",
|
|
1053
|
+
"cursor"
|
|
1054
|
+
];
|
|
1055
|
+
var KNOWN_REGISTER_SCOPES = ["user", "project", "local"];
|
|
1050
1056
|
function parseSetupFlags(argv) {
|
|
1051
1057
|
let foreground = false;
|
|
1052
1058
|
let noLaunchBrowser = false;
|
|
@@ -1054,6 +1060,8 @@ function parseSetupFlags(argv) {
|
|
|
1054
1060
|
let backendBaseUrl;
|
|
1055
1061
|
let dashboardBaseUrl;
|
|
1056
1062
|
let skipLogin = false;
|
|
1063
|
+
const register = [];
|
|
1064
|
+
let registerScope = "user";
|
|
1057
1065
|
for (let i = 0; i < argv.length; i++) {
|
|
1058
1066
|
const a = argv[i];
|
|
1059
1067
|
if (a === "--foreground" || a === "-f") foreground = true;
|
|
@@ -1062,7 +1070,29 @@ function parseSetupFlags(argv) {
|
|
|
1062
1070
|
else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
|
|
1063
1071
|
else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
|
|
1064
1072
|
else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
|
|
1065
|
-
else
|
|
1073
|
+
else if (a === "--register" && i + 1 < argv.length) {
|
|
1074
|
+
const value = argv[++i];
|
|
1075
|
+
for (const raw of value.split(",")) {
|
|
1076
|
+
const host = raw.trim().toLowerCase();
|
|
1077
|
+
if (host === "") continue;
|
|
1078
|
+
if (!KNOWN_REGISTER_HOSTS.includes(host)) {
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`unknown --register host: ${JSON.stringify(host)} (expected one of ${KNOWN_REGISTER_HOSTS.join(", ")})`
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
if (!register.includes(host)) {
|
|
1084
|
+
register.push(host);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} else if (a === "--register-scope" && i + 1 < argv.length) {
|
|
1088
|
+
const value = argv[++i];
|
|
1089
|
+
if (!KNOWN_REGISTER_SCOPES.includes(value)) {
|
|
1090
|
+
throw new Error(
|
|
1091
|
+
`unknown --register-scope: ${JSON.stringify(value)} (expected one of ${KNOWN_REGISTER_SCOPES.join(", ")})`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
registerScope = value;
|
|
1095
|
+
} else throw new Error(`unknown flag: ${a}`);
|
|
1066
1096
|
}
|
|
1067
1097
|
return {
|
|
1068
1098
|
foreground,
|
|
@@ -1070,9 +1100,77 @@ function parseSetupFlags(argv) {
|
|
|
1070
1100
|
brokerEndpoint,
|
|
1071
1101
|
backendBaseUrl,
|
|
1072
1102
|
dashboardBaseUrl,
|
|
1073
|
-
skipLogin
|
|
1103
|
+
skipLogin,
|
|
1104
|
+
register,
|
|
1105
|
+
registerScope
|
|
1074
1106
|
};
|
|
1075
1107
|
}
|
|
1108
|
+
function buildRegisterEnv(effectiveEnv) {
|
|
1109
|
+
const env = {};
|
|
1110
|
+
if (effectiveEnv.MUHAVEN_BACKEND_URL) env.MUHAVEN_BACKEND_URL = effectiveEnv.MUHAVEN_BACKEND_URL;
|
|
1111
|
+
if (effectiveEnv.MUHAVEN_DASHBOARD_URL) env.MUHAVEN_DASHBOARD_URL = effectiveEnv.MUHAVEN_DASHBOARD_URL;
|
|
1112
|
+
if (effectiveEnv.MUHAVEN_KEYRING) env.MUHAVEN_KEYRING = effectiveEnv.MUHAVEN_KEYRING;
|
|
1113
|
+
return env;
|
|
1114
|
+
}
|
|
1115
|
+
function buildClaudeMcpRegisterJson(registerEnv) {
|
|
1116
|
+
const payload = {
|
|
1117
|
+
type: "stdio",
|
|
1118
|
+
command: "muhaven-mcp"
|
|
1119
|
+
};
|
|
1120
|
+
if (Object.keys(registerEnv).length > 0) {
|
|
1121
|
+
payload.env = registerEnv;
|
|
1122
|
+
}
|
|
1123
|
+
return JSON.stringify(payload);
|
|
1124
|
+
}
|
|
1125
|
+
function buildClaudeMcpAddJsonArgv(serverName, json, scope) {
|
|
1126
|
+
return ["mcp", "add-json", serverName, json, "--scope", scope];
|
|
1127
|
+
}
|
|
1128
|
+
function buildClaudeMcpRemoveArgv(serverName, scope) {
|
|
1129
|
+
return ["mcp", "remove", serverName, "--scope", scope];
|
|
1130
|
+
}
|
|
1131
|
+
async function registerWithHost(deps, options) {
|
|
1132
|
+
if (options.host === "claude-code") {
|
|
1133
|
+
return registerWithClaudeCode(deps, options);
|
|
1134
|
+
}
|
|
1135
|
+
return { status: "not_implemented", host: options.host };
|
|
1136
|
+
}
|
|
1137
|
+
async function registerWithClaudeCode(deps, options) {
|
|
1138
|
+
let probe;
|
|
1139
|
+
try {
|
|
1140
|
+
probe = await deps.shellOut("claude", ["--version"]);
|
|
1141
|
+
} catch (err) {
|
|
1142
|
+
return {
|
|
1143
|
+
status: "cli_missing",
|
|
1144
|
+
host: options.host,
|
|
1145
|
+
cmd: `claude --version (${err.message})`
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
if (probe.exitCode !== 0) {
|
|
1149
|
+
return {
|
|
1150
|
+
status: "cli_missing",
|
|
1151
|
+
host: options.host,
|
|
1152
|
+
cmd: "claude --version"
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
await deps.shellOut("claude", buildClaudeMcpRemoveArgv(options.serverName, options.scope));
|
|
1156
|
+
const json = buildClaudeMcpRegisterJson(options.registerEnv);
|
|
1157
|
+
const addArgv = buildClaudeMcpAddJsonArgv(options.serverName, json, options.scope);
|
|
1158
|
+
let add;
|
|
1159
|
+
try {
|
|
1160
|
+
add = await deps.shellOut("claude", addArgv);
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
return {
|
|
1163
|
+
status: "failed",
|
|
1164
|
+
host: options.host,
|
|
1165
|
+
reason: `spawn claude failed: ${err.message}`
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
if (add.exitCode !== 0) {
|
|
1169
|
+
const reason = [add.stderr, add.stdout].map((s) => s.trim()).filter((s) => s.length > 0).join(" | ") || `exit ${add.exitCode}`;
|
|
1170
|
+
return { status: "failed", host: options.host, reason };
|
|
1171
|
+
}
|
|
1172
|
+
return { status: "registered", host: options.host, scope: options.scope };
|
|
1173
|
+
}
|
|
1076
1174
|
async function runSetup(argv, deps) {
|
|
1077
1175
|
let flags;
|
|
1078
1176
|
try {
|
|
@@ -1080,7 +1178,7 @@ async function runSetup(argv, deps) {
|
|
|
1080
1178
|
} catch (err) {
|
|
1081
1179
|
deps.printErr(`error: ${err.message}`);
|
|
1082
1180
|
deps.printErr(
|
|
1083
|
-
"usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL]"
|
|
1181
|
+
"usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL]\n [--register HOST[,HOST...]] [--register-scope user|project|local]"
|
|
1084
1182
|
);
|
|
1085
1183
|
return 2;
|
|
1086
1184
|
}
|
|
@@ -1243,6 +1341,39 @@ async function runSetup(argv, deps) {
|
|
|
1243
1341
|
return code;
|
|
1244
1342
|
}
|
|
1245
1343
|
}
|
|
1344
|
+
if (flags.register.length > 0) {
|
|
1345
|
+
const registerEnv = buildRegisterEnv(effectiveEnv);
|
|
1346
|
+
for (const host of flags.register) {
|
|
1347
|
+
const outcome = await registerWithHost(deps, {
|
|
1348
|
+
host,
|
|
1349
|
+
scope: flags.registerScope,
|
|
1350
|
+
serverName: "muhaven",
|
|
1351
|
+
registerEnv
|
|
1352
|
+
});
|
|
1353
|
+
switch (outcome.status) {
|
|
1354
|
+
case "registered":
|
|
1355
|
+
deps.print(
|
|
1356
|
+
`Host register: ${outcome.host} wired (scope: ${outcome.scope}). Restart the host to pick up the new MCP server.`
|
|
1357
|
+
);
|
|
1358
|
+
break;
|
|
1359
|
+
case "cli_missing":
|
|
1360
|
+
deps.printErr(
|
|
1361
|
+
`Host register: ${outcome.host} CLI not found on PATH (${outcome.cmd}). Install Claude Code and re-run \`muhaven-broker setup --register ${outcome.host}\`, or copy the JSON snippet from https://docs.muhaven.app/mcp/install#step-3-wire-your-host`
|
|
1362
|
+
);
|
|
1363
|
+
break;
|
|
1364
|
+
case "not_implemented":
|
|
1365
|
+
deps.printErr(
|
|
1366
|
+
`Host register: ${outcome.host} registrar not implemented yet (Wave 5). Use the JSON snippet from https://docs.muhaven.app/mcp/install#step-3-wire-your-host for now.`
|
|
1367
|
+
);
|
|
1368
|
+
break;
|
|
1369
|
+
case "failed":
|
|
1370
|
+
deps.printErr(
|
|
1371
|
+
`Host register: ${outcome.host} failed \u2014 ${outcome.reason}. Setup continues; re-run \`muhaven-broker setup --register ${outcome.host}\` after fixing.`
|
|
1372
|
+
);
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1246
1377
|
deps.print("");
|
|
1247
1378
|
deps.print("================================");
|
|
1248
1379
|
deps.print("Setup complete.");
|
|
@@ -1604,6 +1735,10 @@ function printUsage() {
|
|
|
1604
1735
|
print(" [--foreground|-f] keeps the daemon attached (skip background spawn)");
|
|
1605
1736
|
print(" [--skip-login] starts the daemon but lets you run login later");
|
|
1606
1737
|
print(" [--no-launch-browser] pass-through to login");
|
|
1738
|
+
print(" [--register HOST[,HOST...]] auto-wire the MCP server into the named host");
|
|
1739
|
+
print(" (claude-code today; claude-desktop / cursor reserved for Wave 5)");
|
|
1740
|
+
print(" [--register-scope user|project|local] scope for the host-config write");
|
|
1741
|
+
print(" (default: user \u2014 every project sees the server)");
|
|
1607
1742
|
print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
|
|
1608
1743
|
print(" after 5s). Also clears the keystore JWT as a best effort.");
|
|
1609
1744
|
print(" login Acquire a JWT via the device-code flow + store in keystore");
|
|
@@ -1615,7 +1750,7 @@ function printUsage() {
|
|
|
1615
1750
|
}
|
|
1616
1751
|
function getBrokerPackageVersion() {
|
|
1617
1752
|
{
|
|
1618
|
-
return "0.1.
|
|
1753
|
+
return "0.1.6";
|
|
1619
1754
|
}
|
|
1620
1755
|
}
|
|
1621
1756
|
function printVersion() {
|
|
@@ -1624,6 +1759,33 @@ function printVersion() {
|
|
|
1624
1759
|
function resolveBrokerBinPath() {
|
|
1625
1760
|
return path.resolve(__dirname, "..", "bin", "muhaven-broker.cjs");
|
|
1626
1761
|
}
|
|
1762
|
+
function defaultShellOut(cmd, argv) {
|
|
1763
|
+
return new Promise((resolve, reject) => {
|
|
1764
|
+
const child = child_process.spawn(cmd, argv, {
|
|
1765
|
+
// Inherit env so PATH + npm-shim resolution work; explicitly NOT
|
|
1766
|
+
// forwarding stdio so the parent's transcript stays clean.
|
|
1767
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1768
|
+
// Windows: .cmd / .ps1 shims under %APPDATA%\npm need cmd.exe
|
|
1769
|
+
// to interpret them. Node 18+ auto-routes through cmd.exe when
|
|
1770
|
+
// it sees a non-.exe extension, but explicitly setting
|
|
1771
|
+
// `shell: true` on Windows is safer for npm-global PATH entries.
|
|
1772
|
+
// On POSIX, `shell: false` (the default) is correct + safer.
|
|
1773
|
+
shell: process.platform === "win32"
|
|
1774
|
+
});
|
|
1775
|
+
let stdout = "";
|
|
1776
|
+
let stderr = "";
|
|
1777
|
+
child.stdout?.on("data", (chunk) => {
|
|
1778
|
+
stdout += chunk.toString("utf-8");
|
|
1779
|
+
});
|
|
1780
|
+
child.stderr?.on("data", (chunk) => {
|
|
1781
|
+
stderr += chunk.toString("utf-8");
|
|
1782
|
+
});
|
|
1783
|
+
child.on("error", (err) => reject(err));
|
|
1784
|
+
child.on("close", (code) => {
|
|
1785
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
1786
|
+
});
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1627
1789
|
async function runSetup2(argv) {
|
|
1628
1790
|
const deps = {
|
|
1629
1791
|
print,
|
|
@@ -1637,7 +1799,8 @@ async function runSetup2(argv) {
|
|
|
1637
1799
|
resolveBinPath: resolveBrokerBinPath,
|
|
1638
1800
|
env: process.env,
|
|
1639
1801
|
platformId: process.platform,
|
|
1640
|
-
osRelease: os.release()
|
|
1802
|
+
osRelease: os.release(),
|
|
1803
|
+
shellOut: defaultShellOut
|
|
1641
1804
|
};
|
|
1642
1805
|
return runSetup(argv, deps);
|
|
1643
1806
|
}
|
package/dist/broker.js
CHANGED
|
@@ -1049,6 +1049,12 @@ async function waitForBroker(options) {
|
|
|
1049
1049
|
`muhaven-broker daemon did not become reachable within ${timeoutMs}ms: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
|
|
1050
1050
|
);
|
|
1051
1051
|
}
|
|
1052
|
+
var KNOWN_REGISTER_HOSTS = [
|
|
1053
|
+
"claude-code",
|
|
1054
|
+
"claude-desktop",
|
|
1055
|
+
"cursor"
|
|
1056
|
+
];
|
|
1057
|
+
var KNOWN_REGISTER_SCOPES = ["user", "project", "local"];
|
|
1052
1058
|
function parseSetupFlags(argv) {
|
|
1053
1059
|
let foreground = false;
|
|
1054
1060
|
let noLaunchBrowser = false;
|
|
@@ -1056,6 +1062,8 @@ function parseSetupFlags(argv) {
|
|
|
1056
1062
|
let backendBaseUrl;
|
|
1057
1063
|
let dashboardBaseUrl;
|
|
1058
1064
|
let skipLogin = false;
|
|
1065
|
+
const register = [];
|
|
1066
|
+
let registerScope = "user";
|
|
1059
1067
|
for (let i = 0; i < argv.length; i++) {
|
|
1060
1068
|
const a = argv[i];
|
|
1061
1069
|
if (a === "--foreground" || a === "-f") foreground = true;
|
|
@@ -1064,7 +1072,29 @@ function parseSetupFlags(argv) {
|
|
|
1064
1072
|
else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
|
|
1065
1073
|
else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
|
|
1066
1074
|
else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
|
|
1067
|
-
else
|
|
1075
|
+
else if (a === "--register" && i + 1 < argv.length) {
|
|
1076
|
+
const value = argv[++i];
|
|
1077
|
+
for (const raw of value.split(",")) {
|
|
1078
|
+
const host = raw.trim().toLowerCase();
|
|
1079
|
+
if (host === "") continue;
|
|
1080
|
+
if (!KNOWN_REGISTER_HOSTS.includes(host)) {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`unknown --register host: ${JSON.stringify(host)} (expected one of ${KNOWN_REGISTER_HOSTS.join(", ")})`
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
if (!register.includes(host)) {
|
|
1086
|
+
register.push(host);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
} else if (a === "--register-scope" && i + 1 < argv.length) {
|
|
1090
|
+
const value = argv[++i];
|
|
1091
|
+
if (!KNOWN_REGISTER_SCOPES.includes(value)) {
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`unknown --register-scope: ${JSON.stringify(value)} (expected one of ${KNOWN_REGISTER_SCOPES.join(", ")})`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
registerScope = value;
|
|
1097
|
+
} else throw new Error(`unknown flag: ${a}`);
|
|
1068
1098
|
}
|
|
1069
1099
|
return {
|
|
1070
1100
|
foreground,
|
|
@@ -1072,9 +1102,77 @@ function parseSetupFlags(argv) {
|
|
|
1072
1102
|
brokerEndpoint,
|
|
1073
1103
|
backendBaseUrl,
|
|
1074
1104
|
dashboardBaseUrl,
|
|
1075
|
-
skipLogin
|
|
1105
|
+
skipLogin,
|
|
1106
|
+
register,
|
|
1107
|
+
registerScope
|
|
1076
1108
|
};
|
|
1077
1109
|
}
|
|
1110
|
+
function buildRegisterEnv(effectiveEnv) {
|
|
1111
|
+
const env = {};
|
|
1112
|
+
if (effectiveEnv.MUHAVEN_BACKEND_URL) env.MUHAVEN_BACKEND_URL = effectiveEnv.MUHAVEN_BACKEND_URL;
|
|
1113
|
+
if (effectiveEnv.MUHAVEN_DASHBOARD_URL) env.MUHAVEN_DASHBOARD_URL = effectiveEnv.MUHAVEN_DASHBOARD_URL;
|
|
1114
|
+
if (effectiveEnv.MUHAVEN_KEYRING) env.MUHAVEN_KEYRING = effectiveEnv.MUHAVEN_KEYRING;
|
|
1115
|
+
return env;
|
|
1116
|
+
}
|
|
1117
|
+
function buildClaudeMcpRegisterJson(registerEnv) {
|
|
1118
|
+
const payload = {
|
|
1119
|
+
type: "stdio",
|
|
1120
|
+
command: "muhaven-mcp"
|
|
1121
|
+
};
|
|
1122
|
+
if (Object.keys(registerEnv).length > 0) {
|
|
1123
|
+
payload.env = registerEnv;
|
|
1124
|
+
}
|
|
1125
|
+
return JSON.stringify(payload);
|
|
1126
|
+
}
|
|
1127
|
+
function buildClaudeMcpAddJsonArgv(serverName, json, scope) {
|
|
1128
|
+
return ["mcp", "add-json", serverName, json, "--scope", scope];
|
|
1129
|
+
}
|
|
1130
|
+
function buildClaudeMcpRemoveArgv(serverName, scope) {
|
|
1131
|
+
return ["mcp", "remove", serverName, "--scope", scope];
|
|
1132
|
+
}
|
|
1133
|
+
async function registerWithHost(deps, options) {
|
|
1134
|
+
if (options.host === "claude-code") {
|
|
1135
|
+
return registerWithClaudeCode(deps, options);
|
|
1136
|
+
}
|
|
1137
|
+
return { status: "not_implemented", host: options.host };
|
|
1138
|
+
}
|
|
1139
|
+
async function registerWithClaudeCode(deps, options) {
|
|
1140
|
+
let probe;
|
|
1141
|
+
try {
|
|
1142
|
+
probe = await deps.shellOut("claude", ["--version"]);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
return {
|
|
1145
|
+
status: "cli_missing",
|
|
1146
|
+
host: options.host,
|
|
1147
|
+
cmd: `claude --version (${err.message})`
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (probe.exitCode !== 0) {
|
|
1151
|
+
return {
|
|
1152
|
+
status: "cli_missing",
|
|
1153
|
+
host: options.host,
|
|
1154
|
+
cmd: "claude --version"
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
await deps.shellOut("claude", buildClaudeMcpRemoveArgv(options.serverName, options.scope));
|
|
1158
|
+
const json = buildClaudeMcpRegisterJson(options.registerEnv);
|
|
1159
|
+
const addArgv = buildClaudeMcpAddJsonArgv(options.serverName, json, options.scope);
|
|
1160
|
+
let add;
|
|
1161
|
+
try {
|
|
1162
|
+
add = await deps.shellOut("claude", addArgv);
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
return {
|
|
1165
|
+
status: "failed",
|
|
1166
|
+
host: options.host,
|
|
1167
|
+
reason: `spawn claude failed: ${err.message}`
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
if (add.exitCode !== 0) {
|
|
1171
|
+
const reason = [add.stderr, add.stdout].map((s) => s.trim()).filter((s) => s.length > 0).join(" | ") || `exit ${add.exitCode}`;
|
|
1172
|
+
return { status: "failed", host: options.host, reason };
|
|
1173
|
+
}
|
|
1174
|
+
return { status: "registered", host: options.host, scope: options.scope };
|
|
1175
|
+
}
|
|
1078
1176
|
async function runSetup(argv, deps) {
|
|
1079
1177
|
let flags;
|
|
1080
1178
|
try {
|
|
@@ -1082,7 +1180,7 @@ async function runSetup(argv, deps) {
|
|
|
1082
1180
|
} catch (err) {
|
|
1083
1181
|
deps.printErr(`error: ${err.message}`);
|
|
1084
1182
|
deps.printErr(
|
|
1085
|
-
"usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL]"
|
|
1183
|
+
"usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL]\n [--register HOST[,HOST...]] [--register-scope user|project|local]"
|
|
1086
1184
|
);
|
|
1087
1185
|
return 2;
|
|
1088
1186
|
}
|
|
@@ -1245,6 +1343,39 @@ async function runSetup(argv, deps) {
|
|
|
1245
1343
|
return code;
|
|
1246
1344
|
}
|
|
1247
1345
|
}
|
|
1346
|
+
if (flags.register.length > 0) {
|
|
1347
|
+
const registerEnv = buildRegisterEnv(effectiveEnv);
|
|
1348
|
+
for (const host of flags.register) {
|
|
1349
|
+
const outcome = await registerWithHost(deps, {
|
|
1350
|
+
host,
|
|
1351
|
+
scope: flags.registerScope,
|
|
1352
|
+
serverName: "muhaven",
|
|
1353
|
+
registerEnv
|
|
1354
|
+
});
|
|
1355
|
+
switch (outcome.status) {
|
|
1356
|
+
case "registered":
|
|
1357
|
+
deps.print(
|
|
1358
|
+
`Host register: ${outcome.host} wired (scope: ${outcome.scope}). Restart the host to pick up the new MCP server.`
|
|
1359
|
+
);
|
|
1360
|
+
break;
|
|
1361
|
+
case "cli_missing":
|
|
1362
|
+
deps.printErr(
|
|
1363
|
+
`Host register: ${outcome.host} CLI not found on PATH (${outcome.cmd}). Install Claude Code and re-run \`muhaven-broker setup --register ${outcome.host}\`, or copy the JSON snippet from https://docs.muhaven.app/mcp/install#step-3-wire-your-host`
|
|
1364
|
+
);
|
|
1365
|
+
break;
|
|
1366
|
+
case "not_implemented":
|
|
1367
|
+
deps.printErr(
|
|
1368
|
+
`Host register: ${outcome.host} registrar not implemented yet (Wave 5). Use the JSON snippet from https://docs.muhaven.app/mcp/install#step-3-wire-your-host for now.`
|
|
1369
|
+
);
|
|
1370
|
+
break;
|
|
1371
|
+
case "failed":
|
|
1372
|
+
deps.printErr(
|
|
1373
|
+
`Host register: ${outcome.host} failed \u2014 ${outcome.reason}. Setup continues; re-run \`muhaven-broker setup --register ${outcome.host}\` after fixing.`
|
|
1374
|
+
);
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1248
1379
|
deps.print("");
|
|
1249
1380
|
deps.print("================================");
|
|
1250
1381
|
deps.print("Setup complete.");
|
|
@@ -1606,6 +1737,10 @@ function printUsage() {
|
|
|
1606
1737
|
print(" [--foreground|-f] keeps the daemon attached (skip background spawn)");
|
|
1607
1738
|
print(" [--skip-login] starts the daemon but lets you run login later");
|
|
1608
1739
|
print(" [--no-launch-browser] pass-through to login");
|
|
1740
|
+
print(" [--register HOST[,HOST...]] auto-wire the MCP server into the named host");
|
|
1741
|
+
print(" (claude-code today; claude-desktop / cursor reserved for Wave 5)");
|
|
1742
|
+
print(" [--register-scope user|project|local] scope for the host-config write");
|
|
1743
|
+
print(" (default: user \u2014 every project sees the server)");
|
|
1609
1744
|
print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
|
|
1610
1745
|
print(" after 5s). Also clears the keystore JWT as a best effort.");
|
|
1611
1746
|
print(" login Acquire a JWT via the device-code flow + store in keystore");
|
|
@@ -1617,7 +1752,7 @@ function printUsage() {
|
|
|
1617
1752
|
}
|
|
1618
1753
|
function getBrokerPackageVersion() {
|
|
1619
1754
|
{
|
|
1620
|
-
return "0.1.
|
|
1755
|
+
return "0.1.6";
|
|
1621
1756
|
}
|
|
1622
1757
|
}
|
|
1623
1758
|
function printVersion() {
|
|
@@ -1626,6 +1761,33 @@ function printVersion() {
|
|
|
1626
1761
|
function resolveBrokerBinPath() {
|
|
1627
1762
|
return resolve(__dirname$1, "..", "bin", "muhaven-broker.cjs");
|
|
1628
1763
|
}
|
|
1764
|
+
function defaultShellOut(cmd, argv) {
|
|
1765
|
+
return new Promise((resolve, reject) => {
|
|
1766
|
+
const child = spawn(cmd, argv, {
|
|
1767
|
+
// Inherit env so PATH + npm-shim resolution work; explicitly NOT
|
|
1768
|
+
// forwarding stdio so the parent's transcript stays clean.
|
|
1769
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1770
|
+
// Windows: .cmd / .ps1 shims under %APPDATA%\npm need cmd.exe
|
|
1771
|
+
// to interpret them. Node 18+ auto-routes through cmd.exe when
|
|
1772
|
+
// it sees a non-.exe extension, but explicitly setting
|
|
1773
|
+
// `shell: true` on Windows is safer for npm-global PATH entries.
|
|
1774
|
+
// On POSIX, `shell: false` (the default) is correct + safer.
|
|
1775
|
+
shell: process.platform === "win32"
|
|
1776
|
+
});
|
|
1777
|
+
let stdout = "";
|
|
1778
|
+
let stderr = "";
|
|
1779
|
+
child.stdout?.on("data", (chunk) => {
|
|
1780
|
+
stdout += chunk.toString("utf-8");
|
|
1781
|
+
});
|
|
1782
|
+
child.stderr?.on("data", (chunk) => {
|
|
1783
|
+
stderr += chunk.toString("utf-8");
|
|
1784
|
+
});
|
|
1785
|
+
child.on("error", (err) => reject(err));
|
|
1786
|
+
child.on("close", (code) => {
|
|
1787
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
1788
|
+
});
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1629
1791
|
async function runSetup2(argv) {
|
|
1630
1792
|
const deps = {
|
|
1631
1793
|
print,
|
|
@@ -1639,7 +1801,8 @@ async function runSetup2(argv) {
|
|
|
1639
1801
|
resolveBinPath: resolveBrokerBinPath,
|
|
1640
1802
|
env: process.env,
|
|
1641
1803
|
platformId: process.platform,
|
|
1642
|
-
osRelease: release()
|
|
1804
|
+
osRelease: release(),
|
|
1805
|
+
shellOut: defaultShellOut
|
|
1643
1806
|
};
|
|
1644
1807
|
return runSetup(argv, deps);
|
|
1645
1808
|
}
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.2",
|
|
4
4
|
"name": "muhaven-mcp",
|
|
5
5
|
"display_name": "MuHaven (RWA portfolio)",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.6",
|
|
7
7
|
"description": "Confidential RWA portfolio management on Fhenix CoFHE. Read your encrypted balances, propose yield claims and policy changes — all signing happens in a sibling broker daemon, the LLM never sees your private key.",
|
|
8
8
|
"long_description": "MuHaven MCP exposes 22 tools across read.* / position.* / policy.* / issuer.* / governance.* groups for managing real-world asset (RWA) tokens with FHE-encrypted balances. Authentication uses a one-time device-code ceremony (run `muhaven-broker login`); subsequent tool calls fetch the JWT from the broker over a Unix socket. Position / governance tools return unsigned UserOps + broker signatures — they NEVER auto-submit to a bundler. The companion `muhaven-broker` daemon must be running before tools can be invoked. See README for setup.",
|
|
9
9
|
"author": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muhaven/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "MuHaven MCP server — read/position/policy toolsets bridging Claude Desktop / Cursor / Claude Code to the MuHaven backend, with a sibling muhaven-broker daemon holding the session-key private half over a local IPC socket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|