bsv-mcp 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -60
- package/index.ts +1 -1
- package/package.json +1 -1
- package/tools/a2b/call.ts +32 -0
- package/tools/a2b/discover.ts +30 -0
- package/tools/bsv/explore.ts +113 -5
- package/tools/index.ts +8 -0
- package/tools/wallet/a2bPublish.ts +150 -0
- package/tools/wallet/tools.ts +12 -0
- package/tools/wallet/transferOrdToken/index.ts +103 -0
package/README.md
CHANGED
|
@@ -48,22 +48,23 @@ To use the BSV MCP server with [Cursor](https://cursor.sh/):
|
|
|
48
48
|
5. Enter the following configuration in JSON format:
|
|
49
49
|
|
|
50
50
|
```json
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"Bitcoin SV": {
|
|
54
|
+
"command": "bunx",
|
|
55
|
+
"args": [
|
|
56
|
+
"bsv-mcp@latest"
|
|
57
|
+
],
|
|
58
|
+
"env": {
|
|
59
|
+
"PRIVATE_KEY_WIF": "<your_private_key_wif>"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
60
63
|
}
|
|
61
|
-
}
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
6. Replace `<your_private_key_wif>` with your actual private key WIF (keep this secure!)
|
|
65
|
-
7.
|
|
66
|
-
8. Click "Save"
|
|
67
|
+
7. Click "Save"
|
|
67
68
|
|
|
68
69
|
The BSV tools will now be available to Cursor's AI assistant under the "Bitcoin SV" namespace.
|
|
69
70
|
|
|
@@ -76,59 +77,30 @@ To connect this server to Claude for Desktop:
|
|
|
76
77
|
3. Open your Claude for Desktop configuration file:
|
|
77
78
|
```bash
|
|
78
79
|
# macOS/Linux
|
|
79
|
-
code ~/Library/Application
|
|
80
|
+
code ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
80
81
|
|
|
81
82
|
# Windows
|
|
82
83
|
code %APPDATA%\Claude\claude_desktop_config.json
|
|
83
84
|
```
|
|
84
85
|
4. Add the BSV MCP server to your configuration (create the file if it doesn't exist):
|
|
85
86
|
```json
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"Bitcoin SV": {
|
|
90
|
+
"command": "bunx",
|
|
91
|
+
"args": [
|
|
92
|
+
"bsv-mcp@latest"
|
|
93
|
+
],
|
|
94
|
+
"env": {
|
|
95
|
+
"PRIVATE_KEY_WIF": "<your_private_key_wif>"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
99
100
|
```
|
|
100
101
|
5. Replace `<your_private_key_wif>` with your actual private key WIF
|
|
101
|
-
6.
|
|
102
|
-
7.
|
|
103
|
-
8. The BSV tools will appear when you click the tools icon (hammer) in Claude for Desktop
|
|
104
|
-
|
|
105
|
-
### Generic MCP Client Integration
|
|
106
|
-
|
|
107
|
-
For other MCP clients that support JSON configuration:
|
|
108
|
-
|
|
109
|
-
```json
|
|
110
|
-
{
|
|
111
|
-
"Bitcoin SV": {
|
|
112
|
-
"command": "env",
|
|
113
|
-
"args": [
|
|
114
|
-
"PRIVATE_KEY_WIF=<your_private_key_wif>",
|
|
115
|
-
"bun",
|
|
116
|
-
"run",
|
|
117
|
-
"<path_to_project>/index.ts"
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
If running the server directly:
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
# Set environment variable first
|
|
127
|
-
export PRIVATE_KEY_WIF=<your_private_key_wif>
|
|
128
|
-
|
|
129
|
-
# Then run the server
|
|
130
|
-
bun run index.ts
|
|
131
|
-
```
|
|
102
|
+
6. Save the file and restart Claude for Desktop
|
|
103
|
+
7. The BSV tools will appear when you click the tools icon (hammer) in Claude for Desktop
|
|
132
104
|
|
|
133
105
|
## Available Tools
|
|
134
106
|
|
|
@@ -267,6 +239,15 @@ The tool supports the following endpoint categories and specific endpoints:
|
|
|
267
239
|
| `block_by_hash` | Complete block data via hash | `blockHash` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
|
|
268
240
|
| `block_by_height` | Complete block data via height | `blockHeight` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
|
|
269
241
|
| `tag_count_by_height` | Stats on tag count for a specific block | `blockHeight` | `{"tags":{"amp":3,"bitkey":5,"metanet":12,"planaria":7,"b":120}}` |
|
|
242
|
+
| `block_headers` | Retrieves the last 10 block headers | None | `[{"hash":"000000000000...","height":826458,"version":536870912,...},...]` |
|
|
243
|
+
| `block_pages` | Retrieves pages of transaction IDs for large blocks | `blockHash`, optional: `pageNumber` | `["tx1hash","tx2hash","tx3hash",...]` |
|
|
244
|
+
|
|
245
|
+
#### Stats Data
|
|
246
|
+
| Endpoint | Description | Required Parameters | Example Response |
|
|
247
|
+
|----------|-------------|---------------------|------------------|
|
|
248
|
+
| `block_stats_by_height` | Block statistics for a specific height | `blockHeight` | `{"size":123456,"txCount":512,"outputTotal":54.12345678,"outputTotalUsd":2345.67,...}` |
|
|
249
|
+
| `block_miner_stats` | Block mining statistics for a time period | optional: `days` (default 7) | `{"blocks":{"miner1":412,"miner2":208,...},"total":1008}` |
|
|
250
|
+
| `miner_summary_stats` | Summary of mining statistics | optional: `days` (default 7) | `{"totalBlocks":1008,"totalFees":1.23456789,"totalFeesUsd":53.67,...}` |
|
|
270
251
|
|
|
271
252
|
#### Transaction Data
|
|
272
253
|
| Endpoint | Description | Required Parameters | Example Response |
|
|
@@ -274,6 +255,7 @@ The tool supports the following endpoint categories and specific endpoints:
|
|
|
274
255
|
| `tx_by_hash` | Detailed transaction data | `txHash` | `{"txid":"a1b2c3d4e5f6...","version":1,"locktime":0,"size":225,...}` |
|
|
275
256
|
| `tx_raw` | Raw transaction hex data | `txHash` | `"01000000012345abcdef..."` |
|
|
276
257
|
| `tx_receipt` | Transaction receipt | `txHash` | `{"blockHash":"000000000000...","blockHeight":800000,"confirmations":26458}` |
|
|
258
|
+
| `bulk_tx_details` | Retrieve multiple transactions in one request | `txids` (array) | `[{"txid":"a1b2c3d4e5f6...","version":1,...}, {"txid":"b2c3d4e5f6a7...","version":1,...}]` |
|
|
277
259
|
|
|
278
260
|
#### Address Data
|
|
279
261
|
| Endpoint | Description | Required Parameters | Example Response |
|
|
@@ -298,6 +280,12 @@ The `bsv_explore` tool can be used with natural language prompts like:
|
|
|
298
280
|
"Get unspent outputs for my wallet address"
|
|
299
281
|
"Check transaction details for txid a1b2c3d4e5f6..."
|
|
300
282
|
"What is the current BSV circulating supply?"
|
|
283
|
+
"Show me the latest block headers"
|
|
284
|
+
"Get transaction IDs for page 2 of a large block"
|
|
285
|
+
"Show me block statistics for height 800000"
|
|
286
|
+
"What are the mining statistics for the last 14 days?"
|
|
287
|
+
"Get a summary of mining activity over the past 30 days"
|
|
288
|
+
"Retrieve details for multiple transactions in a single query"
|
|
301
289
|
```
|
|
302
290
|
|
|
303
291
|
Under the hood, the tool accepts parameters to specify which data to retrieve:
|
|
@@ -305,9 +293,12 @@ Under the hood, the tool accepts parameters to specify which data to retrieve:
|
|
|
305
293
|
- `endpoint`: The specific WhatsOnChain endpoint to query (e.g., `chain_info`, `tx_by_hash`)
|
|
306
294
|
- `network`: The BSV network to use (`main` or `test`)
|
|
307
295
|
- Additional parameters as required by the specific endpoint:
|
|
308
|
-
- `blockHash`: For block_by_hash
|
|
309
|
-
- `blockHeight`: For block_by_height
|
|
310
|
-
- `
|
|
296
|
+
- `blockHash`: For block_by_hash and block_pages endpoints
|
|
297
|
+
- `blockHeight`: For block_by_height, tag_count_by_height, and block_stats_by_height endpoints
|
|
298
|
+
- `pageNumber`: For block_pages endpoint (pagination)
|
|
299
|
+
- `days`: For block_miner_stats and miner_summary_stats endpoints (defaults to 7)
|
|
300
|
+
- `txHash`: For transaction-related endpoints (tx_by_hash, tx_raw, tx_receipt)
|
|
301
|
+
- `txids`: For bulk_tx_details endpoint (array of transaction IDs)
|
|
311
302
|
- `address`: For address-related endpoints
|
|
312
303
|
- `limit`: Optional pagination limit for address_history
|
|
313
304
|
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// Schema for invoking another agent via A2A protocol
|
|
6
|
+
export const a2aCallArgsSchema = z.object({
|
|
7
|
+
url: z.string().url().describe("Full agent-to-agent endpoint URL"),
|
|
8
|
+
method: z.string().describe("A2A method name to invoke"),
|
|
9
|
+
params: z.record(z.any()).optional().describe("Payload parameters for the A2A call"),
|
|
10
|
+
});
|
|
11
|
+
export type A2aCallArgs = z.infer<typeof a2aCallArgsSchema>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registers the a2a_call tool for agent-to-agent HTTP/SSE calls
|
|
15
|
+
*/
|
|
16
|
+
export function registerA2aCallTool(server: McpServer) {
|
|
17
|
+
server.tool(
|
|
18
|
+
"a2a_call",
|
|
19
|
+
"Invoke a remote agent's A2A endpoint via HTTP/SSE",
|
|
20
|
+
{ args: a2aCallArgsSchema },
|
|
21
|
+
async (
|
|
22
|
+
{ args }: { args: A2aCallArgs },
|
|
23
|
+
extra: RequestHandlerExtra
|
|
24
|
+
) => {
|
|
25
|
+
// TODO: implement HTTP request logic (e.g., fetch, SSE)
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: "Not implemented" }],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// Schema for agent discovery parameters
|
|
6
|
+
export const a2bDiscoverArgsSchema = z.object({
|
|
7
|
+
query: z.string().describe("Agent name or capability to search for"),
|
|
8
|
+
});
|
|
9
|
+
export type A2bDiscoverArgs = z.infer<typeof a2bDiscoverArgsSchema>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Registers the a2b_discover tool for on-chain agent discovery
|
|
13
|
+
*/
|
|
14
|
+
export function registerA2bDiscoverTool(server: McpServer) {
|
|
15
|
+
server.tool(
|
|
16
|
+
"a2b_discover",
|
|
17
|
+
"Search on-chain agent records by name or capability",
|
|
18
|
+
{ args: a2bDiscoverArgsSchema },
|
|
19
|
+
async (
|
|
20
|
+
{ args }: { args: A2bDiscoverArgs },
|
|
21
|
+
extra: RequestHandlerExtra
|
|
22
|
+
) => {
|
|
23
|
+
// TODO: implement on-chain lookup logic
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: "Not implemented" }],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
package/tools/bsv/explore.ts
CHANGED
|
@@ -17,12 +17,20 @@ enum ExploreEndpoint {
|
|
|
17
17
|
// Block endpoints
|
|
18
18
|
BLOCK_BY_HASH = "block_by_hash",
|
|
19
19
|
BLOCK_BY_HEIGHT = "block_by_height",
|
|
20
|
+
BLOCK_HEADERS = "block_headers",
|
|
21
|
+
BLOCK_PAGES = "block_pages",
|
|
22
|
+
|
|
23
|
+
// Stats endpoints
|
|
20
24
|
TAG_COUNT_BY_HEIGHT = "tag_count_by_height",
|
|
25
|
+
BLOCK_STATS_BY_HEIGHT = "block_stats_by_height",
|
|
26
|
+
BLOCK_MINER_STATS = "block_miner_stats",
|
|
27
|
+
MINER_SUMMARY_STATS = "miner_summary_stats",
|
|
21
28
|
|
|
22
29
|
// Transaction endpoints
|
|
23
30
|
TX_BY_HASH = "tx_by_hash",
|
|
24
31
|
TX_RAW = "tx_raw",
|
|
25
32
|
TX_RECEIPT = "tx_receipt",
|
|
33
|
+
BULK_TX_DETAILS = "bulk_tx_details",
|
|
26
34
|
ADDRESS_HISTORY = "address_history",
|
|
27
35
|
ADDRESS_UTXOS = "address_utxos",
|
|
28
36
|
|
|
@@ -42,10 +50,13 @@ const exploreArgsSchema = z.object({
|
|
|
42
50
|
|
|
43
51
|
// Parameters for specific endpoints
|
|
44
52
|
blockHash: z.string().optional().describe("Block hash (required for block_by_hash endpoint)"),
|
|
45
|
-
blockHeight: z.number().optional().describe("Block height (required for block_by_height
|
|
53
|
+
blockHeight: z.number().optional().describe("Block height (required for block_by_height and block_stats_by_height endpoints)"),
|
|
46
54
|
txHash: z.string().optional().describe("Transaction hash (required for tx_by_hash, tx_raw, and tx_receipt endpoints)"),
|
|
55
|
+
txids: z.array(z.string()).optional().describe("Array of transaction IDs for bulk_tx_details endpoint"),
|
|
47
56
|
address: z.string().optional().describe("Bitcoin address (required for address_history and address_utxos endpoints)"),
|
|
48
57
|
limit: z.number().optional().describe("Limit for paginated results (optional for address_history)"),
|
|
58
|
+
pageNumber: z.number().optional().describe("Page number for block_pages endpoint (defaults to 1)"),
|
|
59
|
+
days: z.number().optional().describe("Number of days for miner stats endpoints (defaults to 7)"),
|
|
49
60
|
});
|
|
50
61
|
|
|
51
62
|
// Type for the tool arguments
|
|
@@ -67,11 +78,18 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
67
78
|
"BLOCK DATA:\n" +
|
|
68
79
|
"- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
|
|
69
80
|
"- block_by_height: Complete block data via height (requires blockHeight parameter)\n" +
|
|
70
|
-
"- tag_count_by_height: Stats on tag count for a specific block via height (requires blockHeight parameter)\n
|
|
81
|
+
"- tag_count_by_height: Stats on tag count for a specific block via height (requires blockHeight parameter)\n" +
|
|
82
|
+
"- block_headers: Retrieves the last 10 block headers\n" +
|
|
83
|
+
"- block_pages: Retrieves pages of transaction IDs for large blocks (requires blockHash and optional pageNumber)\n\n" +
|
|
84
|
+
"STATS DATA:\n" +
|
|
85
|
+
"- block_stats_by_height: Block statistics for a specific height (requires blockHeight parameter)\n" +
|
|
86
|
+
"- block_miner_stats: Block mining statistics for a time period (optional days parameter, default 7)\n" +
|
|
87
|
+
"- miner_summary_stats: Summary of mining statistics (optional days parameter, default 7)\n\n" +
|
|
71
88
|
"TRANSACTION DATA:\n" +
|
|
72
89
|
"- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
|
|
73
90
|
"- tx_raw: Raw transaction hex data (requires txHash parameter)\n" +
|
|
74
|
-
"- tx_receipt: Transaction receipt (requires txHash parameter)\n
|
|
91
|
+
"- tx_receipt: Transaction receipt (requires txHash parameter)\n" +
|
|
92
|
+
"- bulk_tx_details: Bulk transaction details (requires txids parameter as array of transaction hashes)\n\n" +
|
|
75
93
|
"ADDRESS DATA:\n" +
|
|
76
94
|
"- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
|
|
77
95
|
"- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
|
|
@@ -99,6 +117,18 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
99
117
|
if ([ExploreEndpoint.ADDRESS_HISTORY, ExploreEndpoint.ADDRESS_UTXOS].includes(params.endpoint) && !params.address) {
|
|
100
118
|
throw new Error("address is required for address endpoints");
|
|
101
119
|
}
|
|
120
|
+
|
|
121
|
+
if (params.endpoint === ExploreEndpoint.BLOCK_PAGES && !params.blockHash) {
|
|
122
|
+
throw new Error("blockHash is required for block_pages endpoint");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (params.endpoint === ExploreEndpoint.BLOCK_STATS_BY_HEIGHT && params.blockHeight === undefined) {
|
|
126
|
+
throw new Error("blockHeight is required for block_stats_by_height endpoint");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (params.endpoint === ExploreEndpoint.BULK_TX_DETAILS && (!params.txids || params.txids.length === 0)) {
|
|
130
|
+
throw new Error("txids array is required for bulk_tx_details endpoint");
|
|
131
|
+
}
|
|
102
132
|
|
|
103
133
|
// Build API URL based on the selected endpoint
|
|
104
134
|
let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
|
|
@@ -125,6 +155,27 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
125
155
|
case ExploreEndpoint.TAG_COUNT_BY_HEIGHT:
|
|
126
156
|
apiUrl += `/block/tagcount/height/${params.blockHeight}/stats`;
|
|
127
157
|
break;
|
|
158
|
+
case ExploreEndpoint.BLOCK_HEADERS:
|
|
159
|
+
apiUrl += "/block/headers";
|
|
160
|
+
break;
|
|
161
|
+
case ExploreEndpoint.BLOCK_PAGES: {
|
|
162
|
+
const pageNumber = params.pageNumber || 1;
|
|
163
|
+
apiUrl += `/block/hash/${params.blockHash}/page/${pageNumber}`;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case ExploreEndpoint.BLOCK_STATS_BY_HEIGHT:
|
|
167
|
+
apiUrl += `/block/height/${params.blockHeight}/stats`;
|
|
168
|
+
break;
|
|
169
|
+
case ExploreEndpoint.BLOCK_MINER_STATS: {
|
|
170
|
+
const days = params.days !== undefined ? params.days : 7;
|
|
171
|
+
apiUrl += `/miner/blocks/stats?days=${days}`;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case ExploreEndpoint.MINER_SUMMARY_STATS: {
|
|
175
|
+
const days = params.days !== undefined ? params.days : 7;
|
|
176
|
+
apiUrl += `/miner/summary/stats?days=${days}`;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
128
179
|
case ExploreEndpoint.TX_BY_HASH:
|
|
129
180
|
apiUrl += `/tx/hash/${params.txHash}`;
|
|
130
181
|
break;
|
|
@@ -134,6 +185,9 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
134
185
|
case ExploreEndpoint.TX_RECEIPT:
|
|
135
186
|
apiUrl += `/tx/${params.txHash}/receipt`;
|
|
136
187
|
break;
|
|
188
|
+
case ExploreEndpoint.BULK_TX_DETAILS:
|
|
189
|
+
apiUrl += "/txs";
|
|
190
|
+
break;
|
|
137
191
|
case ExploreEndpoint.ADDRESS_HISTORY:
|
|
138
192
|
apiUrl += `/address/${params.address}/history`;
|
|
139
193
|
if (params.limit !== undefined) {
|
|
@@ -150,10 +204,64 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
150
204
|
throw new Error(`Unsupported endpoint: ${params.endpoint}`);
|
|
151
205
|
}
|
|
152
206
|
|
|
153
|
-
//
|
|
207
|
+
// Special handling for bulk_tx_details which requires a POST request
|
|
208
|
+
if (params.endpoint === ExploreEndpoint.BULK_TX_DETAILS) {
|
|
209
|
+
const response = await fetch(apiUrl, {
|
|
210
|
+
method: "POST",
|
|
211
|
+
headers: {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
},
|
|
214
|
+
body: JSON.stringify({ txids: params.txids }),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
if (!response.ok) {
|
|
218
|
+
let errorMsg = `API error: ${response.status} ${response.statusText}`;
|
|
219
|
+
if (response.status === 404) {
|
|
220
|
+
errorMsg += " - One or more transaction IDs not found";
|
|
221
|
+
}
|
|
222
|
+
throw new Error(errorMsg);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const result = await response.json();
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
content: [
|
|
229
|
+
{
|
|
230
|
+
type: "text",
|
|
231
|
+
text: JSON.stringify(result, null, 2),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// For all other endpoints, use GET request
|
|
154
238
|
const response = await fetch(apiUrl);
|
|
155
239
|
if (!response.ok) {
|
|
156
|
-
|
|
240
|
+
let errorMsg = `API error: ${response.status} ${response.statusText}`;
|
|
241
|
+
// Provide helpful tips for specific error codes
|
|
242
|
+
if (response.status === 404) {
|
|
243
|
+
switch (params.endpoint) {
|
|
244
|
+
case ExploreEndpoint.BLOCK_BY_HASH:
|
|
245
|
+
errorMsg += ` - Block hash "${params.blockHash}" not found`;
|
|
246
|
+
break;
|
|
247
|
+
case ExploreEndpoint.BLOCK_BY_HEIGHT:
|
|
248
|
+
errorMsg += ` - Block at height ${params.blockHeight} not found`;
|
|
249
|
+
break;
|
|
250
|
+
case ExploreEndpoint.BLOCK_PAGES:
|
|
251
|
+
errorMsg += ` - Block hash "${params.blockHash}" not found or page ${params.pageNumber || 1} does not exist`;
|
|
252
|
+
break;
|
|
253
|
+
case ExploreEndpoint.TX_BY_HASH:
|
|
254
|
+
case ExploreEndpoint.TX_RAW:
|
|
255
|
+
case ExploreEndpoint.TX_RECEIPT:
|
|
256
|
+
errorMsg += ` - Transaction hash "${params.txHash}" not found`;
|
|
257
|
+
break;
|
|
258
|
+
case ExploreEndpoint.ADDRESS_HISTORY:
|
|
259
|
+
case ExploreEndpoint.ADDRESS_UTXOS:
|
|
260
|
+
errorMsg += ` - No data found for address "${params.address}"`;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
throw new Error(errorMsg);
|
|
157
265
|
}
|
|
158
266
|
|
|
159
267
|
const result = await response.json();
|
package/tools/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { registerBsvTools } from "./bsv";
|
|
3
3
|
import { registerOrdinalsTools } from "./ordinals";
|
|
4
4
|
import { registerUtilsTools } from "./utils";
|
|
5
|
+
import { registerA2bDiscoverTool } from "./a2b/discover";
|
|
6
|
+
import { registerA2aCallTool } from "./a2b/call";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Register all tools with the MCP server
|
|
@@ -17,5 +19,11 @@ export function registerAllTools(server: McpServer): void {
|
|
|
17
19
|
// Register utility tools
|
|
18
20
|
registerUtilsTools(server);
|
|
19
21
|
|
|
22
|
+
// Register agent-to-blockchain discovery tool
|
|
23
|
+
registerA2bDiscoverTool(server);
|
|
24
|
+
|
|
25
|
+
// Register agent-to-agent call tool
|
|
26
|
+
registerA2aCallTool(server);
|
|
27
|
+
|
|
20
28
|
// Add more tool categories as needed
|
|
21
29
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type { Wallet } from "./wallet";
|
|
5
|
+
import { createOrdinals } from "js-1sat-ord";
|
|
6
|
+
import type { Inscription, Destination, ChangeResult, PreMAP } from "js-1sat-ord";
|
|
7
|
+
import { Utils } from "@bsv/sdk";
|
|
8
|
+
const { toArray, toBase64 } = Utils;
|
|
9
|
+
|
|
10
|
+
// https://raw.githubusercontent.com/google/A2A/refs/heads/main/specification/json/a2a.json
|
|
11
|
+
|
|
12
|
+
// A2A AgentCard schema (per A2A spec)
|
|
13
|
+
const AgentCapabilitiesSchema = z.object({
|
|
14
|
+
streaming: z.boolean().default(false),
|
|
15
|
+
pushNotifications: z.boolean().default(false),
|
|
16
|
+
stateTransitionHistory: z.boolean().default(false),
|
|
17
|
+
});
|
|
18
|
+
const AgentSkillSchema = z.object({
|
|
19
|
+
id: z.string(),
|
|
20
|
+
name: z.string(),
|
|
21
|
+
description: z.string().nullable(),
|
|
22
|
+
tags: z.array(z.string()).nullable(),
|
|
23
|
+
examples: z.array(z.string()).nullable(),
|
|
24
|
+
inputModes: z.array(z.string()).nullable(),
|
|
25
|
+
outputModes: z.array(z.string()).nullable(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Provider per A2A spec
|
|
29
|
+
const AgentProviderSchema = z.object({
|
|
30
|
+
organization: z.string(),
|
|
31
|
+
url: z.string().url().nullable().default(null),
|
|
32
|
+
}).nullable().default(null);
|
|
33
|
+
|
|
34
|
+
// Authentication per A2A spec
|
|
35
|
+
const AgentAuthenticationSchema = z.object({
|
|
36
|
+
schemes: z.array(z.string()),
|
|
37
|
+
credentials: z.string().nullable().default(null),
|
|
38
|
+
}).nullable().default(null);
|
|
39
|
+
|
|
40
|
+
export const AgentCardSchema = z.object({
|
|
41
|
+
name: z.string(),
|
|
42
|
+
description: z.string().nullable().default(null),
|
|
43
|
+
url: z.string().url(),
|
|
44
|
+
provider: AgentProviderSchema,
|
|
45
|
+
version: z.string(),
|
|
46
|
+
documentationUrl: z.string().url().nullable().default(null),
|
|
47
|
+
capabilities: AgentCapabilitiesSchema,
|
|
48
|
+
authentication: AgentAuthenticationSchema,
|
|
49
|
+
defaultInputModes: z.array(z.string()).default(["text"]),
|
|
50
|
+
defaultOutputModes: z.array(z.string()).default(["text"]),
|
|
51
|
+
skills: z.array(AgentSkillSchema),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Schema for on-chain agent publish parameters
|
|
55
|
+
export const a2bPublishArgsSchema = z.object({
|
|
56
|
+
agentUrl: z.string().url().describe("Agent base URL (e.g. https://example.com)"),
|
|
57
|
+
agentName: z.string().describe("Human-friendly agent name"),
|
|
58
|
+
description: z.string().nullable().optional().describe("Optional agent description"),
|
|
59
|
+
providerOrganization: z.string().optional().describe("Optional provider organization name"),
|
|
60
|
+
providerUrl: z.string().url().optional().describe("Optional provider URL"),
|
|
61
|
+
version: z.string().optional().describe("Optional agent version"),
|
|
62
|
+
documentationUrl: z.string().url().nullable().optional().describe("Optional documentation URL"),
|
|
63
|
+
streaming: z.boolean().default(false).describe("Supports SSE (tasks/sendSubscribe)"),
|
|
64
|
+
pushNotifications: z.boolean().default(false).describe("Supports push notifications"),
|
|
65
|
+
stateTransitionHistory: z.boolean().default(false).describe("Supports state transition history"),
|
|
66
|
+
defaultInputModes: z.array(z.string()).default(["text"]).describe("Default input modes"),
|
|
67
|
+
defaultOutputModes: z.array(z.string()).default(["text"]).describe("Default output modes"),
|
|
68
|
+
skills: z.array(AgentSkillSchema).optional().default([]).describe("List of agent skills"),
|
|
69
|
+
destinationAddress: z.string().optional().describe("Optional target address for inscription"),
|
|
70
|
+
});
|
|
71
|
+
export type A2bPublishArgs = z.infer<typeof a2bPublishArgsSchema>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Registers the wallet_a2bPublish tool for publishing an agent record on-chain
|
|
75
|
+
*/
|
|
76
|
+
export function registerA2bPublishTool(server: McpServer, wallet: Wallet) {
|
|
77
|
+
server.tool(
|
|
78
|
+
"wallet_a2bPublish",
|
|
79
|
+
"Publish an agent.json record on-chain via Ordinal inscription",
|
|
80
|
+
{ args: a2bPublishArgsSchema },
|
|
81
|
+
async (
|
|
82
|
+
{ args }: { args: A2bPublishArgs },
|
|
83
|
+
extra: RequestHandlerExtra
|
|
84
|
+
) => {
|
|
85
|
+
try {
|
|
86
|
+
const paymentPk = wallet.getPrivateKey();
|
|
87
|
+
if (!paymentPk) throw new Error("No private key available");
|
|
88
|
+
const { paymentUtxos } = await wallet.getUtxos();
|
|
89
|
+
if (!paymentUtxos?.length) throw new Error("No payment UTXOs available to fund inscription");
|
|
90
|
+
|
|
91
|
+
// Assemble AgentCard with defaults and user overrides
|
|
92
|
+
const agentCard = {
|
|
93
|
+
name: args.agentName,
|
|
94
|
+
description: args.description ?? null,
|
|
95
|
+
url: args.agentUrl,
|
|
96
|
+
provider:
|
|
97
|
+
args.providerOrganization && args.providerUrl
|
|
98
|
+
? { organization: args.providerOrganization, url: args.providerUrl }
|
|
99
|
+
: null,
|
|
100
|
+
version: args.version ?? "1.0.0",
|
|
101
|
+
documentationUrl: args.documentationUrl ?? null,
|
|
102
|
+
capabilities: {
|
|
103
|
+
streaming: args.streaming,
|
|
104
|
+
pushNotifications: args.pushNotifications,
|
|
105
|
+
stateTransitionHistory: args.stateTransitionHistory,
|
|
106
|
+
},
|
|
107
|
+
authentication: null,
|
|
108
|
+
defaultInputModes: args.defaultInputModes,
|
|
109
|
+
defaultOutputModes: args.defaultOutputModes,
|
|
110
|
+
skills: args.skills,
|
|
111
|
+
};
|
|
112
|
+
// Validate compliance
|
|
113
|
+
AgentCardSchema.parse(agentCard);
|
|
114
|
+
const fileContent = JSON.stringify(agentCard, null, 2);
|
|
115
|
+
// Base64 payload for inscription
|
|
116
|
+
const dataB64 = toBase64(toArray(fileContent));
|
|
117
|
+
const inscription: Inscription = { dataB64, contentType: "application/json" };
|
|
118
|
+
// Destination for the ordinal
|
|
119
|
+
const walletAddress = paymentPk.toAddress().toString();
|
|
120
|
+
const targetAddress = args.destinationAddress ?? walletAddress;
|
|
121
|
+
const destinations: Destination[] = [{ address: targetAddress, inscription }];
|
|
122
|
+
// Default MAP metadata: file path, content type, encoding
|
|
123
|
+
const metaData: PreMAP = { app: 'bsv-mcp', type: 'agent' };
|
|
124
|
+
|
|
125
|
+
// Inscribe the ordinal on-chain via js-1sat-ord
|
|
126
|
+
const result = await createOrdinals({ utxos: paymentUtxos, destinations, paymentPk, changeAddress: walletAddress, metaData });
|
|
127
|
+
const changeResult = result as ChangeResult;
|
|
128
|
+
await changeResult.tx.broadcast();
|
|
129
|
+
// Refresh UTXOs
|
|
130
|
+
try { await wallet.refreshUtxos(); } catch {}
|
|
131
|
+
// Return transaction details
|
|
132
|
+
return {
|
|
133
|
+
content: [{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: JSON.stringify({
|
|
136
|
+
txid: changeResult.tx.id("hex"),
|
|
137
|
+
spentOutpoints: changeResult.spentOutpoints,
|
|
138
|
+
payChange: changeResult.payChange,
|
|
139
|
+
inscriptionAddress: targetAddress,
|
|
140
|
+
agentCard,
|
|
141
|
+
}),
|
|
142
|
+
}],
|
|
143
|
+
};
|
|
144
|
+
} catch (err: unknown) {
|
|
145
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
146
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -39,6 +39,10 @@ import type { createOrdinalsArgsSchema } from "./createOrdinals";
|
|
|
39
39
|
import { registerGetAddressTool } from "./getAddress";
|
|
40
40
|
import { registerPurchaseListingTool } from "./purchaseListing";
|
|
41
41
|
import { registerSendToAddressTool } from "./sendToAddress";
|
|
42
|
+
import { registerTransferOrdTokenTool } from "./transferOrdToken";
|
|
43
|
+
import { registerA2bPublishTool } from "./a2bPublish";
|
|
44
|
+
import type { transferOrdTokenArgsSchema } from "./transferOrdToken";
|
|
45
|
+
import type { a2bPublishArgsSchema } from "./a2bPublish";
|
|
42
46
|
import { Utils, type WalletProtocol } from "@bsv/sdk";
|
|
43
47
|
|
|
44
48
|
// Define mapping from tool names to argument schemas
|
|
@@ -70,6 +74,8 @@ type ToolArgSchemas = {
|
|
|
70
74
|
wallet_getAddress: typeof getAddressArgsSchema;
|
|
71
75
|
wallet_sendToAddress: typeof sendToAddressArgsSchema;
|
|
72
76
|
wallet_purchaseListing: typeof purchaseListingArgsSchema;
|
|
77
|
+
wallet_transferOrdToken: typeof transferOrdTokenArgsSchema;
|
|
78
|
+
wallet_a2bPublish: typeof a2bPublishArgsSchema;
|
|
73
79
|
wallet_createOrdinals: typeof createOrdinalsArgsSchema;
|
|
74
80
|
};
|
|
75
81
|
|
|
@@ -111,6 +117,12 @@ export function registerWalletTools(
|
|
|
111
117
|
// Register the wallet_purchaseListing tool
|
|
112
118
|
registerPurchaseListingTool(server, wallet);
|
|
113
119
|
|
|
120
|
+
// Register the wallet_transferOrdToken tool
|
|
121
|
+
registerTransferOrdTokenTool(server, wallet);
|
|
122
|
+
|
|
123
|
+
// Register the wallet_a2bPublish tool
|
|
124
|
+
registerA2bPublishTool(server, wallet);
|
|
125
|
+
|
|
114
126
|
// Register only the minimal public-facing tools
|
|
115
127
|
// wallet_createAction, wallet_signAction and wallet_getHeight have been removed
|
|
116
128
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
transferOrdTokens,
|
|
6
|
+
TokenType,
|
|
7
|
+
TokenInputMode,
|
|
8
|
+
TokenSelectionStrategy,
|
|
9
|
+
selectTokenUtxos,
|
|
10
|
+
type TransferOrdTokensConfig,
|
|
11
|
+
type Utxo,
|
|
12
|
+
type TokenUtxo,
|
|
13
|
+
type Payment,
|
|
14
|
+
type Distribution,
|
|
15
|
+
type TokenChangeResult,
|
|
16
|
+
} from "js-1sat-ord";
|
|
17
|
+
import type { Wallet } from "../wallet";
|
|
18
|
+
|
|
19
|
+
// Schema for BSV-20/BSV-21 token transfer arguments
|
|
20
|
+
export const transferOrdTokenArgsSchema = z.object({
|
|
21
|
+
protocol: z.enum(["bsv-20", "bsv-21"]),
|
|
22
|
+
tokenID: z.string(),
|
|
23
|
+
sendAmount: z.number(),
|
|
24
|
+
paymentUtxos: z.array(
|
|
25
|
+
z.object({ txid: z.string(), vout: z.number(), satoshis: z.number(), script: z.string() })
|
|
26
|
+
),
|
|
27
|
+
tokenUtxos: z.array(
|
|
28
|
+
z.object({ txid: z.string(), vout: z.number(), satoshis: z.literal(1), script: z.string(), amt: z.string(), id: z.string() })
|
|
29
|
+
),
|
|
30
|
+
distributions: z.array(z.object({ address: z.string(), tokens: z.number() })),
|
|
31
|
+
decimals: z.number(),
|
|
32
|
+
additionalPayments: z.array(z.object({ to: z.string(), amount: z.number() })).optional(),
|
|
33
|
+
});
|
|
34
|
+
export type TransferOrdTokenArgs = z.infer<typeof transferOrdTokenArgsSchema>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Register the wallet_transferOrdToken tool for transferring BSV tokens.
|
|
38
|
+
*/
|
|
39
|
+
export function registerTransferOrdTokenTool(server: McpServer, wallet: Wallet) {
|
|
40
|
+
server.tool(
|
|
41
|
+
"wallet_transferOrdToken",
|
|
42
|
+
"Transfers BSV-20 or BSV-21 tokens from your wallet via js-1sat-ord transferOrdTokens.",
|
|
43
|
+
{ args: transferOrdTokenArgsSchema },
|
|
44
|
+
async (
|
|
45
|
+
{ args }: { args: TransferOrdTokenArgs },
|
|
46
|
+
extra: RequestHandlerExtra
|
|
47
|
+
) => {
|
|
48
|
+
try {
|
|
49
|
+
// fetch keys
|
|
50
|
+
const paymentPk = wallet.getPrivateKey();
|
|
51
|
+
if (!paymentPk) throw new Error("No private key available");
|
|
52
|
+
const ordPk = paymentPk;
|
|
53
|
+
const changeAddress = paymentPk.toAddress().toString();
|
|
54
|
+
const ordAddress = changeAddress;
|
|
55
|
+
|
|
56
|
+
// select token UTXOs
|
|
57
|
+
const { selectedUtxos: inputTokens } = selectTokenUtxos(
|
|
58
|
+
args.tokenUtxos as TokenUtxo[],
|
|
59
|
+
args.sendAmount,
|
|
60
|
+
args.decimals,
|
|
61
|
+
{ inputStrategy: TokenSelectionStrategy.SmallestFirst, outputStrategy: TokenSelectionStrategy.LargestFirst }
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// build config
|
|
65
|
+
const config: TransferOrdTokensConfig = {
|
|
66
|
+
protocol: args.protocol === "bsv-20" ? TokenType.BSV20 : TokenType.BSV21,
|
|
67
|
+
tokenID: args.tokenID,
|
|
68
|
+
utxos: args.paymentUtxos as Utxo[],
|
|
69
|
+
inputTokens,
|
|
70
|
+
distributions: args.distributions as Distribution[],
|
|
71
|
+
tokenChangeAddress: ordAddress,
|
|
72
|
+
changeAddress,
|
|
73
|
+
paymentPk,
|
|
74
|
+
ordPk,
|
|
75
|
+
additionalPayments: args.additionalPayments as Payment[] || [],
|
|
76
|
+
decimals: args.decimals,
|
|
77
|
+
inputMode: TokenInputMode.Needed,
|
|
78
|
+
splitConfig: { outputs: inputTokens.length === 1 ? 2 : 1, threshold: args.sendAmount },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// execute transfer
|
|
82
|
+
const result: TokenChangeResult = await transferOrdTokens(config);
|
|
83
|
+
await result.tx.broadcast();
|
|
84
|
+
|
|
85
|
+
// refresh UTXOs
|
|
86
|
+
try { await wallet.refreshUtxos(); } catch {}
|
|
87
|
+
|
|
88
|
+
// respond
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
91
|
+
txid: result.tx.id("hex"),
|
|
92
|
+
spentOutpoints: result.spentOutpoints,
|
|
93
|
+
payChange: result.payChange,
|
|
94
|
+
tokenChange: result.tokenChange,
|
|
95
|
+
}) }]
|
|
96
|
+
};
|
|
97
|
+
} catch (err: unknown) {
|
|
98
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|