Files
agenticSystem/mcp-server/tools/files/helpers.js
Jordan 5dc2dbcf4a 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>
2026-06-19 19:11:50 +01:00

96 lines
3.2 KiB
JavaScript

import axios from "axios";
import path from "path";
import { LOCAL_SERVER_URL, getLocalServerHeaders } from "../../config/index.js";
import { getCurrentSessionId } from "../../utils/sessionContext.js";
import { getMcpSessionCredentials } from "../../auth/credentials.js";
import { resolveCurrentAcaiUser, resolveCurrentModeOverride } from "../helpers/sessionHelpers.js";
/**
* Resuelve `project_dir` para la tool en curso.
*
* Orden de precedencia:
* 1. AsyncLocalStorage (mcpSessionId) -> credenciales de la sesion HTTP
* 2. process.env.ACAI_PROJECT_DIR (modo stdio / fallback legacy)
*
* Devuelve string vacio si no hay forma de resolverlo — el caller decide
* como manejar el error.
*/
export function resolveCurrentProjectDir() {
const sessionId = getCurrentSessionId();
if (sessionId) {
const creds = getMcpSessionCredentials(sessionId);
if (creds?.project_dir) return creds.project_dir;
}
return process.env.ACAI_PROJECT_DIR || "";
}
export function getCurrentProjectInfo() {
const projectDir = resolveCurrentProjectDir();
if (!projectDir) {
throw new Error("ACAI_PROJECT_DIR not set");
}
return {
projectDir,
projectSlug: path.basename(path.resolve(projectDir)),
};
}
export async function callLocalFileEndpoint(method, endpoint, payload = null, query = null) {
const headers = getLocalServerHeaders();
const authHeader = process.env.ACAI_AUTH_HEADER || "";
const mode = resolveCurrentModeOverride();
const role = process.env.ACAI_ROLE_OVERRIDE || "";
if (authHeader) headers["Authorization"] = authHeader;
if (mode) headers["X-Acai-Mode"] = mode;
if (role) headers["X-Acai-Role"] = role;
// Inyectar X-Acai-User cuando hay sesion HTTP activa: permite que los
// endpoints autenticados del server Python identifiquen al usuario sin
// depender de Authorization Basic.
const acaiUser = resolveCurrentAcaiUser();
if (acaiUser) headers["X-Acai-User"] = acaiUser;
if (method === "GET") {
const response = await axios.get(`${LOCAL_SERVER_URL}${endpoint}`, {
params: query || undefined,
headers,
timeout: 30000,
validateStatus: (status) => status < 600,
});
return { status: response.status, data: response.data };
}
const response = await axios.post(`${LOCAL_SERVER_URL}${endpoint}`, payload || {}, {
headers,
timeout: 30000,
validateStatus: (status) => status < 600,
});
return { status: response.status, data: response.data };
}
export function buildLocalFileErrorResponse(toolName, result, extra = {}) {
const payload = result?.data || {};
const message =
payload.message ||
payload.error ||
payload.compileError ||
`HTTP ${result?.status || 500}`;
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: {
code: `HTTP_${result.status}`,
message,
context: toolName,
...extra,
...payload,
},
}, null, 2),
}],
isError: true,
};
}