analyze/upload vía /api/image-bytes + MCP HTTP (vscode) forzado a test

Imágenes:
- analyze_image y upload resuelven los bytes por el endpoint Python
  /api/image-bytes (pythonGetBinary). analyze_image enruta los dominios
  forge (env ACAI_FORGE_DOMAIN) al endpoint en vez de fetch directo (que
  daba ECONNREFUSED 127.0.0.1 dentro del container).

Aislamiento de entorno (vscode = solo test):
- resolveCurrentModeOverride(): sesión MCP HTTP (mcpSessionId presente) →
  "local"; stdio (chat/cron) → ACAI_MODE_OVERRIDE de entorno. Lo usan los
  builders de headers (pythonServerClient, files/helpers) → toda tool del
  MCP HTTP manda X-Acai-Mode: local.
- httpServer.resolveProjectCredentials fuerza forceMode:"local" al resolver
  project-info → la sesión obtiene web_url/api_web_url forge-local y opera
  siempre contra test, nunca producción.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordan
2026-06-19 19:11:50 +01:00
parent 5883473e92
commit 5dc2dbcf4a
7 changed files with 120 additions and 118 deletions

View File

@@ -1,5 +1,5 @@
import axios from "axios";
import { resolveCurrentAcaiUser } from "./sessionHelpers.js";
import { resolveCurrentAcaiUser, resolveCurrentModeOverride } from "./sessionHelpers.js";
const PYTHON_BASE = `http://app:${process.env.ACAI_PORT || 9091}`;
@@ -11,7 +11,7 @@ const PYTHON_BASE = `http://app:${process.env.ACAI_PORT || 9091}`;
*/
function buildPythonHeaders(extra = {}) {
const authHeader = process.env.ACAI_AUTH_HEADER || "";
const mode = process.env.ACAI_MODE_OVERRIDE || process.env.ACAI_MODE || "";
const mode = resolveCurrentModeOverride();
const role = process.env.ACAI_ROLE_OVERRIDE || "";
const acaiUser = resolveCurrentAcaiUser();
@@ -43,3 +43,20 @@ export async function pythonGet(path, params = null, timeout = 30000) {
});
return response.data;
}
/**
* GET binario al server Python (p.ej. /api/image-bytes). Devuelve
* { buffer: Buffer, mimeType: string }. Lanza si el status no es 2xx.
*/
export async function pythonGetBinary(path, params = null, timeout = 30000) {
const response = await axios.get(`${PYTHON_BASE}${path}`, {
params: params || undefined,
headers: buildPythonHeaders(),
responseType: "arraybuffer",
timeout,
maxContentLength: Infinity,
});
const mimeType = (response.headers?.["content-type"] || "").split(";")[0].trim()
|| "application/octet-stream";
return { buffer: Buffer.from(response.data), mimeType };
}

View File

@@ -25,3 +25,23 @@ export function resolveCurrentAcaiUser() {
const creds = getMcpSessionCredentials(sessionId);
return creds?.acai_user || null;
}
/**
* Modo efectivo (X-Acai-Mode) para las llamadas al server Python.
*
* Regla de seguridad: una sesion MCP HTTP (mcpSessionId presente) es SIEMPRE un
* cliente externo — en la practica el plugin VS Code Acai Forge — y solo puede
* operar sobre TEST. Por eso forzamos "local" pase lo que pase el .acai del
* proyecto. El server Python honra este header para decidir el destino real
* (BD y ficheros), de modo que vscode nunca toca produccion.
*
* Las sesiones stdio (chat del dashboard / cronjobs) NO tienen mcpSessionId:
* mantienen el override de entorno (ACAI_MODE_OVERRIDE), que puede ser
* "production" cuando corresponde (chat en modo produccion, cron de prod).
*
* @returns {string} "local" | "production" | "" (vacio = usar .acai)
*/
export function resolveCurrentModeOverride() {
if (getCurrentSessionId()) return "local";
return process.env.ACAI_MODE_OVERRIDE || process.env.ACAI_MODE || "";
}