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,169 @@
|
|
|
1
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
|
+
import type {
|
|
5
|
+
CallToolResult,
|
|
6
|
+
ServerNotification,
|
|
7
|
+
ServerRequest,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { BSOCIAL_API_URL } from "../constants";
|
|
11
|
+
import type { IdentityData, SigmaIdentityProfile } from "./types";
|
|
12
|
+
|
|
13
|
+
// Schema for bap_getId arguments
|
|
14
|
+
export const bapGetIdArgsSchema = z.object({
|
|
15
|
+
idKey: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe(
|
|
19
|
+
"Optional Identity Key (Paymail or public key). If not provided, attempts to use the server's configured identity key.",
|
|
20
|
+
),
|
|
21
|
+
});
|
|
22
|
+
export type BapGetIdArgs = z.infer<typeof bapGetIdArgsSchema>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Fetches the identity profile from Sigma API.
|
|
26
|
+
* @param idKey The identity key (Paymail or public key) to fetch the profile for.
|
|
27
|
+
* @returns The identity data, or null if not found.
|
|
28
|
+
*/
|
|
29
|
+
export const fetchProfile = async (
|
|
30
|
+
idKey: string,
|
|
31
|
+
): Promise<IdentityData | null> => {
|
|
32
|
+
const response = await fetch(`${BSOCIAL_API_URL}/identity/get`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({ idKey }),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
// For 404, Sigma API might return a specific body, but generally, we treat it as profile not found.
|
|
42
|
+
if (response.status === 404) {
|
|
43
|
+
return null; // Explicitly return null for 404
|
|
44
|
+
}
|
|
45
|
+
const errorBody = await response.text();
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Sigma API request failed with status ${response.status}: ${errorBody}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const profileData = (await response.json()) as SigmaIdentityProfile;
|
|
53
|
+
// Ensure result exists, even if it's null (which means not found)
|
|
54
|
+
if (typeof profileData.result === "undefined") {
|
|
55
|
+
throw new Error("Sigma API response is missing the 'result' field.");
|
|
56
|
+
}
|
|
57
|
+
return profileData.result; // This can be IdentityData or null
|
|
58
|
+
} catch (jsonError) {
|
|
59
|
+
// Handle cases where response is not JSON or structure is unexpected
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Failed to parse Sigma API response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Registers the bap_getId tool for fetching a BAP identity.
|
|
68
|
+
*/
|
|
69
|
+
export function registerBapGetIdTool(
|
|
70
|
+
server: McpServer,
|
|
71
|
+
identityPk?: PrivateKey, // Accept the global identityPk
|
|
72
|
+
) {
|
|
73
|
+
server.tool(
|
|
74
|
+
"bap_getId",
|
|
75
|
+
"Retrieves a Bitcoin Attestation Protocol (BAP) identity profile using an idKey (Paymail or public key). If no idKey is provided, it attempts to use the server's configured identity key.",
|
|
76
|
+
{ ...bapGetIdArgsSchema.shape },
|
|
77
|
+
async (
|
|
78
|
+
{ idKey },
|
|
79
|
+
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
80
|
+
): Promise<CallToolResult> => {
|
|
81
|
+
let targetIdKey = idKey;
|
|
82
|
+
|
|
83
|
+
if (!targetIdKey) {
|
|
84
|
+
// First priority: Use authenticated user's BAP ID from OAuth session
|
|
85
|
+
const authInfo = (extra as any).authInfo;
|
|
86
|
+
if (authInfo?.metadata?.bapId) {
|
|
87
|
+
targetIdKey = authInfo.metadata.bapId;
|
|
88
|
+
console.error(`Using authenticated user's BAP ID: ${targetIdKey}`);
|
|
89
|
+
}
|
|
90
|
+
// Second priority: Use authenticated user's pubkey
|
|
91
|
+
else if (authInfo?.metadata?.pubkey) {
|
|
92
|
+
targetIdKey = authInfo.metadata.pubkey;
|
|
93
|
+
console.error(`Using authenticated user's pubkey: ${targetIdKey}`);
|
|
94
|
+
}
|
|
95
|
+
// Fallback: Attempt to get idKey from the passed identityPk or environment variable
|
|
96
|
+
else {
|
|
97
|
+
let pkToUse = identityPk;
|
|
98
|
+
|
|
99
|
+
if (!pkToUse) {
|
|
100
|
+
const identityKeyWifEnv = process.env.IDENTITY_KEY_WIF;
|
|
101
|
+
if (identityKeyWifEnv) {
|
|
102
|
+
try {
|
|
103
|
+
pkToUse = PrivateKey.fromWif(identityKeyWifEnv);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Don't error here, let it be handled by the final check
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (pkToUse) {
|
|
111
|
+
// Sigma typically uses the public key string as the idKey
|
|
112
|
+
targetIdKey = pkToUse.toPublicKey().toString();
|
|
113
|
+
} else {
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: "text",
|
|
118
|
+
text: "idKey not provided and could not be derived from authenticated session or server configuration.",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
isError: true,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!targetIdKey) {
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: "Critical error: targetIdKey is unexpectedly undefined after derivation attempts.",
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
isError: true,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const identityDataResult = await fetchProfile(targetIdKey);
|
|
141
|
+
if (identityDataResult === null) {
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `No BAP identity found for idKey: ${targetIdKey}`,
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
isError: false, // Not an error, just no data found for the given key
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{ type: "text", text: JSON.stringify(identityDataResult, null, 2) },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
} catch (error) {
|
|
158
|
+
const errorMessage =
|
|
159
|
+
error instanceof Error ? error.message : String(error);
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{ type: "text", text: `Error fetching BAP ID: ${errorMessage}` },
|
|
163
|
+
],
|
|
164
|
+
isError: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { PrivateKey } from "@bsv/sdk";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { Wallet } from "../wallet/wallet";
|
|
4
|
+
import { registerBapFriendTool } from "./friend";
|
|
5
|
+
import { registerBapGenerateTool } from "./generate";
|
|
6
|
+
import { registerBapGetCurrentAddressTool } from "./getCurrentAddress";
|
|
7
|
+
import { registerBapGetIdTool } from "./getId";
|
|
8
|
+
|
|
9
|
+
const logFunc = console.error;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for BAP tools registration.
|
|
13
|
+
*/
|
|
14
|
+
export interface BapToolsConfig {
|
|
15
|
+
disableBroadcasting?: boolean;
|
|
16
|
+
identityPk?: PrivateKey;
|
|
17
|
+
masterXprv?: string;
|
|
18
|
+
wallet?: Wallet;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register all BAP tools with the MCP server
|
|
23
|
+
* @param server The MCP server instance
|
|
24
|
+
* @param identityPk The optional identity private key for operations requiring it
|
|
25
|
+
* @param masterXprv The BAP HD Master Key (xprv string) if available.
|
|
26
|
+
* @param config Configuration options for BAP tools.
|
|
27
|
+
*/
|
|
28
|
+
export function registerBapTools(
|
|
29
|
+
server: McpServer,
|
|
30
|
+
config?: BapToolsConfig,
|
|
31
|
+
): void {
|
|
32
|
+
const {
|
|
33
|
+
identityPk, // Server's main configured identity (from keys.json or IDENTITY_KEY_WIF)
|
|
34
|
+
masterXprv, // Server's master BAP key (from keys.json)
|
|
35
|
+
wallet,
|
|
36
|
+
disableBroadcasting = false,
|
|
37
|
+
} = config || {};
|
|
38
|
+
|
|
39
|
+
const envIdentityKeyWif = process.env.IDENTITY_KEY_WIF;
|
|
40
|
+
|
|
41
|
+
// --- Register bap_generate ONLY if no identity exists ---
|
|
42
|
+
const canGenerateBapIdentity = !envIdentityKeyWif && !masterXprv;
|
|
43
|
+
if (canGenerateBapIdentity) {
|
|
44
|
+
logFunc(
|
|
45
|
+
"INFO: Registering bap_generate tool. No master identity (xprv) or direct identity WIF found.",
|
|
46
|
+
);
|
|
47
|
+
registerBapGenerateTool(server, { disableBroadcasting });
|
|
48
|
+
} else {
|
|
49
|
+
logFunc(
|
|
50
|
+
"INFO: bap_generate tool not registered. Master identity (xprv) or direct identity WIF already exists.",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Register tools requiring an established identity ---
|
|
55
|
+
// Established identity means either an identityPk was loaded/configured, OR an env var was set.
|
|
56
|
+
const hasEstablishedIdentity = !!identityPk || !!envIdentityKeyWif;
|
|
57
|
+
|
|
58
|
+
if (hasEstablishedIdentity) {
|
|
59
|
+
logFunc(
|
|
60
|
+
"INFO: Registering BAP tools that require an established identity (bap_getId, bap_getCurrentAddress).",
|
|
61
|
+
);
|
|
62
|
+
// registerBapGetIdTool can use server's identityPk if available
|
|
63
|
+
|
|
64
|
+
// registerBapGetCurrentAddressTool strictly requires the server's identityPk (not just env var)
|
|
65
|
+
if (identityPk) {
|
|
66
|
+
registerBapGetCurrentAddressTool(server, identityPk);
|
|
67
|
+
} else {
|
|
68
|
+
logFunc(
|
|
69
|
+
"WARN: bap_getCurrentAddress tool not registered. Server identityPk not available (might be using env var directly).",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
logFunc(
|
|
74
|
+
"INFO: BAP tools requiring established identity not registered (no identityPk or IDENTITY_KEY_WIF).",
|
|
75
|
+
);
|
|
76
|
+
// We might still want bap_getId available even without server identity,
|
|
77
|
+
// as it can take an idKey argument. Let's register it outside the check.
|
|
78
|
+
// Moved registerBapGetIdTool outside this block - see below.
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Always register bap_getId as it can operate on user-provided idKey too.
|
|
82
|
+
// If server identityPk is available, it can use it as default.
|
|
83
|
+
registerBapGetIdTool(server, identityPk);
|
|
84
|
+
|
|
85
|
+
// --- Register tools requiring the master HD key (xprv) ---
|
|
86
|
+
if (masterXprv) {
|
|
87
|
+
try {
|
|
88
|
+
// No need to parse HD key here if tools don't need the HD object itself
|
|
89
|
+
// const masterHdKey = HD.fromString(masterXprv);
|
|
90
|
+
|
|
91
|
+
// Register bap_friend if wallet is also available
|
|
92
|
+
if (wallet) {
|
|
93
|
+
logFunc(
|
|
94
|
+
"INFO: Registering bap_friend tool (requires wallet & masterXprv).",
|
|
95
|
+
);
|
|
96
|
+
registerBapFriendTool(server, wallet, masterXprv, {
|
|
97
|
+
disableBroadcasting,
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
logFunc("WARN: Wallet not available, bap_friend tool not registered.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Add other tools that specifically require masterXprv here...
|
|
104
|
+
} catch (e) {
|
|
105
|
+
// Catch potential HD.fromString errors if parsing is done here
|
|
106
|
+
console.error(
|
|
107
|
+
`ERROR: Failed during masterXprv tool registration: ${e instanceof Error ? e.message : String(e)}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Place, PostalAddress } from "schema-dts";
|
|
2
|
+
|
|
3
|
+
export interface BapPublicKey {
|
|
4
|
+
id: string;
|
|
5
|
+
controller: string;
|
|
6
|
+
type: "EcdsaSecp256k1VerificationKey2019";
|
|
7
|
+
bitcoinAddress: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export enum SchemaType {
|
|
11
|
+
Person = "Person",
|
|
12
|
+
Organization = "Organization",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Define our base Identity interface including common fields + custom ones
|
|
16
|
+
export interface Identity {
|
|
17
|
+
// Common Schema.org fields we use
|
|
18
|
+
"@context"?: string;
|
|
19
|
+
"@type": SchemaType;
|
|
20
|
+
alternateName?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
image?: string; // Used for Person
|
|
23
|
+
logo?: string; // Used for Organization
|
|
24
|
+
address?: string | PostalAddress;
|
|
25
|
+
location?: Place; // Use Place from schema-dts
|
|
26
|
+
homeLocation?: Place; // Use Place from schema-dts
|
|
27
|
+
url?: string;
|
|
28
|
+
email?: string;
|
|
29
|
+
|
|
30
|
+
// Person specific (optional)
|
|
31
|
+
givenName?: string;
|
|
32
|
+
familyName?: string;
|
|
33
|
+
|
|
34
|
+
// Organization specific (optional)
|
|
35
|
+
legalName?: string;
|
|
36
|
+
|
|
37
|
+
// Custom properties
|
|
38
|
+
_id?: string;
|
|
39
|
+
paymail?: string;
|
|
40
|
+
bitcoinAddress?: string;
|
|
41
|
+
banner?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Address related to BAP/blockchain context, not Schema.org PostalAddress
|
|
45
|
+
export type BapAddress = {
|
|
46
|
+
address: string;
|
|
47
|
+
txId: string;
|
|
48
|
+
block: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type IdentityData = {
|
|
52
|
+
addresses?: BapAddress[]; // Use BapAddress type
|
|
53
|
+
block: number;
|
|
54
|
+
currentAddress: string;
|
|
55
|
+
firstSeen: number;
|
|
56
|
+
idKey: string; // BAP ID format
|
|
57
|
+
identity: Identity;
|
|
58
|
+
rootAddress: string;
|
|
59
|
+
timestamp: number;
|
|
60
|
+
valid: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// The API response structure for fetching a profile
|
|
64
|
+
export interface SigmaIdentityProfile {
|
|
65
|
+
result: IdentityData;
|
|
66
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Identity, IdentityData, SchemaType } from "./types";
|
|
2
|
+
|
|
3
|
+
type AllFields = {
|
|
4
|
+
schemaType: SchemaType;
|
|
5
|
+
alternateName: string;
|
|
6
|
+
description: string;
|
|
7
|
+
bitcoinAddress: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const buildIdentity = ({
|
|
11
|
+
schemaType,
|
|
12
|
+
alternateName,
|
|
13
|
+
description,
|
|
14
|
+
bitcoinAddress,
|
|
15
|
+
}: AllFields): string => {
|
|
16
|
+
const identity: Identity = {
|
|
17
|
+
"@context": "https://schema.org",
|
|
18
|
+
"@type": schemaType as SchemaType,
|
|
19
|
+
alternateName,
|
|
20
|
+
description,
|
|
21
|
+
bitcoinAddress,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (description) {
|
|
25
|
+
identity.description = description;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (bitcoinAddress) {
|
|
29
|
+
identity.bitcoinAddress = bitcoinAddress;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return JSON.stringify(identity);
|
|
33
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
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 follows from BMAP API
|
|
7
|
+
const bmapReadFollowsArgsSchema = z.object({
|
|
8
|
+
bapId: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("BAP identity key to get follow relationships for"),
|
|
11
|
+
type: z
|
|
12
|
+
.enum(["followers", "following"])
|
|
13
|
+
.default("following")
|
|
14
|
+
.describe("Type of relationship to query"),
|
|
15
|
+
limit: z
|
|
16
|
+
.number()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(100)
|
|
19
|
+
.default(20)
|
|
20
|
+
.describe("Maximum number of follows to return"),
|
|
21
|
+
page: z
|
|
22
|
+
.number()
|
|
23
|
+
.min(1)
|
|
24
|
+
.default(1)
|
|
25
|
+
.describe("Page number for pagination (1-based)"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type BmapReadFollowsArgs = z.infer<typeof bmapReadFollowsArgsSchema>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read follow relationships from BMAP API
|
|
32
|
+
*/
|
|
33
|
+
export async function readBmapFollows(args: BmapReadFollowsArgs): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
data?: unknown;
|
|
36
|
+
error?: string;
|
|
37
|
+
}> {
|
|
38
|
+
try {
|
|
39
|
+
const { bapId, type, limit, page } = args;
|
|
40
|
+
|
|
41
|
+
const params = new URLSearchParams();
|
|
42
|
+
params.append("limit", limit.toString());
|
|
43
|
+
params.append("page", page.toString());
|
|
44
|
+
|
|
45
|
+
const endpoint = type === "followers" ? "followers" : "following";
|
|
46
|
+
const response = await fetch(
|
|
47
|
+
`${BMAP_URL}/bap/${bapId}/${endpoint}?${params}`,
|
|
48
|
+
{
|
|
49
|
+
method: "GET",
|
|
50
|
+
headers: {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorText = await response.text();
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
return {
|
|
66
|
+
success: true,
|
|
67
|
+
data,
|
|
68
|
+
};
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
71
|
+
console.error("Error reading BMAP follows:", errorMessage);
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: errorMessage,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Register the BMAP read follows tool with the MCP server
|
|
81
|
+
*/
|
|
82
|
+
export function registerBmapReadFollowsTool(server: McpServer) {
|
|
83
|
+
server.tool(
|
|
84
|
+
"bmap_readFollows",
|
|
85
|
+
"Read follow relationships from the BMAP API. Shows who a user is following or who follows them.",
|
|
86
|
+
{ ...bmapReadFollowsArgsSchema.shape },
|
|
87
|
+
async ({ bapId, type, limit, page }): Promise<CallToolResult> => {
|
|
88
|
+
try {
|
|
89
|
+
const result = await readBmapFollows({ bapId, type, limit, page });
|
|
90
|
+
|
|
91
|
+
if (result.success) {
|
|
92
|
+
return {
|
|
93
|
+
content: [
|
|
94
|
+
{
|
|
95
|
+
type: "text",
|
|
96
|
+
text: JSON.stringify(result.data, null, 2),
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
isError: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: result.error || "Unknown error occurred",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
isError: true,
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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 likes from BMAP API
|
|
7
|
+
const bmapReadLikesArgsSchema = z.object({
|
|
8
|
+
txid: z.string().describe("Transaction ID of the post to get likes for"),
|
|
9
|
+
limit: z
|
|
10
|
+
.number()
|
|
11
|
+
.min(1)
|
|
12
|
+
.max(100)
|
|
13
|
+
.default(20)
|
|
14
|
+
.describe("Maximum number of likes to return"),
|
|
15
|
+
page: z
|
|
16
|
+
.number()
|
|
17
|
+
.min(1)
|
|
18
|
+
.default(1)
|
|
19
|
+
.describe("Page number for pagination (1-based)"),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type BmapReadLikesArgs = z.infer<typeof bmapReadLikesArgsSchema>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Read likes and reactions for a specific post from BMAP API
|
|
26
|
+
*/
|
|
27
|
+
export async function readBmapLikes(args: BmapReadLikesArgs): Promise<{
|
|
28
|
+
success: boolean;
|
|
29
|
+
data?: unknown;
|
|
30
|
+
error?: string;
|
|
31
|
+
}> {
|
|
32
|
+
try {
|
|
33
|
+
const { txid, limit, page } = args;
|
|
34
|
+
|
|
35
|
+
const params = new URLSearchParams();
|
|
36
|
+
params.append("limit", limit.toString());
|
|
37
|
+
params.append("page", page.toString());
|
|
38
|
+
|
|
39
|
+
const response = await fetch(`${BMAP_URL}/post/${txid}/like?${params}`, {
|
|
40
|
+
method: "GET",
|
|
41
|
+
headers: {
|
|
42
|
+
"Content-Type": "application/json",
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
const errorText = await response.text();
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: `BMAP API request failed: ${response.status} ${errorText}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
data,
|
|
58
|
+
};
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
61
|
+
console.error("Error reading BMAP likes:", errorMessage);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: errorMessage,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Register the BMAP read likes tool with the MCP server
|
|
71
|
+
*/
|
|
72
|
+
export function registerBmapReadLikesTool(server: McpServer) {
|
|
73
|
+
server.tool(
|
|
74
|
+
"bmap_readLikes",
|
|
75
|
+
"Read likes and reactions for a specific post from the BMAP API. Shows who liked the post and what emoji reactions were used.",
|
|
76
|
+
{ ...bmapReadLikesArgsSchema.shape },
|
|
77
|
+
async ({ txid, limit, page }): Promise<CallToolResult> => {
|
|
78
|
+
try {
|
|
79
|
+
const result = await readBmapLikes({ txid, limit, page });
|
|
80
|
+
|
|
81
|
+
if (result.success) {
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: JSON.stringify(result.data, null, 2),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
isError: false,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: result.error || "Unknown error occurred",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
isError: true,
|
|
101
|
+
};
|
|
102
|
+
} catch (error) {
|
|
103
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
104
|
+
return {
|
|
105
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
106
|
+
isError: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
}
|