diff --git a/mcp-server/tools/media/upload.js b/mcp-server/tools/media/upload.js index 1720338..739a6bc 100644 --- a/mcp-server/tools/media/upload.js +++ b/mcp-server/tools/media/upload.js @@ -100,11 +100,11 @@ async function resolveLocalImageAsBase64(imageUrl) { export function registerUploadRecordImageTool(server) { server.tool( "upload_record_image", - "Upload an image to a specific record field in Acai CMS. Downloads the image from a URL and uploads it. Table names are WITHOUT the 'cms_' prefix. The recordId is the 'num' primary key, never 'id'. If the URL came from generate_image, prefer uploadUrl (or fullUrl) over dockerUrl in Forge environments.", + "Upload an image to a specific record field in Acai CMS. MANDATORY: before calling this tool, you MUST call get_table_schema with minimal=true to find the EXACT upload field name. Look for fields with type='upload'. NEVER guess field names. Table names WITHOUT 'cms_' prefix. recordId is 'num', never 'id'. If the URL came from generate_image, prefer uploadUrl (or fullUrl) over dockerUrl.", withAuthParams({ tableName: z.string().describe("Table name without 'cms_' prefix (e.g., 'productos')"), recordId: z.string().describe("Record 'num' (primary key)"), - fieldName: z.string().describe("Field name (e.g., 'galeria_imagenes')"), + fieldName: z.string().describe("EXACT field name from the schema. MUST match a field with type 'upload' from get_table_schema or get_module_config_vars. Do NOT guess."), imageUrl: z.string().describe("URL of the image to upload"), alt: z.string().optional().describe("Alt text for the image (optional)"), }), diff --git a/mcp-server/tools/tables/schema.js b/mcp-server/tools/tables/schema.js index f2cb06e..7b4d65b 100644 --- a/mcp-server/tools/tables/schema.js +++ b/mcp-server/tools/tables/schema.js @@ -12,9 +12,10 @@ export function registerGetTableSchemaTool(server) { withAuthParams({ tableName: z.string().describe("Table name without cms_ prefix"), minimal: z.boolean().optional().describe("If true, returns only field names + types + labels"), + filterFields: z.string().optional().describe("Filter field names containing these terms (pipe-separated, e.g. 'galeria|foto|image'). Only matching fields are returned. Useful to find the exact field name without loading the full schema."), }), { readOnlyHint: true, destructiveHint: false }, - withAuth(async ({ tableName, minimal }, extra) => { + withAuth(async ({ tableName, minimal, filterFields }, extra) => { try { const validationError = validateRequired({ tableName }, ['tableName'], 'get_table_schema'); if (validationError) return validationError; @@ -45,18 +46,41 @@ export function registerGetTableSchemaTool(server) { const parsed = parseIniSchema(iniContent); - if (minimal) { - const minimalSchema = { menuName: parsed.menuName, menuType: parsed.menuType, enlace: parsed.enlace }; - const minFields = {}; - for (const [key, value] of Object.entries(parsed.fields || {})) { - minFields[key] = { type: value.type }; - if (value.label) minFields[key].label = value.label; + // Filtrar campos si se pasa filterFields (pipe-separated terms) + let fields = parsed.fields || {}; + if (filterFields) { + const terms = filterFields.toLowerCase().split("|").map(t => t.trim()).filter(Boolean); + const filtered = {}; + for (const [key, value] of Object.entries(fields)) { + const keyLower = key.toLowerCase(); + const labelLower = (value.label || "").toLowerCase(); + if (terms.some(t => keyLower.includes(t) || labelLower.includes(t))) { + filtered[key] = value; + } + } + fields = filtered; + if (Object.keys(fields).length === 0) { + return { content: [{ type: "text", text: JSON.stringify({ + tableName, filterFields, matches: 0, + message: "No fields matching filter. Try broader terms or omit filterFields to see all.", + }, null, 2) }] }; } - minimalSchema.fields = minFields; - return { content: [{ type: "text", text: JSON.stringify(minimalSchema, null, 2) }] }; } - return { content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }] }; + if (minimal || filterFields) { + const result = { menuName: parsed.menuName, tableName }; + const minFields = {}; + for (const [key, value] of Object.entries(fields)) { + minFields[key] = { type: value.type }; + if (value.label) minFields[key].label = value.label; + if (value.maxUploads) minFields[key].maxUploads = value.maxUploads; + } + result.fields = minFields; + if (filterFields) result.filterFields = filterFields; + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + + return { content: [{ type: "text", text: JSON.stringify({ ...parsed, fields }, null, 2) }] }; } catch (error) { return handleToolError(error, 'get_table_schema', { tableName }); }