Files
agenticSystem/mcp-server/tools/tables/schema.js
2026-04-12 14:45:50 +00:00

90 lines
4.6 KiB
JavaScript

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 });
}
})
);
}