@muhaven/mcp 0.1.4 → 0.1.5

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 CHANGED
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5] — 2026-05-17
11
+
12
+ Adds the `muhaven-broker stop` subcommand so operators can cleanly tear
13
+ down a detached daemon spawned by `muhaven-broker setup` without
14
+ hunting for PIDs in `ps` output or hand-rolling `taskkill` recipes.
15
+ Surfaced after `muhaven-broker setup` lands in 0.1.4 — operators
16
+ naturally asked "how do I stop this?" and the answer (`muhaven-broker
17
+ logout` + manual `kill`) was non-obvious.
18
+
19
+ ### Added
20
+
21
+ - **`muhaven-broker stop` subcommand** — clean shutdown:
22
+ - Probes the broker via `hello()`. Unreachable → "not running,
23
+ nothing to stop." exit 0.
24
+ - Best-effort `clearJwt()` so the OS keychain doesn't keep a stale
25
+ JWT after shutdown. Warning + continue on failure (don't abort
26
+ the kill).
27
+ - Reads `hello.pid` (new optional field — see below). On pre-0.1.5
28
+ daemons that omit the field, prints a manual-kill hint with
29
+ cross-platform commands and exits 1.
30
+ - `process.kill(pid, 'SIGTERM')` → polls `hello()` until it fails
31
+ (clean exit) or 5s elapses, then `process.kill(pid, 'SIGKILL')`.
32
+ - Pure orchestrator (`runStop` in `src/broker/stop.ts`) with
33
+ injectable IO so every branch is unit-testable without spawning
34
+ real processes.
35
+
36
+ ### Changed
37
+
38
+ - **`hello` response gains optional `pid?: number`** field (broker
39
+ protocol stays at 0.3.0 — additive optional field, back-compat with
40
+ pre-0.1.5 daemons). Populated from `process.pid` at request-handle
41
+ time; consumers MUST handle `undefined` for older daemons.
42
+
43
+ ### Tests
44
+
45
+ - 206 vitest pass (up from 197 in 0.1.4). Net +9 cases in new
46
+ `__tests__/stop.test.ts`:
47
+ - `runStop` not-running short-circuit
48
+ - happy path (hello → clearJwt → SIGTERM → exit-detected → 0)
49
+ - pre-0.1.5 daemon (no `pid` in hello) returns 1 with manual hint
50
+ - SIGKILL fallback after gracefulShutdownMs timeout
51
+ - SIGTERM permission error returns 1
52
+ - SIGKILL permission error returns 1 with "may be orphaned" hint
53
+ - `clearJwt` failure does NOT abort the kill (warning + continue)
54
+ - `defaultKillProcess` returns false on ESRCH (process gone)
55
+ - `defaultKillProcess` rethrows non-ESRCH errors (POSIX-only test)
56
+
10
57
  ## [0.1.4] — 2026-05-17
11
58
 
12
59
  Adds the one-shot `muhaven-broker setup` subcommand so a fresh install
package/dist/broker.cjs CHANGED
@@ -658,7 +658,8 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
658
658
  sessionKeyAddress: signer.address,
659
659
  hasJwt,
660
660
  hasSessionKey,
661
- ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
661
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {},
662
+ ...options.pid !== void 0 ? { pid: options.pid } : {}
662
663
  };
663
664
  }
664
665
  case "sign_hash": {
@@ -889,7 +890,8 @@ var BrokerDaemon = class {
889
890
  effectiveConfig: {
890
891
  backendBaseUrl: this.config.backendBaseUrl,
891
892
  dashboardBaseUrl: this.config.dashboardBaseUrl
892
- }
893
+ },
894
+ pid: process.pid
893
895
  }
894
896
  );
895
897
  socket.end(serializeResponse(res));
@@ -1260,6 +1262,82 @@ async function runSetup(argv, deps) {
1260
1262
  return 0;
1261
1263
  }
1262
1264
 
1265
+ // src/broker/stop.ts
1266
+ async function runStop(deps) {
1267
+ const gracefulShutdownMs = deps.gracefulShutdownMs ?? 5e3;
1268
+ const pollIntervalMs = deps.pollIntervalMs ?? 200;
1269
+ const broker = deps.newBrokerClient(deps.endpoint, deps.brokerTimeoutMs);
1270
+ let hello;
1271
+ try {
1272
+ hello = await broker.hello();
1273
+ } catch {
1274
+ deps.print("Broker daemon: not running, nothing to stop.");
1275
+ return 0;
1276
+ }
1277
+ try {
1278
+ await broker.clearJwt();
1279
+ deps.print("JWT cleared from keystore.");
1280
+ } catch (err) {
1281
+ deps.print(
1282
+ `Warning: clearJwt failed (${err instanceof Error ? err.message : String(err)}); continuing with daemon shutdown.`
1283
+ );
1284
+ }
1285
+ const pid = hello.pid;
1286
+ if (pid === void 0) {
1287
+ deps.printErr(
1288
+ "Broker daemon did not advertise its PID (older than @muhaven/mcp@0.1.5)."
1289
+ );
1290
+ deps.printErr("Stop manually with:");
1291
+ deps.printErr(" POSIX: pkill -f muhaven-broker");
1292
+ deps.printErr(" Windows: Stop-Process -Name node -Force (filter to muhaven-broker)");
1293
+ return 1;
1294
+ }
1295
+ try {
1296
+ deps.killProcess(pid, "SIGTERM");
1297
+ deps.print(`Sent SIGTERM to broker daemon (PID ${pid}). Waiting up to ${gracefulShutdownMs}ms for clean exit...`);
1298
+ } catch (err) {
1299
+ deps.printErr(
1300
+ `Failed to send SIGTERM to PID ${pid}: ${err instanceof Error ? err.message : String(err)}`
1301
+ );
1302
+ return 1;
1303
+ }
1304
+ const maxAttempts = Math.ceil(gracefulShutdownMs / pollIntervalMs);
1305
+ for (let i = 0; i < maxAttempts; i++) {
1306
+ await deps.sleep(pollIntervalMs);
1307
+ try {
1308
+ await broker.hello();
1309
+ } catch {
1310
+ deps.print("Broker daemon stopped cleanly.");
1311
+ return 0;
1312
+ }
1313
+ }
1314
+ deps.print(`Daemon did not exit after ${gracefulShutdownMs}ms \u2014 sending SIGKILL.`);
1315
+ try {
1316
+ deps.killProcess(pid, "SIGKILL");
1317
+ deps.print(`Broker daemon force-killed (PID ${pid}).`);
1318
+ return 0;
1319
+ } catch (err) {
1320
+ deps.printErr(
1321
+ `Failed to SIGKILL PID ${pid}: ${err instanceof Error ? err.message : String(err)}`
1322
+ );
1323
+ deps.printErr(
1324
+ " Daemon process may be orphaned. Inspect with `ps aux | grep muhaven-broker` and kill manually."
1325
+ );
1326
+ return 1;
1327
+ }
1328
+ }
1329
+ function defaultKillProcess(pid, signal) {
1330
+ try {
1331
+ process.kill(pid, signal);
1332
+ return true;
1333
+ } catch (err) {
1334
+ if (err.code === "ESRCH") {
1335
+ return false;
1336
+ }
1337
+ throw err;
1338
+ }
1339
+ }
1340
+
1263
1341
  // src/broker/cli.ts
1264
1342
  function print(line) {
1265
1343
  process.stdout.write(line + "\n");
@@ -1526,16 +1604,18 @@ function printUsage() {
1526
1604
  print(" [--foreground|-f] keeps the daemon attached (skip background spawn)");
1527
1605
  print(" [--skip-login] starts the daemon but lets you run login later");
1528
1606
  print(" [--no-launch-browser] pass-through to login");
1607
+ print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
1608
+ print(" after 5s). Also clears the keystore JWT as a best effort.");
1529
1609
  print(" login Acquire a JWT via the device-code flow + store in keystore");
1530
1610
  print(" [--from-daemon] resolves backend/dashboard URLs from the running daemon");
1531
- print(" logout Clear the JWT from the keystore");
1611
+ print(" logout Clear the JWT from the keystore (does NOT stop the daemon)");
1532
1612
  print(" doctor Print environment + keystore + reachability report");
1533
1613
  print(" -h, --help Show this help");
1534
1614
  print(" -v, --version Print the @muhaven/mcp package version");
1535
1615
  }
1536
1616
  function getBrokerPackageVersion() {
1537
1617
  {
1538
- return "0.1.4";
1618
+ return "0.1.5";
1539
1619
  }
1540
1620
  }
1541
1621
  function printVersion() {
@@ -1561,6 +1641,19 @@ async function runSetup2(argv) {
1561
1641
  };
1562
1642
  return runSetup(argv, deps);
1563
1643
  }
1644
+ async function runStop2() {
1645
+ const config = loadMcpConfig();
1646
+ const deps = {
1647
+ print,
1648
+ printErr,
1649
+ newBrokerClient: (endpoint, timeoutMs) => new BrokerClient({ endpoint, timeoutMs }),
1650
+ killProcess: defaultKillProcess,
1651
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
1652
+ endpoint: config.brokerEndpoint,
1653
+ brokerTimeoutMs: config.brokerTimeoutMs
1654
+ };
1655
+ return runStop(deps);
1656
+ }
1564
1657
  async function runCli(argv) {
1565
1658
  const [sub, ...rest] = argv;
1566
1659
  switch (sub) {
@@ -1569,6 +1662,8 @@ async function runCli(argv) {
1569
1662
  return 0;
1570
1663
  case "setup":
1571
1664
  return runSetup2(rest);
1665
+ case "stop":
1666
+ return runStop2();
1572
1667
  case "login":
1573
1668
  return runLogin(rest);
1574
1669
  case "logout":
@@ -1597,3 +1692,4 @@ exports.runDoctor = runDoctor;
1597
1692
  exports.runLogin = runLogin;
1598
1693
  exports.runLogout = runLogout;
1599
1694
  exports.runSetup = runSetup2;
1695
+ exports.runStop = runStop2;
package/dist/broker.d.cts CHANGED
@@ -39,6 +39,10 @@ declare function getBrokerPackageVersion(): string;
39
39
  * surface unnecessarily).
40
40
  */
41
41
  declare function runSetup(argv: readonly string[]): Promise<number>;
42
+ /**
43
+ * Wire `runStop` against the real BrokerClient + Node's process.kill.
44
+ */
45
+ declare function runStop(): Promise<number>;
42
46
  declare function runCli(argv: readonly string[]): Promise<number>;
43
47
 
44
- export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup };
48
+ export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup, runStop };
package/dist/broker.d.ts CHANGED
@@ -39,6 +39,10 @@ declare function getBrokerPackageVersion(): string;
39
39
  * surface unnecessarily).
40
40
  */
41
41
  declare function runSetup(argv: readonly string[]): Promise<number>;
42
+ /**
43
+ * Wire `runStop` against the real BrokerClient + Node's process.kill.
44
+ */
45
+ declare function runStop(): Promise<number>;
42
46
  declare function runCli(argv: readonly string[]): Promise<number>;
43
47
 
44
- export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup };
48
+ export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup, runStop };
package/dist/broker.js CHANGED
@@ -660,7 +660,8 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
660
660
  sessionKeyAddress: signer.address,
661
661
  hasJwt,
662
662
  hasSessionKey,
663
- ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
663
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {},
664
+ ...options.pid !== void 0 ? { pid: options.pid } : {}
664
665
  };
665
666
  }
666
667
  case "sign_hash": {
@@ -891,7 +892,8 @@ var BrokerDaemon = class {
891
892
  effectiveConfig: {
892
893
  backendBaseUrl: this.config.backendBaseUrl,
893
894
  dashboardBaseUrl: this.config.dashboardBaseUrl
894
- }
895
+ },
896
+ pid: process.pid
895
897
  }
896
898
  );
897
899
  socket.end(serializeResponse(res));
@@ -1262,6 +1264,82 @@ async function runSetup(argv, deps) {
1262
1264
  return 0;
1263
1265
  }
1264
1266
 
1267
+ // src/broker/stop.ts
1268
+ async function runStop(deps) {
1269
+ const gracefulShutdownMs = deps.gracefulShutdownMs ?? 5e3;
1270
+ const pollIntervalMs = deps.pollIntervalMs ?? 200;
1271
+ const broker = deps.newBrokerClient(deps.endpoint, deps.brokerTimeoutMs);
1272
+ let hello;
1273
+ try {
1274
+ hello = await broker.hello();
1275
+ } catch {
1276
+ deps.print("Broker daemon: not running, nothing to stop.");
1277
+ return 0;
1278
+ }
1279
+ try {
1280
+ await broker.clearJwt();
1281
+ deps.print("JWT cleared from keystore.");
1282
+ } catch (err) {
1283
+ deps.print(
1284
+ `Warning: clearJwt failed (${err instanceof Error ? err.message : String(err)}); continuing with daemon shutdown.`
1285
+ );
1286
+ }
1287
+ const pid = hello.pid;
1288
+ if (pid === void 0) {
1289
+ deps.printErr(
1290
+ "Broker daemon did not advertise its PID (older than @muhaven/mcp@0.1.5)."
1291
+ );
1292
+ deps.printErr("Stop manually with:");
1293
+ deps.printErr(" POSIX: pkill -f muhaven-broker");
1294
+ deps.printErr(" Windows: Stop-Process -Name node -Force (filter to muhaven-broker)");
1295
+ return 1;
1296
+ }
1297
+ try {
1298
+ deps.killProcess(pid, "SIGTERM");
1299
+ deps.print(`Sent SIGTERM to broker daemon (PID ${pid}). Waiting up to ${gracefulShutdownMs}ms for clean exit...`);
1300
+ } catch (err) {
1301
+ deps.printErr(
1302
+ `Failed to send SIGTERM to PID ${pid}: ${err instanceof Error ? err.message : String(err)}`
1303
+ );
1304
+ return 1;
1305
+ }
1306
+ const maxAttempts = Math.ceil(gracefulShutdownMs / pollIntervalMs);
1307
+ for (let i = 0; i < maxAttempts; i++) {
1308
+ await deps.sleep(pollIntervalMs);
1309
+ try {
1310
+ await broker.hello();
1311
+ } catch {
1312
+ deps.print("Broker daemon stopped cleanly.");
1313
+ return 0;
1314
+ }
1315
+ }
1316
+ deps.print(`Daemon did not exit after ${gracefulShutdownMs}ms \u2014 sending SIGKILL.`);
1317
+ try {
1318
+ deps.killProcess(pid, "SIGKILL");
1319
+ deps.print(`Broker daemon force-killed (PID ${pid}).`);
1320
+ return 0;
1321
+ } catch (err) {
1322
+ deps.printErr(
1323
+ `Failed to SIGKILL PID ${pid}: ${err instanceof Error ? err.message : String(err)}`
1324
+ );
1325
+ deps.printErr(
1326
+ " Daemon process may be orphaned. Inspect with `ps aux | grep muhaven-broker` and kill manually."
1327
+ );
1328
+ return 1;
1329
+ }
1330
+ }
1331
+ function defaultKillProcess(pid, signal) {
1332
+ try {
1333
+ process.kill(pid, signal);
1334
+ return true;
1335
+ } catch (err) {
1336
+ if (err.code === "ESRCH") {
1337
+ return false;
1338
+ }
1339
+ throw err;
1340
+ }
1341
+ }
1342
+
1265
1343
  // src/broker/cli.ts
1266
1344
  function print(line) {
1267
1345
  process.stdout.write(line + "\n");
@@ -1528,16 +1606,18 @@ function printUsage() {
1528
1606
  print(" [--foreground|-f] keeps the daemon attached (skip background spawn)");
1529
1607
  print(" [--skip-login] starts the daemon but lets you run login later");
1530
1608
  print(" [--no-launch-browser] pass-through to login");
1609
+ print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
1610
+ print(" after 5s). Also clears the keystore JWT as a best effort.");
1531
1611
  print(" login Acquire a JWT via the device-code flow + store in keystore");
1532
1612
  print(" [--from-daemon] resolves backend/dashboard URLs from the running daemon");
1533
- print(" logout Clear the JWT from the keystore");
1613
+ print(" logout Clear the JWT from the keystore (does NOT stop the daemon)");
1534
1614
  print(" doctor Print environment + keystore + reachability report");
1535
1615
  print(" -h, --help Show this help");
1536
1616
  print(" -v, --version Print the @muhaven/mcp package version");
1537
1617
  }
1538
1618
  function getBrokerPackageVersion() {
1539
1619
  {
1540
- return "0.1.4";
1620
+ return "0.1.5";
1541
1621
  }
1542
1622
  }
1543
1623
  function printVersion() {
@@ -1563,6 +1643,19 @@ async function runSetup2(argv) {
1563
1643
  };
1564
1644
  return runSetup(argv, deps);
1565
1645
  }
1646
+ async function runStop2() {
1647
+ const config = loadMcpConfig();
1648
+ const deps = {
1649
+ print,
1650
+ printErr,
1651
+ newBrokerClient: (endpoint, timeoutMs) => new BrokerClient({ endpoint, timeoutMs }),
1652
+ killProcess: defaultKillProcess,
1653
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
1654
+ endpoint: config.brokerEndpoint,
1655
+ brokerTimeoutMs: config.brokerTimeoutMs
1656
+ };
1657
+ return runStop(deps);
1658
+ }
1566
1659
  async function runCli(argv) {
1567
1660
  const [sub, ...rest] = argv;
1568
1661
  switch (sub) {
@@ -1571,6 +1664,8 @@ async function runCli(argv) {
1571
1664
  return 0;
1572
1665
  case "setup":
1573
1666
  return runSetup2(rest);
1667
+ case "stop":
1668
+ return runStop2();
1574
1669
  case "login":
1575
1670
  return runLogin(rest);
1576
1671
  case "logout":
@@ -1592,4 +1687,4 @@ async function runCli(argv) {
1592
1687
  }
1593
1688
  }
1594
1689
 
1595
- export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup2 as runSetup };
1690
+ export { getBrokerPackageVersion, parseLoginFlags, runCli, runDoctor, runLogin, runLogout, runSetup2 as runSetup, runStop2 as runStop };
package/dist/index.cjs CHANGED
@@ -1210,7 +1210,7 @@ var SERVER_NAME = "@muhaven/mcp";
1210
1210
  var SERVER_VERSION = resolveServerVersion();
1211
1211
  function resolveServerVersion() {
1212
1212
  {
1213
- return "0.1.4";
1213
+ return "0.1.5";
1214
1214
  }
1215
1215
  }
1216
1216
  function toJsonInputSchema(schema) {
@@ -1794,7 +1794,8 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
1794
1794
  sessionKeyAddress: signer.address,
1795
1795
  hasJwt,
1796
1796
  hasSessionKey,
1797
- ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
1797
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {},
1798
+ ...options.pid !== void 0 ? { pid: options.pid } : {}
1798
1799
  };
1799
1800
  }
1800
1801
  case "sign_hash": {
@@ -2025,7 +2026,8 @@ var BrokerDaemon = class {
2025
2026
  effectiveConfig: {
2026
2027
  backendBaseUrl: this.config.backendBaseUrl,
2027
2028
  dashboardBaseUrl: this.config.dashboardBaseUrl
2028
- }
2029
+ },
2030
+ pid: process.pid
2029
2031
  }
2030
2032
  );
2031
2033
  socket.end(serializeResponse(res));
package/dist/index.d.cts CHANGED
@@ -18,6 +18,12 @@ import { z } from 'zod';
18
18
  * keeper of the device-flow JWT (per ADR-3 D1 "polling, not loopback
19
19
  * callback") in addition to the session-key private half.
20
20
  *
21
+ * @muhaven/mcp@0.1.5 added `hello.pid` (still protocol 0.3.0 — additive
22
+ * optional field) so `muhaven-broker stop` can reach into the daemon
23
+ * process by PID without forcing operators to grep `ps` output. Older
24
+ * 0.1.4 daemons omit the field; `runStop` falls back to a structured
25
+ * error message in that case.
26
+ *
21
27
  * Threat-model invariants:
22
28
  * - The broker NEVER reaches out to the network. It only:
23
29
  * (a) signs hashes that the MCP server received from the backend,
@@ -94,6 +100,13 @@ interface BrokerHelloResponse {
94
100
  readonly backendBaseUrl: string;
95
101
  readonly dashboardBaseUrl: string;
96
102
  };
103
+ /**
104
+ * OS process id of the daemon. Surfaced so `muhaven-broker stop` can
105
+ * `process.kill(pid, 'SIGTERM')` without grepping `ps` output. Added in
106
+ * @muhaven/mcp@0.1.5 (no protocol-version bump — additive optional
107
+ * field). Older daemons omit; consumers MUST handle `undefined`.
108
+ */
109
+ readonly pid?: number;
97
110
  }
98
111
  interface BrokerSignHashResponse {
99
112
  readonly type: 'sign_hash';
@@ -804,6 +817,13 @@ interface HandleBrokerRequestOptions {
804
817
  readonly backendBaseUrl: string;
805
818
  readonly dashboardBaseUrl: string;
806
819
  };
820
+ /**
821
+ * The daemon's own OS PID. Injectable so unit tests can pin a value;
822
+ * production callers pass `process.pid` from the daemon process at
823
+ * handler-construction time. Surfaced via `hello.pid` so
824
+ * `muhaven-broker stop` can SIGTERM by PID. Added in @muhaven/mcp@0.1.5.
825
+ */
826
+ pid?: number;
807
827
  }
808
828
  declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number, options?: HandleBrokerRequestOptions): Promise<BrokerResponse>;
809
829
  declare class BrokerDaemon {
package/dist/index.d.ts CHANGED
@@ -18,6 +18,12 @@ import { z } from 'zod';
18
18
  * keeper of the device-flow JWT (per ADR-3 D1 "polling, not loopback
19
19
  * callback") in addition to the session-key private half.
20
20
  *
21
+ * @muhaven/mcp@0.1.5 added `hello.pid` (still protocol 0.3.0 — additive
22
+ * optional field) so `muhaven-broker stop` can reach into the daemon
23
+ * process by PID without forcing operators to grep `ps` output. Older
24
+ * 0.1.4 daemons omit the field; `runStop` falls back to a structured
25
+ * error message in that case.
26
+ *
21
27
  * Threat-model invariants:
22
28
  * - The broker NEVER reaches out to the network. It only:
23
29
  * (a) signs hashes that the MCP server received from the backend,
@@ -94,6 +100,13 @@ interface BrokerHelloResponse {
94
100
  readonly backendBaseUrl: string;
95
101
  readonly dashboardBaseUrl: string;
96
102
  };
103
+ /**
104
+ * OS process id of the daemon. Surfaced so `muhaven-broker stop` can
105
+ * `process.kill(pid, 'SIGTERM')` without grepping `ps` output. Added in
106
+ * @muhaven/mcp@0.1.5 (no protocol-version bump — additive optional
107
+ * field). Older daemons omit; consumers MUST handle `undefined`.
108
+ */
109
+ readonly pid?: number;
97
110
  }
98
111
  interface BrokerSignHashResponse {
99
112
  readonly type: 'sign_hash';
@@ -804,6 +817,13 @@ interface HandleBrokerRequestOptions {
804
817
  readonly backendBaseUrl: string;
805
818
  readonly dashboardBaseUrl: string;
806
819
  };
820
+ /**
821
+ * The daemon's own OS PID. Injectable so unit tests can pin a value;
822
+ * production callers pass `process.pid` from the daemon process at
823
+ * handler-construction time. Surfaced via `hello.pid` so
824
+ * `muhaven-broker stop` can SIGTERM by PID. Added in @muhaven/mcp@0.1.5.
825
+ */
826
+ pid?: number;
807
827
  }
808
828
  declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number, options?: HandleBrokerRequestOptions): Promise<BrokerResponse>;
809
829
  declare class BrokerDaemon {
package/dist/index.js CHANGED
@@ -1206,7 +1206,7 @@ var SERVER_NAME = "@muhaven/mcp";
1206
1206
  var SERVER_VERSION = resolveServerVersion();
1207
1207
  function resolveServerVersion() {
1208
1208
  {
1209
- return "0.1.4";
1209
+ return "0.1.5";
1210
1210
  }
1211
1211
  }
1212
1212
  function toJsonInputSchema(schema) {
@@ -1790,7 +1790,8 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
1790
1790
  sessionKeyAddress: signer.address,
1791
1791
  hasJwt,
1792
1792
  hasSessionKey,
1793
- ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
1793
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {},
1794
+ ...options.pid !== void 0 ? { pid: options.pid } : {}
1794
1795
  };
1795
1796
  }
1796
1797
  case "sign_hash": {
@@ -2021,7 +2022,8 @@ var BrokerDaemon = class {
2021
2022
  effectiveConfig: {
2022
2023
  backendBaseUrl: this.config.backendBaseUrl,
2023
2024
  dashboardBaseUrl: this.config.dashboardBaseUrl
2024
- }
2025
+ },
2026
+ pid: process.pid
2025
2027
  }
2026
2028
  );
2027
2029
  socket.end(serializeResponse(res));
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.4",
6
+ "version": "0.1.5",
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.4",
3
+ "version": "0.1.5",
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": {