bsv-mcp 0.0.34 → 0.0.36

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/index.ts CHANGED
@@ -39,18 +39,19 @@ function initializePrivateKey(): PrivateKey | undefined {
39
39
 
40
40
  // Check if private key is set
41
41
  if (!privateKeyWif) {
42
- console.warn(
43
- "\x1b[33mWarning: PRIVATE_KEY_WIF environment variable is not set\x1b[0m",
44
- );
45
- console.warn(
46
- "The server will run, but wallet operations requiring a private key will return errors.",
47
- );
48
- console.warn(
49
- "Set this variable with a valid Bitcoin SV private key in WIF format to enable all features:",
50
- );
51
- console.warn(
52
- "Example: PRIVATE_KEY_WIF=your_private_key_wif bun run index.ts",
53
- );
42
+ // stdio protocol you cant send anything to console!!
43
+ // console.warn(
44
+ // "\x1b[33mWarning: PRIVATE_KEY_WIF environment variable is not set\x1b[0m",
45
+ // );
46
+ // console.warn(
47
+ // "The server will run, but wallet operations requiring a private key will return errors.",
48
+ // );
49
+ // console.warn(
50
+ // "Set this variable with a valid Bitcoin SV private key in WIF format to enable all features:",
51
+ // );
52
+ // console.warn(
53
+ // "Example: PRIVATE_KEY_WIF=your_private_key_wif bun run index.ts",
54
+ // );
54
55
  return undefined;
55
56
  }
56
57
 
@@ -58,13 +59,13 @@ function initializePrivateKey(): PrivateKey | undefined {
58
59
  try {
59
60
  return PrivateKey.fromWif(privateKeyWif);
60
61
  } catch (error) {
61
- console.warn("\x1b[33mWarning: Invalid private key format\x1b[0m");
62
- console.warn(
63
- "The PRIVATE_KEY_WIF provided is not a valid Bitcoin SV private key in WIF format.",
64
- );
65
- console.warn(
66
- "The server will run, but wallet operations requiring a private key will return errors.",
67
- );
62
+ // console.warn("\x1b[33mWarning: Invalid private key format\x1b[0m");
63
+ // console.warn(
64
+ // "The PRIVATE_KEY_WIF provided is not a valid Bitcoin SV private key in WIF format.",
65
+ // );
66
+ // console.warn(
67
+ // "The server will run, but wallet operations requiring a private key will return errors.",
68
+ // );
68
69
  return undefined;
69
70
  }
70
71
  }
@@ -73,23 +74,18 @@ function initializePrivateKey(): PrivateKey | undefined {
73
74
  const privKey = initializePrivateKey();
74
75
 
75
76
  const server = new McpServer(
76
- { name: "Bitcoin SV", version: "0.0.34" },
77
- // {
78
- // // Advertise only what you actually implement
79
- // capabilities: {
80
- // completions: {},
81
- // experimental: {},
82
- // logging: {},
83
- // prompts: {},
84
- // resources: {},
85
- // tools: {},
86
- // },
87
- // // Optional instructions banner for clients
88
- // instructions: `
89
- // This server exposes Bitcoin SV helpers.
90
- // Tools are idempotent unless marked destructive.
91
- // `,
92
- // },
77
+ { name: "Bitcoin SV", version: "0.0.36" },
78
+ {
79
+ capabilities: {
80
+ prompts: {},
81
+ resources: {},
82
+ tools: {},
83
+ },
84
+ instructions: `
85
+ This server exposes Bitcoin SV helpers.
86
+ Tools are idempotent unless marked destructive.
87
+ `,
88
+ },
93
89
  );
94
90
 
95
91
  // Initialize wallet and register tools based on configuration
@@ -134,3 +130,4 @@ if (CONFIG.loadPrompts) {
134
130
  // Connect to the transport
135
131
  const transport = new StdioServerTransport();
136
132
  await server.connect(transport);
133
+ console.error("BSV MCP Server running on stdio");
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "bsv-mcp",
3
- "module": "index.ts",
3
+ "module": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.0.34",
5
+ "version": "0.0.36",
6
6
  "license": "MIT",
7
7
  "author": "satchmo",
8
8
  "description": "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
@@ -14,13 +14,15 @@
14
14
  "bitcoin",
15
15
  "bsv",
16
16
  "bitcoin-sv",
17
- "mcp",
18
- "model-context-protocol",
19
17
  "wallet",
20
18
  "ordinals",
21
- "blockchain"
19
+ "blockchain",
20
+ "1sat-ordinals",
21
+ "explorer",
22
+ "block explorer"
22
23
  ],
23
24
  "files": [
25
+ "dist/**/*.js",
24
26
  "package.json",
25
27
  "*.ts",
26
28
  "tools/*.ts",
@@ -35,7 +37,7 @@
35
37
  "smithery.yaml"
36
38
  ],
37
39
  "bin": {
38
- "bsv-mcp": "./index.ts"
40
+ "bsv-mcp": "./dist/index.js"
39
41
  },
40
42
  "private": false,
41
43
  "devDependencies": {
@@ -48,7 +50,8 @@
48
50
  "dependencies": {
49
51
  "@bsv/sdk": "^1.4.20",
50
52
  "@modelcontextprotocol/sdk": "^1.10.2",
51
- "@types/node": "^22.14.1",
53
+ "@types/node": "^22.15.2",
54
+ "bun": "^1.2.10",
52
55
  "js-1sat-ord": "^0.1.81",
53
56
  "mnee": "^2.0.0",
54
57
  "satoshi-token": "^0.0.4",
@@ -56,6 +59,7 @@
56
59
  "zod": "^3.24.3"
57
60
  },
58
61
  "scripts": {
62
+ "build": "bun build ./index.ts --outdir ./dist --target node",
59
63
  "lint": "biome check .",
60
64
  "lint:fix": "biome check . --write"
61
65
  }
@@ -0,0 +1,242 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+
3
+ // Main JungleBus API documentation URL
4
+ const JUNGLEBUS_DOCS_URL = "https://junglebus.gorillapool.io/docs/";
5
+
6
+ /**
7
+ * Manually structured JungleBus API documentation
8
+ * Provides a clean summary of key JungleBus API endpoints and functionality
9
+ */
10
+ export function getJungleBusDocumentation(): string {
11
+ return `# JungleBus API Documentation
12
+
13
+ ## Overview
14
+
15
+ JungleBus is a transaction monitoring service for Bitcoin SV that allows applications to subscribe to transaction events and filter them based on specific criteria. The API provides both subscription and querying capabilities.
16
+
17
+ ## API Endpoints
18
+
19
+ ### Base URL
20
+ \`\`\`
21
+ https://junglebus.gorillapool.io/v1
22
+ \`\`\`
23
+
24
+ ### Transaction API
25
+
26
+ #### Get Transaction by ID
27
+ \`\`\`
28
+ GET /transaction/get/{txid}
29
+ \`\`\`
30
+
31
+ Returns detailed information about a specific transaction, including:
32
+ - Transaction data (hex format)
33
+ - Block information (hash, height, time)
34
+ - Input and output details
35
+ - Address information
36
+
37
+ #### Get Transactions by Block Hash
38
+ \`\`\`
39
+ GET /block/{blockhash}/transactions
40
+ \`\`\`
41
+
42
+ Returns all transactions within a specific block.
43
+
44
+ ### Subscription API
45
+
46
+ #### Create Subscription
47
+ \`\`\`
48
+ POST /subscribe
49
+ \`\`\`
50
+
51
+ Create a new subscription to monitor transactions based on filtering criteria.
52
+
53
+ Example request body:
54
+ \`\`\`json
55
+ {
56
+ "callback": "https://your-callback-url.com",
57
+ "fromBlock": 0,
58
+ "query": {
59
+ "find": {
60
+ "out.tape.cell.s": "BEEF"
61
+ }
62
+ }
63
+ }
64
+ \`\`\`
65
+
66
+ #### Delete Subscription
67
+ \`\`\`
68
+ DELETE /subscribe/{id}
69
+ \`\`\`
70
+
71
+ Deletes an existing subscription.
72
+
73
+ ### Network API
74
+
75
+ #### Get Network Info
76
+ \`\`\`
77
+ GET /network/info
78
+ \`\`\`
79
+
80
+ Returns current blockchain network information, including block height and other statistics.
81
+
82
+ ## Client Implementations
83
+
84
+ ### TypeScript Client
85
+
86
+ Installation:
87
+ \`\`\`bash
88
+ $ npm install @gorillapool/js-junglebus
89
+ \`\`\`
90
+
91
+ Usage:
92
+ \`\`\`javascript
93
+ import { JungleBusClient } from '@gorillapool/js-junglebus';
94
+
95
+ const server = "junglebus.gorillapool.io";
96
+ const jungleBusClient = new JungleBusClient(server, {
97
+ onConnected(ctx) {
98
+ // add your own code here
99
+ console.log(ctx);
100
+ },
101
+ onConnecting(ctx) {
102
+ // add your own code here
103
+ console.log(ctx);
104
+ },
105
+ onDisconnected(ctx) {
106
+ // add your own code here
107
+ console.log(ctx);
108
+ },
109
+ onError(ctx) {
110
+ // add your own code here
111
+ console.error(ctx);
112
+ }
113
+ });
114
+
115
+ // create subscriptions in the dashboard of the JungleBus website
116
+ const subId = "...."; // fill in the ID for the subscription
117
+ const fromBlock = 750000;
118
+
119
+ const subscription = jungleBusClient.Subscribe(
120
+ subId,
121
+ fromBlock,
122
+ onPublish(tx) => {
123
+ // add your own code here
124
+ console.log(tx);
125
+ },
126
+ onStatus(ctx) => {
127
+ // add your own code here
128
+ console.log(ctx);
129
+ },
130
+ onError(ctx) => {
131
+ // add your own code here
132
+ console.log(ctx);
133
+ },
134
+ onMempool(tx) => {
135
+ // add your own code here
136
+ console.log(tx);
137
+ }
138
+ );
139
+
140
+ // For lite mode (transaction hash and block height only)
141
+ await client.Subscribe("a5e2fa655c41753331539a2a86546bf9335ff6d9b7a512dc9acddb00ab9985c0", 1550000, onPublish, onStatus, onError, onMempool, true);
142
+ \`\`\`
143
+
144
+ ### Go Client
145
+
146
+ Installation:
147
+ \`\`\`bash
148
+ go get github.com/GorillaPool/go-junglebus
149
+ \`\`\`
150
+
151
+ Usage:
152
+ \`\`\`go
153
+ package main
154
+
155
+ import (
156
+ "context"
157
+ "log"
158
+ "sync"
159
+ "github.com/GorillaPool/go-junglebus"
160
+ "github.com/GorillaPool/go-junglebus/models"
161
+ )
162
+
163
+ func main() {
164
+ wg := &sync.WaitGroup{}
165
+
166
+ junglebusClient, err := junglebus.New(
167
+ junglebus.WithHTTP("https://junglebus.gorillapool.io"),
168
+ )
169
+ if err != nil {
170
+ log.Fatalln(err.Error())
171
+ }
172
+
173
+ subscriptionID := "..." // fill in the ID for the subscription
174
+ fromBlock := uint64(750000)
175
+
176
+ eventHandler := junglebus.EventHandler{
177
+ // do not set this function to leave out mined transactions
178
+ OnTransaction: func(tx *models.TransactionResponse) {
179
+ log.Printf("[TX]: %d: %v", tx.BlockHeight, tx.Id)
180
+ },
181
+ // do not set this function to leave out mempool transactions
182
+ OnMempool: func(tx *models.TransactionResponse) {
183
+ log.Printf("[MEMPOOL TX]: %v", tx.Id)
184
+ },
185
+ OnStatus: func(status *models.ControlResponse) {
186
+ log.Printf("[STATUS]: %v", status)
187
+ },
188
+ OnError: func(err error) {
189
+ log.Printf("[ERROR]: %v", err)
190
+ },
191
+ }
192
+
193
+ var subscription *junglebus.Subscription
194
+ if subscription, err = junglebusClient.Subscribe(context.Background(), subscriptionID, fromBlock, eventHandler); err != nil {
195
+ log.Printf("ERROR: failed getting subscription %s", err.Error())
196
+ }
197
+
198
+ // For lite mode
199
+ if subscription, err := junglebusClient.SubscribeWithQueue(context.Background(), subscriptionID, fromBlock, 0, eventHandler, &junglebus.SubscribeOptions{
200
+ QueueSize: 100000,
201
+ LiteMode: true,
202
+ }); err != nil {
203
+ log.Printf("ERROR: failed getting subscription %s", err.Error())
204
+ }
205
+
206
+ wg.Add(1)
207
+ wg.Wait()
208
+ }
209
+ \`\`\`
210
+
211
+ ## Further Reading
212
+
213
+ For complete API documentation, visit [JungleBus Docs](${JUNGLEBUS_DOCS_URL})
214
+ `;
215
+ }
216
+
217
+ /**
218
+ * Register the JungleBus API documentation resource with the MCP server
219
+ * @param server The MCP server instance
220
+ */
221
+ export function registerJungleBusResource(server: McpServer): void {
222
+ server.resource(
223
+ "junglebus-api-docs",
224
+ JUNGLEBUS_DOCS_URL,
225
+ {
226
+ title: "JungleBus API Documentation",
227
+ description:
228
+ "API documentation for JungleBus, a transaction monitoring service for Bitcoin SV",
229
+ },
230
+ async (uri) => {
231
+ const documentationContent = getJungleBusDocumentation();
232
+ return {
233
+ contents: [
234
+ {
235
+ uri: uri.href,
236
+ text: documentationContent,
237
+ },
238
+ ],
239
+ };
240
+ },
241
+ );
242
+ }
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { registerBRCsResources } from "./brcs";
3
3
  import { registerChangelogResource } from "./changelog";
4
+ import { registerJungleBusResource } from "./junglebus";
4
5
 
5
6
  /**
6
7
  * Register all resources with the MCP server
@@ -13,5 +14,8 @@ export function registerResources(server: McpServer): void {
13
14
  // Register changelog resource
14
15
  registerChangelogResource(server);
15
16
 
17
+ // Register JungleBus API documentation resource
18
+ registerJungleBusResource(server);
19
+
16
20
  // Add more resource categories here as needed
17
21
  }
@@ -6,25 +6,34 @@ import type {
6
6
  } from "@modelcontextprotocol/sdk/types.js";
7
7
  import { z } from "zod";
8
8
 
9
- type OverlayRequest = {
9
+ // API endpoint for the A2B Overlay service
10
+ const OVERLAY_API_URL = "https://a2b-overlay-production.up.railway.app/v1";
11
+
12
+ type A2BDiscoveryItem = {
13
+ txid: string;
14
+ outpoint: string;
10
15
  type: "agent" | "tool";
11
- query: string;
12
- limit: number;
13
- offset: number;
14
- fromBlock?: number;
15
- toBlock?: number;
16
+ app: string;
17
+ serverName: string;
18
+ command: string;
19
+ description: string;
20
+ keywords: string[];
21
+ args: Record<string, string>;
22
+ env: Record<string, string>;
23
+ blockHeight: number;
24
+ timestamp: string;
25
+ tools?: string[];
26
+ prompts?: string[];
27
+ resources?: string[];
16
28
  };
17
29
 
18
- type OverlayResponse = {
19
- agents: {
20
- name: string;
21
- description: string;
22
- capabilities: string[];
23
- }[];
24
- tools: {
25
- name: string;
26
- description: string;
27
- }[];
30
+ type OverlaySearchResponse = {
31
+ items: A2BDiscoveryItem[];
32
+ total: number;
33
+ limit: number;
34
+ offset: number;
35
+ queryType: "agent" | "tool" | "all";
36
+ query: string;
28
37
  };
29
38
 
30
39
  // Schema for agent discovery parameters
@@ -38,6 +47,73 @@ export const a2bDiscoverArgsSchema = z.object({
38
47
  });
39
48
  export type A2bDiscoverArgs = z.infer<typeof a2bDiscoverArgsSchema>;
40
49
 
50
+ /**
51
+ * Format the response in a user-friendly way
52
+ */
53
+ function formatSearchResults(data: unknown, queryType: string): string {
54
+ // console.log(`Received data: ${JSON.stringify(data).substring(0, 200)}...`); // Debug log
55
+
56
+ // Check if data is an object with items property
57
+ if (!data) {
58
+ return `No ${queryType} results found.`;
59
+ }
60
+
61
+ // Handle the new API format where data is an object with 'items' array
62
+ const items = Array.isArray(data)
63
+ ? data
64
+ : (data as { items?: A2BDiscoveryItem[] }).items;
65
+
66
+ // Ensure items is an array
67
+ if (!items || !Array.isArray(items) || items.length === 0) {
68
+ return `No ${queryType} results found.`;
69
+ }
70
+
71
+ let result = `Found ${items.length} ${queryType === "all" ? "items" : `${queryType}s`}:\n\n`;
72
+
73
+ items.forEach((item, index) => {
74
+ if (!item) return;
75
+
76
+ // Agent or MCP server name with description
77
+ result += `${index + 1}. **${item.serverName || "Unknown"}** - ${item.description || "No description"}\n`;
78
+
79
+ // Display command to run
80
+ if (item.command) {
81
+ const args = item.args ? Object.values(item.args).join(" ") : "";
82
+ result += ` Command: \`${item.command} ${args}\`\n`;
83
+ }
84
+
85
+ // Add tools count if available
86
+ if (item.tools && Array.isArray(item.tools)) {
87
+ result += ` Tools: ${item.tools.length} available\n`;
88
+ }
89
+
90
+ // Add keywords if available
91
+ if (
92
+ item.keywords &&
93
+ Array.isArray(item.keywords) &&
94
+ item.keywords.length > 0
95
+ ) {
96
+ result += ` Keywords: ${item.keywords.join(", ")}\n`;
97
+ }
98
+
99
+ // Add blockchain details
100
+ if (item.outpoint) {
101
+ result += ` Outpoint: ${item.outpoint}\n`;
102
+ }
103
+
104
+ if (item.blockHeight !== undefined) {
105
+ const date = item.timestamp
106
+ ? new Date(item.timestamp).toLocaleDateString()
107
+ : "Unknown date";
108
+ result += ` Block: ${item.blockHeight}, ${date}\n`;
109
+ }
110
+
111
+ result += "\n";
112
+ });
113
+
114
+ return result;
115
+ }
116
+
41
117
  /**
42
118
  * Registers the a2b_discover tool for on-chain agent discovery
43
119
  */
@@ -50,49 +126,113 @@ export function registerA2bDiscoverTool(server: McpServer) {
50
126
  { args }: { args: A2bDiscoverArgs },
51
127
  extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
52
128
  ) => {
53
- if (args.queryType === "agent") {
54
- return {
55
- content: [
56
- { type: "text", text: "Agent discovery is not supported yet" },
57
- ],
58
- isError: true,
59
- };
60
- }
61
-
62
- if (args.queryType !== "tool") {
63
- return {
64
- content: [
65
- {
66
- type: "text",
67
- text: "Only tool discovery is supported currently",
68
- },
69
- ],
70
- isError: true,
71
- };
72
- }
73
-
74
129
  try {
75
130
  const params = new URLSearchParams();
131
+
132
+ // Set query type (agent, tool, or all)
76
133
  params.set("type", args.queryType);
77
- params.set("query", args.query);
78
- params.set("limit", args.limit?.toString() ?? "5");
134
+
135
+ // Use enhanced search for better relevance scoring
136
+ let searchEndpoint = "/search/enhanced";
137
+
138
+ // For empty queries, use the regular search endpoint
139
+ if (!args.query || !args.query.trim()) {
140
+ searchEndpoint = "/search";
141
+ } else {
142
+ params.set("q", args.query); // enhanced search uses 'q' parameter
143
+ }
144
+
145
+ // Add pagination parameters
146
+ params.set("limit", args.limit?.toString() ?? "10");
79
147
  params.set("offset", args.offset?.toString() ?? "0");
148
+
149
+ // Add block range if specified
80
150
  if (args.fromBlock) {
81
151
  params.set("fromBlock", args.fromBlock.toString());
82
152
  }
83
153
  if (args.toBlock) {
84
154
  params.set("toBlock", args.toBlock.toString());
85
155
  }
86
- const OVERLAY_URL = `https://overlay.a2b.network/v1/search?${params.toString()}`;
87
- const response = await fetch(OVERLAY_URL);
88
- const data = (await response.json()) as OverlayResponse;
156
+
157
+ // Construct the full URL
158
+ const searchUrl = `${OVERLAY_API_URL}${searchEndpoint}?${params.toString()}`;
159
+ //console.log(`Searching URL: ${searchUrl}`);
160
+
161
+ // Make the request to the overlay API
162
+ const response = await fetch(searchUrl, {
163
+ method: "GET",
164
+ headers: {
165
+ Accept: "application/json",
166
+ },
167
+ });
168
+
169
+ if (!response.ok) {
170
+ throw new Error(
171
+ `API returned status ${response.status}: ${response.statusText}`,
172
+ );
173
+ }
174
+
175
+ const data = (await response.json()) as OverlaySearchResponse;
176
+
177
+ // Format the results for better readability
178
+ let result = "";
179
+
180
+ if (data?.items?.length > 0) {
181
+ result = `Found ${data.items.length} ${args.queryType}(s):\n\n`;
182
+
183
+ data.items.forEach((item: A2BDiscoveryItem, index: number) => {
184
+ // Server name and description
185
+ result += `${index + 1}. **${item.serverName || "Unknown"}** - ${item.description || "No description"}\n`;
186
+
187
+ // Command to run
188
+ if (item.command) {
189
+ const cmdArgs = item.args
190
+ ? Object.values(item.args).join(" ")
191
+ : "";
192
+ result += ` Command: \`${item.command} ${cmdArgs}\`\n`;
193
+ }
194
+
195
+ // Tools available
196
+ if (item.tools?.length) {
197
+ result += ` Tools: ${item.tools.length} available\n`;
198
+ }
199
+
200
+ // Keywords
201
+ if (item.keywords?.length) {
202
+ result += ` Keywords: ${item.keywords.join(", ")}\n`;
203
+ }
204
+
205
+ // Blockchain details
206
+ if (item.outpoint) {
207
+ result += ` Outpoint: ${item.outpoint}\n`;
208
+ }
209
+
210
+ if (item.blockHeight !== undefined) {
211
+ const date = item.timestamp
212
+ ? new Date(item.timestamp).toLocaleDateString()
213
+ : "Unknown date";
214
+ result += ` Block: ${item.blockHeight}, ${date}\n`;
215
+ }
216
+
217
+ result += "\n";
218
+ });
219
+ } else {
220
+ result = `No ${args.queryType} results found.`;
221
+ }
222
+
89
223
  return {
90
- content: [{ type: "text", text: JSON.stringify(data) }],
224
+ content: [{ type: "text", text: result }],
91
225
  isError: false,
92
226
  };
93
227
  } catch (error) {
228
+ console.error("Search error:", error);
94
229
  return {
95
- content: [{ type: "text", text: `Error querying overlay: ${error}` }],
230
+ content: [
231
+ {
232
+ type: "text",
233
+ text: `Error querying A2B Overlay: ${error instanceof Error ? error.message : String(error)}`,
234
+ },
235
+ ],
96
236
  isError: true,
97
237
  };
98
238
  }