@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/index.d.cts CHANGED
@@ -9,11 +9,14 @@ import { z } from 'zod';
9
9
  * (Windows). Each request is a single JSON object; each response is a
10
10
  * single JSON object. No request pipelining, no streaming.
11
11
  *
12
- * **Protocol version 0.2.0** — bumped from 0.1.0 in Wave 4 P3 ADR-3
13
- * to add the `store_jwt` / `get_jwt` / `clear_jwt` triple. The broker
14
- * is now the single keeper of the device-flow JWT (per ADR-3 D1
15
- * "polling, not loopback callback") in addition to the session-key
16
- * private half.
12
+ * **Protocol version 0.3.0** — additive bump from 0.2.0 in @muhaven/mcp@0.1.3
13
+ * to add `hello.hasSessionKey` + `hello.effectiveConfig` (so a daemon
14
+ * booted without `MUHAVEN_BROKER_SESSION_KEY` can serve read paths AND
15
+ * surface its effective backend/dashboard URLs to `muhaven-broker login
16
+ * --from-daemon`). The 0.2.0 bump from 0.1.0 (Wave 4 P3 ADR-3) added the
17
+ * `store_jwt` / `get_jwt` / `clear_jwt` triple — the broker is the single
18
+ * keeper of the device-flow JWT (per ADR-3 D1 "polling, not loopback
19
+ * callback") in addition to the session-key private half.
17
20
  *
18
21
  * Threat-model invariants:
19
22
  * - The broker NEVER reaches out to the network. It only:
@@ -26,7 +29,7 @@ import { z } from 'zod';
26
29
  * - Requests are size-capped (`maxRequestBytes`) — a malformed peer
27
30
  * cannot exhaust broker memory by sending an unbounded JSON blob.
28
31
  */
29
- declare const BROKER_PROTOCOL_VERSION = "0.2.0";
32
+ declare const BROKER_PROTOCOL_VERSION = "0.3.0";
30
33
  interface BrokerHelloRequest {
31
34
  readonly type: 'hello';
32
35
  }
@@ -57,10 +60,40 @@ interface BrokerClearJwtRequest {
57
60
  interface BrokerHelloResponse {
58
61
  readonly type: 'hello';
59
62
  readonly version: string;
60
- /** 0x-prefixed checksummed address derived from the session key. */
63
+ /**
64
+ * 0x-prefixed checksummed address derived from the session key. When
65
+ * the daemon was booted without `MUHAVEN_BROKER_SESSION_KEY` (read-only
66
+ * posture; see `hasSessionKey`), this is the zero address.
67
+ *
68
+ * **DO NOT** use address-equality (`sessionKeyAddress !== ZERO_ADDRESS`)
69
+ * as a proxy for "session key is loaded" — that's a soft signal that
70
+ * future changes (e.g. allowing custom EOA-bound dev posture) could
71
+ * break silently. The authoritative aliveness check is `hasSessionKey`.
72
+ */
61
73
  readonly sessionKeyAddress: `0x${string}`;
62
74
  /** Whether a JWT is currently in the keystore. Useful for `doctor`. */
63
75
  readonly hasJwt: boolean;
76
+ /**
77
+ * Whether a session-key private half is loaded into the daemon. False
78
+ * when the daemon was booted without `MUHAVEN_BROKER_SESSION_KEY` — in
79
+ * that posture read tools still work (the broker serves JWT verbs), but
80
+ * any `sign_hash` request returns `session_key_unavailable`. Field added
81
+ * in protocol 0.3.0; absence implies `true` for back-compat with
82
+ * 0.2.0 daemons.
83
+ */
84
+ readonly hasSessionKey?: boolean;
85
+ /**
86
+ * Effective backend + dashboard URLs the daemon resolved from its own
87
+ * process env at boot. Surfaced so `muhaven-broker login --from-daemon`
88
+ * can stay in lockstep with the daemon's view rather than re-reading
89
+ * the CLI's env (which may diverge — e.g. login invoked over ssh inherits
90
+ * a different shell env than the systemd-launched daemon). Field added
91
+ * in protocol 0.3.0; absent on older daemons.
92
+ */
93
+ readonly effectiveConfig?: {
94
+ readonly backendBaseUrl: string;
95
+ readonly dashboardBaseUrl: string;
96
+ };
64
97
  }
65
98
  interface BrokerSignHashResponse {
66
99
  readonly type: 'sign_hash';
@@ -87,7 +120,7 @@ interface BrokerErrorResponse {
87
120
  readonly code: BrokerErrorCode;
88
121
  readonly message: string;
89
122
  }
90
- type BrokerErrorCode = 'invalid_request' | 'payload_too_large' | 'unsupported_type' | 'internal' | 'forbidden' | 'keystore_unavailable';
123
+ type BrokerErrorCode = 'invalid_request' | 'payload_too_large' | 'unsupported_type' | 'internal' | 'forbidden' | 'keystore_unavailable' | 'session_key_unavailable';
91
124
  type BrokerRequest = BrokerHelloRequest | BrokerSignHashRequest | BrokerStoreJwtRequest | BrokerGetJwtRequest | BrokerClearJwtRequest;
92
125
  type BrokerResponse = BrokerHelloResponse | BrokerSignHashResponse | BrokerStoreJwtResponse | BrokerGetJwtResponse | BrokerClearJwtResponse | BrokerErrorResponse;
93
126
  /**
@@ -316,6 +349,27 @@ interface AuthRequiredPayload {
316
349
  readonly message: string;
317
350
  readonly loginCommand: string;
318
351
  }
352
+ /**
353
+ * Sibling to `AUTH_REQUIRED` — used when the broker daemon is reachable
354
+ * but boots in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY` at
355
+ * startup). The remediation is NOT `muhaven-broker login` — that mints
356
+ * a JWT, which is different from minting a session key. The session key
357
+ * comes from the dashboard's `/agent/policy/transition` ceremony (see
358
+ * Q1 in the post-§4 queue).
359
+ *
360
+ * Surfacing this as a distinct code lets the host LLM disambiguate:
361
+ * - `AUTH_REQUIRED` → run a CLI command (deterministic, one-step).
362
+ * - `SESSION_KEY_REQUIRED` → open a URL and complete a passkey ceremony
363
+ * (operator-mediated, multi-step). The LLM should NOT auto-suggest
364
+ * `muhaven-broker login` for this case — that would loop the user
365
+ * indefinitely without minting a key.
366
+ */
367
+ interface SessionKeyRequiredPayload {
368
+ readonly ok: false;
369
+ readonly code: 'SESSION_KEY_REQUIRED';
370
+ readonly message: string;
371
+ readonly mintUrl: string;
372
+ }
319
373
 
320
374
  /**
321
375
  * Tool handlers — pure functions of `(input, deps)` returning a structured
@@ -343,6 +397,13 @@ interface ToolDeps {
343
397
  /** Surface this MCP server is configured for. Always 'mcp' here, but
344
398
  * carried as a dep so the audit tool can filter to the local surface. */
345
399
  surface: 'mcp';
400
+ /**
401
+ * Dashboard base URL — used to build the mint-URL surfaced in the
402
+ * `SESSION_KEY_REQUIRED` payload when the broker is in read-only
403
+ * posture. Optional for back-compat; the payload falls back to the
404
+ * production default when absent.
405
+ */
406
+ dashboardBaseUrl?: string;
346
407
  }
347
408
  type ToolResult<T> = {
348
409
  ok: true;
@@ -351,7 +412,7 @@ type ToolResult<T> = {
351
412
  ok: false;
352
413
  code: string;
353
414
  message: string;
354
- } | AuthRequiredPayload;
415
+ } | AuthRequiredPayload | SessionKeyRequiredPayload;
355
416
 
356
417
  /**
357
418
  * Static registry binding each tool descriptor to its zod schema and
@@ -393,10 +454,17 @@ declare function selectRegistry(readOnly: boolean): readonly ToolEntry[];
393
454
  * instructs the user to run `muhaven-broker login`.
394
455
  */
395
456
 
457
+ declare const SERVER_VERSION: string;
396
458
  interface BuildServerOptions {
397
459
  registry: readonly ToolEntry[];
398
460
  backend: BackendClient;
399
461
  broker: BrokerClient | undefined;
462
+ /**
463
+ * Threaded into `ToolDeps` so the `SESSION_KEY_REQUIRED` payload's
464
+ * `mintUrl` points at the operator's actual dashboard, not a hardcoded
465
+ * production URL.
466
+ */
467
+ dashboardBaseUrl?: string;
400
468
  }
401
469
  declare function buildMcpServer(opts: BuildServerOptions): Server;
402
470
  /**
@@ -456,12 +524,26 @@ interface McpRuntimeConfig {
456
524
  interface BrokerRuntimeConfig {
457
525
  /** Endpoint to bind: socket path on POSIX, named pipe name on Windows. */
458
526
  endpoint: string;
459
- /** 0x-prefixed 32-byte private key. Sensitive — keychain-backed. */
460
- sessionKeyHex: `0x${string}`;
527
+ /**
528
+ * 0x-prefixed 32-byte private key, OR undefined for read-only posture.
529
+ * When undefined, the daemon still serves `hello` + the JWT verbs
530
+ * (so MCP read tools work), but any `sign_hash` request returns
531
+ * `session_key_unavailable`. Sensitive when present — keychain-backed.
532
+ */
533
+ sessionKeyHex: `0x${string}` | undefined;
461
534
  /** Maximum payload bytes accepted from the IPC peer. */
462
535
  maxRequestBytes: number;
463
536
  /** Per-request hard timeout (ms). */
464
537
  requestTimeoutMs: number;
538
+ /**
539
+ * Effective backend URL the daemon read from its own env at boot. Used
540
+ * to populate `hello.effectiveConfig` so a `muhaven-broker login
541
+ * --from-daemon` call uses the same URL as the daemon, even when the
542
+ * login CLI was launched from a different shell env.
543
+ */
544
+ backendBaseUrl: string;
545
+ /** Effective dashboard URL paired with backendBaseUrl. */
546
+ dashboardBaseUrl: string;
465
547
  }
466
548
  /**
467
549
  * Compute the default IPC endpoint for the broker. POSIX: a socket file
@@ -587,6 +669,29 @@ interface ISigner {
587
669
  readonly address: `0x${string}`;
588
670
  signHash(hash: `0x${string}`): Promise<`0x${string}`>;
589
671
  }
672
+ /**
673
+ * Sentinel signer for the read-only daemon posture (no
674
+ * `MUHAVEN_BROKER_SESSION_KEY` at boot). `address` returns the zero
675
+ * address; `signHash` throws `MissingSessionKeyError`, which the daemon
676
+ * maps to a structured `session_key_unavailable` broker error response.
677
+ *
678
+ * Closes §3e⁶ F-broker-session-key-required-for-reads — the daemon can
679
+ * serve `hello` + JWT verbs for read paths without an on-chain signer.
680
+ */
681
+ declare class MissingSessionKeyError extends Error {
682
+ constructor();
683
+ }
684
+ declare const ZERO_ADDRESS: "0x0000000000000000000000000000000000000000";
685
+ declare class NullSigner implements ISigner {
686
+ readonly address: "0x0000000000000000000000000000000000000000";
687
+ signHash(_hash: `0x${string}`): Promise<`0x${string}`>;
688
+ }
689
+ declare class ViemSigner implements ISigner {
690
+ private readonly account;
691
+ constructor(privateKey: `0x${string}`);
692
+ get address(): `0x${string}`;
693
+ signHash(hash: `0x${string}`): Promise<`0x${string}`>;
694
+ }
590
695
 
591
696
  /**
592
697
  * Cross-platform JWT keystore for the broker daemon.
@@ -681,13 +786,38 @@ interface BrokerLogEvent {
681
786
  * keystore, returns the response object. Easy to unit-test without
682
787
  * spawning a socket.
683
788
  */
684
- declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number): Promise<BrokerResponse>;
789
+ interface HandleBrokerRequestOptions {
790
+ /**
791
+ * Whether the daemon was booted with a session-key private half. Drives
792
+ * the `hello.hasSessionKey` field + the `session_key_unavailable` error
793
+ * mapping. Defaults to `true` when omitted (current-runtime back-compat
794
+ * — older callers that don't know about the read-only posture still
795
+ * get the pre-0.3.0 behaviour).
796
+ */
797
+ hasSessionKey?: boolean;
798
+ /**
799
+ * Effective backend + dashboard URLs the daemon resolved from its env
800
+ * at boot. Surfaced via `hello.effectiveConfig` so the `--from-daemon`
801
+ * login path can use the daemon's view of these URLs (not the CLI's).
802
+ */
803
+ effectiveConfig?: {
804
+ readonly backendBaseUrl: string;
805
+ readonly dashboardBaseUrl: string;
806
+ };
807
+ }
808
+ declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number, options?: HandleBrokerRequestOptions): Promise<BrokerResponse>;
685
809
  declare class BrokerDaemon {
686
810
  private readonly server;
687
811
  private readonly signer;
688
812
  private readonly log;
689
813
  private readonly config;
690
814
  private keystore;
815
+ /**
816
+ * Whether a session-key private half is actually loaded. `false` =
817
+ * daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
818
+ * at env-load time) and uses a `NullSigner` whose `signHash` throws.
819
+ */
820
+ private readonly hasSessionKey;
691
821
  constructor(options: BrokerDaemonOptions);
692
822
  start(): Promise<string>;
693
823
  stop(): Promise<void>;
@@ -695,4 +825,4 @@ declare class BrokerDaemon {
695
825
  private runAndRespond;
696
826
  }
697
827
 
698
- export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, type BackendErrorCode, BrokerClient, BrokerClientError, type BrokerClientErrorCode, BrokerDaemon, type BrokerDaemonOptions, type BrokerRequest, type BrokerResponse, type BrokerRuntimeConfig, DeviceFlowAbortedError, DeviceFlowClient, type DeviceFlowEvent, type IKeystore, JwtSource, type KeystoreBackend, KeystoreError, type McpRuntimeConfig, NoJwtAvailableError, type RunMcpStdioCliOptions, TOOL_DESCRIPTORS, type ToolDescriptor, type ToolEntry, type ToolHashEntry, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };
828
+ export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, type BackendErrorCode, BrokerClient, BrokerClientError, type BrokerClientErrorCode, BrokerDaemon, type BrokerDaemonOptions, type BrokerRequest, type BrokerResponse, type BrokerRuntimeConfig, DeviceFlowAbortedError, DeviceFlowClient, type DeviceFlowEvent, type HandleBrokerRequestOptions, type IKeystore, type ISigner, JwtSource, type KeystoreBackend, KeystoreError, type McpRuntimeConfig, MissingSessionKeyError, NoJwtAvailableError, NullSigner, type RunMcpStdioCliOptions, SERVER_VERSION, TOOL_DESCRIPTORS, type ToolDescriptor, type ToolEntry, type ToolHashEntry, ViemSigner, ZERO_ADDRESS, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };
package/dist/index.d.ts CHANGED
@@ -9,11 +9,14 @@ import { z } from 'zod';
9
9
  * (Windows). Each request is a single JSON object; each response is a
10
10
  * single JSON object. No request pipelining, no streaming.
11
11
  *
12
- * **Protocol version 0.2.0** — bumped from 0.1.0 in Wave 4 P3 ADR-3
13
- * to add the `store_jwt` / `get_jwt` / `clear_jwt` triple. The broker
14
- * is now the single keeper of the device-flow JWT (per ADR-3 D1
15
- * "polling, not loopback callback") in addition to the session-key
16
- * private half.
12
+ * **Protocol version 0.3.0** — additive bump from 0.2.0 in @muhaven/mcp@0.1.3
13
+ * to add `hello.hasSessionKey` + `hello.effectiveConfig` (so a daemon
14
+ * booted without `MUHAVEN_BROKER_SESSION_KEY` can serve read paths AND
15
+ * surface its effective backend/dashboard URLs to `muhaven-broker login
16
+ * --from-daemon`). The 0.2.0 bump from 0.1.0 (Wave 4 P3 ADR-3) added the
17
+ * `store_jwt` / `get_jwt` / `clear_jwt` triple — the broker is the single
18
+ * keeper of the device-flow JWT (per ADR-3 D1 "polling, not loopback
19
+ * callback") in addition to the session-key private half.
17
20
  *
18
21
  * Threat-model invariants:
19
22
  * - The broker NEVER reaches out to the network. It only:
@@ -26,7 +29,7 @@ import { z } from 'zod';
26
29
  * - Requests are size-capped (`maxRequestBytes`) — a malformed peer
27
30
  * cannot exhaust broker memory by sending an unbounded JSON blob.
28
31
  */
29
- declare const BROKER_PROTOCOL_VERSION = "0.2.0";
32
+ declare const BROKER_PROTOCOL_VERSION = "0.3.0";
30
33
  interface BrokerHelloRequest {
31
34
  readonly type: 'hello';
32
35
  }
@@ -57,10 +60,40 @@ interface BrokerClearJwtRequest {
57
60
  interface BrokerHelloResponse {
58
61
  readonly type: 'hello';
59
62
  readonly version: string;
60
- /** 0x-prefixed checksummed address derived from the session key. */
63
+ /**
64
+ * 0x-prefixed checksummed address derived from the session key. When
65
+ * the daemon was booted without `MUHAVEN_BROKER_SESSION_KEY` (read-only
66
+ * posture; see `hasSessionKey`), this is the zero address.
67
+ *
68
+ * **DO NOT** use address-equality (`sessionKeyAddress !== ZERO_ADDRESS`)
69
+ * as a proxy for "session key is loaded" — that's a soft signal that
70
+ * future changes (e.g. allowing custom EOA-bound dev posture) could
71
+ * break silently. The authoritative aliveness check is `hasSessionKey`.
72
+ */
61
73
  readonly sessionKeyAddress: `0x${string}`;
62
74
  /** Whether a JWT is currently in the keystore. Useful for `doctor`. */
63
75
  readonly hasJwt: boolean;
76
+ /**
77
+ * Whether a session-key private half is loaded into the daemon. False
78
+ * when the daemon was booted without `MUHAVEN_BROKER_SESSION_KEY` — in
79
+ * that posture read tools still work (the broker serves JWT verbs), but
80
+ * any `sign_hash` request returns `session_key_unavailable`. Field added
81
+ * in protocol 0.3.0; absence implies `true` for back-compat with
82
+ * 0.2.0 daemons.
83
+ */
84
+ readonly hasSessionKey?: boolean;
85
+ /**
86
+ * Effective backend + dashboard URLs the daemon resolved from its own
87
+ * process env at boot. Surfaced so `muhaven-broker login --from-daemon`
88
+ * can stay in lockstep with the daemon's view rather than re-reading
89
+ * the CLI's env (which may diverge — e.g. login invoked over ssh inherits
90
+ * a different shell env than the systemd-launched daemon). Field added
91
+ * in protocol 0.3.0; absent on older daemons.
92
+ */
93
+ readonly effectiveConfig?: {
94
+ readonly backendBaseUrl: string;
95
+ readonly dashboardBaseUrl: string;
96
+ };
64
97
  }
65
98
  interface BrokerSignHashResponse {
66
99
  readonly type: 'sign_hash';
@@ -87,7 +120,7 @@ interface BrokerErrorResponse {
87
120
  readonly code: BrokerErrorCode;
88
121
  readonly message: string;
89
122
  }
90
- type BrokerErrorCode = 'invalid_request' | 'payload_too_large' | 'unsupported_type' | 'internal' | 'forbidden' | 'keystore_unavailable';
123
+ type BrokerErrorCode = 'invalid_request' | 'payload_too_large' | 'unsupported_type' | 'internal' | 'forbidden' | 'keystore_unavailable' | 'session_key_unavailable';
91
124
  type BrokerRequest = BrokerHelloRequest | BrokerSignHashRequest | BrokerStoreJwtRequest | BrokerGetJwtRequest | BrokerClearJwtRequest;
92
125
  type BrokerResponse = BrokerHelloResponse | BrokerSignHashResponse | BrokerStoreJwtResponse | BrokerGetJwtResponse | BrokerClearJwtResponse | BrokerErrorResponse;
93
126
  /**
@@ -316,6 +349,27 @@ interface AuthRequiredPayload {
316
349
  readonly message: string;
317
350
  readonly loginCommand: string;
318
351
  }
352
+ /**
353
+ * Sibling to `AUTH_REQUIRED` — used when the broker daemon is reachable
354
+ * but boots in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY` at
355
+ * startup). The remediation is NOT `muhaven-broker login` — that mints
356
+ * a JWT, which is different from minting a session key. The session key
357
+ * comes from the dashboard's `/agent/policy/transition` ceremony (see
358
+ * Q1 in the post-§4 queue).
359
+ *
360
+ * Surfacing this as a distinct code lets the host LLM disambiguate:
361
+ * - `AUTH_REQUIRED` → run a CLI command (deterministic, one-step).
362
+ * - `SESSION_KEY_REQUIRED` → open a URL and complete a passkey ceremony
363
+ * (operator-mediated, multi-step). The LLM should NOT auto-suggest
364
+ * `muhaven-broker login` for this case — that would loop the user
365
+ * indefinitely without minting a key.
366
+ */
367
+ interface SessionKeyRequiredPayload {
368
+ readonly ok: false;
369
+ readonly code: 'SESSION_KEY_REQUIRED';
370
+ readonly message: string;
371
+ readonly mintUrl: string;
372
+ }
319
373
 
320
374
  /**
321
375
  * Tool handlers — pure functions of `(input, deps)` returning a structured
@@ -343,6 +397,13 @@ interface ToolDeps {
343
397
  /** Surface this MCP server is configured for. Always 'mcp' here, but
344
398
  * carried as a dep so the audit tool can filter to the local surface. */
345
399
  surface: 'mcp';
400
+ /**
401
+ * Dashboard base URL — used to build the mint-URL surfaced in the
402
+ * `SESSION_KEY_REQUIRED` payload when the broker is in read-only
403
+ * posture. Optional for back-compat; the payload falls back to the
404
+ * production default when absent.
405
+ */
406
+ dashboardBaseUrl?: string;
346
407
  }
347
408
  type ToolResult<T> = {
348
409
  ok: true;
@@ -351,7 +412,7 @@ type ToolResult<T> = {
351
412
  ok: false;
352
413
  code: string;
353
414
  message: string;
354
- } | AuthRequiredPayload;
415
+ } | AuthRequiredPayload | SessionKeyRequiredPayload;
355
416
 
356
417
  /**
357
418
  * Static registry binding each tool descriptor to its zod schema and
@@ -393,10 +454,17 @@ declare function selectRegistry(readOnly: boolean): readonly ToolEntry[];
393
454
  * instructs the user to run `muhaven-broker login`.
394
455
  */
395
456
 
457
+ declare const SERVER_VERSION: string;
396
458
  interface BuildServerOptions {
397
459
  registry: readonly ToolEntry[];
398
460
  backend: BackendClient;
399
461
  broker: BrokerClient | undefined;
462
+ /**
463
+ * Threaded into `ToolDeps` so the `SESSION_KEY_REQUIRED` payload's
464
+ * `mintUrl` points at the operator's actual dashboard, not a hardcoded
465
+ * production URL.
466
+ */
467
+ dashboardBaseUrl?: string;
400
468
  }
401
469
  declare function buildMcpServer(opts: BuildServerOptions): Server;
402
470
  /**
@@ -456,12 +524,26 @@ interface McpRuntimeConfig {
456
524
  interface BrokerRuntimeConfig {
457
525
  /** Endpoint to bind: socket path on POSIX, named pipe name on Windows. */
458
526
  endpoint: string;
459
- /** 0x-prefixed 32-byte private key. Sensitive — keychain-backed. */
460
- sessionKeyHex: `0x${string}`;
527
+ /**
528
+ * 0x-prefixed 32-byte private key, OR undefined for read-only posture.
529
+ * When undefined, the daemon still serves `hello` + the JWT verbs
530
+ * (so MCP read tools work), but any `sign_hash` request returns
531
+ * `session_key_unavailable`. Sensitive when present — keychain-backed.
532
+ */
533
+ sessionKeyHex: `0x${string}` | undefined;
461
534
  /** Maximum payload bytes accepted from the IPC peer. */
462
535
  maxRequestBytes: number;
463
536
  /** Per-request hard timeout (ms). */
464
537
  requestTimeoutMs: number;
538
+ /**
539
+ * Effective backend URL the daemon read from its own env at boot. Used
540
+ * to populate `hello.effectiveConfig` so a `muhaven-broker login
541
+ * --from-daemon` call uses the same URL as the daemon, even when the
542
+ * login CLI was launched from a different shell env.
543
+ */
544
+ backendBaseUrl: string;
545
+ /** Effective dashboard URL paired with backendBaseUrl. */
546
+ dashboardBaseUrl: string;
465
547
  }
466
548
  /**
467
549
  * Compute the default IPC endpoint for the broker. POSIX: a socket file
@@ -587,6 +669,29 @@ interface ISigner {
587
669
  readonly address: `0x${string}`;
588
670
  signHash(hash: `0x${string}`): Promise<`0x${string}`>;
589
671
  }
672
+ /**
673
+ * Sentinel signer for the read-only daemon posture (no
674
+ * `MUHAVEN_BROKER_SESSION_KEY` at boot). `address` returns the zero
675
+ * address; `signHash` throws `MissingSessionKeyError`, which the daemon
676
+ * maps to a structured `session_key_unavailable` broker error response.
677
+ *
678
+ * Closes §3e⁶ F-broker-session-key-required-for-reads — the daemon can
679
+ * serve `hello` + JWT verbs for read paths without an on-chain signer.
680
+ */
681
+ declare class MissingSessionKeyError extends Error {
682
+ constructor();
683
+ }
684
+ declare const ZERO_ADDRESS: "0x0000000000000000000000000000000000000000";
685
+ declare class NullSigner implements ISigner {
686
+ readonly address: "0x0000000000000000000000000000000000000000";
687
+ signHash(_hash: `0x${string}`): Promise<`0x${string}`>;
688
+ }
689
+ declare class ViemSigner implements ISigner {
690
+ private readonly account;
691
+ constructor(privateKey: `0x${string}`);
692
+ get address(): `0x${string}`;
693
+ signHash(hash: `0x${string}`): Promise<`0x${string}`>;
694
+ }
590
695
 
591
696
  /**
592
697
  * Cross-platform JWT keystore for the broker daemon.
@@ -681,13 +786,38 @@ interface BrokerLogEvent {
681
786
  * keystore, returns the response object. Easy to unit-test without
682
787
  * spawning a socket.
683
788
  */
684
- declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number): Promise<BrokerResponse>;
789
+ interface HandleBrokerRequestOptions {
790
+ /**
791
+ * Whether the daemon was booted with a session-key private half. Drives
792
+ * the `hello.hasSessionKey` field + the `session_key_unavailable` error
793
+ * mapping. Defaults to `true` when omitted (current-runtime back-compat
794
+ * — older callers that don't know about the read-only posture still
795
+ * get the pre-0.3.0 behaviour).
796
+ */
797
+ hasSessionKey?: boolean;
798
+ /**
799
+ * Effective backend + dashboard URLs the daemon resolved from its env
800
+ * at boot. Surfaced via `hello.effectiveConfig` so the `--from-daemon`
801
+ * login path can use the daemon's view of these URLs (not the CLI's).
802
+ */
803
+ effectiveConfig?: {
804
+ readonly backendBaseUrl: string;
805
+ readonly dashboardBaseUrl: string;
806
+ };
807
+ }
808
+ declare function handleBrokerRequest(req: BrokerRequest, signer: ISigner, keystore: IKeystore, nowSec?: () => number, options?: HandleBrokerRequestOptions): Promise<BrokerResponse>;
685
809
  declare class BrokerDaemon {
686
810
  private readonly server;
687
811
  private readonly signer;
688
812
  private readonly log;
689
813
  private readonly config;
690
814
  private keystore;
815
+ /**
816
+ * Whether a session-key private half is actually loaded. `false` =
817
+ * daemon booted in read-only posture (no `MUHAVEN_BROKER_SESSION_KEY`
818
+ * at env-load time) and uses a `NullSigner` whose `signHash` throws.
819
+ */
820
+ private readonly hasSessionKey;
691
821
  constructor(options: BrokerDaemonOptions);
692
822
  start(): Promise<string>;
693
823
  stop(): Promise<void>;
@@ -695,4 +825,4 @@ declare class BrokerDaemon {
695
825
  private runAndRespond;
696
826
  }
697
827
 
698
- export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, type BackendErrorCode, BrokerClient, BrokerClientError, type BrokerClientErrorCode, BrokerDaemon, type BrokerDaemonOptions, type BrokerRequest, type BrokerResponse, type BrokerRuntimeConfig, DeviceFlowAbortedError, DeviceFlowClient, type DeviceFlowEvent, type IKeystore, JwtSource, type KeystoreBackend, KeystoreError, type McpRuntimeConfig, NoJwtAvailableError, type RunMcpStdioCliOptions, TOOL_DESCRIPTORS, type ToolDescriptor, type ToolEntry, type ToolHashEntry, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };
828
+ export { BROKER_PROTOCOL_VERSION, BackendClient, BackendError, type BackendErrorCode, BrokerClient, BrokerClientError, type BrokerClientErrorCode, BrokerDaemon, type BrokerDaemonOptions, type BrokerRequest, type BrokerResponse, type BrokerRuntimeConfig, DeviceFlowAbortedError, DeviceFlowClient, type DeviceFlowEvent, type HandleBrokerRequestOptions, type IKeystore, type ISigner, JwtSource, type KeystoreBackend, KeystoreError, type McpRuntimeConfig, MissingSessionKeyError, NoJwtAvailableError, NullSigner, type RunMcpStdioCliOptions, SERVER_VERSION, TOOL_DESCRIPTORS, type ToolDescriptor, type ToolEntry, type ToolHashEntry, ViemSigner, ZERO_ADDRESS, buildMcpServer, buildToolHashTable, defaultBrokerEndpoint, fullToolRegistry, handleBrokerRequest, hashToolDescriptor, loadBrokerConfig, loadMcpConfig, openKeystore, parseBrokerRequest, registryForReadOnly, runMcpStdioCli, selectRegistry, serializeResponse, verifyDescriptorAgainstPin };