diff --git a/apps/nextjs/src/app/api/mcp/[transport]/route.ts b/apps/nextjs/src/app/api/mcp/[transport]/route.ts index 505260adc..6d4635b10 100644 --- a/apps/nextjs/src/app/api/mcp/[transport]/route.ts +++ b/apps/nextjs/src/app/api/mcp/[transport]/route.ts @@ -273,7 +273,7 @@ const handler = async (req: NextRequest) => { hint: "Authenticate with an ApiKey header or via OAuth at /.well-known/oauth-authorization-server", }, { - "WWW-Authenticate": `Bearer resource_metadata="${baseUrl}${resourcePath}/.well-known/oauth-protected-resource"`, + "WWW-Authenticate": `Bearer resource_metadata="${baseUrl}/.well-known/oauth-protected-resource${resourcePath}"`, }, ); } diff --git a/apps/nextjs/src/app/well-known/[...path]/route.ts b/apps/nextjs/src/app/well-known/[...path]/route.ts new file mode 100644 index 000000000..f1ade29a7 --- /dev/null +++ b/apps/nextjs/src/app/well-known/[...path]/route.ts @@ -0,0 +1,48 @@ +import type { NextRequest } from "next/server"; + +import { extractBaseUrlFromHeaders } from "@homarr/common"; + +const corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Headers": "*", +}; + +export async function GET(_req: NextRequest, props: { params: Promise<{ path: string[] }> }) { + const [kind, ...rest] = (await props.params).path; + const baseUrl = extractBaseUrlFromHeaders(_req.headers); + + if (kind === "oauth-authorization-server") { + return Response.json( + { + issuer: baseUrl, + authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`, + token_endpoint: `${baseUrl}/api/mcp/oauth/token`, + registration_endpoint: `${baseUrl}/api/mcp/oauth/register`, + response_types_supported: ["code"], + grant_types_supported: ["authorization_code"], + code_challenge_methods_supported: ["S256"], + token_endpoint_auth_methods_supported: ["none"], + scopes_supported: ["mcp:tools"], + }, + { headers: corsHeaders }, + ); + } + + if (kind === "oauth-protected-resource") { + const resourcePath = rest.join("/"); + return Response.json( + { + resource: resourcePath ? `${baseUrl}/${resourcePath}` : baseUrl, + authorization_servers: [baseUrl], + }, + { headers: corsHeaders }, + ); + } + + return Response.json({ error: "not_found" }, { status: 404, headers: corsHeaders }); +} + +export async function OPTIONS() { + return new Response(null, { status: 204, headers: corsHeaders }); +} diff --git a/apps/nextjs/src/app/well-known/oauth-authorization-server/route.ts b/apps/nextjs/src/app/well-known/oauth-authorization-server/route.ts deleted file mode 100644 index c400b4020..000000000 --- a/apps/nextjs/src/app/well-known/oauth-authorization-server/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { headers } from "next/headers"; - -import { extractBaseUrlFromHeaders } from "@homarr/common"; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, OPTIONS", - "Access-Control-Allow-Headers": "*", -}; - -export async function GET() { - const baseUrl = extractBaseUrlFromHeaders(await headers()); - - return Response.json( - { - issuer: baseUrl, - authorization_endpoint: `${baseUrl}/api/mcp/oauth/authorize`, - token_endpoint: `${baseUrl}/api/mcp/oauth/token`, - registration_endpoint: `${baseUrl}/api/mcp/oauth/register`, - response_types_supported: ["code"], - grant_types_supported: ["authorization_code"], - code_challenge_methods_supported: ["S256"], - token_endpoint_auth_methods_supported: ["none"], - scopes_supported: ["mcp:tools"], - }, - { headers: corsHeaders }, - ); -} - -export async function OPTIONS() { - return new Response(null, { status: 204, headers: corsHeaders }); -} diff --git a/apps/nextjs/src/app/well-known/oauth-protected-resource/route.ts b/apps/nextjs/src/app/well-known/oauth-protected-resource/route.ts deleted file mode 100644 index eb5f22e5a..000000000 --- a/apps/nextjs/src/app/well-known/oauth-protected-resource/route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { headers } from "next/headers"; - -import { extractBaseUrlFromHeaders } from "@homarr/common"; - -const corsHeaders = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, OPTIONS", - "Access-Control-Allow-Headers": "*", -}; - -export async function GET() { - const baseUrl = extractBaseUrlFromHeaders(await headers()); - - return Response.json( - { - resource: `${baseUrl}/api/mcp`, - authorization_servers: [baseUrl], - }, - { headers: corsHeaders }, - ); -} - -export async function OPTIONS() { - return new Response(null, { status: 204, headers: corsHeaders }); -}