@toon-protocol/client-mcp 0.31.2 → 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
@@ -74,6 +74,12 @@ interface PublishRequest {
74
74
  destination?: string;
75
75
  /** Fee override in base units. Defaults to the daemon's configured fee. */
76
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;
77
83
  }
78
84
  interface PublishResponse {
79
85
  eventId: string;
@@ -90,9 +96,17 @@ interface SubscribeRequest {
90
96
  filters: NostrFilter | NostrFilter[];
91
97
  /** Optional caller-supplied subscription id (else one is generated). */
92
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;
93
105
  }
94
106
  interface SubscribeResponse {
95
107
  subId: string;
108
+ /** The relays the subscription was registered on. */
109
+ relays: string[];
96
110
  }
97
111
  /** `GET /events` — drain buffered events for a subscription (free read). */
98
112
  interface EventsQuery {
@@ -102,6 +116,8 @@ interface EventsQuery {
102
116
  cursor?: number;
103
117
  /** Max events to return (default 200). */
104
118
  limit?: number;
119
+ /** Restrict the drain to events received from a single relay. */
120
+ relayUrl?: string;
105
121
  }
106
122
  interface EventsResponse {
107
123
  events: NostrEvent[];
@@ -150,6 +166,11 @@ interface SwapRequest {
150
166
  chainRecipient: string;
151
167
  /** Split the swap into N equal packets (default 1). */
152
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;
153
174
  }
154
175
  /** One accumulated, decrypted claim harvested from a single swap packet. */
155
176
  interface SwapClaim {
@@ -190,6 +211,74 @@ interface SwapResponse {
190
211
  /** First rejection message, if any. */
191
212
  message?: string;
192
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
+ }
193
282
  /** Uniform error envelope returned with non-2xx responses. */
194
283
  interface ErrorResponse {
195
284
  error: string;
@@ -255,6 +344,11 @@ declare class ControlClient {
255
344
  }>;
256
345
  channels(): Promise<ChannelsResponse>;
257
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>;
258
352
  private request;
259
353
  }
260
354
 
@@ -303,6 +397,14 @@ interface RelaySubscriptionOptions {
303
397
  * object it is used directly and this is not called.
304
398
  */
305
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;
306
408
  /** Optional logger. */
307
409
  logger?: (msg: string) => void;
308
410
  }
@@ -321,6 +423,7 @@ declare class RelaySubscription {
321
423
  private readonly log;
322
424
  private readonly wsFactory;
323
425
  private readonly decodeEvent?;
426
+ private readonly onEvent?;
324
427
  /** Active subscriptions: subId -> filters (re-sent on every (re)connect). */
325
428
  private readonly subscriptions;
326
429
  /** Ring buffer of received events, ordered by ascending `seq`. */
@@ -525,18 +628,23 @@ declare function resolveMnemonic(file: DaemonConfigFile): string;
525
628
  declare function resolveConfig(file: DaemonConfigFile): ResolvedDaemonConfig;
526
629
 
527
630
  /**
528
- * ClientRunner — the daemon's connection owner. Wraps a single `ToonClient`
529
- * (BTP session + payment channels + signer) plus a persistent
530
- * `RelaySubscription` (free reads), and exposes the high-level operations the
531
- * 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`).
634
+ *
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).
532
640
  *
533
- * Bootstrap is asynchronous and non-blocking: `start()` returns immediately and
534
- * the connection comes up in the background (the managed anon proxy alone can
535
- * take 30–90s). Until it is ready, write operations report `bootstrapping` so
536
- * tools surface "retry" rather than hang.
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.
537
644
  *
538
- * The `ToonClient` is injected via `ToonClientLike` so unit tests can drive the
539
- * runner with a fake no BTP/anon/on-chain dependency.
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".
540
648
  */
541
649
 
542
650
  /** The subset of `ToonClient` the runner depends on. */
@@ -591,110 +699,142 @@ type StartReadProxy = (opts: {
591
699
  socksPort: number;
592
700
  log?: (msg: string) => void;
593
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;
594
711
  interface ClientRunnerDeps {
595
712
  config: ResolvedDaemonConfig;
596
- /** Factory producing the (real or fake) ToonClient. */
597
- createClient: () => ToonClientLike;
598
- /** Factory producing the relay subscription (defaults to the real one). */
599
- 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;
600
717
  /**
601
718
  * Starts the daemon-managed read proxy (defaults to the real
602
719
  * `startManagedAnonProxy`). Injected so tests avoid the anon download/spawn.
603
720
  */
604
721
  startReadProxy?: StartReadProxy;
605
722
  logger?: (msg: string) => void;
723
+ /** Path to the dynamic-targets store (tests override). */
724
+ targetsPath?: string;
606
725
  }
607
726
  declare class ClientRunner {
608
727
  private readonly config;
609
- private readonly client;
610
- private readonly relay;
728
+ private readonly createClient;
729
+ private readonly createRelay;
611
730
  private readonly startReadProxy;
612
731
  private readonly log;
732
+ private readonly targetsPath?;
613
733
  private readonly startedAt;
614
- private bootstrapping;
615
- private ready;
616
- private lastError;
617
- /** Channel opened against the default apex destination. */
618
- private apexChannelId;
619
- /** Teardown for a daemon-managed read proxy (btp-direct + relay-`.anyone`). */
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`). */
620
751
  private stopReadProxy;
621
- /** Error from the read proxy (kept separate from the paid-path `lastError`). */
622
752
  private readProxyError;
623
753
  private stopped;
754
+ private started;
624
755
  constructor(deps: ClientRunnerDeps);
625
756
  /**
626
- * Begin bootstrapping in the background. Resolves once kicked off; the
627
- * connection becomes ready asynchronously. Awaitable for tests via the
628
- * 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.
629
760
  */
630
761
  start(): void;
631
- /** Start the daemon-managed `anon` read proxy (best-effort; logs on failure). */
632
- private bringUpReadProxy;
633
- /** The background bootstrap routine (exposed for awaiting in tests). */
762
+ /** Await the default apex's bootstrap (kicking it off if not already running). */
634
763
  bootstrap(): Promise<void>;
635
764
  /**
636
- * Open the apex channel or, on a restart, RESUME the existing one.
637
- *
638
- * `ChannelManager` persists the nonce watermark (by channelId) but not the
639
- * peer→channelId mapping, so a naive `openChannel()` after restart re-deposits
640
- * into a fresh channel and reverts on-chain. We persist the channelId here and,
641
- * when present, `trackChannel()` the live channel (which rehydrates the nonce
642
- * 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).
643
768
  */
644
- private openOrResumeApexChannel;
769
+ private registerRelay;
645
770
  /**
646
- * Inject the apex settlement negotiation directly into the ToonClient when
647
- * configured. Required for HS / direct-apex mode where bootstrap discovers 0
648
- * 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.
649
773
  */
650
- 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;
787
+ /**
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.
791
+ */
792
+ private bootstrapApex;
793
+ private doBootstrapApex;
651
794
  /**
652
- * Route additional apex CHILD peers (e.g. `dvm`, `mill`) through the SAME
653
- * apex payment channel. In the parent→child apex model the client holds ONE
654
- * channel with the apex (g.townhouse) and pays via it regardless of which
655
- * child the ILP destination addresses; but `ToonClient.resolvePeerId` keys off
656
- * the destination's last segment (`town`/`dvm`/`mill`), so without this each
657
- * child would (a) fail the "no negotiation for peer" guard and (b) try to open
658
- * a SECOND on-chain channel to the same apex receive (which reverts —
659
- * channel-exists). So: inject the same apex negotiation under each child peer
660
- * AND pre-map its peer→channel to the already-open apex channel so
661
- * `ensureChannel` reuses it (no second open; one shared nonce sequence).
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.
662
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. */
663
814
  private routeChildPeersThroughApexChannel;
815
+ private defaultApex;
816
+ /** Whether any apex has finished bootstrapping. */
664
817
  isReady(): boolean;
665
818
  isBootstrapping(): boolean;
666
819
  getStatus(): StatusResponse;
667
- /** 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. */
668
823
  publish(req: PublishRequest): Promise<PublishResponse>;
669
- /** Register a free-read subscription (does not require the paid path). */
670
- subscribe(req: SubscribeRequest): SubscribeResponse;
671
- /** Drain buffered events newer than the cursor (free read). */
672
- getEvents(query: EventsQuery): EventsResponse;
673
- /** Open (or return) a payment channel for a destination. */
674
- openChannel(destination?: string): Promise<{
824
+ /** Open (or return) a payment channel on the selected (or default) apex. */
825
+ openChannel(destination?: string, btpUrl?: string): Promise<{
675
826
  channelId: string;
676
827
  }>;
677
- /** List tracked channels with their nonce watermark + cumulative amount. */
828
+ /** List tracked channels across ALL apexes with nonce + cumulative amount. */
678
829
  getChannels(): ChannelsResponse;
679
- /**
680
- * Swap source asset → target asset against a mill peer.
681
- *
682
- * Uses SDK `streamSwap`, which builds a NIP-59 gift-wrapped kind:20032 swap
683
- * rumor per packet and sends it over the open BTP session. The source-asset
684
- * balance proof is signed by the ToonClient's ChannelManager against the apex
685
- * channel — so the mill peer MUST be routed via `apexChildPeers` (otherwise
686
- * there is no channel to sign against and the mill rejects with F99).
687
- *
688
- * A fresh ephemeral gift-wrap key is generated per swap (used for sealing the
689
- * rumor AND decrypting the FULFILL claims) — independent of the daemon's
690
- * settlement identity, so callers never need to expose a key.
691
- */
830
+ /** Swap source→target asset against a mill peer via the selected apex. */
692
831
  swap(req: SwapRequest): Promise<SwapResponse>;
693
- /** Graceful teardown of the relay subscription + ToonClient. */
832
+ /** Graceful teardown: close every relay + stop every apex client + read proxy. */
694
833
  stop(): Promise<void>;
695
- private assertReady;
834
+ private selectApex;
835
+ private assertApexReady;
696
836
  }
697
- /** 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. */
698
838
  declare class NotReadyError extends Error {
699
839
  readonly retryable = true;
700
840
  constructor(message: string);
@@ -822,4 +962,4 @@ declare const TOOL_DEFINITIONS: ToolDefinition[];
822
962
  */
823
963
  declare function dispatchTool(client: ControlClient, name: string, args: Record<string, unknown>): Promise<ToolResult>;
824
964
 
825
- export { type ApexNegotiationConfig, 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 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, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, scaffoldFirstRun, 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
@@ -8,11 +8,11 @@ import {
8
8
  hasConfiguredIdentity,
9
9
  registerRoutes,
10
10
  scaffoldFirstRun
11
- } from "./chunk-PRFPAEGG.js";
11
+ } from "./chunk-AYZWVPNQ.js";
12
12
  import {
13
13
  TOOL_DEFINITIONS,
14
14
  dispatchTool
15
- } from "./chunk-I5L5CB4N.js";
15
+ } from "./chunk-CPVOJ7FE.js";
16
16
  import {
17
17
  ControlApiError,
18
18
  ControlClient,
@@ -30,7 +30,7 @@ import {
30
30
  resolveMnemonic,
31
31
  spawnDaemonDetached,
32
32
  waitForReady
33
- } from "./chunk-ARFPM2PT.js";
33
+ } from "./chunk-ALMTQYYJ.js";
34
34
  import "./chunk-32QD72IL.js";
35
35
  import "./chunk-QTDCFXPF.js";
36
36
  import "./chunk-LR7W2ISE.js";
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-I5L5CB4N.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-ARFPM2PT.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.2",
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",
@@ -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":[]}