@toon-protocol/client-mcp 0.1.0 → 0.2.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.
@@ -0,0 +1,861 @@
1
+ import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
+ import {
3
+ ControlApiError,
4
+ DaemonUnreachableError
5
+ } from "./chunk-NP35YO5O.js";
6
+
7
+ // ../views/dist/tool-names.js
8
+ var PUBLISH_TOOL = "toon_publish_unsigned";
9
+ var UPLOAD_TOOL = "toon_upload_media";
10
+ var APP_RESOURCE_URI = "ui://toon/app";
11
+ var WRITE_TOOLS = /* @__PURE__ */ new Set([PUBLISH_TOOL, UPLOAD_TOOL]);
12
+
13
+ // ../views/dist/filters.js
14
+ function buildRepoListFilter() {
15
+ return { kinds: [30617] };
16
+ }
17
+ function buildIssueListFilter(ownerPubkey, repoId) {
18
+ return { kinds: [1621], "#a": [`30617:${ownerPubkey}:${repoId}`], limit: 100 };
19
+ }
20
+ function buildCommentFilter(eventIds) {
21
+ return { kinds: [1622], "#e": eventIds, limit: 500 };
22
+ }
23
+ function buildEventByIdFilter(eventIds) {
24
+ return { ids: eventIds };
25
+ }
26
+ function buildProfileFilter(pubkeys) {
27
+ return { kinds: [0], authors: pubkeys };
28
+ }
29
+ function buildFeedFilter(authors, limit = 100) {
30
+ const f = { kinds: [1], limit };
31
+ if (authors && authors.length > 0)
32
+ f.authors = authors;
33
+ return f;
34
+ }
35
+ function buildMediaFeedFilter(authors, limit = 100) {
36
+ const f = { kinds: [20, 21, 22], limit };
37
+ if (authors && authors.length > 0)
38
+ f.authors = authors;
39
+ return f;
40
+ }
41
+
42
+ // ../views/dist/spec.js
43
+ var DEFAULT_MAX_DEPTH = 32;
44
+ var DEFAULT_MAX_NODES = 500;
45
+ var FILTER_ARRAY_KEYS = /* @__PURE__ */ new Set([
46
+ "kinds",
47
+ "authors",
48
+ "ids",
49
+ "#d",
50
+ "#e",
51
+ "#a",
52
+ "#p",
53
+ "#t"
54
+ ]);
55
+ var FILTER_NUM_KEYS = /* @__PURE__ */ new Set(["since", "until", "limit"]);
56
+ function isPlainObject(v) {
57
+ return typeof v === "object" && v !== null && !Array.isArray(v);
58
+ }
59
+ function toSet(v) {
60
+ return v instanceof Set ? v : new Set(v);
61
+ }
62
+ function validateFilter(filter, path, errors) {
63
+ if (!isPlainObject(filter)) {
64
+ errors.push(`${path}: filter must be an object`);
65
+ return;
66
+ }
67
+ for (const [key, value] of Object.entries(filter)) {
68
+ if (FILTER_ARRAY_KEYS.has(key)) {
69
+ if (!Array.isArray(value) || !value.every((x) => typeof x === "string" || typeof x === "number")) {
70
+ errors.push(`${path}.${key}: must be an array of string/number`);
71
+ }
72
+ } else if (FILTER_NUM_KEYS.has(key)) {
73
+ if (typeof value !== "number" || !Number.isFinite(value)) {
74
+ errors.push(`${path}.${key}: must be a finite number`);
75
+ }
76
+ } else {
77
+ errors.push(`${path}.${key}: unsupported filter key`);
78
+ }
79
+ }
80
+ }
81
+ function isJsonSafe(value, depth = 0) {
82
+ if (depth > 8)
83
+ return false;
84
+ if (value === null)
85
+ return true;
86
+ switch (typeof value) {
87
+ case "string":
88
+ case "boolean":
89
+ return true;
90
+ case "number":
91
+ return Number.isFinite(value);
92
+ case "object":
93
+ if (Array.isArray(value))
94
+ return value.every((v) => isJsonSafe(v, depth + 1));
95
+ if (isPlainObject(value))
96
+ return Object.values(value).every((v) => isJsonSafe(v, depth + 1));
97
+ return false;
98
+ default:
99
+ return false;
100
+ }
101
+ }
102
+ function validateViewSpec(input, opts) {
103
+ const errors = [];
104
+ const allowedAtoms = toSet(opts.allowedAtoms);
105
+ const allowedTools = opts.allowedTools ? toSet(opts.allowedTools) : void 0;
106
+ const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
107
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
108
+ if (!isPlainObject(input)) {
109
+ return { ok: false, errors: ["spec must be an object"] };
110
+ }
111
+ if (input["title"] !== void 0 && typeof input["title"] !== "string") {
112
+ errors.push("spec.title: must be a string");
113
+ }
114
+ if (!isPlainObject(input["root"])) {
115
+ return { ok: false, errors: ["spec.root: required object"] };
116
+ }
117
+ let nodeCount = 0;
118
+ const walk = (node, path, depth) => {
119
+ if (depth > maxDepth) {
120
+ errors.push(`${path}: exceeds max depth ${maxDepth}`);
121
+ return;
122
+ }
123
+ if (++nodeCount > maxNodes) {
124
+ errors.push(`spec: exceeds max node count ${maxNodes}`);
125
+ return;
126
+ }
127
+ if (!isPlainObject(node)) {
128
+ errors.push(`${path}: node must be an object`);
129
+ return;
130
+ }
131
+ if (typeof node["atom"] !== "string") {
132
+ errors.push(`${path}.atom: must be a string`);
133
+ } else if (!allowedAtoms.has(node["atom"])) {
134
+ errors.push(`${path}.atom: unknown atom "${node["atom"]}"`);
135
+ }
136
+ if (node["props"] !== void 0) {
137
+ if (!isPlainObject(node["props"]) || !isJsonSafe(node["props"])) {
138
+ errors.push(`${path}.props: must be a JSON-serializable object`);
139
+ }
140
+ }
141
+ if (node["bind"] !== void 0) {
142
+ const bind = node["bind"];
143
+ if (!isPlainObject(bind)) {
144
+ errors.push(`${path}.bind: must be an object`);
145
+ } else {
146
+ if (bind["query"] !== void 0)
147
+ validateFilter(bind["query"], `${path}.bind.query`, errors);
148
+ if (bind["eventId"] !== void 0 && typeof bind["eventId"] !== "string") {
149
+ errors.push(`${path}.bind.eventId: must be a string`);
150
+ }
151
+ if (bind["kindAuto"] !== void 0 && typeof bind["kindAuto"] !== "boolean") {
152
+ errors.push(`${path}.bind.kindAuto: must be a boolean`);
153
+ }
154
+ }
155
+ }
156
+ if (node["actions"] !== void 0) {
157
+ const actions = node["actions"];
158
+ if (!isPlainObject(actions)) {
159
+ errors.push(`${path}.actions: must be an object`);
160
+ } else {
161
+ for (const [name, ref] of Object.entries(actions)) {
162
+ const p = `${path}.actions.${name}`;
163
+ if (!isPlainObject(ref)) {
164
+ errors.push(`${p}: must be an object`);
165
+ continue;
166
+ }
167
+ if (typeof ref["tool"] !== "string") {
168
+ errors.push(`${p}.tool: must be a string`);
169
+ } else if (allowedTools && !allowedTools.has(ref["tool"])) {
170
+ errors.push(`${p}.tool: tool "${ref["tool"]}" not allowed`);
171
+ }
172
+ if (ref["args"] !== void 0 && (!isPlainObject(ref["args"]) || !isJsonSafe(ref["args"]))) {
173
+ errors.push(`${p}.args: must be a JSON-serializable object`);
174
+ }
175
+ if (ref["spendy"] !== void 0 && typeof ref["spendy"] !== "boolean") {
176
+ errors.push(`${p}.spendy: must be a boolean`);
177
+ }
178
+ if (ref["confirmLabel"] !== void 0 && typeof ref["confirmLabel"] !== "string") {
179
+ errors.push(`${p}.confirmLabel: must be a string`);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ if (node["children"] !== void 0) {
185
+ if (!Array.isArray(node["children"])) {
186
+ errors.push(`${path}.children: must be an array`);
187
+ } else {
188
+ node["children"].forEach((child, i) => walk(child, `${path}.children[${i}]`, depth + 1));
189
+ }
190
+ }
191
+ };
192
+ walk(input["root"], "spec.root", 0);
193
+ if (errors.length > 0)
194
+ return { ok: false, errors };
195
+ return { ok: true, spec: input };
196
+ }
197
+
198
+ // ../views/dist/catalog.js
199
+ var ATOM_CATALOG = [
200
+ // layout
201
+ {
202
+ id: "stack",
203
+ description: "Vertical (default) or horizontal stack of child nodes.",
204
+ propsSchema: { direction: "'row' | 'col'", gap: "number (tailwind gap step)" }
205
+ },
206
+ { id: "section", description: "Titled section wrapper.", propsSchema: { title: "string" } },
207
+ { id: "card", description: "Bordered card container." },
208
+ {
209
+ id: "tabs",
210
+ description: "Tabbed container; one child node per tab. Use for multi-section journeys.",
211
+ propsSchema: { labels: "string[] (tab names, in child order)" }
212
+ },
213
+ // social
214
+ { id: "profile-header", description: "NIP-01 kind:0 profile header (avatar, name, nip05, bio).", kinds: [0] },
215
+ { id: "note-card", description: 'NIP-01 kind:1 text note; optional "reply" action.', kinds: [1] },
216
+ { id: "reaction-bar", description: 'NIP-25 kind:7 reaction counts; optional "react" action.', kinds: [7] },
217
+ {
218
+ id: "follow-button",
219
+ description: "NIP-02 follow/unfollow (publishes kind:3).",
220
+ writes: [{ name: "toon_publish_unsigned" }],
221
+ propsSchema: { label: "string" }
222
+ },
223
+ {
224
+ id: "composer",
225
+ description: 'Text composer that publishes a note (kind:1) via its "post" action.',
226
+ writes: [{ name: "toon_publish_unsigned" }],
227
+ propsSchema: { placeholder: "string", label: "string" }
228
+ },
229
+ // media
230
+ {
231
+ id: "media-embed",
232
+ description: "Render image/video streamed from Arweave (NIP-68/71/94).",
233
+ kinds: [20, 21, 22, 1063]
234
+ },
235
+ {
236
+ id: "media-uploader",
237
+ description: "Upload media to Arweave then publish a media event. Spendy.",
238
+ writes: [{ name: "toon_upload_media", spendy: true }],
239
+ propsSchema: { label: "string" }
240
+ },
241
+ // forge
242
+ { id: "repo-card", description: "NIP-34 kind:30617 repository card.", kinds: [30617] },
243
+ {
244
+ id: "issue-card",
245
+ description: 'NIP-34 kind:1621 issue; optional "comment" action.',
246
+ kinds: [1621],
247
+ writes: [{ name: "toon_publish_unsigned" }]
248
+ },
249
+ { id: "pr-card", description: "NIP-34 kind:1617 patch/PR summary (status, commits, base).", kinds: [1617] },
250
+ {
251
+ id: "comment-thread",
252
+ description: 'NIP-34 kind:1622 comment list; optional "comment" action.',
253
+ kinds: [1622],
254
+ writes: [{ name: "toon_publish_unsigned" }]
255
+ },
256
+ // fallback
257
+ { id: "generic-event", description: "Fallback: decoded JSON + tags for any kind without a bespoke atom." }
258
+ ];
259
+ var CATALOG_ATOM_IDS = new Set(ATOM_CATALOG.map((a) => a.id));
260
+
261
+ // ../views/dist/examples.js
262
+ function feedView() {
263
+ return {
264
+ title: "Feed",
265
+ root: {
266
+ atom: "stack",
267
+ children: [
268
+ { atom: "composer", actions: { post: { tool: PUBLISH_TOOL, args: { kind: 1 } } } },
269
+ { atom: "note-card", bind: { query: buildFeedFilter(void 0, 50), kindAuto: true } }
270
+ ]
271
+ }
272
+ };
273
+ }
274
+ function profileView(pubkey) {
275
+ return {
276
+ title: "Profile",
277
+ root: {
278
+ atom: "stack",
279
+ children: [
280
+ { atom: "profile-header", bind: { query: buildProfileFilter([pubkey]) } },
281
+ {
282
+ atom: "follow-button",
283
+ props: { label: "Follow" },
284
+ actions: { follow: { tool: PUBLISH_TOOL, args: { kind: 3, tags: [["p", pubkey]] } } }
285
+ },
286
+ { atom: "note-card", bind: { query: buildFeedFilter([pubkey], 50), kindAuto: true } }
287
+ ]
288
+ }
289
+ };
290
+ }
291
+ function threadView(rootId) {
292
+ return {
293
+ title: "Thread",
294
+ root: {
295
+ atom: "stack",
296
+ children: [
297
+ { atom: "note-card", bind: { query: buildEventByIdFilter([rootId]), kindAuto: true } },
298
+ { atom: "note-card", bind: { query: buildCommentFilter([rootId]), kindAuto: true } },
299
+ {
300
+ atom: "composer",
301
+ props: { placeholder: "Reply\u2026", label: "Reply" },
302
+ actions: { post: { tool: PUBLISH_TOOL, args: { kind: 1, tags: [["e", rootId, "", "root"]] } } }
303
+ }
304
+ ]
305
+ }
306
+ };
307
+ }
308
+ function forgeView(ownerPubkey, repoId) {
309
+ return {
310
+ title: "Forge",
311
+ root: {
312
+ atom: "tabs",
313
+ props: { labels: ["Repos", "Issues"] },
314
+ children: [
315
+ {
316
+ atom: "section",
317
+ props: { title: "Repositories" },
318
+ children: [{ atom: "repo-card", bind: { query: buildRepoListFilter(), kindAuto: true } }]
319
+ },
320
+ {
321
+ atom: "section",
322
+ props: { title: "Issues" },
323
+ children: [
324
+ { atom: "issue-card", bind: { query: buildIssueListFilter(ownerPubkey, repoId), kindAuto: true } }
325
+ ]
326
+ }
327
+ ]
328
+ }
329
+ };
330
+ }
331
+ function mediaView() {
332
+ return {
333
+ title: "Media",
334
+ root: {
335
+ atom: "stack",
336
+ children: [
337
+ { atom: "media-uploader", props: { label: "Post a picture" }, actions: { upload: { tool: UPLOAD_TOOL, args: { kind: 20 }, spendy: true } } },
338
+ { atom: "media-embed", bind: { query: buildMediaFeedFilter(void 0, 30), kindAuto: true } }
339
+ ]
340
+ }
341
+ };
342
+ }
343
+ var EXAMPLE_VIEWSPECS = [
344
+ { name: "feed", description: "Social feed with a post composer.", spec: feedView() },
345
+ { name: "profile", description: "A profile header, follow button, and the author\u2019s notes.", spec: profileView("<pubkey-hex>") },
346
+ { name: "thread", description: "A note with its replies and a reply composer.", spec: threadView("<root-event-id>") },
347
+ { name: "forge", description: "Tabbed repos + issues (NIP-34).", spec: forgeView("<owner-pubkey>", "<repo-id>") },
348
+ { name: "media", description: "Media gallery with an uploader.", spec: mediaView() }
349
+ ];
350
+
351
+ // src/mcp-tools.ts
352
+ var TOOL_DEFINITIONS = [
353
+ {
354
+ name: "toon_status",
355
+ description: "Report TOON client daemon health: bootstrapping/ready state, transport, relay connection, buffered-event count, and per-chain settlement status.",
356
+ inputSchema: {
357
+ type: "object",
358
+ properties: {},
359
+ additionalProperties: false
360
+ }
361
+ },
362
+ {
363
+ name: "toon_identity",
364
+ description: "Return this client's public identity (Nostr pubkey + EVM/Solana/Mina addresses). Never returns private keys.",
365
+ inputSchema: {
366
+ type: "object",
367
+ properties: {},
368
+ additionalProperties: false
369
+ }
370
+ },
371
+ {
372
+ name: "toon_publish",
373
+ description: "Pay-to-write: publish a fully-signed Nostr event to the TOON network. Signs an off-chain payment-channel claim and forwards it over BTP. Returns the event id, channel id, and the advanced channel nonce.",
374
+ inputSchema: {
375
+ type: "object",
376
+ properties: {
377
+ event: {
378
+ type: "object",
379
+ description: "A fully-signed Nostr event (must include id, pubkey, sig, kind, created_at, tags, content)."
380
+ },
381
+ destination: {
382
+ type: "string",
383
+ description: "Optional ILP destination override (default: the apex/town)."
384
+ },
385
+ fee: {
386
+ type: "string",
387
+ description: "Optional fee override in base units (default: daemon config)."
388
+ },
389
+ btpUrl: {
390
+ type: "string",
391
+ description: "Which apex (BTP write target) to publish through (default: the config-seeded apex). Use toon_targets to list registered apexes. Writes always go through BTP \u2014 never a relay directly."
392
+ }
393
+ },
394
+ required: ["event"],
395
+ additionalProperties: false
396
+ }
397
+ },
398
+ {
399
+ name: "toon_publish_unsigned",
400
+ description: "Pay-to-write WITHOUT holding a key: supply only the event shell (kind, content, tags) and the daemon signs it with the held Nostr key, signs the payment-channel claim, and forwards over BTP. For replaceable kinds (0 profile, 3 follow list) the daemon merges the latest known tags first. This is the path MCP-app UI actions use so the iframe never signs.",
401
+ inputSchema: {
402
+ type: "object",
403
+ properties: {
404
+ kind: { type: "number", description: "Event kind (integer 0\u201365535)." },
405
+ content: { type: "string", description: "Event content (default empty)." },
406
+ tags: {
407
+ type: "array",
408
+ items: { type: "array", items: { type: "string" } },
409
+ description: "Event tags (array of string arrays)."
410
+ },
411
+ destination: { type: "string", description: "Optional ILP destination override." },
412
+ fee: { type: "string", description: "Optional fee override (base units)." },
413
+ btpUrl: { type: "string", description: "Which apex to publish through (default: config-seeded)." }
414
+ },
415
+ required: ["kind"],
416
+ additionalProperties: false
417
+ }
418
+ },
419
+ {
420
+ name: "toon_upload_media",
421
+ description: "Pay-to-write media (SPENDY, two-step): upload base64 bytes to Arweave via the kind:5094 blob-storage DVM, then sign+publish a media event (default kind:1063 NIP-94; 20=picture, 21/22=video, 1=note w/ NIP-92 imeta) referencing the resulting Arweave URL. Single-packet only.",
422
+ inputSchema: {
423
+ type: "object",
424
+ properties: {
425
+ dataBase64: { type: "string", description: "Base64-encoded media bytes." },
426
+ mime: { type: "string", description: "MIME type (default 'application/octet-stream')." },
427
+ kind: { type: "number", description: "Media event kind (default 1063)." },
428
+ caption: { type: "string", description: "Caption/content for the media event." },
429
+ tags: {
430
+ type: "array",
431
+ items: { type: "array", items: { type: "string" } },
432
+ description: "Extra tags merged into the published media event."
433
+ },
434
+ fee: { type: "string", description: "Optional fee override (base units)." },
435
+ btpUrl: { type: "string", description: "Which apex to publish through (default: config-seeded)." }
436
+ },
437
+ required: ["dataBase64"],
438
+ additionalProperties: false
439
+ }
440
+ },
441
+ {
442
+ name: "toon_atoms",
443
+ description: "List the atom vocabulary (ids, kinds rendered, props, write actions) plus example ViewSpecs, for composing a view to pass to toon_render. This is how you build the user a generative UI for their journey.",
444
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
445
+ },
446
+ {
447
+ name: "toon_render",
448
+ description: "Render an agent-authored ViewSpec (a tree of atoms with data binds and write actions) as the in-host UI. Call toon_atoms first to learn the vocabulary. The ViewSpec is validated; the host renders the app with it.",
449
+ inputSchema: {
450
+ type: "object",
451
+ properties: {
452
+ spec: {
453
+ type: "object",
454
+ description: "A ViewSpec: { title?, root: ViewNode }. See toon_atoms examples."
455
+ }
456
+ },
457
+ required: ["spec"],
458
+ additionalProperties: false
459
+ },
460
+ _meta: { ui: { resourceUri: APP_RESOURCE_URI } }
461
+ },
462
+ {
463
+ name: "toon_query",
464
+ description: "Free read for the UI: resolve a NIP-01 filter to matching events (subscribes, waits briefly, returns matches). Used to fill ViewSpec binds.",
465
+ inputSchema: {
466
+ type: "object",
467
+ properties: {
468
+ filter: { description: "A NIP-01 filter object." },
469
+ timeoutMs: { type: "number", description: "Bounded wait, ms (default 1200)." }
470
+ },
471
+ required: ["filter"],
472
+ additionalProperties: false
473
+ }
474
+ },
475
+ {
476
+ name: "toon_subscribe",
477
+ description: "Free read: register a persistent town-relay subscription with NIP-01 filter(s). Returns a subscription id to drain with toon_read.",
478
+ inputSchema: {
479
+ type: "object",
480
+ properties: {
481
+ filters: {
482
+ description: "A NIP-01 filter object or an array of OR-ed filters."
483
+ },
484
+ subId: { type: "string", description: "Optional caller-supplied id." },
485
+ relayUrl: {
486
+ type: "string",
487
+ description: "Restrict to one relay. Omit to FAN OUT across every registered relay (reads merge into one ordered stream)."
488
+ }
489
+ },
490
+ required: ["filters"],
491
+ additionalProperties: false
492
+ }
493
+ },
494
+ {
495
+ name: "toon_read",
496
+ description: "Free read: drain buffered events newer than a cursor. Pass back the returned cursor to fetch only events received since the last read.",
497
+ inputSchema: {
498
+ type: "object",
499
+ properties: {
500
+ subId: { type: "string", description: "Restrict to one subscription." },
501
+ cursor: {
502
+ type: "number",
503
+ description: "Cursor from a prior toon_read."
504
+ },
505
+ limit: {
506
+ type: "number",
507
+ description: "Max events to return (default 200)."
508
+ },
509
+ relayUrl: {
510
+ type: "string",
511
+ description: "Restrict the drain to events from a single relay."
512
+ }
513
+ },
514
+ additionalProperties: false
515
+ }
516
+ },
517
+ {
518
+ name: "toon_open_channel",
519
+ description: "Open (or return the existing) payment channel for a destination. Channels open lazily on first publish; use this to pre-open.",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ destination: {
524
+ type: "string",
525
+ description: "ILP destination (default: apex)."
526
+ }
527
+ },
528
+ additionalProperties: false
529
+ }
530
+ },
531
+ {
532
+ name: "toon_channels",
533
+ description: "List tracked payment channels with their nonce watermark and cumulative transferred amount.",
534
+ inputSchema: {
535
+ type: "object",
536
+ properties: {},
537
+ additionalProperties: false
538
+ }
539
+ },
540
+ {
541
+ name: "toon_swap",
542
+ description: "Pay a mill peer (asset A) to receive asset B plus a signed target-chain claim. Builds the NIP-59 gift-wrapped kind:20032 swap rumor and streams it; the source-asset claim is signed against the open apex channel (the mill must be routed via apexChildPeers). Returns the accumulated, decrypted target-chain claim(s) and settlement metadata.",
543
+ inputSchema: {
544
+ type: "object",
545
+ properties: {
546
+ destination: {
547
+ type: "string",
548
+ description: "Mill peer ILP destination (e.g. g.townhouse.mill)."
549
+ },
550
+ amount: {
551
+ type: "string",
552
+ description: "Total source-asset amount to swap, source micro-units."
553
+ },
554
+ millPubkey: {
555
+ type: "string",
556
+ description: "Mill's 64-char lowercase hex Nostr pubkey (gift-wrap recipient)."
557
+ },
558
+ pair: {
559
+ type: "object",
560
+ description: "The swap pair (from kind:10032 discovery or operator-supplied): { from:{assetCode,assetScale,chain}, to:{...}, rate, minAmount?, maxAmount? }."
561
+ },
562
+ chainRecipient: {
563
+ type: "string",
564
+ description: "Sender's payout address on pair.to.chain (EVM 0x-hex / Solana / Mina base58)."
565
+ },
566
+ packetCount: {
567
+ type: "number",
568
+ description: "Split the swap into N equal packets (default 1)."
569
+ }
570
+ },
571
+ required: [
572
+ "destination",
573
+ "amount",
574
+ "millPubkey",
575
+ "pair",
576
+ "chainRecipient"
577
+ ],
578
+ additionalProperties: false
579
+ }
580
+ },
581
+ {
582
+ name: "toon_http_fetch_paid",
583
+ description: "Fetch a paid HTTP resource: issues the request, and if the server returns 402 Payment Required, transparently pays over TOON and retries, returning the settled resource. Settlement happens inside the daemon against the open apex channel (the caller never holds chain keys). Returns { status, headers, body } (body decoded as text).",
584
+ inputSchema: {
585
+ type: "object",
586
+ properties: {
587
+ url: {
588
+ type: "string",
589
+ description: "Absolute URL of the resource to fetch."
590
+ },
591
+ method: {
592
+ type: "string",
593
+ description: "HTTP method (default GET)."
594
+ },
595
+ headers: {
596
+ type: "object",
597
+ description: "Request headers as a flat string\u2192string map."
598
+ },
599
+ body: {
600
+ type: "string",
601
+ description: "Request body (string, sent verbatim; e.g. with POST)."
602
+ },
603
+ timeout: {
604
+ type: "number",
605
+ description: "Per-request timeout, ms."
606
+ }
607
+ },
608
+ required: ["url"],
609
+ additionalProperties: false
610
+ }
611
+ },
612
+ {
613
+ name: "toon_targets",
614
+ description: "List every registered target: relays (read sources, with connection + buffered-event status) and apexes (BTP write targets, with ready/channel status). The TOON client is 1-to-many \u2014 many apexes to write through, many relays to read from.",
615
+ inputSchema: {
616
+ type: "object",
617
+ properties: {},
618
+ additionalProperties: false
619
+ }
620
+ },
621
+ {
622
+ name: "toon_add_relay",
623
+ description: "Add a relay READ target at runtime (persisted across restarts). It joins all fan-out reads immediately; `.anyone` hidden-service relays reuse the managed anon read proxy automatically.",
624
+ inputSchema: {
625
+ type: "object",
626
+ properties: {
627
+ relayUrl: {
628
+ type: "string",
629
+ description: "Relay WS URL (ws://host:7100 or a .anyone service)."
630
+ }
631
+ },
632
+ required: ["relayUrl"],
633
+ additionalProperties: false
634
+ }
635
+ },
636
+ {
637
+ name: "toon_remove_relay",
638
+ description: "Remove a relay read target (persisted). The config-seeded default relay cannot be removed.",
639
+ inputSchema: {
640
+ type: "object",
641
+ properties: {
642
+ relayUrl: { type: "string", description: "Relay WS URL to remove." }
643
+ },
644
+ required: ["relayUrl"],
645
+ additionalProperties: false
646
+ }
647
+ },
648
+ {
649
+ name: "toon_add_apex",
650
+ description: "Add an apex WRITE target at runtime (persisted across restarts). Settlement params are DISCOVERED by reading the apex\u2019s kind:10032 announcement off the given relay \u2014 you do not supply chain/settlement details. The relay is added as a read target first if unknown.",
651
+ inputSchema: {
652
+ type: "object",
653
+ properties: {
654
+ ilpAddress: {
655
+ type: "string",
656
+ description: "ILP address of the apex (e.g. g.townhouse.town)."
657
+ },
658
+ relayUrl: {
659
+ type: "string",
660
+ description: "Relay to discover the apex\u2019s kind:10032 on."
661
+ },
662
+ pubkey: {
663
+ type: "string",
664
+ description: "Optional apex Nostr pubkey (64-char hex) to narrow discovery."
665
+ },
666
+ chain: {
667
+ type: "string",
668
+ enum: ["evm", "solana", "mina"],
669
+ description: "Preferred settlement chain (default: apex\u2019s first)."
670
+ },
671
+ childPeers: {
672
+ type: "array",
673
+ items: { type: "string" },
674
+ description: 'Child peers via this apex\u2019s channel (e.g. ["dvm","mill"]).'
675
+ },
676
+ feePerEvent: {
677
+ type: "string",
678
+ description: "Per-write fee override for this apex (base units)."
679
+ }
680
+ },
681
+ required: ["ilpAddress", "relayUrl"],
682
+ additionalProperties: false
683
+ }
684
+ },
685
+ {
686
+ name: "toon_remove_apex",
687
+ description: "Remove an apex write target by its BTP URL (persisted). The config-seeded default apex cannot be removed.",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ btpUrl: {
692
+ type: "string",
693
+ description: "BTP URL of the apex to remove (from toon_targets)."
694
+ }
695
+ },
696
+ required: ["btpUrl"],
697
+ additionalProperties: false
698
+ }
699
+ }
700
+ ];
701
+ async function dispatchTool(client, name, args) {
702
+ try {
703
+ switch (name) {
704
+ case "toon_status":
705
+ return ok(await client.status());
706
+ case "toon_identity": {
707
+ const s = await client.status();
708
+ return ok({
709
+ identity: s.identity,
710
+ ready: s.ready,
711
+ bootstrapping: s.bootstrapping
712
+ });
713
+ }
714
+ case "toon_publish":
715
+ return ok(await client.publish(args));
716
+ case "toon_publish_unsigned":
717
+ return ok(
718
+ await client.publishUnsigned(args)
719
+ );
720
+ case "toon_upload_media":
721
+ return ok(
722
+ await client.uploadMedia(args)
723
+ );
724
+ case "toon_atoms":
725
+ return okStructured("Atom vocabulary + example ViewSpecs.", {
726
+ atoms: ATOM_CATALOG,
727
+ examples: EXAMPLE_VIEWSPECS
728
+ });
729
+ case "toon_render": {
730
+ const check = validateViewSpec(args["spec"], {
731
+ allowedAtoms: CATALOG_ATOM_IDS,
732
+ allowedTools: WRITE_TOOLS
733
+ });
734
+ if (!check.ok) {
735
+ return err(`Invalid ViewSpec:
736
+ - ${check.errors.join("\n- ")}`);
737
+ }
738
+ return okStructured(
739
+ `Rendering view${check.spec.title ? `: ${check.spec.title}` : ""}.`,
740
+ { viewSpec: check.spec }
741
+ );
742
+ }
743
+ case "toon_query": {
744
+ const res = await client.query({
745
+ filters: args["filter"],
746
+ ...typeof args["timeoutMs"] === "number" ? { timeoutMs: args["timeoutMs"] } : {}
747
+ });
748
+ return okStructured(`${res.events.length} event(s).`, {
749
+ events: res.events
750
+ });
751
+ }
752
+ case "toon_subscribe":
753
+ return ok(
754
+ await client.subscribe({
755
+ filters: args["filters"],
756
+ ...typeof args["subId"] === "string" ? { subId: args["subId"] } : {},
757
+ ...typeof args["relayUrl"] === "string" ? { relayUrl: args["relayUrl"] } : {}
758
+ })
759
+ );
760
+ case "toon_read":
761
+ return ok(
762
+ await client.events({
763
+ ...typeof args["subId"] === "string" ? { subId: args["subId"] } : {},
764
+ ...typeof args["cursor"] === "number" ? { cursor: args["cursor"] } : {},
765
+ ...typeof args["limit"] === "number" ? { limit: args["limit"] } : {},
766
+ ...typeof args["relayUrl"] === "string" ? { relayUrl: args["relayUrl"] } : {}
767
+ })
768
+ );
769
+ case "toon_open_channel":
770
+ return ok(
771
+ await client.openChannel(
772
+ typeof args["destination"] === "string" ? { destination: args["destination"] } : {}
773
+ )
774
+ );
775
+ case "toon_channels":
776
+ return ok(await client.channels());
777
+ case "toon_swap":
778
+ return ok(
779
+ await client.swap({
780
+ destination: String(args["destination"]),
781
+ amount: String(args["amount"]),
782
+ millPubkey: String(args["millPubkey"]),
783
+ pair: args["pair"],
784
+ chainRecipient: String(args["chainRecipient"]),
785
+ ...typeof args["packetCount"] === "number" ? { packetCount: args["packetCount"] } : {}
786
+ })
787
+ );
788
+ case "toon_http_fetch_paid": {
789
+ const req = {
790
+ url: String(args["url"]),
791
+ ...typeof args["method"] === "string" ? { method: args["method"] } : {},
792
+ ...args["headers"] && typeof args["headers"] === "object" ? { headers: args["headers"] } : {},
793
+ ...typeof args["body"] === "string" ? { body: args["body"] } : {},
794
+ ...typeof args["timeout"] === "number" ? { timeout: args["timeout"] } : {}
795
+ };
796
+ return ok(await client.httpFetchPaid(req));
797
+ }
798
+ case "toon_targets":
799
+ return ok(await client.targets());
800
+ case "toon_add_relay":
801
+ return ok(
802
+ await client.addRelay({ relayUrl: String(args["relayUrl"]) })
803
+ );
804
+ case "toon_remove_relay":
805
+ return ok(
806
+ await client.removeRelay({ relayUrl: String(args["relayUrl"]) })
807
+ );
808
+ case "toon_add_apex": {
809
+ const req = {
810
+ ilpAddress: String(args["ilpAddress"]),
811
+ relayUrl: String(args["relayUrl"]),
812
+ ...typeof args["pubkey"] === "string" ? { pubkey: args["pubkey"] } : {},
813
+ ...typeof args["chain"] === "string" ? { chain: args["chain"] } : {},
814
+ ...Array.isArray(args["childPeers"]) ? { childPeers: args["childPeers"].map(String) } : {},
815
+ ...typeof args["feePerEvent"] === "string" ? { feePerEvent: args["feePerEvent"] } : {}
816
+ };
817
+ return ok(await client.addApex(req));
818
+ }
819
+ case "toon_remove_apex":
820
+ return ok(await client.removeApex({ btpUrl: String(args["btpUrl"]) }));
821
+ default:
822
+ return err(`Unknown tool: ${name}`);
823
+ }
824
+ } catch (e) {
825
+ if (e instanceof ControlApiError && e.status === 504) {
826
+ return err(
827
+ `${e.detail ?? e.message} \u2014 retry once the relay is reachable and the apex is online.`
828
+ );
829
+ }
830
+ if (e instanceof ControlApiError && e.retryable) {
831
+ return err(
832
+ `TOON client is still bootstrapping (the anon proxy / BTP session can take 30\u201390s) \u2014 retry shortly. (${e.message})`
833
+ );
834
+ }
835
+ if (e instanceof DaemonUnreachableError) {
836
+ return err(
837
+ `TOON client daemon is not reachable at ${e.baseUrl}. It may have failed to start \u2014 check ~/.toon-client/daemon.log.`
838
+ );
839
+ }
840
+ if (e instanceof ControlApiError) {
841
+ return err(`${e.message}${e.detail ? `: ${e.detail}` : ""}`);
842
+ }
843
+ return err(e instanceof Error ? e.message : String(e));
844
+ }
845
+ }
846
+ function ok(data) {
847
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
848
+ }
849
+ function okStructured(text, structuredContent) {
850
+ return { content: [{ type: "text", text }], structuredContent };
851
+ }
852
+ function err(message) {
853
+ return { content: [{ type: "text", text: message }], isError: true };
854
+ }
855
+
856
+ export {
857
+ APP_RESOURCE_URI,
858
+ TOOL_DEFINITIONS,
859
+ dispatchTool
860
+ };
861
+ //# sourceMappingURL=chunk-KNQ2DKNN.js.map