import { corsJson, preflight, withCors } from "@/lib/cors"; import { handleMcpPost } from "@/lib/mcp/server"; import { authenticateAccessToken } from "@/lib/oauth"; import { serverOrigin } from "@/lib/request-origin"; import { tokenAgentKey } from "@/lib/actor"; import { authenticateToken } from "@/lib/tokens"; /** * The MCP endpoint for agents. Credentials are `Authorization: Bearer …` with an OAuth access token * (see lib/oauth.ts), or an API token for agents that can't do OAuth. Without valid credentials the * 401 points to the OAuth metadata, which is how a client finds out where to sign the user in. */ async function authenticate(request: Request) { const secret = /^Bearer\s+(\S+)$/i.exec(request.headers.get("authorization") ?? "")?.[1]; if (!secret) return null; const grant = await authenticateAccessToken(secret); if (grant) return grant; const token = await authenticateToken(secret); // An API token used here is an agent too, named after the token. return token ? { user: token.user, scope: token.token, agent: { key: tokenAgentKey(token.token.id), name: token.token.name } } : "invalid"; } export async function POST(request: Request) { const origin = await serverOrigin(); const auth = await authenticate(request); if (!auth || auth === "invalid") { const challenge = [ ...(auth === "invalid" ? ['error="invalid_token"', 'error_description="The access token is invalid or expired"'] : []), `resource_metadata="${origin}/.well-known/oauth-protected-resource/mcp"`, 'scope="read write"', ]; return corsJson({ error: auth ? "invalid_token" : "unauthorized", error_description: "Sign in with OAuth, or send an API token as a bearer token." }, 401, { "WWW-Authenticate": `Bearer ${challenge.join(", ")}`, }); } return withCors(await handleMcpPost(request, { ...auth, origin })); } // Every answer comes back in the POST's response, so there is no event stream to open, and no // session to end. const notStreaming = () => withCors(new Response("This MCP server answers every request directly. Send JSON-RPC messages with POST.\n", { status: 405, headers: { Allow: "POST" } })); export const GET = notStreaming; export const DELETE = notStreaming; export const OPTIONS = preflight;