bsv-mcp 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,15 @@
1
- import { type PrivateKey, Utils } from "@bsv/sdk";
1
+ import type { OneSatContext } from "@1sat/actions";
2
+ import type { PrivateKey } from "@bsv/sdk";
2
3
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
4
  import { registerA2bPublishMcpTool } from "./a2bPublishMcp";
5
+ import { registerBrc100Tools } from "./brc100";
4
6
  import { registerCreateOrdinalsTool } from "./createOrdinals";
5
7
  import { registerGatherCollectionInfoTool } from "./gatherCollectionInfo";
6
8
  import { registerGetAddressTool } from "./getAddress";
7
9
  import { registerWalletGetBalanceTool } from "./getBalance";
8
- import { registerGetPublicKeyTool } from "./getPublicKey";
9
10
  import { registerMintCollectionTool } from "./mintCollection";
10
11
  import { registerPurchaseListingTool } from "./purchaseListing";
11
12
  import { registerRefreshUtxosTool } from "./refreshUtxos";
12
- import { walletEncryptionArgsSchema } from "./schemas";
13
13
  import { registerSendToAddressTool } from "./sendToAddress";
14
14
  import { registerTransferOrdTokenTool } from "./transferOrdToken";
15
15
  import type { Wallet } from "./wallet";
@@ -21,28 +21,29 @@ export function registerWalletTools(
21
21
  disableBroadcasting: boolean;
22
22
  enableA2bTools: boolean;
23
23
  identityPk?: PrivateKey;
24
+ ctx?: OneSatContext;
24
25
  },
25
26
  ): void {
26
27
  // Register the wallet_sendToAddress tool
27
- registerSendToAddressTool(server, wallet);
28
+ registerSendToAddressTool(server, config.ctx);
28
29
 
29
30
  // Register the wallet_getAddress tool
30
31
  registerGetAddressTool(server, wallet);
31
32
 
32
- // Register the wallet_getPublicKey tool
33
- registerGetPublicKeyTool(server, wallet);
34
-
35
33
  // Register the wallet_purchaseListing tool
36
- registerPurchaseListingTool(server, wallet);
34
+ registerPurchaseListingTool(server, config.ctx);
37
35
 
38
36
  // Register the wallet_transferOrdToken tool
39
- registerTransferOrdTokenTool(server, wallet);
37
+ registerTransferOrdTokenTool(server, config.ctx);
40
38
 
41
39
  // Register the wallet_refreshUtxos tool
42
40
  registerRefreshUtxosTool(server, wallet);
43
41
 
44
42
  // Register the wallet_getBalance tool
45
- registerWalletGetBalanceTool(server, wallet);
43
+ registerWalletGetBalanceTool(server, config.ctx);
44
+
45
+ // Register full BRC-100 wallet interface
46
+ registerBrc100Tools(server, config.ctx);
46
47
 
47
48
  // A2B tools have to be explicitly enabled
48
49
  if (config.enableA2bTools) {
@@ -52,83 +53,8 @@ export function registerWalletTools(
52
53
  });
53
54
  }
54
55
 
55
- // Register combined wallet_encryption tool
56
- server.tool(
57
- "wallet_encryption",
58
- "Combined tool for encrypting and decrypting data using the wallet's cryptographic keys.\n\n" +
59
- "PARAMETERS:\n" +
60
- '- mode: (required) Either "encrypt" to encrypt plaintext or "decrypt" to decrypt ciphertext\n' +
61
- "- data: (required) Text string or array of numbers to process\n" +
62
- "- encoding: (optional) For text input, the encoding format (utf8, hex, base64) - default is utf8\n" +
63
- "- recipientPublicKeyHex: (optional) Hexadecimal representation of the recipient's public key (required for encrypt mode)\n\n" +
64
- "EXAMPLES:\n" +
65
- "1. Encrypt text data:\n" +
66
- " {\n" +
67
- ' "mode": "encrypt",\n' +
68
- ' "data": "Hello World",\n' +
69
- ' "encoding": "utf8",\n' +
70
- ' "recipientPublicKeyHex": "020202020202020202020202020202020202020202020202020202020202020202"\n' +
71
- " }\n\n" +
72
- "2. Decrypt previously encrypted data:\n" +
73
- " {\n" +
74
- ' "mode": "decrypt",\n' +
75
- ' "data": [encrypted bytes from previous response]\n' +
76
- " }",
77
- { ...walletEncryptionArgsSchema.shape },
78
- async ({ mode, data, encoding, recipientPublicKeyHex }) => {
79
- try {
80
-
81
- let inputData: number[];
82
- if (typeof data === "string") {
83
- inputData = Utils.toArray(data, encoding || "utf8");
84
- } else {
85
- inputData = Array.from(data);
86
- }
87
-
88
- // let resultBuffer: Buffer;
89
- // let resultOutput: string | number[];
90
-
91
- if (mode === "encrypt") {
92
- // recipientPublicKeyHex is validated by Zod schema to be present for encrypt mode
93
- // The schema ensures recipientPublicKeyHex is defined here.
94
- // resultBuffer = await wallet.encrypt({
95
- // data: inputBuffer,
96
- // recipientPublicKeyHex: recipientPublicKeyHex as string, // Still needs type assertion if Zod optional isn't fully narrowed
97
- // });
98
- throw new Error("Encrypt method not yet implemented in wallet");
99
- // if (encoding === "utf8") {
100
- // resultOutput = Array.from(resultBuffer);
101
- // } else {
102
- // resultOutput = resultBuffer.toString(encoding || "base64");
103
- // }
104
- }
105
- // mode === "decrypt"
106
- // resultBuffer = await wallet.decrypt({ data: inputBuffer });
107
- throw new Error("Decrypt method not yet implemented in wallet");
108
-
109
- // return {
110
- // content: [
111
- // { type: "text", text: JSON.stringify({ data: resultOutput }) },
112
- // ],
113
- // };
114
- } catch (error) {
115
- const errorMessage =
116
- error instanceof Error ? error.message : String(error);
117
- return {
118
- content: [
119
- {
120
- type: "text",
121
- text: `Error during ${mode}: ${errorMessage}`,
122
- },
123
- ],
124
- isError: true,
125
- };
126
- }
127
- },
128
- );
129
-
130
56
  // Register createOrdinals tool
131
- registerCreateOrdinalsTool(server, wallet);
57
+ registerCreateOrdinalsTool(server, config.ctx);
132
58
 
133
59
  // Register collection tools
134
60
  registerGatherCollectionInfoTool(server, wallet);
@@ -1,156 +1,164 @@
1
- import { PrivateKey } from "@bsv/sdk";
2
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import {
4
- type Distribution,
5
- type LocalSigner,
6
- OneSatBroadcaster,
7
- type Payment,
8
- selectTokenUtxos,
9
- type TokenChangeResult,
10
- TokenInputMode,
11
- TokenSelectionStrategy,
12
- TokenType,
13
- type TokenUtxo,
14
- type TransferOrdTokensConfig,
15
- transferOrdTokens,
16
- type Utxo,
17
- } from "js-1sat-ord";
18
- import { z } from "zod";
19
- import { V5Broadcaster } from "../../utils/broadcaster";
20
- import type { Wallet } from "./wallet";
1
+ import type { OneSatContext } from '@1sat/actions'
2
+ import { sendBsv21, transferOrdinals } from '@1sat/actions'
3
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4
+ import { z } from 'zod'
5
+
6
+ const walletOutputSchema = z.object({
7
+ outpoint: z.string(),
8
+ satoshis: z.number().optional(),
9
+ tags: z.array(z.string()).optional(),
10
+ customInstructions: z.string().optional(),
11
+ lockingScript: z.string().optional(),
12
+ })
21
13
 
22
- // Schema for BSV-20/BSV-21 token transfer arguments
23
14
  export const transferOrdTokenArgsSchema = z.object({
24
- protocol: z.enum(["bsv-20", "bsv-21"]),
25
- tokenID: z.string(),
26
- sendAmount: z.number(),
27
- paymentUtxos: z.array(
28
- z.object({
29
- txid: z.string(),
30
- vout: z.number(),
31
- satoshis: z.number(),
32
- script: z.string(),
33
- }),
34
- ),
35
- tokenUtxos: z.array(
36
- z.object({
37
- txid: z.string(),
38
- vout: z.number(),
39
- satoshis: z.literal(1),
40
- script: z.string(),
41
- amt: z.string(),
42
- id: z.string(),
43
- }),
15
+ type: z.enum(['ordinal', 'bsv21']).describe(
16
+ "'ordinal' to transfer an inscription/NFT, 'bsv21' to send fungible BSV21 tokens",
44
17
  ),
45
- distributions: z.array(z.object({ address: z.string(), tokens: z.number() })),
46
- decimals: z.number(),
47
- additionalPayments: z
48
- .array(z.object({ to: z.string(), amount: z.number() }))
49
- .optional(),
50
- });
51
- export type TransferOrdTokenArgs = z.infer<typeof transferOrdTokenArgsSchema>;
18
+ // ordinal fields
19
+ ordinal: walletOutputSchema
20
+ .optional()
21
+ .describe(
22
+ "WalletOutput of the ordinal to transfer (from wallet_getOrdinals). Required when type='ordinal'",
23
+ ),
24
+ inputBEEF: z
25
+ .array(z.number())
26
+ .optional()
27
+ .describe(
28
+ "BEEF bytes from listOutputs (include: 'entire transactions'). Required when type='ordinal'",
29
+ ),
30
+ // bsv21 fields
31
+ tokenId: z
32
+ .string()
33
+ .optional()
34
+ .describe("Token ID (txid_vout format). Required when type='bsv21'"),
35
+ amount: z
36
+ .string()
37
+ .optional()
38
+ .describe("Amount of tokens to send as a string integer. Required when type='bsv21'"),
39
+ // shared destination fields
40
+ address: z.string().optional().describe('Recipient P2PKH address'),
41
+ counterparty: z
42
+ .string()
43
+ .optional()
44
+ .describe('Recipient identity public key (hex)'),
45
+ })
46
+
47
+ export type TransferOrdTokenArgs = z.infer<typeof transferOrdTokenArgsSchema>
52
48
 
53
- /**
54
- * Register the wallet_transferOrdToken tool for transferring BSV tokens.
55
- */
56
49
  export function registerTransferOrdTokenTool(
57
50
  server: McpServer,
58
- wallet: Wallet,
51
+ ctx: OneSatContext | undefined,
59
52
  ) {
60
53
  server.tool(
61
- "wallet_transferOrdToken",
62
- "Transfers BSV-20 or BSV-21 tokens from your wallet via js-1sat-ord transferOrdTokens.",
54
+ 'wallet_transferOrdToken',
55
+ "Transfer an ordinal inscription or send BSV21 fungible tokens. Use type='ordinal' to transfer an NFT/inscription (requires the WalletOutput from wallet_getOrdinals and the BEEF). Use type='bsv21' to send fungible tokens by token ID and amount.",
63
56
  { ...transferOrdTokenArgsSchema.shape },
64
- async ({ protocol, tokenID, sendAmount, paymentUtxos, tokenUtxos, distributions, decimals, additionalPayments }) => {
65
- try {
66
- // fetch keys
67
- const paymentPk = wallet.getPaymentKey();
68
- if (!paymentPk) throw new Error("No private key available");
69
- const ordPk = paymentPk;
70
- const changeAddress = paymentPk.toAddress().toString();
71
- const ordAddress = changeAddress;
57
+ async ({ type, ordinal, inputBEEF, tokenId, amount, address, counterparty }) => {
58
+ if (!ctx) {
59
+ return {
60
+ content: [
61
+ {
62
+ type: 'text',
63
+ text: 'Wallet not initialized. Please configure a wallet before transferring.',
64
+ },
65
+ ],
66
+ isError: true,
67
+ }
68
+ }
72
69
 
73
- // select token UTXOs
74
- const { selectedUtxos: inputTokens } = selectTokenUtxos(
75
- tokenUtxos as TokenUtxo[],
76
- sendAmount,
77
- decimals,
78
- {
79
- inputStrategy: TokenSelectionStrategy.SmallestFirst,
80
- outputStrategy: TokenSelectionStrategy.LargestFirst,
81
- },
82
- );
70
+ try {
71
+ if (type === 'ordinal') {
72
+ if (!ordinal) {
73
+ return {
74
+ content: [{ type: 'text', text: "ordinal is required when type='ordinal'" }],
75
+ isError: true,
76
+ }
77
+ }
78
+ if (!inputBEEF) {
79
+ return {
80
+ content: [{ type: 'text', text: "inputBEEF is required when type='ordinal'" }],
81
+ isError: true,
82
+ }
83
+ }
84
+ if (!address && !counterparty) {
85
+ return {
86
+ content: [{ type: 'text', text: 'address or counterparty is required' }],
87
+ isError: true,
88
+ }
89
+ }
83
90
 
84
- // build config
85
- const config: TransferOrdTokensConfig = {
86
- protocol:
87
- protocol === "bsv-20" ? TokenType.BSV20 : TokenType.BSV21,
88
- tokenID,
89
- utxos: paymentUtxos as Utxo[],
90
- inputTokens,
91
- distributions: distributions as Distribution[],
92
- tokenChangeAddress: ordAddress,
93
- changeAddress,
94
- paymentPk,
95
- ordPk,
96
- additionalPayments: (additionalPayments as Payment[]) || [],
97
- decimals,
98
- inputMode: TokenInputMode.Needed,
99
- splitConfig: {
100
- outputs: inputTokens.length === 1 ? 2 : 1,
101
- threshold: sendAmount,
102
- },
103
- };
91
+ const result = await transferOrdinals.execute(ctx, {
92
+ transfers: [
93
+ {
94
+ ordinal: ordinal as Parameters<typeof transferOrdinals.execute>[1]['transfers'][0]['ordinal'],
95
+ address,
96
+ counterparty,
97
+ },
98
+ ],
99
+ inputBEEF,
100
+ })
104
101
 
105
- const identityPk = process.env.IDENTITY_KEY_WIF
106
- ? PrivateKey.fromWif(process.env.IDENTITY_KEY_WIF)
107
- : undefined;
102
+ if (result.error) {
103
+ return {
104
+ content: [{ type: 'text', text: result.error }],
105
+ isError: true,
106
+ }
107
+ }
108
108
 
109
- if (identityPk) {
110
- config.signer = {
111
- idKey: identityPk,
112
- } as LocalSigner;
109
+ return {
110
+ content: [{ type: 'text', text: JSON.stringify({ txid: result.txid }) }],
111
+ }
113
112
  }
114
113
 
115
- // execute transfer
116
- const result: TokenChangeResult = await transferOrdTokens(config);
117
- const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
118
- if (!disableBroadcasting) {
119
- const broadcaster = new V5Broadcaster();
120
- await result.tx.broadcast(broadcaster);
114
+ // bsv21
115
+ if (!tokenId) {
116
+ return {
117
+ content: [{ type: 'text', text: "tokenId is required when type='bsv21'" }],
118
+ isError: true,
119
+ }
120
+ }
121
+ if (!amount) {
122
+ return {
123
+ content: [{ type: 'text', text: "amount is required when type='bsv21'" }],
124
+ isError: true,
125
+ }
126
+ }
127
+ if (!address && !counterparty) {
128
+ return {
129
+ content: [{ type: 'text', text: 'address or counterparty is required' }],
130
+ isError: true,
131
+ }
132
+ }
121
133
 
122
- // refresh UTXOs
123
- try {
124
- await wallet.refreshUtxos();
125
- } catch {}
134
+ const result = await sendBsv21.execute(ctx, {
135
+ tokenId,
136
+ amount,
137
+ address,
138
+ counterparty,
139
+ })
126
140
 
127
- // respond
141
+ if (result.error) {
128
142
  return {
129
- content: [
130
- {
131
- type: "text",
132
- text: JSON.stringify({
133
- txid: result.tx.id("hex"),
134
- spentOutpoints: result.spentOutpoints,
135
- payChange: result.payChange,
136
- tokenChange: result.tokenChange,
137
- }),
138
- },
139
- ],
140
- };
143
+ content: [{ type: 'text', text: result.error }],
144
+ isError: true,
145
+ }
141
146
  }
147
+
148
+ return {
149
+ content: [{ type: 'text', text: JSON.stringify({ txid: result.txid }) }],
150
+ }
151
+ } catch (err: unknown) {
142
152
  return {
143
153
  content: [
144
154
  {
145
- type: "text",
146
- text: result.tx.toHex(),
155
+ type: 'text',
156
+ text: err instanceof Error ? err.message : String(err),
147
157
  },
148
158
  ],
149
- };
150
- } catch (err: unknown) {
151
- const msg = err instanceof Error ? err.message : String(err);
152
- return { content: [{ type: "text", text: msg }], isError: true };
159
+ isError: true,
160
+ }
153
161
  }
154
162
  },
155
- );
163
+ )
156
164
  }