@toon-protocol/client-mcp 0.11.0 → 0.12.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/daemon.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ClientRunner,
5
5
  registerRoutes,
6
6
  scaffoldFirstRun
7
- } from "./chunk-KVK6OZVD.js";
7
+ } from "./chunk-74KX5LXI.js";
8
8
  import {
9
9
  ControlClient,
10
10
  ToonClient,
@@ -17,7 +17,7 @@ import {
17
17
  resolveConfig,
18
18
  spawnDaemonDetached,
19
19
  waitForReady
20
- } from "./chunk-CMGJ3NFT.js";
20
+ } from "./chunk-W6T6ZK7U.js";
21
21
  import "./chunk-32QD72IL.js";
22
22
  import "./chunk-DLYE6U2Z.js";
23
23
  import "./chunk-LR7W2ISE.js";
package/dist/index.d.ts CHANGED
@@ -1006,6 +1006,15 @@ interface ToonClientLike {
1006
1006
  }>;
1007
1007
  getChannelCloseState(channelId: string): 'open' | 'closing' | 'settleable' | 'settled';
1008
1008
  getSettleableAt(channelId: string): bigint | undefined;
1009
+ /**
1010
+ * Re-read a resumed channel's on-chain deposit (persisted state omits it).
1011
+ * Optional so lightweight fakes need not implement it; the real ToonClient
1012
+ * does. Best-effort — callers await + catch.
1013
+ */
1014
+ rehydrateChannelDeposit?(channelId: string, opts: {
1015
+ chain: string;
1016
+ tokenNetworkAddress: string;
1017
+ }): Promise<bigint | undefined>;
1009
1018
  sendSwapPacket(params: {
1010
1019
  destination: string;
1011
1020
  amount: bigint;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  hasConfiguredIdentity,
9
9
  registerRoutes,
10
10
  scaffoldFirstRun
11
- } from "./chunk-KVK6OZVD.js";
11
+ } from "./chunk-74KX5LXI.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-JPQ4VCCF.js";
21
+ } from "./chunk-LN64MLWP.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-CMGJ3NFT.js";
39
+ } from "./chunk-W6T6ZK7U.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,7 +4,7 @@ import {
4
4
  APP_RESOURCE_URI,
5
5
  TOOL_DEFINITIONS,
6
6
  dispatchTool
7
- } from "./chunk-JPQ4VCCF.js";
7
+ } from "./chunk-LN64MLWP.js";
8
8
  import {
9
9
  ARWEAVE_GATEWAYS,
10
10
  ControlClient,
@@ -13,7 +13,7 @@ import {
13
13
  readConfigFile,
14
14
  spawnDaemonDetached,
15
15
  waitForReady
16
- } from "./chunk-CMGJ3NFT.js";
16
+ } from "./chunk-W6T6ZK7U.js";
17
17
  import "./chunk-32QD72IL.js";
18
18
  import "./chunk-DLYE6U2Z.js";
19
19
  import "./chunk-LR7W2ISE.js";
@@ -84,7 +84,7 @@ async function main() {
84
84
  // Travels to EVERY host in the `initialize` result (incl. claude.ai chat,
85
85
  // which never loads the Claude Code skill). Keep the render-first policy
86
86
  // here so it governs all surfaces, not just Claude Code.
87
- instructions: 'TOON is pay-to-write Nostr over Interledger; reads are free, writes are paid. RENDERING IS THE DEFAULT SURFACE: when the user asks to see/show/open/view/browse/render TOON events, profiles, feeds, threads, channels, or balances, call toon_atoms then toon_render (ui://toon/app) \u2014 do NOT route TOON data to a generic HTML/SVG/chart/widget tool or dump it as text. Always call toon_atoms before toon_render. On ANY intent to upload media or post/publish a picture/video/image (e.g. "I want to upload", "publish an image", "post a photo"), your FIRST action is toon_atoms then toon_render with the media view (media-uploader atom). The uploader has an in-app FILE PICKER, so do NOT ask whether a file is attached, where the image is, or for a URL, and do NOT recount upload-path history or known issues \u2014 just render the uploader and let the user pick a file. Writes (post/like/follow/upload/swap) spend a payment-channel claim; surface the fee and confirm before paying. Fall back to text only on explicit request or render failure.'
87
+ instructions: 'TOON is pay-to-write Nostr over Interledger; reads are free, writes are paid. RENDERING IS THE DEFAULT SURFACE: when the user asks to see/show/open/view/browse/render TOON events, profiles, feeds, threads, channels, or balances, call toon_atoms then toon_render (ui://toon/app) \u2014 do NOT route TOON data to a generic HTML/SVG/chart/widget tool or dump it as text. Always call toon_atoms before toon_render. The rendered card IS the response: for read-only views (feeds, profiles, threads, channels, balances, wallet) go STRAIGHT toon_atoms \u2192 toon_render \u2014 do NOT precede a render with daemon-health (toon_status), identity (toon_identity), or balance preflight, and do NOT narrate the tool calls or write a status report; at most a one-line caption after. On ANY intent to upload media or post/publish a picture/video/image (e.g. "I want to upload", "publish an image", "post a photo"), your FIRST action is toon_atoms then toon_render with the media view (media-uploader atom). The uploader has an in-app FILE PICKER, so do NOT ask whether a file is attached, where the image is, or for a URL, and do NOT recount upload-path history or known issues \u2014 just render the uploader and let the user pick a file. Writes (post/like/follow/upload/swap) spend a payment-channel claim; surface the fee and confirm before paying. Fall back to text only on explicit request or render failure.'
88
88
  }
89
89
  );
90
90
  const appHtml = loadAppHtml();
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 { 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. On ANY intent to upload media or post/publish a ' +\n 'picture/video/image (e.g. \"I want to upload\", \"publish an image\", ' +\n '\"post a photo\"), your FIRST action is toon_atoms then toon_render with ' +\n 'the media view (media-uploader atom). The uploader has an in-app FILE ' +\n 'PICKER, so do NOT ask whether a file is attached, where the image is, ' +\n 'or for a URL, and do NOT recount upload-path history or known issues — ' +\n 'just render the uploader and let the user pick a file. Writes ' +\n '(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 //\n // CRUCIAL: ar.io / arweave.net gateways serve a 302 from the apex\n // (`https://arweave.net/<txId>`) to a per-tx SANDBOX SUBDOMAIN\n // (`https://<base32>.arweave.net/<txId>`). CSP `img-src` is checked against the\n // REDIRECT TARGET, so an apex-only allowlist still blocks the image. Allow both\n // the apex (initial request) and a wildcard subdomain (where the bytes load).\n const arweaveCspDomains = ARWEAVE_GATEWAYS.flatMap((gateway) => {\n try {\n const host = new URL(gateway).host;\n return [gateway, `https://*.${host}`];\n } catch {\n return [gateway];\n }\n });\n const APP_CSP = {\n csp: {\n resourceDomains: arweaveCspDomains,\n connectDomains: arweaveCspDomains,\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":"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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. The rendered card IS the response: for read-only ' +\n 'views (feeds, profiles, threads, channels, balances, wallet) go ' +\n 'STRAIGHT toon_atoms → toon_render — do NOT precede a render with ' +\n 'daemon-health (toon_status), identity (toon_identity), or balance ' +\n 'preflight, and do NOT narrate the tool calls or write a status ' +\n 'report; at most a one-line caption after. On ANY intent to upload ' +\n 'media or post/publish a ' +\n 'picture/video/image (e.g. \"I want to upload\", \"publish an image\", ' +\n '\"post a photo\"), your FIRST action is toon_atoms then toon_render with ' +\n 'the media view (media-uploader atom). The uploader has an in-app FILE ' +\n 'PICKER, so do NOT ask whether a file is attached, where the image is, ' +\n 'or for a URL, and do NOT recount upload-path history or known issues — ' +\n 'just render the uploader and let the user pick a file. Writes ' +\n '(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 //\n // CRUCIAL: ar.io / arweave.net gateways serve a 302 from the apex\n // (`https://arweave.net/<txId>`) to a per-tx SANDBOX SUBDOMAIN\n // (`https://<base32>.arweave.net/<txId>`). CSP `img-src` is checked against the\n // REDIRECT TARGET, so an apex-only allowlist still blocks the image. Allow both\n // the apex (initial request) and a wildcard subdomain (where the bytes load).\n const arweaveCspDomains = ARWEAVE_GATEWAYS.flatMap((gateway) => {\n try {\n const host = new URL(gateway).host;\n return [gateway, `https://*.${host}`];\n } catch {\n return [gateway];\n }\n });\n const APP_CSP = {\n csp: {\n resourceDomains: arweaveCspDomains,\n connectDomains: arweaveCspDomains,\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":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/client-mcp",
3
- "version": "0.11.0",
3
+ "version": "0.12.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 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.10",
53
- "@toon-protocol/views": "0.11.0"
52
+ "@toon-protocol/client": "0.14.11",
53
+ "@toon-protocol/views": "0.12.0"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=20"