@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 +13 -14
- package/dist/accounts.d.ts +4 -1
- package/dist/accounts.js +6 -5
- package/dist/index.js +18 -11
- package/dist/network.d.ts +7 -6
- package/dist/network.js +19 -17
- package/npm-shrinkwrap.json +10 -10
- package/package.json +3 -3
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
|
|
7
|
-
claude mcp add blockula -- npx -y @blockula/mcp
|
|
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
|
|
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`): `
|
|
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 (`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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` | `
|
|
110
|
-
| `BLOCKULA_RPC` |
|
|
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` |
|
|
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) |
|
package/dist/accounts.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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") ? "
|
|
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
|
-
/**
|
|
110
|
-
|
|
111
|
-
|
|
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 (
|
|
4
|
-
// claude mcp add blockula -- npx -y @blockula/mcp
|
|
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
|
|
8
|
-
// BLOCKULA_RPC node URL (default:
|
|
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,
|
|
19
|
-
//
|
|
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|
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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://
|
|
83
|
-
*
|
|
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,
|
|
116
|
-
*
|
|
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
|
-
|
|
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
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
testnet: { key: "testnet", label: "
|
|
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 = {
|
|
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
|
|
74
|
-
const PUBLIC_LABEL = { "blockula-testnet-2": "
|
|
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
|
|
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
|
-
|
|
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://
|
|
102
|
-
*
|
|
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,
|
|
175
|
-
*
|
|
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 = "
|
|
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 };
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockula/mcp",
|
|
3
|
-
"version": "1.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.
|
|
9
|
+
"version": "1.0.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@blockula/sdk": "1.0.
|
|
13
|
-
"@modelcontextprotocol/sdk": "1.32.
|
|
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.
|
|
25
|
-
"resolved": "https://registry.npmjs.org/@blockula/sdk/-/sdk-1.0.
|
|
26
|
-
"integrity": "sha512-
|
|
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.
|
|
51
|
-
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.
|
|
52
|
-
"integrity": "sha512-
|
|
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.
|
|
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.
|
|
23
|
-
"@modelcontextprotocol/sdk": "1.32.
|
|
22
|
+
"@blockula/sdk": "1.0.2",
|
|
23
|
+
"@modelcontextprotocol/sdk": "1.32.2",
|
|
24
24
|
"zod": "4.6.7"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|