@muhaven/mcp 0.4.0 → 0.4.2

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,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.2] — 2026-05-24
11
+
12
+ ### Fixed
13
+
14
+ - **Revoke kill-switch — MCP-side gate (defense-in-depth).** A revoked
15
+ Scoped session let an already-running broker keep buying autonomously
16
+ (the broker signs from a local snapshot with no "revoked" concept and the
17
+ on-chain validator stays installed). `position.buy` Path D now hard-gates
18
+ on the backend mirror: when the broker hands over an active snapshot but
19
+ `GET /agent/policy/scoped-session` reports NO active session (= revoked or
20
+ expired on the dashboard), the buy refuses with the new
21
+ `pathDFallbackReason: 'session_revoked'`, best-effort calls the broker's
22
+ `clear_policy_snapshot` to purge the dormant key-backed snapshot, and
23
+ falls back to the Path C deep-link. A transient mirror-fetch ERROR is
24
+ still treated as best-effort (not "revoked") so a backend blip doesn't
25
+ break Path D. The authoritative enforcement is the server-side
26
+ `encrypt-shares` gate (backend change, same release window); this MCP
27
+ check is the fast, clear fail before the encrypt round-trip. SecEng
28
+ investigation 2026-05-24.
29
+
30
+ ## [0.4.1] — 2026-05-24
31
+
32
+ ### Added
33
+
34
+ - **Default broker chain RPC + `--broker-rpc-url` override (OPEN-D
35
+ follow-up).** The broker daemon needs a chain RPC URL for the Path D
36
+ `currentNonce()` pre-check. Previously, with neither
37
+ `MUHAVEN_BROKER_RPC_URL` nor `MUHAVEN_BUNDLER_URL` set, `chainRpcUrl`
38
+ was undefined → the `current_nonce` IPC returned `chain_rpc_failed` and
39
+ Path D fell back to the deep-link. Now:
40
+ - `loadBrokerConfig` **defaults `chainRpcUrl` to the public Arb Sepolia
41
+ RPC** (`https://sepolia-rollup.arbitrum.io/rpc`) when no RPC env is
42
+ set, so Path D works out-of-the-box. Resolution order is unchanged
43
+ where set: `MUHAVEN_BROKER_RPC_URL` → `MUHAVEN_BUNDLER_URL` →
44
+ default. (The broker's only use is a read-only `eth_call`, which the
45
+ public RPC serves.)
46
+ - `muhaven-broker start` / `update` / `setup` gained a
47
+ **`--broker-rpc-url <URL>`** flag to point the spawned daemon at a
48
+ private/faster RPC without exporting an env var. Validated by the same
49
+ https-or-loopback rule as the other URL flags; forwarded into the
50
+ spawned daemon's child env. When omitted, the daemon inherits a
51
+ shell-set value or falls back to the default above.
52
+
53
+ ### Changed
54
+
55
+ - `BrokerRuntimeConfig.chainRpcUrl` is now always populated (default
56
+ applied) rather than possibly `undefined`. The type stays optional for
57
+ test-injected configs; the `current_nonce` handler's
58
+ `chain_rpc_failed`-when-unset path remains as a backstop.
59
+
10
60
  ## [0.4.0] — 2026-05-24
11
61
 
12
62
  ### Added
package/dist/broker.cjs CHANGED
@@ -18,6 +18,7 @@ var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
18
18
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
19
19
  var DEFAULT_BUNDLER_TIMEOUT_MS = 2e4;
20
20
  var DEFAULT_CHAIN_ID = 421614;
21
+ var DEFAULT_BROKER_RPC_URL = "https://sepolia-rollup.arbitrum.io/rpc";
21
22
  var DEFAULT_ENTRY_POINT_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
22
23
  var ADDRESS_HEX_RE = /^0x[0-9a-fA-F]{40}$/;
23
24
  function defaultBrokerEndpoint() {
@@ -163,8 +164,8 @@ function loadBrokerConfig(env = process.env) {
163
164
  env.MUHAVEN_DASHBOARD_URL,
164
165
  DEFAULT_DASHBOARD_URL
165
166
  );
166
- const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env);
167
- const chainRpcUrl = chainRpcUrlRaw === void 0 ? void 0 : resolvePublicUrlEnv(
167
+ const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env) ?? DEFAULT_BROKER_RPC_URL;
168
+ const chainRpcUrl = resolvePublicUrlEnv(
168
169
  "MUHAVEN_BROKER_RPC_URL",
169
170
  chainRpcUrlRaw,
170
171
  chainRpcUrlRaw
@@ -2568,6 +2569,7 @@ function parseSetupFlags(argv) {
2568
2569
  let brokerEndpoint;
2569
2570
  let backendBaseUrl;
2570
2571
  let dashboardBaseUrl;
2572
+ let brokerRpcUrl;
2571
2573
  let skipLogin = false;
2572
2574
  const register = [];
2573
2575
  let registerScope = "user";
@@ -2579,6 +2581,7 @@ function parseSetupFlags(argv) {
2579
2581
  else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
2580
2582
  else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
2581
2583
  else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
2584
+ else if (a === "--broker-rpc-url" && i + 1 < argv.length) brokerRpcUrl = argv[++i];
2582
2585
  else if (a === "--register" && i + 1 < argv.length) {
2583
2586
  const value = argv[++i];
2584
2587
  for (const raw of value.split(",")) {
@@ -2609,6 +2612,7 @@ function parseSetupFlags(argv) {
2609
2612
  brokerEndpoint,
2610
2613
  backendBaseUrl,
2611
2614
  dashboardBaseUrl,
2615
+ brokerRpcUrl,
2612
2616
  skipLogin,
2613
2617
  register,
2614
2618
  registerScope
@@ -2732,7 +2736,7 @@ async function runSetup(argv, deps) {
2732
2736
  } catch (err) {
2733
2737
  deps.printErr(`error: ${err.message}`);
2734
2738
  deps.printErr(
2735
- "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]"
2739
+ "usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL] [--broker-rpc-url URL]\n [--register HOST[,HOST...]] [--register-scope user|project|local]"
2736
2740
  );
2737
2741
  return 2;
2738
2742
  }
@@ -2757,6 +2761,13 @@ async function runSetup(argv, deps) {
2757
2761
  return 2;
2758
2762
  }
2759
2763
  }
2764
+ if (flags.brokerRpcUrl) {
2765
+ const err = validateHttpUrlFlag("--broker-rpc-url", flags.brokerRpcUrl);
2766
+ if (err) {
2767
+ deps.printErr(`error: ${err}`);
2768
+ return 2;
2769
+ }
2770
+ }
2760
2771
  const overrides = applyEnvDefaults({
2761
2772
  env: deps.env,
2762
2773
  platformId: deps.platformId,
@@ -2772,6 +2783,7 @@ async function runSetup(argv, deps) {
2772
2783
  if (flags.brokerEndpoint) effectiveEnv.MUHAVEN_BROKER_ENDPOINT = flags.brokerEndpoint;
2773
2784
  if (flags.backendBaseUrl) effectiveEnv.MUHAVEN_BACKEND_URL = flags.backendBaseUrl;
2774
2785
  if (flags.dashboardBaseUrl) effectiveEnv.MUHAVEN_DASHBOARD_URL = flags.dashboardBaseUrl;
2786
+ if (flags.brokerRpcUrl) effectiveEnv.MUHAVEN_BROKER_RPC_URL = flags.brokerRpcUrl;
2775
2787
  for (const name of overrides.preserved) {
2776
2788
  deps.print(`Env preserved: ${name} (set in your shell)`);
2777
2789
  }
@@ -2854,7 +2866,11 @@ async function runSetup(argv, deps) {
2854
2866
  MUHAVEN_BROKER_ENDPOINT: config.brokerEndpoint,
2855
2867
  MUHAVEN_BACKEND_URL: effectiveEnv.MUHAVEN_BACKEND_URL,
2856
2868
  MUHAVEN_DASHBOARD_URL: effectiveEnv.MUHAVEN_DASHBOARD_URL,
2857
- MUHAVEN_BROKER_SESSION_KEY: sessionKey
2869
+ MUHAVEN_BROKER_SESSION_KEY: sessionKey,
2870
+ // Forward the chain RPC URL only when resolved (flag or shell env);
2871
+ // absent → the daemon's loadBrokerConfig applies the public Arb
2872
+ // Sepolia default.
2873
+ ...effectiveEnv.MUHAVEN_BROKER_RPC_URL ? { MUHAVEN_BROKER_RPC_URL: effectiveEnv.MUHAVEN_BROKER_RPC_URL } : {}
2858
2874
  }
2859
2875
  });
2860
2876
  try {
@@ -3047,6 +3063,7 @@ function parseBringUpFlags(argv) {
3047
3063
  let brokerEndpoint;
3048
3064
  let backendBaseUrl;
3049
3065
  let dashboardBaseUrl;
3066
+ let brokerRpcUrl;
3050
3067
  for (let i = 0; i < argv.length; i++) {
3051
3068
  const a = argv[i];
3052
3069
  if (a === "--no-launch-browser") noLaunchBrowser = true;
@@ -3063,14 +3080,23 @@ function parseBringUpFlags(argv) {
3063
3080
  } else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
3064
3081
  else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
3065
3082
  else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
3083
+ else if (a === "--broker-rpc-url" && i + 1 < argv.length) brokerRpcUrl = argv[++i];
3066
3084
  else throw new Error(`unknown flag: ${a}`);
3067
3085
  }
3068
- return { session, noLaunchBrowser, skipLogin, brokerEndpoint, backendBaseUrl, dashboardBaseUrl };
3086
+ return {
3087
+ session,
3088
+ noLaunchBrowser,
3089
+ skipLogin,
3090
+ brokerEndpoint,
3091
+ backendBaseUrl,
3092
+ dashboardBaseUrl,
3093
+ brokerRpcUrl
3094
+ };
3069
3095
  }
3070
3096
  function usageLine(mode) {
3071
3097
  return `usage: muhaven-broker ${mode} --session <key|-> [--no-launch-browser] [--skip-login]
3072
3098
  [--broker-endpoint PATH] [--backend-base-url URL]
3073
- [--dashboard-base-url URL]
3099
+ [--dashboard-base-url URL] [--broker-rpc-url URL]
3074
3100
  (omit --session to be asked interactively; pipe the key with \`--session -\`)`;
3075
3101
  }
3076
3102
  async function runBringUp(mode, argv, deps) {
@@ -3103,6 +3129,13 @@ async function runBringUp(mode, argv, deps) {
3103
3129
  return 2;
3104
3130
  }
3105
3131
  }
3132
+ if (flags.brokerRpcUrl) {
3133
+ const e = validateHttpUrlFlag("--broker-rpc-url", flags.brokerRpcUrl);
3134
+ if (e) {
3135
+ deps.printErr(`error: ${e}`);
3136
+ return 2;
3137
+ }
3138
+ }
3106
3139
  const resolution = await resolveSessionKey({
3107
3140
  sessionFlag: flags.session,
3108
3141
  policy: "require",
@@ -3127,6 +3160,7 @@ async function runBringUp(mode, argv, deps) {
3127
3160
  if (flags.brokerEndpoint) effectiveEnv.MUHAVEN_BROKER_ENDPOINT = flags.brokerEndpoint;
3128
3161
  if (flags.backendBaseUrl) effectiveEnv.MUHAVEN_BACKEND_URL = flags.backendBaseUrl;
3129
3162
  if (flags.dashboardBaseUrl) effectiveEnv.MUHAVEN_DASHBOARD_URL = flags.dashboardBaseUrl;
3163
+ if (flags.brokerRpcUrl) effectiveEnv.MUHAVEN_BROKER_RPC_URL = flags.brokerRpcUrl;
3130
3164
  for (const name of overrides.preserved) deps.print(`Env preserved: ${name} (set in your shell)`);
3131
3165
  for (const [k, v] of Object.entries(overrides.toSet)) deps.print(`Env defaulted: ${k}=${v}`);
3132
3166
  const config = loadMcpConfig(effectiveEnv);
@@ -3167,7 +3201,11 @@ async function runBringUp(mode, argv, deps) {
3167
3201
  MUHAVEN_BROKER_ENDPOINT: config.brokerEndpoint,
3168
3202
  MUHAVEN_BACKEND_URL: effectiveEnv.MUHAVEN_BACKEND_URL,
3169
3203
  MUHAVEN_DASHBOARD_URL: effectiveEnv.MUHAVEN_DASHBOARD_URL,
3170
- MUHAVEN_BROKER_SESSION_KEY: sessionKey
3204
+ MUHAVEN_BROKER_SESSION_KEY: sessionKey,
3205
+ // Forward the chain RPC URL only when resolved (flag or shell env).
3206
+ // When absent, the daemon's loadBrokerConfig applies the public
3207
+ // Arb Sepolia default — no need to inject it here.
3208
+ ...effectiveEnv.MUHAVEN_BROKER_RPC_URL ? { MUHAVEN_BROKER_RPC_URL: effectiveEnv.MUHAVEN_BROKER_RPC_URL } : {}
3171
3209
  }
3172
3210
  });
3173
3211
  let ready;
@@ -3538,8 +3576,10 @@ function printUsage() {
3538
3576
  print(" start Bring the daemon up on a DASHBOARD-minted session key (daemon NOT running)");
3539
3577
  print(" --session <key|-> the key (or `-` to read it from stdin); omit to be");
3540
3578
  print(" asked interactively. [--skip-login] [--no-launch-browser]");
3579
+ print(" [--broker-rpc-url URL] chain RPC for Path D (default: public Arb Sepolia)");
3541
3580
  print(" update Rotate the session key on a running daemon (stop \u2192 swap \u2192 restart,");
3542
3581
  print(" reusing the existing JWT). --session <key|-> (or interactive).");
3582
+ print(" [--broker-rpc-url URL] override the daemon chain RPC for Path D");
3543
3583
  print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
3544
3584
  print(" after 5s). Also clears the keystore JWT as a best effort.");
3545
3585
  print(" login Acquire a JWT via the device-code flow + store in keystore");
@@ -3551,7 +3591,7 @@ function printUsage() {
3551
3591
  }
3552
3592
  function getBrokerPackageVersion() {
3553
3593
  {
3554
- return "0.4.0";
3594
+ return "0.4.2";
3555
3595
  }
3556
3596
  }
3557
3597
  function printVersion() {
package/dist/broker.js CHANGED
@@ -20,6 +20,7 @@ var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
20
20
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
21
21
  var DEFAULT_BUNDLER_TIMEOUT_MS = 2e4;
22
22
  var DEFAULT_CHAIN_ID = 421614;
23
+ var DEFAULT_BROKER_RPC_URL = "https://sepolia-rollup.arbitrum.io/rpc";
23
24
  var DEFAULT_ENTRY_POINT_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
24
25
  var ADDRESS_HEX_RE = /^0x[0-9a-fA-F]{40}$/;
25
26
  function defaultBrokerEndpoint() {
@@ -165,8 +166,8 @@ function loadBrokerConfig(env = process.env) {
165
166
  env.MUHAVEN_DASHBOARD_URL,
166
167
  DEFAULT_DASHBOARD_URL
167
168
  );
168
- const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env);
169
- const chainRpcUrl = chainRpcUrlRaw === void 0 ? void 0 : resolvePublicUrlEnv(
169
+ const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env) ?? DEFAULT_BROKER_RPC_URL;
170
+ const chainRpcUrl = resolvePublicUrlEnv(
170
171
  "MUHAVEN_BROKER_RPC_URL",
171
172
  chainRpcUrlRaw,
172
173
  chainRpcUrlRaw
@@ -2570,6 +2571,7 @@ function parseSetupFlags(argv) {
2570
2571
  let brokerEndpoint;
2571
2572
  let backendBaseUrl;
2572
2573
  let dashboardBaseUrl;
2574
+ let brokerRpcUrl;
2573
2575
  let skipLogin = false;
2574
2576
  const register = [];
2575
2577
  let registerScope = "user";
@@ -2581,6 +2583,7 @@ function parseSetupFlags(argv) {
2581
2583
  else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
2582
2584
  else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
2583
2585
  else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
2586
+ else if (a === "--broker-rpc-url" && i + 1 < argv.length) brokerRpcUrl = argv[++i];
2584
2587
  else if (a === "--register" && i + 1 < argv.length) {
2585
2588
  const value = argv[++i];
2586
2589
  for (const raw of value.split(",")) {
@@ -2611,6 +2614,7 @@ function parseSetupFlags(argv) {
2611
2614
  brokerEndpoint,
2612
2615
  backendBaseUrl,
2613
2616
  dashboardBaseUrl,
2617
+ brokerRpcUrl,
2614
2618
  skipLogin,
2615
2619
  register,
2616
2620
  registerScope
@@ -2734,7 +2738,7 @@ async function runSetup(argv, deps) {
2734
2738
  } catch (err) {
2735
2739
  deps.printErr(`error: ${err.message}`);
2736
2740
  deps.printErr(
2737
- "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]"
2741
+ "usage: muhaven-broker setup [--foreground|-f] [--no-launch-browser] [--skip-login]\n [--broker-endpoint PATH] [--backend-base-url URL]\n [--dashboard-base-url URL] [--broker-rpc-url URL]\n [--register HOST[,HOST...]] [--register-scope user|project|local]"
2738
2742
  );
2739
2743
  return 2;
2740
2744
  }
@@ -2759,6 +2763,13 @@ async function runSetup(argv, deps) {
2759
2763
  return 2;
2760
2764
  }
2761
2765
  }
2766
+ if (flags.brokerRpcUrl) {
2767
+ const err = validateHttpUrlFlag("--broker-rpc-url", flags.brokerRpcUrl);
2768
+ if (err) {
2769
+ deps.printErr(`error: ${err}`);
2770
+ return 2;
2771
+ }
2772
+ }
2762
2773
  const overrides = applyEnvDefaults({
2763
2774
  env: deps.env,
2764
2775
  platformId: deps.platformId,
@@ -2774,6 +2785,7 @@ async function runSetup(argv, deps) {
2774
2785
  if (flags.brokerEndpoint) effectiveEnv.MUHAVEN_BROKER_ENDPOINT = flags.brokerEndpoint;
2775
2786
  if (flags.backendBaseUrl) effectiveEnv.MUHAVEN_BACKEND_URL = flags.backendBaseUrl;
2776
2787
  if (flags.dashboardBaseUrl) effectiveEnv.MUHAVEN_DASHBOARD_URL = flags.dashboardBaseUrl;
2788
+ if (flags.brokerRpcUrl) effectiveEnv.MUHAVEN_BROKER_RPC_URL = flags.brokerRpcUrl;
2777
2789
  for (const name of overrides.preserved) {
2778
2790
  deps.print(`Env preserved: ${name} (set in your shell)`);
2779
2791
  }
@@ -2856,7 +2868,11 @@ async function runSetup(argv, deps) {
2856
2868
  MUHAVEN_BROKER_ENDPOINT: config.brokerEndpoint,
2857
2869
  MUHAVEN_BACKEND_URL: effectiveEnv.MUHAVEN_BACKEND_URL,
2858
2870
  MUHAVEN_DASHBOARD_URL: effectiveEnv.MUHAVEN_DASHBOARD_URL,
2859
- MUHAVEN_BROKER_SESSION_KEY: sessionKey
2871
+ MUHAVEN_BROKER_SESSION_KEY: sessionKey,
2872
+ // Forward the chain RPC URL only when resolved (flag or shell env);
2873
+ // absent → the daemon's loadBrokerConfig applies the public Arb
2874
+ // Sepolia default.
2875
+ ...effectiveEnv.MUHAVEN_BROKER_RPC_URL ? { MUHAVEN_BROKER_RPC_URL: effectiveEnv.MUHAVEN_BROKER_RPC_URL } : {}
2860
2876
  }
2861
2877
  });
2862
2878
  try {
@@ -3049,6 +3065,7 @@ function parseBringUpFlags(argv) {
3049
3065
  let brokerEndpoint;
3050
3066
  let backendBaseUrl;
3051
3067
  let dashboardBaseUrl;
3068
+ let brokerRpcUrl;
3052
3069
  for (let i = 0; i < argv.length; i++) {
3053
3070
  const a = argv[i];
3054
3071
  if (a === "--no-launch-browser") noLaunchBrowser = true;
@@ -3065,14 +3082,23 @@ function parseBringUpFlags(argv) {
3065
3082
  } else if (a === "--broker-endpoint" && i + 1 < argv.length) brokerEndpoint = argv[++i];
3066
3083
  else if (a === "--backend-base-url" && i + 1 < argv.length) backendBaseUrl = argv[++i];
3067
3084
  else if (a === "--dashboard-base-url" && i + 1 < argv.length) dashboardBaseUrl = argv[++i];
3085
+ else if (a === "--broker-rpc-url" && i + 1 < argv.length) brokerRpcUrl = argv[++i];
3068
3086
  else throw new Error(`unknown flag: ${a}`);
3069
3087
  }
3070
- return { session, noLaunchBrowser, skipLogin, brokerEndpoint, backendBaseUrl, dashboardBaseUrl };
3088
+ return {
3089
+ session,
3090
+ noLaunchBrowser,
3091
+ skipLogin,
3092
+ brokerEndpoint,
3093
+ backendBaseUrl,
3094
+ dashboardBaseUrl,
3095
+ brokerRpcUrl
3096
+ };
3071
3097
  }
3072
3098
  function usageLine(mode) {
3073
3099
  return `usage: muhaven-broker ${mode} --session <key|-> [--no-launch-browser] [--skip-login]
3074
3100
  [--broker-endpoint PATH] [--backend-base-url URL]
3075
- [--dashboard-base-url URL]
3101
+ [--dashboard-base-url URL] [--broker-rpc-url URL]
3076
3102
  (omit --session to be asked interactively; pipe the key with \`--session -\`)`;
3077
3103
  }
3078
3104
  async function runBringUp(mode, argv, deps) {
@@ -3105,6 +3131,13 @@ async function runBringUp(mode, argv, deps) {
3105
3131
  return 2;
3106
3132
  }
3107
3133
  }
3134
+ if (flags.brokerRpcUrl) {
3135
+ const e = validateHttpUrlFlag("--broker-rpc-url", flags.brokerRpcUrl);
3136
+ if (e) {
3137
+ deps.printErr(`error: ${e}`);
3138
+ return 2;
3139
+ }
3140
+ }
3108
3141
  const resolution = await resolveSessionKey({
3109
3142
  sessionFlag: flags.session,
3110
3143
  policy: "require",
@@ -3129,6 +3162,7 @@ async function runBringUp(mode, argv, deps) {
3129
3162
  if (flags.brokerEndpoint) effectiveEnv.MUHAVEN_BROKER_ENDPOINT = flags.brokerEndpoint;
3130
3163
  if (flags.backendBaseUrl) effectiveEnv.MUHAVEN_BACKEND_URL = flags.backendBaseUrl;
3131
3164
  if (flags.dashboardBaseUrl) effectiveEnv.MUHAVEN_DASHBOARD_URL = flags.dashboardBaseUrl;
3165
+ if (flags.brokerRpcUrl) effectiveEnv.MUHAVEN_BROKER_RPC_URL = flags.brokerRpcUrl;
3132
3166
  for (const name of overrides.preserved) deps.print(`Env preserved: ${name} (set in your shell)`);
3133
3167
  for (const [k, v] of Object.entries(overrides.toSet)) deps.print(`Env defaulted: ${k}=${v}`);
3134
3168
  const config = loadMcpConfig(effectiveEnv);
@@ -3169,7 +3203,11 @@ async function runBringUp(mode, argv, deps) {
3169
3203
  MUHAVEN_BROKER_ENDPOINT: config.brokerEndpoint,
3170
3204
  MUHAVEN_BACKEND_URL: effectiveEnv.MUHAVEN_BACKEND_URL,
3171
3205
  MUHAVEN_DASHBOARD_URL: effectiveEnv.MUHAVEN_DASHBOARD_URL,
3172
- MUHAVEN_BROKER_SESSION_KEY: sessionKey
3206
+ MUHAVEN_BROKER_SESSION_KEY: sessionKey,
3207
+ // Forward the chain RPC URL only when resolved (flag or shell env).
3208
+ // When absent, the daemon's loadBrokerConfig applies the public
3209
+ // Arb Sepolia default — no need to inject it here.
3210
+ ...effectiveEnv.MUHAVEN_BROKER_RPC_URL ? { MUHAVEN_BROKER_RPC_URL: effectiveEnv.MUHAVEN_BROKER_RPC_URL } : {}
3173
3211
  }
3174
3212
  });
3175
3213
  let ready;
@@ -3540,8 +3578,10 @@ function printUsage() {
3540
3578
  print(" start Bring the daemon up on a DASHBOARD-minted session key (daemon NOT running)");
3541
3579
  print(" --session <key|-> the key (or `-` to read it from stdin); omit to be");
3542
3580
  print(" asked interactively. [--skip-login] [--no-launch-browser]");
3581
+ print(" [--broker-rpc-url URL] chain RPC for Path D (default: public Arb Sepolia)");
3543
3582
  print(" update Rotate the session key on a running daemon (stop \u2192 swap \u2192 restart,");
3544
3583
  print(" reusing the existing JWT). --session <key|-> (or interactive).");
3584
+ print(" [--broker-rpc-url URL] override the daemon chain RPC for Path D");
3545
3585
  print(" stop Cleanly stop a running daemon (SIGTERM with SIGKILL fallback");
3546
3586
  print(" after 5s). Also clears the keystore JWT as a best effort.");
3547
3587
  print(" login Acquire a JWT via the device-code flow + store in keystore");
@@ -3553,7 +3593,7 @@ function printUsage() {
3553
3593
  }
3554
3594
  function getBrokerPackageVersion() {
3555
3595
  {
3556
- return "0.4.0";
3596
+ return "0.4.2";
3557
3597
  }
3558
3598
  }
3559
3599
  function printVersion() {
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
28
28
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
29
29
  var DEFAULT_BUNDLER_TIMEOUT_MS = 2e4;
30
30
  var DEFAULT_CHAIN_ID = 421614;
31
+ var DEFAULT_BROKER_RPC_URL = "https://sepolia-rollup.arbitrum.io/rpc";
31
32
  var DEFAULT_ENTRY_POINT_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
32
33
  var ADDRESS_HEX_RE = /^0x[0-9a-fA-F]{40}$/;
33
34
  function defaultBrokerEndpoint() {
@@ -173,8 +174,8 @@ function loadBrokerConfig(env = process.env) {
173
174
  env.MUHAVEN_DASHBOARD_URL,
174
175
  DEFAULT_DASHBOARD_URL
175
176
  );
176
- const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env);
177
- const chainRpcUrl = chainRpcUrlRaw === void 0 ? void 0 : resolvePublicUrlEnv(
177
+ const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env) ?? DEFAULT_BROKER_RPC_URL;
178
+ const chainRpcUrl = resolvePublicUrlEnv(
178
179
  "MUHAVEN_BROKER_RPC_URL",
179
180
  chainRpcUrlRaw,
180
181
  chainRpcUrlRaw
@@ -2798,18 +2799,33 @@ async function attemptPathD(args, deps) {
2798
2799
  message: `backend /agent/policy/state lookup failed: ${err2 instanceof Error ? err2.message : String(err2)}`
2799
2800
  };
2800
2801
  }
2802
+ let mirrorFetchOk = false;
2803
+ let mirrorHadActiveSession = false;
2801
2804
  try {
2802
2805
  const mirror = await deps.backend.get(
2803
2806
  "/api/v1/agent/policy/scoped-session",
2804
2807
  { surface: "mcp" }
2805
2808
  );
2809
+ mirrorFetchOk = true;
2806
2810
  if (mirror?.session) {
2811
+ mirrorHadActiveSession = true;
2807
2812
  mirrorSessionRow = mirror.session;
2808
2813
  mirrorEnableStatus = mirror.session.enableStatus ?? null;
2809
2814
  mirrorValidatorNonce = mirror.session.validatorNonce ?? null;
2810
2815
  }
2811
2816
  } catch (err2) {
2812
2817
  }
2818
+ if (mirrorFetchOk && !mirrorHadActiveSession) {
2819
+ try {
2820
+ await deps.broker.clearPolicySnapshot(activeId);
2821
+ } catch {
2822
+ }
2823
+ return {
2824
+ kind: "fallback",
2825
+ reason: "session_revoked",
2826
+ message: "the Scoped session was revoked (or expired) on the dashboard \u2014 the broker snapshot is stale; purged it and falling back to Path C. Re-mint a Scoped session to resume autonomous buys."
2827
+ };
2828
+ }
2813
2829
  if (!snapshot.permissionId) {
2814
2830
  return {
2815
2831
  kind: "fallback",
@@ -3679,7 +3695,7 @@ var SERVER_NAME = "@muhaven/mcp";
3679
3695
  var SERVER_VERSION = resolveServerVersion();
3680
3696
  function resolveServerVersion() {
3681
3697
  {
3682
- return "0.4.0";
3698
+ return "0.4.2";
3683
3699
  }
3684
3700
  }
3685
3701
  function toJsonInputSchema(schema) {
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ var DEFAULT_BROKER_MAX_BYTES = 64 * 1024;
24
24
  var DEFAULT_JWT_CACHE_TTL_SEC = 30;
25
25
  var DEFAULT_BUNDLER_TIMEOUT_MS = 2e4;
26
26
  var DEFAULT_CHAIN_ID = 421614;
27
+ var DEFAULT_BROKER_RPC_URL = "https://sepolia-rollup.arbitrum.io/rpc";
27
28
  var DEFAULT_ENTRY_POINT_ADDRESS = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
28
29
  var ADDRESS_HEX_RE = /^0x[0-9a-fA-F]{40}$/;
29
30
  function defaultBrokerEndpoint() {
@@ -169,8 +170,8 @@ function loadBrokerConfig(env = process.env) {
169
170
  env.MUHAVEN_DASHBOARD_URL,
170
171
  DEFAULT_DASHBOARD_URL
171
172
  );
172
- const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env);
173
- const chainRpcUrl = chainRpcUrlRaw === void 0 ? void 0 : resolvePublicUrlEnv(
173
+ const chainRpcUrlRaw = readEnv("MUHAVEN_BROKER_RPC_URL", env) ?? readEnv("MUHAVEN_BUNDLER_URL", env) ?? DEFAULT_BROKER_RPC_URL;
174
+ const chainRpcUrl = resolvePublicUrlEnv(
174
175
  "MUHAVEN_BROKER_RPC_URL",
175
176
  chainRpcUrlRaw,
176
177
  chainRpcUrlRaw
@@ -2794,18 +2795,33 @@ async function attemptPathD(args, deps) {
2794
2795
  message: `backend /agent/policy/state lookup failed: ${err2 instanceof Error ? err2.message : String(err2)}`
2795
2796
  };
2796
2797
  }
2798
+ let mirrorFetchOk = false;
2799
+ let mirrorHadActiveSession = false;
2797
2800
  try {
2798
2801
  const mirror = await deps.backend.get(
2799
2802
  "/api/v1/agent/policy/scoped-session",
2800
2803
  { surface: "mcp" }
2801
2804
  );
2805
+ mirrorFetchOk = true;
2802
2806
  if (mirror?.session) {
2807
+ mirrorHadActiveSession = true;
2803
2808
  mirrorSessionRow = mirror.session;
2804
2809
  mirrorEnableStatus = mirror.session.enableStatus ?? null;
2805
2810
  mirrorValidatorNonce = mirror.session.validatorNonce ?? null;
2806
2811
  }
2807
2812
  } catch (err2) {
2808
2813
  }
2814
+ if (mirrorFetchOk && !mirrorHadActiveSession) {
2815
+ try {
2816
+ await deps.broker.clearPolicySnapshot(activeId);
2817
+ } catch {
2818
+ }
2819
+ return {
2820
+ kind: "fallback",
2821
+ reason: "session_revoked",
2822
+ message: "the Scoped session was revoked (or expired) on the dashboard \u2014 the broker snapshot is stale; purged it and falling back to Path C. Re-mint a Scoped session to resume autonomous buys."
2823
+ };
2824
+ }
2809
2825
  if (!snapshot.permissionId) {
2810
2826
  return {
2811
2827
  kind: "fallback",
@@ -3675,7 +3691,7 @@ var SERVER_NAME = "@muhaven/mcp";
3675
3691
  var SERVER_VERSION = resolveServerVersion();
3676
3692
  function resolveServerVersion() {
3677
3693
  {
3678
- return "0.4.0";
3694
+ return "0.4.2";
3679
3695
  }
3680
3696
  }
3681
3697
  function toJsonInputSchema(schema) {
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.4.0",
6
+ "version": "0.4.2",
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 24 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 deep-link to the dashboard for passkey signing — 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.4.0",
3
+ "version": "0.4.2",
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": {