bsv-mcp 0.0.19 → 0.0.20

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/README.md CHANGED
@@ -48,22 +48,23 @@ To use the BSV MCP server with [Cursor](https://cursor.sh/):
48
48
  5. Enter the following configuration in JSON format:
49
49
 
50
50
  ```json
51
- {
52
- "Bitcoin SV": {
53
- "command": "env",
54
- "args": [
55
- "PRIVATE_KEY_WIF=<your_private_key_wif>",
56
- "bun",
57
- "run",
58
- "<path_to_project>/index.ts"
59
- ]
51
+ {
52
+ "mcpServers": {
53
+ "Bitcoin SV": {
54
+ "command": "bunx",
55
+ "args": [
56
+ "bsv-mcp@latest"
57
+ ],
58
+ "env": {
59
+ "PRIVATE_KEY_WIF": "<your_private_key_wif>"
60
+ }
61
+ }
62
+ }
60
63
  }
61
- }
62
64
  ```
63
65
 
64
66
  6. Replace `<your_private_key_wif>` with your actual private key WIF (keep this secure!)
65
- 7. Replace `<path_to_project>` with the full path to where you cloned this repository
66
- 8. Click "Save"
67
+ 7. Click "Save"
67
68
 
68
69
  The BSV tools will now be available to Cursor's AI assistant under the "Bitcoin SV" namespace.
69
70
 
@@ -76,59 +77,30 @@ To connect this server to Claude for Desktop:
76
77
  3. Open your Claude for Desktop configuration file:
77
78
  ```bash
78
79
  # macOS/Linux
79
- code ~/Library/Application\ Support/Claude/claude_desktop_config.json
80
+ code ~/Library/Application Support/Claude/claude_desktop_config.json
80
81
 
81
82
  # Windows
82
83
  code %APPDATA%\Claude\claude_desktop_config.json
83
84
  ```
84
85
  4. Add the BSV MCP server to your configuration (create the file if it doesn't exist):
85
86
  ```json
86
- {
87
- "mcpServers": {
88
- "Bitcoin SV": {
89
- "command": "env",
90
- "args": [
91
- "PRIVATE_KEY_WIF=<your_private_key_wif>",
92
- "bun",
93
- "run",
94
- "<path_to_project>/index.ts"
95
- ]
96
- }
97
- }
98
- }
87
+ {
88
+ "mcpServers": {
89
+ "Bitcoin SV": {
90
+ "command": "bunx",
91
+ "args": [
92
+ "bsv-mcp@latest"
93
+ ],
94
+ "env": {
95
+ "PRIVATE_KEY_WIF": "<your_private_key_wif>"
96
+ }
97
+ }
98
+ }
99
+ }
99
100
  ```
100
101
  5. Replace `<your_private_key_wif>` with your actual private key WIF
101
- 6. Replace `<path_to_project>` with the full path to where you cloned this repository
102
- 7. Save the file and restart Claude for Desktop
103
- 8. The BSV tools will appear when you click the tools icon (hammer) in Claude for Desktop
104
-
105
- ### Generic MCP Client Integration
106
-
107
- For other MCP clients that support JSON configuration:
108
-
109
- ```json
110
- {
111
- "Bitcoin SV": {
112
- "command": "env",
113
- "args": [
114
- "PRIVATE_KEY_WIF=<your_private_key_wif>",
115
- "bun",
116
- "run",
117
- "<path_to_project>/index.ts"
118
- ]
119
- }
120
- }
121
- ```
122
-
123
- If running the server directly:
124
-
125
- ```bash
126
- # Set environment variable first
127
- export PRIVATE_KEY_WIF=<your_private_key_wif>
128
-
129
- # Then run the server
130
- bun run index.ts
131
- ```
102
+ 6. Save the file and restart Claude for Desktop
103
+ 7. The BSV tools will appear when you click the tools icon (hammer) in Claude for Desktop
132
104
 
133
105
  ## Available Tools
134
106
 
package/index.ts CHANGED
@@ -37,7 +37,7 @@ const privKey = validatePrivateKey();
37
37
 
38
38
  const server = new McpServer({
39
39
  name: "Bitcoin SV MCP Server",
40
- version: "0.0.19",
40
+ version: "0.0.20",
41
41
  });
42
42
 
43
43
  // Initialize wallet with the validated private key
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.19",
5
+ "version": "0.0.20",
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",
@@ -0,0 +1,32 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for invoking another agent via A2A protocol
6
+ export const a2aCallArgsSchema = z.object({
7
+ url: z.string().url().describe("Full agent-to-agent endpoint URL"),
8
+ method: z.string().describe("A2A method name to invoke"),
9
+ params: z.record(z.any()).optional().describe("Payload parameters for the A2A call"),
10
+ });
11
+ export type A2aCallArgs = z.infer<typeof a2aCallArgsSchema>;
12
+
13
+ /**
14
+ * Registers the a2a_call tool for agent-to-agent HTTP/SSE calls
15
+ */
16
+ export function registerA2aCallTool(server: McpServer) {
17
+ server.tool(
18
+ "a2a_call",
19
+ "Invoke a remote agent's A2A endpoint via HTTP/SSE",
20
+ { args: a2aCallArgsSchema },
21
+ async (
22
+ { args }: { args: A2aCallArgs },
23
+ extra: RequestHandlerExtra
24
+ ) => {
25
+ // TODO: implement HTTP request logic (e.g., fetch, SSE)
26
+ return {
27
+ content: [{ type: "text", text: "Not implemented" }],
28
+ isError: true,
29
+ };
30
+ }
31
+ );
32
+ }
@@ -0,0 +1,30 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+
5
+ // Schema for agent discovery parameters
6
+ export const a2bDiscoverArgsSchema = z.object({
7
+ query: z.string().describe("Agent name or capability to search for"),
8
+ });
9
+ export type A2bDiscoverArgs = z.infer<typeof a2bDiscoverArgsSchema>;
10
+
11
+ /**
12
+ * Registers the a2b_discover tool for on-chain agent discovery
13
+ */
14
+ export function registerA2bDiscoverTool(server: McpServer) {
15
+ server.tool(
16
+ "a2b_discover",
17
+ "Search on-chain agent records by name or capability",
18
+ { args: a2bDiscoverArgsSchema },
19
+ async (
20
+ { args }: { args: A2bDiscoverArgs },
21
+ extra: RequestHandlerExtra
22
+ ) => {
23
+ // TODO: implement on-chain lookup logic
24
+ return {
25
+ content: [{ type: "text", text: "Not implemented" }],
26
+ isError: true,
27
+ };
28
+ }
29
+ );
30
+ }
package/tools/index.ts CHANGED
@@ -2,6 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { registerBsvTools } from "./bsv";
3
3
  import { registerOrdinalsTools } from "./ordinals";
4
4
  import { registerUtilsTools } from "./utils";
5
+ import { registerA2bDiscoverTool } from "./a2b/discover";
6
+ import { registerA2aCallTool } from "./a2b/call";
5
7
 
6
8
  /**
7
9
  * Register all tools with the MCP server
@@ -17,5 +19,11 @@ export function registerAllTools(server: McpServer): void {
17
19
  // Register utility tools
18
20
  registerUtilsTools(server);
19
21
 
22
+ // Register agent-to-blockchain discovery tool
23
+ registerA2bDiscoverTool(server);
24
+
25
+ // Register agent-to-agent call tool
26
+ registerA2aCallTool(server);
27
+
20
28
  // Add more tool categories as needed
21
29
  }
@@ -0,0 +1,150 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+ import type { Wallet } from "./wallet";
5
+ import { createOrdinals } from "js-1sat-ord";
6
+ import type { Inscription, Destination, ChangeResult, PreMAP } from "js-1sat-ord";
7
+ import { Utils } from "@bsv/sdk";
8
+ const { toArray, toBase64 } = Utils;
9
+
10
+ // https://raw.githubusercontent.com/google/A2A/refs/heads/main/specification/json/a2a.json
11
+
12
+ // A2A AgentCard schema (per A2A spec)
13
+ const AgentCapabilitiesSchema = z.object({
14
+ streaming: z.boolean().default(false),
15
+ pushNotifications: z.boolean().default(false),
16
+ stateTransitionHistory: z.boolean().default(false),
17
+ });
18
+ const AgentSkillSchema = z.object({
19
+ id: z.string(),
20
+ name: z.string(),
21
+ description: z.string().nullable(),
22
+ tags: z.array(z.string()).nullable(),
23
+ examples: z.array(z.string()).nullable(),
24
+ inputModes: z.array(z.string()).nullable(),
25
+ outputModes: z.array(z.string()).nullable(),
26
+ });
27
+
28
+ // Provider per A2A spec
29
+ const AgentProviderSchema = z.object({
30
+ organization: z.string(),
31
+ url: z.string().url().nullable().default(null),
32
+ }).nullable().default(null);
33
+
34
+ // Authentication per A2A spec
35
+ const AgentAuthenticationSchema = z.object({
36
+ schemes: z.array(z.string()),
37
+ credentials: z.string().nullable().default(null),
38
+ }).nullable().default(null);
39
+
40
+ export const AgentCardSchema = z.object({
41
+ name: z.string(),
42
+ description: z.string().nullable().default(null),
43
+ url: z.string().url(),
44
+ provider: AgentProviderSchema,
45
+ version: z.string(),
46
+ documentationUrl: z.string().url().nullable().default(null),
47
+ capabilities: AgentCapabilitiesSchema,
48
+ authentication: AgentAuthenticationSchema,
49
+ defaultInputModes: z.array(z.string()).default(["text"]),
50
+ defaultOutputModes: z.array(z.string()).default(["text"]),
51
+ skills: z.array(AgentSkillSchema),
52
+ });
53
+
54
+ // Schema for on-chain agent publish parameters
55
+ export const a2bPublishArgsSchema = z.object({
56
+ agentUrl: z.string().url().describe("Agent base URL (e.g. https://example.com)"),
57
+ agentName: z.string().describe("Human-friendly agent name"),
58
+ description: z.string().nullable().optional().describe("Optional agent description"),
59
+ providerOrganization: z.string().optional().describe("Optional provider organization name"),
60
+ providerUrl: z.string().url().optional().describe("Optional provider URL"),
61
+ version: z.string().optional().describe("Optional agent version"),
62
+ documentationUrl: z.string().url().nullable().optional().describe("Optional documentation URL"),
63
+ streaming: z.boolean().default(false).describe("Supports SSE (tasks/sendSubscribe)"),
64
+ pushNotifications: z.boolean().default(false).describe("Supports push notifications"),
65
+ stateTransitionHistory: z.boolean().default(false).describe("Supports state transition history"),
66
+ defaultInputModes: z.array(z.string()).default(["text"]).describe("Default input modes"),
67
+ defaultOutputModes: z.array(z.string()).default(["text"]).describe("Default output modes"),
68
+ skills: z.array(AgentSkillSchema).optional().default([]).describe("List of agent skills"),
69
+ destinationAddress: z.string().optional().describe("Optional target address for inscription"),
70
+ });
71
+ export type A2bPublishArgs = z.infer<typeof a2bPublishArgsSchema>;
72
+
73
+ /**
74
+ * Registers the wallet_a2bPublish tool for publishing an agent record on-chain
75
+ */
76
+ export function registerA2bPublishTool(server: McpServer, wallet: Wallet) {
77
+ server.tool(
78
+ "wallet_a2bPublish",
79
+ "Publish an agent.json record on-chain via Ordinal inscription",
80
+ { args: a2bPublishArgsSchema },
81
+ async (
82
+ { args }: { args: A2bPublishArgs },
83
+ extra: RequestHandlerExtra
84
+ ) => {
85
+ try {
86
+ const paymentPk = wallet.getPrivateKey();
87
+ if (!paymentPk) throw new Error("No private key available");
88
+ const { paymentUtxos } = await wallet.getUtxos();
89
+ if (!paymentUtxos?.length) throw new Error("No payment UTXOs available to fund inscription");
90
+
91
+ // Assemble AgentCard with defaults and user overrides
92
+ const agentCard = {
93
+ name: args.agentName,
94
+ description: args.description ?? null,
95
+ url: args.agentUrl,
96
+ provider:
97
+ args.providerOrganization && args.providerUrl
98
+ ? { organization: args.providerOrganization, url: args.providerUrl }
99
+ : null,
100
+ version: args.version ?? "1.0.0",
101
+ documentationUrl: args.documentationUrl ?? null,
102
+ capabilities: {
103
+ streaming: args.streaming,
104
+ pushNotifications: args.pushNotifications,
105
+ stateTransitionHistory: args.stateTransitionHistory,
106
+ },
107
+ authentication: null,
108
+ defaultInputModes: args.defaultInputModes,
109
+ defaultOutputModes: args.defaultOutputModes,
110
+ skills: args.skills,
111
+ };
112
+ // Validate compliance
113
+ AgentCardSchema.parse(agentCard);
114
+ const fileContent = JSON.stringify(agentCard, null, 2);
115
+ // Base64 payload for inscription
116
+ const dataB64 = toBase64(toArray(fileContent));
117
+ const inscription: Inscription = { dataB64, contentType: "application/json" };
118
+ // Destination for the ordinal
119
+ const walletAddress = paymentPk.toAddress().toString();
120
+ const targetAddress = args.destinationAddress ?? walletAddress;
121
+ const destinations: Destination[] = [{ address: targetAddress, inscription }];
122
+ // Default MAP metadata: file path, content type, encoding
123
+ const metaData: PreMAP = { app: 'bsv-mcp', type: 'agent' };
124
+
125
+ // Inscribe the ordinal on-chain via js-1sat-ord
126
+ const result = await createOrdinals({ utxos: paymentUtxos, destinations, paymentPk, changeAddress: walletAddress, metaData });
127
+ const changeResult = result as ChangeResult;
128
+ await changeResult.tx.broadcast();
129
+ // Refresh UTXOs
130
+ try { await wallet.refreshUtxos(); } catch {}
131
+ // Return transaction details
132
+ return {
133
+ content: [{
134
+ type: "text",
135
+ text: JSON.stringify({
136
+ txid: changeResult.tx.id("hex"),
137
+ spentOutpoints: changeResult.spentOutpoints,
138
+ payChange: changeResult.payChange,
139
+ inscriptionAddress: targetAddress,
140
+ agentCard,
141
+ }),
142
+ }],
143
+ };
144
+ } catch (err: unknown) {
145
+ const msg = err instanceof Error ? err.message : String(err);
146
+ return { content: [{ type: "text", text: msg }], isError: true };
147
+ }
148
+ }
149
+ );
150
+ }
@@ -39,6 +39,10 @@ import type { createOrdinalsArgsSchema } from "./createOrdinals";
39
39
  import { registerGetAddressTool } from "./getAddress";
40
40
  import { registerPurchaseListingTool } from "./purchaseListing";
41
41
  import { registerSendToAddressTool } from "./sendToAddress";
42
+ import { registerTransferOrdTokenTool } from "./transferOrdToken";
43
+ import { registerA2bPublishTool } from "./a2bPublish";
44
+ import type { transferOrdTokenArgsSchema } from "./transferOrdToken";
45
+ import type { a2bPublishArgsSchema } from "./a2bPublish";
42
46
  import { Utils, type WalletProtocol } from "@bsv/sdk";
43
47
 
44
48
  // Define mapping from tool names to argument schemas
@@ -70,6 +74,8 @@ type ToolArgSchemas = {
70
74
  wallet_getAddress: typeof getAddressArgsSchema;
71
75
  wallet_sendToAddress: typeof sendToAddressArgsSchema;
72
76
  wallet_purchaseListing: typeof purchaseListingArgsSchema;
77
+ wallet_transferOrdToken: typeof transferOrdTokenArgsSchema;
78
+ wallet_a2bPublish: typeof a2bPublishArgsSchema;
73
79
  wallet_createOrdinals: typeof createOrdinalsArgsSchema;
74
80
  };
75
81
 
@@ -111,6 +117,12 @@ export function registerWalletTools(
111
117
  // Register the wallet_purchaseListing tool
112
118
  registerPurchaseListingTool(server, wallet);
113
119
 
120
+ // Register the wallet_transferOrdToken tool
121
+ registerTransferOrdTokenTool(server, wallet);
122
+
123
+ // Register the wallet_a2bPublish tool
124
+ registerA2bPublishTool(server, wallet);
125
+
114
126
  // Register only the minimal public-facing tools
115
127
  // wallet_createAction, wallet_signAction and wallet_getHeight have been removed
116
128
 
@@ -0,0 +1,103 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
3
+ import { z } from "zod";
4
+ import {
5
+ transferOrdTokens,
6
+ TokenType,
7
+ TokenInputMode,
8
+ TokenSelectionStrategy,
9
+ selectTokenUtxos,
10
+ type TransferOrdTokensConfig,
11
+ type Utxo,
12
+ type TokenUtxo,
13
+ type Payment,
14
+ type Distribution,
15
+ type TokenChangeResult,
16
+ } from "js-1sat-ord";
17
+ import type { Wallet } from "../wallet";
18
+
19
+ // Schema for BSV-20/BSV-21 token transfer arguments
20
+ export const transferOrdTokenArgsSchema = z.object({
21
+ protocol: z.enum(["bsv-20", "bsv-21"]),
22
+ tokenID: z.string(),
23
+ sendAmount: z.number(),
24
+ paymentUtxos: z.array(
25
+ z.object({ txid: z.string(), vout: z.number(), satoshis: z.number(), script: z.string() })
26
+ ),
27
+ tokenUtxos: z.array(
28
+ z.object({ txid: z.string(), vout: z.number(), satoshis: z.literal(1), script: z.string(), amt: z.string(), id: z.string() })
29
+ ),
30
+ distributions: z.array(z.object({ address: z.string(), tokens: z.number() })),
31
+ decimals: z.number(),
32
+ additionalPayments: z.array(z.object({ to: z.string(), amount: z.number() })).optional(),
33
+ });
34
+ export type TransferOrdTokenArgs = z.infer<typeof transferOrdTokenArgsSchema>;
35
+
36
+ /**
37
+ * Register the wallet_transferOrdToken tool for transferring BSV tokens.
38
+ */
39
+ export function registerTransferOrdTokenTool(server: McpServer, wallet: Wallet) {
40
+ server.tool(
41
+ "wallet_transferOrdToken",
42
+ "Transfers BSV-20 or BSV-21 tokens from your wallet via js-1sat-ord transferOrdTokens.",
43
+ { args: transferOrdTokenArgsSchema },
44
+ async (
45
+ { args }: { args: TransferOrdTokenArgs },
46
+ extra: RequestHandlerExtra
47
+ ) => {
48
+ try {
49
+ // fetch keys
50
+ const paymentPk = wallet.getPrivateKey();
51
+ if (!paymentPk) throw new Error("No private key available");
52
+ const ordPk = paymentPk;
53
+ const changeAddress = paymentPk.toAddress().toString();
54
+ const ordAddress = changeAddress;
55
+
56
+ // select token UTXOs
57
+ const { selectedUtxos: inputTokens } = selectTokenUtxos(
58
+ args.tokenUtxos as TokenUtxo[],
59
+ args.sendAmount,
60
+ args.decimals,
61
+ { inputStrategy: TokenSelectionStrategy.SmallestFirst, outputStrategy: TokenSelectionStrategy.LargestFirst }
62
+ );
63
+
64
+ // build config
65
+ const config: TransferOrdTokensConfig = {
66
+ protocol: args.protocol === "bsv-20" ? TokenType.BSV20 : TokenType.BSV21,
67
+ tokenID: args.tokenID,
68
+ utxos: args.paymentUtxos as Utxo[],
69
+ inputTokens,
70
+ distributions: args.distributions as Distribution[],
71
+ tokenChangeAddress: ordAddress,
72
+ changeAddress,
73
+ paymentPk,
74
+ ordPk,
75
+ additionalPayments: args.additionalPayments as Payment[] || [],
76
+ decimals: args.decimals,
77
+ inputMode: TokenInputMode.Needed,
78
+ splitConfig: { outputs: inputTokens.length === 1 ? 2 : 1, threshold: args.sendAmount },
79
+ };
80
+
81
+ // execute transfer
82
+ const result: TokenChangeResult = await transferOrdTokens(config);
83
+ await result.tx.broadcast();
84
+
85
+ // refresh UTXOs
86
+ try { await wallet.refreshUtxos(); } catch {}
87
+
88
+ // respond
89
+ return {
90
+ content: [{ type: "text", text: JSON.stringify({
91
+ txid: result.tx.id("hex"),
92
+ spentOutpoints: result.spentOutpoints,
93
+ payChange: result.payChange,
94
+ tokenChange: result.tokenChange,
95
+ }) }]
96
+ };
97
+ } catch (err: unknown) {
98
+ const msg = err instanceof Error ? err.message : String(err);
99
+ return { content: [{ type: "text", text: msg }], isError: true };
100
+ }
101
+ }
102
+ );
103
+ }