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:
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user