90 lines
4.6 KiB
JavaScript
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 });
|
|
}
|
|
})
|
|
);
|
|
}
|