feat: guard de schemas de tablas en las file tools (redirige a las table tools)
This commit is contained in:
@@ -1,7 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||||
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
||||||
import { isProtectedLayoutPath, buildProtectedLayoutPathError } from "./protectedPaths.js";
|
import {
|
||||||
|
isProtectedLayoutPath,
|
||||||
|
buildProtectedLayoutPathError,
|
||||||
|
isProtectedSchemaPath,
|
||||||
|
buildProtectedSchemaPathError,
|
||||||
|
} from "./protectedPaths.js";
|
||||||
|
|
||||||
export function registerAcaiDeleteTool(server) {
|
export function registerAcaiDeleteTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -21,6 +26,10 @@ export function registerAcaiDeleteTool(server) {
|
|||||||
return buildProtectedLayoutPathError(file_path);
|
return buildProtectedLayoutPathError(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedSchemaPath(file_path)) {
|
||||||
|
return buildProtectedSchemaPathError(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
||||||
const result = await callLocalFileEndpoint("POST", "/api/files/delete", {
|
const result = await callLocalFileEndpoint("POST", "/api/files/delete", {
|
||||||
project: projectSlug,
|
project: projectSlug,
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||||
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
||||||
import { isProtectedLayoutPath, buildProtectedLayoutPathError } from "./protectedPaths.js";
|
import {
|
||||||
|
isProtectedLayoutPath,
|
||||||
|
buildProtectedLayoutPathError,
|
||||||
|
isProtectedSchemaPath,
|
||||||
|
buildProtectedSchemaPathError,
|
||||||
|
} from "./protectedPaths.js";
|
||||||
|
|
||||||
export function registerAcaiLineReplaceTool(server) {
|
export function registerAcaiLineReplaceTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -29,6 +34,10 @@ export function registerAcaiLineReplaceTool(server) {
|
|||||||
return buildProtectedLayoutPathError(file_path);
|
return buildProtectedLayoutPathError(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedSchemaPath(file_path)) {
|
||||||
|
return buildProtectedSchemaPathError(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
||||||
const result = await callLocalFileEndpoint("POST", "/api/files/line-replace", {
|
const result = await callLocalFileEndpoint("POST", "/api/files/line-replace", {
|
||||||
project: projectSlug,
|
project: projectSlug,
|
||||||
|
|||||||
@@ -11,11 +11,26 @@ const PROTECTED_LAYOUT_PATHS = [
|
|||||||
"template/estandar/modulos/custom-footer/",
|
"template/estandar/modulos/custom-footer/",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Table schemas live here. They are the .ini.php mirror of the real MySQL
|
||||||
|
// structure: editing the file by hand does not run any DDL, so the schema and
|
||||||
|
// the database drift apart (and the CMS keeps serving stale cached metadata).
|
||||||
|
const SCHEMA_DIR_PREFIX = "cms/data/schema/";
|
||||||
|
|
||||||
|
// Normalizes a relative path: drops leading slashes and "./" segments so
|
||||||
|
// "/cms/...", "./cms/..." and "cms/..." all compare equal.
|
||||||
|
function normalizeRelPath(relPath) {
|
||||||
|
let norm = String(relPath).replace(/^\/+/, "");
|
||||||
|
while (norm.startsWith("./")) {
|
||||||
|
norm = norm.slice(2).replace(/^\/+/, "");
|
||||||
|
}
|
||||||
|
return norm;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns true when `relPath` points at the layout.json or any of the
|
// Returns true when `relPath` points at the layout.json or any of the
|
||||||
// generated custom-{header,footer}[-twig] module folders.
|
// generated custom-{header,footer}[-twig] module folders.
|
||||||
export function isProtectedLayoutPath(relPath) {
|
export function isProtectedLayoutPath(relPath) {
|
||||||
if (!relPath) return false;
|
if (!relPath) return false;
|
||||||
const norm = String(relPath).replace(/^\/+/, "");
|
const norm = normalizeRelPath(relPath);
|
||||||
return PROTECTED_LAYOUT_PATHS.some(p => {
|
return PROTECTED_LAYOUT_PATHS.some(p => {
|
||||||
// Folder entries end with "/" -> prefix match on the normalized path.
|
// Folder entries end with "/" -> prefix match on the normalized path.
|
||||||
// File entries (no trailing slash) -> exact match only.
|
// File entries (no trailing slash) -> exact match only.
|
||||||
@@ -37,3 +52,26 @@ export function buildProtectedLayoutPathError(relPath) {
|
|||||||
isError: true,
|
isError: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true when `relPath` points inside cms/data/schema/ (the table schema
|
||||||
|
// directory), so file tools can bail out before hitting the Python endpoint.
|
||||||
|
export function isProtectedSchemaPath(relPath) {
|
||||||
|
if (!relPath) return false;
|
||||||
|
const norm = normalizeRelPath(relPath);
|
||||||
|
if (!norm) return false;
|
||||||
|
return norm === SCHEMA_DIR_PREFIX.slice(0, -1) || norm.startsWith(SCHEMA_DIR_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builds a consistent MCP error response pointing the agent to the table tools.
|
||||||
|
export function buildProtectedSchemaPathError(relPath) {
|
||||||
|
return {
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
error: `Forbidden path: ${relPath} lives in ${SCHEMA_DIR_PREFIX} and table schemas are read-only through the file tools (acai-write, acai-line-replace, acai-delete). To change the structure use the table tools instead - create_table, create_field, update_field, delete_field, update_table_metadata, delete_table - which run the real DDL in MySQL and refresh the caches. To read a schema use get_table_schema or acai-view.`,
|
||||||
|
}, null, 2),
|
||||||
|
}],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
|
||||||
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
import { getCurrentProjectInfo, callLocalFileEndpoint, buildLocalFileErrorResponse } from "./helpers.js";
|
||||||
import { isProtectedLayoutPath, buildProtectedLayoutPathError } from "./protectedPaths.js";
|
import {
|
||||||
|
isProtectedLayoutPath,
|
||||||
|
buildProtectedLayoutPathError,
|
||||||
|
isProtectedSchemaPath,
|
||||||
|
buildProtectedSchemaPathError,
|
||||||
|
} from "./protectedPaths.js";
|
||||||
|
|
||||||
export function registerAcaiWriteTool(server) {
|
export function registerAcaiWriteTool(server) {
|
||||||
server.tool(
|
server.tool(
|
||||||
@@ -28,6 +33,10 @@ Before writing, check the matching documentation for the file type:
|
|||||||
return buildProtectedLayoutPathError(file_path);
|
return buildProtectedLayoutPathError(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isProtectedSchemaPath(file_path)) {
|
||||||
|
return buildProtectedSchemaPathError(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
const { projectSlug, projectDir } = getCurrentProjectInfo();
|
||||||
const result = await callLocalFileEndpoint("POST", "/api/files/write", {
|
const result = await callLocalFileEndpoint("POST", "/api/files/write", {
|
||||||
project: projectSlug,
|
project: projectSlug,
|
||||||
|
|||||||
Reference in New Issue
Block a user