Skip to content

fix(web): stop advertising MCP OAuth without entitlement#985

Merged
brendan-kellam merged 5 commits intosourcebot-dev:mainfrom
brianphillips:oauth-enterprise-only
Mar 6, 2026
Merged

fix(web): stop advertising MCP OAuth without entitlement#985
brendan-kellam merged 5 commits intosourcebot-dev:mainfrom
brianphillips:oauth-enterprise-only

Conversation

@brianphillips
Copy link
Contributor

@brianphillips brianphillips commented Mar 6, 2026

What

Fix MCP OAuth discovery so instances without the oauth entitlement do not advertise OAuth support to clients.

  • Gate the MCP WWW-Authenticate OAuth hint behind hasEntitlement('oauth')
  • Return 404 from the OAuth well-known metadata endpoints when OAuth is not licensed
  • Document the fix in the changelog

Problem

Non-licensed instances were still exposing OAuth discovery metadata and OAuth-specific authentication hints to MCP clients. That let clients infer OAuth support even though the OAuth flow was not actually available on that plan.

Solution

Gate the OAuth advertisement paths instead of only gating the token flow.

  • /api/mcp only includes the OAuth WWW-Authenticate hint when the oauth entitlement is active
  • /.well-known/oauth-protected-resource returns 404 without the entitlement
  • /.well-known/oauth-authorization-server returns 404 without the entitlement
  • Add a redirect for /register to /api/ee/oauth/register to handle non-compliant mcp clients
  • Existing OAuth token/register/revoke route enforcement stays unchanged, and MCP API-key auth remains available

Testing

  • yarn workspace @sourcebot/web test ✅ (258 tests passed)

Checklist

  • Bug resolved
  • Regression tests added
  • No side effects introduced in the existing web test suite

🤖 Agent Plan

Expand full agent plan
  1. Inspect the MCP route and OAuth discovery endpoints to confirm where OAuth support is advertised.
  2. Gate the MCP WWW-Authenticate hint and OAuth well-known metadata routes on hasEntitlement('oauth').
  3. Leave the existing OAuth token/register/revoke entitlement checks in place so enforcement behavior stays consistent.
  4. Update the changelog to note the fix.
  5. Run the relevant @sourcebot/web test suite to validate the change before opening the PR.
  6. Create the PR against the repository default branch with labels that match the change type.

Claude Code:
image

Codex:
image

Cursor:
image

Summary by CodeRabbit

  • Bug Fixes

    • OAuth endpoints and metadata are only advertised and reachable when OAuth entitlement is enabled; lacking entitlement now returns not-found or appropriate error responses.
    • WWW-Authenticate header and OAuth error messages no longer expose unsupported auth details and use a unified user-facing message.
  • Documentation

    • MCP docs clarified: anonymous access bypasses OAuth/API key requirements when enabled.
  • Chore

    • Added client-registration fallback rewrite, adjusted proxy exclusions, removed an obsolete protected-resource route, and unified not-found responses for dynamic paths.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 6, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3742977d-fc92-4d5b-9336-0881274550af

📥 Commits

Reviewing files that changed from the base of the PR and between b230c86 and 37f3bbe.

📒 Files selected for processing (1)
  • packages/web/src/app/api/(server)/[...slug]/route.ts

Walkthrough

Adds entitlement checks and a shared error message constant so OAuth discovery and token/revoke/register endpoints only advertise or return OAuth metadata when the instance has the OAuth entitlement; also removes an older protected-resource route, adds a generic 404 catch-all API route, updates proxy/rewrite for /register, and adjusts docs.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased Fixed entry noting OAuth should not be advertised for MCP endpoints unless entitlement is configured.
OAuth well-known / protected-resource
packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts, packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/route.ts
Gate well-known metadata endpoints with hasEntitlement('oauth'); return 404 using OAUTH_NOT_SUPPORTED_ERROR_MESSAGE when missing; removed the legacy protected-resource route implementation.
OAuth flows (token, revoke, register)
packages/web/src/app/api/(server)/ee/oauth/token/route.ts, packages/web/src/app/api/(server)/ee/oauth/revoke/route.ts, packages/web/src/app/api/(server)/ee/oauth/register/route.ts
Replace hard-coded "OAuth not supported" strings with OAUTH_NOT_SUPPORTED_ERROR_MESSAGE; entitlement checks remain and determine 403/404 responses.
MCP / WWW-Authenticate handling
packages/web/src/app/api/(server)/mcp/route.ts
Only set WWW-Authenticate OAuth header on UNAUTHORIZED when OAuth entitlement is present to avoid leaking OAuth metadata.
Proxy / Routing changes
packages/web/next.config.mjs, packages/web/src/proxy.ts
Added rewrite /register → /api/ee/oauth/register and excluded /register from middleware matcher so Dynamic Client Registration requests reach the register endpoint.
Generic not-found route
packages/web/src/app/api/(server)/[...slug]/route.ts
Added a catch-all handler that returns a uniform 404 ServiceError JSON for GET/POST/PUT/PATCH/DELETE on the dynamic slug path.
Constants
packages/web/src/ee/features/oauth/constants.ts
Added OAUTH_NOT_SUPPORTED_ERROR_MESSAGE constant used across OAuth-related endpoints.
Docs
docs/docs/features/mcp-server.mdx
Documented that anonymous access bypasses OAuth/API key authorization when enabled and updated Authorization messaging accordingly.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Server as Web Server (API routes)
  participant Ent as Entitlement Checker (hasEntitlement)

  Client->>Server: GET /.well-known/oauth-authorization-server
  Server->>Ent: hasEntitlement('oauth')?
  Ent-->>Server: yes / no
  alt entitlement present
    Server-->>Client: 200 JSON (OAuth metadata)
  else entitlement missing
    Server-->>Client: 404 JSON (error_description = OAUTH_NOT_SUPPORTED_ERROR_MESSAGE)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • msukkari
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective: preventing OAuth from being advertised to MCP clients without the proper entitlement. The changes across multiple files consistently implement this gating mechanism.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/web/src/app/api/(server)/mcp/route.ts (1)

21-21: Add regression coverage for the entitlement-gated auth hint.

This branch changes public MCP auth discovery behavior, but the PR doesn't add coverage for it. Please add a small test matrix that asserts the WWW-Authenticate header is present on 401s when oauth is entitled and absent when it is not.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/api/`(server)/mcp/route.ts at line 21, Add regression
tests that cover the entitlement-gated auth hint: create a small test matrix
that sends a request triggering the same 401 branch (StatusCodes.UNAUTHORIZED)
in the MCP route handler and toggles hasEntitlement('oauth') true/false; when
hasEntitlement('oauth') is true assert the response includes a WWW-Authenticate
header with the expected auth hint, and when false assert the WWW-Authenticate
header is absent. Stub/mocking: mock hasEntitlement to return true/false for
each case and invoke the route handler (the exported handler in route.ts) or
perform an integration request against it; ensure the assertions check
response.status === 401 and presence/absence of
response.headers['www-authenticate'] accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Around line 10-11: Prefix the existing changelog entry "Avoid advertising
OAuth support on MCP endpoints if that entitlement is not actually configured.
[`#985`](https://github.com/sourcebot-dev/sourcebot/pull/985)" with "[EE]" to mark
it as enterprise-only; update the line to begin with "### Fixed" followed by "-
[EE] Avoid advertising OAuth support on MCP endpoints if that entitlement is not
actually configured. [`#985`](...)" (keep the PR link unchanged) so the entry
follows the enterprise-only convention.

---

Nitpick comments:
In `@packages/web/src/app/api/`(server)/mcp/route.ts:
- Line 21: Add regression tests that cover the entitlement-gated auth hint:
create a small test matrix that sends a request triggering the same 401 branch
(StatusCodes.UNAUTHORIZED) in the MCP route handler and toggles
hasEntitlement('oauth') true/false; when hasEntitlement('oauth') is true assert
the response includes a WWW-Authenticate header with the expected auth hint, and
when false assert the WWW-Authenticate header is absent. Stub/mocking: mock
hasEntitlement to return true/false for each case and invoke the route handler
(the exported handler in route.ts) or perform an integration request against it;
ensure the assertions check response.status === 401 and presence/absence of
response.headers['www-authenticate'] accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f59e785f-7324-4e4b-af62-386c8d9cb598

📥 Commits

Reviewing files that changed from the base of the PR and between 5e1ae96 and 559b0ad.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/route.ts
  • packages/web/src/app/api/(server)/mcp/route.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
docs/docs/features/mcp-server.mdx (1)

31-33: Use a relative path for internal documentation links.

Line 32 uses a full URL while line 280 uses a relative path for the same link. For consistency and maintainability, prefer relative paths for internal documentation links.

📝 Suggested fix
 <Note>
-    If [anonymous access](https://docs.sourcebot.dev/docs/configuration/auth/access-settings#anonymous-access) is enabled on your Sourcebot instance, no OAuth token or API key is required. You can connect directly to the MCP endpoint without any authorization.
+    If [anonymous access](/docs/configuration/auth/access-settings#anonymous-access) is enabled on your Sourcebot instance, no OAuth token or API key is required. You can connect directly to the MCP endpoint without any authorization.
 </Note>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/docs/features/mcp-server.mdx` around lines 31 - 33, Replace the absolute
internal docs URL in the Note block with the relative path used elsewhere:
change the full link
"https://docs.sourcebot.dev/docs/configuration/auth/access-settings#anonymous-access"
to the relative route
"/docs/configuration/auth/access-settings#anonymous-access" so the Note in
docs/features/mcp-server.mdx uses the same relative internal link format as line
~280.
packages/web/src/app/api/(server)/[...slug]/route.ts (1)

1-13: Prefer serviceErrorResponse over open-coding the JSON error payload.

This duplicates the standard API error serialization logic that already exists in @/lib/serviceError. Reusing the helper keeps the 404 shape/status handling consistent with the rest of the API and avoids another spot to update if ServiceError evolves.

♻️ Suggested cleanup
-import { ErrorCode } from "@/lib/errorCodes"
-import { ServiceError } from "@/lib/serviceError"
+import { ErrorCode } from "@/lib/errorCodes"
+import { serviceErrorResponse } from "@/lib/serviceError"
 import { StatusCodes } from "http-status-codes"
-import { NextResponse } from "next/server"
 
-// Repeat for other methods or use a handler:
-const handler = () => NextResponse.json(
-    {
+const handler = () =>
+  serviceErrorResponse({
         statusCode: StatusCodes.NOT_FOUND,
         errorCode: ErrorCode.NOT_FOUND,
         message: "This API endpoint does not exist",
-    } satisfies ServiceError,
-    { status: 404 }
-  )
+  })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/app/api/`(server)/[...slug]/route.ts around lines 1 - 13,
Replace the open-coded JSON error response in the fallback handler with the
shared helper: call serviceErrorResponse(...) instead of NextResponse.json(...)
so the 404 payload uses the canonical ServiceError shape; locate the handler
function in this file and replace its body to invoke serviceErrorResponse with
StatusCodes.NOT_FOUND, ErrorCode.NOT_FOUND and the same message so serialization
and status are consistent with the rest of the API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/docs/features/mcp-server.mdx`:
- Around line 31-33: Replace the absolute internal docs URL in the Note block
with the relative path used elsewhere: change the full link
"https://docs.sourcebot.dev/docs/configuration/auth/access-settings#anonymous-access"
to the relative route
"/docs/configuration/auth/access-settings#anonymous-access" so the Note in
docs/features/mcp-server.mdx uses the same relative internal link format as line
~280.

In `@packages/web/src/app/api/`(server)/[...slug]/route.ts:
- Around line 1-13: Replace the open-coded JSON error response in the fallback
handler with the shared helper: call serviceErrorResponse(...) instead of
NextResponse.json(...) so the 404 payload uses the canonical ServiceError shape;
locate the handler function in this file and replace its body to invoke
serviceErrorResponse with StatusCodes.NOT_FOUND, ErrorCode.NOT_FOUND and the
same message so serialization and status are consistent with the rest of the
API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54b877d9-0ef0-4006-9b3c-3e12825da95d

📥 Commits

Reviewing files that changed from the base of the PR and between 559b0ad and b230c86.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/docs/features/mcp-server.mdx
  • packages/web/next.config.mjs
  • packages/web/src/app/api/(server)/[...slug]/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-authorization-server/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/[...path]/route.ts
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/register/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/revoke/route.ts
  • packages/web/src/app/api/(server)/ee/oauth/token/route.ts
  • packages/web/src/ee/features/oauth/constants.ts
  • packages/web/src/proxy.ts
💤 Files with no reviewable changes (1)
  • packages/web/src/app/api/(server)/ee/.well-known/oauth-protected-resource/route.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web/src/ee/features/oauth/constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

@brendan-kellam brendan-kellam merged commit 2bc272c into sourcebot-dev:main Mar 6, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants