@muhaven/mcp 0.1.2 → 0.1.3

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/dist/broker.js CHANGED
@@ -71,23 +71,26 @@ function loadMcpConfig(env = process.env) {
71
71
  }
72
72
  var PRIVKEY_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
73
73
  function loadBrokerConfig(env = process.env) {
74
- const sessionKeyHex = env.MUHAVEN_BROKER_SESSION_KEY;
75
- if (!sessionKeyHex) {
76
- throw new Error(
77
- "MUHAVEN_BROKER_SESSION_KEY is required (0x-prefixed 32-byte hex). Mint a session key via the dashboard policy-template install flow."
78
- );
79
- }
80
- if (!PRIVKEY_HEX_RE.test(sessionKeyHex)) {
81
- throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
74
+ const sessionKeyHexRaw = env.MUHAVEN_BROKER_SESSION_KEY;
75
+ let sessionKeyHex;
76
+ if (sessionKeyHexRaw && sessionKeyHexRaw.length > 0) {
77
+ if (!PRIVKEY_HEX_RE.test(sessionKeyHexRaw)) {
78
+ throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
79
+ }
80
+ sessionKeyHex = sessionKeyHexRaw;
82
81
  }
83
82
  const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
84
83
  const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
85
84
  const requestTimeoutMs = readEnvInt("MUHAVEN_BROKER_TIMEOUT_MS", DEFAULT_BROKER_TIMEOUT_MS, env);
85
+ const backendBaseUrl = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
86
+ const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
86
87
  return {
87
88
  endpoint,
88
89
  sessionKeyHex,
89
90
  maxRequestBytes,
90
- requestTimeoutMs
91
+ requestTimeoutMs,
92
+ backendBaseUrl,
93
+ dashboardBaseUrl
91
94
  };
92
95
  }
93
96
  var BrokerClientError = class extends Error {
@@ -520,7 +523,7 @@ async function openKeystore(options = {}) {
520
523
  }
521
524
 
522
525
  // src/broker/protocol.ts
523
- var BROKER_PROTOCOL_VERSION = "0.2.0";
526
+ var BROKER_PROTOCOL_VERSION = "0.3.0";
524
527
  var HASH_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
525
528
  var JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
526
529
  function isHashHex(value) {
@@ -606,6 +609,21 @@ function parseBrokerRequest(line) {
606
609
  function serializeResponse(res) {
607
610
  return JSON.stringify(res) + "\n";
608
611
  }
612
+ var MissingSessionKeyError = class extends Error {
613
+ constructor() {
614
+ super(
615
+ "session_key_unavailable: daemon booted in read-only posture (no MUHAVEN_BROKER_SESSION_KEY at env-load time). Mint a session key via the dashboard /agent/policy/transition flow, set MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. (Note: `muhaven-broker login` mints a JWT, NOT a session key \u2014 do not loop on that command for this error.)"
616
+ );
617
+ this.name = "MissingSessionKeyError";
618
+ }
619
+ };
620
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
621
+ var NullSigner = class {
622
+ address = ZERO_ADDRESS;
623
+ async signHash(_hash) {
624
+ throw new MissingSessionKeyError();
625
+ }
626
+ };
609
627
  var ViemSigner = class {
610
628
  account;
611
629
  constructor(privateKey) {
@@ -622,7 +640,7 @@ var ViemSigner = class {
622
640
  // src/broker/daemon.ts
623
641
  var noopLogger = (_e) => {
624
642
  };
625
- async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3)) {
643
+ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3), options = {}) {
626
644
  switch (req.type) {
627
645
  case "hello": {
628
646
  let hasJwt = false;
@@ -632,16 +650,26 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
632
650
  } catch {
633
651
  hasJwt = false;
634
652
  }
653
+ const hasSessionKey = options.hasSessionKey ?? true;
635
654
  return {
636
655
  type: "hello",
637
656
  version: BROKER_PROTOCOL_VERSION,
638
657
  sessionKeyAddress: signer.address,
639
- hasJwt
658
+ hasJwt,
659
+ hasSessionKey,
660
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
640
661
  };
641
662
  }
642
663
  case "sign_hash": {
643
- const signature = await signer.signHash(req.hash);
644
- return { type: "sign_hash", signature, signerAddress: signer.address };
664
+ try {
665
+ const signature = await signer.signHash(req.hash);
666
+ return { type: "sign_hash", signature, signerAddress: signer.address };
667
+ } catch (err) {
668
+ if (err instanceof MissingSessionKeyError) {
669
+ return errorResponse("session_key_unavailable", err.message);
670
+ }
671
+ throw err;
672
+ }
645
673
  }
646
674
  case "store_jwt": {
647
675
  try {
@@ -713,9 +741,24 @@ var BrokerDaemon = class {
713
741
  log;
714
742
  config;
715
743
  keystore;
744
+ /**
745
+ * Whether a session-key private half is actually loaded. `false` =
746
+ * daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
747
+ * at env-load time) and uses a `NullSigner` whose `signHash` throws.
748
+ */
749
+ hasSessionKey;
716
750
  constructor(options) {
717
751
  this.config = options.config;
718
- this.signer = options.signer ?? new ViemSigner(options.config.sessionKeyHex);
752
+ if (options.signer) {
753
+ this.signer = options.signer;
754
+ this.hasSessionKey = true;
755
+ } else if (options.config.sessionKeyHex) {
756
+ this.signer = new ViemSigner(options.config.sessionKeyHex);
757
+ this.hasSessionKey = true;
758
+ } else {
759
+ this.signer = new NullSigner();
760
+ this.hasSessionKey = false;
761
+ }
719
762
  this.keystore = options.keystore ?? null;
720
763
  this.log = options.logger ?? noopLogger;
721
764
  this.server = createServer((socket) => this.onConnection(socket));
@@ -753,6 +796,7 @@ var BrokerDaemon = class {
753
796
  meta: {
754
797
  endpoint: this.config.endpoint,
755
798
  signer: this.signer.address,
799
+ hasSessionKey: this.hasSessionKey,
756
800
  keystore: this.keystore.backend,
757
801
  version: BROKER_PROTOCOL_VERSION
758
802
  }
@@ -834,7 +878,19 @@ var BrokerDaemon = class {
834
878
  return;
835
879
  }
836
880
  try {
837
- const res = await handleBrokerRequest(parsed, this.signer, this.keystore);
881
+ const res = await handleBrokerRequest(
882
+ parsed,
883
+ this.signer,
884
+ this.keystore,
885
+ void 0,
886
+ {
887
+ hasSessionKey: this.hasSessionKey,
888
+ effectiveConfig: {
889
+ backendBaseUrl: this.config.backendBaseUrl,
890
+ dashboardBaseUrl: this.config.dashboardBaseUrl
891
+ }
892
+ }
893
+ );
838
894
  socket.end(serializeResponse(res));
839
895
  } catch (err) {
840
896
  this.log({
@@ -850,6 +906,15 @@ var BrokerDaemon = class {
850
906
  };
851
907
  async function runBrokerDaemonCli() {
852
908
  const config = loadBrokerConfig();
909
+ if (!config.sessionKeyHex) {
910
+ process.stderr.write(
911
+ JSON.stringify({
912
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
913
+ level: "info",
914
+ msg: "broker booting in read-only posture (no MUHAVEN_BROKER_SESSION_KEY)"
915
+ }) + "\n"
916
+ );
917
+ }
853
918
  const daemon = new BrokerDaemon({
854
919
  config,
855
920
  logger: (e) => {
@@ -902,9 +967,11 @@ function parseLoginFlags(argv) {
902
967
  let brokerEndpoint;
903
968
  let backendBaseUrl;
904
969
  let dashboardBaseUrl;
970
+ let fromDaemon = false;
905
971
  for (let i = 0; i < argv.length; i++) {
906
972
  const a = argv[i];
907
973
  if (a === "--no-launch-browser") noLaunchBrowser = true;
974
+ else if (a === "--from-daemon") fromDaemon = true;
908
975
  else if (a === "--broker-endpoint" && i + 1 < argv.length) {
909
976
  brokerEndpoint = argv[++i];
910
977
  } else if (a === "--backend-base-url" && i + 1 < argv.length) {
@@ -915,7 +982,12 @@ function parseLoginFlags(argv) {
915
982
  throw new Error(`unknown flag: ${a}`);
916
983
  }
917
984
  }
918
- return { noLaunchBrowser, brokerEndpoint, backendBaseUrl, dashboardBaseUrl };
985
+ if (fromDaemon && (backendBaseUrl || dashboardBaseUrl)) {
986
+ throw new Error(
987
+ "--from-daemon is mutually exclusive with --backend-base-url / --dashboard-base-url"
988
+ );
989
+ }
990
+ return { noLaunchBrowser, brokerEndpoint, backendBaseUrl, dashboardBaseUrl, fromDaemon };
919
991
  }
920
992
  async function tryLaunchBrowser(url) {
921
993
  return new Promise((resolve) => {
@@ -929,7 +1001,9 @@ async function runLogin(argv) {
929
1001
  flags = parseLoginFlags(argv);
930
1002
  } catch (err) {
931
1003
  printErr(`error: ${err.message}`);
932
- printErr("usage: muhaven-broker login [--no-launch-browser] [--broker-endpoint PATH] [--backend-base-url URL] [--dashboard-base-url URL]");
1004
+ printErr(
1005
+ "usage: muhaven-broker login [--no-launch-browser] [--broker-endpoint PATH] [--from-daemon | (--backend-base-url URL --dashboard-base-url URL)]"
1006
+ );
933
1007
  return 2;
934
1008
  }
935
1009
  const env = process.env;
@@ -943,8 +1017,9 @@ async function runLogin(argv) {
943
1017
  endpoint: config.brokerEndpoint,
944
1018
  timeoutMs: config.brokerTimeoutMs
945
1019
  });
1020
+ let helloResult;
946
1021
  try {
947
- await broker.hello();
1022
+ helloResult = await broker.hello();
948
1023
  } catch (err) {
949
1024
  printErr(
950
1025
  `cannot reach muhaven-broker daemon at ${config.brokerEndpoint}: ${err.message}`
@@ -952,9 +1027,42 @@ async function runLogin(argv) {
952
1027
  printErr("hint: start the daemon first (`muhaven-broker` with no subcommand).");
953
1028
  return 1;
954
1029
  }
1030
+ let backendBaseUrl = config.backendBaseUrl;
1031
+ let dashboardBaseUrl = config.dashboardBaseUrl;
1032
+ if (flags.fromDaemon) {
1033
+ if (!helloResult.effectiveConfig) {
1034
+ printErr(
1035
+ "--from-daemon requested but broker did not return effectiveConfig (daemon is older than protocol 0.3.0). Upgrade the daemon (`@muhaven/mcp@0.1.3+`) or drop the flag."
1036
+ );
1037
+ return 1;
1038
+ }
1039
+ const daemonBackend = helloResult.effectiveConfig.backendBaseUrl;
1040
+ const daemonDashboard = helloResult.effectiveConfig.dashboardBaseUrl;
1041
+ if (!daemonBackend || !daemonDashboard) {
1042
+ printErr(
1043
+ "--from-daemon: daemon returned an empty backend/dashboard URL \u2014 refusing to proceed."
1044
+ );
1045
+ return 1;
1046
+ }
1047
+ if (daemonBackend !== config.backendBaseUrl) {
1048
+ print(
1049
+ `\u26A0 daemon backend (${daemonBackend}) differs from CLI env (${config.backendBaseUrl}). Using daemon's value per --from-daemon.`
1050
+ );
1051
+ }
1052
+ if (daemonDashboard !== config.dashboardBaseUrl) {
1053
+ print(
1054
+ `\u26A0 daemon dashboard (${daemonDashboard}) differs from CLI env (${config.dashboardBaseUrl}). Using daemon's value per --from-daemon.`
1055
+ );
1056
+ }
1057
+ backendBaseUrl = daemonBackend;
1058
+ dashboardBaseUrl = daemonDashboard;
1059
+ print(`Using daemon's effective config:`);
1060
+ print(` backend: ${backendBaseUrl}`);
1061
+ print(` dashboard: ${dashboardBaseUrl}`);
1062
+ }
955
1063
  const flow = new DeviceFlowClient({
956
- backendBaseUrl: config.backendBaseUrl,
957
- dashboardBaseUrl: config.dashboardBaseUrl,
1064
+ backendBaseUrl,
1065
+ dashboardBaseUrl,
958
1066
  requesterMetadata: {
959
1067
  processName: detectMcpHost(),
960
1068
  hostname: hostname(),
@@ -1069,7 +1177,13 @@ async function runDoctor() {
1069
1177
  });
1070
1178
  try {
1071
1179
  const h = await broker.hello();
1072
- print(`Broker daemon : reachable (proto v${h.version}, signer ${h.sessionKeyAddress}, hasJwt=${h.hasJwt})`);
1180
+ const hasKey = h.hasSessionKey ?? true;
1181
+ const keyTag = hasKey ? `signer ${h.sessionKeyAddress}` : "NO SESSION KEY (read-only posture)";
1182
+ print(`Broker daemon : reachable (proto v${h.version}, ${keyTag}, hasJwt=${h.hasJwt})`);
1183
+ if (h.effectiveConfig) {
1184
+ print(`Daemon backend URL: ${h.effectiveConfig.backendBaseUrl}`);
1185
+ print(`Daemon dashboard : ${h.effectiveConfig.dashboardBaseUrl}`);
1186
+ }
1073
1187
  return 0;
1074
1188
  } catch (err) {
1075
1189
  print(`Broker daemon : NOT reachable (${err.message})`);
@@ -1082,6 +1196,7 @@ function printUsage() {
1082
1196
  print("");
1083
1197
  print(" (no subcommand) Run the daemon (production mode)");
1084
1198
  print(" login Acquire a JWT via the device-code flow + store in keystore");
1199
+ print(" [--from-daemon] resolves backend/dashboard URLs from the running daemon");
1085
1200
  print(" logout Clear the JWT from the keystore");
1086
1201
  print(" doctor Print environment + keystore + reachability report");
1087
1202
  print(" -h, --help Show this help");
@@ -1109,4 +1224,4 @@ async function runCli(argv) {
1109
1224
  }
1110
1225
  }
1111
1226
 
1112
- export { runCli, runDoctor, runLogin, runLogout };
1227
+ export { parseLoginFlags, runCli, runDoctor, runLogin, runLogout };
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var promises = require('fs/promises');
4
+ require('fs');
4
5
  var path = require('path');
5
6
  var url = require('url');
6
7
  var index_js = require('@modelcontextprotocol/sdk/server/index.js');
@@ -81,23 +82,26 @@ function loadMcpConfig(env = process.env) {
81
82
  }
82
83
  var PRIVKEY_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
83
84
  function loadBrokerConfig(env = process.env) {
84
- const sessionKeyHex = env.MUHAVEN_BROKER_SESSION_KEY;
85
- if (!sessionKeyHex) {
86
- throw new Error(
87
- "MUHAVEN_BROKER_SESSION_KEY is required (0x-prefixed 32-byte hex). Mint a session key via the dashboard policy-template install flow."
88
- );
89
- }
90
- if (!PRIVKEY_HEX_RE.test(sessionKeyHex)) {
91
- throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
85
+ const sessionKeyHexRaw = env.MUHAVEN_BROKER_SESSION_KEY;
86
+ let sessionKeyHex;
87
+ if (sessionKeyHexRaw && sessionKeyHexRaw.length > 0) {
88
+ if (!PRIVKEY_HEX_RE.test(sessionKeyHexRaw)) {
89
+ throw new Error("MUHAVEN_BROKER_SESSION_KEY must be a 0x-prefixed 32-byte hex string");
90
+ }
91
+ sessionKeyHex = sessionKeyHexRaw;
92
92
  }
93
93
  const endpoint = env.MUHAVEN_BROKER_ENDPOINT ?? defaultBrokerEndpoint();
94
94
  const maxRequestBytes = readEnvInt("MUHAVEN_BROKER_MAX_BYTES", DEFAULT_BROKER_MAX_BYTES, env);
95
95
  const requestTimeoutMs = readEnvInt("MUHAVEN_BROKER_TIMEOUT_MS", DEFAULT_BROKER_TIMEOUT_MS, env);
96
+ const backendBaseUrl = trimTrailingSlash(env.MUHAVEN_BACKEND_URL ?? DEFAULT_BACKEND_URL);
97
+ const dashboardBaseUrl = trimTrailingSlash(env.MUHAVEN_DASHBOARD_URL ?? DEFAULT_DASHBOARD_URL);
96
98
  return {
97
99
  endpoint,
98
100
  sessionKeyHex,
99
101
  maxRequestBytes,
100
- requestTimeoutMs
102
+ requestTimeoutMs,
103
+ backendBaseUrl,
104
+ dashboardBaseUrl
101
105
  };
102
106
  }
103
107
  var BrokerClientError = class extends Error {
@@ -736,6 +740,15 @@ function authRequiredPayload() {
736
740
  loginCommand: "muhaven-broker login"
737
741
  };
738
742
  }
743
+ function sessionKeyRequiredPayload(dashboardBaseUrl = "https://muhaven.app") {
744
+ const mintUrl = `${trimTrailingSlash(dashboardBaseUrl)}/agent/policy/transition`;
745
+ return {
746
+ ok: false,
747
+ code: "SESSION_KEY_REQUIRED",
748
+ message: `No session key loaded in broker (read-only posture). Mint one via the dashboard at ${mintUrl}, copy the 0x-prefixed hex into MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. Do NOT run \`muhaven-broker login\` for this \u2014 that mints a JWT, not a session key.`,
749
+ mintUrl
750
+ };
751
+ }
739
752
 
740
753
  // src/tools/handlers.ts
741
754
  function ok(data) {
@@ -825,6 +838,7 @@ function sortKeys(value) {
825
838
  }
826
839
  return value;
827
840
  }
841
+ var cachedHasSessionKeyProbe = null;
828
842
  async function signEnvelope(intent, toolName, summary, deps) {
829
843
  const intentHash = computeIntentHash(intent);
830
844
  if (!deps.broker) {
@@ -833,8 +847,29 @@ async function signEnvelope(intent, toolName, summary, deps) {
833
847
  "position tools require a running muhaven-broker daemon \u2014 see README \xA7Broker setup"
834
848
  );
835
849
  }
850
+ const broker = deps.broker;
851
+ if (cachedHasSessionKeyProbe === null) {
852
+ cachedHasSessionKeyProbe = (async () => {
853
+ try {
854
+ const hello = await broker.hello();
855
+ return hello.hasSessionKey ?? true;
856
+ } catch (err2) {
857
+ cachedHasSessionKeyProbe = null;
858
+ throw err2;
859
+ }
860
+ })();
861
+ }
862
+ let hasSessionKey;
836
863
  try {
837
- const sig = await deps.broker.signHash(intentHash, { tool: toolName, summary });
864
+ hasSessionKey = await cachedHasSessionKeyProbe;
865
+ } catch (e) {
866
+ return mapBrokerError(e);
867
+ }
868
+ if (hasSessionKey === false) {
869
+ return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
870
+ }
871
+ try {
872
+ const sig = await broker.signHash(intentHash, { tool: toolName, summary });
838
873
  return ok({
839
874
  intentHash,
840
875
  unsignedUserOp: {
@@ -846,6 +881,10 @@ async function signEnvelope(intent, toolName, summary, deps) {
846
881
  signerAddress: sig.signerAddress
847
882
  });
848
883
  } catch (e) {
884
+ if (e instanceof BrokerClientError && e.code === "broker_error" && /session_key_unavailable/.test(e.message)) {
885
+ cachedHasSessionKeyProbe = Promise.resolve(false);
886
+ return sessionKeyRequiredPayload(deps.dashboardBaseUrl);
887
+ }
849
888
  return mapBrokerError(e);
850
889
  }
851
890
  }
@@ -1168,7 +1207,12 @@ function selectRegistry(readOnly) {
1168
1207
 
1169
1208
  // src/server.ts
1170
1209
  var SERVER_NAME = "@muhaven/mcp";
1171
- var SERVER_VERSION = "0.1.0";
1210
+ var SERVER_VERSION = resolveServerVersion();
1211
+ function resolveServerVersion() {
1212
+ {
1213
+ return "0.1.3";
1214
+ }
1215
+ }
1172
1216
  function toJsonInputSchema(schema) {
1173
1217
  return {
1174
1218
  type: "object",
@@ -1242,7 +1286,8 @@ function buildMcpServer(opts) {
1242
1286
  const result = await entry.handler(parsed, {
1243
1287
  backend: opts.backend,
1244
1288
  broker: opts.broker,
1245
- surface: "mcp"
1289
+ surface: "mcp",
1290
+ dashboardBaseUrl: opts.dashboardBaseUrl
1246
1291
  });
1247
1292
  return toolJsonResponse(result);
1248
1293
  } catch (err2) {
@@ -1304,7 +1349,8 @@ async function runMcpStdioCli(opts = {}) {
1304
1349
  const server = buildMcpServer({
1305
1350
  registry,
1306
1351
  backend,
1307
- broker: config.readOnly ? void 0 : broker
1352
+ broker: config.readOnly ? void 0 : broker,
1353
+ dashboardBaseUrl: config.dashboardBaseUrl
1308
1354
  });
1309
1355
  const transport = new stdio_js.StdioServerTransport();
1310
1356
  await server.connect(transport);
@@ -1459,7 +1505,7 @@ async function safeJson(res) {
1459
1505
  }
1460
1506
 
1461
1507
  // src/broker/protocol.ts
1462
- var BROKER_PROTOCOL_VERSION = "0.2.0";
1508
+ var BROKER_PROTOCOL_VERSION = "0.3.0";
1463
1509
  var HASH_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
1464
1510
  var JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
1465
1511
  function isHashHex(value) {
@@ -1545,6 +1591,21 @@ function parseBrokerRequest(line) {
1545
1591
  function serializeResponse(res) {
1546
1592
  return JSON.stringify(res) + "\n";
1547
1593
  }
1594
+ var MissingSessionKeyError = class extends Error {
1595
+ constructor() {
1596
+ super(
1597
+ "session_key_unavailable: daemon booted in read-only posture (no MUHAVEN_BROKER_SESSION_KEY at env-load time). Mint a session key via the dashboard /agent/policy/transition flow, set MUHAVEN_BROKER_SESSION_KEY, and restart the daemon. (Note: `muhaven-broker login` mints a JWT, NOT a session key \u2014 do not loop on that command for this error.)"
1598
+ );
1599
+ this.name = "MissingSessionKeyError";
1600
+ }
1601
+ };
1602
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1603
+ var NullSigner = class {
1604
+ address = ZERO_ADDRESS;
1605
+ async signHash(_hash) {
1606
+ throw new MissingSessionKeyError();
1607
+ }
1608
+ };
1548
1609
  var ViemSigner = class {
1549
1610
  account;
1550
1611
  constructor(privateKey) {
@@ -1716,7 +1777,7 @@ async function openKeystore(options = {}) {
1716
1777
  // src/broker/daemon.ts
1717
1778
  var noopLogger = (_e) => {
1718
1779
  };
1719
- async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3)) {
1780
+ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.floor(Date.now() / 1e3), options = {}) {
1720
1781
  switch (req.type) {
1721
1782
  case "hello": {
1722
1783
  let hasJwt = false;
@@ -1726,16 +1787,26 @@ async function handleBrokerRequest(req, signer, keystore, nowSec = () => Math.fl
1726
1787
  } catch {
1727
1788
  hasJwt = false;
1728
1789
  }
1790
+ const hasSessionKey = options.hasSessionKey ?? true;
1729
1791
  return {
1730
1792
  type: "hello",
1731
1793
  version: BROKER_PROTOCOL_VERSION,
1732
1794
  sessionKeyAddress: signer.address,
1733
- hasJwt
1795
+ hasJwt,
1796
+ hasSessionKey,
1797
+ ...options.effectiveConfig ? { effectiveConfig: options.effectiveConfig } : {}
1734
1798
  };
1735
1799
  }
1736
1800
  case "sign_hash": {
1737
- const signature = await signer.signHash(req.hash);
1738
- return { type: "sign_hash", signature, signerAddress: signer.address };
1801
+ try {
1802
+ const signature = await signer.signHash(req.hash);
1803
+ return { type: "sign_hash", signature, signerAddress: signer.address };
1804
+ } catch (err2) {
1805
+ if (err2 instanceof MissingSessionKeyError) {
1806
+ return errorResponse("session_key_unavailable", err2.message);
1807
+ }
1808
+ throw err2;
1809
+ }
1739
1810
  }
1740
1811
  case "store_jwt": {
1741
1812
  try {
@@ -1807,9 +1878,24 @@ var BrokerDaemon = class {
1807
1878
  log;
1808
1879
  config;
1809
1880
  keystore;
1881
+ /**
1882
+ * Whether a session-key private half is actually loaded. `false` =
1883
+ * daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
1884
+ * at env-load time) and uses a `NullSigner` whose `signHash` throws.
1885
+ */
1886
+ hasSessionKey;
1810
1887
  constructor(options) {
1811
1888
  this.config = options.config;
1812
- this.signer = options.signer ?? new ViemSigner(options.config.sessionKeyHex);
1889
+ if (options.signer) {
1890
+ this.signer = options.signer;
1891
+ this.hasSessionKey = true;
1892
+ } else if (options.config.sessionKeyHex) {
1893
+ this.signer = new ViemSigner(options.config.sessionKeyHex);
1894
+ this.hasSessionKey = true;
1895
+ } else {
1896
+ this.signer = new NullSigner();
1897
+ this.hasSessionKey = false;
1898
+ }
1813
1899
  this.keystore = options.keystore ?? null;
1814
1900
  this.log = options.logger ?? noopLogger;
1815
1901
  this.server = net.createServer((socket) => this.onConnection(socket));
@@ -1847,6 +1933,7 @@ var BrokerDaemon = class {
1847
1933
  meta: {
1848
1934
  endpoint: this.config.endpoint,
1849
1935
  signer: this.signer.address,
1936
+ hasSessionKey: this.hasSessionKey,
1850
1937
  keystore: this.keystore.backend,
1851
1938
  version: BROKER_PROTOCOL_VERSION
1852
1939
  }
@@ -1928,7 +2015,19 @@ var BrokerDaemon = class {
1928
2015
  return;
1929
2016
  }
1930
2017
  try {
1931
- const res = await handleBrokerRequest(parsed, this.signer, this.keystore);
2018
+ const res = await handleBrokerRequest(
2019
+ parsed,
2020
+ this.signer,
2021
+ this.keystore,
2022
+ void 0,
2023
+ {
2024
+ hasSessionKey: this.hasSessionKey,
2025
+ effectiveConfig: {
2026
+ backendBaseUrl: this.config.backendBaseUrl,
2027
+ dashboardBaseUrl: this.config.dashboardBaseUrl
2028
+ }
2029
+ }
2030
+ );
1932
2031
  socket.end(serializeResponse(res));
1933
2032
  } catch (err2) {
1934
2033
  this.log({
@@ -1953,8 +2052,13 @@ exports.DeviceFlowAbortedError = DeviceFlowAbortedError;
1953
2052
  exports.DeviceFlowClient = DeviceFlowClient;
1954
2053
  exports.JwtSource = JwtSource;
1955
2054
  exports.KeystoreError = KeystoreError;
2055
+ exports.MissingSessionKeyError = MissingSessionKeyError;
1956
2056
  exports.NoJwtAvailableError = NoJwtAvailableError;
2057
+ exports.NullSigner = NullSigner;
2058
+ exports.SERVER_VERSION = SERVER_VERSION;
1957
2059
  exports.TOOL_DESCRIPTORS = TOOL_DESCRIPTORS;
2060
+ exports.ViemSigner = ViemSigner;
2061
+ exports.ZERO_ADDRESS = ZERO_ADDRESS;
1958
2062
  exports.buildMcpServer = buildMcpServer;
1959
2063
  exports.buildToolHashTable = buildToolHashTable;
1960
2064
  exports.defaultBrokerEndpoint = defaultBrokerEndpoint;