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.
- package/bin/agentbtc.js +41 -46
- package/package.json +2 -2
- 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("š
|
|
53
|
+
console.log("š AgenticBTC Setup\n");
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
const
|
|
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
|
|
95
|
+
// Add AgenticBTC server config
|
|
99
96
|
const serverConfig = {
|
|
100
97
|
command: "npx",
|
|
101
|
-
args: ["
|
|
98
|
+
args: ["agenticbtc-mcp", "server"],
|
|
102
99
|
env: {
|
|
103
|
-
|
|
104
|
-
|
|
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.
|
|
107
|
+
serverConfig.env.AGENTICBTC_LND_HOST = lndHost;
|
|
111
108
|
}
|
|
112
109
|
if (lndMacaroon) {
|
|
113
|
-
serverConfig.env.
|
|
110
|
+
serverConfig.env.AGENTICBTC_LND_MACAROON = lndMacaroon;
|
|
114
111
|
}
|
|
115
112
|
|
|
116
|
-
config.mcpServers.
|
|
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
|
|
141
|
-
console.log("š§ Test with:
|
|
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 || "
|
|
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("š
|
|
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
|
-
|
|
171
|
-
|
|
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":
|
|
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?.
|
|
197
|
-
console.log("ā
|
|
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("ā ļø
|
|
200
|
-
console.log(" Run:
|
|
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:
|
|
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
|
-
š
|
|
213
|
+
š AgenticBTC - Intelligent Payment Routing for AI Agents
|
|
219
214
|
|
|
220
215
|
Usage:
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
229
|
-
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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 '
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
83
|
-
if (
|
|
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:
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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);
|