Añadido el modo producción / test
This commit is contained in:
@@ -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(', ')}]`);
|
||||
|
||||
@@ -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: [...] }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user