feat: add Bearer token auth to MCP alongside API key auth

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 09:24:10 +02:00
parent 1fad25726d
commit f01add3943

View File

@@ -4,6 +4,7 @@ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/
import { Hono } from "hono"; import { Hono } from "hono";
import { db as prodDb } from "../../db/index.ts"; import { db as prodDb } from "../../db/index.ts";
import { getUserCount, verifyApiKey } from "../services/auth.service.ts"; import { getUserCount, verifyApiKey } from "../services/auth.service.ts";
import { verifyAccessToken } from "../services/oauth.service.ts";
import { getCollectionSummary } from "./resources/collection.ts"; import { getCollectionSummary } from "./resources/collection.ts";
import { import {
categoryToolDefinitions, categoryToolDefinitions,
@@ -88,20 +89,36 @@ export const mcpRoutes = new Hono();
// Auth middleware for all MCP requests // Auth middleware for all MCP requests
mcpRoutes.use("/*", async (c, next) => { mcpRoutes.use("/*", async (c, next) => {
const db = c.get("db") ?? prodDb; const db = c.get("db") ?? prodDb;
const apiKey = c.req.header("X-API-Key");
// Require API key when auth is configured (users exist) // Skip auth if no users exist
if (getUserCount(db) > 0) { if (getUserCount(db) <= 0) {
if (!apiKey) { return next();
return c.json({ error: "API key required" }, 401);
}
const valid = await verifyApiKey(db, apiKey);
if (!valid) {
return c.json({ error: "Invalid API key" }, 401);
}
} }
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) => { mcpRoutes.post("/", async (c) => {