diff --git a/package.json b/package.json index 9003bec..701a9f2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "@basehub/mutation-api-helpers": "2.1.2", "@modelcontextprotocol/sdk": "^1.11.4", - "basehub": "9.0.9", + "basehub": "^9.0.16", "ts-loader": "^9.4.2", "typescript": "^5.0.4", "xmcp": "latest", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3728a3f..2956197 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^1.11.4 version: 1.12.3 basehub: - specifier: 9.0.9 - version: 9.0.9(@babel/runtime@7.27.6)(react@18.3.1)(typescript@5.8.3) + specifier: ^9.0.16 + version: 9.0.16(@babel/runtime@7.27.6)(react@18.3.1)(typescript@5.8.3) ts-loader: specifier: ^9.4.2 version: 9.5.2(typescript@5.8.3)(webpack@5.99.9(@swc/core@1.12.1)) @@ -1014,8 +1014,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - basehub@9.0.9: - resolution: {integrity: sha512-WzApoUN9ax3EUYpwqeR01QDVMziHFDr+4W0oLPnf6DkjGvbJiKmXGtYEKab2HMvGabjy3SDakBoLDSlACtQuJQ==} + basehub@9.0.16: + resolution: {integrity: sha512-Akwmy//E1oz+8cm7KGtBqg8hmXTlBRJhKHY2fCrR9peUCX4abUHJFLdCEI+LDLnOUd8VwzlCOzzlWw9WCOBhog==} hasBin: true binary-extensions@2.3.0: @@ -3770,7 +3770,7 @@ snapshots: balanced-match@1.0.2: {} - basehub@9.0.9(@babel/runtime@7.27.6)(react@18.3.1)(typescript@5.8.3): + basehub@9.0.16(@babel/runtime@7.27.6)(react@18.3.1)(typescript@5.8.3): dependencies: '@basehub/mutation-api-helpers': 2.0.11 '@radix-ui/react-slot': 1.2.3(react@18.3.1) diff --git a/src/tools/update-blocks-content.ts b/src/tools/update-blocks-content.ts new file mode 100644 index 0000000..7934f36 --- /dev/null +++ b/src/tools/update-blocks-content.ts @@ -0,0 +1,119 @@ +import { basehub } from "basehub"; +import { z } from "zod"; +import type { InferSchema } from "xmcp"; + +export const schema = { + updates: z + .array( + z.object({ + component: z + .string() + .describe("Component type (e.g. 'hero', 'features')"), + field: z + .string() + .describe("Field to update (e.g. 'title', 'subtitle')"), + value: z.string().describe("New value for the field"), + }) + ) + .min(1, "You must provide at least one field to update."), + autoCommit: z.string().optional().describe("Optional commit message"), +}; + +export const metadata = { + name: "update_blocks_content", + description: ` +Before using this MCP, make sure you've read the \`basehub.d.ts\` file in your codebase to use the correct component names. + +This MCP updates multiple BaseHub component fields in a single batch. + +Example input: +[ + { "component": "", "field": "title", "value": "New title!" }, + { "component": "", "field": "subtitle", "value": "New subtitle!" } +] +`, + annotations: { + title: "Update Blocks Content", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + }, +}; + +export default async function updateBlocksContent({ + updates, + autoCommit, +}: InferSchema) { + try { + const updatesByComponent = updates.reduce< + Record> + >((acc, { component, field, value }) => { + if (!acc[component]) { + acc[component] = []; + } + acc[component].push({ field, value }); + return acc; + }, {}); + + const query = { + _componentInstances: Object.keys(updatesByComponent).reduce( + (acc, component) => { + acc[component] = { items: { _id: true } }; + return acc; + }, + {} as Record + ), + }; + + const response = (await basehub().query(query)) as { + _componentInstances?: Record }>; + }; + + if (!response._componentInstances) { + throw new Error("Failed to fetch component instances"); + } + + const transactionData = Object.entries(updatesByComponent).map( + ([component, fields]) => { + const instance = response._componentInstances?.[component]?.items?.[0]; + if (!instance?._id) { + throw new Error(`No instance found for component "${component}"`); + } + + const updateData = fields.reduce< + Record + >((acc, { field, value }) => { + acc[field] = { type: "text", value }; + return acc; + }, {}); + + return { + id: instance._id, + type: "update" as const, + value: updateData, + }; + } + ); + + const result = await basehub().mutation({ + transaction: { + __args: { + data: transactionData, + // TODO: research auto commit AI criteria (mainly used for dev env) + // ...(autoCommit ? { autoCommit } : {}), + }, + message: true, + status: true, + duration: true, + }, + }); + + return { + content: [{ type: "text", text: JSON.stringify(result) }], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "An unknown error occurred"; + throw new Error(`update blocks content failed: ${errorMessage}`); + } +}