/** * Acai Code MCP Server - Stdio Entry Point * * Used when Claude Code launches the MCP server directly via .mcp.json. * Reads credentials from .acai file on each tool call (auto-refresh on token renewal). */ import fs from "fs"; import path from "path"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createMcpServer } from "./server.js"; import { registerPrompts } from "./prompts/index.js"; import { registerTools } from "./tools/index.js"; import { registerResources } from "./resources/index.js"; import { sessionCredentials } from "./auth/credentials.js"; // Create server instance const server = createMcpServer(); registerPrompts(server); registerTools(server); registerResources(server); // Static env vars (web_url and website don't change, token does) const projectDir = process.env.ACAI_PROJECT_DIR || ""; const acaiFilePath = projectDir ? path.join(projectDir, ".acai") : ""; // Read .acai once at startup for URL fallbacks let acaiFileData = {}; if (acaiFilePath) { try { acaiFileData = JSON.parse(fs.readFileSync(acaiFilePath, "utf-8")); } catch { /* ignore - fall back to env vars */ } } const website = process.env.ACAI_WEBSITE || acaiFileData.domain || ""; const webUrl = process.env.ACAI_WEB_URL || acaiFileData.local_web_url || ""; const derivedForgeHost = (() => { // First check .acai for explicit forge host if (acaiFileData.local_forge_host) return acaiFileData.local_forge_host; if (!webUrl) return ""; try { const parsed = new URL(webUrl); return parsed.hostname.includes("forge.acaisuite.com") ? parsed.host : ""; } catch { return ""; } })(); const apiWebUrl = process.env.ACAI_API_WEB_URL || (derivedForgeHost ? "http://web:80/" : webUrl); const forgeHost = process.env.ACAI_FORGE_HOST || derivedForgeHost; // Read fresh credentials from .acai file function readFreshCredentials() { let token = process.env.ACAI_TOKEN || ""; let tokenHash = process.env.ACAI_TOKEN_HASH || ""; // If .acai file exists, read fresh token from disk (renewed by Python server) if (acaiFilePath) { try { const data = JSON.parse(fs.readFileSync(acaiFilePath, "utf-8")); if (data.token) token = data.token; if (data.tokenHash) tokenHash = data.tokenHash; } catch { // Fall back to env vars if .acai can't be read } } return { token, tokenHash, website, web_url: webUrl, api_web_url: apiWebUrl, forge_host: forgeHost, profileName: "stdio", role: "developer", }; } if (!webUrl) { console.error("[MCP stdio] WARNING: No ACAI_WEB_URL in environment. Tools will fail."); } // Set initial credentials sessionCredentials.set("_default", readFreshCredentials()); // Intercept tool calls to refresh credentials from .acai before each call const _origSetHandler = server.server.setRequestHandler; server.server.setRequestHandler = (schema, handler) => { return _origSetHandler.call(server.server, schema, async (request, extra) => { // Re-read .acai on every tool call to pick up renewed tokens const freshCreds = readFreshCredentials(); sessionCredentials.set("_default", freshCreds); if (extra?.sessionId) { sessionCredentials.set(extra.sessionId, freshCreds); } return handler(request, extra); }); }; // Connect via stdio transport const transport = new StdioServerTransport(); await server.connect(transport); console.error(`[MCP stdio] Connected — ${website} → ${webUrl} (project: ${projectDir})`);