@toon-protocol/client-mcp 0.31.1 → 0.31.2

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/README.md CHANGED
@@ -56,16 +56,43 @@ The `toon-client` MCP server exposes **8 tools**:
56
56
  ## Install
57
57
 
58
58
  ```bash
59
- pnpm add -g @toon-protocol/client-mcp # or use npx/pnpm dlx
59
+ pnpm add -g @toon-protocol/client-mcp # installs both bins
60
60
  ```
61
61
 
62
- This installs two bins: `toon-clientd` and `toon-mcp`.
62
+ This installs two bins: `toon-clientd` and `toon-mcp`. To run a bin straight
63
+ from npm without a global install, name it explicitly (the package name is not a
64
+ bin, so plain `npx @toon-protocol/client-mcp` fails):
65
+
66
+ ```bash
67
+ npx -y -p @toon-protocol/client-mcp toon-mcp # MCP stdio server
68
+ npx -y -p @toon-protocol/client-mcp toon-clientd # daemon
69
+ ```
70
+
71
+ ## First run (zero-config onboarding)
72
+
73
+ On the **first** `toon-clientd run`/`start` (including the auto-spawn from
74
+ `toon-mcp`), the daemon onboards a brand-new user with no manual setup:
75
+
76
+ - **Identity** — if no mnemonic is configured, it generates a fresh BIP-39
77
+ mnemonic, encrypts it to `~/.toon-client/keystore.json`, records
78
+ `keystorePath` in `config.json`, and prints the seed phrase + derived
79
+ addresses **once** (back it up). The keystore is encrypted with
80
+ `TOON_CLIENT_KEYSTORE_PASSWORD` when set, otherwise a default password so the
81
+ identity reloads on every restart with **no env var required**.
82
+ - **Transport scaffolding** — it writes a starter `~/.toon-client/config.json`
83
+ carrying the `btpUrl`/`relayUrl`/`managedAnonProxy` knobs plus a `_help`
84
+ block documenting direct-vs-`.anyone` selection.
85
+
86
+ The one thing you must supply is the apex you pay: set **`btpUrl`** (and usually
87
+ `relayUrl`) in the scaffolded config, then publish. Everything below is for
88
+ overriding those auto-provisioned defaults.
63
89
 
64
90
  ## Configure the daemon
65
91
 
66
92
  The daemon reads `~/.toon-client/config.json` (override with `TOON_CLIENT_CONFIG`).
67
- The **mnemonic is never stored in plaintext by default** — supply it via env or an
68
- encrypted keystore (scrypt + AES-256-GCM, mode 0600).
93
+ The **mnemonic is never stored in plaintext by default** — it is auto-generated
94
+ into an encrypted keystore (scrypt + AES-256-GCM, mode 0600) on first run, or you
95
+ can supply your own via env or an imported keystore.
69
96
 
70
97
  ```jsonc
71
98
  // ~/.toon-client/config.json
@@ -96,9 +123,23 @@ Environment overrides: `TOON_CLIENT_MNEMONIC`, `TOON_CLIENT_KEYSTORE_PASSWORD`,
96
123
  `TOON_CLIENT_BTP_URL`, `TOON_CLIENT_RELAY_URL`, `TOON_CLIENT_SOCKS`,
97
124
  `TOON_CLIENT_HTTP_PORT`, `TOON_CLIENT_NETWORK`, `TOON_CLIENT_HOME`.
98
125
 
99
- The managed `.anyone` SOCKS5h proxy auto-starts when `btpUrl` is a `.anyone`
100
- host and no explicit `socksProxy` is set. The first bootstrap pays the anon
101
- warm-up cost (~30–90s) **once** — the detached daemon then stays up.
126
+ **Transport is inferred from the URLs no separate "direct vs hidden-service"
127
+ switch.** The daemon parses the host of each URL independently:
128
+
129
+ - `btpUrl`/`relayUrl` on a normal host (`ws://1.2.3.4:3000/btp`, `wss://…`) →
130
+ **direct**, dialed as-is.
131
+ - `btpUrl` on a `.anyone` host → the ToonClient auto-starts a managed `anon`
132
+ SOCKS5h proxy and routes paid writes (and reads) through it.
133
+ - `btpUrl` **direct** but `relayUrl` on a `.anyone` host → the daemon starts its
134
+ own managed `anon` proxy just for the free-read subscription; paid writes stay
135
+ direct. (Mixed transports — a direct apex with a hidden-service relay — work
136
+ out of the box.)
137
+
138
+ No explicit `socksProxy` is needed for any of these. Set `socksProxy` only to
139
+ point at your own external proxy, or `managedAnonProxy: false` to opt out of the
140
+ managed proxy entirely. The first bootstrap pays the anon warm-up cost (~30–90s)
141
+ **once** — the detached daemon then stays up. A read-proxy failure surfaces under
142
+ `relay.proxyError` in `toon_status` and never blocks direct paid writes.
102
143
 
103
144
  ### Create an encrypted keystore
104
145
 
@@ -2243,6 +2243,7 @@ var base58 = /* @__PURE__ */ genBase58("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefg
2243
2243
  var createBase58check = (sha2563) => /* @__PURE__ */ chain(checksum(4, (data) => sha2563(sha2563(data))), base58);
2244
2244
 
2245
2245
  // ../../node_modules/.pnpm/@scure+bip39@1.6.0/node_modules/@scure/bip39/esm/index.js
2246
+ var isJapanese = (wordlist2) => wordlist2[0] === "\u3042\u3044\u3053\u304F\u3057\u3093";
2246
2247
  function nfkd(str) {
2247
2248
  if (typeof str !== "string")
2248
2249
  throw new TypeError("invalid mnemonic type: " + typeof str);
@@ -2258,6 +2259,12 @@ function normalize(str) {
2258
2259
  function aentropy(ent) {
2259
2260
  abytes(ent, 16, 20, 24, 28, 32);
2260
2261
  }
2262
+ function generateMnemonic(wordlist2, strength = 128) {
2263
+ anumber(strength);
2264
+ if (strength % 32 !== 0 || strength > 256)
2265
+ throw new TypeError("Invalid entropy");
2266
+ return entropyToMnemonic(randomBytes(strength / 8), wordlist2);
2267
+ }
2261
2268
  var calcChecksum = (entropy) => {
2262
2269
  const bitsLeft = 8 - entropy.length / 4;
2263
2270
  return new Uint8Array([sha2562(entropy)[0] >> bitsLeft << bitsLeft]);
@@ -2277,6 +2284,11 @@ function mnemonicToEntropy(mnemonic, wordlist2) {
2277
2284
  aentropy(entropy);
2278
2285
  return entropy;
2279
2286
  }
2287
+ function entropyToMnemonic(entropy, wordlist2) {
2288
+ aentropy(entropy);
2289
+ const words = getCoder(wordlist2).encode(entropy);
2290
+ return words.join(isJapanese(wordlist2) ? "\u3000" : " ");
2291
+ }
2280
2292
  function validateMnemonic(mnemonic, wordlist2) {
2281
2293
  try {
2282
2294
  mnemonicToEntropy(mnemonic, wordlist2);
@@ -6589,6 +6601,9 @@ var ValidationError = class extends ToonClientError {
6589
6601
  this.name = "ValidationError";
6590
6602
  }
6591
6603
  };
6604
+ function generateMnemonic2() {
6605
+ return generateMnemonic(wordlist, 128);
6606
+ }
6592
6607
  function validateMnemonic2(mnemonic) {
6593
6608
  return validateMnemonic(mnemonic, wordlist);
6594
6609
  }
@@ -10409,6 +10424,8 @@ var SCRYPT_R = 8;
10409
10424
  var SCRYPT_P = 1;
10410
10425
  var SCRYPT_KEY_LEN = 32;
10411
10426
  var SCRYPT_MAXMEM = SCRYPT_N * SCRYPT_R * 256 + 32 * 1024 * 1024;
10427
+ var SALT_LEN = 32;
10428
+ var IV_LEN = 12;
10412
10429
  var AUTH_TAG_LEN = 16;
10413
10430
  function assertNode() {
10414
10431
  const versions = globalThis.process?.versions;
@@ -10418,6 +10435,42 @@ function assertNode() {
10418
10435
  );
10419
10436
  }
10420
10437
  }
10438
+ function encryptMnemonic2(mnemonic, password) {
10439
+ assertNode();
10440
+ if (typeof mnemonic !== "string" || mnemonic.length === 0) {
10441
+ throw new Error("encryptMnemonic: mnemonic must be a non-empty string");
10442
+ }
10443
+ if (typeof password !== "string" || password.length === 0) {
10444
+ throw new Error("encryptMnemonic: password must be a non-empty string");
10445
+ }
10446
+ const salt = randomBytes2(SALT_LEN);
10447
+ const iv = randomBytes2(IV_LEN);
10448
+ const key = scryptSync(password, salt, SCRYPT_KEY_LEN, {
10449
+ N: SCRYPT_N,
10450
+ r: SCRYPT_R,
10451
+ p: SCRYPT_P,
10452
+ maxmem: SCRYPT_MAXMEM
10453
+ });
10454
+ try {
10455
+ const cipher = createCipheriv("aes-256-gcm", key, iv, {
10456
+ authTagLength: AUTH_TAG_LEN
10457
+ });
10458
+ const ciphertext = Buffer.concat([
10459
+ cipher.update(mnemonic, "utf8"),
10460
+ cipher.final()
10461
+ ]);
10462
+ const tag = cipher.getAuthTag();
10463
+ return {
10464
+ salt: salt.toString("base64"),
10465
+ iv: iv.toString("base64"),
10466
+ ciphertext: ciphertext.toString("base64"),
10467
+ tag: tag.toString("base64"),
10468
+ version: 1
10469
+ };
10470
+ } finally {
10471
+ key.fill(0);
10472
+ }
10473
+ }
10421
10474
  function decryptMnemonic2(encrypted, password) {
10422
10475
  assertNode();
10423
10476
  if (typeof password !== "string" || password.length === 0) {
@@ -10456,6 +10509,13 @@ function decryptMnemonic2(encrypted, password) {
10456
10509
  key.fill(0);
10457
10510
  }
10458
10511
  }
10512
+ function generateKeystore(path, password) {
10513
+ assertNode();
10514
+ const mnemonic = generateMnemonic2();
10515
+ const keystore = encryptMnemonic2(mnemonic, password);
10516
+ writeKeystoreFile(path, keystore);
10517
+ return { mnemonic, keystore };
10518
+ }
10459
10519
  function loadKeystore(path, password) {
10460
10520
  assertNode();
10461
10521
  const raw = readFileSync2(path, "utf8");
@@ -10467,11 +10527,19 @@ function loadKeystore(path, password) {
10467
10527
  }
10468
10528
  return decryptMnemonic2(parsed, password);
10469
10529
  }
10530
+ function writeKeystoreFile(path, keystore) {
10531
+ assertNode();
10532
+ writeFileSync2(path, JSON.stringify(keystore, null, 2), {
10533
+ encoding: "utf8",
10534
+ mode: 384
10535
+ });
10536
+ }
10470
10537
 
10471
10538
  // src/daemon/config.ts
10472
10539
  import { readFileSync as readFileSync3 } from "fs";
10473
10540
  import { homedir } from "os";
10474
10541
  import { join as join2 } from "path";
10542
+ var DEFAULT_KEYSTORE_PASSWORD = "toon-client-default";
10475
10543
  function configDir() {
10476
10544
  return process.env["TOON_CLIENT_HOME"] ?? join2(homedir(), ".toon-client");
10477
10545
  }
@@ -10492,7 +10560,7 @@ function resolveMnemonic(file) {
10492
10560
  const envMnemonic = process.env["TOON_CLIENT_MNEMONIC"];
10493
10561
  if (envMnemonic) return envMnemonic.trim();
10494
10562
  if (file.keystorePath) {
10495
- const password = process.env["TOON_CLIENT_KEYSTORE_PASSWORD"];
10563
+ const password = process.env["TOON_CLIENT_KEYSTORE_PASSWORD"] ?? (file.keystoreAutoPassword ? DEFAULT_KEYSTORE_PASSWORD : void 0);
10496
10564
  if (!password) {
10497
10565
  throw new Error(
10498
10566
  "keystorePath is set but TOON_CLIENT_KEYSTORE_PASSWORD is not provided"
@@ -10524,20 +10592,29 @@ function resolveConfig(file) {
10524
10592
  const chain2 = process.env["TOON_CLIENT_CHAIN"] ?? file.chain ?? "evm";
10525
10593
  const apex = file.apexChains?.[chain2] ?? file.apex;
10526
10594
  const managedPort = Number(file.managedAnonSocksPort ?? 9050);
10527
- const wantManaged = file.managedAnonProxy ?? (explicitSocks ? false : isAnyoneHost2(btpUrl));
10595
+ const allowManaged = file.managedAnonProxy ?? true;
10596
+ const btpIsAnyone = isAnyoneHost2(btpUrl);
10597
+ const relayIsAnyone = isAnyoneHost2(relayUrl);
10528
10598
  let transport;
10529
10599
  let managedAnonProxy;
10530
10600
  let managedAnonSocksPort;
10531
10601
  let readsSocksProxy;
10602
+ let manageReadProxy = false;
10532
10603
  if (explicitSocks) {
10533
10604
  transport = { type: "socks5", socksProxy: explicitSocks };
10534
10605
  managedAnonProxy = false;
10535
10606
  readsSocksProxy = explicitSocks;
10536
- } else if (wantManaged) {
10607
+ } else if (allowManaged && btpIsAnyone) {
10537
10608
  transport = { type: "direct" };
10538
10609
  managedAnonProxy = true;
10539
10610
  managedAnonSocksPort = managedPort;
10540
10611
  readsSocksProxy = `socks5h://127.0.0.1:${managedPort}`;
10612
+ } else if (allowManaged && relayIsAnyone) {
10613
+ transport = { type: "direct" };
10614
+ managedAnonProxy = false;
10615
+ manageReadProxy = true;
10616
+ managedAnonSocksPort = managedPort;
10617
+ readsSocksProxy = `socks5h://127.0.0.1:${managedPort}`;
10541
10618
  } else {
10542
10619
  transport = { type: "direct" };
10543
10620
  managedAnonProxy = false;
@@ -10581,6 +10658,8 @@ function resolveConfig(file) {
10581
10658
  httpPort,
10582
10659
  relayUrl,
10583
10660
  ...readsSocksProxy !== void 0 ? { socksProxy: readsSocksProxy } : {},
10661
+ manageReadProxy,
10662
+ ...manageReadProxy ? { readProxySocksPort: managedPort } : {},
10584
10663
  destination,
10585
10664
  feePerEvent,
10586
10665
  apex,
@@ -10795,7 +10874,10 @@ export {
10795
10874
  encodeEventToToon,
10796
10875
  decodeEventFromToon,
10797
10876
  buildIlpPrepare,
10877
+ deriveFullIdentity,
10798
10878
  ToonClient,
10879
+ generateKeystore,
10880
+ DEFAULT_KEYSTORE_PASSWORD,
10799
10881
  configDir,
10800
10882
  defaultConfigPath,
10801
10883
  readConfigFile,
@@ -10831,4 +10913,4 @@ export {
10831
10913
  @scure/bip32/lib/esm/index.js:
10832
10914
  (*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) *)
10833
10915
  */
10834
- //# sourceMappingURL=chunk-MI2IWKHX.js.map
10916
+ //# sourceMappingURL=chunk-ARFPM2PT.js.map