Fix upload_image_to_assets 404 en Forge (header Host) + guard data-URI

- saveFileBuilder (fileBuilder.js) hacía POST directo a viewer_functions.php
  sin header Host -> en Forge (api_web_url interno http://web:80) Apache
  servía el vhost por defecto -> 404. Ahora delega en
  AcaiHttpClient.postViewerAction, que resuelve api_web_url + Host:
  forge_host (igual que el resto de tools). Pasa credentials completo.
- upload_record_image: rechaza data-URI/base64 con error claro (antes
  derivaba el nombre del base64 -> "File name too long" en mcp_respond.php).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordan Diaz
2026-06-12 09:29:17 +00:00
parent 9277862e56
commit 4543300101
3 changed files with 37 additions and 18 deletions

View File

@@ -1,11 +1,13 @@
import axios from "axios"; import { AcaiHttpClient } from "./acaiHttpClient.js";
/** /**
* Helper to save files using saveFileBuilder action * Helper to save files using saveFileBuilder action.
* Delega en AcaiHttpClient.postViewerAction, que construye la URL con
* api_web_url + el header Host (forge_host) y aplica assertSafeCmsTarget.
* Used by multiple tools (save.js, saveGeneralSection.js, write.js, etc.) * Used by multiple tools (save.js, saveGeneralSection.js, write.js, etc.)
* *
* @param {Object} params * @param {Object} params
* @param {string} params.web_url - URL base del sitio (ej: http://localhost:PORT) * @param {Object} params.credentials - Target completo (web_url, api_web_url, forge_host, mode)
* @param {string} params.token - Session token * @param {string} params.token - Session token
* @param {string} params.tokenHash - Token hash * @param {string} params.tokenHash - Token hash
* @param {string} params.path - Folder path (e.g., '/modulos/mymodule/') * @param {string} params.path - Folder path (e.g., '/modulos/mymodule/')
@@ -14,7 +16,7 @@ import axios from "axios";
* @returns {Promise<Object>} Response from the API * @returns {Promise<Object>} Response from the API
*/ */
export async function saveFileBuilder({ export async function saveFileBuilder({
web_url, credentials,
token, token,
tokenHash, tokenHash,
path, path,
@@ -26,12 +28,7 @@ export async function saveFileBuilder({
return null; return null;
} }
const viewerUrl = web_url + '/cms/lib/viewer_functions.php';
const payload = { const payload = {
action_ws: 'saveFileBuilder',
token: token,
tokenHash: tokenHash,
fileName: fileName, fileName: fileName,
content: content, content: content,
rawDataSended: rawDataSended, rawDataSended: rawDataSended,
@@ -39,14 +36,17 @@ export async function saveFileBuilder({
path: path path: path
}; };
console.error(`[saveFileBuilder] URL: ${viewerUrl}`);
console.error(`[saveFileBuilder] Path: ${path}`); console.error(`[saveFileBuilder] Path: ${path}`);
console.error(`[saveFileBuilder] Content length: ${content.length} chars`); console.error(`[saveFileBuilder] Content length: ${content.length} chars`);
try { try {
const response = await axios.post(viewerUrl, payload, { const response = await AcaiHttpClient.postViewerAction(
headers: { "Content-Type": "application/json" } credentials,
}); 'saveFileBuilder',
payload,
token,
tokenHash
);
console.error(`[saveFileBuilder] Response for ${fileName}:`, JSON.stringify(response.data, null, 2)); console.error(`[saveFileBuilder] Response for ${fileName}:`, JSON.stringify(response.data, null, 2));
@@ -69,7 +69,7 @@ export async function saveFileBuilder({
* Helper to save multiple files at once * Helper to save multiple files at once
* *
* @param {Object} params * @param {Object} params
* @param {string} params.web_url - URL base del sitio (ej: http://localhost:PORT) * @param {Object} params.credentials - Target completo (web_url, api_web_url, forge_host, mode)
* @param {string} params.token - Session token * @param {string} params.token - Session token
* @param {string} params.tokenHash - Token hash * @param {string} params.tokenHash - Token hash
* @param {string} params.path - Folder path (e.g., '/modulos/mymodule/') * @param {string} params.path - Folder path (e.g., '/modulos/mymodule/')
@@ -77,7 +77,7 @@ export async function saveFileBuilder({
* @returns {Promise<Object>} Results for each file * @returns {Promise<Object>} Results for each file
*/ */
export async function saveMultipleFiles({ export async function saveMultipleFiles({
web_url, credentials,
token, token,
tokenHash, tokenHash,
path, path,
@@ -88,7 +88,7 @@ export async function saveMultipleFiles({
for (const [fileName, content] of Object.entries(files)) { for (const [fileName, content] of Object.entries(files)) {
if (content) { if (content) {
results[fileName] = await saveFileBuilder({ results[fileName] = await saveFileBuilder({
web_url, credentials,
token, token,
tokenHash, tokenHash,
path, path,

View File

@@ -119,6 +119,25 @@ export function registerUploadRecordImageTool(server) {
); );
if (validationError) return validationError; if (validationError) return validationError;
// Rechazar data-URI / base64 crudo: derivar el nombre de fichero
// del base64 produce nombres de miles de chars que revientan
// file_put_contents ("File name too long"). Exigir URL http real.
const trimmedImage = imageUrl.trim();
const isHttpUrl = /^https?:\/\//i.test(trimmedImage);
const isFsPath = trimmedImage.startsWith("/") && !trimmedImage.startsWith("//");
if (!isHttpUrl && !isFsPath) {
return {
content: [{
type: "text",
text: JSON.stringify({
error: "imageUrl must be an http(s) URL, not a data URI or raw base64. " +
"First upload the image with the 'upload_image_to_assets' tool and pass the returned imageUrl here."
}, null, 2)
}],
isError: true,
};
}
const projectSlug = path.basename(resolveCurrentProjectDir()); const projectSlug = path.basename(resolveCurrentProjectDir());
// Intentar via Python server (tiene sync + optimizacion) // Intentar via Python server (tiene sync + optimizacion)

View File

@@ -154,7 +154,7 @@ export function registerUploadImageToAssetsTool(server) {
// Upload using saveFileBuilder // Upload using saveFileBuilder
const uploadResult = await saveFileBuilder({ const uploadResult = await saveFileBuilder({
web_url: credentials.api_web_url || credentials.web_url, credentials,
token: credentials.token, token: credentials.token,
tokenHash: credentials.tokenHash, tokenHash: credentials.tokenHash,
path: assetsPath, path: assetsPath,