@aibtc/mcp-server 1.71.0 → 1.72.1

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.
Files changed (123) hide show
  1. package/README.md +36 -3
  2. package/dist/bridge/index.js +2 -2
  3. package/dist/config/networks.js +4 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +118 -43
  6. package/dist/tools/arxiv-research.tools.d.ts +1 -1
  7. package/dist/tools/arxiv-research.tools.js +6 -6
  8. package/dist/tools/at-stake-legion.tools.d.ts +1 -1
  9. package/dist/tools/at-stake-legion.tools.js +14 -14
  10. package/dist/tools/at-stake.tools.d.ts +1 -1
  11. package/dist/tools/at-stake.tools.js +17 -17
  12. package/dist/tools/bitcoin.tools.d.ts +1 -1
  13. package/dist/tools/bitcoin.tools.js +12 -12
  14. package/dist/tools/bitflow.tools.d.ts +1 -1
  15. package/dist/tools/bitflow.tools.js +21 -21
  16. package/dist/tools/bns.tools.d.ts +1 -1
  17. package/dist/tools/bns.tools.js +18 -18
  18. package/dist/tools/bounty-hint.d.ts +1 -1
  19. package/dist/tools/bounty-scanner.tools.d.ts +1 -1
  20. package/dist/tools/bounty-scanner.tools.js +20 -20
  21. package/dist/tools/child-inscription.tools.d.ts +1 -1
  22. package/dist/tools/child-inscription.tools.js +6 -6
  23. package/dist/tools/competition.tools.d.ts +1 -1
  24. package/dist/tools/competition.tools.js +4 -4
  25. package/dist/tools/contract.tools.d.ts +1 -1
  26. package/dist/tools/contract.tools.js +8 -8
  27. package/dist/tools/credentials.tools.d.ts +1 -1
  28. package/dist/tools/credentials.tools.js +8 -8
  29. package/dist/tools/defi.tools.d.ts +1 -1
  30. package/dist/tools/defi.tools.js +21 -21
  31. package/dist/tools/dual-stacking.tools.d.ts +1 -1
  32. package/dist/tools/dual-stacking.tools.js +7 -7
  33. package/dist/tools/earning.tools.d.ts +1 -1
  34. package/dist/tools/earning.tools.js +2 -2
  35. package/dist/tools/endpoint.tools.d.ts +1 -1
  36. package/dist/tools/endpoint.tools.js +14 -13
  37. package/dist/tools/identity.tools.d.ts +1 -1
  38. package/dist/tools/identity.tools.js +19 -19
  39. package/dist/tools/inbox-x402.tools.d.ts +1 -1
  40. package/dist/tools/inbox-x402.tools.js +2 -2
  41. package/dist/tools/inbox.tools.d.ts +1 -1
  42. package/dist/tools/inbox.tools.js +2 -2
  43. package/dist/tools/index.d.ts +6 -3
  44. package/dist/tools/index.js +69 -58
  45. package/dist/tools/inference-marketplace.tools.d.ts +1 -1
  46. package/dist/tools/inference-marketplace.tools.js +10 -10
  47. package/dist/tools/jingswap.tools.d.ts +1 -1
  48. package/dist/tools/jingswap.tools.js +30 -30
  49. package/dist/tools/legion.tools.d.ts +1 -1
  50. package/dist/tools/legion.tools.js +20 -20
  51. package/dist/tools/lightning.tools.d.ts +1 -1
  52. package/dist/tools/lightning.tools.js +14 -14
  53. package/dist/tools/mempool.tools.d.ts +1 -1
  54. package/dist/tools/mempool.tools.js +5 -5
  55. package/dist/tools/news.tools.d.ts +1 -1
  56. package/dist/tools/news.tools.js +2 -1
  57. package/dist/tools/nft.tools.d.ts +1 -1
  58. package/dist/tools/nft.tools.js +12 -12
  59. package/dist/tools/nonce.tools.d.ts +1 -1
  60. package/dist/tools/nonce.tools.js +8 -8
  61. package/dist/tools/nostr.tools.d.ts +1 -1
  62. package/dist/tools/nostr.tools.js +12 -12
  63. package/dist/tools/openrouter.tools.d.ts +1 -1
  64. package/dist/tools/openrouter.tools.js +4 -4
  65. package/dist/tools/ordinals-marketplace.tools.d.ts +1 -1
  66. package/dist/tools/ordinals-marketplace.tools.js +10 -10
  67. package/dist/tools/ordinals-p2p.tools.d.ts +1 -1
  68. package/dist/tools/ordinals-p2p.tools.js +18 -18
  69. package/dist/tools/ordinals.tools.d.ts +1 -1
  70. package/dist/tools/ordinals.tools.js +8 -8
  71. package/dist/tools/pillar-direct.tools.d.ts +1 -1
  72. package/dist/tools/pillar-direct.tools.js +36 -36
  73. package/dist/tools/pillar.tools.d.ts +1 -1
  74. package/dist/tools/pillar.tools.js +28 -26
  75. package/dist/tools/profiles.d.ts +51 -0
  76. package/dist/tools/profiles.js +111 -0
  77. package/dist/tools/psbt.tools.d.ts +1 -1
  78. package/dist/tools/psbt.tools.js +8 -8
  79. package/dist/tools/query.tools.d.ts +1 -1
  80. package/dist/tools/query.tools.js +12 -12
  81. package/dist/tools/relay-diagnostic.tools.d.ts +1 -1
  82. package/dist/tools/relay-diagnostic.tools.js +3 -3
  83. package/dist/tools/reputation.tools.d.ts +1 -1
  84. package/dist/tools/reputation.tools.js +22 -22
  85. package/dist/tools/runes.tools.d.ts +1 -1
  86. package/dist/tools/runes.tools.js +18 -18
  87. package/dist/tools/sbtc.tools.d.ts +1 -1
  88. package/dist/tools/sbtc.tools.js +12 -12
  89. package/dist/tools/scaffold.tools.d.ts +1 -1
  90. package/dist/tools/scaffold.tools.js +4 -4
  91. package/dist/tools/settings.tools.d.ts +1 -1
  92. package/dist/tools/settings.tools.js +9 -9
  93. package/dist/tools/signing.tools.d.ts +1 -1
  94. package/dist/tools/signing.tools.js +20 -20
  95. package/dist/tools/souldinals.tools.d.ts +1 -1
  96. package/dist/tools/souldinals.tools.js +10 -10
  97. package/dist/tools/stacking-lottery.tools.d.ts +1 -1
  98. package/dist/tools/stacking-lottery.tools.js +11 -11
  99. package/dist/tools/stacking.tools.d.ts +1 -1
  100. package/dist/tools/stacking.tools.js +13 -13
  101. package/dist/tools/stacks-market.tools.d.ts +1 -1
  102. package/dist/tools/stacks-market.tools.js +22 -22
  103. package/dist/tools/styx.tools.d.ts +1 -1
  104. package/dist/tools/styx.tools.js +11 -11
  105. package/dist/tools/taproot-multisig.tools.d.ts +1 -1
  106. package/dist/tools/taproot-multisig.tools.js +5 -5
  107. package/dist/tools/tenero.tools.d.ts +1 -1
  108. package/dist/tools/tenero.tools.js +22 -22
  109. package/dist/tools/tokens.tools.d.ts +1 -1
  110. package/dist/tools/tokens.tools.js +10 -10
  111. package/dist/tools/transfer.tools.d.ts +1 -1
  112. package/dist/tools/transfer.tools.js +4 -4
  113. package/dist/tools/wallet-management.tools.d.ts +1 -1
  114. package/dist/tools/wallet-management.tools.js +16 -16
  115. package/dist/tools/wallet.tools.d.ts +1 -1
  116. package/dist/tools/wallet.tools.js +2 -2
  117. package/dist/tools/yield-dashboard.tools.d.ts +1 -1
  118. package/dist/tools/yield-dashboard.tools.js +5 -5
  119. package/dist/tools/yield-hunter.tools.d.ts +1 -1
  120. package/dist/tools/yield-hunter.tools.js +6 -6
  121. package/dist/utils/fee.js +1 -1
  122. package/package.json +7 -6
  123. package/skill/SKILL.md +1 -1
package/README.md CHANGED
@@ -27,7 +27,9 @@ Bitcoin-native MCP server for AI agents: BTC/STX wallets, DeFi yield, sBTC peg,
27
27
  npx @aibtc/mcp-server@latest --install
28
28
  ```
29
29
 
30
- That's it! This automatically configures Claude Code. Restart your terminal and start chatting.
30
+ This configures Claude Code and creates the agent's wallet: it prints the Stacks and Bitcoin addresses, a generated password and the 24-word mnemonic **once**. Write both down. The mnemonic is stored only encrypted (AES-256-GCM) in `~/.aibtc/` on this machine, and the password is not saved anywhere; the agent asks for it to unlock, and you can change it with `wallet_rotate_password`. If a wallet for the network already exists it is kept. The wallet is only created when the install runs in an interactive terminal (never into a pipe or CI log). Pass `--no-wallet` to skip it and create or import one from the agent instead.
31
+
32
+ Restart your terminal, send a little STX to the printed address, and ask the agent to unlock the wallet and make a paid inference call.
31
33
 
32
34
  ### Claude Desktop (App)
33
35
 
@@ -114,6 +116,37 @@ npx @aibtc/mcp-server@latest --install --cursor --testnet # Cursor, testnet
114
116
 
115
117
  > **Why npx?** Using `npx @aibtc/mcp-server@latest` ensures you always get the newest version automatically. Global installs (`npm install -g`) won't auto-update.
116
118
 
119
+ ### Tool Profiles
120
+
121
+ Every tool definition is loaded into the model's context, so `--install` writes `AIBTC_TOOLS=core`: a lean core of 25 tools: wallet (including `wallet_rotate_password`), balances, STX/BTC/sBTC transfers, x402 (`list_x402_endpoints`, `probe_x402_endpoint`, `execute_x402_endpoint`) and earning (`earning_opportunities`, `bounty_list/get/submit`, `identity_register`).
122
+
123
+ Add groups with `AIBTC_TOOLS=core,defi,ordinals` in the server's `env` (the core is always included), or load everything with `AIBTC_TOOLS=all` / `--profile full`. If `AIBTC_TOOLS` is not set at all, every tool is loaded, so configs written by earlier versions keep all their tools:
124
+
125
+ ```bash
126
+ npx @aibtc/mcp-server@latest --install --profile full # writes AIBTC_TOOLS=all
127
+ ```
128
+
129
+ | Group | Tools |
130
+ |-------|-------|
131
+ | `wallet` | Wallet management extras, encrypted credential store |
132
+ | `stacks` | Stacks transactions, contracts, tokens, NFTs, chain queries, nonce tools |
133
+ | `sbtc` | sBTC deposit/withdraw, Styx BTC→sBTC |
134
+ | `bitcoin` | Bitcoin L1 UTXOs, mempool watch |
135
+ | `lightning` | Lightning (Spark) wallet and L402 payments |
136
+ | `stacking` | PoX stacking, dual stacking, StackSpot |
137
+ | `defi` | ALEX, Zest, Bitflow, Jingswap, yield hunter/dashboard, Tenero analytics |
138
+ | `pillar` | Pillar smart wallet |
139
+ | `ordinals` | Inscriptions, runes, PSBT, ordinals marketplace/P2P, taproot multisig |
140
+ | `bns` | BNS names |
141
+ | `identity` | ERC-8004 identity and reputation, message signing |
142
+ | `social` | Nostr, AIBTC inbox |
143
+ | `earn` | Bounty board (create, accept, pay, my views) |
144
+ | `legion` | AIBTC News Legion |
145
+ | `markets` | Stacks prediction market, At Stake |
146
+ | `dev` | Scaffolding, OpenRouter, settings, inference marketplace, arXiv |
147
+
148
+ The server's instructions list the groups that are off, so the agent can tell you which one to enable. The OpenRouter `bridge` starts from the full set and narrows it with its own `--read-only`/`--allow`/`--block` flags.
149
+
117
150
  ### Manual Configuration
118
151
 
119
152
  If you prefer to configure manually, add the following to your client's config file. The `-y` flag stops npx from prompting for confirmation.
@@ -181,7 +214,7 @@ NETWORK = "mainnet"
181
214
 
182
215
  ## Giving Claude a Wallet
183
216
 
184
- When you first use @aibtc/mcp-server, Claude doesn't have a wallet. Here's the smooth onboarding flow:
217
+ `--install` creates a wallet for you (see [Quick Start](#quick-start)). If you installed with `--no-wallet`, configured the client by hand, or want another wallet, the agent can create or import one:
185
218
 
186
219
  ### Example Conversation
187
220
 
@@ -547,7 +580,7 @@ Or use any SIP-010 token by contract ID: `SP2X...::token-name`
547
580
  | Environment Variable | Description | Default |
548
581
  |---------------------|-------------|---------|
549
582
  | `NETWORK` | `mainnet` or `testnet` | `mainnet` |
550
- | `API_URL` | Default x402 API base URL | `https://x402.biwas.xyz` |
583
+ | `AIBTC_TOOLS` | `core`, `core,<group,...>`, or `all` (see [Tool Profiles](#tool-profiles)). `--install` writes `core` | all tools when unset |
551
584
  | `CLIENT_MNEMONIC` | (Optional) Pre-configured mnemonic | - |
552
585
  | `HIRO_API_KEY` | (Optional) Hiro API key for higher rate limits | - |
553
586
  | `SPEND_LIMIT_ENABLED` | Set `false` to disable the wallet spending limit | `true` |
@@ -27,8 +27,8 @@
27
27
  * --network <net> mainnet|testnet for the spawned server (default mainnet)
28
28
  * --max-turns <n> tool-call loop cap (default 10)
29
29
  */
30
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
31
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
30
+ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
31
+ import { Client } from "@modelcontextprotocol/client";
32
32
  import { STATIC_ENDPOINTS } from "../endpoints/registry.js";
33
33
  const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";
34
34
  const DEFAULT_MODEL = "anthropic/claude-3.5-haiku";
@@ -1,5 +1,8 @@
1
1
  export const NETWORK = process.env.NETWORK === "testnet" ? "testnet" : "mainnet";
2
- export const API_URL = process.env.API_URL || "https://x402.biwas.xyz";
2
+ // Base URL for bare-path x402 calls. Fixed per network, not read from the
3
+ // environment: the server loads the .env of whatever directory the client
4
+ // starts it in, and a project's generic API_URL must not redirect payments.
5
+ export const API_URL = NETWORK === "mainnet" ? "https://x402.aibtc.com" : "https://x402.aibtc.dev";
3
6
  export function getStacksNetwork(network) {
4
7
  return network === "mainnet" ? "mainnet" : "testnet";
5
8
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import "dotenv/config";
2
+ export {};
3
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import "dotenv/config";
3
2
  import fs from "fs/promises";
4
3
  import path from "path";
5
4
  import os from "os";
6
5
  import { createRequire } from "module";
7
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { randomBytes } from "crypto";
7
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
8
+ import { McpServer } from "@modelcontextprotocol/server";
9
9
  import { registerAllTools } from "./tools/index.js";
10
+ import { describeSelection, resolveToolSelection } from "./tools/profiles.js";
10
11
  import { installBountyHint } from "./tools/bounty-hint.js";
11
12
  import { NETWORK, API_URL } from "./config/index.js";
12
13
  import { redactSensitive } from "./utils/redact.js";
13
14
  import { initializeStorage } from "./utils/storage.js";
15
+ import { getWalletManager } from "./services/wallet-manager.js";
16
+ import { getLightningManager } from "./services/lightning-manager.js";
14
17
  const require = createRequire(import.meta.url);
15
18
  const packageJson = require("../package.json");
16
19
  // =============================================================================
@@ -64,30 +67,30 @@ async function writeJsonConfig(filePath, config) {
64
67
  await fs.writeFile(filePath, JSON.stringify(config, null, 2));
65
68
  }
66
69
  // Standard MCP server entry shared by every JSON-based client config.
67
- function serverEntry(network) {
70
+ function serverEntry(env) {
68
71
  return {
69
72
  command: "npx",
70
73
  args: ["-y", SERVER_NPM],
71
- env: { NETWORK: network },
74
+ env,
72
75
  };
73
76
  }
74
77
  // Most clients (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI) use
75
78
  // the same `{ "mcpServers": { "aibtc": {...} } }` JSON shape.
76
- async function writeMcpServersJson(configPath, network) {
79
+ async function writeMcpServersJson(configPath, env) {
77
80
  const config = await readJsonConfig(configPath);
78
81
  const servers = (config.mcpServers ??= {});
79
- servers["aibtc"] = serverEntry(network);
82
+ servers["aibtc"] = serverEntry(env);
80
83
  await writeJsonConfig(configPath, config);
81
84
  }
82
85
  // VS Code (.vscode/mcp.json) uses a `servers` key and a typed stdio entry.
83
- async function writeVsCodeJson(configPath, network) {
86
+ async function writeVsCodeJson(configPath, env) {
84
87
  const config = await readJsonConfig(configPath);
85
88
  const servers = (config.servers ??= {});
86
89
  servers["aibtc"] = {
87
90
  type: "stdio",
88
91
  command: "npx",
89
92
  args: ["-y", SERVER_NPM],
90
- env: { NETWORK: network },
93
+ env,
91
94
  };
92
95
  await writeJsonConfig(configPath, config);
93
96
  }
@@ -106,7 +109,7 @@ function stripCodexSection(content) {
106
109
  }
107
110
  return out.join("\n");
108
111
  }
109
- async function writeCodexToml(configPath, network) {
112
+ async function writeCodexToml(configPath, env) {
110
113
  let existing = "";
111
114
  try {
112
115
  existing = await fs.readFile(configPath, "utf8");
@@ -123,7 +126,7 @@ async function writeCodexToml(configPath, network) {
123
126
  `args = ["-y", "${SERVER_NPM}"]`,
124
127
  "",
125
128
  "[mcp_servers.aibtc.env]",
126
- `NETWORK = "${network}"`,
129
+ ...Object.entries(env).map(([key, value]) => `${key} = "${value}"`),
127
130
  ].join("\n");
128
131
  const content = (preserved ? `${preserved}\n\n` : "") + block + "\n";
129
132
  await fs.mkdir(path.dirname(configPath), { recursive: true });
@@ -188,23 +191,86 @@ function resolveInstallTarget() {
188
191
  // Default to the entry explicitly marked as default (flag === null) — Claude Code.
189
192
  return matched[0] ?? INSTALL_TARGETS.find((t) => t.flag === null);
190
193
  }
194
+ /**
195
+ * Create the agent's wallet during install so a fresh user can fund it right
196
+ * away. The generated password and the mnemonic are printed once and not
197
+ * stored in plain text; the password can be changed with wallet_rotate_password.
198
+ * Returns the Stacks address of the new or already-existing wallet.
199
+ */
200
+ async function ensureInstallWallet(network) {
201
+ await initializeStorage();
202
+ const walletManager = getWalletManager();
203
+ const existing = (await walletManager.listWallets()).find((w) => w.network === network);
204
+ if (existing) {
205
+ console.log(`\n👛 Existing ${network} wallet kept: ${existing.name} (${existing.address})`);
206
+ return existing.address;
207
+ }
208
+ const password = randomBytes(18).toString("base64url");
209
+ const wallet = await walletManager.createWallet("main", password, network);
210
+ // Print the credentials before anything else can fail: the wallet is already
211
+ // on disk, and without the password it can never be unlocked.
212
+ console.log(`\n👛 Wallet created (${network}), stored encrypted in ~/.aibtc/`);
213
+ console.log(` Stacks: ${wallet.address}`);
214
+ if (wallet.btcAddress)
215
+ console.log(` Bitcoin: ${wallet.btcAddress}`);
216
+ console.log(`\n Password: ${password}`);
217
+ console.log(` Mnemonic: ${wallet.mnemonic}`);
218
+ console.log("\n⚠️ Write both down now. They are shown once and the password is not saved anywhere.");
219
+ console.log(" The password unlocks the wallet; change it any time by asking your agent to rotate it.");
220
+ console.log(" The mnemonic is the only way to recover the wallet and its funds.");
221
+ // Same unified setup as wallet_create: a Lightning wallet derived from the
222
+ // same mnemonic (mainnet only). Its failure is reported, the main wallet stands.
223
+ try {
224
+ const lightning = await getLightningManager().setupFromMainMnemonic(wallet.mnemonic, password, "main", network);
225
+ if (lightning.kind === "setup") {
226
+ console.log(` Lightning: ${lightning.depositAddress} (deposit address, same mnemonic)`);
227
+ }
228
+ }
229
+ catch (error) {
230
+ console.error(` Lightning setup failed: ${redactSensitive(error instanceof Error ? error.message : String(error))}. ` +
231
+ "Ask your agent to run lightning_create later.");
232
+ }
233
+ return wallet.address;
234
+ }
191
235
  async function runInstall() {
192
236
  const network = process.argv.includes("--testnet") ? "testnet" : "mainnet";
237
+ // Only --profile full installs everything; resolving against AIBTC_TOOLS=core
238
+ // also validates the flag.
239
+ const fullProfile = resolveToolSelection(process.argv, { AIBTC_TOOLS: "core" }).all;
193
240
  const target = resolveInstallTarget();
194
241
  const configPath = target.configPath();
195
242
  console.log(`🔧 Installing @aibtc/mcp-server to ${target.label}...\n`);
196
- await target.write(configPath, network);
243
+ const env = { NETWORK: network };
244
+ env.AIBTC_TOOLS = fullProfile ? "all" : "core";
245
+ await target.write(configPath, env);
197
246
  console.log("✅ Successfully installed!\n");
198
247
  console.log(` Client: ${target.label}`);
199
248
  console.log(` Config: ${configPath}`);
200
249
  console.log(` Network: ${network}`);
201
- console.log("\n📋 Next steps:");
202
- console.log(` 1. ${target.restart}`);
203
- console.log(" 2. Ask the agent: \"What's your wallet address?\"");
204
- console.log(" 3. The agent will guide you through wallet setup\n");
205
- if (network === "testnet") {
206
- console.log("💡 Tip: Get testnet STX at https://explorer.hiro.so/sandbox/faucet?chain=testnet\n");
250
+ console.log(` Tools: ${fullProfile ? "all" : "lean core (set AIBTC_TOOLS=core,defi,ordinals,... in the env, or re-run with --profile full)"}`);
251
+ // The wallet's password and mnemonic are printed once, so only create it when
252
+ // a person is reading the terminal, never into a pipe, file or CI log.
253
+ let address = null;
254
+ if (process.argv.includes("--no-wallet")) {
255
+ // Explicitly skipped
256
+ }
257
+ else if (!process.stdout.isTTY) {
258
+ console.log("\n👛 Wallet not created: output is not a terminal, and the password and mnemonic");
259
+ console.log(" are only ever printed to one. Re-run --install in a terminal, or ask the agent to create one.");
260
+ }
261
+ else {
262
+ address = await ensureInstallWallet(network);
207
263
  }
264
+ console.log(`\n📋 ${target.restart}, then try:`);
265
+ const fund = network === "testnet"
266
+ ? "testnet STX from https://explorer.hiro.so/sandbox/faucet?chain=testnet"
267
+ : "0.01 STX";
268
+ console.log(address
269
+ ? ` 1. Send ${fund} to ${address}`
270
+ : ` 1. Ask your agent: "What's your wallet address?" and send it ${fund}`);
271
+ console.log(` 2. Ask your agent: "Unlock my wallet" and give it the password`);
272
+ const x402Host = network === "testnet" ? "x402.aibtc.dev" : "x402.aibtc.com";
273
+ console.log(` 3. Ask it: "Make a paid inference call on ${x402Host}" (0.001 STX per call)\n`);
208
274
  }
209
275
  // =============================================================================
210
276
  // YIELD HUNTER DAEMON
@@ -251,36 +317,45 @@ else if (process.argv.includes("--install") || process.argv.includes("install"))
251
317
  }
252
318
  else {
253
319
  // Normal MCP server mode
254
- const server = new McpServer({
255
- name: "aibtc-mcp-server",
256
- version: packageJson.version,
257
- }, {
258
- instructions: [
259
- "This server lets an agent earn and move real Bitcoin/sBTC on Stacks.",
260
- "",
261
- "Hunt bounties. The most direct way to earn here is the sBTC bounty board:",
262
- "call bounty_list to see open bounties, bounty_get for the task detail, do",
263
- "the work, then bounty_submit your deliverable. Accepted submissions are paid",
264
- "in sBTC. Check periodically for new open bounties.",
265
- "",
266
- "For the full menu of ways to put assets to work (stacking, DeFi yield,",
267
- "trading, paid endpoints), call earning_opportunities. Submitting to a bounty",
268
- "requires a Registered (L1+) on-chain identity — see identity_register.",
269
- ].join("\n"),
270
- });
271
- // Append a bounty-board hint to spend/onboarding-tool output.
272
- // Must wrap registerTool before registration; restore it after.
273
- const restoreBountyHint = installBountyHint(server);
274
- // Register all tools from the modular registry
275
- registerAllTools(server);
276
- restoreBountyHint();
320
+ // One instance per connection. serveStdio picks the protocol era from the
321
+ // opening message (2025-era `initialize` or a 2026-07-28 envelope) and pins
322
+ // the instance built by this factory to the connection.
323
+ function createServer(toolSelection) {
324
+ const server = new McpServer({
325
+ name: "aibtc-mcp-server",
326
+ version: packageJson.version,
327
+ }, {
328
+ instructions: [
329
+ "This server lets an agent earn and move real Bitcoin/sBTC on Stacks.",
330
+ "",
331
+ "Hunt bounties. The most direct way to earn here is the sBTC bounty board:",
332
+ "call bounty_list to see open bounties, bounty_get for the task detail, do",
333
+ "the work, then bounty_submit your deliverable. Accepted submissions are paid",
334
+ "in sBTC. Check periodically for new open bounties.",
335
+ "",
336
+ "For the full menu of ways to put assets to work (stacking, DeFi yield,",
337
+ "trading, paid endpoints), call earning_opportunities. Submitting to a bounty",
338
+ "requires a Registered (L1+) on-chain identity — see identity_register.",
339
+ "",
340
+ describeSelection(toolSelection),
341
+ ].join("\n"),
342
+ });
343
+ // Append a bounty-board hint to spend/onboarding-tool output.
344
+ // Must wrap registerTool before registration; restore it after.
345
+ const restoreBountyHint = installBountyHint(server);
346
+ // Register all tools from the modular registry
347
+ registerAllTools(server, toolSelection);
348
+ restoreBountyHint();
349
+ return server;
350
+ }
277
351
  async function main() {
352
+ const toolSelection = resolveToolSelection();
278
353
  await initializeStorage();
279
- const transport = new StdioServerTransport();
280
- await server.connect(transport);
354
+ serveStdio(() => createServer(toolSelection), { onerror: (error) => console.error("MCP error:", redactSensitive(String(error))) });
281
355
  console.error("aibtc-mcp-server running on stdio");
282
356
  console.error(`Network: ${NETWORK}`);
283
357
  console.error(`API URL: ${API_URL}`);
358
+ console.error(`Tools: ${toolSelection.all ? "all" : ["core", ...toolSelection.groups].join(", ")}`);
284
359
  }
285
360
  main().catch((error) => {
286
361
  console.error("Fatal error:", redactSensitive(String(error)));
@@ -11,6 +11,6 @@
11
11
  *
12
12
  * Mirrors the arxiv-research skill (aibtcdev/skills/arxiv-research/).
13
13
  */
14
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import { McpServer } from "@modelcontextprotocol/server";
15
15
  export declare function registerArxivResearchTools(server: McpServer): void;
16
16
  //# sourceMappingURL=arxiv-research.tools.d.ts.map
@@ -189,7 +189,7 @@ Each paper includes title, authors (first 3), truncated abstract, arXiv link,
189
189
  relevance score, and topic tags.
190
190
 
191
191
  Read-only. No API key required.`,
192
- inputSchema: {
192
+ inputSchema: z.object({
193
193
  categories: z
194
194
  .string()
195
195
  .optional()
@@ -205,7 +205,7 @@ Read-only. No API key required.`,
205
205
  .min(0)
206
206
  .optional()
207
207
  .describe("Minimum relevance score for results. Default: 3. Set to 0 to include all fetched papers."),
208
- },
208
+ }),
209
209
  }, async ({ categories, max_results, min_score }) => {
210
210
  try {
211
211
  const cats = categories
@@ -260,7 +260,7 @@ disk — use the arxiv-research skill (bun run arxiv-research/arxiv-research.ts
260
260
  to write timestamped digest files to ~/.aibtc/arxiv-research/digests/.
261
261
 
262
262
  Read-only. No API key required.`,
263
- inputSchema: {
263
+ inputSchema: z.object({
264
264
  categories: z
265
265
  .string()
266
266
  .optional()
@@ -280,7 +280,7 @@ Read-only. No API key required.`,
280
280
  .min(0)
281
281
  .optional()
282
282
  .describe("Minimum relevance score for digest inclusion. Default: 3"),
283
- },
283
+ }),
284
284
  }, async ({ categories, max_results, date, min_score }) => {
285
285
  try {
286
286
  const cats = categories
@@ -387,14 +387,14 @@ Digests are NOT created by arxiv_compile_digest (which returns Markdown inline).
387
387
  Use the skill CLI to persist digests to disk.
388
388
 
389
389
  Read-only.`,
390
- inputSchema: {
390
+ inputSchema: z.object({
391
391
  limit: z
392
392
  .number()
393
393
  .min(1)
394
394
  .max(50)
395
395
  .optional()
396
396
  .describe("Maximum entries to show. Default: 10"),
397
- },
397
+ }),
398
398
  }, async ({ limit }) => {
399
399
  try {
400
400
  const maxEntries = limit ?? 10;
@@ -26,6 +26,6 @@
26
26
  * These govern the El Salvador market's two sides. Same word, different
27
27
  * contracts, different way of getting a vote.
28
28
  */
29
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
29
+ import { McpServer } from "@modelcontextprotocol/server";
30
30
  export declare function registerAtStakeLegionTools(server: McpServer): void;
31
31
  //# sourceMappingURL=at-stake-legion.tools.d.ts.map
@@ -81,13 +81,13 @@ export function registerAtStakeLegionTools(server) {
81
81
  "shares with atstake_mint_complete_set.\n\n" +
82
82
  "Read-only. Defaults to the unlocked wallet, which requires one; pass an address " +
83
83
  "to read anyone without unlocking.",
84
- inputSchema: {
84
+ inputSchema: z.object({
85
85
  side: SIDE_ARG,
86
86
  address: z
87
87
  .string()
88
88
  .optional()
89
89
  .describe("Whose eligibility to report. Defaults to the unlocked wallet."),
90
- },
90
+ }),
91
91
  }, async ({ side, address }) => {
92
92
  try {
93
93
  const chosen = resolveSide(side);
@@ -175,7 +175,7 @@ export function registerAtStakeLegionTools(server) {
175
175
  "detail and the conclude forecast come from atstake_legion_get_proposal, which " +
176
176
  "reads the chain directly.\n\n" +
177
177
  "Read-only, no wallet needed.",
178
- inputSchema: {
178
+ inputSchema: z.object({
179
179
  side: z
180
180
  .enum(["yes", "no", "bonded", "idle", "both"])
181
181
  .optional()
@@ -186,7 +186,7 @@ export function registerAtStakeLegionTools(server) {
186
186
  .optional()
187
187
  .describe('Only proposals in this phase: "voting", "concludable", "passed", ' +
188
188
  '"failed", "expired", "pending".'),
189
- },
189
+ }),
190
190
  }, async ({ side, phase }) => {
191
191
  try {
192
192
  const response = await fetch(AT_STAKE_API);
@@ -257,10 +257,10 @@ export function registerAtStakeLegionTools(server) {
257
257
  "this is a forecast, not a promise — but it is the forecast that tells you " +
258
258
  "whether a conclude is worth the gas.\n\n" +
259
259
  "Read-only, no wallet needed.",
260
- inputSchema: {
260
+ inputSchema: z.object({
261
261
  side: SIDE_ARG,
262
262
  proposal_id: z.number().int().positive().describe("The proposal id."),
263
- },
263
+ }),
264
264
  }, async ({ side, proposal_id }) => {
265
265
  try {
266
266
  const chosen = resolveSide(side);
@@ -340,7 +340,7 @@ export function registerAtStakeLegionTools(server) {
340
340
  "legion-wide interval between proposals. Requires the minimum share balance, " +
341
341
  "which conclude re-reads — selling out before it concludes forfeits the payout. " +
342
342
  "All three fields are ASCII-only. Requires an unlocked wallet.",
343
- inputSchema: {
343
+ inputSchema: z.object({
344
344
  side: SIDE_ARG,
345
345
  title: z
346
346
  .string()
@@ -353,7 +353,7 @@ export function registerAtStakeLegionTools(server) {
353
353
  .string()
354
354
  .describe(`Public URL a voter can reproduce the claim from. ASCII, max ` +
355
355
  `${MAX_LINK_LENGTH} characters.`),
356
- },
356
+ }),
357
357
  }, async ({ side, title, description, link }) => {
358
358
  try {
359
359
  const chosen = resolveSide(side);
@@ -452,7 +452,7 @@ export function registerAtStakeLegionTools(server) {
452
452
  "State what you checked, not that you agree. A proposer cannot vote on their " +
453
453
  "own proposal, and each address votes once. Voting opens two blocks after " +
454
454
  "propose. Requires the minimum share balance and an unlocked wallet.",
455
- inputSchema: {
455
+ inputSchema: z.object({
456
456
  side: SIDE_ARG,
457
457
  proposal_id: z.number().int().positive().describe("The proposal id."),
458
458
  support: z
@@ -462,7 +462,7 @@ export function registerAtStakeLegionTools(server) {
462
462
  .string()
463
463
  .describe(`Why, in your own words — what you checked and what you found. ASCII, max ` +
464
464
  `${MAX_RATIONALE_LENGTH} characters. Permanent and public.`),
465
- },
465
+ }),
466
466
  }, async ({ side, proposal_id, support, rationale }) => {
467
467
  try {
468
468
  const chosen = resolveSide(side);
@@ -551,10 +551,10 @@ export function registerAtStakeLegionTools(server) {
551
551
  "The payout is shares of the proposer's own side while the market trades, and " +
552
552
  "an sBTC credit once it has resolved. The caller pays gas and receives nothing.\n\n" +
553
553
  "Requires an unlocked wallet.",
554
- inputSchema: {
554
+ inputSchema: z.object({
555
555
  side: SIDE_ARG,
556
556
  proposal_id: z.number().int().positive().describe("The proposal id."),
557
- },
557
+ }),
558
558
  }, async ({ side, proposal_id }) => {
559
559
  try {
560
560
  const chosen = resolveSide(side);
@@ -643,7 +643,7 @@ export function registerAtStakeLegionTools(server) {
643
643
  "Permissionless, once-only, and callable only after the market resolves and all " +
644
644
  "live proposals have settled. Pays the caller nothing. Requires an unlocked " +
645
645
  "wallet.",
646
- inputSchema: { side: SIDE_ARG },
646
+ inputSchema: z.object({ side: SIDE_ARG }),
647
647
  }, async ({ side }) => {
648
648
  try {
649
649
  const chosen = resolveSide(side);
@@ -711,7 +711,7 @@ export function registerAtStakeLegionTools(server) {
711
711
  "pays out in claim order until it runs dry, and the unpaid remainder stays on " +
712
712
  "your credit.\n\n" +
713
713
  "Requires an unlocked wallet.",
714
- inputSchema: { side: SIDE_ARG },
714
+ inputSchema: z.object({ side: SIDE_ARG }),
715
715
  }, async ({ side }) => {
716
716
  try {
717
717
  const chosen = resolveSide(side);
@@ -24,6 +24,6 @@
24
24
  * and building one belongs in a prover, not in an MCP argument list. The
25
25
  * permissionless NO settlement, `resolve-idle`, takes no arguments and is here.
26
26
  */
27
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
+ import { McpServer } from "@modelcontextprotocol/server";
28
28
  export declare function registerAtStakeTools(server: McpServer): void;
29
29
  //# sourceMappingURL=at-stake.tools.d.ts.map