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 +104 -34
- package/index.ts +33 -6
- package/package.json +25 -2
- package/tools/bsv/explore.ts +177 -0
- package/tools/bsv/getPrice.ts +47 -13
- package/tools/bsv/index.ts +2 -0
- package/tools/wallet/sendToAddress.ts +2 -28
- package/tools/wallet/tools.test.ts +8 -8
- package/Dockerfile +0 -20
- package/biome.json +0 -30
- package/tests/integration/wallet-server.test.ts +0 -216
- package/tsconfig.json +0 -28
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
|
-
| `
|
|
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
|
-
|
|
237
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
//
|
|
15
|
-
|
|
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
|
|
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.
|
|
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
|
+
}
|
package/tools/bsv/getPrice.ts
CHANGED
|
@@ -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
|
|
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: $${
|
|
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 };
|
package/tools/bsv/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
| "
|
|
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
|
-
"
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
}
|