Añadido el modo producción / test

This commit is contained in:
Jordan Diaz
2026-04-08 23:52:54 +00:00
parent c1a29bbbf8
commit 993e7d3000
9 changed files with 240 additions and 90 deletions

View File

@@ -11,7 +11,12 @@
* (the SimpleAuth header that Claude sends with each request).
*/
import fs from "node:fs";
import path from "node:path";
import { fetchProjectInfo } from "./localClient.js";
const DEFAULT_ROLE = 'developer';
const FORGE_INTERNAL_URL = process.env.ACAI_FORGE_WEB_URL || "http://web:80";
// Session-based credentials storage (ephemeral, per-session)
@@ -46,6 +51,102 @@ const cleanupExpiredMcpSessions = () => {
// Run cleanup every 5 minutes
setInterval(cleanupExpiredMcpSessions, 5 * 60 * 1000);
const buildApiUrlFromPublicUrl = (webUrl, explicitForgeHost = "") => {
if (!webUrl) return null;
if (explicitForgeHost) return FORGE_INTERNAL_URL;
try {
const parsed = new URL(webUrl);
if (parsed.hostname.includes(".forge.")) {
return FORGE_INTERNAL_URL;
}
} catch {
return null;
}
return webUrl;
};
const readProjectAcaiFallback = () => {
const projectDir = process.env.ACAI_PROJECT_DIR || "";
if (!projectDir) return null;
const acaiFilePath = path.join(projectDir, ".acai");
if (!fs.existsSync(acaiFilePath)) return null;
try {
const data = JSON.parse(fs.readFileSync(acaiFilePath, "utf-8"));
const website = data.domain || process.env.ACAI_WEBSITE || null;
let webUrl = data.local_web_url || process.env.ACAI_WEB_URL || (website ? `https://${website}` : null);
let forgeHost = data.local_forge_host || process.env.ACAI_FORGE_HOST || null;
// Respeta local_api_web_url del .acai si esta presente (override del usuario);
// si no, fallback al calculo automatico (web:80 para forge, web_url para Docker).
let apiWebUrl = data.local_api_web_url || process.env.ACAI_API_WEB_URL || buildApiUrlFromPublicUrl(webUrl, forgeHost);
let mode = data.mode || process.env.ACAI_MODE || "local";
// Override de entorno (inyectado por cronjobs via mcp_env)
if (process.env.ACAI_MODE_OVERRIDE) {
mode = process.env.ACAI_MODE_OVERRIDE;
if (process.env.ACAI_WEB_URL_OVERRIDE) webUrl = process.env.ACAI_WEB_URL_OVERRIDE;
if (process.env.ACAI_API_WEB_URL_OVERRIDE) apiWebUrl = process.env.ACAI_API_WEB_URL_OVERRIDE;
if (process.env.ACAI_FORGE_HOST_OVERRIDE !== undefined) forgeHost = process.env.ACAI_FORGE_HOST_OVERRIDE;
}
return {
token: data.token || process.env.ACAI_TOKEN || null,
website,
web_url: webUrl,
api_web_url: apiWebUrl,
forge_host: forgeHost,
tokenHash: data.tokenHash || process.env.ACAI_TOKEN_HASH || null,
mode,
profileName: "acai-file",
role: DEFAULT_ROLE,
};
} catch (error) {
console.error(`[Credentials] Failed to read .acai fallback: ${error.message}`);
return null;
}
};
const resolveLocalProjectFallback = async () => {
const projectDir = process.env.ACAI_PROJECT_DIR || "";
if (projectDir) {
try {
const info = await fetchProjectInfo({ project_dir: projectDir });
if (info?.success) {
let webUrl = info.web_url || process.env.ACAI_WEB_URL || null;
let apiWebUrl = info.api_web_url || buildApiUrlFromPublicUrl(info.web_url, info.forge_host) || null;
let forgeHost = info.forge_host || process.env.ACAI_FORGE_HOST || null;
let mode = info.mode || process.env.ACAI_MODE || "local";
// Override de entorno (inyectado por cronjobs via mcp_env)
if (process.env.ACAI_MODE_OVERRIDE) {
mode = process.env.ACAI_MODE_OVERRIDE;
if (process.env.ACAI_WEB_URL_OVERRIDE) webUrl = process.env.ACAI_WEB_URL_OVERRIDE;
if (process.env.ACAI_API_WEB_URL_OVERRIDE) apiWebUrl = process.env.ACAI_API_WEB_URL_OVERRIDE;
if (process.env.ACAI_FORGE_HOST_OVERRIDE !== undefined) forgeHost = process.env.ACAI_FORGE_HOST_OVERRIDE;
}
return {
token: info.token || process.env.ACAI_TOKEN || null,
website: info.domain || process.env.ACAI_WEBSITE || null,
web_url: webUrl,
api_web_url: apiWebUrl,
forge_host: forgeHost,
tokenHash: info.tokenHash || process.env.ACAI_TOKEN_HASH || null,
mode,
profileName: "project-info",
role: DEFAULT_ROLE,
};
}
} catch (error) {
console.error(`[Credentials] project-info fallback failed: ${error.message}`);
}
}
return readProjectAcaiFallback();
};
/**
* Get credentials by MCP-Session-Id
*/
@@ -132,10 +233,38 @@ export const getSessionCredentials = async (sessionId, inlineCredentials = null)
// Priority 2: Session credentials
const sessionCreds = sessionCredentials.get(sessionId);
if (sessionCreds) {
if (sessionCreds.token && sessionCreds.web_url && sessionCreds.api_web_url) {
console.error(`[Credentials] getSessionCredentials(${sessionId}) - FOUND: website=${sessionCreds.website}, hasToken=${!!sessionCreds.token}`);
return sessionCreds;
}
const hydrated = await resolveLocalProjectFallback();
if (hydrated) {
const merged = {
...sessionCreds,
token: sessionCreds.token || hydrated.token,
website: sessionCreds.website || hydrated.website,
web_url: sessionCreds.web_url || hydrated.web_url,
api_web_url: sessionCreds.api_web_url || hydrated.api_web_url,
forge_host: sessionCreds.forge_host || hydrated.forge_host,
tokenHash: sessionCreds.tokenHash || hydrated.tokenHash,
profileName: sessionCreds.profileName || hydrated.profileName,
role: sessionCreds.role || hydrated.role,
};
sessionCredentials.set(sessionId, merged);
console.error(`[Credentials] getSessionCredentials(${sessionId}) - HYDRATED from local fallback`);
return merged;
}
console.error(`[Credentials] getSessionCredentials(${sessionId}) - FOUND: website=${sessionCreds.website}, hasToken=${!!sessionCreds.token}`);
return sessionCreds;
}
const localFallback = await resolveLocalProjectFallback();
if (localFallback) {
sessionCredentials.set(sessionId, localFallback);
console.error(`[Credentials] getSessionCredentials(${sessionId}) - USING local project fallback`);
return localFallback;
}
// Priority 3: Fallback to environment variables (for backwards compatibility)
console.error(`[Credentials] getSessionCredentials(${sessionId}) - NOT FOUND, using env fallback`);
console.error(`[Credentials] Active sessions: [${Array.from(sessionCredentials.keys()).join(', ')}]`);

View File

@@ -1,14 +1,18 @@
import axios from "axios";
import { LOCAL_SERVER_URL } from "../config/index.js";
import { LOCAL_SERVER_URL, getLocalServerHeaders } from "../config/index.js";
export async function fetchProjectInfo(projectName) {
const response = await axios.get(`${LOCAL_SERVER_URL}/api/mcp/project-info`, {
params: { project: projectName }
const params = typeof projectName === "string" ? { project: projectName } : (projectName || {});
const response = await axios.get(`${LOCAL_SERVER_URL}/api/project-info`, {
params,
headers: getLocalServerHeaders(),
});
return response.data; // { success, web_url, token, tokenHash, domain, project_dir }
}
export async function fetchProjectsList() {
const response = await axios.get(`${LOCAL_SERVER_URL}/api/mcp/projects`);
const response = await axios.get(`${LOCAL_SERVER_URL}/api/mcp/projects`, {
headers: getLocalServerHeaders(),
});
return response.data; // { success, projects: [...] }
}