From 18acb4f71bcd928b7c5d6e9d6373b2a5b84b82be Mon Sep 17 00:00:00 2001 From: scot Date: Sat, 18 Jul 2026 07:21:01 +0000 Subject: [PATCH] mcp: accept ?api_key= query param (for MCP hosts without custom-header support, e.g. claude.ai custom connectors) --- apps/api/src/mcp.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/api/src/mcp.ts b/apps/api/src/mcp.ts index 8874ff2..77071b5 100644 --- a/apps/api/src/mcp.ts +++ b/apps/api/src/mcp.ts @@ -5,7 +5,7 @@ import { z } from "zod"; import { pool } from "./db.js"; import { guideMarkdown } from "./guide.js"; import { renderVideo } from "./video.js"; -import { authFromHeader, type AuthedKey, type Scope } from "./auth.js"; +import { authFromHeader, lookupKey, type AuthedKey, type Scope } from "./auth.js"; import { randomBytes } from "node:crypto"; /** @@ -323,7 +323,13 @@ function buildServer(key: AuthedKey): McpServer { export async function mcpRoutes(app: FastifyInstance): Promise { // Stateless Streamable HTTP: fresh server+transport per request, auth per request. app.post("/mcp", async (req, reply) => { - const key = await authFromHeader(req.headers.authorization); + // Auth: Authorization header OR ?api_key= query param. The query-param form + // exists for MCP hosts whose connector UI cannot set custom headers + // (e.g. claude.ai custom connectors where header auth is still beta). + const q = (req.query as { api_key?: string }) ?? {}; + const key = q.api_key + ? await lookupKey(q.api_key) + : await authFromHeader(req.headers.authorization); if (!key) return reply.code(401).send({ error: "invalid or missing API key" }); const server = buildServer(key); const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });