bsv-mcp 0.0.8 → 0.0.10

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 CHANGED
@@ -138,45 +138,46 @@ The toolkit is organized into several categories:
138
138
 
139
139
  Wallet tools provide core BSV wallet functionality:
140
140
 
141
- | Tool Name | Description |
142
- |-----------|-------------|
143
- | `wallet_getPublicKey` | Retrieves a public key for a specified protocol and key ID |
144
- | `wallet_createSignature` | Creates a cryptographic signature for the provided data |
145
- | `wallet_verifySignature` | Verifies a cryptographic signature against the provided data |
146
- | `wallet_encryption` | Combined tool for encrypting and decrypting data using the wallet's cryptographic keys (replaces separate encrypt/decrypt tools) |
147
- | `wallet_getAddress` | Returns a BSV address for the current wallet or a derived path |
148
- | `wallet_sendToAddress` | Sends BSV to a specified address (supports BSV or USD amounts) |
149
- | `wallet_purchaseListing` | Purchases NFTs or BSV-20/BSV-21 tokens from marketplace listings |
150
- | `wallet_createOrdinals` | Creates and inscribes ordinals on the BSV blockchain |
141
+ | Tool Name | Description | Example Output |
142
+ |-----------|-------------|----------------|
143
+ | `wallet_getPublicKey` | Retrieves a public key for a specified protocol and key ID | `{"publicKey":"032d0c73eb9270e9e009fd1f9dd77e19cf764fbad5f799560c4e8fd414e40d6fc2"}` |
144
+ | `wallet_createSignature` | Creates a cryptographic signature for the provided data | `{"signature":[144,124,85,193,226,45,140,249,9,177,11,167,33,215,209,38,...]}` |
145
+ | `wallet_verifySignature` | Verifies a cryptographic signature against the provided data | `{"isValid":true}` |
146
+ | `wallet_encryption` | Combined tool for encrypting and decrypting data using the wallet's cryptographic keys (replaces separate encrypt/decrypt tools) | Encrypt: `{"ciphertext":[89,32,155,38,125,22,49,226,26,...]}` <br> Decrypt: `{"plaintext":[104,101,108,108,111,32,119,111,114,108,100]}` |
147
+ | `wallet_getAddress` | Returns a BSV address for the current wallet or a derived path | `{"address":"1ExampleBsvAddressXXXXXXXXXXXXXXXXX","status":"ok"}` |
148
+ | `wallet_sendToAddress` | Sends BSV to a specified address (supports BSV or USD amounts) | `{"status":"success","txid":"a1b2c3d4e5f6...","satoshis":1000000}` |
149
+ | `wallet_purchaseListing` | Purchases NFTs or BSV-20/BSV-21 tokens from marketplace listings | `{"status":"success","txid":"a1b2c3d4e5f6...","type":"nft","origin":"abcdef123456..."}` |
150
+ | `wallet_createOrdinals` | Creates and inscribes ordinals on the BSV blockchain | `{"txid":"a1b2c3d4e5f6...","inscriptionAddress":"1ExampleAddress...","contentType":"image/png"}` |
151
151
 
152
152
  ### BSV Tools
153
153
 
154
154
  Tools for interacting with the BSV blockchain and network:
155
155
 
156
- | Tool Name | Description |
157
- |-----------|-------------|
158
- | `bsv_getPrice` | Gets the current BSV price from an exchange API |
159
- | `bsv_decodeTransaction` | Decodes a BSV transaction and returns detailed information |
156
+ | Tool Name | Description | Example Output |
157
+ |-----------|-------------|----------------|
158
+ | `bsv_getPrice` | Gets the current BSV price from an exchange API | `Current BSV price: $38.75 USD` |
159
+ | `bsv_decodeTransaction` | Decodes a BSV transaction and returns detailed information | `{"txid":"a1b2c3d4e5f6...","version":1,"locktime":0,"size":225,"inputs":[...],"outputs":[...]}` |
160
+ | `bsv_explore` | Comprehensive blockchain explorer tool accessing WhatsOnChain API endpoints | `{"chain_info":{"chain":"main","blocks":826458,"headers":826458,"bestblockhash":"0000000000..."}}` |
160
161
 
161
162
  ### Ordinals Tools
162
163
 
163
164
  Tools for working with ordinals (NFTs) on BSV:
164
165
 
165
- | Tool Name | Description |
166
- |-----------|-------------|
167
- | `ordinals_getInscription` | Retrieves detailed information about a specific inscription |
168
- | `ordinals_searchInscriptions` | Searches for inscriptions based on various criteria |
169
- | `ordinals_marketListings` | Retrieves market listings for NFTs, BSV-20, and BSV-21 tokens with unified interface |
170
- | `ordinals_marketSales` | Gets information about BSV-20 and BSV-21 token market sales |
171
- | `ordinals_getBsv20ById` | Retrieves details about a specific BSV20 token by ID |
166
+ | Tool Name | Description | Example Output |
167
+ |-----------|-------------|----------------|
168
+ | `ordinals_getInscription` | Retrieves detailed information about a specific inscription | `{"id":"a1b2c3d4e5f6...","origin":"a1b2c3d4e5f6...","contentType":"image/png","content":"iVBORw0KGgoAAA..."}` |
169
+ | `ordinals_searchInscriptions` | Searches for inscriptions based on various criteria | `{"results":[{"id":"a1b2c3...","contentType":"image/png","owner":"1Example..."},...]}` |
170
+ | `ordinals_marketListings` | Retrieves market listings for NFTs, BSV-20, and BSV-21 tokens with unified interface | `{"results":[{"txid":"a1b2c3...","price":9990000,"tick":"PEPE","listing":true},...]}` |
171
+ | `ordinals_marketSales` | Gets information about BSV-20 and BSV-21 token market sales | `{"results":[{"txid":"a1b2c3...","price":34710050,"tick":"$BTC","sale":true},...]}` |
172
+ | `ordinals_getTokenByIdOrTicker` | Retrieves details about a specific BSV20 token by ID | `{"tick":"PEPE","max":"21000000","lim":"1000","dec":"2"}` |
172
173
 
173
174
  ### Utility Tools
174
175
 
175
176
  General-purpose utility functions:
176
177
 
177
- | Tool Name | Description |
178
- |-----------|-------------|
179
- | `utils_convertData` | Converts data between different encodings (utf8, hex, base64, binary) |
178
+ | Tool Name | Description | Example Output |
179
+ |-----------|-------------|----------------|
180
+ | `utils_convertData` | Converts data between different encodings (utf8, hex, base64, binary) | `68656c6c6f20776f726c64` (UTF-8 "hello world" converted to hex) |
180
181
 
181
182
  ## Using the Tools with MCP
182
183
 
@@ -201,6 +202,11 @@ Once connected, you can use natural language to interact with Bitcoin SV through
201
202
  ### Blockchain Operations
202
203
  - "What is the current BSV price?"
203
204
  - "Decode this BSV transaction: (transaction hex or ID)"
205
+ - "Get the latest Bitcoin SV chain information"
206
+ - "Show me block details for height 800000"
207
+ - "Explore transaction history for address 1ExampleBsvAddressXXXX"
208
+ - "Check unspent outputs (UTXOs) for my wallet address"
209
+ - "Get details for transaction with hash a1b2c3d4e5f6..."
204
210
 
205
211
  ### Data Conversion
206
212
  - "Convert 'Hello World' from UTF-8 to hex format"
@@ -233,17 +239,81 @@ For Cursor, check the Cursor MCP logs in Settings → Extensions → Model Conte
233
239
 
234
240
  ## Recent Updates
235
241
 
236
- ### Unified Encryption Tool
237
- - **Combined Wallet Encryption**: The `wallet_encrypt` and `wallet_decrypt` tools have been merged into a single `wallet_encryption` tool with a mode parameter to switch between encryption and decryption operations.
242
+ - **Blockchain Explorer**: Added `bsv_explore` tool for WhatsOnChain API access with mainnet/testnet support
243
+ - **Unified Tools**: Merged `wallet_encrypt`/`wallet_decrypt` into single `wallet_encryption` tool
244
+ - **Enhanced Marketplace**: Support for NFTs, BSV-20/21 tokens in listings, sales and purchases
245
+ - **Performance**: Added price caching and optimized API endpoint structure
246
+ - **Improved Validation**: Better error handling for private keys and parameters
238
247
 
239
- ### Enhanced Marketplace Tools
240
- - **Unified Market Listings**: The `ordinals_marketListings` tool now supports NFTs, BSV-20, and BSV-21 tokens through a single interface with appropriate filtering.
241
- - **Improved Market Sales**: The `ordinals_marketSales` tool (renamed from `ordinals_bsv20MarketSales`) now supports both BSV-20 and BSV-21 token sales.
242
- - **Token Purchases**: The `wallet_purchaseListing` tool has been enhanced to support purchasing both NFTs and BSV-20/BSV-21 tokens.
248
+ ## Bitcoin SV Blockchain Explorer
243
249
 
244
- ### API Refinements
245
- - Standardized endpoint structure for interactions with the GorillaPool API.
246
- - Improved parameter handling for different token types and marketplace actions.
250
+ The `bsv_explore` tool provides comprehensive access to the Bitcoin SV blockchain through the WhatsOnChain API. This powerful explorer tool allows you to query various aspects of the blockchain, including chain data, blocks, transactions, and address information.
251
+
252
+ ### Available Endpoints
253
+
254
+ The tool supports the following endpoint categories and specific endpoints:
255
+
256
+ #### Chain Data
257
+ | Endpoint | Description | Required Parameters | Example Response |
258
+ |----------|-------------|---------------------|------------------|
259
+ | `chain_info` | Network statistics, difficulty, and chain work | None | `{"chain":"main","blocks":826458,"headers":826458,"bestblockhash":"000000000000..."}` |
260
+ | `chain_tips` | Current chain tips including heights and states | None | `[{"height":826458,"hash":"000000000000...","branchlen":0,"status":"active"}]` |
261
+ | `circulating_supply` | Current BSV circulating supply | None | `{"bsv":21000000}` |
262
+ | `peer_info` | Connected peer statistics | None | `[{"addr":"1.2.3.4:8333","services":"000000000000...","lastsend":1621234567}]` |
263
+
264
+ #### Block Data
265
+ | Endpoint | Description | Required Parameters | Example Response |
266
+ |----------|-------------|---------------------|------------------|
267
+ | `block_by_hash` | Complete block data via hash | `blockHash` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
268
+ | `block_by_height` | Complete block data via height | `blockHeight` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
269
+
270
+ #### Transaction Data
271
+ | Endpoint | Description | Required Parameters | Example Response |
272
+ |----------|-------------|---------------------|------------------|
273
+ | `tx_by_hash` | Detailed transaction data | `txHash` | `{"txid":"a1b2c3d4e5f6...","version":1,"locktime":0,"size":225,...}` |
274
+ | `tx_raw` | Raw transaction hex data | `txHash` | `"01000000012345abcdef..."` |
275
+ | `tx_receipt` | Transaction receipt | `txHash` | `{"blockHash":"000000000000...","blockHeight":800000,"confirmations":26458}` |
276
+
277
+ #### Address Data
278
+ | Endpoint | Description | Required Parameters | Example Response |
279
+ |----------|-------------|---------------------|------------------|
280
+ | `address_history` | Transaction history for address | `address`, optional: `limit` | `[{"tx_hash":"a1b2c3d4e5f6...","height":800000},...]` |
281
+ | `address_utxos` | Unspent outputs for address | `address` | `[{"tx_hash":"a1b2c3d4e5f6...","tx_pos":0,"value":100000},...]` |
282
+
283
+ #### Network
284
+ | Endpoint | Description | Required Parameters | Example Response |
285
+ |----------|-------------|---------------------|------------------|
286
+ | `health` | API health check | None | `{"status":"synced"}` |
287
+
288
+ ### Usage Examples
289
+
290
+ The `bsv_explore` tool can be used with natural language prompts like:
291
+
292
+ ```
293
+ "Get the current Bitcoin SV blockchain information"
294
+ "Show me block #800000 details"
295
+ "Fetch transaction history for address 1ExampleBsvAddressXXXXXXXX"
296
+ "Get unspent outputs for my wallet address"
297
+ "Check transaction details for txid a1b2c3d4e5f6..."
298
+ "What is the current BSV circulating supply?"
299
+ ```
300
+
301
+ Under the hood, the tool accepts parameters to specify which data to retrieve:
302
+
303
+ - `endpoint`: The specific WhatsOnChain endpoint to query (e.g., `chain_info`, `tx_by_hash`)
304
+ - `network`: The BSV network to use (`main` or `test`)
305
+ - Additional parameters as required by the specific endpoint:
306
+ - `blockHash`: For block_by_hash endpoint
307
+ - `blockHeight`: For block_by_height endpoint
308
+ - `txHash`: For transaction-related endpoints
309
+ - `address`: For address-related endpoints
310
+ - `limit`: Optional pagination limit for address_history
311
+
312
+ ### Network Options
313
+
314
+ The tool supports both mainnet and testnet:
315
+ - `main`: Bitcoin SV mainnet (default)
316
+ - `test`: Bitcoin SV testnet
247
317
 
248
318
  ## Development
249
319
 
@@ -261,4 +331,4 @@ bun test
261
331
 
262
332
  ## License
263
333
 
264
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
334
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/index.ts CHANGED
@@ -6,16 +6,43 @@ import { registerAllTools } from "./tools";
6
6
  import { registerWalletTools } from "./tools/wallet/tools";
7
7
  import { Wallet } from "./tools/wallet/wallet";
8
8
 
9
+ /**
10
+ * Validate the private key from environment variables
11
+ * Exits the process with an error message if validation fails
12
+ */
13
+ function validatePrivateKey(): PrivateKey {
14
+ const privateKeyWif = process.env.PRIVATE_KEY_WIF;
15
+
16
+ // Check if private key is set
17
+ if (!privateKeyWif) {
18
+ console.error("\x1b[31mError: PRIVATE_KEY_WIF environment variable is not set\x1b[0m");
19
+ console.error("Please set this variable with a valid Bitcoin SV private key in WIF format");
20
+ console.error("Example: PRIVATE_KEY_WIF=your_private_key_wif bun run index.ts");
21
+ process.exit(1);
22
+ }
23
+
24
+ // Validate the private key format
25
+ try {
26
+ return PrivateKey.fromWif(privateKeyWif);
27
+ } catch (error) {
28
+ console.error("\x1b[31mError: Invalid private key format\x1b[0m");
29
+ console.error("The PRIVATE_KEY_WIF provided is not a valid Bitcoin SV private key in WIF format");
30
+ console.error("Please check your key and try again");
31
+ process.exit(1);
32
+ }
33
+ }
34
+
35
+ // Validate private key early before starting the server
36
+ const privKey = validatePrivateKey();
37
+ console.log("\x1b[32mPrivate key validated successfully\x1b[0m");
38
+
9
39
  const server = new McpServer({
10
40
  name: "Bitcoin SV MCP",
11
41
  version: "0.0.1",
12
42
  });
13
43
 
14
- // Singleton wallet instance (for demo, could be replaced with real key management)
15
- // If PRIVATE_KEY_WIF is set in the environment, use it to instantiate the Wallet
16
- const privateKeyWif = process.env.PRIVATE_KEY_WIF;
17
- const privKey = privateKeyWif ? PrivateKey.fromWif(privateKeyWif) : undefined;
18
- const wallet = privKey ? new Wallet(privKey) : new Wallet();
44
+ // Initialize wallet with the validated private key
45
+ const wallet = new Wallet(privKey);
19
46
 
20
47
  // Register wallet tools separately (needs wallet instance)
21
48
  registerWalletTools(server, wallet);
@@ -25,7 +52,7 @@ registerAllTools(server);
25
52
 
26
53
  // Debug: Log all registered tools
27
54
  console.log("Registered tools:", Object.keys(server));
28
- console.log("MCP Server:", server);
55
+ console.log("MCP Server ready 🚀");
29
56
 
30
57
  // Connect to the transport
31
58
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -2,7 +2,31 @@
2
2
  "name": "bsv-mcp",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.0.8",
5
+ "version": "0.0.10",
6
+ "license": "MIT",
7
+ "author": "satchmo",
8
+ "description": "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/b-open-io/bsv-mcp"
12
+ },
13
+ "keywords": [
14
+ "bitcoin",
15
+ "bsv",
16
+ "bitcoin-sv",
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "wallet",
20
+ "ordinals",
21
+ "blockchain"
22
+ ],
23
+ "files": [
24
+ "index.ts",
25
+ "tools/**/*.ts",
26
+ "LICENSE",
27
+ "README.md",
28
+ "smithery.yaml"
29
+ ],
6
30
  "bin": {
7
31
  "bsv-mcp": "./index.ts"
8
32
  },
@@ -20,7 +44,6 @@
20
44
  "@types/node": "^22.14.1",
21
45
  "js-1sat-ord": "^0.1.80",
22
46
  "satoshi-token": "^0.0.4",
23
- "tsx": "^4.19.3",
24
47
  "zod": "^3.24.2"
25
48
  },
26
49
  "scripts": {
@@ -0,0 +1,177 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+
4
+ // Base URL for WhatsOnChain API
5
+ const WOC_API_BASE_URL = "https://api.whatsonchain.com/v1/bsv";
6
+
7
+ /**
8
+ * WhatsOnChain API endpoints available for exploration
9
+ */
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
+
21
+ // Transaction endpoints
22
+ TX_BY_HASH = "tx_by_hash",
23
+ TX_RAW = "tx_raw",
24
+ TX_RECEIPT = "tx_receipt",
25
+ ADDRESS_HISTORY = "address_history",
26
+ ADDRESS_UTXOS = "address_utxos",
27
+
28
+ // Health endpoint
29
+ HEALTH = "health",
30
+ }
31
+
32
+ enum Network {
33
+ MAIN = "main",
34
+ TEST = "test",
35
+ }
36
+
37
+ // Schema for the bsv_explore tool arguments
38
+ const exploreArgsSchema = z.object({
39
+ endpoint: z.nativeEnum(ExploreEndpoint).describe("WhatsOnChain API endpoint to call"),
40
+ network: z.nativeEnum(Network).default(Network.MAIN).describe("Network to use (main or test)"),
41
+
42
+ // Parameters for specific endpoints
43
+ blockHash: z.string().optional().describe("Block hash (required for block_by_hash endpoint)"),
44
+ blockHeight: z.number().optional().describe("Block height (required for block_by_height endpoint)"),
45
+ txHash: z.string().optional().describe("Transaction hash (required for tx_by_hash, tx_raw, and tx_receipt endpoints)"),
46
+ address: z.string().optional().describe("Bitcoin address (required for address_history and address_utxos endpoints)"),
47
+ limit: z.number().optional().describe("Limit for paginated results (optional for address_history)"),
48
+ });
49
+
50
+ // Type for the tool arguments
51
+ type ExploreArgs = z.infer<typeof exploreArgsSchema>;
52
+
53
+ /**
54
+ * Register the bsv_explore tool with the MCP server
55
+ * @param server The MCP server instance
56
+ */
57
+ export function registerExploreTool(server: McpServer): void {
58
+ server.tool(
59
+ "bsv_explore",
60
+ "Explore Bitcoin SV blockchain data using the WhatsOnChain API. Access multiple data types:\n\n" +
61
+ "CHAIN DATA:\n" +
62
+ "- chain_info: Network stats, difficulty, and chain work\n" +
63
+ "- chain_tips: Current chain tips including heights and states\n" +
64
+ "- circulating_supply: Current BSV circulating supply\n" +
65
+ "- peer_info: Connected peer statistics\n\n" +
66
+ "BLOCK DATA:\n" +
67
+ "- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
68
+ "- block_by_height: Complete block data via height (requires blockHeight parameter)\n\n" +
69
+ "TRANSACTION DATA:\n" +
70
+ "- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
71
+ "- tx_raw: Raw transaction hex data (requires txHash parameter)\n" +
72
+ "- tx_receipt: Transaction receipt (requires txHash parameter)\n\n" +
73
+ "ADDRESS DATA:\n" +
74
+ "- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
75
+ "- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
76
+ "NETWORK:\n" +
77
+ "- health: API health check\n\n" +
78
+ "Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
79
+ { args: exploreArgsSchema },
80
+ async ({ args }) => {
81
+ try {
82
+ const params = exploreArgsSchema.parse(args);
83
+
84
+ // Validate required parameters for specific endpoints
85
+ if (params.endpoint === ExploreEndpoint.BLOCK_BY_HASH && !params.blockHash) {
86
+ throw new Error("blockHash is required for block_by_hash endpoint");
87
+ }
88
+
89
+ if (params.endpoint === ExploreEndpoint.BLOCK_BY_HEIGHT && params.blockHeight === undefined) {
90
+ throw new Error("blockHeight is required for block_by_height endpoint");
91
+ }
92
+
93
+ if ([ExploreEndpoint.TX_BY_HASH, ExploreEndpoint.TX_RAW, ExploreEndpoint.TX_RECEIPT].includes(params.endpoint) && !params.txHash) {
94
+ throw new Error("txHash is required for transaction endpoints");
95
+ }
96
+
97
+ if ([ExploreEndpoint.ADDRESS_HISTORY, ExploreEndpoint.ADDRESS_UTXOS].includes(params.endpoint) && !params.address) {
98
+ throw new Error("address is required for address endpoints");
99
+ }
100
+
101
+ // Build API URL based on the selected endpoint
102
+ let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
103
+
104
+ switch (params.endpoint) {
105
+ case ExploreEndpoint.CHAIN_INFO:
106
+ apiUrl += "/chain/info";
107
+ break;
108
+ case ExploreEndpoint.CHAIN_TIPS:
109
+ apiUrl += "/chain/tips";
110
+ break;
111
+ case ExploreEndpoint.CIRCULATING_SUPPLY:
112
+ apiUrl += "/circulatingsupply";
113
+ break;
114
+ case ExploreEndpoint.PEER_INFO:
115
+ apiUrl += "/peer/info";
116
+ break;
117
+ case ExploreEndpoint.BLOCK_BY_HASH:
118
+ apiUrl += `/block/hash/${params.blockHash}`;
119
+ break;
120
+ case ExploreEndpoint.BLOCK_BY_HEIGHT:
121
+ apiUrl += `/block/height/${params.blockHeight}`;
122
+ break;
123
+ case ExploreEndpoint.TX_BY_HASH:
124
+ apiUrl += `/tx/hash/${params.txHash}`;
125
+ break;
126
+ case ExploreEndpoint.TX_RAW:
127
+ apiUrl += `/tx/${params.txHash}/hex`;
128
+ break;
129
+ case ExploreEndpoint.TX_RECEIPT:
130
+ apiUrl += `/tx/${params.txHash}/receipt`;
131
+ break;
132
+ case ExploreEndpoint.ADDRESS_HISTORY:
133
+ apiUrl += `/address/${params.address}/history`;
134
+ if (params.limit !== undefined) {
135
+ apiUrl += `?limit=${params.limit}`;
136
+ }
137
+ break;
138
+ case ExploreEndpoint.ADDRESS_UTXOS:
139
+ apiUrl += `/address/${params.address}/unspent`;
140
+ break;
141
+ case ExploreEndpoint.HEALTH:
142
+ apiUrl += "/woc";
143
+ break;
144
+ default:
145
+ throw new Error(`Unsupported endpoint: ${params.endpoint}`);
146
+ }
147
+
148
+ // Make the API request
149
+ const response = await fetch(apiUrl);
150
+ if (!response.ok) {
151
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
152
+ }
153
+
154
+ const result = await response.json();
155
+
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: JSON.stringify(result, null, 2),
161
+ },
162
+ ],
163
+ };
164
+ } catch (error) {
165
+ return {
166
+ content: [
167
+ {
168
+ type: "text",
169
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
170
+ }
171
+ ],
172
+ isError: true,
173
+ };
174
+ }
175
+ }
176
+ );
177
+ }
@@ -1,6 +1,48 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
 
4
+ // Define cache duration (5 minutes in milliseconds)
5
+ const PRICE_CACHE_DURATION = 5 * 60 * 1000;
6
+
7
+ // Cache object to store price data
8
+ let cachedPrice: { value: number; timestamp: number } | null = null;
9
+
10
+ /**
11
+ * Get the BSV price with caching mechanism
12
+ * @returns The current BSV price in USD
13
+ */
14
+ async function getBsvPriceWithCache(): Promise<number> {
15
+ // Return cached price if it's still valid
16
+ if (cachedPrice && (Date.now() - cachedPrice.timestamp) < PRICE_CACHE_DURATION) {
17
+ console.log("Using cached BSV price");
18
+ return cachedPrice.value;
19
+ }
20
+
21
+ console.log("Fetching fresh BSV price");
22
+ // If no valid cache, fetch new price
23
+ const res = await fetch(
24
+ "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
25
+ );
26
+ if (!res.ok) throw new Error("Failed to fetch price");
27
+
28
+ const data = (await res.json()) as {
29
+ currency: string;
30
+ rate: string;
31
+ time: number;
32
+ };
33
+
34
+ const price = Number(data.rate);
35
+ if (Number.isNaN(price) || price <= 0) throw new Error("Invalid price received");
36
+
37
+ // Update cache
38
+ cachedPrice = {
39
+ value: price,
40
+ timestamp: Date.now()
41
+ };
42
+
43
+ return price;
44
+ }
45
+
4
46
  /**
5
47
  * Register the BSV price lookup tool
6
48
  * @param server The MCP server instance
@@ -14,23 +56,12 @@ export function registerGetPriceTool(server: McpServer): void {
14
56
  },
15
57
  async () => {
16
58
  try {
17
- const res = await fetch(
18
- "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
19
- );
20
- if (!res.ok) throw new Error("Failed to fetch price");
21
- const data = (await res.json()) as {
22
- currency: string;
23
- rate: string;
24
- time: number;
25
- };
26
- const price = data.rate;
27
- if (typeof price !== "string" && typeof price !== "number")
28
- throw new Error("Price not found");
59
+ const price = await getBsvPriceWithCache();
29
60
  return {
30
61
  content: [
31
62
  {
32
63
  type: "text",
33
- text: `Current BSV price: $${Number(price).toFixed(2)} USD`,
64
+ text: `Current BSV price: $${price.toFixed(2)} USD`,
34
65
  },
35
66
  ],
36
67
  };
@@ -43,3 +74,6 @@ export function registerGetPriceTool(server: McpServer): void {
43
74
  },
44
75
  );
45
76
  }
77
+
78
+ // Export the cached price getter for use in other modules
79
+ export { getBsvPriceWithCache };
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { registerDecodeTransactionTool } from "./decodeTransaction";
3
3
  import { registerGetPriceTool } from "./getPrice";
4
+ import { registerExploreTool } from "./explore";
4
5
 
5
6
  /**
6
7
  * Register all BSV tools with the MCP server
@@ -10,4 +11,5 @@ export function registerBsvTools(server: McpServer): void {
10
11
  // Register BSV-related tools
11
12
  registerGetPriceTool(server);
12
13
  registerDecodeTransactionTool(server);
14
+ registerExploreTool(server);
13
15
  }
@@ -5,33 +5,7 @@ import { toSatoshi } from "satoshi-token";
5
5
  import type { z } from "zod";
6
6
  import { sendToAddressArgsSchema } from "./schemas";
7
7
  import type { Wallet } from "./wallet";
8
-
9
- /**
10
- * Fetch the current BSV price from whatsonchain API
11
- * @returns The BSV price in USD
12
- */
13
- async function getBsvPrice(): Promise<number> {
14
- try {
15
- const res = await fetch(
16
- "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
17
- );
18
- if (!res.ok) throw new Error("Failed to fetch BSV price");
19
-
20
- // Parse the response with proper type casting
21
- const data = (await res.json()) as {
22
- rate: string;
23
- currency: string;
24
- time: number;
25
- };
26
- const price = Number(data.rate);
27
-
28
- if (Number.isNaN(price) || price <= 0) throw new Error("Invalid BSV price");
29
- return price;
30
- } catch (error) {
31
- console.error("BSV price fetch error:", error);
32
- throw error;
33
- }
34
- }
8
+ import { getBsvPriceWithCache } from "../bsv/getPrice";
35
9
 
36
10
  // Use the schema imported from schemas.ts
37
11
  export type SendToAddressArgs = z.infer<typeof sendToAddressArgsSchema>;
@@ -62,7 +36,7 @@ export function registerSendToAddressTool(server: McpServer, wallet: Wallet) {
62
36
  let satoshis: number;
63
37
  if (currency === "USD") {
64
38
  // Get current BSV price
65
- const bsvPriceUsd = await getBsvPrice();
39
+ const bsvPriceUsd = await getBsvPriceWithCache();
66
40
 
67
41
  // Convert USD to BSV
68
42
  const bsvAmount = amount / bsvPriceUsd;
@@ -11,15 +11,13 @@ type WalletToolName =
11
11
  | "wallet_getPublicKey"
12
12
  | "wallet_createSignature"
13
13
  | "wallet_verifySignature"
14
- | "wallet_encrypt"
15
- | "wallet_decrypt";
14
+ | "wallet_encryption";
16
15
 
17
16
  const toolNames: WalletToolName[] = [
18
17
  "wallet_getPublicKey",
19
18
  "wallet_createSignature",
20
19
  "wallet_verifySignature",
21
- "wallet_encrypt",
22
- "wallet_decrypt",
20
+ "wallet_encryption",
23
21
  ];
24
22
 
25
23
  // Helper function to get dummy arguments for each tool
@@ -36,10 +34,12 @@ function getDummyArgs(tool: WalletToolName): Record<string, unknown> {
36
34
  signature: "sig",
37
35
  publicKey: "pubkey",
38
36
  };
39
- case "wallet_encrypt":
40
- return { data: "test", publicKey: "pubkey" };
41
- case "wallet_decrypt":
42
- return { encryptedData: "data" };
37
+ // TODO we merged encrypt and decrypt into encryption we need to update the test file
38
+ // case "wallet_encryption":
39
+ // if
40
+ // return { data: "test", publicKey: "pubkey" };
41
+ // case "wallet_decryption":
42
+ // return { encryptedData: "data" };
43
43
  default:
44
44
  return {};
45
45
  }
package/Dockerfile DELETED
@@ -1,20 +0,0 @@
1
- # Use the official Bun image
2
- FROM oven/bun:1
3
-
4
- # Set working directory
5
- WORKDIR /app
6
-
7
- # Copy all application code first
8
- COPY . .
9
-
10
- # Install dependencies
11
- RUN bun install --frozen-lockfile
12
-
13
- # Set user for security
14
- USER bun
15
-
16
- # Expose port (if needed)
17
- EXPOSE 3000
18
-
19
- # Run the application
20
- CMD ["bun", "run", "index.ts"]
package/biome.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
- "vcs": {
4
- "enabled": false,
5
- "clientKind": "git",
6
- "useIgnoreFile": false
7
- },
8
- "files": {
9
- "ignoreUnknown": false,
10
- "ignore": []
11
- },
12
- "formatter": {
13
- "enabled": true,
14
- "indentStyle": "tab"
15
- },
16
- "organizeImports": {
17
- "enabled": true
18
- },
19
- "linter": {
20
- "enabled": true,
21
- "rules": {
22
- "recommended": true
23
- }
24
- },
25
- "javascript": {
26
- "formatter": {
27
- "quoteStyle": "double"
28
- }
29
- }
30
- }
@@ -1,216 +0,0 @@
1
- import { expect, test } from "bun:test";
2
- import { PrivateKey } from "@bsv/sdk";
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { z } from "zod";
5
- import { registerWalletTools } from "../../tools/wallet/tools";
6
- import { Wallet } from "../../tools/wallet/wallet";
7
-
8
- // List of expected wallet tool names
9
- const EXPECTED_WALLET_TOOLS = [
10
- "wallet_getPublicKey",
11
- "wallet_createSignature",
12
- "wallet_verifySignature",
13
- "wallet_encrypt",
14
- "wallet_decrypt",
15
- "wallet_createAction",
16
- "wallet_signAction",
17
- "wallet_listActions",
18
- "wallet_listOutputs",
19
- "wallet_getNetwork",
20
- "wallet_getVersion",
21
- "wallet_revealCounterpartyKeyLinkage",
22
- "wallet_revealSpecificKeyLinkage",
23
- "wallet_createHmac",
24
- "wallet_verifyHmac",
25
- "wallet_abortAction",
26
- "wallet_internalizeAction",
27
- "wallet_relinquishOutput",
28
- "wallet_acquireCertificate",
29
- "wallet_listCertificates",
30
- "wallet_proveCertificate",
31
- "wallet_relinquishCertificate",
32
- "wallet_discoverByIdentityKey",
33
- "wallet_discoverByAttributes",
34
- "wallet_isAuthenticated",
35
- "wallet_waitForAuthentication",
36
- "wallet_getHeaderForHeight",
37
- ];
38
-
39
- // Mock wallet with test responses
40
- class TestWallet extends Wallet {
41
- constructor() {
42
- super(PrivateKey.fromRandom());
43
- }
44
-
45
- // Override with test implementations that actually return something
46
- async getPublicKey() {
47
- return { publicKey: "mockPublicKey123" };
48
- }
49
-
50
- // Note: other methods will still return 'not implemented' errors
51
- }
52
-
53
- test("MCP server registers all expected wallet tools", async () => {
54
- // Create an MCP server
55
- const server = new McpServer({
56
- name: "Wallet Server Test",
57
- version: "0.0.1",
58
- });
59
-
60
- // Create test wallet and register tools
61
- const wallet = new TestWallet();
62
- const handlers = registerWalletTools(server, wallet);
63
-
64
- // Check that we have all expected tool handlers
65
- for (const toolName of EXPECTED_WALLET_TOOLS) {
66
- expect(handlers[toolName as keyof typeof handlers]).toBeDefined();
67
- expect(typeof handlers[toolName as keyof typeof handlers]).toBe("function");
68
- }
69
-
70
- // Check that we don't have any unexpected handlers
71
- const actualToolNames = Object.keys(handlers);
72
- expect(actualToolNames.length).toBe(EXPECTED_WALLET_TOOLS.length);
73
-
74
- for (const toolName of actualToolNames) {
75
- expect(EXPECTED_WALLET_TOOLS).toContain(toolName);
76
- }
77
- });
78
-
79
- test("MCP server with wallet tools", async () => {
80
- // Create an MCP server
81
- const server = new McpServer({
82
- name: "Wallet Server Test",
83
- version: "0.0.1",
84
- });
85
-
86
- // Create test wallet and register tools
87
- const wallet = new TestWallet();
88
- const handlers = registerWalletTools(server, wallet);
89
-
90
- // Mock the request handler extra
91
- const mockExtra = {
92
- signal: new AbortController().signal,
93
- };
94
-
95
- // Test the overridden getPublicKey handler which should return data
96
- const getPublicKeyHandler = handlers.wallet_getPublicKey;
97
- const getPublicKeyResult = await getPublicKeyHandler({ args: {} }, mockExtra);
98
-
99
- // Check the success result based on the actual structure
100
- expect(getPublicKeyResult.content).toBeDefined();
101
- expect(getPublicKeyResult.content?.[0]?.type).toBe("text");
102
-
103
- const publicKeyContent = getPublicKeyResult.content?.[0]?.text;
104
- if (typeof publicKeyContent === "string") {
105
- expect(JSON.parse(publicKeyContent)).toEqual({
106
- publicKey: "mockPublicKey123",
107
- });
108
- }
109
- });
110
-
111
- // For a more complete integration test, we could create a real HTTP server
112
- // and use fetch to make requests to it
113
- test.skip("MCP server with HTTP requests", async () => {
114
- // Create MCP server
115
- const server = new McpServer({
116
- name: "Wallet Server Test",
117
- version: "0.0.1",
118
- });
119
-
120
- // Register wallet tools
121
- const wallet = new TestWallet();
122
- const handlers = registerWalletTools(server, wallet);
123
-
124
- // Define expected request type
125
- type ToolCallRequest = {
126
- type: string;
127
- name: string;
128
- args?: Record<string, unknown>;
129
- };
130
-
131
- // Create a simple HTTP server
132
- const httpServer = Bun.serve({
133
- port: 0, // Use random available port
134
- async fetch(req) {
135
- if (req.method === "POST") {
136
- const bodyText = await req.text();
137
- let body: unknown;
138
-
139
- try {
140
- body = JSON.parse(bodyText);
141
- } catch (err) {
142
- return new Response("Invalid JSON", { status: 400 });
143
- }
144
-
145
- // Type guard for the request body
146
- const isToolCallRequest = (obj: unknown): obj is ToolCallRequest => {
147
- return (
148
- typeof obj === "object" &&
149
- obj !== null &&
150
- "type" in obj &&
151
- "name" in obj &&
152
- typeof (obj as ToolCallRequest).type === "string" &&
153
- typeof (obj as ToolCallRequest).name === "string"
154
- );
155
- };
156
-
157
- // Handle MCP requests by calling the appropriate tool
158
- if (
159
- isToolCallRequest(body) &&
160
- body.type === "tool_call" &&
161
- body.name.startsWith("wallet_")
162
- ) {
163
- // Get handler from our handlers object
164
- const toolName = body.name as keyof typeof handlers;
165
- const handler = handlers[toolName];
166
- if (handler) {
167
- const result = await handler(
168
- { args: body.args || {} },
169
- {
170
- signal: new AbortController().signal,
171
- },
172
- );
173
-
174
- return new Response(JSON.stringify(result), {
175
- headers: { "Content-Type": "application/json" },
176
- });
177
- }
178
- }
179
- }
180
-
181
- return new Response("Not found", { status: 404 });
182
- },
183
- });
184
-
185
- try {
186
- const port = httpServer.port;
187
- const baseUrl = `http://localhost:${port}`;
188
-
189
- // Test calling the getPublicKey tool
190
- const response = await fetch(`${baseUrl}`, {
191
- method: "POST",
192
- headers: { "Content-Type": "application/json" },
193
- body: JSON.stringify({
194
- type: "tool_call",
195
- name: "wallet_getPublicKey",
196
- args: {},
197
- }),
198
- });
199
-
200
- const resultData = (await response.json()) as {
201
- isError?: boolean;
202
- content?: Array<{ type: string; text: string }>;
203
- };
204
-
205
- expect(resultData.content).toBeDefined();
206
- expect(resultData.content?.[0]?.type).toBe("text");
207
-
208
- if (resultData.content?.[0]?.text) {
209
- const parsedContent = JSON.parse(resultData.content[0].text);
210
- expect(parsedContent).toEqual({ publicKey: "mockPublicKey123" });
211
- }
212
- } finally {
213
- // Shutdown the server
214
- httpServer.stop();
215
- }
216
- });
package/tsconfig.json DELETED
@@ -1,28 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "ESNext",
7
- "moduleDetection": "force",
8
- "jsx": "react-jsx",
9
- "allowJs": true,
10
-
11
- // Bundler mode
12
- "moduleResolution": "bundler",
13
- "allowImportingTsExtensions": true,
14
- "verbatimModuleSyntax": true,
15
- "noEmit": true,
16
-
17
- // Best practices
18
- "strict": true,
19
- "skipLibCheck": true,
20
- "noFallthroughCasesInSwitch": true,
21
- "noUncheckedIndexedAccess": true,
22
-
23
- // Some stricter flags (disabled by default)
24
- "noUnusedLocals": false,
25
- "noUnusedParameters": false,
26
- "noPropertyAccessFromIndexSignature": false
27
- }
28
- }