bsv-mcp 0.0.21 → 0.0.23

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.
@@ -1,5 +1,5 @@
1
- import { z } from "zod";
2
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
3
 
4
4
  // Base URL for WhatsOnChain API
5
5
  const WOC_API_BASE_URL = "https://api.whatsonchain.com/v1/bsv";
@@ -8,55 +8,90 @@ const WOC_API_BASE_URL = "https://api.whatsonchain.com/v1/bsv";
8
8
  * WhatsOnChain API endpoints available for exploration
9
9
  */
10
10
  enum ExploreEndpoint {
11
- // Chain endpoints
12
- CHAIN_INFO = "chain_info",
13
- CHAIN_TIPS = "chain_tips",
14
- CIRCULATING_SUPPLY = "circulating_supply",
15
- PEER_INFO = "peer_info",
16
-
17
- // Block endpoints
18
- BLOCK_BY_HASH = "block_by_hash",
19
- BLOCK_BY_HEIGHT = "block_by_height",
20
- BLOCK_HEADERS = "block_headers",
21
- BLOCK_PAGES = "block_pages",
22
-
23
- // Stats endpoints
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",
28
-
29
- // Transaction endpoints
30
- TX_BY_HASH = "tx_by_hash",
31
- TX_RAW = "tx_raw",
32
- TX_RECEIPT = "tx_receipt",
33
- BULK_TX_DETAILS = "bulk_tx_details",
34
- ADDRESS_HISTORY = "address_history",
35
- ADDRESS_UTXOS = "address_utxos",
36
-
37
- // Health endpoint
38
- HEALTH = "health",
11
+ // Chain endpoints
12
+ CHAIN_INFO = "chain_info",
13
+ CHAIN_TIPS = "chain_tips",
14
+ CIRCULATING_SUPPLY = "circulating_supply",
15
+ PEER_INFO = "peer_info",
16
+
17
+ // Block endpoints
18
+ BLOCK_BY_HASH = "block_by_hash",
19
+ BLOCK_BY_HEIGHT = "block_by_height",
20
+ BLOCK_HEADERS = "block_headers",
21
+ BLOCK_PAGES = "block_pages",
22
+
23
+ // Stats endpoints
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",
28
+
29
+ // Transaction endpoints
30
+ TX_BY_HASH = "tx_by_hash",
31
+ TX_RAW = "tx_raw",
32
+ TX_RECEIPT = "tx_receipt",
33
+ BULK_TX_DETAILS = "bulk_tx_details",
34
+ ADDRESS_HISTORY = "address_history",
35
+ ADDRESS_UTXOS = "address_utxos",
36
+
37
+ // Health endpoint
38
+ HEALTH = "health",
39
39
  }
40
40
 
41
41
  enum Network {
42
- MAIN = "main",
43
- TEST = "test",
42
+ MAIN = "main",
43
+ TEST = "test",
44
44
  }
45
45
 
46
46
  // Schema for the bsv_explore tool arguments
47
47
  const exploreArgsSchema = z.object({
48
- endpoint: z.nativeEnum(ExploreEndpoint).describe("WhatsOnChain API endpoint to call"),
49
- network: z.nativeEnum(Network).default(Network.MAIN).describe("Network to use (main or test)"),
50
-
51
- // Parameters for specific endpoints
52
- blockHash: z.string().optional().describe("Block hash (required for block_by_hash endpoint)"),
53
- blockHeight: z.number().optional().describe("Block height (required for block_by_height and block_stats_by_height endpoints)"),
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"),
56
- address: z.string().optional().describe("Bitcoin address (required for address_history and address_utxos endpoints)"),
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)"),
48
+ endpoint: z
49
+ .nativeEnum(ExploreEndpoint)
50
+ .describe("WhatsOnChain API endpoint to call"),
51
+ network: z
52
+ .nativeEnum(Network)
53
+ .default(Network.MAIN)
54
+ .describe("Network to use (main or test)"),
55
+
56
+ // Parameters for specific endpoints
57
+ blockHash: z
58
+ .string()
59
+ .optional()
60
+ .describe("Block hash (required for block_by_hash endpoint)"),
61
+ blockHeight: z
62
+ .number()
63
+ .optional()
64
+ .describe(
65
+ "Block height (required for block_by_height and block_stats_by_height endpoints)",
66
+ ),
67
+ txHash: z
68
+ .string()
69
+ .optional()
70
+ .describe(
71
+ "Transaction hash (required for tx_by_hash, tx_raw, and tx_receipt endpoints)",
72
+ ),
73
+ txids: z
74
+ .array(z.string())
75
+ .optional()
76
+ .describe("Array of transaction IDs for bulk_tx_details endpoint"),
77
+ address: z
78
+ .string()
79
+ .optional()
80
+ .describe(
81
+ "Bitcoin address (required for address_history and address_utxos endpoints)",
82
+ ),
83
+ limit: z
84
+ .number()
85
+ .optional()
86
+ .describe("Limit for paginated results (optional for address_history)"),
87
+ pageNumber: z
88
+ .number()
89
+ .optional()
90
+ .describe("Page number for block_pages endpoint (defaults to 1)"),
91
+ days: z
92
+ .number()
93
+ .optional()
94
+ .describe("Number of days for miner stats endpoints (defaults to 7)"),
60
95
  });
61
96
 
62
97
  // Type for the tool arguments
@@ -67,224 +102,259 @@ type ExploreArgs = z.infer<typeof exploreArgsSchema>;
67
102
  * @param server The MCP server instance
68
103
  */
69
104
  export function registerExploreTool(server: McpServer): void {
70
- server.tool(
71
- "bsv_explore",
72
- "Explore Bitcoin SV blockchain data using the WhatsOnChain API. Access multiple data types:\n\n" +
73
- "CHAIN DATA:\n" +
74
- "- chain_info: Network stats, difficulty, and chain work\n" +
75
- "- chain_tips: Current chain tips including heights and states\n" +
76
- "- circulating_supply: Current BSV circulating supply\n" +
77
- "- peer_info: Connected peer statistics\n\n" +
78
- "BLOCK DATA:\n" +
79
- "- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
80
- "- block_by_height: Complete block data 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" +
88
- "TRANSACTION DATA:\n" +
89
- "- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
90
- "- tx_raw: Raw transaction hex data (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" +
93
- "ADDRESS DATA:\n" +
94
- "- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
95
- "- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
96
- "NETWORK:\n" +
97
- "- health: API health check\n\n" +
98
- "Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
99
- { args: exploreArgsSchema },
100
- async ({ args }) => {
101
- try {
102
- const params = exploreArgsSchema.parse(args);
103
-
104
- // Validate required parameters for specific endpoints
105
- if (params.endpoint === ExploreEndpoint.BLOCK_BY_HASH && !params.blockHash) {
106
- throw new Error("blockHash is required for block_by_hash endpoint");
107
- }
108
-
109
- if ((params.endpoint === ExploreEndpoint.BLOCK_BY_HEIGHT || params.endpoint === ExploreEndpoint.TAG_COUNT_BY_HEIGHT) && params.blockHeight === undefined) {
110
- throw new Error("blockHeight is required for block_by_height and tag_count_by_height endpoints");
111
- }
105
+ server.tool(
106
+ "bsv_explore",
107
+ "Explore Bitcoin SV blockchain data using the WhatsOnChain API. Access multiple data types:\n\n" +
108
+ "CHAIN DATA:\n" +
109
+ "- chain_info: Network stats, difficulty, and chain work\n" +
110
+ "- chain_tips: Current chain tips including heights and states\n" +
111
+ "- circulating_supply: Current BSV circulating supply\n" +
112
+ "- peer_info: Connected peer statistics\n\n" +
113
+ "BLOCK DATA:\n" +
114
+ "- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
115
+ "- block_by_height: Complete block data via height (requires blockHeight parameter)\n" +
116
+ "- tag_count_by_height: Stats on tag count for a specific block via height (requires blockHeight parameter)\n" +
117
+ "- block_headers: Retrieves the last 10 block headers\n" +
118
+ "- block_pages: Retrieves pages of transaction IDs for large blocks (requires blockHash and optional pageNumber)\n\n" +
119
+ "STATS DATA:\n" +
120
+ "- block_stats_by_height: Block statistics for a specific height (requires blockHeight parameter)\n" +
121
+ "- block_miner_stats: Block mining statistics for a time period (optional days parameter, default 7)\n" +
122
+ "- miner_summary_stats: Summary of mining statistics (optional days parameter, default 7)\n\n" +
123
+ "TRANSACTION DATA:\n" +
124
+ "- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
125
+ "- tx_raw: Raw transaction hex data (requires txHash parameter)\n" +
126
+ "- tx_receipt: Transaction receipt (requires txHash parameter)\n" +
127
+ "- bulk_tx_details: Bulk transaction details (requires txids parameter as array of transaction hashes)\n\n" +
128
+ "ADDRESS DATA:\n" +
129
+ "- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
130
+ "- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
131
+ "NETWORK:\n" +
132
+ "- health: API health check\n\n" +
133
+ "Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
134
+ { args: exploreArgsSchema },
135
+ async ({ args }) => {
136
+ try {
137
+ const params = exploreArgsSchema.parse(args);
138
+
139
+ // Validate required parameters for specific endpoints
140
+ if (
141
+ params.endpoint === ExploreEndpoint.BLOCK_BY_HASH &&
142
+ !params.blockHash
143
+ ) {
144
+ throw new Error("blockHash is required for block_by_hash endpoint");
145
+ }
146
+
147
+ if (
148
+ (params.endpoint === ExploreEndpoint.BLOCK_BY_HEIGHT ||
149
+ params.endpoint === ExploreEndpoint.TAG_COUNT_BY_HEIGHT) &&
150
+ params.blockHeight === undefined
151
+ ) {
152
+ throw new Error(
153
+ "blockHeight is required for block_by_height and tag_count_by_height endpoints",
154
+ );
155
+ }
156
+
157
+ if (
158
+ [
159
+ ExploreEndpoint.TX_BY_HASH,
160
+ ExploreEndpoint.TX_RAW,
161
+ ExploreEndpoint.TX_RECEIPT,
162
+ ].includes(params.endpoint) &&
163
+ !params.txHash
164
+ ) {
165
+ throw new Error("txHash is required for transaction endpoints");
166
+ }
167
+
168
+ if (
169
+ [
170
+ ExploreEndpoint.ADDRESS_HISTORY,
171
+ ExploreEndpoint.ADDRESS_UTXOS,
172
+ ].includes(params.endpoint) &&
173
+ !params.address
174
+ ) {
175
+ throw new Error("address is required for address endpoints");
176
+ }
112
177
 
113
- if ([ExploreEndpoint.TX_BY_HASH, ExploreEndpoint.TX_RAW, ExploreEndpoint.TX_RECEIPT].includes(params.endpoint) && !params.txHash) {
114
- throw new Error("txHash is required for transaction endpoints");
115
- }
178
+ if (
179
+ params.endpoint === ExploreEndpoint.BLOCK_PAGES &&
180
+ !params.blockHash
181
+ ) {
182
+ throw new Error("blockHash is required for block_pages endpoint");
183
+ }
116
184
 
117
- if ([ExploreEndpoint.ADDRESS_HISTORY, ExploreEndpoint.ADDRESS_UTXOS].includes(params.endpoint) && !params.address) {
118
- throw new Error("address is required for address endpoints");
119
- }
185
+ if (
186
+ params.endpoint === ExploreEndpoint.BLOCK_STATS_BY_HEIGHT &&
187
+ params.blockHeight === undefined
188
+ ) {
189
+ throw new Error(
190
+ "blockHeight is required for block_stats_by_height endpoint",
191
+ );
192
+ }
120
193
 
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
- }
132
-
133
- // Build API URL based on the selected endpoint
134
- let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
135
-
136
- switch (params.endpoint) {
137
- case ExploreEndpoint.CHAIN_INFO:
138
- apiUrl += "/chain/info";
139
- break;
140
- case ExploreEndpoint.CHAIN_TIPS:
141
- apiUrl += "/chain/tips";
142
- break;
143
- case ExploreEndpoint.CIRCULATING_SUPPLY:
144
- apiUrl += "/circulatingsupply";
145
- break;
146
- case ExploreEndpoint.PEER_INFO:
147
- apiUrl += "/peer/info";
148
- break;
149
- case ExploreEndpoint.BLOCK_BY_HASH:
150
- apiUrl += `/block/hash/${params.blockHash}`;
151
- break;
152
- case ExploreEndpoint.BLOCK_BY_HEIGHT:
153
- apiUrl += `/block/height/${params.blockHeight}`;
154
- break;
155
- case ExploreEndpoint.TAG_COUNT_BY_HEIGHT:
156
- apiUrl += `/block/tagcount/height/${params.blockHeight}/stats`;
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
- }
179
- case ExploreEndpoint.TX_BY_HASH:
180
- apiUrl += `/tx/hash/${params.txHash}`;
181
- break;
182
- case ExploreEndpoint.TX_RAW:
183
- apiUrl += `/tx/${params.txHash}/hex`;
184
- break;
185
- case ExploreEndpoint.TX_RECEIPT:
186
- apiUrl += `/tx/${params.txHash}/receipt`;
187
- break;
188
- case ExploreEndpoint.BULK_TX_DETAILS:
189
- apiUrl += "/txs";
190
- break;
191
- case ExploreEndpoint.ADDRESS_HISTORY:
192
- apiUrl += `/address/${params.address}/history`;
193
- if (params.limit !== undefined) {
194
- apiUrl += `?limit=${params.limit}`;
195
- }
196
- break;
197
- case ExploreEndpoint.ADDRESS_UTXOS:
198
- apiUrl += `/address/${params.address}/unspent`;
199
- break;
200
- case ExploreEndpoint.HEALTH:
201
- apiUrl += "/woc";
202
- break;
203
- default:
204
- throw new Error(`Unsupported endpoint: ${params.endpoint}`);
205
- }
206
-
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
238
- const response = await fetch(apiUrl);
239
- if (!response.ok) {
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);
265
- }
266
-
267
- const result = await response.json();
268
-
269
- return {
270
- content: [
271
- {
272
- type: "text",
273
- text: JSON.stringify(result, null, 2),
274
- },
275
- ],
276
- };
277
- } catch (error) {
278
- return {
279
- content: [
280
- {
281
- type: "text",
282
- text: `Error: ${error instanceof Error ? error.message : String(error)}`
283
- }
284
- ],
285
- isError: true,
286
- };
287
- }
288
- }
289
- );
290
- }
194
+ if (
195
+ params.endpoint === ExploreEndpoint.BULK_TX_DETAILS &&
196
+ (!params.txids || params.txids.length === 0)
197
+ ) {
198
+ throw new Error(
199
+ "txids array is required for bulk_tx_details endpoint",
200
+ );
201
+ }
202
+
203
+ // Build API URL based on the selected endpoint
204
+ let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
205
+
206
+ switch (params.endpoint) {
207
+ case ExploreEndpoint.CHAIN_INFO:
208
+ apiUrl += "/chain/info";
209
+ break;
210
+ case ExploreEndpoint.CHAIN_TIPS:
211
+ apiUrl += "/chain/tips";
212
+ break;
213
+ case ExploreEndpoint.CIRCULATING_SUPPLY:
214
+ apiUrl += "/circulatingsupply";
215
+ break;
216
+ case ExploreEndpoint.PEER_INFO:
217
+ apiUrl += "/peer/info";
218
+ break;
219
+ case ExploreEndpoint.BLOCK_BY_HASH:
220
+ apiUrl += `/block/hash/${params.blockHash}`;
221
+ break;
222
+ case ExploreEndpoint.BLOCK_BY_HEIGHT:
223
+ apiUrl += `/block/height/${params.blockHeight}`;
224
+ break;
225
+ case ExploreEndpoint.TAG_COUNT_BY_HEIGHT:
226
+ apiUrl += `/block/tagcount/height/${params.blockHeight}/stats`;
227
+ break;
228
+ case ExploreEndpoint.BLOCK_HEADERS:
229
+ apiUrl += "/block/headers";
230
+ break;
231
+ case ExploreEndpoint.BLOCK_PAGES: {
232
+ const pageNumber = params.pageNumber || 1;
233
+ apiUrl += `/block/hash/${params.blockHash}/page/${pageNumber}`;
234
+ break;
235
+ }
236
+ case ExploreEndpoint.BLOCK_STATS_BY_HEIGHT:
237
+ apiUrl += `/block/height/${params.blockHeight}/stats`;
238
+ break;
239
+ case ExploreEndpoint.BLOCK_MINER_STATS: {
240
+ const days = params.days !== undefined ? params.days : 7;
241
+ apiUrl += `/miner/blocks/stats?days=${days}`;
242
+ break;
243
+ }
244
+ case ExploreEndpoint.MINER_SUMMARY_STATS: {
245
+ const days = params.days !== undefined ? params.days : 7;
246
+ apiUrl += `/miner/summary/stats?days=${days}`;
247
+ break;
248
+ }
249
+ case ExploreEndpoint.TX_BY_HASH:
250
+ apiUrl += `/tx/hash/${params.txHash}`;
251
+ break;
252
+ case ExploreEndpoint.TX_RAW:
253
+ apiUrl += `/tx/${params.txHash}/hex`;
254
+ break;
255
+ case ExploreEndpoint.TX_RECEIPT:
256
+ apiUrl += `/tx/${params.txHash}/receipt`;
257
+ break;
258
+ case ExploreEndpoint.BULK_TX_DETAILS:
259
+ apiUrl += "/txs";
260
+ break;
261
+ case ExploreEndpoint.ADDRESS_HISTORY:
262
+ apiUrl += `/address/${params.address}/history`;
263
+ if (params.limit !== undefined) {
264
+ apiUrl += `?limit=${params.limit}`;
265
+ }
266
+ break;
267
+ case ExploreEndpoint.ADDRESS_UTXOS:
268
+ apiUrl += `/address/${params.address}/unspent`;
269
+ break;
270
+ case ExploreEndpoint.HEALTH:
271
+ apiUrl += "/woc";
272
+ break;
273
+ default:
274
+ throw new Error(`Unsupported endpoint: ${params.endpoint}`);
275
+ }
276
+
277
+ // Special handling for bulk_tx_details which requires a POST request
278
+ if (params.endpoint === ExploreEndpoint.BULK_TX_DETAILS) {
279
+ const response = await fetch(apiUrl, {
280
+ method: "POST",
281
+ headers: {
282
+ "Content-Type": "application/json",
283
+ },
284
+ body: JSON.stringify({ txids: params.txids }),
285
+ });
286
+
287
+ if (!response.ok) {
288
+ let errorMsg = `API error: ${response.status} ${response.statusText}`;
289
+ if (response.status === 404) {
290
+ errorMsg += " - One or more transaction IDs not found";
291
+ }
292
+ throw new Error(errorMsg);
293
+ }
294
+
295
+ const result = await response.json();
296
+
297
+ return {
298
+ content: [
299
+ {
300
+ type: "text",
301
+ text: JSON.stringify(result, null, 2),
302
+ },
303
+ ],
304
+ };
305
+ }
306
+
307
+ // For all other endpoints, use GET request
308
+ const response = await fetch(apiUrl);
309
+ if (!response.ok) {
310
+ let errorMsg = `API error: ${response.status} ${response.statusText}`;
311
+ // Provide helpful tips for specific error codes
312
+ if (response.status === 404) {
313
+ switch (params.endpoint) {
314
+ case ExploreEndpoint.BLOCK_BY_HASH:
315
+ errorMsg += ` - Block hash "${params.blockHash}" not found`;
316
+ break;
317
+ case ExploreEndpoint.BLOCK_BY_HEIGHT:
318
+ errorMsg += ` - Block at height ${params.blockHeight} not found`;
319
+ break;
320
+ case ExploreEndpoint.BLOCK_PAGES:
321
+ errorMsg += ` - Block hash "${params.blockHash}" not found or page ${params.pageNumber || 1} does not exist`;
322
+ break;
323
+ case ExploreEndpoint.TX_BY_HASH:
324
+ case ExploreEndpoint.TX_RAW:
325
+ case ExploreEndpoint.TX_RECEIPT:
326
+ errorMsg += ` - Transaction hash "${params.txHash}" not found`;
327
+ break;
328
+ case ExploreEndpoint.ADDRESS_HISTORY:
329
+ case ExploreEndpoint.ADDRESS_UTXOS:
330
+ errorMsg += ` - No data found for address "${params.address}"`;
331
+ break;
332
+ }
333
+ }
334
+ throw new Error(errorMsg);
335
+ }
336
+
337
+ const result = await response.json();
338
+
339
+ return {
340
+ content: [
341
+ {
342
+ type: "text",
343
+ text: JSON.stringify(result, null, 2),
344
+ },
345
+ ],
346
+ };
347
+ } catch (error) {
348
+ return {
349
+ content: [
350
+ {
351
+ type: "text",
352
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
353
+ },
354
+ ],
355
+ isError: true,
356
+ };
357
+ }
358
+ },
359
+ );
360
+ }