From f01add3943666e01e2d5d13124300ca7264d8e87 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 4 Apr 2026 09:24:10 +0200 Subject: [PATCH] feat: add Bearer token auth to MCP alongside API key auth Co-Authored-By: Claude Sonnet 4.6 --- src/server/mcp/index.ts | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/server/mcp/index.ts b/src/server/mcp/index.ts index 0ec0422..214b42e 100644 --- a/src/server/mcp/index.ts +++ b/src/server/mcp/index.ts @@ -4,6 +4,7 @@ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/ import { Hono } from "hono"; import { db as prodDb } from "../../db/index.ts"; import { getUserCount, verifyApiKey } from "../services/auth.service.ts"; +import { verifyAccessToken } from "../services/oauth.service.ts"; import { getCollectionSummary } from "./resources/collection.ts"; import { categoryToolDefinitions, @@ -88,20 +89,36 @@ export const mcpRoutes = new Hono(); // Auth middleware for all MCP requests mcpRoutes.use("/*", async (c, next) => { const db = c.get("db") ?? prodDb; - const apiKey = c.req.header("X-API-Key"); - // Require API key when auth is configured (users exist) - if (getUserCount(db) > 0) { - if (!apiKey) { - return c.json({ error: "API key required" }, 401); - } - const valid = await verifyApiKey(db, apiKey); - if (!valid) { - return c.json({ error: "Invalid API key" }, 401); - } + // Skip auth if no users exist + if (getUserCount(db) <= 0) { + return next(); } - return next(); + // Try Bearer token first (OAuth) + const authHeader = c.req.header("Authorization"); + if (authHeader?.startsWith("Bearer ")) { + const token = authHeader.slice(7); + if (verifyAccessToken(db, token)) { + return next(); + } + return c.json({ error: "invalid_token" }, 401); + } + + // Try API key (existing flow) + const apiKey = c.req.header("X-API-Key"); + if (apiKey) { + const valid = await verifyApiKey(db, apiKey); + if (valid) { + return next(); + } + return c.json({ error: "Invalid API key" }, 401); + } + + // No auth provided — return 401 with WWW-Authenticate to trigger OAuth flow + return c.text("Unauthorized", 401, { + "WWW-Authenticate": 'Bearer resource_metadata="/.well-known/oauth-authorization-server"', + }); }); mcpRoutes.post("/", async (c) => {