bsv-mcp 0.0.35 → 0.2.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 (71) hide show
  1. package/CHANGELOG.md +112 -1
  2. package/README.md +233 -4
  3. package/dist/index.js +111762 -0
  4. package/index.ts +804 -86
  5. package/next-env.d.ts +6 -0
  6. package/package.json +57 -15
  7. package/prompts/bsvSdk/index.ts +5 -5
  8. package/prompts/bsvSdk/transaction.ts +2 -2
  9. package/prompts/bsvSdk/wallet.ts +1 -1
  10. package/prompts/index.ts +1 -1
  11. package/resources/bitcom.ts +132 -0
  12. package/resources/resources.ts +7 -3
  13. package/smithery.yaml +3 -0
  14. package/test-mcp-server.ts +157 -0
  15. package/tools/bap/friend.ts +267 -0
  16. package/tools/bap/generate.test.ts +350 -0
  17. package/tools/bap/generate.ts +433 -0
  18. package/tools/bap/getCurrentAddress.ts +76 -0
  19. package/tools/bap/getId.test.ts +121 -0
  20. package/tools/bap/getId.ts +169 -0
  21. package/tools/bap/index.ts +111 -0
  22. package/tools/bap/types.ts +66 -0
  23. package/tools/bap/utils.ts +33 -0
  24. package/tools/bigblocks/components.ts +304 -0
  25. package/tools/bigblocks/docs.ts +488 -0
  26. package/tools/bigblocks/examples.ts +527 -0
  27. package/tools/bigblocks/generator.ts +485 -0
  28. package/tools/bigblocks/index.ts +23 -0
  29. package/tools/bsocial/bigblocksApiClient.ts +189 -0
  30. package/tools/bsocial/bmapFollow.ts +125 -0
  31. package/tools/bsocial/bmapLikes.ts +111 -0
  32. package/tools/bsocial/bmapReadPosts.ts +148 -0
  33. package/tools/bsocial/createPost.ts +136 -0
  34. package/tools/bsocial/index.ts +45 -0
  35. package/tools/bsocial/readPosts.ts +255 -0
  36. package/tools/bsv/decodeTransaction.ts +5 -1
  37. package/tools/constants.ts +21 -0
  38. package/tools/index.ts +103 -17
  39. package/tools/mnee/index.ts +8 -0
  40. package/tools/mnee/parseTx.ts +1 -1
  41. package/tools/mnee/sendMnee.ts +1 -1
  42. package/tools/utils/aip.ts +46 -0
  43. package/tools/utils/conversion.ts +1 -0
  44. package/tools/utils/errorHandler.ts +76 -0
  45. package/tools/utils/index.ts +45 -1
  46. package/tools/utils/installAgentMaster.ts +84 -0
  47. package/tools/utils/logger.ts +20 -0
  48. package/tools/utils/toolConfig.ts +80 -0
  49. package/tools/utils/toolRegistration.ts +66 -0
  50. package/tools/utils/transactionBuilder.ts +196 -0
  51. package/tools/wallet/a2bPublishAgent.ts +8 -4
  52. package/tools/wallet/a2bPublishMcp.ts +28 -24
  53. package/tools/wallet/createOrdinals.ts +4 -2
  54. package/tools/wallet/fetchPaymentUtxos.ts +44 -43
  55. package/tools/wallet/gatherCollectionInfo.ts +374 -0
  56. package/tools/wallet/getAddress.ts +13 -21
  57. package/tools/wallet/getBalance.ts +66 -0
  58. package/tools/wallet/getBalanceDroplet.ts +59 -0
  59. package/tools/wallet/integratedWallet.ts +120 -0
  60. package/tools/wallet/mintCollection.ts +378 -0
  61. package/tools/wallet/purchaseListing.ts +4 -4
  62. package/tools/wallet/schemas.ts +18 -249
  63. package/tools/wallet/sendOrdinals.ts +4 -2
  64. package/tools/wallet/sendToAddress.ts +5 -1
  65. package/tools/wallet/setupDroplet.ts +172 -0
  66. package/tools/wallet/tools.ts +71 -156
  67. package/tools/wallet/transactionHelper.ts +121 -0
  68. package/tools/wallet/transferOrdToken.ts +7 -4
  69. package/tools/wallet/utxo.ts +62 -0
  70. package/tools/wallet/wallet.ts +237 -234
  71. 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
+ { args: bapGetIdArgsSchema },
77
+ async (
78
+ { args }: { args: BapGetIdArgs },
79
+ extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
80
+ ): Promise<CallToolResult> => {
81
+ let targetIdKey = args.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,304 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { z } from "zod";
5
+
6
+ // TypeScript interfaces for BigBlocks registry
7
+ interface BigBlocksComponent {
8
+ name: string;
9
+ type: string;
10
+ description: string;
11
+ files?: string[];
12
+ dependencies?: string[];
13
+ registryDependencies?: string[];
14
+ }
15
+
16
+ interface BigBlocksRegistry {
17
+ components: BigBlocksComponent[];
18
+ version?: string;
19
+ }
20
+
21
+ interface OrganizedComponents {
22
+ [category: string]: {
23
+ [subcategory: string]: BigBlocksComponent[];
24
+ };
25
+ }
26
+
27
+ // Load BigBlocks registry data
28
+ function loadBigBlocksRegistry(): BigBlocksRegistry | null {
29
+ try {
30
+ const registryPath = path.resolve(
31
+ "node_modules/bigblocks/registry/registry.json",
32
+ );
33
+ const registryData = JSON.parse(fs.readFileSync(registryPath, "utf-8"));
34
+ return registryData as BigBlocksRegistry;
35
+ } catch (error) {
36
+ console.error("Failed to load BigBlocks registry:", error);
37
+ return null;
38
+ }
39
+ }
40
+
41
+ // Organize components by type for easier browsing
42
+ function organizeComponentsByType(
43
+ registry: BigBlocksRegistry,
44
+ ): OrganizedComponents {
45
+ const organized: OrganizedComponents = {};
46
+
47
+ if (!registry?.components) return organized;
48
+
49
+ for (const component of registry.components) {
50
+ const [category, subcategory] = component.type.split(":");
51
+
52
+ if (!organized[category]) {
53
+ organized[category] = {};
54
+ }
55
+
56
+ if (!organized[category][subcategory]) {
57
+ organized[category][subcategory] = [];
58
+ }
59
+
60
+ organized[category][subcategory].push(component);
61
+ }
62
+
63
+ return organized;
64
+ }
65
+
66
+ const componentSearchSchema = z.object({
67
+ category: z
68
+ .string()
69
+ .optional()
70
+ .describe("Component category to search in (component, hook)"),
71
+ type: z
72
+ .string()
73
+ .optional()
74
+ .describe(
75
+ "Component type to filter by (auth, social, wallet, market, profile, backup, ui, bap, provider, layout)",
76
+ ),
77
+ component: z
78
+ .string()
79
+ .optional()
80
+ .describe("Specific component name to get details for"),
81
+ search: z
82
+ .string()
83
+ .optional()
84
+ .describe("Search term to find components by name or description"),
85
+ showAll: z.boolean().optional().describe("Show all available components"),
86
+ });
87
+
88
+ /**
89
+ * Register the BigBlocks component discovery tool
90
+ */
91
+ export function registerBigBlocksComponentTool(server: McpServer): void {
92
+ server.tool(
93
+ "bigblocks_components",
94
+ "Discover and learn about BigBlocks React components from the official registry. BigBlocks provides 96+ production-ready components for Bitcoin applications.\n\n" +
95
+ "Usage examples:\n" +
96
+ '- List all components: {"showAll": true}\n' +
97
+ '- Browse by category: {"category": "component"}\n' +
98
+ '- Filter by type: {"type": "social"}\n' +
99
+ '- Get component details: {"component": "auth-flow"}\n' +
100
+ '- Search components: {"search": "authentication"}\n\n' +
101
+ "Categories:\n" +
102
+ "- component: React components (auth, social, wallet, market, profile, backup, ui, bap, provider, layout)\n" +
103
+ "- hook: React hooks for various functionalities\n\n" +
104
+ "Types:\n" +
105
+ "- auth: Authentication and login components\n" +
106
+ "- social: Social media and networking\n" +
107
+ "- wallet: Bitcoin wallet operations\n" +
108
+ "- market: NFT and token marketplace\n" +
109
+ "- profile: User profile management\n" +
110
+ "- backup: Key backup and recovery\n" +
111
+ "- ui: General UI components\n" +
112
+ "- bap: Bitcoin Application Protocol\n" +
113
+ "- provider: Context providers\n" +
114
+ "- layout: Layout components",
115
+ { args: componentSearchSchema },
116
+ async ({ args }) => {
117
+ try {
118
+ const registry = loadBigBlocksRegistry();
119
+ if (!registry) {
120
+ return {
121
+ content: [
122
+ {
123
+ type: "text",
124
+ text: "Error: Could not load BigBlocks registry data",
125
+ },
126
+ ],
127
+ isError: true,
128
+ };
129
+ }
130
+
131
+ const { category, type, component, search, showAll } = args;
132
+ const organized = organizeComponentsByType(registry);
133
+
134
+ // Show specific component details
135
+ if (component) {
136
+ const comp = registry.components.find((c) => c.name === component);
137
+ if (!comp) {
138
+ const available = registry.components.map((c) => c.name).join(", ");
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: `Component "${component}" not found. Available components: ${available}`,
144
+ },
145
+ ],
146
+ isError: true,
147
+ };
148
+ }
149
+
150
+ let result = `# ${comp.name}\n\n`;
151
+ result += `**Type:** ${comp.type}\n`;
152
+ result += `**Description:** ${comp.description}\n\n`;
153
+
154
+ if (comp.files?.length) {
155
+ result += `**Files:**\n${comp.files.map((f) => `- ${f}`).join("\n")}\n\n`;
156
+ }
157
+
158
+ if (comp.dependencies?.length) {
159
+ result += `**Dependencies:** ${comp.dependencies.join(", ")}\n\n`;
160
+ }
161
+
162
+ if (comp.registryDependencies?.length) {
163
+ result += `**Registry Dependencies:** ${comp.registryDependencies.join(", ")}\n\n`;
164
+ }
165
+
166
+ // Determine the actual component/hook name from files
167
+ const mainFile = comp.files?.[0];
168
+ if (mainFile) {
169
+ const fileName = path.basename(mainFile, path.extname(mainFile));
170
+ result += `**Import:**\n\`\`\`tsx\nimport { ${fileName} } from 'bigblocks';\n\`\`\`\n\n`;
171
+ }
172
+
173
+ return { content: [{ type: "text", text: result }] };
174
+ }
175
+
176
+ // Search components
177
+ if (search) {
178
+ const searchTerm = search.toLowerCase();
179
+ const matchingComponents = registry.components.filter(
180
+ (comp) =>
181
+ comp.name.toLowerCase().includes(searchTerm) ||
182
+ comp.description.toLowerCase().includes(searchTerm) ||
183
+ comp.type.toLowerCase().includes(searchTerm),
184
+ );
185
+
186
+ let result = `# BigBlocks Components Search: "${search}"\n\n`;
187
+ if (matchingComponents.length === 0) {
188
+ result += "No components found matching your search.";
189
+ } else {
190
+ result += `Found ${matchingComponents.length} component(s):\n\n`;
191
+ for (const comp of matchingComponents) {
192
+ result += `## ${comp.name} (${comp.type})\n`;
193
+ result += `${comp.description}\n\n`;
194
+ }
195
+ }
196
+
197
+ return { content: [{ type: "text", text: result }] };
198
+ }
199
+
200
+ // Filter by category and/or type
201
+ if (category || type) {
202
+ let filtered = registry.components;
203
+
204
+ if (category) {
205
+ filtered = filtered.filter((comp) =>
206
+ comp.type.startsWith(`${category}:`),
207
+ );
208
+ }
209
+
210
+ if (type) {
211
+ filtered = filtered.filter((comp) =>
212
+ comp.type.includes(`:${type}`),
213
+ );
214
+ }
215
+
216
+ let result = `# BigBlocks Components${category ? ` - ${category}` : ""}${type ? ` - ${type}` : ""}\n\n`;
217
+
218
+ if (filtered.length === 0) {
219
+ result += "No components found matching your criteria.";
220
+ } else {
221
+ result += `Found ${filtered.length} component(s):\n\n`;
222
+ for (const comp of filtered) {
223
+ result += `## ${comp.name}\n**Type:** ${comp.type}\n**Description:** ${comp.description}\n\n`;
224
+ }
225
+ }
226
+
227
+ return { content: [{ type: "text", text: result }] };
228
+ }
229
+
230
+ // Show all components organized
231
+ if (showAll) {
232
+ let result = "# BigBlocks Component Registry\n\n";
233
+ result += `**Version:** ${registry.version}\n`;
234
+ result += `**Total Components:** ${registry.components.length}\n\n`;
235
+
236
+ for (const [cat, subcats] of Object.entries(organized)) {
237
+ result += `## ${cat.charAt(0).toUpperCase() + cat.slice(1)}s\n\n`;
238
+
239
+ for (const [subcat, components] of Object.entries(subcats)) {
240
+ result += `### ${subcat.charAt(0).toUpperCase() + subcat.slice(1)} (${components.length})\n`;
241
+
242
+ for (const comp of components) {
243
+ result += `- **${comp.name}**: ${comp.description}\n`;
244
+ }
245
+ result += "\n";
246
+ }
247
+ }
248
+
249
+ result +=
250
+ '\n**Get details:** Use {"component": "component-name"} for specific information\n';
251
+ result +=
252
+ '**Filter:** Use {"category": "component", "type": "social"} to filter by type';
253
+
254
+ return { content: [{ type: "text", text: result }] };
255
+ }
256
+
257
+ // Default: show overview
258
+ const componentCount = Object.values(organized.component || {}).reduce(
259
+ (sum, comps) => sum + comps.length,
260
+ 0,
261
+ );
262
+ const hookCount = Object.values(organized.hook || {}).reduce(
263
+ (sum, hooks) => sum + hooks.length,
264
+ 0,
265
+ );
266
+
267
+ return {
268
+ content: [
269
+ {
270
+ type: "text",
271
+ text: `# BigBlocks Component Registry
272
+
273
+ **Version:** ${registry.version}
274
+ **Total:** ${registry.components.length} items
275
+
276
+ **Categories:**
277
+ - **Components:** ${componentCount} React components
278
+ - **Hooks:** ${hookCount} React hooks
279
+
280
+ **Component Types:**
281
+ ${Object.keys(organized.component || {})
282
+ .map(
283
+ (type) => `- **${type}**: ${organized.component[type].length} components`,
284
+ )
285
+ .join("\n")}
286
+
287
+ Use {"showAll": true} to see all components, {"category": "component"} to browse components, or {"component": "name"} for details.`,
288
+ },
289
+ ],
290
+ };
291
+ } catch (error) {
292
+ return {
293
+ content: [
294
+ {
295
+ type: "text",
296
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
297
+ },
298
+ ],
299
+ isError: true,
300
+ };
301
+ }
302
+ },
303
+ );
304
+ }