diff --git a/mcp-server/tools/modules/index.js b/mcp-server/tools/modules/index.js index b4323f1..94b2f7f 100644 --- a/mcp-server/tools/modules/index.js +++ b/mcp-server/tools/modules/index.js @@ -2,6 +2,7 @@ import { registerCheckModuleTool } from './check.js'; import { registerCheckModuleUsageTool } from './checkUsage.js'; import { registerCompileModuleTool } from './compile.js'; import { registerDeleteModuleTool } from './delete.js'; +import { registerUpdateModuleMetadataTool } from './updateMetadata.js'; import { canEditCode } from '../helpers/roleCheck.js'; export function registerModuleTools(server) { @@ -10,5 +11,6 @@ export function registerModuleTools(server) { if (canEditCode()) { registerCompileModuleTool(server); registerDeleteModuleTool(server); + registerUpdateModuleMetadataTool(server); } } diff --git a/mcp-server/tools/modules/updateMetadata.js b/mcp-server/tools/modules/updateMetadata.js new file mode 100644 index 0000000..e3a9495 --- /dev/null +++ b/mcp-server/tools/modules/updateMetadata.js @@ -0,0 +1,88 @@ +import { z } from "zod"; +import { withAuth } from "../../auth/index.js"; +import { withAuthParams } from "../helpers/authSchema.js"; +import { handleToolError } from "../helpers/errorHandler.js"; +import { pythonPost } from "../helpers/pythonServerClient.js"; +import { getCurrentProjectInfo } from "../files/helpers.js"; + +// Tool: update_module_metadata +// Edita la metadata de builder.json de un modulo (label, description, +// onlyAdminModule, MJMLModule) delegando en /api/modules/update-metadata. +// El id/carpeta del modulo NO se puede renombrar y el endpoint rechaza los +// modulos generados del layout (custom-header/footer[-twig]). + +// Claves editables del builder.json. Se envian solo las que llegan definidas, +// de modo que el endpoint hace un merge parcial y no pisa el resto. +const EDITABLE_KEYS = ["label", "description", "onlyAdminModule", "MJMLModule"]; + +export function registerUpdateModuleMetadataTool(server) { + server.tool( + "update_module_metadata", + `Edit a module's metadata in its builder.json: 'label' (display name shown in the visual builder), 'description' (short help text for editors), 'onlyAdminModule' (true = the module is only visible to admin users in the builder), 'MJMLModule' (true = the module is an email/MJML module). + +The module id (its folder name under template/estandar/modulos/) CANNOT be renamed with this tool — there is no rename option at all. Only the fields above change; everything else in builder.json is preserved. + +At least one of label, description, onlyAdminModule or MJMLModule is required. Fields you omit are left untouched. + +Not applicable to the generated layout modules (custom-header, custom-footer, custom-header-twig, custom-footer-twig): those are artifacts of the global layout and the request will be rejected — use set_layout_field for them.`, + withAuthParams({ + module: z.string().min(1).describe("Module id (the folder name under template/estandar/modulos/). Cannot be renamed."), + label: z.string().optional().describe("Display name of the module in the visual builder"), + description: z.string().optional().describe("Short description shown to editors in the builder"), + onlyAdminModule: z.boolean().optional().describe("If true, the module is only visible to admin users in the builder"), + MJMLModule: z.boolean().optional().describe("If true, the module is treated as an email (MJML) module"), + }), + { readOnlyHint: false, destructiveHint: false }, + withAuth(async ({ module, label, description, onlyAdminModule, MJMLModule }, _extra) => { + try { + const provided = { label, description, onlyAdminModule, MJMLModule }; + + // Validacion temprana: sin ninguna clave editable no tiene + // sentido llamar al endpoint. Ojo con los booleanos false — + // se comprueba `undefined`, no truthiness. + const changedKeys = EDITABLE_KEYS.filter((key) => provided[key] !== undefined); + if (changedKeys.length === 0) { + return handleToolError( + `Nothing to update: provide at least one of ${EDITABLE_KEYS.join(", ")}. The module id cannot be renamed with this tool.`, + "update_module_metadata", + { module, editableFields: EDITABLE_KEYS } + ); + } + + const { projectSlug } = getCurrentProjectInfo(); + + const body = { project: projectSlug, module }; + for (const key of changedKeys) body[key] = provided[key]; + + const result = await pythonPost("/api/modules/update-metadata", body); + + if (!result?.success) { + return { + content: [{ + type: "text", + text: JSON.stringify({ + success: false, + error: result?.error || "Could not update module metadata", + }), + }], + isError: true, + }; + } + + return { + content: [{ + type: "text", + text: JSON.stringify({ + success: true, + module: result.module || module, + updatedFields: changedKeys, + metadata: result.metadata || {}, + }, null, 2), + }], + }; + } catch (error) { + return handleToolError(error, "update_module_metadata", { module }); + } + }) + ); +}