import { z } from "zod"; import { withAuth, getSessionCredentials, getApiClient } from "../../auth/index.js"; import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js"; import { AcaiHttpClient } from "../helpers/acaiHttpClient.js"; import { withAuthParams } from "../helpers/authSchema.js"; import { parseIniSchema } from "./iniParser.js"; export function registerGetTableSchemaTool(server) { server.tool( "get_table_schema", "Get the full schema of a database table, including all fields and their types/metadata. Table names WITHOUT 'cms_' prefix. Primary key is 'num', never 'id'. Use minimal=true for compact output (saves tokens).", 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, filterFields }, extra) => { try { const validationError = validateRequired({ tableName }, ['tableName'], 'get_table_schema'); if (validationError) return validationError; const credentials = await getSessionCredentials(extra.sessionId); const payload = { action_ws: "getTableSchemas", tableName, token: credentials.token, tokenHash: credentials.tokenHash, }; const response = await AcaiHttpClient.postViewerFunctions( await getApiClient(extra.sessionId), payload ); const apiError = handleApiResponse(response.data, 'get_table_schema'); if (apiError) return apiError; const schemas = response.data.schemas || {}; const filename = tableName + '.ini.php'; const iniContent = schemas[filename]; if (!iniContent) { return { content: [{ type: "text", text: `Table '${tableName}' not found` }], isError: true, }; } const parsed = parseIniSchema(iniContent); // 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) }] }; } } 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 }); } }) ); }