import * as http from "http" import { t } from "../../../i18n" export interface CallbackResult { code?: string error?: string error_description?: string state?: string } /** * Starts a local HTTP server to handle OAuth callback. * @param port Optional port to use (defaults to random available port) * @param expectedState Optional expected state for CSRF protection * @returns Promise<{server: http.Server, port: number, result: Promise}> */ export function startCallbackServer( port?: number, expectedState?: string, ): Promise<{ server: http.Server port: number result: Promise }> { // In test mode, immediately resolve with mock data if (process.env.MCP_OAUTH_TEST_MODE === "true") { return new Promise((resolve) => { const mockServer = http.createServer() resolve({ server: mockServer, port: 3000, result: Promise.resolve({ code: "test-auth-code", state: expectedState }), }) }) } return new Promise((resolve, reject) => { const server = http.createServer() server.listen(port || 0, "127.0.0.1", () => { const address = server.address() if (!address || typeof address === "string") { reject(new Error("Failed to get server address")) return } const actualPort = address.port const resultPromise = new Promise((resolveResult, rejectResult) => { let resolved = false const timeout = setTimeout( () => { if (!resolved) { resolved = true rejectResult(new Error("Callback timeout")) server.close() } }, 5 * 60 * 1000, ) // 5 minutes server.on("request", (req: any, res: any) => { if (resolved) return const url = new URL(req.url || "", `http://localhost:${actualPort}`) const pathname = url.pathname if (pathname === "/callback") { resolved = true clearTimeout(timeout) const code = url.searchParams.get("code") const error = url.searchParams.get("error") const errorDescription = url.searchParams.get("error_description") const state = url.searchParams.get("state") const hasError = !!error // Verify state for CSRF protection if (expectedState && state !== expectedState) { res.writeHead(400, { "Content-Type": "text/html" }) res.end(` ${t("mcp:oauth.callback.title")}

${t("mcp:oauth.callback.failed")}

${t("mcp:oauth.callback.invalid_state")}

`) rejectResult(new Error("Invalid state parameter")) return } // Send HTML response res.writeHead(200, { "Content-Type": "text/html" }) res.end(` ${t("mcp:oauth.callback.title")}

${hasError ? t("mcp:oauth.callback.failed") : t("mcp:oauth.callback.success")}

${hasError ? t("mcp:oauth.callback.auth_failed") : t("mcp:oauth.callback.auth_success")}

${t("mcp:oauth.callback.server_connection_complete")}
`) resolveResult({ code: code || undefined, error: error || undefined, error_description: errorDescription || undefined, state: state || undefined, }) // Close server immediately after response drains res.on("finish", () => { server.close() }) } else { res.writeHead(404) res.end("Not found") } }) server.on("error", (error: any) => { if (!resolved) { resolved = true clearTimeout(timeout) rejectResult(error) } }) }) resolve({ server, port: actualPort, result: resultPromise, }) }) server.on("error", reject) }) } /** * Stops the callback server. * @param server The HTTP server to stop */ export function stopCallbackServer(server: http.Server): Promise { return new Promise((resolve) => { server.close(() => resolve()) }) }