@toon-protocol/client-mcp 0.31.1 → 0.32.0

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.ts CHANGED
@@ -53,6 +53,13 @@ interface StatusResponse {
53
53
  buffered: number;
54
54
  /** Active subscription ids. */
55
55
  subscriptions: string[];
56
+ /**
57
+ * Error from the daemon-managed read proxy, if any. Set only in the
58
+ * btp-direct + relay-`.anyone` case where the daemon starts its own `anon`
59
+ * proxy for reads; a failure here means hidden-service reads are down while
60
+ * direct paid writes are unaffected.
61
+ */
62
+ proxyError?: string;
56
63
  };
57
64
  /** Per-chain settlement status when a named `network` tier is configured. */
58
65
  network?: ChainStatus[];
@@ -67,6 +74,12 @@ interface PublishRequest {
67
74
  destination?: string;
68
75
  /** Fee override in base units. Defaults to the daemon's configured fee. */
69
76
  fee?: string;
77
+ /**
78
+ * Which apex (BTP write target) to publish through. Defaults to the
79
+ * config-seeded apex. Writes always go through BTP — never to a relay
80
+ * directly — so this selects among the registered apexes, not relays.
81
+ */
82
+ btpUrl?: string;
70
83
  }
71
84
  interface PublishResponse {
72
85
  eventId: string;
@@ -83,9 +96,17 @@ interface SubscribeRequest {
83
96
  filters: NostrFilter | NostrFilter[];
84
97
  /** Optional caller-supplied subscription id (else one is generated). */
85
98
  subId?: string;
99
+ /**
100
+ * Restrict the subscription to a single relay. Omit to FAN OUT across every
101
+ * registered relay (the same subId is registered on each); reads merge into
102
+ * one ordered stream.
103
+ */
104
+ relayUrl?: string;
86
105
  }
87
106
  interface SubscribeResponse {
88
107
  subId: string;
108
+ /** The relays the subscription was registered on. */
109
+ relays: string[];
89
110
  }
90
111
  /** `GET /events` — drain buffered events for a subscription (free read). */
91
112
  interface EventsQuery {
@@ -95,6 +116,8 @@ interface EventsQuery {
95
116
  cursor?: number;
96
117
  /** Max events to return (default 200). */
97
118
  limit?: number;
119
+ /** Restrict the drain to events received from a single relay. */
120
+ relayUrl?: string;
98
121
  }
99
122
  interface EventsResponse {
100
123
  events: NostrEvent[];
@@ -143,6 +166,11 @@ interface SwapRequest {
143
166
  chainRecipient: string;
144
167
  /** Split the swap into N equal packets (default 1). */
145
168
  packetCount?: number;
169
+ /**
170
+ * Which apex to settle the source-asset claim through (default: the
171
+ * config-seeded apex). The mill must be a child peer of this apex.
172
+ */
173
+ btpUrl?: string;
146
174
  }
147
175
  /** One accumulated, decrypted claim harvested from a single swap packet. */
148
176
  interface SwapClaim {
@@ -183,6 +211,74 @@ interface SwapResponse {
183
211
  /** First rejection message, if any. */
184
212
  message?: string;
185
213
  }
214
+ /** `POST /relays` — add a relay READ target (fans into all fan-out reads). */
215
+ interface AddRelayRequest {
216
+ /** Relay WS URL, e.g. `ws://host:7100` or a `.anyone` hidden service. */
217
+ relayUrl: string;
218
+ }
219
+ /** `DELETE /relays` — remove a relay read target. */
220
+ interface RemoveRelayRequest {
221
+ relayUrl: string;
222
+ }
223
+ /**
224
+ * `POST /apex` — add an apex WRITE target. Settlement params are DISCOVERED by
225
+ * reading the apex's `kind:10032` announcement off a relay, so the caller never
226
+ * hand-supplies chain/settlement details.
227
+ */
228
+ interface AddApexRequest {
229
+ /** ILP address of the apex to add (e.g. `g.townhouse.town`). */
230
+ ilpAddress: string;
231
+ /**
232
+ * Relay to discover the apex's `kind:10032` on. If it isn't already a read
233
+ * target it is added (and persisted) first.
234
+ */
235
+ relayUrl: string;
236
+ /** Optional apex Nostr pubkey to narrow the discovery filter (64-char hex). */
237
+ pubkey?: string;
238
+ /** Preferred settlement chain family; defaults to the apex's first chain. */
239
+ chain?: SettlementChain;
240
+ /** Child peers reached via this apex's channel (e.g. `["dvm","mill"]`). */
241
+ childPeers?: string[];
242
+ /** Per-write fee override (base units) for this apex. */
243
+ feePerEvent?: string;
244
+ }
245
+ interface AddApexResponse {
246
+ btpUrl: string;
247
+ destination: string;
248
+ chain: SettlementChain;
249
+ /** Whether the apex bootstrapped + opened a channel by the time we replied. */
250
+ ready: boolean;
251
+ }
252
+ /** `DELETE /apex` — remove an apex write target by its BTP URL. */
253
+ interface RemoveApexRequest {
254
+ btpUrl: string;
255
+ }
256
+ /** Status of one registered relay read target. */
257
+ interface RelayTargetStatus {
258
+ relayUrl: string;
259
+ connected: boolean;
260
+ buffered: number;
261
+ subscriptions: string[];
262
+ /** True for the permanent config-seeded relay (not removable). */
263
+ isDefault: boolean;
264
+ }
265
+ /** Status of one registered apex write target. */
266
+ interface ApexTargetStatus {
267
+ btpUrl: string;
268
+ destination: string;
269
+ chain: SettlementChain;
270
+ ready: boolean;
271
+ bootstrapping: boolean;
272
+ channelId?: string;
273
+ lastError?: string;
274
+ /** True for the permanent config-seeded apex (not removable). */
275
+ isDefault: boolean;
276
+ }
277
+ /** `GET /targets` — list every registered relay + apex target. */
278
+ interface TargetsResponse {
279
+ relays: RelayTargetStatus[];
280
+ apexes: ApexTargetStatus[];
281
+ }
186
282
  /** Uniform error envelope returned with non-2xx responses. */
187
283
  interface ErrorResponse {
188
284
  error: string;
@@ -248,6 +344,11 @@ declare class ControlClient {
248
344
  }>;
249
345
  channels(): Promise<ChannelsResponse>;
250
346
  swap(body: SwapRequest): Promise<SwapResponse>;
347
+ targets(): Promise<TargetsResponse>;
348
+ addRelay(body: AddRelayRequest): Promise<TargetsResponse>;
349
+ removeRelay(body: RemoveRelayRequest): Promise<TargetsResponse>;
350
+ addApex(body: AddApexRequest): Promise<AddApexResponse>;
351
+ removeApex(body: RemoveApexRequest): Promise<TargetsResponse>;
251
352
  private request;
252
353
  }
253
354
 
@@ -296,6 +397,14 @@ interface RelaySubscriptionOptions {
296
397
  * object it is used directly and this is not called.
297
398
  */
298
399
  decodeEvent?: (raw: string) => NostrEvent;
400
+ /**
401
+ * Invoked once per newly-buffered (de-duplicated) event. The daemon uses this
402
+ * to feed a runner-level MERGED buffer across many relays — so a fan-out read
403
+ * (`toon_read` with no relayUrl) draws from one ordered stream with a single
404
+ * scalar cursor. The relay still keeps its own buffer for `bufferedCount` /
405
+ * single-relay drains.
406
+ */
407
+ onEvent?: (subId: string, event: NostrEvent) => void;
299
408
  /** Optional logger. */
300
409
  logger?: (msg: string) => void;
301
410
  }
@@ -314,6 +423,7 @@ declare class RelaySubscription {
314
423
  private readonly log;
315
424
  private readonly wsFactory;
316
425
  private readonly decodeEvent?;
426
+ private readonly onEvent?;
317
427
  /** Active subscriptions: subId -> filters (re-sent on every (re)connect). */
318
428
  private readonly subscriptions;
319
429
  /** Ring buffer of received events, ordered by ascending `seq`. */
@@ -406,6 +516,15 @@ interface DaemonConfigFile {
406
516
  mnemonic?: string;
407
517
  mnemonicAccountIndex?: number;
408
518
  keystorePath?: string;
519
+ /**
520
+ * Set when the daemon auto-generated the keystore (#251 first-run onboarding).
521
+ * Such a keystore is encrypted with a default password so the identity reloads
522
+ * across restarts without `TOON_CLIENT_KEYSTORE_PASSWORD`. A user-imported
523
+ * keystore leaves this unset and still requires the env password.
524
+ */
525
+ keystoreAutoPassword?: boolean;
526
+ /** Human-facing onboarding notes written by first-run scaffolding (ignored at runtime). */
527
+ _help?: Record<string, string>;
409
528
  /** BTP WebSocket URL of the apex/connector. */
410
529
  btpUrl?: string;
411
530
  /** Transport: `direct` or a `socks5h://` proxy for `.anyone` hosts. */
@@ -462,6 +581,15 @@ interface ResolvedDaemonConfig {
462
581
  httpPort: number;
463
582
  relayUrl: string;
464
583
  socksProxy?: string;
584
+ /**
585
+ * When true the daemon must start its OWN managed `anon` proxy for free reads
586
+ * — the btp-direct + relay-`.anyone` case the ToonClient does not cover (it
587
+ * only auto-starts a proxy for a `.anyone` btpUrl). `readProxySocksPort` is the
588
+ * loopback port to bind; `socksProxy` already points the relay at it.
589
+ */
590
+ manageReadProxy: boolean;
591
+ /** Loopback SOCKS port for the daemon-managed read proxy (when `manageReadProxy`). */
592
+ readProxySocksPort?: number;
465
593
  destination: string;
466
594
  feePerEvent: bigint;
467
595
  apex?: ApexNegotiationConfig;
@@ -475,6 +603,14 @@ interface ResolvedDaemonConfig {
475
603
  toonClientConfig: ToonClientConfig;
476
604
  network?: string;
477
605
  }
606
+ /**
607
+ * Password used to encrypt an auto-generated keystore (#251 first-run
608
+ * onboarding) when `TOON_CLIENT_KEYSTORE_PASSWORD` is unset. At-rest
609
+ * obfuscation only — its purpose is letting the daemon reload the identity
610
+ * across restarts with no env var. Users wanting a real password re-import the
611
+ * keystore and set the env var.
612
+ */
613
+ declare const DEFAULT_KEYSTORE_PASSWORD = "toon-client-default";
478
614
  /** Default config directory: `~/.toon-client`. Overridable via env. */
479
615
  declare function configDir(): string;
480
616
  /** Default config file path. */
@@ -492,18 +628,23 @@ declare function resolveMnemonic(file: DaemonConfigFile): string;
492
628
  declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
493
629
 
494
630
  /**
495
- * ClientRunner — the daemon's connection owner. Wraps a single `ToonClient`
496
- * (BTP session + payment channels + signer) plus a persistent
497
- * `RelaySubscription` (free reads), and exposes the high-level operations the
498
- * HTTP routes map onto.
631
+ * ClientRunner — the daemon's connection owner. The TOON client is 1-to-MANY:
632
+ * it can write through several apexes (each a `ToonClient` + BTP session +
633
+ * payment channel) and read from several relays (each a `RelaySubscription`).
499
634
  *
500
- * Bootstrap is asynchronous and non-blocking: `start()` returns immediately and
501
- * the connection comes up in the background (the managed anon proxy alone can
502
- * take 30–90s). Until it is ready, write operations report `bootstrapping` so
503
- * tools surface "retry" rather than hang.
635
+ * Writes go through BTP, never to a relay directly — `publish`/`swap` select
636
+ * an apex (default: the config-seeded one).
637
+ * Reads FAN OUT `subscribe`/`getEvents` apply across every relay and merge
638
+ * into one ordered stream with a single scalar cursor (the runner owns the
639
+ * merged buffer; each `RelaySubscription` mirrors new events into it).
504
640
  *
505
- * The `ToonClient` is injected via `ToonClientLike` so unit tests can drive the
506
- * runner with a fake no BTP/anon/on-chain dependency.
641
+ * Targets are added at runtime (`addRelay`/`addApex`), persisted to
642
+ * `targets.json`, replayed on the next boot, and removable. The config-seeded
643
+ * relay + apex are the permanent DEFAULT targets and cannot be removed.
644
+ *
645
+ * Each apex bootstraps asynchronously and non-blocking: the connection comes up
646
+ * in the background (the managed anon proxy alone can take 30–90s). Until ready,
647
+ * writes against it report `bootstrapping` so tools surface "retry".
507
648
  */
508
649
 
509
650
  /** The subset of `ToonClient` the runner depends on. */
@@ -549,98 +690,151 @@ interface ToonClientLike {
549
690
  message?: string;
550
691
  }>;
551
692
  }
693
+ /** A started managed proxy: just the teardown handle the runner needs. */
694
+ interface ManagedProxyHandle {
695
+ stop(): Promise<void>;
696
+ }
697
+ /** Starts a managed `anon` read proxy on a loopback SOCKS port. */
698
+ type StartReadProxy = (opts: {
699
+ socksPort: number;
700
+ log?: (msg: string) => void;
701
+ }) => Promise<ManagedProxyHandle>;
702
+ /** Builds a `ToonClient` (or a fake) for a given resolved client config. */
703
+ type CreateClient = (config: ToonClientConfig) => ToonClientLike;
704
+ /** Builds a `RelaySubscription` for a given relay URL + optional read proxy. */
705
+ type CreateRelay = (opts: {
706
+ relayUrl: string;
707
+ socksProxy?: string;
708
+ onEvent: (subId: string, event: NostrEvent) => void;
709
+ logger?: (msg: string) => void;
710
+ }) => RelaySubscription;
552
711
  interface ClientRunnerDeps {
553
712
  config: ResolvedDaemonConfig;
554
- /** Factory producing the (real or fake) ToonClient. */
555
- createClient: () => ToonClientLike;
556
- /** Factory producing the relay subscription (defaults to the real one). */
557
- createRelay?: () => RelaySubscription;
713
+ /** Factory producing the (real or fake) ToonClient for a client config. */
714
+ createClient: CreateClient;
715
+ /** Factory producing a relay subscription (defaults to the real one). */
716
+ createRelay?: CreateRelay;
717
+ /**
718
+ * Starts the daemon-managed read proxy (defaults to the real
719
+ * `startManagedAnonProxy`). Injected so tests avoid the anon download/spawn.
720
+ */
721
+ startReadProxy?: StartReadProxy;
558
722
  logger?: (msg: string) => void;
723
+ /** Path to the dynamic-targets store (tests override). */
724
+ targetsPath?: string;
559
725
  }
560
726
  declare class ClientRunner {
561
727
  private readonly config;
562
- private readonly client;
563
- private readonly relay;
728
+ private readonly createClient;
729
+ private readonly createRelay;
730
+ private readonly startReadProxy;
564
731
  private readonly log;
732
+ private readonly targetsPath?;
565
733
  private readonly startedAt;
566
- private bootstrapping;
567
- private ready;
568
- private lastError;
569
- /** Channel opened against the default apex destination. */
570
- private apexChannelId;
734
+ /** Apex write targets, keyed by btpUrl. */
735
+ private readonly apexes;
736
+ /** Relay read targets, keyed by relayUrl. */
737
+ private readonly relays;
738
+ /** Runner-level merged read buffer across all relays (de-duped by event.id). */
739
+ private merged;
740
+ private readonly mergedSeen;
741
+ private mergedSeq;
742
+ /**
743
+ * Fan-out subscriptions (no relayUrl restriction): replayed onto relays added
744
+ * later so a new relay immediately participates in existing reads.
745
+ */
746
+ private readonly fanoutSubs;
747
+ private subIdCounter;
748
+ private readonly defaultBtpUrl;
749
+ private readonly defaultRelayUrl;
750
+ /** Teardown for the daemon-managed read proxy (btp-direct + relay-`.anyone`). */
751
+ private stopReadProxy;
752
+ private readProxyError;
571
753
  private stopped;
754
+ private started;
572
755
  constructor(deps: ClientRunnerDeps);
573
756
  /**
574
- * Begin bootstrapping in the background. Resolves once kicked off; the
575
- * connection becomes ready asynchronously. Awaitable for tests via the
576
- * returned promise of the underlying work.
757
+ * Start the live connections: the shared read proxy, every relay socket, the
758
+ * default apex bootstrap (non-blocking), then replay persisted dynamic
759
+ * targets. Returns immediately; apexes become ready asynchronously.
577
760
  */
578
761
  start(): void;
579
- /** The background bootstrap routine (exposed for awaiting in tests). */
762
+ /** Await the default apex's bootstrap (kicking it off if not already running). */
580
763
  bootstrap(): Promise<void>;
581
764
  /**
582
- * Open the apex channel or, on a restart, RESUME the existing one.
583
- *
584
- * `ChannelManager` persists the nonce watermark (by channelId) but not the
585
- * peer→channelId mapping, so a naive `openChannel()` after restart re-deposits
586
- * into a fresh channel and reverts on-chain. We persist the channelId here and,
587
- * when present, `trackChannel()` the live channel (which rehydrates the nonce
588
- * from the channel store) — no on-chain write, watermark continues.
765
+ * Build + register a relay (idempotent by URL), wiring its events into the
766
+ * merged buffer and replaying active fan-out subscriptions. Does NOT start the
767
+ * socket callers start it (so construction stays side-effect-free for tests).
589
768
  */
590
- private openOrResumeApexChannel;
769
+ private registerRelay;
591
770
  /**
592
- * Inject the apex settlement negotiation directly into the ToonClient when
593
- * configured. Required for HS / direct-apex mode where bootstrap discovers 0
594
- * peers (no relay-based discovery). Mirrors the docker entrypoint's approach.
771
+ * Add a relay read target at runtime. `.anyone` relays reuse the managed read
772
+ * proxy (started here if needed). Persisted unless `persist` is false.
595
773
  */
596
- private injectApexNegotiation;
774
+ addRelay(relayUrl: string, persist?: boolean): Promise<void>;
775
+ /** Remove a relay read target. The config-seeded default cannot be removed. */
776
+ removeRelay(relayUrl: string): void;
777
+ /** Mirror a newly-buffered relay event into the merged cross-relay buffer. */
778
+ private pushMerged;
779
+ /**
780
+ * Register a free-read subscription. With no `relayUrl` it FANS OUT across
781
+ * every relay (and onto relays added later); with one it targets that relay.
782
+ */
783
+ subscribe(req: SubscribeRequest): SubscribeResponse;
784
+ /** Drain merged events newer than the cursor (free read), optionally scoped. */
785
+ getEvents(query: EventsQuery): EventsResponse;
786
+ private makeApex;
597
787
  /**
598
- * Route additional apex CHILD peers (e.g. `dvm`, `mill`) through the SAME
599
- * apex payment channel. In the parent→child apex model the client holds ONE
600
- * channel with the apex (g.townhouse) and pays via it regardless of which
601
- * child the ILP destination addresses; but `ToonClient.resolvePeerId` keys off
602
- * the destination's last segment (`town`/`dvm`/`mill`), so without this each
603
- * child would (a) fail the "no negotiation for peer" guard and (b) try to open
604
- * a SECOND on-chain channel to the same apex receive (which reverts —
605
- * channel-exists). So: inject the same apex negotiation under each child peer
606
- * AND pre-map its peer→channel to the already-open apex channel so
607
- * `ensureChannel` reuses it (no second open; one shared nonce sequence).
788
+ * Bootstrap one apex (memoized): start, inject negotiation, open/resume the
789
+ * channel, route child peers. Concurrent callers await the same in-flight
790
+ * work rather than re-running it.
608
791
  */
792
+ private bootstrapApex;
793
+ private doBootstrapApex;
794
+ /**
795
+ * Add an apex write target. Settlement params are discovered by reading the
796
+ * apex's kind:10032 off the given relay (added first if unknown). Persisted.
797
+ */
798
+ addApex(req: AddApexRequest): Promise<AddApexResponse>;
799
+ /** Build + register + bootstrap an apex from a (persisted) target record. */
800
+ private instantiateApex;
801
+ /** Remove an apex write target. The config-seeded default cannot be removed. */
802
+ removeApex(btpUrl: string): Promise<void>;
803
+ /** Derive a per-apex ToonClientConfig from the default (shared identity/transport). */
804
+ private deriveApexClientConfig;
805
+ private apexChannelStorePathFor;
806
+ private replayPersistedTargets;
807
+ private ensureReadProxy;
808
+ private bringUpReadProxy;
809
+ /** Open the apex channel — or, on a restart, RESUME the existing one. */
810
+ private openOrResumeApexChannel;
811
+ /** Inject the apex settlement negotiation directly into its ToonClient. */
812
+ private injectApexNegotiation;
813
+ /** Route apex CHILD peers (dvm/mill) through the SAME apex payment channel. */
609
814
  private routeChildPeersThroughApexChannel;
815
+ private defaultApex;
816
+ /** Whether any apex has finished bootstrapping. */
610
817
  isReady(): boolean;
611
818
  isBootstrapping(): boolean;
612
819
  getStatus(): StatusResponse;
613
- /** Pay-to-write a single event. Throws NOT_READY while bootstrapping. */
820
+ /** Full registry of relay + apex targets with per-target status. */
821
+ getTargets(): TargetsResponse;
822
+ /** Pay-to-write a single event through the selected (or default) apex. */
614
823
  publish(req: PublishRequest): Promise<PublishResponse>;
615
- /** Register a free-read subscription (does not require the paid path). */
616
- subscribe(req: SubscribeRequest): SubscribeResponse;
617
- /** Drain buffered events newer than the cursor (free read). */
618
- getEvents(query: EventsQuery): EventsResponse;
619
- /** Open (or return) a payment channel for a destination. */
620
- openChannel(destination?: string): Promise<{
824
+ /** Open (or return) a payment channel on the selected (or default) apex. */
825
+ openChannel(destination?: string, btpUrl?: string): Promise<{
621
826
  channelId: string;
622
827
  }>;
623
- /** List tracked channels with their nonce watermark + cumulative amount. */
828
+ /** List tracked channels across ALL apexes with nonce + cumulative amount. */
624
829
  getChannels(): ChannelsResponse;
625
- /**
626
- * Swap source asset → target asset against a mill peer.
627
- *
628
- * Uses SDK `streamSwap`, which builds a NIP-59 gift-wrapped kind:20032 swap
629
- * rumor per packet and sends it over the open BTP session. The source-asset
630
- * balance proof is signed by the ToonClient's ChannelManager against the apex
631
- * channel — so the mill peer MUST be routed via `apexChildPeers` (otherwise
632
- * there is no channel to sign against and the mill rejects with F99).
633
- *
634
- * A fresh ephemeral gift-wrap key is generated per swap (used for sealing the
635
- * rumor AND decrypting the FULFILL claims) — independent of the daemon's
636
- * settlement identity, so callers never need to expose a key.
637
- */
830
+ /** Swap source→target asset against a mill peer via the selected apex. */
638
831
  swap(req: SwapRequest): Promise<SwapResponse>;
639
- /** Graceful teardown of the relay subscription + ToonClient. */
832
+ /** Graceful teardown: close every relay + stop every apex client + read proxy. */
640
833
  stop(): Promise<void>;
641
- private assertReady;
834
+ private selectApex;
835
+ private assertApexReady;
642
836
  }
643
- /** Thrown by paid-write operations while the daemon is not yet ready. */
837
+ /** Thrown by paid-write operations while the target apex is not yet ready. */
644
838
  declare class NotReadyError extends Error {
645
839
  readonly retryable = true;
646
840
  constructor(message: string);
@@ -650,6 +844,49 @@ declare class PublishRejectedError extends Error {
650
844
  constructor(message: string);
651
845
  }
652
846
 
847
+ /**
848
+ * First-run onboarding for `toon-clientd`.
849
+ *
850
+ * A brand-new user (`npx`/plugin install) has no identity and no config file.
851
+ * Before the daemon resolves its config it calls {@link scaffoldFirstRun},
852
+ * which makes a fresh install start with zero manual setup:
853
+ *
854
+ * 1. **Identity** — if no mnemonic source is configured (no
855
+ * `TOON_CLIENT_MNEMONIC`, no `keystorePath`, no `mnemonic`), generate a
856
+ * fresh BIP-39 mnemonic, encrypt it to `~/.toon-client/keystore.json`, and
857
+ * record `keystorePath` (+ `keystoreAutoPassword`) in `config.json`. The
858
+ * keystore is encrypted with `TOON_CLIENT_KEYSTORE_PASSWORD` when set, else
859
+ * a default password so the identity survives restarts with no env var.
860
+ * The mnemonic + derived addresses are printed ONCE for backup.
861
+ * 2. **Transport scaffolding** — ensure `config.json` carries the transport
862
+ * knobs (`btpUrl`/`relayUrl`/`managedAnonProxy`) plus a `_help` block
863
+ * documenting direct-vs-`.anyone` selection, so the user can see what to
864
+ * point at.
865
+ *
866
+ * This is all idempotent: on later runs an identity already exists, so nothing
867
+ * is regenerated and the config is left untouched.
868
+ */
869
+
870
+ /** Default keystore path: `~/.toon-client/keystore.json`. */
871
+ declare function defaultKeystorePath(): string;
872
+ /**
873
+ * True when SOME mnemonic source is already configured: the env var, a keystore
874
+ * path, or an inline mnemonic. When false the daemon would otherwise hard-fail
875
+ * with "No mnemonic configured".
876
+ */
877
+ declare function hasConfiguredIdentity(file: DaemonConfigFile): boolean;
878
+ interface ScaffoldOptions {
879
+ /** Config file path (defaults to `TOON_CLIENT_CONFIG` / `~/.toon-client/config.json`). */
880
+ configPath?: string;
881
+ /** Log sink (defaults to stderr, since stdout may carry MCP/JSON). */
882
+ log?: (msg: string) => void;
883
+ }
884
+ /**
885
+ * Generate + persist an identity and/or scaffold transport config on first run.
886
+ * Safe to call on every startup — it only acts when something is missing.
887
+ */
888
+ declare function scaffoldFirstRun(opts?: ScaffoldOptions): Promise<void>;
889
+
653
890
  /**
654
891
  * Fastify route registration for the `toon-clientd` control plane. Each route
655
892
  * is a thin adapter: parse/validate the request, call the `ClientRunner`, map
@@ -725,4 +962,4 @@ declare const TOOL_DEFINITIONS: ToolDefinition[];
725
962
  */
726
963
  declare function dispatchTool(client: ControlClient, name: string, args: Record<string, unknown>): Promise<ToolResult>;
727
964
 
728
- export { type ApexNegotiationConfig, type ChainStatus, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, ControlApiError, ControlClient, type ControlClientOptions, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, RelaySubscription, type RelaySubscriptionOptions, type ResolvedDaemonConfig, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapClaim, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type ToolDefinition, type ToolResult, type ToonClientLike, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, dispatchTool, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, spawnDaemonDetached, waitForReady };
965
+ export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type ChainStatus, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, ControlApiError, ControlClient, type ControlClientOptions, DEFAULT_KEYSTORE_PASSWORD, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, RelaySubscription, type RelaySubscriptionOptions, type RelayTargetStatus, type RemoveApexRequest, type RemoveRelayRequest, type ResolvedDaemonConfig, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapClaim, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type TargetsResponse, type ToolDefinition, type ToolResult, type ToonClientLike, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, scaffoldFirstRun, spawnDaemonDetached, waitForReady };
package/dist/index.js CHANGED
@@ -4,15 +4,19 @@ import {
4
4
  NotReadyError,
5
5
  PublishRejectedError,
6
6
  RelaySubscription,
7
- registerRoutes
8
- } from "./chunk-SECSBCDA.js";
7
+ defaultKeystorePath,
8
+ hasConfiguredIdentity,
9
+ registerRoutes,
10
+ scaffoldFirstRun
11
+ } from "./chunk-AYZWVPNQ.js";
9
12
  import {
10
13
  TOOL_DEFINITIONS,
11
14
  dispatchTool
12
- } from "./chunk-JOGMX4IT.js";
15
+ } from "./chunk-CPVOJ7FE.js";
13
16
  import {
14
17
  ControlApiError,
15
18
  ControlClient,
19
+ DEFAULT_KEYSTORE_PASSWORD,
16
20
  DaemonUnreachableError,
17
21
  acquireLock,
18
22
  configDir,
@@ -26,7 +30,7 @@ import {
26
30
  resolveMnemonic,
27
31
  spawnDaemonDetached,
28
32
  waitForReady
29
- } from "./chunk-MI2IWKHX.js";
33
+ } from "./chunk-ALMTQYYJ.js";
30
34
  import "./chunk-32QD72IL.js";
31
35
  import "./chunk-QTDCFXPF.js";
32
36
  import "./chunk-LR7W2ISE.js";
@@ -37,6 +41,7 @@ export {
37
41
  ClientRunner,
38
42
  ControlApiError,
39
43
  ControlClient,
44
+ DEFAULT_KEYSTORE_PASSWORD,
40
45
  DaemonUnreachableError,
41
46
  NotReadyError,
42
47
  PublishRejectedError,
@@ -45,7 +50,9 @@ export {
45
50
  acquireLock,
46
51
  configDir,
47
52
  defaultConfigPath,
53
+ defaultKeystorePath,
48
54
  dispatchTool,
55
+ hasConfiguredIdentity,
49
56
  isDaemonRunning,
50
57
  isProcessAlive,
51
58
  readConfigFile,
@@ -54,6 +61,7 @@ export {
54
61
  releaseLock,
55
62
  resolveConfig,
56
63
  resolveMnemonic,
64
+ scaffoldFirstRun,
57
65
  spawnDaemonDetached,
58
66
  waitForReady
59
67
  };
package/dist/mcp.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __cr } from 'module'; const require = __cr(import.meta
3
3
  import {
4
4
  TOOL_DEFINITIONS,
5
5
  dispatchTool
6
- } from "./chunk-JOGMX4IT.js";
6
+ } from "./chunk-CPVOJ7FE.js";
7
7
  import {
8
8
  ControlClient,
9
9
  defaultConfigPath,
@@ -11,7 +11,7 @@ import {
11
11
  readConfigFile,
12
12
  spawnDaemonDetached,
13
13
  waitForReady
14
- } from "./chunk-MI2IWKHX.js";
14
+ } from "./chunk-ALMTQYYJ.js";
15
15
  import "./chunk-32QD72IL.js";
16
16
  import "./chunk-QTDCFXPF.js";
17
17
  import "./chunk-LR7W2ISE.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/client-mcp",
3
- "version": "0.31.1",
3
+ "version": "0.32.0",
4
4
  "description": "Always-on local daemon + MCP server letting a Claude agent (Desktop or Code) act as a TOON Protocol client: pay-to-write publishing, free reads, channel/balance management, and mill swaps.",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Green",
@@ -45,8 +45,8 @@
45
45
  "typescript": "^5.3.0",
46
46
  "vitest": "^1.0.0",
47
47
  "@toon-protocol/client": "0.9.1",
48
- "@toon-protocol/sdk": "0.5.0",
49
- "@toon-protocol/core": "1.4.1"
48
+ "@toon-protocol/core": "1.4.1",
49
+ "@toon-protocol/sdk": "0.5.0"
50
50
  },
51
51
  "engines": {
52
52
  "node": ">=20"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/mcp-tools.ts"],"sourcesContent":["/**\n * MCP tool definitions + dispatch. The MCP server is a thin proxy: each tool\n * maps to a `toon-clientd` control-plane call. This module is the testable core\n * (no stdio / SDK transport) so the tool→HTTP mapping and the\n * \"bootstrapping — retry\" handling can be unit-tested directly.\n */\n\nimport { ControlApiError, DaemonUnreachableError } from './control-client.js';\nimport type { ControlClient } from './control-client.js';\nimport type {\n NostrFilter,\n PublishRequest,\n SwapRequest,\n} from './control-api.js';\n\n/** A JSON-Schema-described MCP tool. */\nexport interface ToolDefinition {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n}\n\n/** MCP tool-call result shape (subset of the SDK's CallToolResult). */\nexport interface ToolResult {\n content: { type: 'text'; text: string }[];\n isError?: boolean;\n}\n\nexport const TOOL_DEFINITIONS: ToolDefinition[] = [\n {\n name: 'toon_status',\n description:\n 'Report TOON client daemon health: bootstrapping/ready state, transport, ' +\n 'relay connection, buffered-event count, and per-chain settlement status.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'toon_identity',\n description:\n \"Return this client's public identity (Nostr pubkey + EVM/Solana/Mina \" +\n 'addresses). Never returns private keys.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'toon_publish',\n description:\n 'Pay-to-write: publish a fully-signed Nostr event to the TOON network. ' +\n 'Signs an off-chain payment-channel claim and forwards it over BTP. ' +\n 'Returns the event id, channel id, and the advanced channel nonce.',\n inputSchema: {\n type: 'object',\n properties: {\n event: {\n type: 'object',\n description:\n 'A fully-signed Nostr event (must include id, pubkey, sig, kind, ' +\n 'created_at, tags, content).',\n },\n destination: {\n type: 'string',\n description:\n 'Optional ILP destination override (default: the apex/town).',\n },\n fee: {\n type: 'string',\n description:\n 'Optional fee override in base units (default: daemon config).',\n },\n },\n required: ['event'],\n additionalProperties: false,\n },\n },\n {\n name: 'toon_subscribe',\n description:\n 'Free read: register a persistent town-relay subscription with NIP-01 ' +\n 'filter(s). Returns a subscription id to drain with toon_read.',\n inputSchema: {\n type: 'object',\n properties: {\n filters: {\n description: 'A NIP-01 filter object or an array of OR-ed filters.',\n },\n subId: { type: 'string', description: 'Optional caller-supplied id.' },\n },\n required: ['filters'],\n additionalProperties: false,\n },\n },\n {\n name: 'toon_read',\n description:\n 'Free read: drain buffered events newer than a cursor. Pass back the ' +\n 'returned cursor to fetch only events received since the last read.',\n inputSchema: {\n type: 'object',\n properties: {\n subId: { type: 'string', description: 'Restrict to one subscription.' },\n cursor: {\n type: 'number',\n description: 'Cursor from a prior toon_read.',\n },\n limit: {\n type: 'number',\n description: 'Max events to return (default 200).',\n },\n },\n additionalProperties: false,\n },\n },\n {\n name: 'toon_open_channel',\n description:\n 'Open (or return the existing) payment channel for a destination. ' +\n 'Channels open lazily on first publish; use this to pre-open.',\n inputSchema: {\n type: 'object',\n properties: {\n destination: {\n type: 'string',\n description: 'ILP destination (default: apex).',\n },\n },\n additionalProperties: false,\n },\n },\n {\n name: 'toon_channels',\n description:\n 'List tracked payment channels with their nonce watermark and cumulative ' +\n 'transferred amount.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n },\n {\n name: 'toon_swap',\n description:\n 'Pay a mill peer (asset A) to receive asset B plus a signed target-chain ' +\n 'claim. Builds the NIP-59 gift-wrapped kind:20032 swap rumor and streams ' +\n 'it; the source-asset claim is signed against the open apex channel (the ' +\n 'mill must be routed via apexChildPeers). Returns the accumulated, ' +\n 'decrypted target-chain claim(s) and settlement metadata.',\n inputSchema: {\n type: 'object',\n properties: {\n destination: {\n type: 'string',\n description: 'Mill peer ILP destination (e.g. g.townhouse.mill).',\n },\n amount: {\n type: 'string',\n description: 'Total source-asset amount to swap, source micro-units.',\n },\n millPubkey: {\n type: 'string',\n description:\n \"Mill's 64-char lowercase hex Nostr pubkey (gift-wrap recipient).\",\n },\n pair: {\n type: 'object',\n description:\n 'The swap pair (from kind:10032 discovery or operator-supplied): ' +\n '{ from:{assetCode,assetScale,chain}, to:{...}, rate, minAmount?, maxAmount? }.',\n },\n chainRecipient: {\n type: 'string',\n description:\n \"Sender's payout address on pair.to.chain (EVM 0x-hex / Solana / Mina base58).\",\n },\n packetCount: {\n type: 'number',\n description: 'Split the swap into N equal packets (default 1).',\n },\n },\n required: [\n 'destination',\n 'amount',\n 'millPubkey',\n 'pair',\n 'chainRecipient',\n ],\n additionalProperties: false,\n },\n },\n];\n\n/**\n * Dispatch an MCP tool call to the daemon control plane. Always resolves with a\n * `ToolResult` (errors are encoded as `isError: true` text, not thrown, so the\n * agent sees a readable message). A retryable error (daemon still\n * bootstrapping) yields a clear \"retry shortly\" message.\n */\nexport async function dispatchTool(\n client: ControlClient,\n name: string,\n args: Record<string, unknown>\n): Promise<ToolResult> {\n try {\n switch (name) {\n case 'toon_status':\n return ok(await client.status());\n case 'toon_identity': {\n const s = await client.status();\n return ok({\n identity: s.identity,\n ready: s.ready,\n bootstrapping: s.bootstrapping,\n });\n }\n case 'toon_publish':\n return ok(await client.publish(args as unknown as PublishRequest));\n case 'toon_subscribe':\n return ok(\n await client.subscribe({\n filters: args['filters'] as NostrFilter | NostrFilter[],\n ...(typeof args['subId'] === 'string'\n ? { subId: args['subId'] }\n : {}),\n })\n );\n case 'toon_read':\n return ok(\n await client.events({\n ...(typeof args['subId'] === 'string'\n ? { subId: args['subId'] }\n : {}),\n ...(typeof args['cursor'] === 'number'\n ? { cursor: args['cursor'] }\n : {}),\n ...(typeof args['limit'] === 'number'\n ? { limit: args['limit'] }\n : {}),\n })\n );\n case 'toon_open_channel':\n return ok(\n await client.openChannel(\n typeof args['destination'] === 'string'\n ? { destination: args['destination'] }\n : {}\n )\n );\n case 'toon_channels':\n return ok(await client.channels());\n case 'toon_swap':\n return ok(\n await client.swap({\n destination: String(args['destination']),\n amount: String(args['amount']),\n millPubkey: String(args['millPubkey']),\n pair: args['pair'] as SwapRequest['pair'],\n chainRecipient: String(args['chainRecipient']),\n ...(typeof args['packetCount'] === 'number'\n ? { packetCount: args['packetCount'] }\n : {}),\n })\n );\n default:\n return err(`Unknown tool: ${name}`);\n }\n } catch (e) {\n if (e instanceof ControlApiError && e.retryable) {\n return err(\n `TOON client is still bootstrapping (the anon proxy / BTP session can take ` +\n `30–90s) — retry shortly. (${e.message})`\n );\n }\n if (e instanceof DaemonUnreachableError) {\n return err(\n `TOON client daemon is not reachable at ${e.baseUrl}. It may have failed ` +\n `to start — check ~/.toon-client/daemon.log.`\n );\n }\n if (e instanceof ControlApiError) {\n return err(`${e.message}${e.detail ? `: ${e.detail}` : ''}`);\n }\n return err(e instanceof Error ? e.message : String(e));\n }\n}\n\nfunction ok(data: unknown): ToolResult {\n return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };\n}\n\nfunction err(message: string): ToolResult {\n return { content: [{ type: 'text', text: message }], isError: true };\n}\n"],"mappings":"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","names":[]}