bsv-mcp 0.0.33 → 0.0.34

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/CHANGELOG.md CHANGED
@@ -1,10 +1,18 @@
1
1
  # BSV MCP Server Changelog
2
2
 
3
+ ## v0.0.34 - Transaction Broadcast Control
4
+
5
+ ### Features
6
+ - Added `DISABLE_BROADCASTING` environment variable to control transaction broadcasting behavior
7
+ - When set to "true", transactions are created but not broadcast to the network
8
+ - Returns raw transaction hex instead of broadcasting, useful for testing and review
9
+ - Code cleanup and organization improvements
10
+
3
11
  ## v0.0.33 - Identity Key Sigma Signing
4
12
 
5
13
  ### Features
6
14
  - Added optional `IDENTITY_KEY_WIF` environment variable for sigma-protocol signing.
7
- - `wallet_a2bPublishMcp`, `wallet_createOrdinals`, and `wallet_purchaseListing` tools now support signing with an identity key.
15
+ - `wallet_createOrdinals`, and `wallet_purchaseListing` tools now support signing with an identity key.
8
16
  - Updated `README.md` to document `IDENTITY_KEY_WIF` usage and JSON configuration examples.
9
17
 
10
18
  ## v0.0.32 - Reliability Improvements
package/README.md CHANGED
@@ -365,6 +365,7 @@ The BSV MCP server can be customized using environment variables to enable or di
365
365
  | `DISABLE_ORDINALS_TOOLS` | `false` | Set to `true` to disable Ordinals/NFT tools |
366
366
  | `DISABLE_UTILS_TOOLS` | `false` | Set to `true` to disable utility tools |
367
367
  | `IDENTITY_KEY_WIF` | `not set` | Optional WIF for identity key; if set, ordinals inscriptions will be signed with sigma-protocol for authentication, curation, and web-of-trust. |
368
+ | `DISABLE_BROADCASTING` | `false` | Set to `true` to disable transaction broadcasting; returns raw transaction hex instead - useful for testing and transaction review before broadcasting |
368
369
 
369
370
  ### Examples
370
371
 
@@ -386,6 +387,12 @@ Use all tools except wallet operations:
386
387
  DISABLE_WALLET_TOOLS=true bunx bsv-mcp@latest
387
388
  ```
388
389
 
390
+ Create transactions without broadcasting them (dry-run mode):
391
+
392
+ ```bash
393
+ DISABLE_BROADCASTING=true bunx bsv-mcp@latest
394
+ ```
395
+
389
396
  ## Troubleshooting
390
397
 
391
398
  If you're having issues with the BSV MCP server:
@@ -427,6 +434,7 @@ For Cursor, check the Cursor MCP logs in Settings → Extensions → Model Conte
427
434
 
428
435
  ## Recent Updates
429
436
 
437
+ - **Transaction Broadcast Control**: Added `DISABLE_BROADCASTING` environment variable to prevent transactions from being broadcast to the network
430
438
  - **Blockchain Explorer**: Added `bsv_explore` tool for WhatsOnChain API access with mainnet/testnet support
431
439
  - **Unified Tools**: Merged `wallet_encrypt`/`wallet_decrypt` into single `wallet_encryption` tool
432
440
  - **Enhanced Marketplace**: Support for NFTs, BSV-20/21 tokens in listings, sales and purchases
package/index.ts CHANGED
@@ -24,7 +24,10 @@ const CONFIG = {
24
24
  loadBsvTools: process.env.DISABLE_BSV_TOOLS !== "true",
25
25
  loadOrdinalsTools: process.env.DISABLE_ORDINALS_TOOLS !== "true",
26
26
  loadUtilsTools: process.env.DISABLE_UTILS_TOOLS !== "true",
27
- loadA2bTools: process.env.DISABLE_A2B_TOOLS !== "true",
27
+ loadA2bTools: process.env.ENABLE_A2B_TOOLS === "true",
28
+
29
+ // Transaction broadcasting control
30
+ disableBroadcasting: process.env.DISABLE_BROADCASTING === "true",
28
31
  };
29
32
 
30
33
  /**
@@ -70,7 +73,7 @@ function initializePrivateKey(): PrivateKey | undefined {
70
73
  const privKey = initializePrivateKey();
71
74
 
72
75
  const server = new McpServer(
73
- { name: "Bitcoin SV", version: "0.0.33" },
76
+ { name: "Bitcoin SV", version: "0.0.34" },
74
77
  // {
75
78
  // // Advertise only what you actually implement
76
79
  // capabilities: {
@@ -102,7 +105,10 @@ if (CONFIG.loadTools) {
102
105
  // Initialize wallet with the private key if wallet tools are enabled
103
106
  if (CONFIG.loadWalletTools) {
104
107
  wallet = new Wallet(privKey);
105
- registerWalletTools(server, wallet);
108
+ registerWalletTools(server, wallet, {
109
+ disableBroadcasting: CONFIG.disableBroadcasting,
110
+ enableA2bTools: CONFIG.loadA2bTools,
111
+ });
106
112
  }
107
113
  }
108
114
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "bsv-mcp",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.0.33",
5
+ "version": "0.0.34",
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",
package/tools/index.ts CHANGED
@@ -32,14 +32,14 @@ export function registerAllTools(
32
32
  enableBsvTools: true,
33
33
  enableOrdinalsTools: true,
34
34
  enableUtilsTools: true,
35
- enableA2bTools: true,
35
+ enableA2bTools: false,
36
36
  },
37
37
  ): void {
38
38
  const {
39
39
  enableBsvTools = true,
40
40
  enableOrdinalsTools = true,
41
41
  enableUtilsTools = true,
42
- enableA2bTools = true,
42
+ enableA2bTools = false,
43
43
  } = config;
44
44
 
45
45
  // Register BSV-related tools
@@ -1,4 +1,4 @@
1
- import { Utils } from "@bsv/sdk";
1
+ import { PrivateKey, Utils } from "@bsv/sdk";
2
2
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
4
4
  import type {
@@ -8,10 +8,13 @@ import type {
8
8
  import { createOrdinals } from "js-1sat-ord";
9
9
  import type {
10
10
  ChangeResult,
11
+ CreateOrdinalsConfig,
11
12
  Destination,
12
13
  Inscription,
14
+ LocalSigner,
13
15
  PreMAP,
14
16
  } from "js-1sat-ord";
17
+ import { Sigma } from "sigma-protocol";
15
18
  import { z } from "zod";
16
19
  import type { Wallet } from "./wallet";
17
20
  const { toArray, toBase64 } = Utils;
@@ -246,32 +249,57 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
246
249
  // Default MAP metadata: file path, content type, encoding
247
250
  const metaData: PreMAP = { app: "bsv-mcp", type: "a2b" };
248
251
 
249
- // Inscribe the ordinal on-chain via js-1sat-ord
250
- const result = await createOrdinals({
252
+ const createOrdinalsConfig: CreateOrdinalsConfig = {
251
253
  utxos: paymentUtxos,
252
254
  destinations,
253
255
  paymentPk,
254
256
  changeAddress: walletAddress,
255
257
  metaData,
256
- });
258
+ };
259
+
260
+ const identityPk = process.env.IDENTITY_KEY_WIF
261
+ ? PrivateKey.fromWif(process.env.IDENTITY_KEY_WIF)
262
+ : undefined;
263
+ if (identityPk) {
264
+ createOrdinalsConfig.signer = {
265
+ idKey: identityPk,
266
+ } as LocalSigner;
267
+ }
268
+
269
+ // Inscribe the ordinal on-chain via js-1sat-ord
270
+ const result = await createOrdinals(createOrdinalsConfig);
257
271
  const changeResult = result as ChangeResult;
258
- await changeResult.tx.broadcast();
259
- // Refresh UTXOs
260
- try {
261
- await wallet.refreshUtxos();
262
- } catch {}
263
- // Return transaction details
272
+
273
+ const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
274
+ if (!disableBroadcasting) {
275
+ await changeResult.tx.broadcast();
276
+
277
+ // Refresh UTXOs
278
+ try {
279
+ await wallet.refreshUtxos();
280
+ } catch {}
281
+ // Return transaction details
282
+ return {
283
+ content: [
284
+ {
285
+ type: "text",
286
+ text: JSON.stringify({
287
+ txid: changeResult.tx.id("hex"),
288
+ spentOutpoints: changeResult.spentOutpoints,
289
+ payChange: changeResult.payChange,
290
+ inscriptionAddress: targetAddress,
291
+ agentCard,
292
+ }),
293
+ },
294
+ ],
295
+ };
296
+ }
297
+
264
298
  return {
265
299
  content: [
266
300
  {
267
301
  type: "text",
268
- text: JSON.stringify({
269
- txid: changeResult.tx.id("hex"),
270
- spentOutpoints: changeResult.spentOutpoints,
271
- payChange: changeResult.payChange,
272
- inscriptionAddress: targetAddress,
273
- agentCard,
274
- }),
302
+ text: changeResult.tx.toHex(),
275
303
  },
276
304
  ],
277
305
  };
@@ -32,9 +32,16 @@ export const a2bPublishMcpArgsSchema = z.object({
32
32
  command: z.string().describe("The command to execute the tool"),
33
33
  args: z.array(z.string()).describe("Arguments to pass to the command"),
34
34
  env: z
35
- .record(z.string())
35
+ .array(
36
+ z.object({
37
+ key: z.string().describe("Environment variable name"),
38
+ description: z
39
+ .string()
40
+ .describe("Description of the environment variable"),
41
+ }),
42
+ )
36
43
  .optional()
37
- .describe("Optional environment variables"),
44
+ .describe("Optional environment variables with descriptions"),
38
45
  description: z.string().optional().describe("Optional tool description"),
39
46
  destinationAddress: z
40
47
  .string()
@@ -47,7 +54,11 @@ export type A2bPublishMcpArgs = z.infer<typeof a2bPublishMcpArgsSchema>;
47
54
  /**
48
55
  * Registers the wallet_a2bPublishMcp for publishing an MCP tool configuration on-chain
49
56
  */
50
- export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
57
+ export function registerA2bPublishMcpTool(
58
+ server: McpServer,
59
+ wallet: Wallet,
60
+ config: { disableBroadcasting: boolean },
61
+ ) {
51
62
  server.tool(
52
63
  "wallet_a2bPublishMcp",
53
64
  "Publish an MCP tool configuration record on-chain via Ordinal inscription",
@@ -84,7 +95,15 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
84
95
  const toolConfig: McpConfig = {
85
96
  command: args.command,
86
97
  args: args.args,
87
- env: args.env,
98
+ env: args.env
99
+ ? args.env.reduce(
100
+ (acc, { key, description }) => {
101
+ acc[key] = description;
102
+ return acc;
103
+ },
104
+ {} as Record<string, string>,
105
+ )
106
+ : undefined,
88
107
  };
89
108
 
90
109
  // Validate compliance
@@ -94,7 +113,7 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
94
113
  const fullConfig = {
95
114
  mcpServers: {
96
115
  [args.toolName]: {
97
- description: args.description || `MCP Tool: ${args.toolName}`,
116
+ description: args.description || "",
98
117
  type: "mcp-tool",
99
118
  ...toolConfig,
100
119
  },
@@ -137,42 +156,53 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
137
156
  finalTx = signResponse.signedTx;
138
157
  }
139
158
  // Broadcast the transaction
140
- await finalTx.broadcast();
141
-
142
- // Refresh UTXOs after spending
143
- try {
144
- await wallet.refreshUtxos();
145
- } catch (refreshError) {
146
- console.warn(
147
- "Failed to refresh UTXOs after transaction:",
148
- refreshError,
149
- );
150
- }
151
-
152
- // Build a nicely formatted result
153
- const outpointIndex = 0; // First output with the inscription
154
- const outpoint = `${changeResult.tx.id("hex")}_${outpointIndex}`;
159
+ if (!config.disableBroadcasting) {
160
+ await finalTx.broadcast();
155
161
 
156
- // Tool URL for discovery is the outpoint
157
- const onchainUrl = `ord://${outpoint}`;
162
+ // Refresh UTXOs after spending
163
+ try {
164
+ await wallet.refreshUtxos();
165
+ } catch (refreshError) {
166
+ console.warn(
167
+ "Failed to refresh UTXOs after transaction:",
168
+ refreshError,
169
+ );
170
+ }
158
171
 
172
+ // Build a nicely formatted result
173
+ const outpointIndex = 0; // First output with the inscription
174
+ const outpoint = `${finalTx.id("hex")}_${outpointIndex}`;
175
+
176
+ // Tool URL for discovery is the outpoint
177
+ const onchainUrl = `ord://${outpoint}`;
178
+
179
+ return {
180
+ content: [
181
+ {
182
+ type: "text",
183
+ text: JSON.stringify(
184
+ {
185
+ status: "success",
186
+ txid: finalTx.id("hex"),
187
+ outpoint,
188
+ onchainUrl,
189
+ toolName: args.toolName,
190
+ description:
191
+ args.description || `MCP Tool: ${args.toolName}`,
192
+ address: targetAddress,
193
+ },
194
+ null,
195
+ 2,
196
+ ),
197
+ },
198
+ ],
199
+ };
200
+ }
159
201
  return {
160
202
  content: [
161
203
  {
162
204
  type: "text",
163
- text: JSON.stringify(
164
- {
165
- status: "success",
166
- txid: changeResult.tx.id("hex"),
167
- outpoint,
168
- onchainUrl,
169
- toolName: args.toolName,
170
- description: args.description || `MCP Tool: ${args.toolName}`,
171
- address: targetAddress,
172
- },
173
- null,
174
- 2,
175
- ),
205
+ text: finalTx.toHex(),
176
206
  },
177
207
  ],
178
208
  };
@@ -11,8 +11,10 @@ import type {
11
11
  ChangeResult,
12
12
  CreateOrdinalsCollectionItemMetadata,
13
13
  CreateOrdinalsCollectionMetadata,
14
+ CreateOrdinalsConfig,
14
15
  Destination,
15
16
  Inscription,
17
+ LocalSigner,
16
18
  PreMAP,
17
19
  } from "js-1sat-ord";
18
20
  import { Sigma } from "sigma-protocol";
@@ -101,8 +103,7 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
101
103
  },
102
104
  ];
103
105
 
104
- // 6. Create and broadcast the transaction
105
- const result = await createOrdinals({
106
+ const createOrdinalsConfig: CreateOrdinalsConfig = {
106
107
  utxos: paymentUtxos,
107
108
  destinations,
108
109
  paymentPk,
@@ -111,41 +112,57 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
111
112
  | PreMAP
112
113
  | CreateOrdinalsCollectionMetadata
113
114
  | CreateOrdinalsCollectionItemMetadata,
114
- });
115
+ };
116
+
117
+ if (identityPk) {
118
+ createOrdinalsConfig.signer = {
119
+ idKey: identityPk,
120
+ } as LocalSigner;
121
+ }
122
+
123
+ // 6. Create and broadcast the transaction
124
+ const result = await createOrdinals(createOrdinalsConfig);
115
125
 
116
126
  const changeResult = result as ChangeResult;
117
127
 
118
128
  // 7. Optionally sign with identity key and broadcast the transaction
119
- let finalTx = changeResult.tx;
120
- if (identityPk) {
121
- const sigma = new Sigma(changeResult.tx);
122
- const signResponse = sigma.sign(identityPk);
123
- finalTx = signResponse.signedTx;
124
- }
125
- await finalTx.broadcast();
126
-
127
- // 8. Refresh the wallet's UTXOs after spending
128
- try {
129
- await wallet.refreshUtxos();
130
- } catch (refreshError) {
131
- console.warn(
132
- "Failed to refresh UTXOs after transaction:",
133
- refreshError,
134
- );
129
+
130
+ const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
131
+ if (!disableBroadcasting) {
132
+ await changeResult.tx.broadcast();
133
+
134
+ // 8. Refresh the wallet's UTXOs after spending
135
+ try {
136
+ await wallet.refreshUtxos();
137
+ } catch (refreshError) {
138
+ console.warn(
139
+ "Failed to refresh UTXOs after transaction:",
140
+ refreshError,
141
+ );
142
+ }
143
+
144
+ // 9. Return transaction details
145
+ return {
146
+ content: [
147
+ {
148
+ type: "text",
149
+ text: JSON.stringify({
150
+ txid: changeResult.tx.id("hex"),
151
+ spentOutpoints: changeResult.spentOutpoints,
152
+ payChange: changeResult.payChange,
153
+ inscriptionAddress: args.destinationAddress || walletAddress,
154
+ contentType: args.contentType,
155
+ }),
156
+ },
157
+ ],
158
+ };
135
159
  }
136
160
 
137
- // 9. Return transaction details
138
161
  return {
139
162
  content: [
140
163
  {
141
164
  type: "text",
142
- text: JSON.stringify({
143
- txid: changeResult.tx.id("hex"),
144
- spentOutpoints: changeResult.spentOutpoints,
145
- payChange: changeResult.payChange,
146
- inscriptionAddress: args.destinationAddress || walletAddress,
147
- contentType: args.contentType,
148
- }),
165
+ text: changeResult.tx.toHex(),
149
166
  },
150
167
  ],
151
168
  };
@@ -9,7 +9,9 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
9
9
  import {
10
10
  type ChangeResult,
11
11
  type ExistingListing,
12
+ type LocalSigner,
12
13
  type Payment,
14
+ type PurchaseOrdListingConfig,
13
15
  type Royalty,
14
16
  TokenType,
15
17
  type TokenUtxo,
@@ -252,7 +254,7 @@ Please fund this wallet address with enough BSV to cover the purchase price
252
254
  }
253
255
  }
254
256
 
255
- transaction = await purchaseOrdListing({
257
+ const purchaseOrdListingConfig: PurchaseOrdListingConfig = {
256
258
  utxos: paymentUtxos,
257
259
  paymentPk,
258
260
  ordAddress: args.ordAddress,
@@ -260,7 +262,9 @@ Please fund this wallet address with enough BSV to cover the purchase price
260
262
  additionalPayments,
261
263
  metaData,
262
264
  royalties,
263
- });
265
+ };
266
+
267
+ transaction = await purchaseOrdListing(purchaseOrdListingConfig);
264
268
  }
265
269
 
266
270
  // After successful transaction creation, refresh the wallet's UTXOs
@@ -272,47 +276,54 @@ Please fund this wallet address with enough BSV to cover the purchase price
272
276
  }
273
277
 
274
278
  // Optionally sign with identity key then broadcast the transaction
275
- let finalTx = transaction.tx;
276
- if (identityPk) {
277
- const sigma = new Sigma(finalTx);
278
- const signResponse = sigma.sign(identityPk);
279
- finalTx = signResponse.signedTx;
280
- }
281
- const broadcastResult = await finalTx.broadcast(oneSatBroadcaster());
282
279
 
283
- // Handle broadcast response
284
- const resultStatus =
285
- typeof broadcastResult === "object" && "status" in broadcastResult
286
- ? broadcastResult.status
287
- : "unknown";
280
+ const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
281
+ if (!disableBroadcasting) {
282
+ const broadcastResult = await transaction.tx.broadcast(
283
+ oneSatBroadcaster(),
284
+ );
285
+ // Handle broadcast response
286
+ const resultStatus =
287
+ typeof broadcastResult === "object" && "status" in broadcastResult
288
+ ? broadcastResult.status
289
+ : "unknown";
288
290
 
289
- const resultMessage =
290
- typeof broadcastResult === "object" && "error" in broadcastResult
291
- ? broadcastResult.error
292
- : "Transaction broadcast successful";
291
+ const resultMessage =
292
+ typeof broadcastResult === "object" && "error" in broadcastResult
293
+ ? broadcastResult.error
294
+ : "Transaction broadcast successful";
293
295
 
296
+ return {
297
+ content: [
298
+ {
299
+ type: "text",
300
+ text: JSON.stringify({
301
+ status: resultStatus,
302
+ message: resultMessage,
303
+ txid: transaction.tx.id("hex"),
304
+ listingOutpoint: args.listingOutpoint,
305
+ destinationAddress: args.ordAddress,
306
+ listingType: args.listingType,
307
+ tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
308
+ tokenID: args.tokenID,
309
+ price: listingData.data.list.price,
310
+ marketFee,
311
+ marketFeeAddress: MARKET_WALLET_ADDRESS,
312
+ royaltiesPaid:
313
+ args.listingType === "nft" &&
314
+ listingData.origin?.data?.map?.royalties
315
+ ? JSON.parse(listingData.origin.data.map.royalties)
316
+ : undefined,
317
+ }),
318
+ },
319
+ ],
320
+ };
321
+ }
294
322
  return {
295
323
  content: [
296
324
  {
297
325
  type: "text",
298
- text: JSON.stringify({
299
- status: resultStatus,
300
- message: resultMessage,
301
- txid: transaction.tx.id("hex"),
302
- listingOutpoint: args.listingOutpoint,
303
- destinationAddress: args.ordAddress,
304
- listingType: args.listingType,
305
- tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
306
- tokenID: args.tokenID,
307
- price: listingData.data.list.price,
308
- marketFee,
309
- marketFeeAddress: MARKET_WALLET_ADDRESS,
310
- royaltiesPaid:
311
- args.listingType === "nft" &&
312
- listingData.origin?.data?.map?.royalties
313
- ? JSON.parse(listingData.origin.data.map.royalties)
314
- : undefined,
315
- }),
326
+ text: transaction.tx.toHex(),
316
327
  },
317
328
  ],
318
329
  };
@@ -7,7 +7,12 @@ import type {
7
7
  ServerRequest,
8
8
  } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { sendOrdinals } from "js-1sat-ord";
10
- import type { ChangeResult, SendOrdinalsConfig } from "js-1sat-ord";
10
+ import type {
11
+ ChangeResult,
12
+ LocalSigner,
13
+ SendOrdinalsConfig,
14
+ } from "js-1sat-ord";
15
+ import { Sigma } from "sigma-protocol";
11
16
  import { z } from "zod";
12
17
  import type { Wallet } from "./wallet";
13
18
 
@@ -92,17 +97,15 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
92
97
  };
93
98
 
94
99
  const identityKeyWif = process.env.IDENTITY_KEY_WIF;
95
- let identityPk: PrivateKey | undefined;
96
- if (identityKeyWif) {
97
- try {
98
- identityPk = PrivateKey.fromWif(identityKeyWif);
99
- } catch (e) {
100
- console.warn(
101
- "Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
102
- e,
103
- );
104
- }
100
+ const identityPk = identityKeyWif
101
+ ? PrivateKey.fromWif(identityKeyWif)
102
+ : undefined;
103
+ if (identityPk) {
104
+ sendOrdinalsConfig.signer = {
105
+ idKey: identityPk,
106
+ } as LocalSigner;
105
107
  }
108
+
106
109
  // Add metadata if provided
107
110
  if (args.metadata) {
108
111
  sendOrdinalsConfig.metaData = args.metadata;
@@ -113,32 +116,45 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
113
116
 
114
117
  const result = await sendOrdinals(sendOrdinalsConfig);
115
118
  const changeResult = result as ChangeResult;
119
+ // no signing when you send since we don't emit an inscription
116
120
 
117
121
  // 7. Broadcast the transaction
118
- await changeResult.tx.broadcast();
119
-
120
- // 8. Refresh the wallet's UTXOs after spending
121
- try {
122
- await wallet.refreshUtxos();
123
- } catch (refreshError) {
124
- console.warn(
125
- "Failed to refresh UTXOs after transaction:",
126
- refreshError,
127
- );
122
+ const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
123
+ if (!disableBroadcasting) {
124
+ await changeResult.tx.broadcast();
125
+
126
+ // 8. Refresh the wallet's UTXOs after spending
127
+ try {
128
+ await wallet.refreshUtxos();
129
+ } catch (refreshError) {
130
+ console.warn(
131
+ "Failed to refresh UTXOs after transaction:",
132
+ refreshError,
133
+ );
134
+ }
135
+
136
+ // 9. Return transaction details
137
+ return {
138
+ content: [
139
+ {
140
+ type: "text",
141
+ text: JSON.stringify({
142
+ txid: changeResult.tx.id("hex"),
143
+ spentOutpoints: changeResult.spentOutpoints,
144
+ payChange: changeResult.payChange,
145
+ inscriptionOutpoint: args.inscriptionOutpoint,
146
+ destinationAddress: args.destinationAddress,
147
+ }),
148
+ },
149
+ ],
150
+ };
128
151
  }
129
152
 
130
- // 9. Return transaction details
131
153
  return {
132
154
  content: [
133
155
  {
134
156
  type: "text",
135
- text: JSON.stringify({
136
- txid: changeResult.tx.id("hex"),
137
- spentOutpoints: changeResult.spentOutpoints,
138
- payChange: changeResult.payChange,
139
- inscriptionOutpoint: args.inscriptionOutpoint,
140
- destinationAddress: args.destinationAddress,
141
- }),
157
+ text: changeResult.tx.toHex(),
142
158
  },
143
159
  ],
144
160
  };
@@ -99,6 +99,10 @@ type ToolHandlerMap = {
99
99
  export function registerWalletTools(
100
100
  server: McpServer,
101
101
  wallet: Wallet,
102
+ config: {
103
+ disableBroadcasting: boolean;
104
+ enableA2bTools: boolean;
105
+ },
102
106
  ): ToolHandlerMap {
103
107
  const handlers = {} as ToolHandlerMap;
104
108
 
@@ -129,11 +133,16 @@ export function registerWalletTools(
129
133
  // Register the wallet_transferOrdToken tool
130
134
  registerTransferOrdTokenTool(server, wallet);
131
135
 
132
- // Register the wallet_a2bPublishAgent tool
133
- // registerA2bPublishAgentTool(server, wallet);
136
+ // A2B tools have to be explicitly enabled
137
+ if (config.enableA2bTools) {
138
+ // Register the wallet_a2bPublishAgent tool
139
+ // registerA2bPublishAgentTool(server, wallet);
134
140
 
135
- // Register the wallet_a2bPublishMcp tool
136
- registerA2bPublishMcpTool(server, wallet);
141
+ // Register the wallet_a2bPublishMcp tool
142
+ registerA2bPublishMcpTool(server, wallet, {
143
+ disableBroadcasting: config.disableBroadcasting,
144
+ });
145
+ }
137
146
 
138
147
  // Register only the minimal public-facing tools
139
148
  // wallet_createAction, wallet_signAction and wallet_getHeight have been removed
@@ -1,3 +1,4 @@
1
+ import { PrivateKey } from "@bsv/sdk";
1
2
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
4
  import type {
@@ -6,6 +7,7 @@ import type {
6
7
  } from "@modelcontextprotocol/sdk/types.js";
7
8
  import {
8
9
  type Distribution,
10
+ type LocalSigner,
9
11
  type Payment,
10
12
  type TokenChangeResult,
11
13
  TokenInputMode,
@@ -106,26 +108,47 @@ export function registerTransferOrdTokenTool(
106
108
  },
107
109
  };
108
110
 
111
+ const identityPk = process.env.IDENTITY_KEY_WIF
112
+ ? PrivateKey.fromWif(process.env.IDENTITY_KEY_WIF)
113
+ : undefined;
114
+
115
+ if (identityPk) {
116
+ config.signer = {
117
+ idKey: identityPk,
118
+ } as LocalSigner;
119
+ }
120
+
109
121
  // execute transfer
110
122
  const result: TokenChangeResult = await transferOrdTokens(config);
111
- await result.tx.broadcast();
123
+ const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
124
+ if (!disableBroadcasting) {
125
+ await result.tx.broadcast();
112
126
 
113
- // refresh UTXOs
114
- try {
115
- await wallet.refreshUtxos();
116
- } catch {}
127
+ // refresh UTXOs
128
+ try {
129
+ await wallet.refreshUtxos();
130
+ } catch {}
117
131
 
118
- // respond
132
+ // respond
133
+ return {
134
+ content: [
135
+ {
136
+ type: "text",
137
+ text: JSON.stringify({
138
+ txid: result.tx.id("hex"),
139
+ spentOutpoints: result.spentOutpoints,
140
+ payChange: result.payChange,
141
+ tokenChange: result.tokenChange,
142
+ }),
143
+ },
144
+ ],
145
+ };
146
+ }
119
147
  return {
120
148
  content: [
121
149
  {
122
150
  type: "text",
123
- text: JSON.stringify({
124
- txid: result.tx.id("hex"),
125
- spentOutpoints: result.spentOutpoints,
126
- payChange: result.payChange,
127
- tokenChange: result.tokenChange,
128
- }),
151
+ text: result.tx.toHex(),
129
152
  },
130
153
  ],
131
154
  };
@@ -82,12 +82,20 @@ export class Wallet extends ProtoWallet implements WalletInterface {
82
82
  }
83
83
 
84
84
  const address = privateKey.toAddress();
85
-
86
- const utxos = await fetchPayUtxos(address);
87
- const nftUtxos = await fetchNftUtxos(address);
88
- this.paymentUtxos = utxos;
89
- this.nftUtxos = nftUtxos;
90
85
  this.lastUtxoFetch = Date.now();
86
+
87
+ try {
88
+ const utxos = await fetchPayUtxos(address);
89
+ this.paymentUtxos = utxos;
90
+ } catch (error) {
91
+ console.error("Error fetching payment UTXOs:", error);
92
+ }
93
+ try {
94
+ const nftUtxos = await fetchNftUtxos(address);
95
+ this.nftUtxos = nftUtxos;
96
+ } catch (error) {
97
+ console.error("Error fetching NFT UTXOs:", error);
98
+ }
91
99
  } catch (error) {
92
100
  console.error("Error refreshing UTXOs:", error);
93
101
  throw error;