@blockula/mcp 1.0.0 → 1.0.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
@@ -3,11 +3,11 @@
3
3
  🧛 MCP server that lets any coding agent use **Blockula**, a post-quantum blockchain. Agents can hold a wallet, send FANG, claim `.boo` names, stake, vote on proposals, look things up in plain English, and start or summon chains.
4
4
 
5
5
  ```bash
6
- claude mcp add blockula -- npx -y @blockula/mcp # the public testnet
7
- claude mcp add blockula -- npx -y @blockula/mcp --network local # a `blockula dev` chain on this machine
6
+ claude mcp add blockula -- npx -y @blockula/mcp # a `blockula dev` chain on this machine
7
+ claude mcp add blockula -e BLOCKULA_RPC=https://<your node> -- npx -y @blockula/mcp # a chain you launched (`blockula summon`)
8
8
  ```
9
9
 
10
- With no configuration it connects to the public testnet at `https://blockula-testnet.fly.dev`. It never falls back to a node on this machine unless you configure one. Call `status` to see which network and chain you're on.
10
+ With no configuration it connects to a `blockula dev` node on this machine (`http://localhost:7070`). The public testnet (`blockula-testnet-2`) was retired in November 2026 and has no public node, so nothing public is built in: for any other chain, set `BLOCKULA_RPC` to a node of it. Call `status` to see which network and chain you're on.
11
11
 
12
12
  To run an exact release, name it: `npx -y @blockula/mcp@0.5.0` (the first release on npm, for the protocol v1 testnet `blockula-testnet-2`). Each release pins its whole dependency tree (exact versions plus a published `npm-shrinkwrap.json`), so what runs next to the agent's key is what CI tested.
13
13
 
@@ -15,8 +15,8 @@ To run an exact release, name it: `npx -y @blockula/mcp@0.5.0` (the first releas
15
15
 
16
16
  The network is chosen by the user, once, in the MCP's configuration, and nothing changes it while the server runs: it never switches to whatever answers on `localhost:7070`, and no tool can point it somewhere else.
17
17
 
18
- - `--network` (or `BLOCKULA_NETWORK`): `testnet` (default), `local` (`http://localhost:7070`) or `custom`.
19
- - `BLOCKULA_RPC` picks the node: required for `custom`. Plain `http://` only on this machine or a private network, and no query or fragment. A node written two ways (`Blockula-Testnet.fly.dev`, `:443`, a trailing `/.`) is one node: the public RPC is never its own witness.
18
+ - `--network` (or `BLOCKULA_NETWORK`): `local` (default, `http://localhost:7070`), `testnet` or `custom`.
19
+ - `BLOCKULA_RPC` picks the node: required for `testnet` (its public node was retired: without one the MCP refuses to start and says so) and `custom`. Plain `http://` only on this machine or a private network, and no query or fragment. A node written two ways (`Node.Example`, `:443`, a trailing `/.`) is one node: it's never its own witness.
20
20
  - The testnet pins its chain id (`blockula-testnet-2`, with the `tboo` address prefix), and every network pins a genesis hash where one is known (`KNOWN_GENESIS` in `src/network.ts`: a release pins a launched chain's). `BLOCKULA_CHAIN_ID` and `BLOCKULA_GENESIS_HASH` pin them for any node. Whatever isn't pinned is pinned from the first status the node reports and remembered in `~/.blockula/known-chains.json` (except for local dev chains, which `--reset` replaces).
21
21
  - A node reporting another chain id, genesis hash or address prefix is refused, before anything is signed, at startup or mid-session. So is one whose chain id isn't 1 to 64 letters, digits, `-`, `_` or `.` (the chain id names files and ledger entries here) or whose genesis hash isn't 64 hex characters. If a network was relaunched on purpose under the same chain id, the user removes its entry from `known-chains.json` or sets `BLOCKULA_GENESIS_HASH`. (A relaunch takes a new chain id, as the testnet's did: an entry remembered for the old `blockula-testnet` isn't held against `blockula-testnet-2`.) A node that says it's on `blockula-1` is refused too, unless the user set `BLOCKULA_CHAIN_ID=blockula-1`: it's never pinned on first use, so a configuration that didn't name that chain can't land there.
22
22
 
@@ -57,10 +57,10 @@ Never allow `mcp__blockula__*`: that lets every send through without asking.
57
57
 
58
58
  **Spending a user's money safely:**
59
59
  1. `create_wallet`, then `use_account` with the user's account address (`boo1…`: the web wallet's Devices & agents → Give an agent a budget shows it, with the name as a label). A name works on any chain but `blockula-1`; there an unproven name answers `needs_confirmation` with the address it resolved to, for the user to compare with the one their wallet shows. Until the user has authorized the key it answers with a `grant_code`: this key's signed consent to joining that one account (no secret in it). Hand it to the user. A key id alone can't be added (AddKey needs the key's consent, so nobody can pad the key's account list).
60
- 2. The user pastes it in the Blockula web wallet at https://testnet.blockula.app (Devices & agents → Give an agent a budget) and picks a limit and an expiry. The chain enforces both.
60
+ 2. The user pastes it in the Blockula web wallet (Devices & agents → Give an agent a budget) and picks a limit and an expiry. The chain enforces both.
61
61
  3. `use_account` with the same address again, then `send`.
62
62
 
63
- `use_account` returns a `check_code` (the wallet shows the same code for the key) and, on known networks, the wallet's address. An address with no account gets no grant code: it can't add a key until it has FANG. The account the agent acts for is kept per chain: a testnet account doesn't follow the agent to another network.
63
+ `use_account` returns a `check_code` (the wallet shows the same code for the key) and, on a chain with a public wallet built in, the wallet's address (none is, since the public testnet was retired). An address with no account gets no grant code: it can't add a key until it has FANG. The account the agent acts for is kept per chain: a testnet account doesn't follow the agent to another network.
64
64
 
65
65
  **What can be spent.** `wallet_info` (and `use_account`) say what the account can move now (`transferable`), what's `locked` and until when, the session key's allowance this window and when it resets, the caps, and `spendable_now`: the smallest of them, with the one that binds. What the chain would refuse by the node's own answers is refused before anything is signed, sent or counted: a session key signing anything but a transfer (or a Familiar's claim), more than its allowance left, a payment past what the account can move, or a key that isn't on the account. A lying node can only cause such a refusal, never a spend.
66
66
 
@@ -82,14 +82,13 @@ Session keys can only send transfers: staking, voting and name renewals need an
82
82
 
83
83
  ## Your own node
84
84
 
85
- Run a node of the testnet on this machine, and point the MCP at it:
85
+ Point the MCP at a node of a chain you launched (`blockula summon`, docs/LAUNCH.md) or joined (`blockula join`):
86
86
 
87
87
  ```bash
88
- blockula join --genesis https://blockula-testnet.fly.dev --home ~/.blockula/testnet-node # serves http://127.0.0.1:7171
89
- claude mcp add blockula -e BLOCKULA_RPC=http://127.0.0.1:7171 -- npx -y @blockula/mcp --network testnet
88
+ claude mcp add blockula -e BLOCKULA_RPC=http://127.0.0.1:7171 -- npx -y @blockula/mcp
90
89
  ```
91
90
 
92
- `status` then calls it "your node on the public testnet". Unless `BLOCKULA_WITNESSES` says otherwise, the network's public RPC becomes its witness: names are proven against it, and a height your node reports can't run ahead of it (`status` says "proven against 1 node"). `BLOCKULA_WITNESSES=none` opts out, and then names are your node's word. On a chain without a built-in public RPC (a dev chain, or one you launched), list other nodes in `BLOCKULA_WITNESSES` yourself.
91
+ A node on this machine is the `local` network unless you name another; `--network testnet` holds it to `blockula-testnet-2`'s pins (`status` then calls it "your node on blockula-testnet-2"). No chain has a public RPC built in, so names are your node's word until `BLOCKULA_WITNESSES` lists other nodes of the same chain: then they're proven against them, and a height your node reports can't run ahead of theirs.
93
92
 
94
93
  ## install_node
95
94
 
@@ -106,13 +105,13 @@ The checks add up: each refuses a release it fails. The result lists what was ve
106
105
 
107
106
  | Env | Default | |
108
107
  |---|---|---|
109
- | `BLOCKULA_NETWORK` | `testnet` | `testnet`, `local` or `custom` (same as `--network`) |
110
- | `BLOCKULA_RPC` | the network's public RPC | node URL (no query or fragment) |
108
+ | `BLOCKULA_NETWORK` | `local` | `local`, `testnet` or `custom` (same as `--network`) |
109
+ | `BLOCKULA_RPC` | `http://localhost:7070` for `local`; required for `testnet` and `custom` | node URL (no query or fragment) |
111
110
  | `BLOCKULA_CHAIN_ID` / `BLOCKULA_GENESIS_HASH` | the network's pins | the chain the node must be on. A node that says it's on `blockula-1` is used only with `BLOCKULA_CHAIN_ID=blockula-1` |
112
111
  | `BLOCKULA_HOME` | `~/.blockula` | what the MCP keeps, each file mode 600 but the log: `agent-wallet.json` (the agent key and the account it acts for on each chain), `agent-spend.json` (the spending ledger), `agent-txs.json` (the txs it signed, for `tx_status`), `known-chains.json`, `nonces/` (per account: the last nonce signed, and a `.lock` file while a tx is built and sent, which the session holding it keeps touching; one left by a dead session, or untouched for two minutes, is taken over, or delete it), `grant-<chain id>-<address>.txt` (the grant code `use_account` hands out, one per account that hasn't authorized the key yet), and `dev-node.log` (`start_dev_node`'s log) |
113
112
  | `BLOCKULA_MAX_SEND` | `100` | max FANG one call moves: sent, staked, escrowed, paid for a name, bonded (and per vote) |
114
113
  | `BLOCKULA_MAX_DAILY` | `BLOCKULA_MAX_SEND` + 1 | max FANG leaving the account per UTC day on this chain across all calls, fees and pending, dropped or refused txs included (persisted in `BLOCKULA_HOME`) |
115
- | `BLOCKULA_WITNESSES` | the public RPC, for your own node of the public testnet; else none | other nodes of the same chain (comma-separated https URLs): names that get signed are proven against them. `none` opts out |
114
+ | `BLOCKULA_WITNESSES` | none | other nodes of the same chain (comma-separated https URLs): names that get signed are proven against them. `none` opts out |
116
115
  | `BLOCKULA_TIMEOUT_MS` | `15000` | how long one request to the node may take (at most 2147483647) |
117
116
  | `BLOCKULA_TX_WAIT_MS` | `15000` | how long a tx tool waits for finality before answering `pending` |
118
117
  | `BLOCKULA_BIN` | of `~/.blockula/bin/blockula` (`blockula release install`, checked against the pinned key) and `~/.blockula/mcp-bin/blockula` (`install_node`), the one whose `--version` is this MCP's, the first if both or neither are; else `blockula` | node binary used by `start_dev_node` / `summon_chain` (they say which ran, and when it isn't this MCP's version) |
@@ -35,7 +35,10 @@ export declare function actingFor(f: WalletFile, chain: string, prefix: string):
35
35
  export declare function setActingFor(f: WalletFile, chain: string, account: string | undefined): void;
36
36
  /** What wallet_info says about an ignored 0.5.1 account. */
37
37
  export declare function ignoredNote(account: string, prefix: string): string;
38
- /** The web wallet's "Give an agent a budget", by chain id (where the user pastes a grant code). */
38
+ /**
39
+ * The web wallet's "Give an agent a budget", by chain id (where the user pastes a grant code), for
40
+ * chains with a public wallet: none since the public testnet was retired.
41
+ */
39
42
  export declare const WALLET_URLS: Record<string, string>;
40
43
  /** The short code the web wallet shows for a key (web/lib/format.ts `keyCode`), for the user to compare. */
41
44
  export declare const checkCode: (keyId: string) => string;
package/dist/accounts.js CHANGED
@@ -103,13 +103,14 @@ export function setActingFor(f, chain, account) {
103
103
  }
104
104
  /** What wallet_info says about an ignored 0.5.1 account. */
105
105
  export function ignoredNote(account, prefix) {
106
- const other = account.startsWith("tboo1") ? "the public testnet's" : account.startsWith("boo1") ? "a boo1… chain's" : "another chain's";
106
+ const other = account.startsWith("tboo1") ? "a testnet's" : account.startsWith("boo1") ? "a boo1… chain's" : "another chain's";
107
107
  return `This agent used to act for ${account}, ${other} account, which isn't an address on this chain (its addresses start with ${prefix}1). Here it acts for its own key until use_account is called with this chain's account.`;
108
108
  }
109
- /** The web wallet's "Give an agent a budget", by chain id (where the user pastes a grant code). */
110
- export const WALLET_URLS = {
111
- "blockula-testnet-2": "https://testnet.blockula.app/devices#agent",
112
- };
109
+ /**
110
+ * The web wallet's "Give an agent a budget", by chain id (where the user pastes a grant code), for
111
+ * chains with a public wallet: none since the public testnet was retired.
112
+ */
113
+ export const WALLET_URLS = {};
113
114
  /** The short code the web wallet shows for a key (web/lib/format.ts `keyCode`), for the user to compare. */
114
115
  export const checkCode = (keyId) => {
115
116
  const k = keyId.toUpperCase();
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // 🧛 Blockula MCP server. Add to Claude Code with:
3
- // claude mcp add blockula -- npx -y @blockula/mcp (the public testnet)
4
- // claude mcp add blockula -- npx -y @blockula/mcp --network local (a `blockula dev` chain)
3
+ // claude mcp add blockula -- npx -y @blockula/mcp (a `blockula dev` chain)
4
+ // claude mcp add blockula -e BLOCKULA_RPC=https://<node> -- npx -y @blockula/mcp (a chain you launched)
5
5
  //
6
6
  // Env:
7
- // BLOCKULA_NETWORK testnet (default), local or custom (same as --network)
8
- // BLOCKULA_RPC node URL (default: the network's public RPC; required for custom)
7
+ // BLOCKULA_NETWORK local (default), testnet or custom (same as --network)
8
+ // BLOCKULA_RPC node URL (default: http://localhost:7070 for local; required for testnet,
9
+ // whose public node was retired, and for custom)
9
10
  // BLOCKULA_CHAIN_ID / BLOCKULA_GENESIS_HASH pin the chain the node must be on (a node that says
10
11
  // it's on blockula-1 is refused unless BLOCKULA_CHAIN_ID=blockula-1)
11
12
  // BLOCKULA_WITNESSES other nodes of the same chain (comma-separated URLs): .boo names that get
@@ -15,9 +16,8 @@
15
16
  // them a send signs the name itself (the chain resolves it when it runs), and
16
17
  // where an address is needed (stake, unstake, use_account) a name is the
17
18
  // node's word: on blockula-1 (BLOCKULA_CHAIN_ID=blockula-1) the address must be
18
- // confirmed first. Unset, your own node of the public
19
- // testnet (any node but its public RPC) gets the public RPC as a witness;
20
- // `none` opts out.
19
+ // confirmed first. Unset, no node is a witness (no chain has public RPCs
20
+ // built in); `none` says so explicitly.
21
21
  // BLOCKULA_HOME where the agent wallet lives (default ~/.blockula)
22
22
  // BLOCKULA_MAX_SEND max FANG one call moves (sent, staked, escrowed, paid for a name, bonded; default 100)
23
23
  // BLOCKULA_MAX_DAILY max FANG leaving the account per UTC day through this MCP on each chain, fees
@@ -546,7 +546,7 @@ tool("status", "Blockula network status: which network and node you're connected
546
546
  witnesses: activeWitnesses,
547
547
  names: activeWitnesses.length ? `proven against ${activeWitnesses.length} node${activeWitnesses.length === 1 ? "" : "s"} (${activeWitnesses.join(", ")})` : `UNPROVEN: one node's word (${net.rpc}). BLOCKULA_WITNESSES lists other nodes to prove them against.`,
548
548
  pinned: chainData({ chain_id: s.chain_id, genesis_hash: s.genesis_hash, address_prefix: s.address_prefix }),
549
- how_to_change: "The network is fixed while this MCP runs. The user changes it in the MCP's configuration (--network testnet|local|custom, or BLOCKULA_RPC).",
549
+ how_to_change: "The network is fixed while this MCP runs. The user changes it in the MCP's configuration (--network local|testnet|custom, or BLOCKULA_RPC).",
550
550
  explorer: net.rpc,
551
551
  mcp_version: VERSION,
552
552
  node_reports: chainData(Object.fromEntries(pick.filter((k) => k in s).map((k) => [k, s[k]]))),
@@ -884,9 +884,16 @@ tool("install_node", "Download the prebuilt `blockula` node + CLI for this machi
884
884
  rmSync(tmp, { recursive: true, force: true });
885
885
  }
886
886
  });
887
- tool("summon_chain", "Create a brand-new Blockula chain (fresh genesis + validator key) that you can run as your own network.", { chain_id: z.string().regex(/^[a-z0-9-]{3,40}$/), faucet: z.boolean().default(true) }, async ({ chain_id, faucet }) => {
887
+ tool("summon_chain", "Create a brand-new Blockula chain (fresh genesis + validator key) that you can run as your own network. `name`, `description` and `token` (its ticker) are what wallets and explorers show; the genesis hash binds them. A person launching one by hand runs `blockula launch`.", {
888
+ chain_id: z.string().regex(/^[a-z0-9-]{3,40}$/),
889
+ faucet: z.boolean().default(true),
890
+ name: z.string().min(1).max(48).optional(),
891
+ description: z.string().max(280).optional(),
892
+ token: z.string().regex(/^[A-Z][A-Z0-9]{1,7}$/).optional(),
893
+ }, async ({ chain_id, faucet, name, description, token }) => {
888
894
  const bin = nodeBinary();
889
- const out = execFileSync(bin.path, ["summon", chain_id, ...(faucet ? [] : ["--no-faucet"])], { encoding: "utf8" });
895
+ const brand = [...(name ? ["--name", name] : []), ...(name && description ? ["--description", description] : []), ...(token ? ["--token", token] : [])];
896
+ const out = execFileSync(bin.path, ["summon", chain_id, ...(faucet ? [] : ["--no-faucet"]), ...brand], { encoding: "utf8" });
890
897
  return `${out.trim()}\n${ranWith(bin)}`;
891
898
  });
892
899
  /**
@@ -1063,7 +1070,7 @@ tool("wallet_info", "This agent's address and the account it acts for, its balan
1063
1070
  };
1064
1071
  });
1065
1072
  /** What `faucet` answers when the node says its chain has none. */
1066
- const NO_FAUCET = "This chain has no faucet, so nothing was sent. Its FANG comes from accounts that already hold some: ask whoever runs the chain. The public testnet and dev chains (start_dev_node) have faucets.";
1073
+ const NO_FAUCET = "This chain has no faucet, so nothing was sent. Its FANG comes from accounts that already hold some: ask whoever runs the chain. Dev chains (start_dev_node) have faucets.";
1067
1074
  tool("faucet", "Get 100 test FANG from the chain's faucet (to this agent's wallet, or any name/address). On a chain without one, it answers status no_faucet.", { to: z.string().optional().describe("a name or address; default: the account this agent acts for") }, async ({ to }) => {
1068
1075
  const client = await chain();
1069
1076
  let target = to;
package/dist/network.d.ts CHANGED
@@ -29,7 +29,8 @@ export interface Network {
29
29
  witnessesSet: boolean;
30
30
  }
31
31
  export declare const LOCAL_RPC = "http://localhost:7070";
32
- export declare const TESTNET_RPC = "https://blockula-testnet.fly.dev";
32
+ /** Why `--network testnet` needs BLOCKULA_RPC: the public testnet has no public node any more. */
33
+ export declare const TESTNET_RETIRED = "the public testnet (blockula-testnet-2) was retired in November 2026 and has no public node: run your own chain (`blockula dev`, then --network local; or `blockula summon`, then BLOCKULA_RPC=<its node>), or set BLOCKULA_RPC to a blockula-testnet-2 node you run";
33
34
  /**
34
35
  * Nothing reads this: the MCP has no mainnet network. It stays because
35
36
  * `scripts/pin-network.sh --mainnet --rpc` writes it and `scripts/test-pin-network.sh` checks
@@ -73,14 +74,14 @@ export declare const KNOWN_NETWORKS: Record<Exclude<NetworkKey, "custom">, Omit<
73
74
  export declare function defaultWitnesses(chainId: string | undefined, rpc: string): string[];
74
75
  /**
75
76
  * What `status` calls the network: once the node's chain is known, a node on this machine that
76
- * is on a public network is "your node on <it>", never a "local dev node".
77
+ * is on a known network is "your node on <it>", never a "local dev node".
77
78
  */
78
79
  export declare function networkLabel(net: Network, chainId?: string): string;
79
80
  /**
80
81
  * A node URL in one spelling: the scheme and host lowercased, a default port and dot segments
81
82
  * dropped (the URL parser's own normalization), no trailing dot on the host and no trailing
82
- * slash. So a node compares equal however it's written: `https://Blockula-Testnet.fly.dev:443/.`
83
- * is the public RPC, and is never its own witness (audit MCK-1). What isn't an http(s) URL comes
83
+ * slash. So a node compares equal however it's written: `https://Node.Example:443/.` is
84
+ * `https://node.example`, and is never its own witness (audit MCK-1). What isn't an http(s) URL comes
84
85
  * back as it is.
85
86
  */
86
87
  export declare function canonicalUrl(u: string): string;
@@ -112,8 +113,8 @@ export declare function unusableChain(status: {
112
113
  export declare function networkArg(argv: string[]): string | undefined;
113
114
  /**
114
115
  * The network from the configuration: `--network` (or BLOCKULA_NETWORK) names one, BLOCKULA_RPC
115
- * gives the node, BLOCKULA_CHAIN_ID / BLOCKULA_GENESIS_HASH add pins. With none of them, the
116
- * public testnet. Contradictions are errors, never guesses.
116
+ * gives the node, BLOCKULA_CHAIN_ID / BLOCKULA_GENESIS_HASH add pins. With none of them, a
117
+ * `blockula dev` node on this machine (local). Contradictions are errors, never guesses.
117
118
  */
118
119
  export declare function resolveNetwork(env: Record<string, string | undefined>, argv?: string[]): Network;
119
120
  /** Why a node's status doesn't match a pin, or null if it does. Known chain ids must come with their known genesis. */
package/dist/network.js CHANGED
@@ -17,7 +17,8 @@ import { dirname } from "node:path";
17
17
  */
18
18
  export const BLOCKULA_1 = "blockula-1";
19
19
  export const LOCAL_RPC = "http://localhost:7070";
20
- export const TESTNET_RPC = "https://blockula-testnet.fly.dev";
20
+ /** Why `--network testnet` needs BLOCKULA_RPC: the public testnet has no public node any more. */
21
+ export const TESTNET_RETIRED = "the public testnet (blockula-testnet-2) was retired in November 2026 and has no public node: run your own chain (`blockula dev`, then --network local; or `blockula summon`, then BLOCKULA_RPC=<its node>), or set BLOCKULA_RPC to a blockula-testnet-2 node you run";
21
22
  /**
22
23
  * Nothing reads this: the MCP has no mainnet network. It stays because
23
24
  * `scripts/pin-network.sh --mainnet --rpc` writes it and `scripts/test-pin-network.sh` checks
@@ -51,14 +52,14 @@ export function clockFrom(genesis, chainId) {
51
52
  }
52
53
  /** The networks the MCP knows by name. */
53
54
  export const KNOWN_NETWORKS = {
54
- // The protocol v1 public testnet (deploy/launch.sh): its own chain id (never reused) and the
55
- // testnet prefix (DEC-7). Its genesis hash exists only once it launches: until a release adds it
56
- // to KNOWN_GENESIS, the first status the node reports pins it (trust on first use, remembered).
57
- testnet: { key: "testnet", label: "public testnet", rpc: TESTNET_RPC, pin: { chainId: "blockula-testnet-2", prefix: "tboo" }, remember: true },
55
+ // The protocol v1 testnet (deploy/launch.sh): its own chain id (never reused), the testnet
56
+ // prefix (DEC-7) and, in KNOWN_GENESIS, its genesis. The public testnet was retired in November
57
+ // 2026: no public RPC is built in, so it takes BLOCKULA_RPC (TESTNET_RETIRED).
58
+ testnet: { key: "testnet", label: "testnet", pin: { chainId: "blockula-testnet-2", prefix: "tboo" }, remember: true },
58
59
  local: { key: "local", label: "local dev node", rpc: LOCAL_RPC, pin: {}, remember: false },
59
60
  };
60
- /** The public RPCs of the chains whose public RPCs are built in, by chain id. */
61
- const PUBLIC_RPCS = { "blockula-testnet-2": [TESTNET_RPC] };
61
+ /** The public RPCs of the chains whose public RPCs are built in, by chain id: none since the public testnet was retired. */
62
+ const PUBLIC_RPCS = {};
62
63
  /**
63
64
  * The witnesses a node of `chainId` gets when BLOCKULA_WITNESSES is unset: the chain's public RPCs,
64
65
  * unless the node is one of them. So your own node's answers about names and heights are checked
@@ -70,11 +71,11 @@ export function defaultWitnesses(chainId, rpc) {
70
71
  // The same node however `rpc` is spelled: a node is never its own witness (audit MCK-1).
71
72
  return rpcs.includes(canonicalUrl(rpc)) ? [] : rpcs;
72
73
  }
73
- /** The public network a chain id is, when it's one (for labels). */
74
- const PUBLIC_LABEL = { "blockula-testnet-2": "the public testnet" };
74
+ /** The known network a chain id is, when it's one (for labels). */
75
+ const PUBLIC_LABEL = { "blockula-testnet-2": "blockula-testnet-2" };
75
76
  /**
76
77
  * What `status` calls the network: once the node's chain is known, a node on this machine that
77
- * is on a public network is "your node on <it>", never a "local dev node".
78
+ * is on a known network is "your node on <it>", never a "local dev node".
78
79
  */
79
80
  export function networkLabel(net, chainId) {
80
81
  const pub = chainId ? PUBLIC_LABEL[chainId] : undefined;
@@ -93,13 +94,14 @@ const isPrivateUrl = (u) => {
93
94
  if (isLocalUrl(u))
94
95
  return true;
95
96
  const h = new URL(u).hostname;
96
- return /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(h) || /\.(internal|local|lan)$/.test(h);
97
+ // 100.64.0.0/10 and .ts.net are Tailscale's: WireGuard-encrypted, tailnet members only.
98
+ return /^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.)/.test(h) || /\.(internal|local|lan|ts\.net)$/.test(h);
97
99
  };
98
100
  /**
99
101
  * A node URL in one spelling: the scheme and host lowercased, a default port and dot segments
100
102
  * dropped (the URL parser's own normalization), no trailing dot on the host and no trailing
101
- * slash. So a node compares equal however it's written: `https://Blockula-Testnet.fly.dev:443/.`
102
- * is the public RPC, and is never its own witness (audit MCK-1). What isn't an http(s) URL comes
103
+ * slash. So a node compares equal however it's written: `https://Node.Example:443/.` is
104
+ * `https://node.example`, and is never its own witness (audit MCK-1). What isn't an http(s) URL comes
103
105
  * back as it is.
104
106
  */
105
107
  export function canonicalUrl(u) {
@@ -171,8 +173,8 @@ export function networkArg(argv) {
171
173
  }
172
174
  /**
173
175
  * The network from the configuration: `--network` (or BLOCKULA_NETWORK) names one, BLOCKULA_RPC
174
- * gives the node, BLOCKULA_CHAIN_ID / BLOCKULA_GENESIS_HASH add pins. With none of them, the
175
- * public testnet. Contradictions are errors, never guesses.
176
+ * gives the node, BLOCKULA_CHAIN_ID / BLOCKULA_GENESIS_HASH add pins. With none of them, a
177
+ * `blockula dev` node on this machine (local). Contradictions are errors, never guesses.
176
178
  */
177
179
  export function resolveNetwork(env, argv = []) {
178
180
  const named = (networkArg(argv) ?? env.BLOCKULA_NETWORK)?.trim().toLowerCase() || undefined;
@@ -187,11 +189,11 @@ export function resolveNetwork(env, argv = []) {
187
189
  else if (rpc)
188
190
  key = Object.values(KNOWN_NETWORKS).find((n) => n.rpc && canonicalUrl(n.rpc) === rpc)?.key ?? (isLocalUrl(rpc) ? "local" : "custom");
189
191
  else
190
- key = "testnet";
192
+ key = "local";
191
193
  const base = key === "custom" ? { key, label: "custom node", pin: {}, remember: true } : KNOWN_NETWORKS[key];
192
194
  const url = rpc ?? (base.rpc ? canonicalUrl(base.rpc) : undefined);
193
195
  if (!url)
194
- throw new Error("the custom network needs BLOCKULA_RPC");
196
+ throw new Error(key === "testnet" ? TESTNET_RETIRED : "the custom network needs BLOCKULA_RPC");
195
197
  if (key === "local" && !isLocalUrl(url))
196
198
  throw new Error(`the local network is a node on this machine, not ${url}`);
197
199
  const pin = { ...base.pin };
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@blockula/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@blockula/mcp",
9
- "version": "1.0.0",
9
+ "version": "1.0.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@blockula/sdk": "1.0.0",
13
- "@modelcontextprotocol/sdk": "1.32.1",
12
+ "@blockula/sdk": "1.0.2",
13
+ "@modelcontextprotocol/sdk": "1.32.2",
14
14
  "zod": "4.6.7"
15
15
  },
16
16
  "bin": {
@@ -21,9 +21,9 @@
21
21
  }
22
22
  },
23
23
  "node_modules/@blockula/sdk": {
24
- "version": "1.0.0",
25
- "resolved": "https://registry.npmjs.org/@blockula/sdk/-/sdk-1.0.0.tgz",
26
- "integrity": "sha512-s8eNqf62093ypA8i/D6xFu8zkaVnPP18RCy2eYLkzR2c7zO5XNiYk0DiYxhaKB8qIgK4y74vISRRMEYqh6vlyg==",
24
+ "version": "1.0.2",
25
+ "resolved": "https://registry.npmjs.org/@blockula/sdk/-/sdk-1.0.2.tgz",
26
+ "integrity": "sha512-R97mPeiyN2U6Mgr1+ggfTF1a7ol/MSnb78kFQmMWFzLYRIeQq5Np/ZNIR2apJtP8JeYZvjd0LQLonjIdL5kQ4Q==",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@noble/hashes": "2.4.0",
@@ -47,9 +47,9 @@
47
47
  }
48
48
  },
49
49
  "node_modules/@modelcontextprotocol/sdk": {
50
- "version": "1.32.1",
51
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz",
52
- "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==",
50
+ "version": "1.32.2",
51
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.2.tgz",
52
+ "integrity": "sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==",
53
53
  "license": "MIT",
54
54
  "dependencies": {
55
55
  "@hono/node-server": "^1.19.9 || ^2.0.5",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockula/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "\ud83e\udddb MCP server for Blockula \u2014 lets any coding agent run a node, hold a post-quantum wallet, send FANG and claim .boo names",
5
5
  "type": "module",
6
6
  "bin": {
@@ -19,8 +19,8 @@
19
19
  },
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
- "@blockula/sdk": "1.0.0",
23
- "@modelcontextprotocol/sdk": "1.32.1",
22
+ "@blockula/sdk": "1.0.2",
23
+ "@modelcontextprotocol/sdk": "1.32.2",
24
24
  "zod": "4.6.7"
25
25
  },
26
26
  "devDependencies": {