fix: plain 401 for MCP OAuth discovery (#74)

* fix: return plain 401 for MCP OAuth discovery and harden auth flow

- Return plain HTTP 401 with WWW-Authenticate header (no JSON-RPC body)
  so Claude Desktop's MCP client can trigger OAuth discovery correctly
- Remove unused apiKey import from authorize route
- Remove stale codeChallengeMethod parameter from oauth-codes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add Retry-After header to 429 rate limit response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-03-21 14:47:47 +01:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 5d66dd6bfc
commit d61670231f
3 changed files with 14 additions and 13 deletions
-2
View File
@@ -160,7 +160,6 @@ export async function POST(request: Request) {
const redirectUri = url.searchParams.get('redirect_uri')
const state = url.searchParams.get('state')
const codeChallenge = url.searchParams.get('code_challenge') || ''
const codeChallengeMethod = url.searchParams.get('code_challenge_method') || 'S256'
if (!redirectUri) {
return NextResponse.json({ error: 'invalid_request' }, { status: 400 })
@@ -193,7 +192,6 @@ export async function POST(request: Request) {
const code = createAuthCode({
userId: user.id,
codeChallenge,
codeChallengeMethod,
redirectUri,
})
+14 -10
View File
@@ -1234,21 +1234,25 @@ export async function handleMcpRequest(request: Request): Promise<Response> {
const token = extractBearerToken(request)
if (!token) {
return new Response(
JSON.stringify(jsonRpcError(null, -32000, 'Authorization required')),
{ status: 401, headers: { 'Content-Type': 'application/json', 'WWW-Authenticate': wwwAuth } }
)
return new Response('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': wwwAuth },
})
}
const authResult = await validateApiKey(token)
if ('error' in authResult) {
const status = authResult.status
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (status === 401) headers['WWW-Authenticate'] = wwwAuth
return new Response(
JSON.stringify(jsonRpcError(null, -32000, authResult.error)),
{ status, headers }
)
if (status === 429) {
return new Response(authResult.error, {
status: 429,
headers: { 'Content-Type': 'text/plain', 'Retry-After': '60' },
})
}
return new Response('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': wwwAuth },
})
}
const { userId } = authResult
-1
View File
@@ -21,7 +21,6 @@ function getEncryptionKey(): Buffer {
export interface AuthCodePayload {
userId: string
codeChallenge: string
codeChallengeMethod: string
redirectUri: string
exp: number
}