93 lines
3.2 KiB
JavaScript
93 lines
3.2 KiB
JavaScript
/**
|
|
* 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 website = process.env.ACAI_WEBSITE || "";
|
|
const webUrl = process.env.ACAI_WEB_URL || "";
|
|
const derivedForgeHost = (() => {
|
|
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;
|
|
const acaiFilePath = projectDir ? path.join(projectDir, ".acai") : "";
|
|
|
|
// 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})`);
|