bsv-mcp 0.0.36 → 0.2.7
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 +124 -0
- package/README.md +233 -4
- package/dist/index.js +120082 -54669
- package/index.ts +1196 -88
- package/next-env.d.ts +6 -0
- package/package.json +58 -15
- package/prompts/bsvSdk/index.ts +5 -5
- package/prompts/bsvSdk/transaction.ts +2 -2
- package/prompts/bsvSdk/wallet.ts +1 -1
- package/prompts/index.ts +1 -1
- package/resources/bitcom.ts +132 -0
- package/resources/resources.ts +7 -3
- package/smithery.yaml +3 -0
- package/test-mcp-server.ts +157 -0
- package/tools/a2b/discover.ts +13 -13
- package/tools/bap/friend.ts +266 -0
- package/tools/bap/generate.test.ts +350 -0
- package/tools/bap/generate.ts +431 -0
- package/tools/bap/getCurrentAddress.ts +69 -0
- package/tools/bap/getId.test.ts +121 -0
- package/tools/bap/getId.ts +169 -0
- package/tools/bap/index.ts +111 -0
- package/tools/bap/types.ts +66 -0
- package/tools/bap/utils.ts +33 -0
- package/tools/bsocial/bmapFollow.ts +121 -0
- package/tools/bsocial/bmapLikes.ts +111 -0
- package/tools/bsocial/bmapReadPosts.ts +148 -0
- package/tools/bsocial/createPost.ts +136 -0
- package/tools/bsocial/index.ts +45 -0
- package/tools/bsocial/readPosts.ts +255 -0
- package/tools/bsv/decodeTransaction.ts +7 -6
- package/tools/bsv/explore.ts +2 -3
- package/tools/bsv/getPrice.ts +1 -9
- package/tools/bsv/token.ts +14 -26
- package/tools/constants.ts +21 -0
- package/tools/index.ts +91 -18
- package/tools/mnee/getBalance.ts +2 -4
- package/tools/mnee/index.ts +8 -0
- package/tools/mnee/parseTx.ts +4 -4
- package/tools/mnee/sendMnee.ts +6 -6
- package/tools/ordinals/getInscription.ts +2 -3
- package/tools/ordinals/getTokenByIdOrTicker.ts +6 -3
- package/tools/ordinals/marketListings.ts +2 -18
- package/tools/ordinals/marketSales.ts +2 -4
- package/tools/ordinals/searchInscriptions.ts +2 -4
- package/tools/utils/aip.ts +46 -0
- package/tools/utils/conversion.ts +1 -0
- package/tools/utils/errorHandler.ts +76 -0
- package/tools/utils/index.ts +56 -14
- package/tools/utils/installAgentMaster.ts +84 -0
- package/tools/utils/logger.ts +20 -0
- package/tools/utils/toolConfig.ts +80 -0
- package/tools/utils/toolRegistration.ts +66 -0
- package/tools/utils/transactionBuilder.ts +196 -0
- package/tools/wallet/a2bPublishAgent.ts +25 -30
- package/tools/wallet/a2bPublishMcp.ts +43 -44
- package/tools/wallet/createOrdinals.ts +13 -20
- package/tools/wallet/fetchPaymentUtxos.ts +48 -39
- package/tools/wallet/gatherCollectionInfo.ts +370 -0
- package/tools/wallet/getAddress.ts +14 -30
- package/tools/wallet/getBalance.ts +54 -0
- package/tools/wallet/getBalanceDroplet.ts +48 -0
- package/tools/wallet/getPublicKey.ts +3 -16
- package/tools/wallet/integratedWallet.ts +120 -0
- package/tools/wallet/mintCollection.ts +373 -0
- package/tools/wallet/purchaseListing.ts +21 -31
- package/tools/wallet/refreshUtxos.ts +2 -14
- package/tools/wallet/schemas.ts +18 -249
- package/tools/wallet/sendOrdinals.ts +14 -21
- package/tools/wallet/sendToAddress.ts +2 -18
- package/tools/wallet/setupDroplet.ts +161 -0
- package/tools/wallet/tools.ts +55 -210
- package/tools/wallet/transactionHelper.ts +121 -0
- package/tools/wallet/transferOrdToken.ts +19 -24
- package/tools/wallet/utxo.ts +62 -0
- package/tools/wallet/wallet.ts +237 -234
- package/vite.config.ts +15 -0
- package/tools/a2b/call.ts +0 -32
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { BMAP_URL } from "../constants";
|
|
5
|
+
|
|
6
|
+
// Schema for reading posts from BMAP API
|
|
7
|
+
const bmapReadPostsArgsSchema = z.object({
|
|
8
|
+
bapId: z
|
|
9
|
+
.string()
|
|
10
|
+
.optional()
|
|
11
|
+
.describe("BAP identity key to filter posts by author"),
|
|
12
|
+
txid: z.string().optional().describe("Specific transaction ID to fetch"),
|
|
13
|
+
limit: z
|
|
14
|
+
.number()
|
|
15
|
+
.min(1)
|
|
16
|
+
.max(100)
|
|
17
|
+
.default(20)
|
|
18
|
+
.describe("Maximum number of posts to return"),
|
|
19
|
+
page: z
|
|
20
|
+
.number()
|
|
21
|
+
.min(1)
|
|
22
|
+
.default(1)
|
|
23
|
+
.describe("Page number for pagination (1-based)"),
|
|
24
|
+
feed: z
|
|
25
|
+
.boolean()
|
|
26
|
+
.default(false)
|
|
27
|
+
.describe("Whether to fetch feed (posts from followed users)"),
|
|
28
|
+
address: z.string().optional().describe("Bitcoin address to filter posts by"),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
type BmapReadPostsArgs = z.infer<typeof bmapReadPostsArgsSchema>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Read social posts from the BMAP API (the query/read layer)
|
|
35
|
+
*/
|
|
36
|
+
export async function readBmapPosts(args: BmapReadPostsArgs): Promise<{
|
|
37
|
+
success: boolean;
|
|
38
|
+
data?: unknown;
|
|
39
|
+
error?: string;
|
|
40
|
+
}> {
|
|
41
|
+
try {
|
|
42
|
+
const { bapId, txid, limit, page, feed, address } = args;
|
|
43
|
+
|
|
44
|
+
// Handle single post request
|
|
45
|
+
if (txid) {
|
|
46
|
+
const response = await fetch(`${BMAP_URL}/post/${txid}`, {
|
|
47
|
+
method: "GET",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
const errorText = await response.text();
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = await response.json();
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
data,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle posts list request
|
|
69
|
+
const params = new URLSearchParams();
|
|
70
|
+
if (bapId) params.append("bapId", bapId);
|
|
71
|
+
if (address) params.append("address", address);
|
|
72
|
+
params.append("limit", limit.toString());
|
|
73
|
+
params.append("page", page.toString());
|
|
74
|
+
if (feed) params.append("feed", "true");
|
|
75
|
+
|
|
76
|
+
const response = await fetch(`${BMAP_URL}/posts?${params}`, {
|
|
77
|
+
method: "GET",
|
|
78
|
+
headers: {
|
|
79
|
+
"Content-Type": "application/json",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const errorText = await response.text();
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const data = await response.json();
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
data,
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
|
+
console.error("Error reading BMAP posts:", errorMessage);
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
error: errorMessage,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Register the BMAP read posts tool with the MCP server
|
|
108
|
+
*/
|
|
109
|
+
export function registerBmapReadPostsTool(server: McpServer) {
|
|
110
|
+
server.tool(
|
|
111
|
+
"bmap_readPosts",
|
|
112
|
+
"Read social posts from the BMAP API (query layer). Can fetch posts by author (BAP ID), specific post by transaction ID, or recent posts from all users. Supports pagination and feed functionality.",
|
|
113
|
+
{ ...bmapReadPostsArgsSchema.shape },
|
|
114
|
+
async ({ bapId, txid, limit, page, feed, address }): Promise<CallToolResult> => {
|
|
115
|
+
try {
|
|
116
|
+
const result = await readBmapPosts({ bapId, txid, limit, page, feed, address });
|
|
117
|
+
|
|
118
|
+
if (result.success) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: JSON.stringify(result.data, null, 2),
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
isError: false,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: result.error || "Unknown error occurred",
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
isError: true,
|
|
138
|
+
};
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
143
|
+
isError: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Utils } from "@bsv/sdk";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { B_PREFIX, MAP_PREFIX } from "../constants";
|
|
6
|
+
import { signOpReturnWithAIP } from "../utils/aip";
|
|
7
|
+
import { createResponse, formatError } from "../utils/errorHandler";
|
|
8
|
+
import {
|
|
9
|
+
buildAndSendTransaction,
|
|
10
|
+
buildOpReturnScript,
|
|
11
|
+
} from "../utils/transactionBuilder";
|
|
12
|
+
import type { Wallet } from "../wallet/wallet";
|
|
13
|
+
|
|
14
|
+
const { toArray } = Utils;
|
|
15
|
+
|
|
16
|
+
// Schema for creating a social post
|
|
17
|
+
const createPostArgsSchema = z.object({
|
|
18
|
+
content: z.string().describe("The content of the post"),
|
|
19
|
+
contentType: z
|
|
20
|
+
.enum(["text/plain", "text/markdown"])
|
|
21
|
+
.default("text/plain")
|
|
22
|
+
.describe("Content type of the post"),
|
|
23
|
+
app: z
|
|
24
|
+
.string()
|
|
25
|
+
.default("bsv-mcp")
|
|
26
|
+
.describe("Application name creating the post"),
|
|
27
|
+
additionalMapData: z
|
|
28
|
+
.record(z.string())
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Additional MAP protocol key-value pairs"),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type CreatePostArgs = z.infer<typeof createPostArgsSchema>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a social post on the BSV blockchain using B:// and MAP protocols
|
|
37
|
+
*/
|
|
38
|
+
export async function createSocialPost(
|
|
39
|
+
args: CreatePostArgs,
|
|
40
|
+
wallet: Wallet,
|
|
41
|
+
): Promise<{
|
|
42
|
+
success: boolean;
|
|
43
|
+
txid?: string;
|
|
44
|
+
rawTx?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
}> {
|
|
47
|
+
try {
|
|
48
|
+
const { content, contentType, app, additionalMapData } = args;
|
|
49
|
+
|
|
50
|
+
// Get wallet info
|
|
51
|
+
const address = await wallet.getAddress();
|
|
52
|
+
const paymentKey = wallet.getPaymentKey();
|
|
53
|
+
if (!paymentKey) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: "Payment key not available in wallet",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Prepare file extension based on content type
|
|
61
|
+
const fileExtension = contentType === "text/markdown" ? "md" : "txt";
|
|
62
|
+
const fileName = `post.${fileExtension}`;
|
|
63
|
+
|
|
64
|
+
// Build B protocol data
|
|
65
|
+
const bData = [
|
|
66
|
+
toArray(B_PREFIX, "utf8"),
|
|
67
|
+
toArray(content, "utf8"),
|
|
68
|
+
toArray(contentType, "utf8"),
|
|
69
|
+
toArray("utf-8", "utf8"),
|
|
70
|
+
toArray(fileName, "utf8"),
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// Build MAP protocol data
|
|
74
|
+
const mapData: Uint8Array[] = [
|
|
75
|
+
toArray(MAP_PREFIX, "utf8"),
|
|
76
|
+
toArray("SET", "utf8"),
|
|
77
|
+
toArray("app", "utf8"),
|
|
78
|
+
toArray(app, "utf8"),
|
|
79
|
+
toArray("type", "utf8"),
|
|
80
|
+
toArray("post", "utf8"),
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// Add any additional MAP data
|
|
84
|
+
if (additionalMapData) {
|
|
85
|
+
for (const [key, value] of Object.entries(additionalMapData)) {
|
|
86
|
+
mapData.push(toArray(key, "utf8"), toArray(value, "utf8"));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Combine protocols with pipe separator
|
|
91
|
+
const pipeData = toArray("|", "utf8");
|
|
92
|
+
const dataToSign = [...bData, pipeData, ...mapData];
|
|
93
|
+
|
|
94
|
+
// Sign the data with AIP
|
|
95
|
+
const { signedData } = await signOpReturnWithAIP(
|
|
96
|
+
dataToSign,
|
|
97
|
+
paymentKey,
|
|
98
|
+
address.toString(),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Create OP_RETURN script
|
|
102
|
+
const script = buildOpReturnScript(signedData);
|
|
103
|
+
|
|
104
|
+
// Get UTXOs
|
|
105
|
+
const utxos = await wallet.getPaymentUtxos();
|
|
106
|
+
|
|
107
|
+
// Build and send transaction using the new utility
|
|
108
|
+
return await buildAndSendTransaction({
|
|
109
|
+
outputs: [{ script, satoshis: 0 }],
|
|
110
|
+
utxos,
|
|
111
|
+
changeAddress: address.toString(),
|
|
112
|
+
paymentKey,
|
|
113
|
+
});
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error("Error creating social post:", formatError(error));
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: formatError(error),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register the social post tool with the MCP server
|
|
125
|
+
*/
|
|
126
|
+
export function registerCreatePostTool(server: McpServer, wallet: Wallet) {
|
|
127
|
+
server.tool(
|
|
128
|
+
"bsocial_createPost",
|
|
129
|
+
"Create a social post on the BSV blockchain using B:// and MAP protocols. Posts are stored permanently on-chain and can include plain text or markdown content.",
|
|
130
|
+
{ ...createPostArgsSchema.shape },
|
|
131
|
+
async ({ content, contentType, app, additionalMapData }): Promise<CallToolResult> => {
|
|
132
|
+
const result = await createSocialPost({ content, contentType, app, additionalMapData }, wallet);
|
|
133
|
+
return createResponse(result);
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { Wallet } from "../wallet/wallet";
|
|
3
|
+
import { registerBmapReadFollowsTool } from "./bmapFollow";
|
|
4
|
+
import { registerBmapReadLikesTool } from "./bmapLikes";
|
|
5
|
+
import { registerBmapReadPostsTool } from "./bmapReadPosts";
|
|
6
|
+
import { registerCreatePostTool } from "./createPost";
|
|
7
|
+
import { registerReadPostsTool } from "./readPosts";
|
|
8
|
+
|
|
9
|
+
interface BsocialToolsConfig {
|
|
10
|
+
wallet?: Wallet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Register all BSocial tools with the MCP server
|
|
15
|
+
* @param server The MCP server instance
|
|
16
|
+
* @param config Configuration including wallet instance
|
|
17
|
+
*/
|
|
18
|
+
export function registerBsocialTools(
|
|
19
|
+
server: McpServer,
|
|
20
|
+
config: BsocialToolsConfig,
|
|
21
|
+
): void {
|
|
22
|
+
// Register tools that don't require wallet
|
|
23
|
+
registerReadPostsTool(server);
|
|
24
|
+
console.error("✅ Registered bsocial_readPosts tool");
|
|
25
|
+
|
|
26
|
+
// Register BMAP API query tools (read layer)
|
|
27
|
+
registerBmapReadPostsTool(server);
|
|
28
|
+
console.error("✅ Registered bmap_readPosts tool");
|
|
29
|
+
|
|
30
|
+
registerBmapReadLikesTool(server);
|
|
31
|
+
console.error("✅ Registered bmap_readLikes tool");
|
|
32
|
+
|
|
33
|
+
registerBmapReadFollowsTool(server);
|
|
34
|
+
console.error("✅ Registered bmap_readFollows tool");
|
|
35
|
+
|
|
36
|
+
// Register tools that require wallet
|
|
37
|
+
if (config.wallet) {
|
|
38
|
+
registerCreatePostTool(server, config.wallet);
|
|
39
|
+
console.error("✅ Registered bsocial_createPost tool");
|
|
40
|
+
} else {
|
|
41
|
+
console.error(
|
|
42
|
+
"⚠️ bsocial_createPost tool not registered (no wallet available)",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import type {
|
|
4
|
+
ApiResponse,
|
|
5
|
+
BapIdentity,
|
|
6
|
+
PostMeta,
|
|
7
|
+
PostResponse,
|
|
8
|
+
PostsResponse,
|
|
9
|
+
PostTransaction,
|
|
10
|
+
} from "bmap-api-types";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { BMAP_URL } from "../constants";
|
|
13
|
+
|
|
14
|
+
// Schema for reading posts
|
|
15
|
+
const readPostsArgsSchema = z.object({
|
|
16
|
+
bapId: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("BAP identity key to filter posts by author"),
|
|
20
|
+
txid: z.string().optional().describe("Specific transaction ID to fetch"),
|
|
21
|
+
limit: z
|
|
22
|
+
.number()
|
|
23
|
+
.min(1)
|
|
24
|
+
.max(100)
|
|
25
|
+
.default(20)
|
|
26
|
+
.describe("Maximum number of posts to return"),
|
|
27
|
+
page: z
|
|
28
|
+
.number()
|
|
29
|
+
.min(1)
|
|
30
|
+
.default(1)
|
|
31
|
+
.describe("Page number for pagination (1-based)"),
|
|
32
|
+
feed: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.default(false)
|
|
35
|
+
.describe("Whether to fetch feed (posts from followed users)"),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
type ReadPostsArgs = z.infer<typeof readPostsArgsSchema>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Read social posts from the BMAP API using official types
|
|
42
|
+
*/
|
|
43
|
+
export async function readSocialPosts(args: ReadPostsArgs): Promise<{
|
|
44
|
+
success: boolean;
|
|
45
|
+
posts?: PostTransaction[];
|
|
46
|
+
signers?: BapIdentity[];
|
|
47
|
+
meta?: PostMeta[];
|
|
48
|
+
pagination?: {
|
|
49
|
+
page: number;
|
|
50
|
+
limit: number;
|
|
51
|
+
count: number;
|
|
52
|
+
};
|
|
53
|
+
error?: string;
|
|
54
|
+
}> {
|
|
55
|
+
try {
|
|
56
|
+
const { bapId, txid, limit, page, feed } = args;
|
|
57
|
+
|
|
58
|
+
// Handle single post request
|
|
59
|
+
if (txid) {
|
|
60
|
+
const response = await fetch(`${BMAP_URL}/post/${txid}`, {
|
|
61
|
+
method: "GET",
|
|
62
|
+
headers: {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const errorText = await response.text();
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const data = (await response.json()) as ApiResponse<PostResponse>;
|
|
76
|
+
|
|
77
|
+
if (data.status !== "success") {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: data.error || "Unknown API error",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
posts: [data.result.post],
|
|
87
|
+
signers: data.result.signers,
|
|
88
|
+
meta: [data.result.meta],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle posts list request
|
|
93
|
+
const params = new URLSearchParams();
|
|
94
|
+
if (bapId) params.append("bapId", bapId);
|
|
95
|
+
params.append("limit", limit.toString());
|
|
96
|
+
params.append("page", page.toString());
|
|
97
|
+
if (feed) params.append("feed", "true");
|
|
98
|
+
|
|
99
|
+
const response = await fetch(`${BMAP_URL}/posts?${params}`, {
|
|
100
|
+
method: "GET",
|
|
101
|
+
headers: {
|
|
102
|
+
"Content-Type": "application/json",
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const errorText = await response.text();
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const data = (await response.json()) as ApiResponse<PostsResponse>;
|
|
115
|
+
|
|
116
|
+
if (data.status !== "success") {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: data.error || "Unknown API error",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
posts: data.data.results,
|
|
126
|
+
signers: data.data.signers,
|
|
127
|
+
meta: data.data.meta,
|
|
128
|
+
pagination: {
|
|
129
|
+
page: data.data.page,
|
|
130
|
+
limit: data.data.limit,
|
|
131
|
+
count: data.data.count,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
136
|
+
console.error("Error reading social posts:", errorMessage);
|
|
137
|
+
return {
|
|
138
|
+
success: false,
|
|
139
|
+
error: errorMessage,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Format a post transaction for display
|
|
146
|
+
*/
|
|
147
|
+
function formatPost(
|
|
148
|
+
post: PostTransaction,
|
|
149
|
+
index: number,
|
|
150
|
+
signer?: BapIdentity,
|
|
151
|
+
meta?: PostMeta,
|
|
152
|
+
): string {
|
|
153
|
+
const lines = [
|
|
154
|
+
`Post ${index + 1}:`,
|
|
155
|
+
` TX ID: ${post.tx.h}`,
|
|
156
|
+
` Content: ${post.B[0]?.content || "[No Content]"}`,
|
|
157
|
+
` Content Type: ${post.B[0]?.["content-type"] || "text/plain"}`,
|
|
158
|
+
` Timestamp: ${new Date(post.timestamp * 1000).toISOString()}`,
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
// Add signer information
|
|
162
|
+
if (signer) {
|
|
163
|
+
lines.push(` Author: ${signer.displayName || signer.idKey}`);
|
|
164
|
+
if (signer.paymail) {
|
|
165
|
+
lines.push(` Paymail: ${signer.paymail}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Add MAP data
|
|
170
|
+
const mapData = post.MAP.find((m) => m.type === "post");
|
|
171
|
+
if (mapData?.app) {
|
|
172
|
+
lines.push(` App: ${mapData.app}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Add metadata
|
|
176
|
+
if (meta) {
|
|
177
|
+
if (meta.likes > 0) {
|
|
178
|
+
lines.push(` Likes: ${meta.likes}`);
|
|
179
|
+
}
|
|
180
|
+
if (meta.replies > 0) {
|
|
181
|
+
lines.push(` Replies: ${meta.replies}`);
|
|
182
|
+
}
|
|
183
|
+
if (meta.reactions.length > 0) {
|
|
184
|
+
const reactions = meta.reactions
|
|
185
|
+
.map((r) => `${r.emoji}(${r.count})`)
|
|
186
|
+
.join(" ");
|
|
187
|
+
lines.push(` Reactions: ${reactions}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return lines.join("\n");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Register the read posts tool with the MCP server
|
|
196
|
+
*/
|
|
197
|
+
export function registerReadPostsTool(server: McpServer) {
|
|
198
|
+
server.tool(
|
|
199
|
+
"bsocial_readPosts",
|
|
200
|
+
"Read social posts from the BSV blockchain using BMAP API. Can fetch posts by author (BAP ID), specific post by transaction ID, or recent posts from all users. Supports pagination and feed functionality.",
|
|
201
|
+
{ ...readPostsArgsSchema.shape },
|
|
202
|
+
async ({ bapId, txid, limit, page, feed }): Promise<CallToolResult> => {
|
|
203
|
+
try {
|
|
204
|
+
const result = await readSocialPosts({ bapId, txid, limit, page, feed });
|
|
205
|
+
|
|
206
|
+
if (result.success && result.posts) {
|
|
207
|
+
// Format posts for display
|
|
208
|
+
const formattedPosts = result.posts
|
|
209
|
+
.map((post, index) => {
|
|
210
|
+
const signer = result.signers?.find(
|
|
211
|
+
(s) => s.currentAddress === post.AIP[0]?.address,
|
|
212
|
+
);
|
|
213
|
+
const meta = result.meta?.[index];
|
|
214
|
+
return formatPost(post, index, signer, meta);
|
|
215
|
+
})
|
|
216
|
+
.join("\n\n");
|
|
217
|
+
|
|
218
|
+
let output =
|
|
219
|
+
result.posts.length > 0 ? formattedPosts : "No posts found";
|
|
220
|
+
|
|
221
|
+
// Add pagination info
|
|
222
|
+
if (result.pagination) {
|
|
223
|
+
output += `\n\nPagination: Page ${result.pagination.page}, showing ${result.posts.length} of ${result.pagination.count} posts`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: output,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
isError: false,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: "text",
|
|
241
|
+
text: result.error || "Unknown error occurred",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
isError: true,
|
|
245
|
+
};
|
|
246
|
+
} catch (error) {
|
|
247
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
248
|
+
return {
|
|
249
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
250
|
+
isError: true,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Transaction, 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
|
+
import type {
|
|
5
|
+
ServerNotification,
|
|
6
|
+
ServerRequest,
|
|
7
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
4
8
|
import { z } from "zod";
|
|
5
9
|
|
|
6
10
|
// Schema for decode transaction arguments
|
|
@@ -119,15 +123,12 @@ export function registerDecodeTransactionTool(server: McpServer): void {
|
|
|
119
123
|
server.tool(
|
|
120
124
|
"bsv_decodeTransaction",
|
|
121
125
|
"Decodes and analyzes Bitcoin SV transactions to provide detailed insights. This powerful tool accepts either a transaction ID or raw transaction data and returns comprehensive information including inputs, outputs, fee calculations, script details, and blockchain context. Supports both hex and base64 encoded transactions and automatically fetches additional on-chain data when available.",
|
|
122
|
-
|
|
123
|
-
args: decodeTransactionArgsSchema,
|
|
124
|
-
},
|
|
126
|
+
decodeTransactionArgsSchema.shape,
|
|
125
127
|
async (
|
|
126
|
-
{
|
|
127
|
-
extra: RequestHandlerExtra,
|
|
128
|
+
{ tx, encoding },
|
|
129
|
+
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
128
130
|
) => {
|
|
129
131
|
try {
|
|
130
|
-
const { tx, encoding } = args;
|
|
131
132
|
let transaction: Transaction;
|
|
132
133
|
let rawTx: string;
|
|
133
134
|
let txid: string;
|
package/tools/bsv/explore.ts
CHANGED
|
@@ -131,10 +131,9 @@ export function registerExploreTool(server: McpServer): void {
|
|
|
131
131
|
"NETWORK:\n" +
|
|
132
132
|
"- health: API health check\n\n" +
|
|
133
133
|
"Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
|
|
134
|
-
|
|
135
|
-
async (
|
|
134
|
+
exploreArgsSchema.shape,
|
|
135
|
+
async (params) => {
|
|
136
136
|
try {
|
|
137
|
-
const params = exploreArgsSchema.parse(args);
|
|
138
137
|
|
|
139
138
|
// Validate required parameters for specific endpoints
|
|
140
139
|
if (
|
package/tools/bsv/getPrice.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
2
|
|
|
4
3
|
// Define cache duration (5 minutes in milliseconds)
|
|
5
4
|
const PRICE_CACHE_DURATION = 5 * 60 * 1000;
|
|
@@ -53,14 +52,7 @@ export function registerGetPriceTool(server: McpServer): void {
|
|
|
53
52
|
server.tool(
|
|
54
53
|
"bsv_getPrice",
|
|
55
54
|
"Retrieves the current price of Bitcoin SV (BSV) in USD from a reliable exchange API. This tool provides real-time market data that can be used for calculating transaction values, monitoring market conditions, or converting between BSV and fiat currencies.",
|
|
56
|
-
{
|
|
57
|
-
args: z
|
|
58
|
-
.object({})
|
|
59
|
-
.optional()
|
|
60
|
-
.describe(
|
|
61
|
-
"No parameters required - simply returns the current BSV price in USD",
|
|
62
|
-
),
|
|
63
|
-
},
|
|
55
|
+
{},
|
|
64
56
|
async () => {
|
|
65
57
|
try {
|
|
66
58
|
const price = await getBsvPriceWithCache();
|