@toon-protocol/client-mcp 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -212,6 +212,10 @@ interface ChannelInfo {
212
212
  depositTotal?: string;
213
213
  /** Spendable balance = depositTotal − cumulativeAmount (clamped ≥ 0), decimal. */
214
214
  availableBalance?: string;
215
+ /** Where the channel sits in the withdraw journey. */
216
+ closeState?: 'open' | 'closing' | 'settleable' | 'settled';
217
+ /** Unix SECONDS the channel becomes settleable, when closing. */
218
+ settleableAt?: string;
215
219
  }
216
220
  /** `GET /channels` — list tracked channels with nonce watermarks. */
217
221
  interface ChannelsResponse {
@@ -248,6 +252,26 @@ interface ChannelDepositResponse {
248
252
  /** New on-chain deposit total after the deposit, base units, decimal. */
249
253
  depositTotal: string;
250
254
  }
255
+ /** `POST /channels/close` — begin the settlement grace period (withdraw, step 1). */
256
+ interface CloseChannelRequest {
257
+ channelId: string;
258
+ }
259
+ interface CloseChannelResponse {
260
+ channelId: string;
261
+ txHash?: string;
262
+ /** Unix SECONDS when close was initiated. */
263
+ closedAt: string;
264
+ /** Unix SECONDS the channel becomes settleable (closedAt + settlementTimeout). */
265
+ settleableAt: string;
266
+ }
267
+ /** `POST /channels/settle` — release collateral after the grace period (step 2). */
268
+ interface SettleChannelRequest {
269
+ channelId: string;
270
+ }
271
+ interface SettleChannelResponse {
272
+ channelId: string;
273
+ txHash?: string;
274
+ }
251
275
  /**
252
276
  * `POST /swap` — pay asset A to a swap peer, receive asset B + a signed
253
277
  * target-chain claim. The daemon builds the NIP-59 gift-wrapped kind:20032 swap
@@ -509,6 +533,8 @@ declare class ControlClient {
509
533
  channels(): Promise<ChannelsResponse>;
510
534
  balances(): Promise<BalancesResponse>;
511
535
  depositToChannel(body: ChannelDepositRequest): Promise<ChannelDepositResponse>;
536
+ closeChannel(body: CloseChannelRequest): Promise<CloseChannelResponse>;
537
+ settleChannel(body: SettleChannelRequest): Promise<SettleChannelResponse>;
512
538
  swap(body: SwapRequest): Promise<SwapResponse>;
513
539
  httpFetchPaid(body: HttpFetchPaidRequest): Promise<HttpFetchPaidResponse>;
514
540
  targets(): Promise<TargetsResponse>;
@@ -720,14 +746,14 @@ interface DaemonConfigFile {
720
746
  destination?: string;
721
747
  /**
722
748
  * ILP route for PUBLISHES (relay writes → `POST /write`). When unset it is
723
- * DERIVED from the `…​.relay.store` anchor (`g.proxy.relay.store` →
749
+ * DERIVED from the `….relay.store` anchor (`g.proxy.relay.store` →
724
750
  * `g.proxy.relay`) — never the bare anchor, which the apex forwards to the
725
751
  * store and which 404s a `/write`. Env: `TOON_CLIENT_PUBLISH_DESTINATION`.
726
752
  */
727
753
  publishDestination?: string;
728
754
  /**
729
755
  * ILP route for UPLOADS (kind:5094 blob → `POST /store` → Arweave). When unset
730
- * it is DERIVED from the `…​.relay.store` anchor (`g.proxy.relay.store` →
756
+ * it is DERIVED from the `….relay.store` anchor (`g.proxy.relay.store` →
731
757
  * `g.proxy.store`). Env: `TOON_CLIENT_STORE_DESTINATION`.
732
758
  */
733
759
  storeDestination?: string;
@@ -933,6 +959,18 @@ interface ToonClientLike {
933
959
  txHash?: string;
934
960
  depositTotal: string;
935
961
  }>;
962
+ closeChannel(channelId: string): Promise<{
963
+ channelId: string;
964
+ txHash?: string;
965
+ closedAt: string;
966
+ settleableAt: string;
967
+ }>;
968
+ settleChannel(channelId: string): Promise<{
969
+ channelId: string;
970
+ txHash?: string;
971
+ }>;
972
+ getChannelCloseState(channelId: string): 'open' | 'closing' | 'settleable' | 'settled';
973
+ getSettleableAt(channelId: string): bigint | undefined;
936
974
  sendSwapPacket(params: {
937
975
  destination: string;
938
976
  amount: bigint;
@@ -1154,6 +1192,16 @@ declare class ClientRunner {
1154
1192
  * the client signs its own on-chain tx.
1155
1193
  */
1156
1194
  depositToChannel(req: ChannelDepositRequest): Promise<ChannelDepositResponse>;
1195
+ /** Close a channel to begin the settlement grace period (withdraw, step 1). */
1196
+ closeChannel(req: CloseChannelRequest): Promise<CloseChannelResponse>;
1197
+ /**
1198
+ * Settle a closed channel to release collateral (withdraw, step 2). The client
1199
+ * enforces the `now >= settleableAt` guard and throws a retryable error if
1200
+ * called early; `mapError` maps that to HTTP 425.
1201
+ */
1202
+ settleChannel(req: SettleChannelRequest): Promise<SettleChannelResponse>;
1203
+ /** Run `fn` against the apex client that tracks `channelId`, else throw. */
1204
+ private withTrackingApex;
1157
1205
  /** Swap source→target asset against a swap peer via the selected apex. */
1158
1206
  swap(req: SwapRequest): Promise<SwapResponse>;
1159
1207
  /**
@@ -1346,4 +1394,4 @@ interface JourneyResult {
1346
1394
  */
1347
1395
  declare function runJourney(plan: JourneyPlan, client: ControlClient): Promise<JourneyResult>;
1348
1396
 
1349
- export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type BalanceInfo, type BalancesResponse, type ChainStatus, type ChannelDepositRequest, type ChannelDepositResponse, 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 FundWalletRequest, type FundWalletResponse, type HttpFetchPaidRequest, type HttpFetchPaidResponse, type JourneyPlan, type JourneyResult, type JourneyState, type JourneyStep, type JourneyStepResult, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, type PublishUnsignedRequest, type QueryRequest, type QueryResponse, 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 UploadMediaRequest, type UploadMediaResponse, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, runJourney, scaffoldFirstRun, spawnDaemonDetached, waitForReady };
1397
+ export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type BalanceInfo, type BalancesResponse, type ChainStatus, type ChannelDepositRequest, type ChannelDepositResponse, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, type CloseChannelRequest, type CloseChannelResponse, ControlApiError, ControlClient, type ControlClientOptions, DEFAULT_KEYSTORE_PASSWORD, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type FundWalletRequest, type FundWalletResponse, type HttpFetchPaidRequest, type HttpFetchPaidResponse, type JourneyPlan, type JourneyResult, type JourneyState, type JourneyStep, type JourneyStepResult, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, type PublishUnsignedRequest, type QueryRequest, type QueryResponse, RelaySubscription, type RelaySubscriptionOptions, type RelayTargetStatus, type RemoveApexRequest, type RemoveRelayRequest, type ResolvedDaemonConfig, type SettleChannelRequest, type SettleChannelResponse, 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 UploadMediaRequest, type UploadMediaResponse, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, runJourney, scaffoldFirstRun, spawnDaemonDetached, waitForReady };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  hasConfiguredIdentity,
9
9
  registerRoutes,
10
10
  scaffoldFirstRun
11
- } from "./chunk-ZIJNTR5Y.js";
11
+ } from "./chunk-NDIIPZB4.js";
12
12
  import {
13
13
  PUBLISH_TOOL,
14
14
  TOOL_DEFINITIONS,
@@ -18,7 +18,7 @@ import {
18
18
  buildFollowListFilter,
19
19
  buildProfileFilter,
20
20
  dispatchTool
21
- } from "./chunk-KWQKOCE4.js";
21
+ } from "./chunk-RW2PMNYZ.js";
22
22
  import {
23
23
  ControlApiError,
24
24
  ControlClient,
@@ -36,7 +36,7 @@ import {
36
36
  resolveMnemonic,
37
37
  spawnDaemonDetached,
38
38
  waitForReady
39
- } from "./chunk-5Z74D23J.js";
39
+ } from "./chunk-XYR3ACYX.js";
40
40
  import "./chunk-32QD72IL.js";
41
41
  import "./chunk-DLYE6U2Z.js";
42
42
  import "./chunk-LR7W2ISE.js";
package/dist/mcp.js CHANGED
@@ -4,15 +4,16 @@ import {
4
4
  APP_RESOURCE_URI,
5
5
  TOOL_DEFINITIONS,
6
6
  dispatchTool
7
- } from "./chunk-KWQKOCE4.js";
7
+ } from "./chunk-RW2PMNYZ.js";
8
8
  import {
9
+ ARWEAVE_GATEWAYS,
9
10
  ControlClient,
10
11
  defaultConfigPath,
11
12
  isDaemonRunning,
12
13
  readConfigFile,
13
14
  spawnDaemonDetached,
14
15
  waitForReady
15
- } from "./chunk-5Z74D23J.js";
16
+ } from "./chunk-XYR3ACYX.js";
16
17
  import "./chunk-32QD72IL.js";
17
18
  import "./chunk-DLYE6U2Z.js";
18
19
  import "./chunk-LR7W2ISE.js";
@@ -90,9 +91,15 @@ async function main() {
90
91
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
91
92
  tools: TOOL_DEFINITIONS
92
93
  }));
94
+ const APP_CSP = {
95
+ csp: {
96
+ resourceDomains: [...ARWEAVE_GATEWAYS],
97
+ connectDomains: [...ARWEAVE_GATEWAYS]
98
+ }
99
+ };
93
100
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
94
101
  resources: [
95
- { uri: APP_RESOURCE_URI, name: "TOON", mimeType: APP_MIME }
102
+ { uri: APP_RESOURCE_URI, name: "TOON", mimeType: APP_MIME, _meta: { ui: APP_CSP } }
96
103
  ]
97
104
  }));
98
105
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
@@ -100,7 +107,9 @@ async function main() {
100
107
  throw new Error(`Unknown resource: ${request.params.uri}`);
101
108
  }
102
109
  return {
103
- contents: [{ uri: APP_RESOURCE_URI, mimeType: APP_MIME, text: appHtml }]
110
+ contents: [
111
+ { uri: APP_RESOURCE_URI, mimeType: APP_MIME, text: appHtml, _meta: { ui: APP_CSP } }
112
+ ]
104
113
  };
105
114
  });
106
115
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
package/dist/mcp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `toon-mcp` — a thin MCP stdio server exposing the TOON client to a Claude\n * agent (Desktop or Code). It holds NO chain keys and NO long-lived\n * connections: every tool maps to an HTTP call against the always-on\n * `toon-clientd` daemon, which it auto-spawns (detached) if it is not running.\n *\n * Works on both surfaces:\n * • Claude Desktop — `claude_desktop_config.json` mcpServers entry.\n * • Claude Code — `claude mcp add toon -- toon-mcp` (or `.mcp.json`).\n */\n\nimport { createRequire } from 'node:module';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolResult,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { APP_RESOURCE_URI } from '@toon-protocol/views';\nimport { ControlClient } from './control-client.js';\nimport { dispatchTool, TOOL_DEFINITIONS } from './mcp-tools.js';\n\n/** MIME marking the bundle as an MCP-app UI resource (ext-apps profile). */\nconst APP_MIME = 'text/html;profile=mcp-app';\n\n/** Load the prebuilt single-file MCP-app bundle served as `ui://toon/app`. */\nfunction loadAppHtml(): string {\n // 1. Prefer the copy shipped next to the built server (tsup onSuccess copies\n // it into dist/app). This is what a published client-mcp serves — no\n // dependency on the unpublished @toon-protocol/views package at runtime.\n try {\n return readFileSync(new URL('./app/index.html', import.meta.url), 'utf8');\n } catch {\n /* running from source / not yet copied — fall through to the dev resolve */\n }\n // 2. Dev fallback: resolve the bundle from the @toon-protocol/views workspace.\n try {\n const req = createRequire(import.meta.url);\n const entry = req.resolve('@toon-protocol/views'); // …/views/dist/index.js\n return readFileSync(join(dirname(entry), 'app', 'index.html'), 'utf8');\n } catch {\n return '<!doctype html><html><body><div id=\"root\">toon app bundle missing — run `pnpm --filter @toon-protocol/views build`</div></body></html>';\n }\n}\nimport { defaultConfigPath, readConfigFile } from './daemon/config.js';\nimport {\n isDaemonRunning,\n spawnDaemonDetached,\n waitForReady,\n} from './daemon/lifecycle.js';\n\n/** stdout carries the MCP protocol — all logging must go to stderr. */\nfunction log(msg: string): void {\n console.error(`[toon-mcp] ${msg}`);\n}\n\n/** Resolve the daemon control-plane URL without needing the mnemonic. */\nfunction controlPlaneUrl(): string {\n const file = readConfigFile(\n process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath()\n );\n const port = Number(\n process.env['TOON_CLIENT_HTTP_PORT'] ?? file.httpPort ?? 8787\n );\n return `http://127.0.0.1:${port}`;\n}\n\n/**\n * Make sure the daemon is up: if the lock shows it running, return; otherwise\n * spawn it detached and wait until the control plane is reachable. Best-effort\n * — failures surface as readable tool errors rather than crashing the server.\n */\nasync function ensureDaemon(url: string): Promise<void> {\n if (isDaemonRunning()) return;\n const client = new ControlClient({ baseUrl: url });\n if (await client.ping()) return;\n log('daemon not running — spawning detached');\n try {\n const pid = spawnDaemonDetached();\n log(`spawned toon-clientd (pid ${pid}); waiting for control plane`);\n await waitForReady(url, 20_000);\n } catch (err) {\n log(\n `failed to spawn daemon: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\nasync function main(): Promise<void> {\n const url = controlPlaneUrl();\n const control = new ControlClient({ baseUrl: url });\n\n // Kick off daemon startup; don't block server init on it (BTP bootstrap can\n // take a moment). Tools report \"bootstrapping — retry\" until it is ready.\n void ensureDaemon(url);\n\n const server = new Server(\n { name: 'toon-client', version: '0.1.0' },\n {\n capabilities: { tools: {}, resources: {} },\n // Travels to EVERY host in the `initialize` result (incl. claude.ai chat,\n // which never loads the Claude Code skill). Keep the render-first policy\n // here so it governs all surfaces, not just Claude Code.\n instructions:\n 'TOON is pay-to-write Nostr over Interledger; reads are free, writes ' +\n 'are paid. RENDERING IS THE DEFAULT SURFACE: when the user asks to ' +\n 'see/show/open/view/browse/render TOON events, profiles, feeds, ' +\n 'threads, channels, or balances, call toon_atoms then toon_render ' +\n '(ui://toon/app) — do NOT route TOON data to a generic ' +\n 'HTML/SVG/chart/widget tool or dump it as text. Always call toon_atoms ' +\n 'before toon_render. Writes (post/like/follow/upload/swap) spend a ' +\n 'payment-channel claim; surface the fee and confirm before paying. ' +\n 'Fall back to text only on explicit request or render failure.',\n }\n );\n\n const appHtml = loadAppHtml();\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOL_DEFINITIONS,\n }));\n\n // The MCP-app UI resource the host renders for toon_render results.\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: [\n { uri: APP_RESOURCE_URI, name: 'TOON', mimeType: APP_MIME },\n ],\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n if (request.params.uri !== APP_RESOURCE_URI) {\n throw new Error(`Unknown resource: ${request.params.uri}`);\n }\n return {\n contents: [{ uri: APP_RESOURCE_URI, mimeType: APP_MIME, text: appHtml }],\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n const args = (request.params.arguments ?? {}) as Record<string, unknown>;\n // If the daemon went away, try once to bring it back before dispatching.\n if (!(await control.ping())) await ensureDaemon(url);\n // Our ToolResult is a structural subset of CallToolResult (content + isError);\n // the SDK's handler union also carries a task-augmented variant we never use.\n return (await dispatchTool(control, name, args)) as CallToolResult;\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n log(`ready; proxying to ${url}`);\n}\n\nmain().catch((err) => {\n log(err instanceof Error ? (err.stack ?? err.message) : String(err));\n process.exitCode = 1;\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/mcp.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `toon-mcp` — a thin MCP stdio server exposing the TOON client to a Claude\n * agent (Desktop or Code). It holds NO chain keys and NO long-lived\n * connections: every tool maps to an HTTP call against the always-on\n * `toon-clientd` daemon, which it auto-spawns (detached) if it is not running.\n *\n * Works on both surfaces:\n * • Claude Desktop — `claude_desktop_config.json` mcpServers entry.\n * • Claude Code — `claude mcp add toon -- toon-mcp` (or `.mcp.json`).\n */\n\nimport { createRequire } from 'node:module';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolResult,\n} from '@modelcontextprotocol/sdk/types.js';\nimport { APP_RESOURCE_URI } from '@toon-protocol/views';\nimport { ARWEAVE_GATEWAYS } from '@toon-protocol/arweave';\nimport { ControlClient } from './control-client.js';\nimport { dispatchTool, TOOL_DEFINITIONS } from './mcp-tools.js';\n\n/** MIME marking the bundle as an MCP-app UI resource (ext-apps profile). */\nconst APP_MIME = 'text/html;profile=mcp-app';\n\n/** Load the prebuilt single-file MCP-app bundle served as `ui://toon/app`. */\nfunction loadAppHtml(): string {\n // 1. Prefer the copy shipped next to the built server (tsup onSuccess copies\n // it into dist/app). This is what a published client-mcp serves — no\n // dependency on the unpublished @toon-protocol/views package at runtime.\n try {\n return readFileSync(new URL('./app/index.html', import.meta.url), 'utf8');\n } catch {\n /* running from source / not yet copied — fall through to the dev resolve */\n }\n // 2. Dev fallback: resolve the bundle from the @toon-protocol/views workspace.\n try {\n const req = createRequire(import.meta.url);\n const entry = req.resolve('@toon-protocol/views'); // …/views/dist/index.js\n return readFileSync(join(dirname(entry), 'app', 'index.html'), 'utf8');\n } catch {\n return '<!doctype html><html><body><div id=\"root\">toon app bundle missing — run `pnpm --filter @toon-protocol/views build`</div></body></html>';\n }\n}\nimport { defaultConfigPath, readConfigFile } from './daemon/config.js';\nimport {\n isDaemonRunning,\n spawnDaemonDetached,\n waitForReady,\n} from './daemon/lifecycle.js';\n\n/** stdout carries the MCP protocol — all logging must go to stderr. */\nfunction log(msg: string): void {\n console.error(`[toon-mcp] ${msg}`);\n}\n\n/** Resolve the daemon control-plane URL without needing the mnemonic. */\nfunction controlPlaneUrl(): string {\n const file = readConfigFile(\n process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath()\n );\n const port = Number(\n process.env['TOON_CLIENT_HTTP_PORT'] ?? file.httpPort ?? 8787\n );\n return `http://127.0.0.1:${port}`;\n}\n\n/**\n * Make sure the daemon is up: if the lock shows it running, return; otherwise\n * spawn it detached and wait until the control plane is reachable. Best-effort\n * — failures surface as readable tool errors rather than crashing the server.\n */\nasync function ensureDaemon(url: string): Promise<void> {\n if (isDaemonRunning()) return;\n const client = new ControlClient({ baseUrl: url });\n if (await client.ping()) return;\n log('daemon not running — spawning detached');\n try {\n const pid = spawnDaemonDetached();\n log(`spawned toon-clientd (pid ${pid}); waiting for control plane`);\n await waitForReady(url, 20_000);\n } catch (err) {\n log(\n `failed to spawn daemon: ${err instanceof Error ? err.message : String(err)}`\n );\n }\n}\n\nasync function main(): Promise<void> {\n const url = controlPlaneUrl();\n const control = new ControlClient({ baseUrl: url });\n\n // Kick off daemon startup; don't block server init on it (BTP bootstrap can\n // take a moment). Tools report \"bootstrapping — retry\" until it is ready.\n void ensureDaemon(url);\n\n const server = new Server(\n { name: 'toon-client', version: '0.1.0' },\n {\n capabilities: { tools: {}, resources: {} },\n // Travels to EVERY host in the `initialize` result (incl. claude.ai chat,\n // which never loads the Claude Code skill). Keep the render-first policy\n // here so it governs all surfaces, not just Claude Code.\n instructions:\n 'TOON is pay-to-write Nostr over Interledger; reads are free, writes ' +\n 'are paid. RENDERING IS THE DEFAULT SURFACE: when the user asks to ' +\n 'see/show/open/view/browse/render TOON events, profiles, feeds, ' +\n 'threads, channels, or balances, call toon_atoms then toon_render ' +\n '(ui://toon/app) — do NOT route TOON data to a generic ' +\n 'HTML/SVG/chart/widget tool or dump it as text. Always call toon_atoms ' +\n 'before toon_render. Writes (post/like/follow/upload/swap) spend a ' +\n 'payment-channel claim; surface the fee and confirm before paying. ' +\n 'Fall back to text only on explicit request or render failure.',\n }\n );\n\n const appHtml = loadAppHtml();\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOL_DEFINITIONS,\n }));\n\n // The MCP-app UI resource the host renders for toon_render results.\n //\n // CSP: the rendered feed/uploader shows media stored on Arweave. Without an\n // explicit `resourceDomains`, the host iframe's default `img-src`/`media-src`\n // blocks those gateways and images never load (toon-client#127). Advertise the\n // Arweave gateways as both resource (img/media/static) and connect origins.\n // Per the ext-apps spec the host reads `_meta.ui.csp` from the `resources/read`\n // content item, with the `resources/list` entry as fallback — so set it on both.\n const APP_CSP = {\n csp: {\n resourceDomains: [...ARWEAVE_GATEWAYS],\n connectDomains: [...ARWEAVE_GATEWAYS],\n },\n };\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: [\n { uri: APP_RESOURCE_URI, name: 'TOON', mimeType: APP_MIME, _meta: { ui: APP_CSP } },\n ],\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n if (request.params.uri !== APP_RESOURCE_URI) {\n throw new Error(`Unknown resource: ${request.params.uri}`);\n }\n return {\n contents: [\n { uri: APP_RESOURCE_URI, mimeType: APP_MIME, text: appHtml, _meta: { ui: APP_CSP } },\n ],\n };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const name = request.params.name;\n const args = (request.params.arguments ?? {}) as Record<string, unknown>;\n // If the daemon went away, try once to bring it back before dispatching.\n if (!(await control.ping())) await ensureDaemon(url);\n // Our ToolResult is a structural subset of CallToolResult (content + isError);\n // the SDK's handler union also carries a task-augmented variant we never use.\n return (await dispatchTool(control, name, args)) as CallToolResult;\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n log(`ready; proxying to ${url}`);\n}\n\nmain().catch((err) => {\n log(err instanceof Error ? (err.stack ?? err.message) : String(err));\n process.exitCode = 1;\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAYA,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,SAAS,YAAY;AAC9B,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAOP,IAAM,WAAW;AAGjB,SAAS,cAAsB;AAI7B,MAAI;AACF,WAAO,aAAa,IAAI,IAAI,oBAAoB,YAAY,GAAG,GAAG,MAAM;AAAA,EAC1E,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,MAAM,cAAc,YAAY,GAAG;AACzC,UAAM,QAAQ,IAAI,QAAQ,sBAAsB;AAChD,WAAO,aAAa,KAAK,QAAQ,KAAK,GAAG,OAAO,YAAY,GAAG,MAAM;AAAA,EACvE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,IAAI,KAAmB;AAC9B,UAAQ,MAAM,cAAc,GAAG,EAAE;AACnC;AAGA,SAAS,kBAA0B;AACjC,QAAM,OAAO;AAAA,IACX,QAAQ,IAAI,oBAAoB,KAAK,kBAAkB;AAAA,EACzD;AACA,QAAM,OAAO;AAAA,IACX,QAAQ,IAAI,uBAAuB,KAAK,KAAK,YAAY;AAAA,EAC3D;AACA,SAAO,oBAAoB,IAAI;AACjC;AAOA,eAAe,aAAa,KAA4B;AACtD,MAAI,gBAAgB,EAAG;AACvB,QAAM,SAAS,IAAI,cAAc,EAAE,SAAS,IAAI,CAAC;AACjD,MAAI,MAAM,OAAO,KAAK,EAAG;AACzB,MAAI,6CAAwC;AAC5C,MAAI;AACF,UAAM,MAAM,oBAAoB;AAChC,QAAI,6BAA6B,GAAG,8BAA8B;AAClE,UAAM,aAAa,KAAK,GAAM;AAAA,EAChC,SAAS,KAAK;AACZ;AAAA,MACE,2BAA2B,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,IAC7E;AAAA,EACF;AACF;AAEA,eAAe,OAAsB;AACnC,QAAM,MAAM,gBAAgB;AAC5B,QAAM,UAAU,IAAI,cAAc,EAAE,SAAS,IAAI,CAAC;AAIlD,OAAK,aAAa,GAAG;AAErB,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,eAAe,SAAS,QAAQ;AAAA,IACxC;AAAA,MACE,cAAc,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA,MAIzC,cACE;AAAA,IASJ;AAAA,EACF;AAEA,QAAM,UAAU,YAAY;AAE5B,SAAO,kBAAkB,wBAAwB,aAAa;AAAA,IAC5D,OAAO;AAAA,EACT,EAAE;AAUF,QAAM,UAAU;AAAA,IACd,KAAK;AAAA,MACH,iBAAiB,CAAC,GAAG,gBAAgB;AAAA,MACrC,gBAAgB,CAAC,GAAG,gBAAgB;AAAA,IACtC;AAAA,EACF;AAEA,SAAO,kBAAkB,4BAA4B,aAAa;AAAA,IAChE,WAAW;AAAA,MACT,EAAE,KAAK,kBAAkB,MAAM,QAAQ,UAAU,UAAU,OAAO,EAAE,IAAI,QAAQ,EAAE;AAAA,IACpF;AAAA,EACF,EAAE;AAEF,SAAO,kBAAkB,2BAA2B,OAAO,YAAY;AACrE,QAAI,QAAQ,OAAO,QAAQ,kBAAkB;AAC3C,YAAM,IAAI,MAAM,qBAAqB,QAAQ,OAAO,GAAG,EAAE;AAAA,IAC3D;AACA,WAAO;AAAA,MACL,UAAU;AAAA,QACR,EAAE,KAAK,kBAAkB,UAAU,UAAU,MAAM,SAAS,OAAO,EAAE,IAAI,QAAQ,EAAE;AAAA,MACrF;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,kBAAkB,uBAAuB,OAAO,YAAY;AACjE,UAAM,OAAO,QAAQ,OAAO;AAC5B,UAAM,OAAQ,QAAQ,OAAO,aAAa,CAAC;AAE3C,QAAI,CAAE,MAAM,QAAQ,KAAK,EAAI,OAAM,aAAa,GAAG;AAGnD,WAAQ,MAAM,aAAa,SAAS,MAAM,IAAI;AAAA,EAChD,CAAC;AAED,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAC9B,MAAI,sBAAsB,GAAG,EAAE;AACjC;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,MAAI,eAAe,QAAS,IAAI,SAAS,IAAI,UAAW,OAAO,GAAG,CAAC;AACnE,UAAQ,WAAW;AACrB,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/client-mcp",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
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 swaps.",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Green",
@@ -49,8 +49,8 @@
49
49
  "typescript": "^5.3.0",
50
50
  "vitest": "^1.0.0",
51
51
  "@toon-protocol/arweave": "0.1.1",
52
- "@toon-protocol/client": "0.14.7",
53
- "@toon-protocol/views": "0.10.1"
52
+ "@toon-protocol/views": "0.10.3",
53
+ "@toon-protocol/client": "0.14.8"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=20"