diff --git a/src/server.ts b/src/server.ts index 1767f53..bd77b85 100644 --- a/src/server.ts +++ b/src/server.ts @@ -43,6 +43,38 @@ async function startLogin(req: IncomingMessage, res: ServerResponse, rawSearch: } } +// Better Auth redirects here when the client sends prompt=consent (which forces +// the consent screen even with skipConsent). For a personal MCP connector the +// login *is* the authorization, so auto-approve: call the consent endpoint with +// the signed oauth_query + the user's session, then 302 to the client callback. +async function handleConsent(req: IncomingMessage, res: ServerResponse, rawSearch: string) { + const headers = new Headers(); + if (req.headers.cookie) headers.set("cookie", req.headers.cookie); + const oauthQuery = rawSearch.startsWith("?") ? rawSearch.slice(1) : rawSearch; + try { + const result = await auth.api.oauth2Consent({ + body: { accept: true, oauth_query: oauthQuery }, + headers, + returnHeaders: true, + }); + const setCookie = result.headers.getSetCookie(); + if (setCookie.length) res.setHeader("set-cookie", setCookie); + const target = (result.response as { url?: string } | null)?.url; + if (!target) { + res.writeHead(500, { "content-type": "text/plain" }); + res.end("Consent failed."); + return; + } + res.writeHead(302, { location: target }); + res.end(); + } catch (err) { + // eslint-disable-next-line no-console + console.error("[consent] oauth2Consent failed", err); + res.writeHead(500, { "content-type": "text/plain" }); + res.end("Consent error."); + } +} + const server = createServer((req, res) => { const url = new URL(req.url ?? "/", "http://localhost"); if (url.pathname !== "/healthz") { @@ -60,6 +92,10 @@ const server = createServer((req, res) => { void startLogin(req, res, url.search); return; } + if (url.pathname === "/consent" && req.method === "GET") { + void handleConsent(req, res, url.search); + return; + } // Everything else -> Better Auth (async handler). void handler(req, res); });