bsv-mcp 0.0.32 → 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,5 +1,20 @@
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
+
11
+ ## v0.0.33 - Identity Key Sigma Signing
12
+
13
+ ### Features
14
+ - Added optional `IDENTITY_KEY_WIF` environment variable for sigma-protocol signing.
15
+ - `wallet_createOrdinals`, and `wallet_purchaseListing` tools now support signing with an identity key.
16
+ - Updated `README.md` to document `IDENTITY_KEY_WIF` usage and JSON configuration examples.
17
+
3
18
  ## v0.0.32 - Reliability Improvements
4
19
 
5
20
  ### Bug Fixes
package/README.md CHANGED
@@ -18,8 +18,7 @@ This project is built using [Bun](https://bun.sh/), a fast JavaScript runtime an
18
18
 
19
19
  **macOS (using Homebrew):**
20
20
  ```bash
21
- brew tap oven-sh/bun
22
- brew install bun
21
+ brew install oven-sh/bun/bun
23
22
  ```
24
23
 
25
24
  **macOS/Linux/WSL (using installer script):**
@@ -38,7 +37,7 @@ This server implements the [Model Context Protocol](https://modelcontextprotocol
38
37
 
39
38
  ![MCP Configuration Example](docs/images/mcp-config-example.png)
40
39
 
41
- > **Note:** The PRIVATE_KEY_WIF environment variable is now optional. Without it, the server runs in limited mode with educational resources and non-wallet tools available. Wallet and MNEE token operations require a valid private key.
40
+ > **Note:** The `PRIVATE_KEY_WIF` environment variable is now optional. Without it, the server runs in limited mode with educational resources and non-wallet tools available. Wallet and MNEE token operations require a valid private key. You can also set the `IDENTITY_KEY_WIF` environment variable to enable sigma-protocol signing of ordinals inscriptions for authentication, curation, and web-of-trust.
42
41
 
43
42
  ### Cursor
44
43
 
@@ -58,14 +57,15 @@ To use the BSV MCP server with [Cursor](https://cursor.sh/):
58
57
  "bsv-mcp@latest"
59
58
  ],
60
59
  "env": {
61
- "PRIVATE_KEY_WIF": "<your_private_key_wif>"
60
+ "PRIVATE_KEY_WIF": "<your_private_key_wif>",
61
+ "IDENTITY_KEY_WIF": "<your_identity_key_wif>"
62
62
  }
63
63
  }
64
64
  }
65
65
  }
66
66
  ```
67
67
 
68
- 5. Replace `<your_private_key_wif>` with your actual private key WIF (keep this secure!) If you dont have one you can leave this off for now but you wont be able to use tools that require a wallet.
68
+ 5. Replace `<your_private_key_wif>` with your actual private key WIF (keep this secure!) If you dont have one you can leave this off for now but you wont be able to use tools that require a wallet. `<your_identity_key_wif>` is also optional. It will sign 1Sat Ordinals with Sigma protocol using the provided identity key.
69
69
 
70
70
  6. Click "Save"
71
71
 
@@ -84,7 +84,8 @@ If you prefer to use npm instead of Bun:
84
84
  "bsv-mcp@latest"
85
85
  ],
86
86
  "env": {
87
- "PRIVATE_KEY_WIF": "<your_private_key_wif>"
87
+ "PRIVATE_KEY_WIF": "<your_private_key_wif>",
88
+ "IDENTITY_KEY_WIF": "<your_identity_key_wif>"
88
89
  }
89
90
  }
90
91
  }
@@ -118,7 +119,8 @@ Open the Claude configuration json file in your favorite text editor. If you pre
118
119
  "run", "bsv-mcp@latest"
119
120
  ],
120
121
  "env": {
121
- "PRIVATE_KEY_WIF": "<your_private_key_wif>"
122
+ "PRIVATE_KEY_WIF": "<your_private_key_wif>",
123
+ "IDENTITY_KEY_WIF": "<your_identity_key_wif>"
122
124
  }
123
125
  }
124
126
  }
@@ -362,6 +364,8 @@ The BSV MCP server can be customized using environment variables to enable or di
362
364
  | `DISABLE_BSV_TOOLS` | `false` | Set to `true` to disable BSV blockchain tools |
363
365
  | `DISABLE_ORDINALS_TOOLS` | `false` | Set to `true` to disable Ordinals/NFT tools |
364
366
  | `DISABLE_UTILS_TOOLS` | `false` | Set to `true` to disable utility tools |
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 |
365
369
 
366
370
  ### Examples
367
371
 
@@ -383,6 +387,12 @@ Use all tools except wallet operations:
383
387
  DISABLE_WALLET_TOOLS=true bunx bsv-mcp@latest
384
388
  ```
385
389
 
390
+ Create transactions without broadcasting them (dry-run mode):
391
+
392
+ ```bash
393
+ DISABLE_BROADCASTING=true bunx bsv-mcp@latest
394
+ ```
395
+
386
396
  ## Troubleshooting
387
397
 
388
398
  If you're having issues with the BSV MCP server:
@@ -424,6 +434,7 @@ For Cursor, check the Cursor MCP logs in Settings → Extensions → Model Conte
424
434
 
425
435
  ## Recent Updates
426
436
 
437
+ - **Transaction Broadcast Control**: Added `DISABLE_BROADCASTING` environment variable to prevent transactions from being broadcast to the network
427
438
  - **Blockchain Explorer**: Added `bsv_explore` tool for WhatsOnChain API access with mainnet/testnet support
428
439
  - **Unified Tools**: Merged `wallet_encrypt`/`wallet_decrypt` into single `wallet_encryption` tool
429
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.32" },
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.32",
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",
@@ -52,6 +52,7 @@
52
52
  "js-1sat-ord": "^0.1.81",
53
53
  "mnee": "^2.0.0",
54
54
  "satoshi-token": "^0.0.4",
55
+ "sigma-protocol": "^0.1.6",
55
56
  "zod": "^3.24.3"
56
57
  },
57
58
  "scripts": {
package/smithery.yaml CHANGED
@@ -5,17 +5,74 @@ startCommand:
5
5
  type: stdio
6
6
  configSchema:
7
7
  type: object
8
- required:
9
- - privateKeyWif
10
8
  properties:
11
9
  privateKeyWif:
12
10
  type: string
13
- description: "The private key WIF (Wallet Import Format) for Bitcoin SV transactions. This key is used to sign transactions and is required for wallet operations."
11
+ title: "Private Key (WIF)"
12
+ description: "The private key WIF (Wallet Import Format) for Bitcoin SV transactions. Optional but required for wallet operations. Without this, the server runs in limited mode with only educational resources and non-wallet tools."
13
+ disablePrompts:
14
+ type: boolean
15
+ title: "Disable Prompts"
16
+ description: "Set to true to disable all educational prompts"
17
+ default: false
18
+ disableResources:
19
+ type: boolean
20
+ title: "Disable Resources"
21
+ description: "Set to true to disable all resources (BRCs, changelog)"
22
+ default: false
23
+ disableTools:
24
+ type: boolean
25
+ title: "Disable All Tools"
26
+ description: "Set to true to disable all tools"
27
+ default: false
28
+ disableWalletTools:
29
+ type: boolean
30
+ title: "Disable Wallet Tools"
31
+ description: "Set to true to disable Bitcoin wallet tools"
32
+ default: false
33
+ disableMneeTools:
34
+ type: boolean
35
+ title: "Disable MNEE Tools"
36
+ description: "Set to true to disable MNEE token tools"
37
+ default: false
38
+ disableBsvTools:
39
+ type: boolean
40
+ title: "Disable BSV Blockchain Tools"
41
+ description: "Set to true to disable BSV blockchain tools"
42
+ default: false
43
+ disableOrdinalsTools:
44
+ type: boolean
45
+ title: "Disable Ordinals Tools"
46
+ description: "Set to true to disable Ordinals/NFT tools"
47
+ default: false
48
+ disableUtilsTools:
49
+ type: boolean
50
+ title: "Disable Utility Tools"
51
+ description: "Set to true to disable utility tools"
52
+ default: false
53
+ additionalProperties: false
14
54
  commandFunction: |
15
- (config) => ({
16
- command: 'bun',
17
- args: ['run', 'index.ts'],
18
- env: {
19
- PRIVATE_KEY_WIF: config.privateKeyWif
20
- }
21
- })
55
+ (config) => {
56
+ const env = {};
57
+
58
+ // Add private key if provided
59
+ if (config.privateKeyWif) {
60
+ env.PRIVATE_KEY_WIF = config.privateKeyWif;
61
+ }
62
+
63
+ // Map boolean config options to environment variables
64
+ if (config.disablePrompts) env.DISABLE_PROMPTS = 'true';
65
+ if (config.disableResources) env.DISABLE_RESOURCES = 'true';
66
+ if (config.disableTools) env.DISABLE_TOOLS = 'true';
67
+ if (config.disableWalletTools) env.DISABLE_WALLET_TOOLS = 'true';
68
+ if (config.disableMneeTools) env.DISABLE_MNEE_TOOLS = 'true';
69
+ if (config.disableBsvTools) env.DISABLE_BSV_TOOLS = 'true';
70
+ if (config.disableOrdinalsTools) env.DISABLE_ORDINALS_TOOLS = 'true';
71
+ if (config.disableUtilsTools) env.DISABLE_UTILS_TOOLS = 'true';
72
+
73
+ return {
74
+ command: 'bun',
75
+ args: ['run', 'index.ts'],
76
+ env
77
+ };
78
+ }
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
  };
@@ -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 {
@@ -12,6 +12,7 @@ import type {
12
12
  Inscription,
13
13
  PreMAP,
14
14
  } from "js-1sat-ord";
15
+ import { Sigma } from "sigma-protocol";
15
16
  import { z } from "zod";
16
17
  import type { Wallet } from "./wallet";
17
18
  const { toArray, toBase64 } = Utils;
@@ -31,9 +32,16 @@ export const a2bPublishMcpArgsSchema = z.object({
31
32
  command: z.string().describe("The command to execute the tool"),
32
33
  args: z.array(z.string()).describe("Arguments to pass to the command"),
33
34
  env: z
34
- .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
+ )
35
43
  .optional()
36
- .describe("Optional environment variables"),
44
+ .describe("Optional environment variables with descriptions"),
37
45
  description: z.string().optional().describe("Optional tool description"),
38
46
  destinationAddress: z
39
47
  .string()
@@ -46,7 +54,11 @@ export type A2bPublishMcpArgs = z.infer<typeof a2bPublishMcpArgsSchema>;
46
54
  /**
47
55
  * Registers the wallet_a2bPublishMcp for publishing an MCP tool configuration on-chain
48
56
  */
49
- export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
57
+ export function registerA2bPublishMcpTool(
58
+ server: McpServer,
59
+ wallet: Wallet,
60
+ config: { disableBroadcasting: boolean },
61
+ ) {
50
62
  server.tool(
51
63
  "wallet_a2bPublishMcp",
52
64
  "Publish an MCP tool configuration record on-chain via Ordinal inscription",
@@ -56,6 +68,20 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
56
68
  extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
57
69
  ) => {
58
70
  try {
71
+ // Load optional identity key for sigma signing
72
+ const identityKeyWif = process.env.IDENTITY_KEY_WIF;
73
+ let identityPk: PrivateKey | undefined;
74
+ if (identityKeyWif) {
75
+ try {
76
+ identityPk = PrivateKey.fromWif(identityKeyWif);
77
+ } catch (e) {
78
+ console.warn(
79
+ "Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
80
+ e,
81
+ );
82
+ }
83
+ }
84
+
59
85
  const paymentPk = wallet.getPrivateKey();
60
86
  if (!paymentPk) throw new Error("No private key available");
61
87
 
@@ -69,7 +95,15 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
69
95
  const toolConfig: McpConfig = {
70
96
  command: args.command,
71
97
  args: args.args,
72
- 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,
73
107
  };
74
108
 
75
109
  // Validate compliance
@@ -77,10 +111,13 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
77
111
 
78
112
  // Prepare the full configuration with metadata
79
113
  const fullConfig = {
80
- name: args.toolName,
81
- description: args.description || `MCP Tool: ${args.toolName}`,
82
- config: toolConfig,
83
- type: "mcp-tool",
114
+ mcpServers: {
115
+ [args.toolName]: {
116
+ description: args.description || "",
117
+ type: "mcp-tool",
118
+ ...toolConfig,
119
+ },
120
+ },
84
121
  };
85
122
 
86
123
  const fileContent = JSON.stringify(fullConfig, null, 2);
@@ -112,43 +149,60 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
112
149
 
113
150
  const changeResult = result as ChangeResult;
114
151
 
152
+ let finalTx = changeResult.tx;
153
+ if (identityPk) {
154
+ const sigma = new Sigma(result.tx);
155
+ const signResponse = sigma.sign(identityPk);
156
+ finalTx = signResponse.signedTx;
157
+ }
115
158
  // Broadcast the transaction
116
- await changeResult.tx.broadcast();
117
-
118
- // Refresh UTXOs after spending
119
- try {
120
- await wallet.refreshUtxos();
121
- } catch (refreshError) {
122
- console.warn(
123
- "Failed to refresh UTXOs after transaction:",
124
- refreshError,
125
- );
159
+ if (!config.disableBroadcasting) {
160
+ await finalTx.broadcast();
161
+
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
+ }
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
+ };
126
200
  }
127
-
128
- // Build a nicely formatted result
129
- const outpointIndex = 0; // First output with the inscription
130
- const outpoint = `${changeResult.tx.id("hex")}_${outpointIndex}`;
131
-
132
- // Tool URL for discovery is the outpoint
133
- const onchainUrl = `ord://${outpoint}`;
134
-
135
201
  return {
136
202
  content: [
137
203
  {
138
204
  type: "text",
139
- text: JSON.stringify(
140
- {
141
- status: "success",
142
- txid: changeResult.tx.id("hex"),
143
- outpoint,
144
- onchainUrl,
145
- toolName: args.toolName,
146
- description: args.description || `MCP Tool: ${args.toolName}`,
147
- address: targetAddress,
148
- },
149
- null,
150
- 2,
151
- ),
205
+ text: finalTx.toHex(),
152
206
  },
153
207
  ],
154
208
  };
@@ -1,15 +1,23 @@
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
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
+ import type {
5
+ CallToolResult,
6
+ ServerNotification,
7
+ ServerRequest,
8
+ } from "@modelcontextprotocol/sdk/types.js";
4
9
  import { createOrdinals } from "js-1sat-ord";
5
10
  import type {
6
11
  ChangeResult,
7
12
  CreateOrdinalsCollectionItemMetadata,
8
13
  CreateOrdinalsCollectionMetadata,
14
+ CreateOrdinalsConfig,
9
15
  Destination,
10
16
  Inscription,
17
+ LocalSigner,
11
18
  PreMAP,
12
19
  } from "js-1sat-ord";
20
+ import { Sigma } from "sigma-protocol";
13
21
  import { z } from "zod";
14
22
  import type { Wallet } from "./wallet";
15
23
 
@@ -47,9 +55,23 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
47
55
  { args: createOrdinalsArgsSchema },
48
56
  async (
49
57
  { args }: { args: CreateOrdinalsArgs },
50
- extra: RequestHandlerExtra,
58
+ extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
51
59
  ): Promise<CallToolResult> => {
52
60
  try {
61
+ // Load optional identity key for sigma signing
62
+ const identityKeyWif = process.env.IDENTITY_KEY_WIF;
63
+ let identityPk: PrivateKey | undefined;
64
+ if (identityKeyWif) {
65
+ try {
66
+ identityPk = PrivateKey.fromWif(identityKeyWif);
67
+ } catch (e) {
68
+ console.warn(
69
+ "Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
70
+ e,
71
+ );
72
+ }
73
+ }
74
+
53
75
  // 1. Get private key from wallet
54
76
  const paymentPk = wallet.getPrivateKey();
55
77
  if (!paymentPk) {
@@ -81,8 +103,7 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
81
103
  },
82
104
  ];
83
105
 
84
- // 6. Create and broadcast the transaction
85
- const result = await createOrdinals({
106
+ const createOrdinalsConfig: CreateOrdinalsConfig = {
86
107
  utxos: paymentUtxos,
87
108
  destinations,
88
109
  paymentPk,
@@ -91,35 +112,57 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
91
112
  | PreMAP
92
113
  | CreateOrdinalsCollectionMetadata
93
114
  | CreateOrdinalsCollectionItemMetadata,
94
- });
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);
95
125
 
96
126
  const changeResult = result as ChangeResult;
97
127
 
98
- // 7. Broadcast the transaction
99
- await changeResult.tx.broadcast();
128
+ // 7. Optionally sign with identity key and broadcast the transaction
100
129
 
101
- // 8. Refresh the wallet's UTXOs after spending
102
- try {
103
- await wallet.refreshUtxos();
104
- } catch (refreshError) {
105
- console.warn(
106
- "Failed to refresh UTXOs after transaction:",
107
- refreshError,
108
- );
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
+ };
109
159
  }
110
160
 
111
- // 9. Return transaction details
112
161
  return {
113
162
  content: [
114
163
  {
115
164
  type: "text",
116
- text: JSON.stringify({
117
- txid: changeResult.tx.id("hex"),
118
- spentOutpoints: changeResult.spentOutpoints,
119
- payChange: changeResult.payChange,
120
- inscriptionAddress: args.destinationAddress || walletAddress,
121
- contentType: args.contentType,
122
- }),
165
+ text: changeResult.tx.toHex(),
123
166
  },
124
167
  ],
125
168
  };
@@ -1,11 +1,17 @@
1
- // import { PrivateKey } from "@bsv/sdk"; // not used here
1
+ import { PrivateKey } 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
+ import type {
5
+ ServerNotification,
6
+ ServerRequest,
7
+ } from "@modelcontextprotocol/sdk/types.js";
4
8
  import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
5
9
  import {
6
10
  type ChangeResult,
7
11
  type ExistingListing,
12
+ type LocalSigner,
8
13
  type Payment,
14
+ type PurchaseOrdListingConfig,
9
15
  type Royalty,
10
16
  TokenType,
11
17
  type TokenUtxo,
@@ -14,6 +20,7 @@ import {
14
20
  purchaseOrdListing,
15
21
  purchaseOrdTokenListing,
16
22
  } from "js-1sat-ord";
23
+ import { Sigma } from "sigma-protocol";
17
24
  import type { z } from "zod";
18
25
  import {
19
26
  MARKET_FEE_PERCENTAGE,
@@ -82,9 +89,23 @@ export function registerPurchaseListingTool(server: McpServer, wallet: Wallet) {
82
89
  { args: purchaseListingArgsSchema },
83
90
  async (
84
91
  { args }: { args: z.infer<typeof purchaseListingArgsSchema> },
85
- extra: RequestHandlerExtra,
92
+ extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
86
93
  ): Promise<CallToolResult> => {
87
94
  try {
95
+ // Load optional identity key for sigma signing
96
+ const identityKeyWif = process.env.IDENTITY_KEY_WIF;
97
+ let identityPk: PrivateKey | undefined;
98
+ if (identityKeyWif) {
99
+ try {
100
+ identityPk = PrivateKey.fromWif(identityKeyWif);
101
+ } catch (e) {
102
+ console.warn(
103
+ "Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
104
+ e,
105
+ );
106
+ }
107
+ }
108
+
88
109
  // Fetch the listing info directly from the API
89
110
  const response = await fetch(
90
111
  `https://ordinals.gorillapool.io/api/txos/${args.listingOutpoint}?script=true`,
@@ -233,7 +254,7 @@ Please fund this wallet address with enough BSV to cover the purchase price
233
254
  }
234
255
  }
235
256
 
236
- transaction = await purchaseOrdListing({
257
+ const purchaseOrdListingConfig: PurchaseOrdListingConfig = {
237
258
  utxos: paymentUtxos,
238
259
  paymentPk,
239
260
  ordAddress: args.ordAddress,
@@ -241,7 +262,9 @@ Please fund this wallet address with enough BSV to cover the purchase price
241
262
  additionalPayments,
242
263
  metaData,
243
264
  royalties,
244
- });
265
+ };
266
+
267
+ transaction = await purchaseOrdListing(purchaseOrdListingConfig);
245
268
  }
246
269
 
247
270
  // After successful transaction creation, refresh the wallet's UTXOs
@@ -252,44 +275,55 @@ Please fund this wallet address with enough BSV to cover the purchase price
252
275
  // Remove console.warn
253
276
  }
254
277
 
255
- // Broadcast the transaction
256
- const broadcastResult = await transaction.tx.broadcast(
257
- oneSatBroadcaster(),
258
- );
278
+ // Optionally sign with identity key then broadcast the transaction
259
279
 
260
- // Handle broadcast response
261
- const resultStatus =
262
- typeof broadcastResult === "object" && "status" in broadcastResult
263
- ? broadcastResult.status
264
- : "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";
265
290
 
266
- const resultMessage =
267
- typeof broadcastResult === "object" && "error" in broadcastResult
268
- ? broadcastResult.error
269
- : "Transaction broadcast successful";
291
+ const resultMessage =
292
+ typeof broadcastResult === "object" && "error" in broadcastResult
293
+ ? broadcastResult.error
294
+ : "Transaction broadcast successful";
270
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
+ }
271
322
  return {
272
323
  content: [
273
324
  {
274
325
  type: "text",
275
- text: JSON.stringify({
276
- status: resultStatus,
277
- message: resultMessage,
278
- txid: transaction.tx.id("hex"),
279
- listingOutpoint: args.listingOutpoint,
280
- destinationAddress: args.ordAddress,
281
- listingType: args.listingType,
282
- tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
283
- tokenID: args.tokenID,
284
- price: listingData.data.list.price,
285
- marketFee,
286
- marketFeeAddress: MARKET_WALLET_ADDRESS,
287
- royaltiesPaid:
288
- args.listingType === "nft" &&
289
- listingData.origin?.data?.map?.royalties
290
- ? JSON.parse(listingData.origin.data.map.royalties)
291
- : undefined,
292
- }),
326
+ text: transaction.tx.toHex(),
293
327
  },
294
328
  ],
295
329
  };
@@ -1,8 +1,18 @@
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
- import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
+ import type {
5
+ CallToolResult,
6
+ ServerNotification,
7
+ ServerRequest,
8
+ } from "@modelcontextprotocol/sdk/types.js";
4
9
  import { sendOrdinals } from "js-1sat-ord";
5
- 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";
6
16
  import { z } from "zod";
7
17
  import type { Wallet } from "./wallet";
8
18
 
@@ -37,7 +47,7 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
37
47
  { args: sendOrdinalsArgsSchema },
38
48
  async (
39
49
  { args }: { args: SendOrdinalsArgs },
40
- extra: RequestHandlerExtra,
50
+ extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
41
51
  ): Promise<CallToolResult> => {
42
52
  try {
43
53
  // 1. Get private key from wallet
@@ -86,6 +96,16 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
86
96
  changeAddress: walletAddress,
87
97
  };
88
98
 
99
+ const identityKeyWif = process.env.IDENTITY_KEY_WIF;
100
+ const identityPk = identityKeyWif
101
+ ? PrivateKey.fromWif(identityKeyWif)
102
+ : undefined;
103
+ if (identityPk) {
104
+ sendOrdinalsConfig.signer = {
105
+ idKey: identityPk,
106
+ } as LocalSigner;
107
+ }
108
+
89
109
  // Add metadata if provided
90
110
  if (args.metadata) {
91
111
  sendOrdinalsConfig.metaData = args.metadata;
@@ -96,32 +116,45 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
96
116
 
97
117
  const result = await sendOrdinals(sendOrdinalsConfig);
98
118
  const changeResult = result as ChangeResult;
119
+ // no signing when you send since we don't emit an inscription
99
120
 
100
121
  // 7. Broadcast the transaction
101
- await changeResult.tx.broadcast();
102
-
103
- // 8. Refresh the wallet's UTXOs after spending
104
- try {
105
- await wallet.refreshUtxos();
106
- } catch (refreshError) {
107
- console.warn(
108
- "Failed to refresh UTXOs after transaction:",
109
- refreshError,
110
- );
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
+ };
111
151
  }
112
152
 
113
- // 9. Return transaction details
114
153
  return {
115
154
  content: [
116
155
  {
117
156
  type: "text",
118
- text: JSON.stringify({
119
- txid: changeResult.tx.id("hex"),
120
- spentOutpoints: changeResult.spentOutpoints,
121
- payChange: changeResult.payChange,
122
- inscriptionOutpoint: args.inscriptionOutpoint,
123
- destinationAddress: args.destinationAddress,
124
- }),
157
+ text: changeResult.tx.toHex(),
125
158
  },
126
159
  ],
127
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;