import { z } from "zod"; import { withAuth, getApiClient, getSessionCredentials, getCommonParams } from "../../auth/index.js"; import { normalizeSchemaForSave, mergeTableSchemas } from "../../utils/fieldHelpers.js"; import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js"; import { AcaiHttpClient } from "../helpers/acaiHttpClient.js"; import { withAuthParams } from "../helpers/authSchema.js"; export function registerGetTableSchemaTool(server) { server.tool( "get_table_schema", "Get the schema of a database table. Tables WITHOUT 'cms_' prefix. Primary key is 'num', NEVER 'id'. Use minimal=true for just field names + types (saves tokens).", withAuthParams({ tableName: z.string().describe("Name of the table to get schema for (without 'cms_' prefix)"), minimal: z.boolean().optional().describe("If true, returns only field names and types (compact). Default: false (full schema with all metadata)."), }), { readOnlyHint: true, destructiveHint: false }, withAuth(async ({ tableName, minimal }, extra) => { try { // Validate required parameters const validationError = validateRequired({ tableName }, ['tableName'], 'get_table_schema'); if (validationError) return validationError; const credentials = await getSessionCredentials(extra.sessionId); const response = await AcaiHttpClient.saasPostRequest( { id: tableName }, credentials.token ); if (!response.data.success) { return { content: [{ type: "text", text: "Error getting schema: " + JSON.stringify(response.data) }], isError: true, }; } // Find the specific table const table = response.data.data; if (!table) { return { content: [{ type: "text", text: `Table '${tableName}' not found` }], isError: true, }; } // Minimal mode: return only field names, types, and key metadata if (minimal) { const minimalSchema = {}; for (const [key, value] of Object.entries(table)) { if (value && typeof value === 'object' && value.type) { const field = { type: value.type }; if (value.label) field.label = value.label; if (value.optionsType) field.optionsType = value.optionsType; if (value.optionsTablename) field.optionsTablename = value.optionsTablename; if (value.isRequired) field.isRequired = value.isRequired; minimalSchema[key] = field; } else if (typeof value !== 'object') { // Keep top-level scalar metadata (menuName, menuType, enlace, etc.) minimalSchema[key] = value; } } return { content: [{ type: "text", text: JSON.stringify(minimalSchema, null, 2) }], }; } return { content: [{ type: "text", text: JSON.stringify(table, null, 2) }], }; } catch (error) { return handleToolError(error, 'get_table_schema', { tableName }); } }) ); } export function registerUpdateTableSchemaTool(server) { server.tool( "update_table_schema", `Update table-level metadata (menuName, menuOrder, enlace, seo_metas). NOT for field operations — use edit_table_field instead. Tables WITHOUT 'cms_' prefix. 2-step process: saves to SAAS server, then triggers website schema update.`, withAuthParams({ tableName: z.string().describe("Name of the table to update"), schema: z.object({}).passthrough().describe("Schema object with fields objects ( like reference schema table ) to add or update. By default, this is merged with the existing schema."), overwrite: z.boolean().optional().describe("If true, replaces the ENTIRE schema with the provided one (deleting missing fields). If false (default), merges with existing schema."), }), { readOnlyHint: false, destructiveHint: false }, withAuth(async ({ tableName, schema, overwrite = false }, extra) => { try { // Validate required parameters const validationError = validateRequired({ tableName, schema }, ['tableName', 'schema'], 'update_table_schema'); if (validationError) return validationError; let schemaToSave; const credentials = await getSessionCredentials(extra.sessionId); if (overwrite) { // If overwrite is true, use the provided schema directly schemaToSave = { ...schema }; } else { // Step 1: Fetch current schema to preserve existing fields const getResponse = await AcaiHttpClient.saasPostRequest( { id: tableName }, credentials.token ); if (!getResponse.data.success) { return { content: [{ type: "text", text: "Error fetching current schema: " + JSON.stringify(getResponse.data) }], isError: true, }; } const currentTable = getResponse.data.data; if (!currentTable) { return { content: [{ type: "text", text: `Table '${tableName}' not found. Please create it first using create_table.` }], isError: true, }; } // Step 2: Merge new schema into existing schema schemaToSave = mergeTableSchemas(currentTable, schema); } normalizeSchemaForSave(schemaToSave); // Remove tableName from schema (as done in frontend) delete schemaToSave.tableName; // Step 3: Save merged schema to SAAS server (PUT request) const saasResponse = await AcaiHttpClient.saasPutRequest( { action: "saveSchema", schema: schemaToSave, id: tableName, }, credentials.token ); // SAAS returns {success: true} not {result: true} if (!saasResponse.data.success && !saasResponse.data.result) { return { content: [{ type: "text", text: "Error saving schema to SAAS: " + JSON.stringify(saasResponse.data) }], isError: true, }; } // Step 4: Trigger schema update on website const client = await getApiClient(extra.sessionId); const updateResponse = await client.post("/cms/lib/viewer_functions.php", await getCommonParams(extra.sessionId, { action_ws: "updateAllSchemas", tokenHash: credentials.tokenHash })); // Check for website update errors let updateError = handleApiResponse(updateResponse.data, 'update_table_schema'); if (updateError) return updateError; return { content: [{ type: "text", text: JSON.stringify({ success: true, message: overwrite ? "Schema overwritten successfully" : "Schema updated successfully (merged with existing fields)", mergedFields: Object.keys(schemaToSave), saasResponse: saasResponse.data, webResponse: updateResponse.data }, null, 2) }], }; } catch (error) { return handleToolError(error, 'update_table_schema', { tableName, overwrite }); } }) ); }