import { z } from "zod"; import { sessionCredentials, setMcpSessionCredentials } from "../../auth/credentials.js"; import { withAuthParams } from "../helpers/authSchema.js"; import { fetchProjectInfo } from "../../auth/localClient.js"; import { resolveCurrentProjectDir } from "../files/helpers.js"; import { resolveCurrentAcaiUser } from "../helpers/sessionHelpers.js"; import { getCurrentSessionId } from "../../utils/sessionContext.js"; export function registerAuthTools(server) { server.tool( "refresh_acai_token", `Refresh the Acai JWT token when it has expired (403 "Token no vĂ¡lido" errors). Delegates to the Python server which detects expiration, renews the token against the webservice if needed, persists the updated .acai file and returns fresh credentials. Use this tool when any other tool fails with a 403 token error.`, withAuthParams({}), { readOnlyHint: false, destructiveHint: false }, async (_args, extra) => { try { const projectDir = resolveCurrentProjectDir(); if (!projectDir) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: "Project dir no disponible en esta sesion" }) }], isError: true, }; } const acaiUser = resolveCurrentAcaiUser(); // Delegamos al Python que ya gestiona expiracion + refresh + persistencia let info; try { info = await fetchProjectInfo({ project_dir: projectDir }, acaiUser); } catch (e) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: `Token refresh failed: ${e.message}` }) }], isError: true, }; } if (!info?.success) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: info?.error || "Project info resolution failed" }) }], isError: true, }; } // Comparamos token previo para saber si hubo renovacion const mcpSessionId = getCurrentSessionId(); let previousToken = null; if (mcpSessionId) { // Leer creds previas sin tocar lastAccess via interno no expuesto: // usamos sessionCredentials como espejo si existe, sino null. const prev = sessionCredentials.get(mcpSessionId); previousToken = prev?.token || null; } const freshCreds = { token: info.token || "", tokenHash: info.tokenHash || "", website: info.domain || "", web_url: info.web_url || "", api_web_url: info.api_web_url || info.web_url || "", forge_host: info.forge_host || null, project_dir: info.project_dir || projectDir, acai_user: acaiUser || null, profileName: acaiUser || "mcp-session", role: "developer", }; // Persistir en la sesion MCP activa (HTTP multi-tenant) if (mcpSessionId) { setMcpSessionCredentials(mcpSessionId, freshCreds); sessionCredentials.set(mcpSessionId, freshCreds); } // Compatibilidad stdio (cuando extra.sessionId viene del SDK) if (extra?.sessionId) { sessionCredentials.set(extra.sessionId, freshCreds); } const rotated = previousToken && previousToken !== freshCreds.token; return { content: [{ type: "text", text: JSON.stringify({ success: true, message: rotated ? "Token refreshed (rotated by Python)" : "Token refreshed successfully", domain: freshCreds.website, rotated: !!rotated, }, null, 2), }], }; } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: error.message }) }], isError: true, }; } } ); }