Skip to content

Commit 10020a0

Browse files
committed
fix(web): stop advertising MCP OAuth without entitlement
1 parent 5e1ae96 commit 10020a0

File tree

5 files changed

+29
-9
lines changed

5 files changed

+29
-9
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
- Avoid advertising OAuth support on MCP endpoints if that entitlment is not actually configured.
12+
1013
## [4.15.1] - 2026-03-06
1114

1215
### Fixed

packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { apiHandler } from '@/lib/apiHandler';
2-
import { env } from '@sourcebot/shared';
2+
import { env, hasEntitlement } from '@sourcebot/shared';
33

44
// RFC 8414: OAuth 2.0 Authorization Server Metadata
5-
// @note: we do not gate on entitlements here. That is handled in the /register,
6-
// /token, and /revoke routes.
75
// @see: https://datatracker.ietf.org/doc/html/rfc8414
86
export const GET = apiHandler(async () => {
7+
if (!hasEntitlement('oauth')) {
8+
return Response.json(
9+
{ error: 'not_found', error_description: 'OAuth authorization server metadata is not available on this plan.' },
10+
{ status: 404 }
11+
);
12+
}
13+
914
const issuer = env.AUTH_URL.replace(/\/$/, '');
1015

1116
return Response.json({

packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { apiHandler } from '@/lib/apiHandler';
2-
import { env } from '@sourcebot/shared';
2+
import { env, hasEntitlement } from '@sourcebot/shared';
33
import { NextRequest } from 'next/server';
44

55
// RFC 9728: OAuth 2.0 Protected Resource Metadata (path-specific form)
66
// For a resource at /api/mcp, the well-known URI is /.well-known/oauth-protected-resource/api/mcp.
7-
// @note: we do not gate on entitlements here. That is handled in the /register,
8-
// /token, and /revoke routes.
97
// @see: https://datatracker.ietf.org/doc/html/rfc9728#section-3
108
const PROTECTED_RESOURCES = new Set([
119
'api/mcp'
1210
]);
1311

1412
export const GET = apiHandler(async (_request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) => {
13+
if (!hasEntitlement('oauth')) {
14+
return Response.json(
15+
{ error: 'not_found', error_description: 'OAuth protected resource metadata is not available on this plan.' },
16+
{ status: 404 }
17+
);
18+
}
19+
1520
const { path } = await params;
1621
const resourcePath = path.join('/');
1722

packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/route.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import { apiHandler } from '@/lib/apiHandler';
2-
import { env } from '@sourcebot/shared';
2+
import { env, hasEntitlement } from '@sourcebot/shared';
33

44
// RFC 9728: OAuth 2.0 Protected Resource Metadata
55
// Tells OAuth clients which authorization server protects this resource.
66
// @see: https://datatracker.ietf.org/doc/html/rfc9728
77
export const GET = apiHandler(async () => {
8+
if (!hasEntitlement('oauth')) {
9+
return Response.json(
10+
{ error: 'not_found', error_description: 'OAuth protected resource metadata is not available on this plan.' },
11+
{ status: 404 }
12+
);
13+
}
14+
815
const issuer = env.AUTH_URL.replace(/\/$/, '');
916

1017
return Response.json({

packages/web/src/app/api/(server)/mcp/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { StatusCodes } from 'http-status-codes';
99
import { NextRequest } from 'next/server';
1010
import { sew } from '@/actions';
1111
import { apiHandler } from '@/lib/apiHandler';
12-
import { env } from '@sourcebot/shared';
12+
import { env, hasEntitlement } from '@sourcebot/shared';
1313

1414
// On 401, tell MCP clients where to find the OAuth protected resource metadata (RFC 9728)
1515
// so they can discover the authorization server and initiate the authorization code flow.
@@ -18,7 +18,7 @@ import { env } from '@sourcebot/shared';
1818
// @see: https://datatracker.ietf.org/doc/html/rfc9728
1919
function mcpErrorResponse(error: ServiceError): Response {
2020
const response = serviceErrorResponse(error);
21-
if (error.statusCode === StatusCodes.UNAUTHORIZED) {
21+
if (error.statusCode === StatusCodes.UNAUTHORIZED && hasEntitlement('oauth')) {
2222
const issuer = env.AUTH_URL.replace(/\/$/, '');
2323
response.headers.set(
2424
'WWW-Authenticate',

0 commit comments

Comments
 (0)