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;