Claude.ai's connector setup derives the protected-resource metadata URL from the MCP server URL and fetches it before any 401 challenge: /.well-known/oauth-protected-resource/api/extensions/ext/mcp-server/mcp /api/extensions/ext/mcp-server/mcp/.well-known/oauth-protected-resource Both were 404 (only the root document our WWW-Authenticate header points at existed), which the dialog reported as "Authorization with Accounted failed". One shared builder now serves all three locations; the path-based route answers 404 for any path other than the MCP endpoint. Claude-Session: https://claude.ai/code/session_018wCdzRTatKiDByKB8hCNT6 Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
28 lines
1.0 KiB
TypeScript
28 lines
1.0 KiB
TypeScript
import { NextResponse } from 'next/server'
|
|
import {
|
|
MCP_RESOURCE_PATH,
|
|
buildProtectedResourceMetadata,
|
|
} from '@/lib/auth/protected-resource-metadata'
|
|
|
|
/**
|
|
* RFC 9728 §3.1 path-based Protected Resource Metadata:
|
|
* `/.well-known/oauth-protected-resource{resource-path}`.
|
|
*
|
|
* Claude.ai's connector setup derives this URL from the MCP server URL and
|
|
* fetches it before any 401 challenge; without it the dialog reports
|
|
* "Authorization with Accounted failed". Only the MCP endpoint is a protected
|
|
* resource here, so every other path is a 404 rather than a generic answer
|
|
* that would advertise resources this server does not serve.
|
|
*/
|
|
export async function GET(
|
|
request: Request,
|
|
context: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
const { path } = await context.params
|
|
const resourcePath = '/' + (path ?? []).join('/')
|
|
if (resourcePath !== MCP_RESOURCE_PATH) {
|
|
return NextResponse.json({ error: 'not_found' }, { status: 404 })
|
|
}
|
|
return NextResponse.json(buildProtectedResourceMetadata(request))
|
|
}
|