feat: tool set_upload_info para editar info1-info5 de uploads (registros y vars de modulo) + docs
This commit is contained in:
@@ -2,10 +2,14 @@ import { registerUploadRecordImageTool } from './upload.js';
|
||||
import { registerUploadImageToAssetsTool } from './uploadImageToAssets.js';
|
||||
import { registerGenerateImageTool } from './generateImage.js';
|
||||
import { registerAnalyzeImageTool } from './analyze_image.js';
|
||||
import { registerSetUploadInfoTool } from './setUploadInfo.js';
|
||||
|
||||
export function registerMediaTools(server) {
|
||||
registerUploadRecordImageTool(server);
|
||||
registerUploadImageToAssetsTool(server);
|
||||
registerGenerateImageTool(server);
|
||||
registerAnalyzeImageTool(server);
|
||||
// Metadatos info1..info5 de uploads: son datos de contenido, no codigo,
|
||||
// asi que va sin gate de canEditCode() como el resto de tools de media.
|
||||
registerSetUploadInfoTool(server);
|
||||
}
|
||||
|
||||
115
mcp-server/tools/media/setUploadInfo.js
Normal file
115
mcp-server/tools/media/setUploadInfo.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { z } from "zod";
|
||||
import { withAuth } from "../../auth/index.js";
|
||||
import { withAuthParams } from "../helpers/authSchema.js";
|
||||
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||
import { pythonPost } from "../helpers/pythonServerClient.js";
|
||||
import { getCurrentProjectInfo } from "../files/helpers.js";
|
||||
|
||||
// Tool: set_upload_info
|
||||
//
|
||||
// Rellena los metadatos info1..info5 de una imagen YA subida, delegando en
|
||||
// /api/uploads/set-info del server Python.
|
||||
//
|
||||
// El uploadId es la PK (`num`) de la tabla CENTRAL `uploads`, asi que la misma
|
||||
// tool sirve tanto para uploads de registros normales como para uploads que
|
||||
// viven en vars de modulo (que fisicamente cuelgan de `builder_custom`). Por
|
||||
// eso no hace falta pasar tabla/registro/campo: el uploadId ya identifica la
|
||||
// fila de forma univoca.
|
||||
//
|
||||
// Ojo con el filtrado de claves: se comprueba `!== undefined`, NO truthiness,
|
||||
// para que una cadena vacia ("") viaje en el body y sirva para VACIAR un info.
|
||||
|
||||
const INFO_KEYS = ["info1", "info2", "info3", "info4", "info5"];
|
||||
|
||||
// Tope por campo que ya impone el endpoint (_UPLOAD_INFO_MAX_LEN en cms_db.py).
|
||||
// Se replica en el zod para que el agente reciba el error sin round-trip.
|
||||
const INFO_MAX_LEN = 1000;
|
||||
|
||||
export function registerSetUploadInfoTool(server) {
|
||||
server.tool(
|
||||
"set_upload_info",
|
||||
`Set the metadata fields info1..info5 of an image that has ALREADY been uploaded. This is the only way to fill infoN metadata: uploading only ever writes info1 (via the 'alt' param of upload_record_image / replace_record_image).
|
||||
|
||||
uploadId is the primary key ('num') of the CENTRAL 'uploads' table, so THIS SAME TOOL WORKS FOR BOTH KINDS OF UPLOAD — you never pass table/record/field here, the uploadId alone identifies the file:
|
||||
|
||||
1) Uploads of a NORMAL record: get the uploadId with list_record_uploads(tableName, recordId, fieldName).
|
||||
|
||||
2) Uploads stored in MODULE VARS: call get_module_config_vars first. Its 'uploadFields' is a map varName -> { tableName: "builder_custom", recordNum, fieldName, infoLabels? } (set_module_config_vars returns the same map and ALSO covers uploads nested inside a 'multi' var, where the key is "varName.subVarName" — e.g. "slides.imagen" — and the value is an array of { index, fieldName, recordNum }). Then call list_record_uploads with tableName: "builder_custom", recordId: <recordNum> and fieldName: <fieldName> to get the uploadIds.
|
||||
|
||||
Meaning of each infoN:
|
||||
- info1 is BY CONVENTION the alt text — the very same column the 'alt' param writes when uploading. Setting info1 here overwrites that alt text.
|
||||
- info2..info5 are free metadata slots whose meaning is defined PER MODULE: the module declares them in its index-base.tpl with the attributes data-field-info1..data-field-info5, and they are stored as 'infoLabels' in the module's builder.json (e.g. info2 = "caption", info3 = "credit/author", info4 = "link"). You do NOT need to open that file: get_module_config_vars returns them inside each 'uploadFields' entry as 'infoLabels' whenever the module defines them — an array where position 0 is info1, position 1 is info2, etc. (for uploads inside a 'multi' var the labels come in varsMeta.<varName>.subFields.<subVar>.infoLabels). The module's builder.json (vars.<varName>.infoLabels) is still the canonical source if you need to double-check. Read those labels before writing — do NOT invent a meaning.
|
||||
|
||||
At least one of info1..info5 is required. Only the keys you send are written; the ones you omit are left untouched. Passing an empty string ("") CLEARS that info field. Max 1000 characters per field.`,
|
||||
withAuthParams({
|
||||
uploadId: z.string().describe("Upload ID: the 'num' PK of the central 'uploads' table (from list_record_uploads, for a normal record or for builder_custom in the case of module vars)"),
|
||||
info1: z.string().max(INFO_MAX_LEN).optional().describe("info1 — by convention the ALT TEXT of the image (same column the 'alt' upload param writes). Empty string clears it. Max 1000 chars."),
|
||||
info2: z.string().max(INFO_MAX_LEN).optional().describe("info2 — module-defined metadata (label at position 1 of 'infoLabels', returned by get_module_config_vars in uploadFields). Empty string clears it. Max 1000 chars."),
|
||||
info3: z.string().max(INFO_MAX_LEN).optional().describe("info3 — module-defined metadata (label at position 2 of 'infoLabels', returned by get_module_config_vars in uploadFields). Empty string clears it. Max 1000 chars."),
|
||||
info4: z.string().max(INFO_MAX_LEN).optional().describe("info4 — module-defined metadata (label at position 3 of 'infoLabels', returned by get_module_config_vars in uploadFields). Empty string clears it. Max 1000 chars."),
|
||||
info5: z.string().max(INFO_MAX_LEN).optional().describe("info5 — module-defined metadata (label at position 4 of 'infoLabels', returned by get_module_config_vars in uploadFields). Empty string clears it. Max 1000 chars."),
|
||||
}),
|
||||
{ readOnlyHint: false, destructiveHint: false },
|
||||
withAuth(async ({ uploadId, info1, info2, info3, info4, info5 }, _extra) => {
|
||||
try {
|
||||
const validationError = validateRequired(
|
||||
{ uploadId },
|
||||
["uploadId"],
|
||||
"set_upload_info"
|
||||
);
|
||||
if (validationError) return validationError;
|
||||
|
||||
const provided = { info1, info2, info3, info4, info5 };
|
||||
|
||||
// Validacion temprana: sin ninguna clave infoN no hay nada que
|
||||
// escribir, asi que ni llamamos al endpoint. Se filtra por
|
||||
// `undefined` y NO por truthiness, para que "" (vaciar un info)
|
||||
// se considere un valor enviado.
|
||||
const changedKeys = INFO_KEYS.filter((key) => provided[key] !== undefined);
|
||||
if (changedKeys.length === 0) {
|
||||
return handleToolError(
|
||||
`Nothing to update: provide at least one of ${INFO_KEYS.join(", ")}. ` +
|
||||
`info1 is the alt text; the meaning of info2..info5 is defined by the module ` +
|
||||
`(read 'infoLabels' with get_module_config_vars). Pass "" to clear a field.`,
|
||||
"set_upload_info",
|
||||
{ uploadId, editableFields: INFO_KEYS }
|
||||
);
|
||||
}
|
||||
|
||||
const { projectSlug } = getCurrentProjectInfo();
|
||||
|
||||
const body = { project: projectSlug, uploadId };
|
||||
for (const key of changedKeys) body[key] = provided[key];
|
||||
|
||||
const result = await pythonPost("/api/uploads/set-info", body);
|
||||
|
||||
if (!result?.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: false,
|
||||
error: result?.error || "Could not set upload info",
|
||||
}),
|
||||
}],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
success: true,
|
||||
uploadId: result.uploadId || uploadId,
|
||||
updatedFields: changedKeys,
|
||||
info: result.info || {},
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
} catch (error) {
|
||||
return handleToolError(error, "set_upload_info", { uploadId });
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user