agenticbtc-mcp 1.0.1 → 1.0.3

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 (3) hide show
  1. package/bin/agentbtc.js +41 -46
  2. package/package.json +2 -2
  3. package/src/server.js +1067 -40
package/bin/agentbtc.js CHANGED
@@ -17,7 +17,6 @@ function getClaudeConfigPath() {
17
17
  } else if (platform === "win32") {
18
18
  return join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
19
19
  } else {
20
- // Linux/other
21
20
  return join(homedir(), ".config", "claude", "claude_desktop_config.json");
22
21
  }
23
22
  }
@@ -51,11 +50,10 @@ async function checkApi(apiUrl, apiKey) {
51
50
 
52
51
  // Commands
53
52
  async function setup() {
54
- console.log("šŸš€ AgentBTC Setup\n");
53
+ console.log("šŸš€ AgenticBTC Setup\n");
55
54
 
56
- // Get configuration from user
57
- const apiUrl = await prompt(`AgentBTC API URL (http://localhost:8000): `) || "http://localhost:8000";
58
- const apiKey = await prompt("AgentBTC API Key: ");
55
+ const apiUrl = await prompt(`AgenticBTC API URL (https://agenticbtc.app): `) || "https://agenticbtc.app";
56
+ const apiKey = await prompt("AgenticBTC API Key: ");
59
57
 
60
58
  if (!apiKey) {
61
59
  console.log("āŒ API Key is required");
@@ -82,7 +80,6 @@ async function setup() {
82
80
  const configPath = getClaudeConfigPath();
83
81
  let config = { mcpServers: {} };
84
82
 
85
- // Load existing config if it exists
86
83
  if (existsSync(configPath)) {
87
84
  try {
88
85
  const existing = readFileSync(configPath, "utf8");
@@ -95,25 +92,25 @@ async function setup() {
95
92
  }
96
93
  }
97
94
 
98
- // Add AgentBTC server config
95
+ // Add AgenticBTC server config
99
96
  const serverConfig = {
100
97
  command: "npx",
101
- args: ["agentbtc", "server"],
98
+ args: ["agenticbtc-mcp", "server"],
102
99
  env: {
103
- AGENTBTC_API_URL: apiUrl,
104
- AGENTBTC_API_KEY: apiKey,
100
+ AGENTICBTC_API_URL: apiUrl,
101
+ AGENTICBTC_API_KEY: apiKey,
105
102
  NODE_TLS_REJECT_UNAUTHORIZED: "0"
106
103
  }
107
104
  };
108
105
 
109
106
  if (lndHost) {
110
- serverConfig.env.AGENTBTC_LND_HOST = lndHost;
107
+ serverConfig.env.AGENTICBTC_LND_HOST = lndHost;
111
108
  }
112
109
  if (lndMacaroon) {
113
- serverConfig.env.AGENTBTC_LND_MACAROON = lndMacaroon;
110
+ serverConfig.env.AGENTICBTC_LND_MACAROON = lndMacaroon;
114
111
  }
115
112
 
116
- config.mcpServers.agentbtc = serverConfig;
113
+ config.mcpServers.agenticbtc = serverConfig;
117
114
 
118
115
  // Create config directory if needed
119
116
  try {
@@ -137,25 +134,23 @@ async function setup() {
137
134
  }
138
135
 
139
136
  console.log("\nšŸŽ‰ Setup complete!");
140
- console.log("šŸ’” Restart Claude Desktop to load the AgentBTC server.");
141
- console.log("šŸ”§ Test with: agentbtc status");
137
+ console.log("šŸ’” Restart Claude Desktop to load the AgenticBTC MCP server.");
138
+ console.log("šŸ”§ Test with: npx agenticbtc-mcp status");
142
139
  }
143
140
 
144
141
  async function status() {
145
- const apiUrl = process.env.AGENTBTC_API_URL || "http://localhost:8000";
146
- const apiKey = process.env.AGENTBTC_API_KEY || "";
147
- const lndHost = process.env.AGENTBTC_LND_HOST || "";
142
+ const apiUrl = process.env.AGENTICBTC_API_URL || process.env.AGENTBTC_API_URL || "https://agenticbtc.app";
143
+ const apiKey = process.env.AGENTICBTC_API_KEY || process.env.AGENTBTC_API_KEY || "";
144
+ const lndHost = process.env.AGENTICBTC_LND_HOST || process.env.AGENTBTC_LND_HOST || "";
148
145
 
149
- console.log("šŸ“Š AgentBTC Status\n");
146
+ console.log("šŸ“Š AgenticBTC Status\n");
150
147
 
151
- // Configuration
152
148
  console.log("Configuration:");
153
149
  console.log(` API URL: ${apiUrl}`);
154
150
  console.log(` API Key: ${apiKey ? "āœ… Set" : "āŒ Not set"}`);
155
151
  console.log(` LND Host: ${lndHost || "āŒ Not configured"}`);
156
- console.log(` LND Macaroon: ${process.env.AGENTBTC_LND_MACAROON ? "āœ… Set" : "āŒ Not set"}`);
152
+ console.log(` LND Macaroon: ${(process.env.AGENTICBTC_LND_MACAROON || process.env.AGENTBTC_LND_MACAROON) ? "āœ… Set" : "āŒ Not set"}`);
157
153
 
158
- // API connectivity
159
154
  console.log("\nšŸ”„ Testing API connection...");
160
155
  const apiTest = await checkApi(apiUrl, apiKey);
161
156
  if (apiTest.ok) {
@@ -167,12 +162,12 @@ async function status() {
167
162
  }
168
163
  }
169
164
 
170
- // LND connectivity (if configured)
171
- if (lndHost && process.env.AGENTBTC_LND_MACAROON) {
165
+ if (lndHost && (process.env.AGENTICBTC_LND_MACAROON || process.env.AGENTBTC_LND_MACAROON)) {
166
+ const macaroon = process.env.AGENTICBTC_LND_MACAROON || process.env.AGENTBTC_LND_MACAROON;
172
167
  console.log("\nšŸ”„ Testing LND connection...");
173
168
  try {
174
169
  const lndRes = await fetch(`${lndHost}/v1/getinfo`, {
175
- headers: { "Grpc-Metadata-macaroon": process.env.AGENTBTC_LND_MACAROON }
170
+ headers: { "Grpc-Metadata-macaroon": macaroon }
176
171
  });
177
172
  if (lndRes.ok) {
178
173
  const lndData = await lndRes.json();
@@ -187,52 +182,52 @@ async function status() {
187
182
  }
188
183
  }
189
184
 
190
- // Claude Desktop config
191
185
  const configPath = getClaudeConfigPath();
192
186
  console.log(`\nšŸ”§ Claude Desktop config: ${configPath}`);
193
187
  if (existsSync(configPath)) {
194
188
  try {
195
189
  const config = JSON.parse(readFileSync(configPath, "utf8"));
196
- if (config.mcpServers?.agentbtc) {
197
- console.log("āœ… AgentBTC server configured in Claude Desktop");
190
+ if (config.mcpServers?.agenticbtc) {
191
+ console.log("āœ… AgenticBTC server configured in Claude Desktop");
192
+ } else if (config.mcpServers?.agentbtc) {
193
+ console.log("āš ļø Old AgentBTC config found — run 'npx agenticbtc-mcp start' to upgrade");
198
194
  } else {
199
- console.log("āš ļø AgentBTC server not found in Claude Desktop config");
200
- console.log(" Run: agentbtc setup");
195
+ console.log("āš ļø AgenticBTC server not found in Claude Desktop config");
196
+ console.log(" Run: npx agenticbtc-mcp start");
201
197
  }
202
198
  } catch (error) {
203
199
  console.log(`āŒ Could not read config: ${error.message}`);
204
200
  }
205
201
  } else {
206
202
  console.log("āŒ Claude Desktop config not found");
207
- console.log(" Run: agentbtc setup");
203
+ console.log(" Run: npx agenticbtc-mcp start");
208
204
  }
209
205
  }
210
206
 
211
207
  function server() {
212
- // Import and start the MCP server
213
208
  import("../src/server.js");
214
209
  }
215
210
 
216
211
  function help() {
217
212
  console.log(`
218
- šŸš€ AgentBTC - Bitcoin Lightning MCP Server for AI Agents
213
+ šŸš€ AgenticBTC - Intelligent Payment Routing for AI Agents
219
214
 
220
215
  Usage:
221
- agentbtc start Get started with AgentBTC (interactive setup)
222
- agentbtc setup Interactive setup for Claude Desktop
223
- agentbtc server Start the MCP server (used by Claude Desktop)
224
- agentbtc status Check configuration and connectivity
225
- agentbtc --help Show this help message
216
+ npx agenticbtc-mcp start Get started (interactive setup)
217
+ npx agenticbtc-mcp setup Interactive setup for Claude Desktop
218
+ npx agenticbtc-mcp server Start the MCP server (used by Claude Desktop)
219
+ npx agenticbtc-mcp status Check configuration and connectivity
220
+ npx agenticbtc-mcp --help Show this help message
226
221
 
227
222
  Examples:
228
- npx agentbtc start # Get started - first-time setup
229
- agentbtc status # Check if everything is working
223
+ npx agenticbtc-mcp start # First-time setup
224
+ npx agenticbtc-mcp status # Check if everything is working
230
225
 
231
226
  Environment Variables:
232
- AGENTBTC_API_URL AgentBTC API endpoint (default: http://localhost:8000)
233
- AGENTBTC_API_KEY AgentBTC API key (required)
234
- AGENTBTC_LND_HOST Lightning node REST API host (optional)
235
- AGENTBTC_LND_MACAROON Lightning node macaroon in hex (optional)
227
+ AGENTICBTC_API_URL AgenticBTC API endpoint (default: https://agenticbtc.app)
228
+ AGENTICBTC_API_KEY AgenticBTC API key (required)
229
+ AGENTICBTC_LND_HOST Lightning node REST API host (optional)
230
+ AGENTICBTC_LND_MACAROON Lightning node macaroon in hex (optional)
236
231
 
237
232
  More info: https://agenticbtc.app
238
233
  `);
@@ -257,6 +252,6 @@ switch (command) {
257
252
  break;
258
253
  default:
259
254
  console.log(`Unknown command: ${command}`);
260
- console.log("Run 'agentbtc --help' for usage information.");
255
+ console.log("Run 'npx agenticbtc-mcp --help' for usage information.");
261
256
  process.exit(1);
262
- }
257
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenticbtc-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Privacy-intelligent payments for AI agents — your privacy, your choice. Universal payment router with Lightning, Strike, Coinbase, PayPal, Venmo support.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -46,4 +46,4 @@
46
46
  "start": "node src/server.js",
47
47
  "test": "node bin/agenticbtc.js --help"
48
48
  }
49
- }
49
+ }
package/src/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * AgentBTC MCP Server
3
+ * AgenticBTC MCP Server
4
4
  * Exposes Bitcoin payment capabilities to Claude Desktop via Model Context Protocol.
5
5
  */
6
6
 
@@ -73,22 +73,143 @@ async function apiCall(path, options = {}) {
73
73
  return { status: res.status, data };
74
74
  }
75
75
 
76
+ // Helper: resolve agent by name or ID
77
+ async function resolveAgent(nameOrId) {
78
+ const { status, data } = await apiCall(`/api/v1/admin/agents`);
79
+ if (status !== 200) return null;
80
+ const agents = Array.isArray(data) ? data : (data.agents || []);
81
+ // Try exact ID match first
82
+ let agent = agents.find(a => a.id === nameOrId);
83
+ if (!agent) {
84
+ // Try case-insensitive name match
85
+ const lower = nameOrId.toLowerCase();
86
+ agent = agents.find(a => a.name.toLowerCase() === lower);
87
+ }
88
+ if (!agent) {
89
+ // Try partial name match
90
+ const lower = nameOrId.toLowerCase();
91
+ agent = agents.find(a => a.name.toLowerCase().includes(lower));
92
+ }
93
+ return agent || null;
94
+ }
95
+
96
+ // Check spending policy before a payment
97
+ async function checkSpendingPolicy(agentId, amountSats) {
98
+ try {
99
+ const { status, data } = await apiCall(`/api/v1/agents/${agentId}/spending-check?amount_sats=${amountSats}`);
100
+ if (status === 200) return data;
101
+ return { allowed: true, note: "Policy check unavailable — allowing" };
102
+ } catch (e) {
103
+ return { allowed: true, note: "Policy check failed — allowing" };
104
+ }
105
+ }
106
+
107
+ // Log a transaction after payment
108
+ async function logTransaction(agentId, amountSats, destination, memo, status) {
109
+ try {
110
+ await apiCall(`/api/v1/agents/${agentId}/transactions`, {
111
+ method: "POST",
112
+ body: JSON.stringify({
113
+ type: "payment",
114
+ amount_sats: amountSats,
115
+ destination: destination,
116
+ memo: memo,
117
+ status: status,
118
+ }),
119
+ });
120
+ } catch (e) {
121
+ // Silent fail — don't block on logging
122
+ }
123
+ }
124
+
125
+ // Determine auth level from API key
126
+ async function getAuthLevel() {
127
+ try {
128
+ const { status, data } = await apiCall("/api/v1/me");
129
+ if (status === 200) return data;
130
+ return { is_owner: true }; // Fallback if endpoint not available
131
+ } catch (e) {
132
+ return { is_owner: true };
133
+ }
134
+ }
135
+
76
136
  // Tool: Get agent wallet balance
77
137
  server.tool(
78
138
  "get_agent_balance",
79
139
  "Get Bitcoin balance for an agent wallet",
80
- { agent_id: z.string().describe("Agent wallet ID") },
140
+ { agent_id: z.string().describe("Agent wallet name or ID") },
81
141
  async ({ agent_id }) => {
82
- const { status, data } = await apiCall(`/api/v1/agents/${agent_id}`);
83
- if (status === 200) {
142
+ const agent = await resolveAgent(agent_id);
143
+ if (agent) {
84
144
  return {
85
145
  content: [{
86
146
  type: "text",
87
147
  text: JSON.stringify({
88
148
  success: true,
89
- agent: data.name,
90
- balance_sats: data.balance_sats || 0,
91
- balance_btc: data.balance_btc || "0.00000000",
149
+ agent: agent.name,
150
+ agent_id: agent.id,
151
+ balance_sats: agent.balance_sats || 0,
152
+ balance_btc: ((agent.balance_sats || 0) / 100000000).toFixed(8),
153
+ enabled: agent.enabled,
154
+ }, null, 2),
155
+ }],
156
+ };
157
+ }
158
+ return { content: [{ type: "text", text: `Error: Agent '${agent_id}' not found` }] };
159
+ }
160
+ );
161
+
162
+ // Tool: List all agent wallets
163
+ server.tool(
164
+ "list_agent_wallets",
165
+ "List all agent wallets with their balances and status (agent key: own wallet only)",
166
+ {},
167
+ async () => {
168
+ // Check if using agent key — scope to own wallet
169
+ const auth = await getAuthLevel();
170
+ if (!auth.is_owner && auth.agent_id) {
171
+ const agent = await resolveAgent(auth.agent_id);
172
+ if (agent) {
173
+ return {
174
+ content: [{
175
+ type: "text",
176
+ text: JSON.stringify({
177
+ success: true,
178
+ total_agents: 1,
179
+ note: "Agent key — showing own wallet only",
180
+ agents: [{
181
+ id: agent.id, name: agent.name,
182
+ balance_sats: agent.balance_sats || 0,
183
+ balance_btc: ((agent.balance_sats || 0) / 100000000).toFixed(8),
184
+ enabled: agent.enabled,
185
+ }],
186
+ }, null, 2),
187
+ }],
188
+ };
189
+ }
190
+ }
191
+ const { status, data } = await apiCall(`/api/v1/admin/agents`);
192
+ if (status === 200) {
193
+ const agentList = Array.isArray(data) ? data : (data.agents || []);
194
+ const agents = agentList.map(agent => ({
195
+ id: agent.id,
196
+ name: agent.name,
197
+ description: agent.description || "",
198
+ balance_sats: agent.balance_sats || 0,
199
+ balance_btc: ((agent.balance_sats || 0) / 100000000).toFixed(8),
200
+ enabled: agent.enabled,
201
+ created_at: agent.created_at,
202
+ }));
203
+
204
+ return {
205
+ content: [{
206
+ type: "text",
207
+ text: JSON.stringify({
208
+ success: true,
209
+ total_agents: agents.length,
210
+ enabled_agents: agents.filter(a => a.enabled).length,
211
+ total_balance_sats: agents.reduce((sum, a) => sum + a.balance_sats, 0),
212
+ agents: agents,
92
213
  }, null, 2),
93
214
  }],
94
215
  };
@@ -97,12 +218,16 @@ server.tool(
97
218
  }
98
219
  );
99
220
 
100
- // Tool: Create agent wallet
221
+ // Tool: Create agent wallet (owner only)
101
222
  server.tool(
102
223
  "create_agent_wallet",
103
- "Create a new Bitcoin wallet for an AI agent",
224
+ "Create a new Bitcoin wallet for an AI agent (owner access required)",
104
225
  { agent_name: z.string().describe("Name for the agent wallet") },
105
226
  async ({ agent_name }) => {
227
+ const auth = await getAuthLevel();
228
+ if (!auth.is_owner) {
229
+ return { content: [{ type: "text", text: "Error: Owner access required to create wallets" }] };
230
+ }
106
231
  const { status, data } = await apiCall("/api/v1/agents", {
107
232
  method: "POST",
108
233
  body: JSON.stringify({ name: agent_name }),
@@ -124,48 +249,161 @@ server.tool(
124
249
  }
125
250
  );
126
251
 
252
+ // Tool: Create Lightning invoice
253
+ server.tool(
254
+ "create_lightning_invoice",
255
+ "Create a Lightning Network invoice to receive Bitcoin payments",
256
+ {
257
+ amount_sats: z.number().describe("Amount in satoshis"),
258
+ description: z.string().optional().default("AgentBTC MCP payment").describe("Invoice description"),
259
+ agent: z.string().optional().describe("Agent wallet name or ID (optional)"),
260
+ },
261
+ async ({ amount_sats, description, agent }) => {
262
+ if (!LND_HOST) {
263
+ return { content: [{ type: "text", text: "Error: LND node not configured. Set AGENTBTC_LND_HOST environment variable." }] };
264
+ }
265
+
266
+ // Resolve agent if provided (for logging/tracking)
267
+ let agentName = null;
268
+ if (agent) {
269
+ const resolved = await resolveAgent(agent);
270
+ if (!resolved) {
271
+ return { content: [{ type: "text", text: `Error: Agent '${agent}' not found` }] };
272
+ }
273
+ agentName = resolved.name;
274
+ }
275
+
276
+ try {
277
+ // Create invoice directly via LND REST API
278
+ const res = await fetch(`${LND_HOST}/v1/invoices`, {
279
+ method: "POST",
280
+ headers: {
281
+ "Grpc-Metadata-macaroon": LND_MACAROON,
282
+ "Content-Type": "application/json",
283
+ },
284
+ body: JSON.stringify({
285
+ value: amount_sats,
286
+ memo: description + (agentName ? ` [${agentName}]` : ""),
287
+ expiry: "3600",
288
+ }),
289
+ });
290
+ const data = await res.json();
291
+
292
+ if (res.ok && data.payment_request) {
293
+ return {
294
+ content: [{
295
+ type: "text",
296
+ text: JSON.stringify({
297
+ success: true,
298
+ invoice: data.payment_request,
299
+ amount_sats: amount_sats,
300
+ description: description,
301
+ agent: agentName,
302
+ payment_hash: data.r_hash,
303
+ expires: "1 hour",
304
+ }, null, 2),
305
+ }],
306
+ };
307
+ }
308
+ return { content: [{ type: "text", text: `Error creating invoice: ${JSON.stringify(data)}` }] };
309
+ } catch (e) {
310
+ return { content: [{ type: "text", text: `LND connection error: ${e.message}` }] };
311
+ }
312
+ }
313
+ );
314
+
127
315
  // Tool: Pay Lightning invoice
128
316
  server.tool(
129
317
  "pay_lightning_invoice",
130
318
  "Pay a Lightning Network invoice using agent wallet funds",
131
319
  {
132
320
  invoice: z.string().describe("BOLT11 Lightning invoice to pay"),
321
+ agent: z.string().optional().describe("Agent wallet name or ID (for spending policy enforcement)"),
133
322
  fee_limit_sats: z.number().optional().default(100).describe("Max fee in sats"),
134
323
  },
135
- async ({ invoice, fee_limit_sats }) => {
136
- // Check routing status before paying
137
- const routingStatus = await checkRoutingStatus();
138
- if (routingStatus === "suspended") {
139
- return { content: [{ type: "text", text: "ā›” Account suspended — routing verification failed. Contact support." }] };
324
+ async ({ invoice, agent, fee_limit_sats }) => {
325
+ if (!LND_HOST) {
326
+ return { content: [{ type: "text", text: "Error: LND node not configured. Set AGENTBTC_LND_HOST environment variable." }] };
140
327
  }
141
328
 
142
- const { status, data } = await apiCall("/api/v1/invoices/pay", {
143
- method: "POST",
144
- body: JSON.stringify({ payment_request: invoice, fee_limit_sats }),
145
- });
146
- if (status === 201 || status === 200) {
147
- // Report payment for routing verification
148
- await reportPayment(
149
- data.payment_hash,
150
- data.amount_sats,
151
- data.destination || "",
152
- [BK_BLOCK_NODE_PUBKEY]
153
- );
154
- return {
155
- content: [{
156
- type: "text",
157
- text: JSON.stringify({
158
- success: true,
159
- amount_sats: data.amount_sats,
160
- platform_fee_sats: data.platform_fee_sats,
161
- payment_hash: data.payment_hash,
162
- status: data.status,
163
- message: `Paid ${data.amount_sats} sats ⚔`,
164
- }, null, 2),
165
- }],
166
- };
329
+ // Resolve agent for spending policy enforcement
330
+ let resolvedAgent = null;
331
+ if (agent) {
332
+ resolvedAgent = await resolveAgent(agent);
333
+ if (!resolvedAgent) {
334
+ return { content: [{ type: "text", text: `Error: Agent '${agent}' not found` }] };
335
+ }
336
+ if (!resolvedAgent.enabled) {
337
+ return { content: [{ type: "text", text: `Error: Agent '${resolvedAgent.name}' is disabled — payments blocked` }] };
338
+ }
339
+ }
340
+
341
+ // Auto-detect agent from API key auth level
342
+ if (!resolvedAgent) {
343
+ const auth = await getAuthLevel();
344
+ if (auth.agent_id) {
345
+ resolvedAgent = { id: auth.agent_id, name: auth.agent_name || auth.agent_id };
346
+ }
347
+ }
348
+
349
+ try {
350
+ // Decode invoice first to get amount for policy check
351
+ const decodeRes = await fetch(`${LND_HOST}/v1/payreq/${invoice}`, {
352
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
353
+ });
354
+ const decoded = await decodeRes.json();
355
+ const amountSats = parseInt(decoded.num_satoshis || 0);
356
+
357
+ // Check spending policy if agent is identified
358
+ if (resolvedAgent && amountSats > 0) {
359
+ const policy = await checkSpendingPolicy(resolvedAgent.id, amountSats);
360
+ if (!policy.allowed) {
361
+ return { content: [{ type: "text", text: `Payment blocked: ${policy.reason}` }] };
362
+ }
363
+ }
364
+
365
+ // Pay invoice directly via LND REST API
366
+ const res = await fetch(`${LND_HOST}/v1/channels/transactions`, {
367
+ method: "POST",
368
+ headers: {
369
+ "Grpc-Metadata-macaroon": LND_MACAROON,
370
+ "Content-Type": "application/json",
371
+ },
372
+ body: JSON.stringify({
373
+ payment_request: invoice,
374
+ fee_limit: { fixed: fee_limit_sats },
375
+ }),
376
+ });
377
+ const data = await res.json();
378
+
379
+ if (res.ok && !data.payment_error) {
380
+ const paidAmount = parseInt(data.value || data.value_sat || amountSats || 0);
381
+
382
+ // Log transaction against agent
383
+ if (resolvedAgent) {
384
+ await logTransaction(resolvedAgent.id, paidAmount, decoded.destination || "", decoded.description || "", "completed");
385
+ }
386
+
387
+ return {
388
+ content: [{
389
+ type: "text",
390
+ text: JSON.stringify({
391
+ success: true,
392
+ payment_hash: data.payment_hash,
393
+ amount_sats: paidAmount,
394
+ fee_sats: parseInt(data.fee || data.fee_sat || 0),
395
+ agent: resolvedAgent?.name || null,
396
+ status: data.status || "SUCCEEDED",
397
+ message: `Paid ${paidAmount} sats ⚔`,
398
+ }, null, 2),
399
+ }],
400
+ };
401
+ }
402
+ const errMsg = data.payment_error || data.message || JSON.stringify(data);
403
+ return { content: [{ type: "text", text: `Payment failed: ${errMsg}` }] };
404
+ } catch (e) {
405
+ return { content: [{ type: "text", text: `LND connection error: ${e.message}` }] };
167
406
  }
168
- return { content: [{ type: "text", text: `Payment failed: ${JSON.stringify(data)}` }] };
169
407
  }
170
408
  );
171
409
 
@@ -319,6 +557,795 @@ server.tool(
319
557
  }
320
558
  );
321
559
 
560
+ // Tool: Get transaction history
561
+ server.tool(
562
+ "get_transaction_history",
563
+ "Get recent Lightning payment history",
564
+ {
565
+ max_payments: z.number().optional().default(10).describe("Maximum number of payments to return"),
566
+ },
567
+ async ({ max_payments }) => {
568
+ if (!LND_HOST) {
569
+ return { content: [{ type: "text", text: "Error: LND node not configured." }] };
570
+ }
571
+ try {
572
+ // Get payments (outgoing)
573
+ const payRes = await fetch(`${LND_HOST}/v1/payments?include_incomplete=false&max_payments=${max_payments}&reversed=true`, {
574
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
575
+ });
576
+ const payData = await payRes.json();
577
+
578
+ // Get invoices (incoming)
579
+ const invRes = await fetch(`${LND_HOST}/v1/invoices?num_max_invoices=${max_payments}&reversed=true`, {
580
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
581
+ });
582
+ const invData = await invRes.json();
583
+
584
+ const payments = (payData.payments || []).map(p => ({
585
+ type: "sent",
586
+ amount_sats: parseInt(p.value_sat || p.value || 0),
587
+ fee_sats: parseInt(p.fee_sat || p.fee || 0),
588
+ status: p.status,
589
+ payment_hash: p.payment_hash,
590
+ created_at: new Date(parseInt(p.creation_date) * 1000).toISOString(),
591
+ }));
592
+
593
+ const invoices = (invData.invoices || []).filter(i => i.state === "SETTLED").map(i => ({
594
+ type: "received",
595
+ amount_sats: parseInt(i.value || 0),
596
+ memo: i.memo || "",
597
+ settled_at: new Date(parseInt(i.settle_date) * 1000).toISOString(),
598
+ payment_hash: Buffer.from(i.r_hash, "base64").toString("hex"),
599
+ }));
600
+
601
+ return {
602
+ content: [{
603
+ type: "text",
604
+ text: JSON.stringify({
605
+ success: true,
606
+ payments_sent: payments,
607
+ payments_received: invoices,
608
+ total_sent: payments.reduce((s, p) => s + p.amount_sats, 0),
609
+ total_received: invoices.reduce((s, i) => s + i.amount_sats, 0),
610
+ }, null, 2),
611
+ }],
612
+ };
613
+ } catch (e) {
614
+ return { content: [{ type: "text", text: `LND error: ${e.message}` }] };
615
+ }
616
+ }
617
+ );
618
+
619
+ // Tool: Check channel balance
620
+ server.tool(
621
+ "check_channel_balance",
622
+ "Check available Lightning channel balance (outbound and inbound liquidity)",
623
+ {},
624
+ async () => {
625
+ if (!LND_HOST) {
626
+ return { content: [{ type: "text", text: "Error: LND node not configured." }] };
627
+ }
628
+ try {
629
+ const res = await fetch(`${LND_HOST}/v1/balance/channels`, {
630
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
631
+ });
632
+ const data = await res.json();
633
+
634
+ return {
635
+ content: [{
636
+ type: "text",
637
+ text: JSON.stringify({
638
+ success: true,
639
+ local_balance_sats: parseInt(data.local_balance?.sat || data.balance || 0),
640
+ remote_balance_sats: parseInt(data.remote_balance?.sat || 0),
641
+ unsettled_local_sats: parseInt(data.unsettled_local_balance?.sat || 0),
642
+ unsettled_remote_sats: parseInt(data.unsettled_remote_balance?.sat || 0),
643
+ pending_open_local_sats: parseInt(data.pending_open_local_balance?.sat || 0),
644
+ note: "local_balance = outbound (can send), remote_balance = inbound (can receive)",
645
+ }, null, 2),
646
+ }],
647
+ };
648
+ } catch (e) {
649
+ return { content: [{ type: "text", text: `LND error: ${e.message}` }] };
650
+ }
651
+ }
652
+ );
653
+
654
+ // Tool: Delete agent wallet
655
+ server.tool(
656
+ "delete_agent_wallet",
657
+ "Delete an agent wallet by name or ID (owner access required)",
658
+ {
659
+ agent: z.string().describe("Agent wallet name or ID to delete"),
660
+ },
661
+ async ({ agent }) => {
662
+ const auth = await getAuthLevel();
663
+ if (!auth.is_owner) {
664
+ return { content: [{ type: "text", text: "Error: Owner access required to delete wallets" }] };
665
+ }
666
+ const resolved = await resolveAgent(agent);
667
+ if (!resolved) {
668
+ return { content: [{ type: "text", text: `Error: Agent '${agent}' not found` }] };
669
+ }
670
+
671
+ const { status, data } = await apiCall(`/api/v1/agents/${resolved.id}`, {
672
+ method: "DELETE",
673
+ });
674
+
675
+ if (status === 200 || status === 204) {
676
+ return {
677
+ content: [{
678
+ type: "text",
679
+ text: JSON.stringify({
680
+ success: true,
681
+ deleted_agent: resolved.name,
682
+ deleted_id: resolved.id,
683
+ message: `Deleted wallet '${resolved.name}'`,
684
+ }, null, 2),
685
+ }],
686
+ };
687
+ }
688
+ return { content: [{ type: "text", text: `Error deleting agent: ${JSON.stringify(data)}` }] };
689
+ }
690
+ );
691
+
692
+ // Tool: Send to Lightning address
693
+ server.tool(
694
+ "send_to_lightning_address",
695
+ "Send sats to a Lightning address (user@domain.com) using LNURL-pay protocol",
696
+ {
697
+ address: z.string().describe("Lightning address (e.g. user@domain.com)"),
698
+ amount_sats: z.number().describe("Amount in satoshis to send"),
699
+ agent: z.string().optional().describe("Agent wallet name or ID (for spending policy enforcement)"),
700
+ comment: z.string().optional().default("").describe("Optional comment for the recipient"),
701
+ },
702
+ async ({ address, amount_sats, agent, comment }) => {
703
+ if (!LND_HOST) {
704
+ return { content: [{ type: "text", text: "Error: LND node not configured." }] };
705
+ }
706
+
707
+ // Resolve agent for spending policy
708
+ let resolvedAgent = null;
709
+ if (agent) {
710
+ resolvedAgent = await resolveAgent(agent);
711
+ if (!resolvedAgent) return { content: [{ type: "text", text: `Error: Agent '${agent}' not found` }] };
712
+ if (!resolvedAgent.enabled) return { content: [{ type: "text", text: `Error: Agent '${resolvedAgent.name}' is disabled` }] };
713
+ }
714
+ if (!resolvedAgent) {
715
+ const auth = await getAuthLevel();
716
+ if (auth.agent_id) resolvedAgent = { id: auth.agent_id, name: auth.agent_name || auth.agent_id };
717
+ }
718
+
719
+ // Check spending policy
720
+ if (resolvedAgent) {
721
+ const policy = await checkSpendingPolicy(resolvedAgent.id, amount_sats);
722
+ if (!policy.allowed) return { content: [{ type: "text", text: `Payment blocked: ${policy.reason}` }] };
723
+ }
724
+
725
+ try {
726
+ // Parse Lightning address
727
+ const parts = address.split("@");
728
+ if (parts.length !== 2) {
729
+ return { content: [{ type: "text", text: `Error: Invalid Lightning address format. Expected user@domain.com` }] };
730
+ }
731
+ const [user, domain] = parts;
732
+
733
+ // Step 1: Fetch LNURL-pay metadata
734
+ const lnurlRes = await fetch(`https://${domain}/.well-known/lnurlp/${user}`);
735
+ if (!lnurlRes.ok) {
736
+ return { content: [{ type: "text", text: `Error: Could not resolve Lightning address. ${lnurlRes.status} from ${domain}` }] };
737
+ }
738
+ const lnurlData = await lnurlRes.json();
739
+
740
+ // Validate amount
741
+ const minSats = Math.ceil((lnurlData.minSendable || 1000) / 1000);
742
+ const maxSats = Math.floor((lnurlData.maxSendable || 100000000000) / 1000);
743
+ if (amount_sats < minSats || amount_sats > maxSats) {
744
+ return { content: [{ type: "text", text: `Error: Amount must be between ${minSats} and ${maxSats} sats for this address.` }] };
745
+ }
746
+
747
+ // Step 2: Request invoice from callback
748
+ let callbackUrl = `${lnurlData.callback}${lnurlData.callback.includes("?") ? "&" : "?"}amount=${amount_sats * 1000}`;
749
+ if (comment && lnurlData.commentAllowed) {
750
+ callbackUrl += `&comment=${encodeURIComponent(comment)}`;
751
+ }
752
+ const invoiceRes = await fetch(callbackUrl);
753
+ if (!invoiceRes.ok) {
754
+ return { content: [{ type: "text", text: `Error: Failed to get invoice from ${domain}` }] };
755
+ }
756
+ const invoiceData = await invoiceRes.json();
757
+
758
+ if (!invoiceData.pr) {
759
+ return { content: [{ type: "text", text: `Error: No invoice returned from ${domain}` }] };
760
+ }
761
+
762
+ // Step 3: Pay the invoice via LND
763
+ const payRes = await fetch(`${LND_HOST}/v1/channels/transactions`, {
764
+ method: "POST",
765
+ headers: {
766
+ "Grpc-Metadata-macaroon": LND_MACAROON,
767
+ "Content-Type": "application/json",
768
+ },
769
+ body: JSON.stringify({
770
+ payment_request: invoiceData.pr,
771
+ fee_limit: { fixed: Math.max(100, Math.floor(amount_sats * 0.01)) },
772
+ }),
773
+ });
774
+ const payData = await payRes.json();
775
+
776
+ if (payRes.ok && !payData.payment_error) {
777
+ // Log transaction
778
+ if (resolvedAgent) {
779
+ await logTransaction(resolvedAgent.id, amount_sats, address, comment || `Lightning address payment to ${address}`, "completed");
780
+ }
781
+ return {
782
+ content: [{
783
+ type: "text",
784
+ text: JSON.stringify({
785
+ success: true,
786
+ recipient: address,
787
+ amount_sats: amount_sats,
788
+ fee_sats: parseInt(payData.fee || payData.fee_sat || 0),
789
+ agent: resolvedAgent?.name || null,
790
+ payment_hash: payData.payment_hash,
791
+ message: `Sent ${amount_sats} sats to ${address} ⚔`,
792
+ }, null, 2),
793
+ }],
794
+ };
795
+ }
796
+ return { content: [{ type: "text", text: `Payment failed: ${payData.payment_error || JSON.stringify(payData)}` }] };
797
+ } catch (e) {
798
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
799
+ }
800
+ }
801
+ );
802
+
803
+ // Tool: Decode Lightning invoice
804
+ server.tool(
805
+ "decode_invoice",
806
+ "Decode a Lightning invoice to inspect amount, expiry, destination before paying",
807
+ {
808
+ invoice: z.string().describe("BOLT11 Lightning invoice to decode"),
809
+ },
810
+ async ({ invoice }) => {
811
+ if (!LND_HOST) {
812
+ return { content: [{ type: "text", text: "Error: LND node not configured." }] };
813
+ }
814
+ try {
815
+ const res = await fetch(`${LND_HOST}/v1/payreq/${invoice}`, {
816
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
817
+ });
818
+ const data = await res.json();
819
+
820
+ if (!res.ok) {
821
+ return { content: [{ type: "text", text: `Error decoding invoice: ${JSON.stringify(data)}` }] };
822
+ }
823
+
824
+ const expiry = parseInt(data.expiry || 3600);
825
+ const timestamp = parseInt(data.timestamp || 0);
826
+ const expiresAt = new Date((timestamp + expiry) * 1000).toISOString();
827
+ const isExpired = Date.now() > (timestamp + expiry) * 1000;
828
+
829
+ return {
830
+ content: [{
831
+ type: "text",
832
+ text: JSON.stringify({
833
+ success: true,
834
+ amount_sats: parseInt(data.num_satoshis || 0),
835
+ destination: data.destination,
836
+ description: data.description || "",
837
+ payment_hash: data.payment_hash,
838
+ timestamp: new Date(timestamp * 1000).toISOString(),
839
+ expires_at: expiresAt,
840
+ is_expired: isExpired,
841
+ cltv_expiry: parseInt(data.cltv_expiry || 0),
842
+ num_route_hints: (data.route_hints || []).length,
843
+ }, null, 2),
844
+ }],
845
+ };
846
+ } catch (e) {
847
+ return { content: [{ type: "text", text: `LND error: ${e.message}` }] };
848
+ }
849
+ }
850
+ );
851
+
852
+ // Tool: List channels
853
+ server.tool(
854
+ "list_channels",
855
+ "List all Lightning channels with capacity, balance, and peer info",
856
+ {},
857
+ async () => {
858
+ if (!LND_HOST) {
859
+ return { content: [{ type: "text", text: "Error: LND node not configured." }] };
860
+ }
861
+ try {
862
+ const res = await fetch(`${LND_HOST}/v1/channels`, {
863
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
864
+ });
865
+ const data = await res.json();
866
+
867
+ const channels = (data.channels || []).map(ch => ({
868
+ channel_id: ch.chan_id,
869
+ remote_pubkey: ch.remote_pubkey,
870
+ capacity_sats: parseInt(ch.capacity || 0),
871
+ local_balance_sats: parseInt(ch.local_balance || 0),
872
+ remote_balance_sats: parseInt(ch.remote_balance || 0),
873
+ active: ch.active,
874
+ initiator: ch.initiator,
875
+ commit_fee_sats: parseInt(ch.commit_fee || 0),
876
+ unsettled_balance_sats: parseInt(ch.unsettled_balance || 0),
877
+ total_sent_sats: parseInt(ch.total_satoshis_sent || 0),
878
+ total_received_sats: parseInt(ch.total_satoshis_received || 0),
879
+ }));
880
+
881
+ return {
882
+ content: [{
883
+ type: "text",
884
+ text: JSON.stringify({
885
+ success: true,
886
+ num_channels: channels.length,
887
+ total_capacity_sats: channels.reduce((s, c) => s + c.capacity_sats, 0),
888
+ total_local_sats: channels.reduce((s, c) => s + c.local_balance_sats, 0),
889
+ total_remote_sats: channels.reduce((s, c) => s + c.remote_balance_sats, 0),
890
+ channels: channels,
891
+ }, null, 2),
892
+ }],
893
+ };
894
+ } catch (e) {
895
+ return { content: [{ type: "text", text: `LND error: ${e.message}` }] };
896
+ }
897
+ }
898
+ );
899
+
900
+ // ---------------------------------------------------------------
901
+ // AI Self-Setup Tools
902
+ // ---------------------------------------------------------------
903
+
904
+ // Tool: Check setup status
905
+ server.tool(
906
+ "setup_check_status",
907
+ "Check current AgenticBTC configuration status. Returns what's configured, what's missing, and the recommended next step.",
908
+ {},
909
+ async () => {
910
+ const status = {
911
+ api_url_configured: !!API_URL,
912
+ api_key_configured: !!API_KEY,
913
+ lightning_host_configured: !!LND_HOST,
914
+ lightning_macaroon_configured: !!LND_MACAROON,
915
+ };
916
+
917
+ // Test API connectivity
918
+ try {
919
+ const { status: httpStatus } = await apiCall("/api/v1/admin/agents");
920
+ status.api_reachable = httpStatus === 200;
921
+ } catch {
922
+ status.api_reachable = false;
923
+ }
924
+
925
+ // Test Lightning connectivity
926
+ status.lightning_reachable = false;
927
+ if (LND_HOST && LND_MACAROON) {
928
+ try {
929
+ const res = await fetch(`${LND_HOST}/v1/getinfo`, {
930
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
931
+ });
932
+ status.lightning_reachable = res.status === 200;
933
+ } catch {}
934
+ }
935
+
936
+ // Check for agent wallets
937
+ status.has_agent_wallets = false;
938
+ if (status.api_reachable) {
939
+ try {
940
+ const { data } = await apiCall("/api/v1/admin/agents");
941
+ const agents = Array.isArray(data) ? data : (data.agents || []);
942
+ status.has_agent_wallets = agents.length > 0;
943
+ } catch {}
944
+ }
945
+
946
+ // Determine next step
947
+ if (!status.api_key_configured) {
948
+ status.recommended_next_step = "Set your API key. Sign up at https://agenticbtc.app/dashboard to get one.";
949
+ } else if (!status.api_reachable) {
950
+ status.recommended_next_step = "API key set but cannot reach the server. Check your AGENTBTC_API_URL and AGENTBTC_API_KEY.";
951
+ } else if (!status.lightning_host_configured) {
952
+ status.recommended_next_step = "Connect a Lightning node. Use setup_configure_lightning with your Voltage or Umbrel node credentials.";
953
+ } else if (!status.lightning_reachable) {
954
+ status.recommended_next_step = "Lightning node configured but unreachable. Use setup_diagnose_issues for details.";
955
+ } else if (!status.has_agent_wallets) {
956
+ status.recommended_next_step = "Create your first agent wallet. Use setup_create_first_wallet.";
957
+ } else {
958
+ status.recommended_next_step = "Everything looks good! Use setup_test_payment_capabilities to verify end-to-end payments.";
959
+ }
960
+
961
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, status }, null, 2) }] };
962
+ }
963
+ );
964
+
965
+ // Tool: Configure Lightning node
966
+ server.tool(
967
+ "setup_configure_lightning",
968
+ "Configure Lightning node connection. Accepts lndconnect URL or host + macaroon. Validates before saving.",
969
+ {
970
+ lndconnect_url: z.string().optional().describe("lndconnect URL (lndconnect://host:port?macaroon=...)"),
971
+ host: z.string().optional().describe("LND REST API host (e.g. https://your-node.t.voltageapp.io:8080)"),
972
+ macaroon: z.string().optional().describe("Admin macaroon (hex-encoded)"),
973
+ },
974
+ async ({ lndconnect_url, host, macaroon }) => {
975
+ // Parse lndconnect URL if provided
976
+ if (lndconnect_url) {
977
+ try {
978
+ const url = new URL(lndconnect_url);
979
+ host = `https://${url.hostname}:${url.port || 8080}`;
980
+ macaroon = url.searchParams.get("macaroon") || "";
981
+ } catch (e) {
982
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Failed to parse lndconnect URL: ${e.message}` }) }] };
983
+ }
984
+ }
985
+
986
+ if (!host || !macaroon) {
987
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Provide either lndconnect_url, or both host and macaroon." }) }] };
988
+ }
989
+
990
+ try {
991
+ const res = await fetch(`${host}/v1/getinfo`, {
992
+ headers: { "Grpc-Metadata-macaroon": macaroon },
993
+ });
994
+ if (res.status === 200) {
995
+ const data = await res.json();
996
+ return {
997
+ content: [{
998
+ type: "text",
999
+ text: JSON.stringify({
1000
+ success: true,
1001
+ message: "Lightning node connected successfully!",
1002
+ node_alias: data.alias || "",
1003
+ node_pubkey: data.identity_pubkey || "",
1004
+ synced: data.synced_to_chain || false,
1005
+ active_channels: data.num_active_channels || 0,
1006
+ note: "Set AGENTBTC_LND_HOST and AGENTBTC_LND_MACAROON in your Claude Desktop config to persist this.",
1007
+ }, null, 2),
1008
+ }],
1009
+ };
1010
+ }
1011
+ const body = await res.text();
1012
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Node returned HTTP ${res.status}: ${body}` }) }] };
1013
+ } catch (e) {
1014
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Connection failed: ${e.message}` }) }] };
1015
+ }
1016
+ }
1017
+ );
1018
+
1019
+ // Tool: Test payment capabilities
1020
+ server.tool(
1021
+ "setup_test_payment_capabilities",
1022
+ "Test that AgenticBTC payments work end to end. Checks API, Lightning, channels, and liquidity.",
1023
+ {},
1024
+ async () => {
1025
+ const caps = {
1026
+ api_connected: false,
1027
+ lightning_connected: false,
1028
+ can_create_invoices: false,
1029
+ can_send_payments: false,
1030
+ channels_available: false,
1031
+ outbound_liquidity_sats: 0,
1032
+ inbound_liquidity_sats: 0,
1033
+ };
1034
+
1035
+ // Test API
1036
+ try {
1037
+ const { status } = await apiCall("/api/v1/admin/agents");
1038
+ caps.api_connected = status === 200;
1039
+ } catch {}
1040
+
1041
+ // Test Lightning
1042
+ if (LND_HOST && LND_MACAROON) {
1043
+ try {
1044
+ const res = await fetch(`${LND_HOST}/v1/getinfo`, {
1045
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
1046
+ });
1047
+ caps.lightning_connected = res.status === 200;
1048
+ } catch {}
1049
+
1050
+ if (caps.lightning_connected) {
1051
+ // Test invoice creation
1052
+ try {
1053
+ const res = await fetch(`${LND_HOST}/v1/invoices`, {
1054
+ method: "POST",
1055
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON, "Content-Type": "application/json" },
1056
+ body: JSON.stringify({ value: 1, memo: "AgenticBTC setup test", expiry: "60" }),
1057
+ });
1058
+ if (res.ok) {
1059
+ const data = await res.json();
1060
+ caps.can_create_invoices = !!data.payment_request;
1061
+ }
1062
+ } catch {}
1063
+
1064
+ // Check channel balance
1065
+ try {
1066
+ const res = await fetch(`${LND_HOST}/v1/balance/channels`, {
1067
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
1068
+ });
1069
+ if (res.ok) {
1070
+ const data = await res.json();
1071
+ caps.outbound_liquidity_sats = parseInt(data.local_balance?.sat || data.balance || 0);
1072
+ caps.inbound_liquidity_sats = parseInt(data.remote_balance?.sat || 0);
1073
+ caps.channels_available = caps.outbound_liquidity_sats > 0;
1074
+ caps.can_send_payments = caps.outbound_liquidity_sats > 0;
1075
+ }
1076
+ } catch {}
1077
+ }
1078
+ }
1079
+
1080
+ caps.fully_operational = caps.api_connected && caps.can_send_payments;
1081
+ if (caps.fully_operational) {
1082
+ caps.summary = `AgenticBTC is fully operational. You can send up to ${caps.outbound_liquidity_sats} sats and receive up to ${caps.inbound_liquidity_sats} sats.`;
1083
+ } else {
1084
+ const issues = [];
1085
+ if (!caps.api_connected) issues.push("API not connected");
1086
+ if (!caps.lightning_connected) issues.push("Lightning node not connected");
1087
+ else if (!caps.can_send_payments) issues.push("No outbound liquidity (open a channel or receive funds)");
1088
+ caps.summary = `Not fully operational. Issues: ${issues.join(", ")}`;
1089
+ }
1090
+
1091
+ return { content: [{ type: "text", text: JSON.stringify({ success: true, capabilities: caps }, null, 2) }] };
1092
+ }
1093
+ );
1094
+
1095
+ // Tool: Diagnose issues
1096
+ server.tool(
1097
+ "setup_diagnose_issues",
1098
+ "Diagnose AgenticBTC configuration problems. Returns specific error messages with fix instructions.",
1099
+ {},
1100
+ async () => {
1101
+ const issues = [];
1102
+
1103
+ // Check API key
1104
+ if (!API_KEY) {
1105
+ issues.push({ component: "API Key", status: "missing", error: "No API key configured", fix: "Set AGENTBTC_API_KEY in your Claude Desktop MCP config. Get your key from https://agenticbtc.app/dashboard" });
1106
+ } else {
1107
+ try {
1108
+ const { status } = await apiCall("/api/v1/admin/agents");
1109
+ if (status === 200) {
1110
+ issues.push({ component: "API Key", status: "ok", error: null, fix: null });
1111
+ } else if (status === 401) {
1112
+ issues.push({ component: "API Key", status: "invalid", error: "API key rejected (401)", fix: "Generate a new API key from https://agenticbtc.app/dashboard" });
1113
+ } else {
1114
+ issues.push({ component: "API Key", status: "error", error: `Server returned ${status}`, fix: "AgenticBTC server may be down. Try again in a few minutes." });
1115
+ }
1116
+ } catch (e) {
1117
+ issues.push({ component: "API Server", status: "unreachable", error: e.message, fix: `Check AGENTBTC_API_URL is correct. Current: ${API_URL}` });
1118
+ }
1119
+ }
1120
+
1121
+ // Check Lightning
1122
+ if (!LND_HOST) {
1123
+ issues.push({ component: "Lightning Node", status: "not_configured", error: "No Lightning node configured", fix: "Optional. Set AGENTBTC_LND_HOST to your LND REST URL (e.g. https://your-node.t.voltageapp.io:8080)" });
1124
+ } else if (!LND_MACAROON) {
1125
+ issues.push({ component: "Lightning Macaroon", status: "missing", error: "LND host set but no macaroon", fix: "Set AGENTBTC_LND_MACAROON to your hex-encoded admin macaroon." });
1126
+ } else {
1127
+ try {
1128
+ const res = await fetch(`${LND_HOST}/v1/getinfo`, {
1129
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
1130
+ });
1131
+ if (res.status === 200) {
1132
+ const data = await res.json();
1133
+ if (!data.synced_to_chain) {
1134
+ issues.push({ component: "Lightning Node", status: "syncing", error: "Node not synced yet", fix: "Wait for blockchain sync to complete (can take hours)." });
1135
+ } else if ((data.num_active_channels || 0) === 0) {
1136
+ issues.push({ component: "Lightning Channels", status: "no_channels", error: "No active channels", fix: "Open a Lightning channel from your Voltage dashboard or Lightning Terminal." });
1137
+ } else {
1138
+ issues.push({ component: "Lightning Node", status: "ok", error: null, fix: null });
1139
+ }
1140
+ } else if (res.status === 401) {
1141
+ issues.push({ component: "Lightning Macaroon", status: "invalid", error: "Macaroon rejected (401)", fix: "Re-download macaroon from Voltage or re-export from your node." });
1142
+ } else {
1143
+ issues.push({ component: "Lightning Node", status: "error", error: `HTTP ${res.status}`, fix: "Check the host URL is correct." });
1144
+ }
1145
+ } catch (e) {
1146
+ issues.push({ component: "Lightning Node", status: "unreachable", error: e.message, fix: `Cannot connect to ${LND_HOST}. Verify the node is online.` });
1147
+ }
1148
+ }
1149
+
1150
+ const allOk = issues.every(i => i.status === "ok" || i.status === "not_configured");
1151
+ return {
1152
+ content: [{
1153
+ type: "text",
1154
+ text: JSON.stringify({
1155
+ success: true,
1156
+ healthy: allOk,
1157
+ issues,
1158
+ summary: allOk ? "All systems healthy!" : `Found ${issues.filter(i => !["ok", "not_configured"].includes(i.status)).length} issue(s) to fix.`,
1159
+ }, null, 2),
1160
+ }],
1161
+ };
1162
+ }
1163
+ );
1164
+
1165
+ // Tool: Create first wallet
1166
+ server.tool(
1167
+ "setup_create_first_wallet",
1168
+ "Create the user's first agent wallet with sensible defaults (1000 sat per-tx limit, 10000 sat daily budget).",
1169
+ {
1170
+ wallet_name: z.string().optional().default("my-first-agent").describe("Name for the agent wallet"),
1171
+ },
1172
+ async ({ wallet_name }) => {
1173
+ if (!API_KEY) {
1174
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "API key not configured. Set AGENTBTC_API_KEY first." }) }] };
1175
+ }
1176
+ const { status, data } = await apiCall("/api/v1/agents", {
1177
+ method: "POST",
1178
+ body: JSON.stringify({ name: wallet_name }),
1179
+ });
1180
+ if (status === 200 || status === 201) {
1181
+ return {
1182
+ content: [{
1183
+ type: "text",
1184
+ text: JSON.stringify({
1185
+ success: true,
1186
+ message: `Agent wallet '${wallet_name}' created!`,
1187
+ agent_id: data.id,
1188
+ agent_api_key: data.api_key,
1189
+ wallet_name,
1190
+ defaults_applied: { per_transaction_limit_sats: 1000, daily_budget_sats: 10000 },
1191
+ next_steps: [
1192
+ "Fund the wallet by receiving a Lightning payment",
1193
+ "Use 'get_agent_balance' to check the balance",
1194
+ "Use 'pay_lightning_invoice' to make your first payment",
1195
+ ],
1196
+ }, null, 2),
1197
+ }],
1198
+ };
1199
+ }
1200
+ return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Failed to create wallet: ${JSON.stringify(data)}` }) }] };
1201
+ }
1202
+ );
1203
+
1204
+ // Tool: Get onboarding prompt
1205
+ server.tool(
1206
+ "setup_get_onboarding_prompt",
1207
+ "Returns a conversational prompt the AI should use to guide the user through AgenticBTC setup step by step.",
1208
+ {},
1209
+ async () => {
1210
+ return {
1211
+ content: [{
1212
+ type: "text",
1213
+ text: JSON.stringify({
1214
+ success: true,
1215
+ onboarding_prompt: `You are helping a user set up AgenticBTC — a payment system that lets AI agents send and receive Bitcoin payments via Lightning Network.
1216
+
1217
+ Guide the user through these steps in a friendly, conversational way:
1218
+
1219
+ STEP 1: Check current status — Call setup_check_status to see what's already configured.
1220
+ STEP 2: API Key — If no API key, ask them to sign up at https://agenticbtc.app/dashboard
1221
+ STEP 3: Lightning Node (optional) — Ask if they have one. If yes, use setup_configure_lightning. If no, recommend Voltage (voltage.cloud).
1222
+ STEP 4: Create Agent Wallet — Call setup_create_first_wallet
1223
+ STEP 5: Test Everything — Call setup_test_payment_capabilities
1224
+ STEP 6: Complete — Call setup_complete_onboarding
1225
+
1226
+ Be patient, explain everything in plain language, celebrate small wins.`,
1227
+ }, null, 2),
1228
+ }],
1229
+ };
1230
+ }
1231
+ );
1232
+
1233
+ // Tool: Validate credentials
1234
+ server.tool(
1235
+ "setup_validate_credentials",
1236
+ "Validate API key and Lightning credentials without making any payments.",
1237
+ {
1238
+ api_key: z.string().optional().describe("API key to validate"),
1239
+ lnd_host: z.string().optional().describe("LND host to validate"),
1240
+ lnd_macaroon: z.string().optional().describe("LND macaroon to validate"),
1241
+ },
1242
+ async ({ api_key, lnd_host, lnd_macaroon }) => {
1243
+ const testKey = api_key || API_KEY;
1244
+ const testHost = lnd_host || LND_HOST;
1245
+ const testMac = lnd_macaroon || LND_MACAROON;
1246
+ const results = { api_key_valid: false, lightning_valid: false };
1247
+
1248
+ if (testKey) {
1249
+ try {
1250
+ const res = await fetch(`${API_URL}/api/v1/admin/agents`, {
1251
+ headers: { "X-API-Key": testKey, "Content-Type": "application/json" },
1252
+ });
1253
+ results.api_key_valid = res.status === 200;
1254
+ if (res.status === 401) results.api_key_error = "Invalid API key";
1255
+ else if (res.status !== 200) results.api_key_error = `Server returned ${res.status}`;
1256
+ } catch (e) {
1257
+ results.api_key_error = e.message;
1258
+ }
1259
+ }
1260
+
1261
+ if (testHost && testMac) {
1262
+ try {
1263
+ const res = await fetch(`${testHost}/v1/getinfo`, {
1264
+ headers: { "Grpc-Metadata-macaroon": testMac },
1265
+ });
1266
+ if (res.status === 200) {
1267
+ const data = await res.json();
1268
+ results.lightning_valid = true;
1269
+ results.node_alias = data.alias || "";
1270
+ results.node_pubkey = data.identity_pubkey || "";
1271
+ } else if (res.status === 401) {
1272
+ results.lightning_error = "Invalid macaroon";
1273
+ } else {
1274
+ results.lightning_error = `Node returned ${res.status}`;
1275
+ }
1276
+ } catch (e) {
1277
+ results.lightning_error = e.message;
1278
+ }
1279
+ }
1280
+
1281
+ results.success = true;
1282
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
1283
+ }
1284
+ );
1285
+
1286
+ // Tool: Complete onboarding
1287
+ server.tool(
1288
+ "setup_complete_onboarding",
1289
+ "Mark onboarding as complete. Returns a summary of everything that was configured.",
1290
+ {},
1291
+ async () => {
1292
+ const summary = {
1293
+ api_url: API_URL,
1294
+ api_key_set: !!API_KEY,
1295
+ lightning_configured: !!(LND_HOST && LND_MACAROON),
1296
+ };
1297
+
1298
+ // Get node info
1299
+ if (LND_HOST && LND_MACAROON) {
1300
+ try {
1301
+ const res = await fetch(`${LND_HOST}/v1/getinfo`, {
1302
+ headers: { "Grpc-Metadata-macaroon": LND_MACAROON },
1303
+ });
1304
+ if (res.ok) {
1305
+ const data = await res.json();
1306
+ summary.node_alias = data.alias || "";
1307
+ summary.node_pubkey = data.identity_pubkey || "";
1308
+ summary.active_channels = data.num_active_channels || 0;
1309
+ }
1310
+ } catch {}
1311
+ }
1312
+
1313
+ // Get wallet count
1314
+ if (API_KEY) {
1315
+ try {
1316
+ const { status, data } = await apiCall("/api/v1/admin/agents");
1317
+ if (status === 200) {
1318
+ const agents = Array.isArray(data) ? data : (data.agents || []);
1319
+ summary.agent_wallets = agents.length;
1320
+ }
1321
+ } catch {}
1322
+ }
1323
+
1324
+ summary.onboarding_complete = true;
1325
+ summary.what_you_can_do = [
1326
+ "Create and manage agent wallets",
1327
+ "Send and receive Lightning payments",
1328
+ "Access L402-protected APIs",
1329
+ "Set spending limits and budgets",
1330
+ "Monitor transactions and balances",
1331
+ ];
1332
+ if (summary.lightning_configured) {
1333
+ summary.what_you_can_do.push("Route payments through multiple rails with auto-failover");
1334
+ }
1335
+
1336
+ return {
1337
+ content: [{
1338
+ type: "text",
1339
+ text: JSON.stringify({
1340
+ success: true,
1341
+ message: "Onboarding complete! AgenticBTC is ready to use.",
1342
+ summary,
1343
+ }, null, 2),
1344
+ }],
1345
+ };
1346
+ }
1347
+ );
1348
+
322
1349
  // Start server
323
1350
  const transport = new StdioServerTransport();
324
1351
  await server.connect(transport);