bsv-mcp 0.2.14 → 0.3.0

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +90 -10
  2. package/README.md +13 -13
  3. package/dist/app.html +483 -106
  4. package/dist/index.js +265787 -155292
  5. package/index.ts +505 -70
  6. package/package.json +24 -15
  7. package/prompts/bsvSdk/auth.ts +1 -1
  8. package/prompts/bsvSdk/cryptography.ts +1 -1
  9. package/prompts/bsvSdk/overview.ts +1 -1
  10. package/prompts/bsvSdk/primitives.ts +1 -1
  11. package/prompts/bsvSdk/script.ts +1 -1
  12. package/prompts/bsvSdk/transaction.ts +1 -1
  13. package/prompts/bsvSdk/wallet.ts +1 -1
  14. package/prompts/ordinals.ts +1 -1
  15. package/test-mcp-server.ts +26 -4
  16. package/tools/a2b/discover.ts +2 -2
  17. package/tools/bap/friend.ts +17 -24
  18. package/tools/bap/generate.test.ts +34 -32
  19. package/tools/bap/generate.ts +48 -43
  20. package/tools/bap/getCurrentAddress.ts +1 -3
  21. package/tools/bap/getId.test.ts +18 -10
  22. package/tools/bap/getId.ts +8 -6
  23. package/tools/bap/utils.ts +1 -1
  24. package/tools/bsocial/bmapReadPosts.ts +16 -2
  25. package/tools/bsocial/createPost.ts +21 -10
  26. package/tools/bsocial/readPosts.ts +10 -4
  27. package/tools/bsv/decodeTransaction.ts +1 -1
  28. package/tools/bsv/explore.ts +0 -4
  29. package/tools/bsv/getPrice.ts +1 -1
  30. package/tools/bsv/token.ts +14 -6
  31. package/tools/constants.ts +6 -2
  32. package/tools/index.ts +10 -10
  33. package/tools/mnee/getBalance.ts +1 -1
  34. package/tools/mnee/parseTx.ts +1 -1
  35. package/tools/mnee/sendMnee.ts +30 -8
  36. package/tools/ordinals/getInscription.ts +1 -3
  37. package/tools/ordinals/getTokenByIdOrTicker.ts +1 -3
  38. package/tools/ordinals/index.ts +4 -1
  39. package/tools/ordinals/marketListings.ts +1 -1
  40. package/tools/ordinals/marketSales.ts +1 -1
  41. package/tools/ordinals/searchInscriptions.ts +1 -1
  42. package/tools/utils/aip.ts +31 -22
  43. package/tools/utils/installAgentMaster.ts +8 -9
  44. package/tools/utils/toolRegistration.ts +21 -22
  45. package/tools/utils/transactionBuilder.ts +21 -14
  46. package/tools/wallet/a2bPublishMcp.ts +15 -5
  47. package/tools/wallet/brc100.ts +541 -310
  48. package/tools/wallet/cancelListing.ts +29 -37
  49. package/tools/wallet/createOrdinals.ts +43 -27
  50. package/tools/wallet/fetchPaymentUtxos.ts +7 -5
  51. package/tools/wallet/gatherCollectionInfo.ts +3 -5
  52. package/tools/wallet/getAddress.ts +1 -4
  53. package/tools/wallet/getBalance.ts +15 -15
  54. package/tools/wallet/{getBalanceDroplet.ts → getBalanceDroplit.ts} +6 -6
  55. package/tools/wallet/getBsv21Balances.ts +20 -14
  56. package/tools/wallet/getLockData.ts +11 -11
  57. package/tools/wallet/getOrdinals.ts +26 -20
  58. package/tools/wallet/integratedWallet.ts +55 -53
  59. package/tools/wallet/listOrdinal.ts +40 -39
  60. package/tools/wallet/listTokens.ts +18 -17
  61. package/tools/wallet/lockBsv.ts +36 -22
  62. package/tools/wallet/mintCollection.ts +15 -10
  63. package/tools/wallet/opnsDeregister.ts +29 -39
  64. package/tools/wallet/opnsRegister.ts +29 -39
  65. package/tools/wallet/purchaseListing.test.ts +27 -0
  66. package/tools/wallet/purchaseListing.ts +59 -40
  67. package/tools/wallet/refreshUtxos.ts +1 -1
  68. package/tools/wallet/schemas.ts +0 -13
  69. package/tools/wallet/sendAllBsv.ts +26 -20
  70. package/tools/wallet/sendBsv.ts +107 -0
  71. package/tools/wallet/setupDroplit.ts +157 -0
  72. package/tools/wallet/signBsm.ts +18 -18
  73. package/tools/wallet/sweepBsv.ts +48 -27
  74. package/tools/wallet/sweepBsv21.ts +42 -28
  75. package/tools/wallet/sweepOrdinals.ts +38 -26
  76. package/tools/wallet/tools.ts +11 -13
  77. package/tools/wallet/transactionHelper.ts +13 -5
  78. package/tools/wallet/transferOrdToken.ts +72 -84
  79. package/tools/wallet/unlockBsv.ts +23 -17
  80. package/tools/wallet/utxo.ts +1 -0
  81. package/tools/wallet/wallet.ts +1 -13
  82. package/tools/wallet/sendToAddress.ts +0 -74
  83. package/tools/wallet/setupDroplet.ts +0 -161
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "bsv-mcp",
3
3
  "module": "dist/index.js",
4
4
  "type": "module",
5
- "version": "0.2.14",
5
+ "version": "0.3.0",
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",
@@ -62,6 +62,7 @@
62
62
  "bun": "^1.3.10",
63
63
  "class-variance-authority": "^0.7.1",
64
64
  "clsx": "^2.1.1",
65
+ "esbuild": "^0.28.1",
65
66
  "lucide-react": "0.577.0",
66
67
  "next": "^16.1.6",
67
68
  "next-themes": "^0.4.6",
@@ -72,15 +73,28 @@
72
73
  "tailwind-merge": "^3.5.0",
73
74
  "tailwindcss": "^4.2.1",
74
75
  "tailwindcss-animate": "^1.0.7",
75
- "@1sat/actions": "0.0.27",
76
- "@1sat/wallet-remote": "0.0.7",
77
- "@bsv/sdk": "^2.0.6",
78
- "@modelcontextprotocol/ext-apps": "^1.2.0",
79
- "@modelcontextprotocol/sdk": "^1.27.1",
80
- "bitcoin-auth": "^0.0.7",
81
- "bitcoin-backup": "0.0.7",
76
+ "vite": "^7.3.1",
77
+ "vite-plugin-singlefile": "^2.3.0"
78
+ },
79
+ "peerDependencies": {
80
+ "typescript": "^5.9.3"
81
+ },
82
+ "overrides": {
83
+ "zod": "4.4.3"
84
+ },
85
+ "dependencies": {
86
+ "@1sat/actions": "^0.0.192",
87
+ "@1sat/wallet-node": "^0.0.64",
88
+ "@1sat/wallet-remote": "^0.0.55",
89
+ "@bsv/sdk": "^2.2.0",
90
+ "@bsv/wallet-toolbox": "^2.4.4",
91
+ "@gorillapool/js-junglebus": "^0.4.4",
92
+ "@modelcontextprotocol/ext-apps": "^1.7.5",
93
+ "@modelcontextprotocol/sdk": "^1.29.0",
94
+ "bitcoin-auth": "^0.0.8",
95
+ "bitcoin-backup": "^0.0.13",
82
96
  "bmap-api-types": "0.0.9",
83
- "bsv-bap": "0.1.23",
97
+ "bsv-bap": "^0.3.4",
84
98
  "jose": "^6.2.0",
85
99
  "js-1sat-ord": "^0.1.91",
86
100
  "mcp-handler": "^1.0.7",
@@ -88,13 +102,8 @@
88
102
  "satoshi-token": "^0.0.7",
89
103
  "schema-dts": "^1.1.5",
90
104
  "sigma-protocol": "^0.1.9",
91
- "vite": "^7.3.1",
92
- "vite-plugin-singlefile": "^2.3.0"
93
- },
94
- "peerDependencies": {
95
- "typescript": "^5.9.3"
105
+ "zod": "4.4.3"
96
106
  },
97
- "dependencies": {},
98
107
  "scripts": {
99
108
  "build": "bun run ./scripts/build.ts",
100
109
  "build:view": "vite build",
@@ -44,7 +44,7 @@ export function registerAuthPrompt(server: McpServer): void {
44
44
  server.prompt(
45
45
  "bitcoin_sv_sdk_auth",
46
46
  "Detailed information about the authentication functionality in the BSV SDK, including identity protocols, certificates, and session management.",
47
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
47
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
48
48
  return {
49
49
  messages: [
50
50
  {
@@ -45,7 +45,7 @@ export function registerCryptographyPrompt(server: McpServer): void {
45
45
  server.prompt(
46
46
  "bitcoin_sv_sdk_cryptography",
47
47
  "Detailed information about the cryptographic functionality in the BSV SDK, including key generation, signing, encryption, and hashing.",
48
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
48
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
49
49
  return {
50
50
  messages: [
51
51
  {
@@ -88,7 +88,7 @@ export function registerOverviewPrompt(server: McpServer): void {
88
88
  server.prompt(
89
89
  "bitcoin_sv_sdk_overview",
90
90
  "General overview of the Bitcoin SV SDK, including its purpose and main components.",
91
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
91
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
92
92
  return {
93
93
  messages: [
94
94
  {
@@ -52,7 +52,7 @@ export function registerPrimitivesPrompt(server: McpServer): void {
52
52
  server.prompt(
53
53
  "bitcoin_sv_sdk_primitives",
54
54
  "Detailed information about the primitive data types and structures in the BSV SDK, including Binary, Hex, Points, and other fundamental types.",
55
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
55
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
56
56
  return {
57
57
  messages: [
58
58
  {
@@ -53,7 +53,7 @@ export function registerScriptPrompt(server: McpServer): void {
53
53
  server.prompt(
54
54
  "bitcoin_sv_sdk_script",
55
55
  "Detailed information about the script functionality in the BSV SDK, including Bitcoin Script operations, locking and unlocking scripts, and OP_CODES.",
56
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
56
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
57
57
  return {
58
58
  messages: [
59
59
  {
@@ -232,7 +232,7 @@ export function registerTransactionPrompt(server: McpServer): void {
232
232
  server.prompt(
233
233
  "bitcoin_sv_sdk_transaction",
234
234
  "Detailed information about transaction building and management in the BSV SDK, including input/output handling, script integration, and transaction signing.",
235
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
235
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
236
236
  return {
237
237
  messages: [
238
238
  {
@@ -171,7 +171,7 @@ export function registerWalletPrompt(server: McpServer): void {
171
171
  server.prompt(
172
172
  "bitcoin_sv_sdk_wallet",
173
173
  "Detailed information about the wallet functionality in the BSV SDK, including key management, address handling, and UTXO management.",
174
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
174
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
175
175
  return {
176
176
  messages: [
177
177
  {
@@ -86,7 +86,7 @@ export function registerOrdinalsPrompt(server: McpServer): void {
86
86
  server.prompt(
87
87
  "bitcoin_sv_ordinals",
88
88
  "Comprehensive information about Bitcoin SV ordinals, including what they are, how they work, and how to use them.",
89
- async (extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
89
+ async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
90
90
  return {
91
91
  messages: [
92
92
  {
@@ -2,6 +2,28 @@
2
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
3
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4
4
 
5
+ /**
6
+ * Read the first text block out of a callTool result. The client return type
7
+ * is a union that also covers the legacy `toolResult` shape, so the content
8
+ * array has to be checked rather than assumed.
9
+ */
10
+ function firstText(result: { [key: string]: unknown }): string {
11
+ const content = result.content;
12
+ if (!Array.isArray(content) || content.length === 0) {
13
+ throw new Error("Tool result carried no content blocks");
14
+ }
15
+ const block: unknown = content[0];
16
+ if (
17
+ typeof block !== "object" ||
18
+ block === null ||
19
+ !("text" in block) ||
20
+ typeof block.text !== "string"
21
+ ) {
22
+ throw new Error("First content block was not text");
23
+ }
24
+ return block.text;
25
+ }
26
+
5
27
  async function testMCPServer() {
6
28
  console.log("🧪 Testing BSV MCP Server...\n");
7
29
 
@@ -67,7 +89,7 @@ async function testMCPServer() {
67
89
  name: "bsv_getPrice",
68
90
  arguments: {},
69
91
  });
70
- const content = priceResult.content[0] as { text: string };
92
+ const content = { text: firstText(priceResult) };
71
93
  console.log("✅", content.text);
72
94
  } catch (error) {
73
95
  console.log("❌ Error:", error);
@@ -86,7 +108,7 @@ async function testMCPServer() {
86
108
  },
87
109
  },
88
110
  });
89
- const content = convertResult.content[0] as { text: string };
111
+ const content = { text: firstText(convertResult) };
90
112
  console.log("✅ Converted:", content.text);
91
113
  } catch (error) {
92
114
  console.log("❌ Error:", error);
@@ -103,7 +125,7 @@ async function testMCPServer() {
103
125
  },
104
126
  },
105
127
  });
106
- const content = postsResult.content[0] as { text: string };
128
+ const content = { text: firstText(postsResult) };
107
129
  console.log("✅ Read posts (showing first 200 chars):");
108
130
  console.log(`${content.text.substring(0, 200)}...`);
109
131
  } catch (error) {
@@ -122,7 +144,7 @@ async function testMCPServer() {
122
144
  },
123
145
  },
124
146
  });
125
- const content = ordinalsResult.content[0] as { text: string };
147
+ const content = { text: firstText(ordinalsResult) };
126
148
  const response = JSON.parse(content.text);
127
149
  console.log(`✅ Found ${response.total || 0} inscriptions`);
128
150
  } catch (error) {
@@ -50,7 +50,7 @@ export type A2bDiscoverArgs = z.infer<typeof a2bDiscoverArgsSchema>;
50
50
  /**
51
51
  * Format the response in a user-friendly way
52
52
  */
53
- function formatSearchResults(data: unknown, queryType: string): string {
53
+ function _formatSearchResults(data: unknown, queryType: string): string {
54
54
  // console.log(`Received data: ${JSON.stringify(data).substring(0, 200)}...`); // Debug log
55
55
 
56
56
  // Check if data is an object with items property
@@ -124,7 +124,7 @@ export function registerA2bDiscoverTool(server: McpServer) {
124
124
  { ...a2bDiscoverArgsSchema.shape },
125
125
  async (
126
126
  { queryType, query, limit, offset, fromBlock, toBlock },
127
- extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
127
+ _extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
128
128
  ) => {
129
129
  try {
130
130
  const params = new URLSearchParams();
@@ -5,7 +5,6 @@ import {
5
5
  isBroadcastFailure,
6
6
  isBroadcastResponse,
7
7
  P2PKH,
8
- PrivateKey,
9
8
  SatoshisPerKilobyte,
10
9
  Script,
11
10
  Transaction,
@@ -18,21 +17,17 @@ import type {
18
17
  ServerNotification,
19
18
  ServerRequest,
20
19
  } from "@modelcontextprotocol/sdk/types.js";
21
- import { BAP, MemberID } from "bsv-bap";
20
+ import { BAP } from "bsv-bap";
22
21
  import { z } from "zod";
23
22
  import { BsocialBroadcaster } from "../../utils/broadcaster";
24
- import {
25
- friendPrivateKeyFromMemberIdKey,
26
- friendPublicKeyFromSeedString,
27
- } from "../../utils/keys";
23
+ import { friendPrivateKeyFromMemberIdKey } from "../../utils/keys";
28
24
  import { MAP_PREFIX } from "../constants";
29
- import {
30
- fetchPaymentUtxos,
31
- fetchPaymentUtxosFromV5,
32
- } from "../wallet/fetchPaymentUtxos";
25
+ import { signOpReturnWithAIP } from "../utils/aip";
26
+ import { buildOpReturnScript } from "../utils/transactionBuilder";
27
+ import { fetchPaymentUtxosFromV5 } from "../wallet/fetchPaymentUtxos";
33
28
  import type { Wallet } from "../wallet/wallet";
34
29
 
35
- const { toArray, toHex } = Utils;
30
+ const { toArray } = Utils;
36
31
 
37
32
  const APP_DOMAIN = "bsv-mcp";
38
33
 
@@ -58,9 +53,9 @@ export function registerBapFriendTool(
58
53
  { ...bapFriendArgsSchema.shape },
59
54
  async (
60
55
  { targetBapId },
61
- extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
56
+ _extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
62
57
  ): Promise<CallToolResult> => {
63
- const logFunc = console.error;
58
+ const _logFunc = console.error;
64
59
 
65
60
  try {
66
61
  // --- Preconditions ---
@@ -95,10 +90,7 @@ export function registerBapFriendTool(
95
90
 
96
91
  // 3. Derive Initial Identity
97
92
  const bap = new BAP(xprv);
98
- const idpk = PrivateKey.fromWif(
99
- bap.newId().exportMemberBackup().derivedPrivateKey,
100
- );
101
- const identityInstance = new MemberID(idpk);
93
+ const idpk = bap.newId().getAccountKey();
102
94
  const paymentAddress = payPk.toAddress();
103
95
 
104
96
  // --- Derive friend public key ---
@@ -123,15 +115,16 @@ export function registerBapFriendTool(
123
115
  friendPubKey,
124
116
  ];
125
117
 
126
- const payloadBuffers = payloadParts.map(
127
- (part) => toArray(part) as number[],
118
+ const payloadBuffers: number[][] = payloadParts.map((part) =>
119
+ toArray(part),
128
120
  );
129
121
 
130
- const signedBuffers =
131
- identityInstance.signOpReturnWithAIP(payloadBuffers);
132
- const payloadHex = signedBuffers.map((b) => toHex(b));
133
- const asmPayload = `OP_0 ${payloadHex.join(" ")}`;
134
- const opReturnScript = Script.fromASM(asmPayload);
122
+ const { signedData } = await signOpReturnWithAIP(
123
+ payloadBuffers,
124
+ idpk,
125
+ idpk.toAddress(),
126
+ );
127
+ const opReturnScript = buildOpReturnScript(signedData);
135
128
 
136
129
  // --- Build Transaction ---
137
130
  const tx = new Transaction();
@@ -1,12 +1,4 @@
1
- import {
2
- afterEach,
3
- beforeEach,
4
- describe,
5
- expect,
6
- it,
7
- mock,
8
- spyOn,
9
- } from "bun:test";
1
+ import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
10
2
  import fs from "node:fs";
11
3
  import os from "node:os";
12
4
  import path from "node:path";
@@ -24,18 +16,29 @@ import { type BapGenerateArgs, registerBapGenerateTool } from "./generate";
24
16
 
25
17
  type GenericToolHandler = (
26
18
  params: BapGenerateArgs,
27
- extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
19
+ _extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
28
20
  ) => CallToolResult | Promise<CallToolResult>;
29
21
 
22
+ /** Narrow the first content block of a tool result to its text payload. */
23
+ function firstText(result: CallToolResult): string {
24
+ const block = result.content?.[0];
25
+ if (block?.type !== "text") {
26
+ throw new Error(
27
+ `Expected a text content block, got ${block?.type ?? "none"}`,
28
+ );
29
+ }
30
+ return block.text;
31
+ }
32
+
30
33
  const mockMcpServerInstance = {
31
34
  tool: (..._args: Parameters<McpServer["tool"]>): void => undefined,
32
35
  };
33
36
 
34
37
  const KEY_DIR = path.join(os.homedir(), ".bsv-mcp");
35
- const KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
38
+ const _KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
36
39
 
37
40
  const MOCK_PAY_PK_WIF = "L3tg5yk23vBrSGZT8pfrDFQBK8n46eeot2Sf91ezNt7rhyReCA53";
38
- const MOCK_PAY_ADDRESS = PrivateKey.fromWif(MOCK_PAY_PK_WIF).toAddress();
41
+ const _MOCK_PAY_ADDRESS = PrivateKey.fromWif(MOCK_PAY_PK_WIF).toAddress();
39
42
  const MOCK_KEYS_WITH_PAYPK = { payPk: MOCK_PAY_PK_WIF };
40
43
  const MOCK_UTXO_SCRIPT_HASH_PLACEHOLDER =
41
44
  "1a2b3c4d5e6f7081920a1b2c3d4e5f607081920a";
@@ -45,6 +48,7 @@ const MOCK_UTXO = {
45
48
  satoshis: 100000,
46
49
  script: `76a914${MOCK_UTXO_SCRIPT_HASH_PLACEHOLDER}88ac`,
47
50
  };
51
+ const MOCK_BROADCAST_TXID = "b1c2d3e4".repeat(8);
48
52
 
49
53
  describe("BAP Generate Tool", () => {
50
54
  let readFileSyncSpy: ReturnType<typeof spyOn<typeof fs, "readFileSync">>;
@@ -58,7 +62,7 @@ describe("BAP Generate Tool", () => {
58
62
  typeof spyOn<typeof utxoUtils, "fetchPaymentUtxos">
59
63
  >;
60
64
  let transactionBroadcastSpy: ReturnType<
61
- typeof spyOn<Transaction.prototype, "broadcast">
65
+ typeof spyOn<Transaction, "broadcast">
62
66
  >;
63
67
  let passphrasePromptSpy: ReturnType<
64
68
  typeof spyOn<
@@ -96,9 +100,11 @@ describe("BAP Generate Tool", () => {
96
100
  mkdirSyncSpy.mockImplementation(() => undefined);
97
101
  writeFileSyncSpy.mockImplementation(() => undefined);
98
102
  fetchPaymentUtxosSpy.mockResolvedValue([MOCK_UTXO]);
99
- transactionBroadcastSpy.mockResolvedValue(
100
- `mock_txid_broadcasted_${"a".repeat(40)}`,
101
- );
103
+ transactionBroadcastSpy.mockResolvedValue({
104
+ status: "success",
105
+ txid: MOCK_BROADCAST_TXID,
106
+ message: "broadcast accepted",
107
+ });
102
108
  });
103
109
 
104
110
  afterEach(() => {
@@ -159,10 +165,10 @@ describe("BAP Generate Tool", () => {
159
165
  expect(writtenData.xprv?.startsWith("xprv")).toBe(true);
160
166
  expect(writtenData.identityPk).toBeString();
161
167
 
162
- expect(result.content?.[0]?.text).toContain(
168
+ expect(firstText(result)).toContain(
163
169
  "BAP HD Key and Initial Identity Generated & Saved",
164
170
  );
165
- expect(result.content?.[0]?.text).toContain("Registration TXID:");
171
+ expect(firstText(result)).toContain("Registration TXID:");
166
172
  expect(transactionBroadcastSpy).toHaveBeenCalled();
167
173
  });
168
174
 
@@ -178,7 +184,7 @@ describe("BAP Generate Tool", () => {
178
184
  const result = (await handler({}, mockExtra)) as CallToolResult;
179
185
 
180
186
  expect(result.isError).toBe(true);
181
- expect(result.content?.[0]?.text).toContain(
187
+ expect(firstText(result)).toContain(
182
188
  "keys.json not found. Payment Private Key (payPk) is required.",
183
189
  );
184
190
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -196,7 +202,7 @@ describe("BAP Generate Tool", () => {
196
202
  const result = (await handler({}, mockExtra)) as CallToolResult;
197
203
 
198
204
  expect(result.isError).toBe(true);
199
- expect(result.content?.[0]?.text).toContain(
205
+ expect(firstText(result)).toContain(
200
206
  "Payment Private Key (payPk) does not exist in keys.json.",
201
207
  );
202
208
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -218,7 +224,7 @@ describe("BAP Generate Tool", () => {
218
224
  const result = (await handler({}, mockExtra)) as CallToolResult;
219
225
 
220
226
  expect(result.isError).toBe(true);
221
- expect(result.content?.[0]?.text).toContain(
227
+ expect(firstText(result)).toContain(
222
228
  "BAP Master Key (xprv) already exists in keys.json.",
223
229
  );
224
230
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -240,7 +246,7 @@ describe("BAP Generate Tool", () => {
240
246
  const result = (await handler({}, mockExtra)) as CallToolResult;
241
247
 
242
248
  expect(result.isError).toBe(true);
243
- expect(result.content?.[0]?.text).toContain(
249
+ expect(firstText(result)).toContain(
244
250
  "BAP Identity Key (identityPk) already exists in keys.json.",
245
251
  );
246
252
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -264,7 +270,7 @@ describe("BAP Generate Tool", () => {
264
270
  const result = (await handler({}, mockExtra)) as CallToolResult;
265
271
 
266
272
  expect(result.isError).toBe(true);
267
- expect(result.content?.[0]?.text).toContain(
273
+ expect(firstText(result)).toContain(
268
274
  "Failed during BAP key generation/saving or transaction construction: Test HD generation error",
269
275
  );
270
276
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -287,7 +293,7 @@ describe("BAP Generate Tool", () => {
287
293
  const result = (await handler({}, mockExtra)) as CallToolResult;
288
294
 
289
295
  expect(result.isError).toBe(true);
290
- expect(result.content?.[0]?.text).toContain(
296
+ expect(firstText(result)).toContain(
291
297
  "Could not read or parse keys.json: JSON Parse error",
292
298
  );
293
299
  expect(writeFileSyncSpy).not.toHaveBeenCalled();
@@ -306,12 +312,8 @@ describe("BAP Generate Tool", () => {
306
312
  const result = (await handler({}, mockExtra)) as CallToolResult;
307
313
 
308
314
  expect(result.isError).toBe(false);
309
- expect(result.content?.[0]?.text).toContain(
310
- "No UTXOs found for payment address",
311
- );
312
- expect(result.content?.[0]?.text).toContain(
313
- "Cannot fund BAP registration.",
314
- );
315
+ expect(firstText(result)).toContain("No UTXOs found for payment address");
316
+ expect(firstText(result)).toContain("Cannot fund BAP registration.");
315
317
  expect(writeFileSyncSpy).toHaveBeenCalledTimes(1);
316
318
  const writtenData = JSON.parse(
317
319
  writeFileSyncSpy.mock.calls[0][1] as string,
@@ -337,10 +339,10 @@ describe("BAP Generate Tool", () => {
337
339
  const result = (await handler({}, mockExtra)) as CallToolResult;
338
340
 
339
341
  expect(result.isError).toBe(false);
340
- expect(result.content?.[0]?.text).toContain(
342
+ expect(firstText(result)).toContain(
341
343
  "Keys generated and saved, but failed to broadcast BAP registration transaction",
342
344
  );
343
- expect(result.content?.[0]?.text).toContain("Broadcast network error");
345
+ expect(firstText(result)).toContain("Broadcast network error");
344
346
  expect(writeFileSyncSpy).toHaveBeenCalledTimes(1);
345
347
  const writtenData = JSON.parse(
346
348
  writeFileSyncSpy.mock.calls[0][1] as string,
@@ -1,13 +1,14 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import { BAP_KEY_ID, BAP_PROTOCOL_ID } from "@1sat/actions";
3
4
  import {
4
5
  Utils as BSVUtils,
5
6
  fromUtxo,
6
7
  HD,
7
8
  isBroadcastFailure,
9
+ KeyDeriver,
8
10
  P2PKH,
9
- PrivateKey,
10
- Script,
11
+ type PrivateKey,
11
12
  Transaction,
12
13
  } from "@bsv/sdk";
13
14
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -17,6 +18,8 @@ import { z } from "zod";
17
18
  import { V5Broadcaster } from "../../utils/broadcaster";
18
19
  import { SecureKeyManager } from "../../utils/keyManager";
19
20
  import { BAP_PREFIX } from "../constants";
21
+ import { signOpReturnWithAIP } from "../utils/aip";
22
+ import { buildOpReturnScript } from "../utils/transactionBuilder";
20
23
  import { fetchPaymentUtxos } from "../wallet/fetchPaymentUtxos";
21
24
 
22
25
  // Get toArray from BSV SDK Utils
@@ -26,8 +29,14 @@ const KEY_DIR = path.join(os.homedir(), ".bsv-mcp");
26
29
  const KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
27
30
 
28
31
  const bapGenerateArgsSchema = z.object({
29
- alternateName: z.string().optional().describe("Alternate name for the BAP identity profile"),
30
- description: z.string().optional().describe("Description for the BAP identity profile"),
32
+ alternateName: z
33
+ .string()
34
+ .optional()
35
+ .describe("Alternate name for the BAP identity profile"),
36
+ description: z
37
+ .string()
38
+ .optional()
39
+ .describe("Description for the BAP identity profile"),
31
40
  });
32
41
 
33
42
  export type BapGenerateArgs = z.infer<typeof bapGenerateArgsSchema>;
@@ -51,7 +60,7 @@ async function generateBapKeys(
51
60
  }> {
52
61
  const keyManager = new SecureKeyManager({ keyDir: KEY_DIR });
53
62
  let tempXprv = "";
54
- let identityPk: string;
63
+ let _identityPk: string;
55
64
  let payPkToPreserve: PrivateKey | undefined;
56
65
 
57
66
  // 1. Load existing keys and check preconditions
@@ -150,29 +159,22 @@ async function generateBapKeys(
150
159
 
151
160
  // 3. Create BAP instance and generate identity
152
161
  const bapInstance = new BAP(tempXprv);
153
-
154
- // Create identity attributes
155
- const identityAttributes = {
156
- name: {
157
- value: args?.alternateName || "Anonymous User",
158
- nonce: BSVUtils.toHex(BSVUtils.toArray(Math.random().toString())),
159
- },
160
- description: {
161
- value: args?.description || "A BAP identity managed by BSV-MCP.",
162
- nonce: BSVUtils.toHex(BSVUtils.toArray(Math.random().toString())),
163
- },
164
- };
165
-
166
- // Create new ID with the BAP instance
167
- const bapId = bapInstance.newId(undefined, identityAttributes);
162
+ const bapId = bapInstance.newId();
168
163
 
169
164
  // Get the generated identity information
170
- const generatedIdentityKey = bapId.getIdentityKey();
165
+ const generatedIdentityKey = bapId.bapId;
171
166
  const generatedIdentityAddress = bapId.rootAddress;
172
167
 
173
- // Export member backup to get the private key
174
- const memberBackup = bapId.exportMemberBackup();
175
- identityPk = memberBackup.derivedPrivateKey;
168
+ // rootAddress is the identity-0 key derived from the account key at
169
+ // protocolID [1,"sigma"], keyID "identity-0", counterparty "self".
170
+ // That is the key the BAP ID record declares, so it must also be the
171
+ // key that signs it.
172
+ const identityKey = new KeyDeriver(bapId.getAccountKey()).derivePrivateKey(
173
+ BAP_PROTOCOL_ID,
174
+ `${BAP_KEY_ID}-0`,
175
+ "self",
176
+ );
177
+ _identityPk = identityKey.toWif();
176
178
 
177
179
  console.error(`INFO: Generated BAP identity key: ${generatedIdentityKey}`);
178
180
  console.error(
@@ -182,7 +184,7 @@ async function generateBapKeys(
182
184
  // 5. Save Keys to secure storage
183
185
  const updatedKeys = {
184
186
  payPk: payPkToPreserve,
185
- identityPk: PrivateKey.fromWif(identityPk),
187
+ identityPk: identityKey,
186
188
  xprv: tempXprv,
187
189
  };
188
190
 
@@ -201,7 +203,7 @@ async function generateBapKeys(
201
203
 
202
204
  // Create the ID registration transaction output
203
205
  // BAP ID format: OP_0 OP_RETURN <BAP_PREFIX> "ID" <identity_key> <root_address> <current_address>
204
- const idPayload = [
206
+ const idPayload: number[][] = [
205
207
  toArray(BAP_PREFIX, "utf8"),
206
208
  toArray("ID"),
207
209
  toArray(generatedIdentityKey),
@@ -210,14 +212,12 @@ async function generateBapKeys(
210
212
  ];
211
213
 
212
214
  // Sign the ID payload with AIP
213
- const signedIdPayload = bapId.signOpReturnWithAIP(idPayload);
214
-
215
- // Convert to hex strings for Script
216
- const idHexStrings = signedIdPayload.map((bytes) =>
217
- BSVUtils.toHex(bytes as number[]),
215
+ const { signedData: signedIdPayload } = await signOpReturnWithAIP(
216
+ idPayload,
217
+ identityKey,
218
+ bapId.rootAddress,
218
219
  );
219
- const idAsmString = `OP_0 OP_RETURN ${idHexStrings.join(" ")}`;
220
- const idScript = Script.fromASM(idAsmString);
220
+ const idScript = buildOpReturnScript(signedIdPayload);
221
221
 
222
222
  // Create the ALIAS transaction output
223
223
  // First create the structured data object for the alias
@@ -229,7 +229,7 @@ async function generateBapKeys(
229
229
  url: `bitcoin:${paymentAddress}`,
230
230
  };
231
231
 
232
- const aliasPayload = [
232
+ const aliasPayload: number[][] = [
233
233
  toArray(BAP_PREFIX, "utf8"),
234
234
  toArray("ALIAS"),
235
235
  toArray(generatedIdentityKey),
@@ -237,14 +237,12 @@ async function generateBapKeys(
237
237
  ];
238
238
 
239
239
  // Sign the ALIAS payload with AIP
240
- const signedAliasPayload = bapId.signOpReturnWithAIP(aliasPayload);
241
-
242
- // Convert to hex strings for Script
243
- const aliasHexStrings = signedAliasPayload.map((bytes) =>
244
- BSVUtils.toHex(bytes as number[]),
240
+ const { signedData: signedAliasPayload } = await signOpReturnWithAIP(
241
+ aliasPayload,
242
+ identityKey,
243
+ bapId.rootAddress,
245
244
  );
246
- const aliasAsmString = `OP_0 OP_RETURN ${aliasHexStrings.join(" ")}`;
247
- const aliasScript = Script.fromASM(aliasAsmString);
245
+ const aliasScript = buildOpReturnScript(signedAliasPayload);
248
246
 
249
247
  // 6. Build Transaction
250
248
  const tx = new Transaction();
@@ -409,14 +407,21 @@ async function generateBapKeys(
409
407
  /**
410
408
  * Registers the bap_generate tool
411
409
  */
412
- export function registerBapGenerateTool(server: McpServer) {
410
+ export function registerBapGenerateTool(
411
+ server: McpServer,
412
+ config?: { disableBroadcasting?: boolean },
413
+ ) {
413
414
  server.tool(
414
415
  "bap_generate",
415
416
  "Generates a BAP HD master key AND derives the first identity key if no BAP keys (xprv or identityPk) exist. Saves keys to secure storage. Attempts on-chain registration using payPk (honors DISABLE_BROADCASTING). Optionally takes alternateName and description for the profile.",
416
417
  { ...bapGenerateArgsSchema.shape },
417
418
  async ({ alternateName, description }): Promise<CallToolResult> => {
418
419
  try {
419
- const result = await generateBapKeys({ alternateName, description });
420
+ const result = await generateBapKeys({
421
+ alternateName,
422
+ description,
423
+ disableBroadcasting: config?.disableBroadcasting,
424
+ });
420
425
  // Format result as JSON
421
426
  return {
422
427
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],