diff --git a/check-oauth.sh b/check-oauth.sh new file mode 100755 index 0000000..dd9886e --- /dev/null +++ b/check-oauth.sh @@ -0,0 +1,8 @@ +#!/bin/bash +docker exec gearbox bun -e " +const{Database}=require('bun:sqlite'); +const db=new Database('./data/gearbox.db'); +console.log('CLIENTS:', JSON.stringify(db.query('SELECT * FROM oauth_clients ORDER BY id DESC LIMIT 5').all(), null, 2)); +console.log('CODES:', JSON.stringify(db.query('SELECT id,client_id,used,expires_at FROM oauth_codes ORDER BY id DESC LIMIT 5').all(), null, 2)); +console.log('TOKENS:', JSON.stringify(db.query('SELECT id,client_id,expires_at FROM oauth_tokens ORDER BY id DESC LIMIT 5').all(), null, 2)); +" diff --git a/src/server/mcp/index.ts b/src/server/mcp/index.ts index 6235cc3..1ec4ab0 100644 --- a/src/server/mcp/index.ts +++ b/src/server/mcp/index.ts @@ -115,10 +115,12 @@ mcpRoutes.use("/*", async (c, next) => { return c.json({ error: "Invalid API key" }, 401); } - // No auth provided — return 401 with WWW-Authenticate to trigger OAuth flow + // No auth provided — return 401 with WWW-Authenticate to trigger OAuth flow (RFC 9728) + const baseUrl = ( + process.env.GEARBOX_URL || new URL(c.req.url).origin + ).replace(/\/$/, ""); return c.text("Unauthorized", 401, { - "WWW-Authenticate": - 'Bearer resource_metadata="/.well-known/oauth-authorization-server"', + "WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource"`, }); }); diff --git a/src/server/routes/oauth.ts b/src/server/routes/oauth.ts index 87a4ac9..d2b1927 100644 --- a/src/server/routes/oauth.ts +++ b/src/server/routes/oauth.ts @@ -84,6 +84,16 @@ function renderLoginForm(params: { export const wellKnownRoute = new Hono(); +// Protected Resource Metadata (RFC 9728) — Claude fetches this first after 401 +wellKnownRoute.get("/oauth-protected-resource", (c) => { + const baseUrl = getBaseUrl(c); + return c.json({ + resource: `${baseUrl}/mcp`, + authorization_servers: [baseUrl], + }); +}); + +// OAuth Authorization Server Metadata (RFC 8414) — Claude fetches this second wellKnownRoute.get("/oauth-authorization-server", (c) => { const baseUrl = getBaseUrl(c); return c.json({