From cec34e849375f17551b375238dcae7ac1a0022e0 Mon Sep 17 00:00:00 2001 From: Francisco Veiras <74626997+francisco-svg761@users.noreply.github.com> Date: Sat, 21 Jun 2025 17:34:44 -0300 Subject: [PATCH 1/3] checkpoint --- src/tools/update-blocks-content.ts | 62 ++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/tools/update-blocks-content.ts diff --git a/src/tools/update-blocks-content.ts b/src/tools/update-blocks-content.ts new file mode 100644 index 0000000..00b7f35 --- /dev/null +++ b/src/tools/update-blocks-content.ts @@ -0,0 +1,62 @@ +import { basehub } from "basehub"; +import { z } from "zod"; +import { type InferSchema } from "xmcp"; + +export const schema = { + data: z + .array( + z + .object({ id: z.string().describe("ID of the block to update") }) + .passthrough() + ) + .describe( + "Array of update objects, each with at least 'id', 'type', and update fields." + ), + autoCommit: z + .string() + .optional() + .describe( + "Optional commit message. If provided, the transaction will be auto-committed with this message." + ), +}; + +export const metadata = { + name: "update_block_content", + description: `Update one BaseHub block content + example: + [ + { + "id": "a67f747ffc657f5c0b20d", + "type": "update", + "value": "Updated title from tool runner" + } + ] + `, + annotations: { + title: "Update BaseHub Blocks content", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + }, +}; + +export default async function updateBlocksContent({ + data, + autoCommit, +}: InferSchema) { + const result = await basehub().mutation({ + transaction: { + __args: { + data: data.map((item) => ({ ...item, type: "update" })), + ...(autoCommit ? { autoCommit } : {}), + }, + message: true, + status: true, + duration: true, + }, + }); + + return { + content: [{ type: "text", text: JSON.stringify(result) }], + }; +} From 8ede8db7ad431813706c1c21c465baa30ae5ebd1 Mon Sep 17 00:00:00 2001 From: Francisco Veiras <74626997+francisco-svg761@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:05:23 -0300 Subject: [PATCH 2/3] feat: add update blocks content tool --- package.json | 2 +- pnpm-lock.yaml | 25 +---- src/tools/update-blocks-content.ts | 143 ++++++++++++++++++++--------- 3 files changed, 107 insertions(+), 63 deletions(-) diff --git a/package.json b/package.json index 5d34e1d..5b521c7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.11.4", - "basehub": "^9.0.6", + "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 bb66c16..4e98431 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.11.4 version: 1.12.3 basehub: - specifier: ^9.0.6 - version: 9.0.6(@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)) @@ -1006,8 +1006,8 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - basehub@9.0.6: - resolution: {integrity: sha512-ALJg8whF9mS2juQwB6buDLweknzYHxqg68kqTnUt3AzDphHGvJ9i/IS9sfYN+c+GFTg4/NY01qYPgo9ZmI175A==} + basehub@9.0.16: + resolution: {integrity: sha512-Akwmy//E1oz+8cm7KGtBqg8hmXTlBRJhKHY2fCrR9peUCX4abUHJFLdCEI+LDLnOUd8VwzlCOzzlWw9WCOBhog==} hasBin: true binary-extensions@2.3.0: @@ -2339,17 +2339,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve-pkg@2.0.0: - resolution: {integrity: sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==} - engines: {node: '>=8'} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -3766,7 +3758,7 @@ snapshots: balanced-match@1.0.2: {} - basehub@9.0.6(@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) @@ -3787,7 +3779,6 @@ snapshots: mkdirp: 3.0.1 prettier: 3.0.3 pusher-js: 8.4.0 - resolve-pkg: 2.0.0 rimraf: 6.0.1 server-only: 0.0.1 shiki: 1.17.7 @@ -5285,14 +5276,8 @@ snapshots: resolve-from@4.0.0: {} - resolve-from@5.0.0: {} - resolve-pkg-maps@1.0.0: {} - resolve-pkg@2.0.0: - dependencies: - resolve-from: 5.0.0 - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 diff --git a/src/tools/update-blocks-content.ts b/src/tools/update-blocks-content.ts index 00b7f35..691e54d 100644 --- a/src/tools/update-blocks-content.ts +++ b/src/tools/update-blocks-content.ts @@ -1,62 +1,121 @@ import { basehub } from "basehub"; import { z } from "zod"; -import { type InferSchema } from "xmcp"; +import type { InferSchema } from "xmcp"; export const schema = { - data: z + updates: z .array( - z - .object({ id: z.string().describe("ID of the block to update") }) - .passthrough() + 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"), + }) ) - .describe( - "Array of update objects, each with at least 'id', 'type', and update fields." - ), - autoCommit: z - .string() - .optional() - .describe( - "Optional commit message. If provided, the transaction will be auto-committed with this message." - ), + .min(1, "You must provide at least one field to update."), + autoCommit: z.string().optional().describe("Optional commit message"), }; export const metadata = { - name: "update_block_content", - description: `Update one BaseHub block content - example: - [ - { - "id": "a67f747ffc657f5c0b20d", - "type": "update", - "value": "Updated title from tool runner" - } - ] - `, + 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 BaseHub Blocks content", + title: "Update Blocks Content", readOnlyHint: true, destructiveHint: false, idempotentHint: true, }, }; -export default async function updateBlocksContent({ - data, +export default async function smartUpdateBlock({ + updates, autoCommit, }: InferSchema) { - const result = await basehub().mutation({ - transaction: { - __args: { - data: data.map((item) => ({ ...item, type: "update" })), - ...(autoCommit ? { autoCommit } : {}), + 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 }>; + }; + + console.log("test", JSON.stringify(response)); + + 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, }, - message: true, - status: true, - duration: true, - }, - }); - - return { - content: [{ type: "text", text: JSON.stringify(result) }], - }; + }); + + return { + content: [{ type: "text", text: JSON.stringify(result) }], + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : "An unknown error occurred"; + throw new Error(`Smart update block failed: ${errorMessage}`); + } } From 997c970c073a87815e226db9d35974b179d86e3f Mon Sep 17 00:00:00 2001 From: Francisco Veiras <74626997+francisco-svg761@users.noreply.github.com> Date: Tue, 24 Jun 2025 21:19:08 -0300 Subject: [PATCH 3/3] fix: remove log --- src/tools/update-blocks-content.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tools/update-blocks-content.ts b/src/tools/update-blocks-content.ts index 691e54d..7934f36 100644 --- a/src/tools/update-blocks-content.ts +++ b/src/tools/update-blocks-content.ts @@ -40,7 +40,7 @@ Example input: }, }; -export default async function smartUpdateBlock({ +export default async function updateBlocksContent({ updates, autoCommit, }: InferSchema) { @@ -69,8 +69,6 @@ export default async function smartUpdateBlock({ _componentInstances?: Record }>; }; - console.log("test", JSON.stringify(response)); - if (!response._componentInstances) { throw new Error("Failed to fetch component instances"); } @@ -116,6 +114,6 @@ export default async function smartUpdateBlock({ } catch (error) { const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; - throw new Error(`Smart update block failed: ${errorMessage}`); + throw new Error(`update blocks content failed: ${errorMessage}`); } }