Skip to content

feat: MCP OAuth plugin for automatic browser-based authentication#627

Merged
yanekyuk merged 9 commits intoindexnetwork:devfrom
yanekyuk:feat/mcp-oauth-plugin
Apr 3, 2026
Merged

feat: MCP OAuth plugin for automatic browser-based authentication#627
yanekyuk merged 9 commits intoindexnetwork:devfrom
yanekyuk:feat/mcp-oauth-plugin

Conversation

@yanekyuk
Copy link
Copy Markdown
Contributor

@yanekyuk yanekyuk commented Apr 3, 2026

Summary

  • Adds Better Auth mcp plugin (replaces @better-auth/oauth-provider) to handle full MCP OAuth 2.0 PKCE flow natively
  • Adds WWW-Authenticate: Bearer resource_metadata=... header to MCP 401 responses so Claude Code auto-discovers the OAuth server
  • Adds /login frontend page as an OAuth bridge: redirects authenticated users back to the authorize endpoint, shows login modal for unauthenticated users
  • Adds plugin/.mcp.json so installing the plugin registers the MCP server automatically with no manual token config
  • Adds Drizzle schema tables (oauth_application, oauth_access_token, oauth_consent) and migration 0033_add_mcp_oauth_tables
  • Removes unused @better-auth/oauth-provider dependency

Flow

User installs plugin → Claude Code reads plugin/.mcp.json
  → registers MCP server at https://protocol.index.network/mcp
  → hits /mcp unauthenticated → 401 + WWW-Authenticate header
  → fetches /.well-known/oauth-protected-resource → discovers /api/auth/mcp/authorize
  → dynamic client registration → browser opens /login?...
  → no session → AuthModal → sign in
  → session exists → redirected to /api/auth/mcp/authorize → code → callback
  → Claude Code exchanges code for token → MCP tools available ✓

Test Plan

  • Install plugin in Claude Code — verify MCP server is registered automatically
  • Hit /mcp unauthenticated — verify WWW-Authenticate header is present in 401
  • Full browser OAuth flow: browser opens, login, tools become available
  • Existing JWT Bearer token auth continues to work

Summary by CodeRabbit

  • New Features

    • Added OAuth authentication support via Model Context Protocol (MCP).
    • Introduced browser-based login flow with automatic consent management.
    • Added new login page for OAuth credentials and secure redirection.
    • Registered Index Network as an MCP server endpoint.
  • Documentation

    • Added design specification for OAuth plugin implementation.
  • Chores

    • Updated database schema to support OAuth applications, access tokens, and consent management.
    • Updated authentication configuration for MCP integration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Warning

Rate limit exceeded

@yanekyuk has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 15 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 15 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a875cac-7cc5-4c7a-935e-cbc2b11c9106

📥 Commits

Reviewing files that changed from the base of the PR and between 94e25b8 and 4d8a165.

⛔ Files ignored due to path filters (1)
  • protocol/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • frontend/src/app/login/page.tsx
  • frontend/src/components/AuthModal.tsx
  • frontend/src/routes.tsx
  • plugin/.mcp.json
  • protocol/drizzle/0033_add_mcp_oauth_tables.sql
  • protocol/drizzle/meta/0033_snapshot.json
  • protocol/drizzle/meta/_journal.json
  • protocol/package.json
  • protocol/src/adapters/auth.adapter.ts
  • protocol/src/controllers/mcp.handler.ts
  • protocol/src/lib/betterauth/betterauth.ts
  • protocol/src/main.ts
  • protocol/src/schemas/database.schema.ts
📝 Walkthrough

Walkthrough

This pull request introduces an end-to-end MCP OAuth plugin for Index Network, enabling Claude Code to authenticate via browser-based OAuth. Changes include frontend login routes, backend MCP handler updates with OAuth token validation, Better Auth configuration switching from oauthProvider to mcp plugin, and database schema additions for OAuth application, access token, and consent records.

Changes

Cohort / File(s) Summary
Documentation
.claude/handoff.md, docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md
Removed CLI handoff document; added comprehensive MCP OAuth plugin design specification with infrastructure assumptions and flow details.
Frontend OAuth Flow
frontend/src/app/login/page.tsx, frontend/src/components/AuthModal.tsx, frontend/src/routes.tsx
Added new login bridge page that checks session and routes to OAuth authorize endpoint; extended AuthModal to accept and use callbackURL parameter; registered /login route with lazy import.
Database Migrations & Schema
protocol/drizzle/0033_add_mcp_oauth_tables.sql, protocol/drizzle/meta/0033_snapshot.json, protocol/drizzle/meta/_journal.json, protocol/src/schemas/database.schema.ts
Added four new OAuth-related database tables (oauth_application, oauth_access_token, oauth_consent, plus snapshot and journal entries); exported corresponding Drizzle schema objects with indexes on key lookup columns.
Backend OAuth & Auth Handler
protocol/src/controllers/mcp.handler.ts, protocol/src/adapters/auth.adapter.ts
Extended resolveUserId to handle both JWT and opaque Bearer tokens, with opaque tokens triggering OAuth session lookup via /api/auth/mcp/get-session; added WWW-Authenticate header on 401 responses pointing to OAuth protected-resource metadata.
Better Auth Configuration
protocol/src/lib/betterauth/betterauth.ts, protocol/src/main.ts
Switched from oauthProvider plugin to mcp plugin with dynamic loginPage configuration; added exported APP_URL constant; updated allowlisted routes to replace /oauth2/ with /api/auth/mcp/ and added .well-known/oauth-protected-resource endpoint.
Configuration & Dependencies
plugin/.mcp.json, protocol/package.json
Added new MCP manifest registering index-network server at https://protocol.index.network/mcp; removed @better-auth/oauth-provider dependency.

Sequence Diagram(s)

sequenceDiagram
    actor Claude as Claude Code
    participant Frontend as Frontend<br/>(Login Page)
    participant Backend as Backend<br/>(MCP Handler)
    participant Auth as Better Auth<br/>(OAuth Server)
    participant DB as Database

    Claude->>Backend: GET /mcp (no Bearer token)
    Backend->>Backend: resolveUserId(undefined)
    Backend-->>Claude: 401 + WWW-Authenticate header
    
    Claude->>Backend: Discover OAuth via .well-known
    Backend-->>Claude: OAuth server metadata
    
    Claude->>Frontend: Redirect to /login
    Frontend->>Frontend: Check getSession()
    Frontend-->>Claude: No session found
    Frontend->>Claude: Show AuthModal
    
    Claude->>Auth: POST /sign-in (magic link or OAuth)
    Auth->>DB: Create/verify user
    Auth->>Auth: Issue session
    Auth-->>Frontend: Redirect with session
    
    Frontend->>Auth: GET /authorize (with session)
    Auth->>DB: Create oauth_consent record
    Auth-->>Frontend: Redirect back to callback
    
    Frontend->>Frontend: Extract opaque Bearer token
    Claude->>Backend: GET /mcp (with Bearer token)
    Backend->>Backend: resolveUserId(opaque token)
    Backend->>Auth: GET /api/auth/mcp/get-session
    Auth->>DB: Lookup oauth_access_token
    Auth-->>Backend: Return userId
    Backend->>DB: Process MCP request
    Backend-->>Claude: 200 + MCP response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A hop and a bound through OAuth's town,
Where tokens flow and auth settles down,
Better Auth and MCP dance in line,
Claude Code logs in—now databases shine! ✨
No more handoff notes on the ground,
Just smooth OAuth magic all around! 🔐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding MCP OAuth plugin support for automatic browser-based authentication. It directly relates to the primary changes (Better Auth MCP plugin, WWW-Authenticate header, /login bridge page, and .mcp.json registration).

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@yanekyuk
Copy link
Copy Markdown
Contributor Author

yanekyuk commented Apr 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
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 (4)
protocol/src/lib/betterauth/betterauth.ts (1)

12-14: Consider centralizing APP_URL usage across the codebase.

The APP_URL export is a good addition. However, per context snippets, integration.controller.ts (line 64) and conversation.service.ts (line 220) define the same resolution logic locally. Consider importing this export from betterauth.ts in those files to maintain a single source of truth.

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

In `@protocol/src/lib/betterauth/betterauth.ts` around lines 12 - 14, Several
files duplicate the APP_URL resolution; import and use the single exported
APP_URL from betterauth.ts instead of re-implementing the logic. In
integration.controller.ts and conversation.service.ts remove the local
process.env.FRONTEND_URL || process.env.APP_URL || 'https://index.network'
logic, add an import for APP_URL from the betterauth module, and replace the
local variable usages with the imported APP_URL symbol so the codebase has a
single source of truth.
frontend/src/app/login/page.tsx (1)

24-25: Consider validating VITE_PROTOCOL_URL before redirect.

If VITE_PROTOCOL_URL is undefined or empty, the redirect URL becomes /api/auth/mcp/authorize... which may not resolve correctly depending on the deployment. This could silently fail or redirect to an unintended location.

🔧 Optional validation
       if (data?.session) {
         // Session exists → forward to the MCP authorize endpoint with the original OAuth params
         const protocolUrl = import.meta.env.VITE_PROTOCOL_URL ?? "";
+        if (!protocolUrl) {
+          console.error("VITE_PROTOCOL_URL is not configured");
+          setSessionChecked(true);
+          return;
+        }
         window.location.href = `${protocolUrl}/api/auth/mcp/authorize${window.location.search}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/login/page.tsx` around lines 24 - 25, The redirect uses
import.meta.env.VITE_PROTOCOL_URL (bound to protocolUrl) without validation, so
an empty or undefined value yields an unintended path; update the logic before
setting window.location.href to validate protocolUrl (e.g., check non-empty
string, valid URL or fallback) and construct the target by using a safe base
(protocolUrl if valid, otherwise a configured default or relative safe path)
when assigning window.location.href in the component that performs the redirect.
docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md (1)

12-27: Add language identifier to fenced code block.

The flow diagram code block should specify a language (or use text for plain text) to satisfy markdown linting rules.

📝 Fix markdown lint warning
-```
+```text
 User installs plugin
   → Claude Code reads plugin/.mcp.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md` around lines 12
- 27, The fenced code block showing the OAuth flow is missing a language
identifier; update the opening backticks (```) for that flow diagram to include
a language like "text" (i.e. change ``` to ```text) so the markdown linter is
satisfied—look for the triple-backtick block containing the steps ("User
installs plugin … MCP tools available ✓") and add the language identifier there.
protocol/src/schemas/database.schema.ts (1)

493-498: Consider adding exported types for OAuth tables.

Other tables in this file export both select and insert types (e.g., User/NewUser, Intent/NewIntent). The new OAuth tables don't have corresponding type exports, which could be useful for type safety in the auth adapter.

📝 Add type exports for consistency
// Add after line 498:
export type OAuthApplication = typeof oauthApplications.$inferSelect;
export type NewOAuthApplication = typeof oauthApplications.$inferInsert;
export type OAuthAccessToken = typeof oauthAccessTokens.$inferSelect;
export type NewOAuthAccessToken = typeof oauthAccessTokens.$inferInsert;
export type OAuthConsent = typeof oauthConsents.$inferSelect;
export type NewOAuthConsent = typeof oauthConsents.$inferInsert;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@protocol/src/schemas/database.schema.ts` around lines 493 - 498, The OAuth
tables (oauthApplications, oauthAccessTokens, oauthConsents) are missing
exported select/insert types; add exported type aliases for each table like
ExportType = typeof <table>.$inferSelect and NewExportType = typeof
<table>.$inferInsert to match the pattern used for other tables (e.g.,
opportunities, personalIndexes); specifically add exported types for
oauthApplications, oauthAccessTokens, and oauthConsents (both select and insert)
so the auth adapter can use them for typing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md`:
- Around line 40-42: Update the WWW-Authenticate example in the docs to match
the implementation in mcp.handler.ts: change the code fence to include a
language identifier (```http) and replace the header so it uses
resource_metadata pointing to "/.well-known/oauth-protected-resource" (matching
the oauth-protected-resource used in mcp.handler.ts) instead of
oauth-authorization-server, and remove the realm parameter so the example
exactly mirrors the handler's output.

---

Nitpick comments:
In `@docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md`:
- Around line 12-27: The fenced code block showing the OAuth flow is missing a
language identifier; update the opening backticks (```) for that flow diagram to
include a language like "text" (i.e. change ``` to ```text) so the markdown
linter is satisfied—look for the triple-backtick block containing the steps
("User installs plugin … MCP tools available ✓") and add the language identifier
there.

In `@frontend/src/app/login/page.tsx`:
- Around line 24-25: The redirect uses import.meta.env.VITE_PROTOCOL_URL (bound
to protocolUrl) without validation, so an empty or undefined value yields an
unintended path; update the logic before setting window.location.href to
validate protocolUrl (e.g., check non-empty string, valid URL or fallback) and
construct the target by using a safe base (protocolUrl if valid, otherwise a
configured default or relative safe path) when assigning window.location.href in
the component that performs the redirect.

In `@protocol/src/lib/betterauth/betterauth.ts`:
- Around line 12-14: Several files duplicate the APP_URL resolution; import and
use the single exported APP_URL from betterauth.ts instead of re-implementing
the logic. In integration.controller.ts and conversation.service.ts remove the
local process.env.FRONTEND_URL || process.env.APP_URL || 'https://index.network'
logic, add an import for APP_URL from the betterauth module, and replace the
local variable usages with the imported APP_URL symbol so the codebase has a
single source of truth.

In `@protocol/src/schemas/database.schema.ts`:
- Around line 493-498: The OAuth tables (oauthApplications, oauthAccessTokens,
oauthConsents) are missing exported select/insert types; add exported type
aliases for each table like ExportType = typeof <table>.$inferSelect and
NewExportType = typeof <table>.$inferInsert to match the pattern used for other
tables (e.g., opportunities, personalIndexes); specifically add exported types
for oauthApplications, oauthAccessTokens, and oauthConsents (both select and
insert) so the auth adapter can use them for typing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 867111ed-a2fb-478a-a832-35285d0ee4e6

📥 Commits

Reviewing files that changed from the base of the PR and between 3539340 and 94e25b8.

⛔ Files ignored due to path filters (1)
  • protocol/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • .claude/handoff.md
  • docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md
  • frontend/src/app/login/page.tsx
  • frontend/src/components/AuthModal.tsx
  • frontend/src/routes.tsx
  • plugin/.mcp.json
  • protocol/drizzle/0033_add_mcp_oauth_tables.sql
  • protocol/drizzle/meta/0033_snapshot.json
  • protocol/drizzle/meta/_journal.json
  • protocol/package.json
  • protocol/src/adapters/auth.adapter.ts
  • protocol/src/controllers/mcp.handler.ts
  • protocol/src/lib/betterauth/betterauth.ts
  • protocol/src/main.ts
  • protocol/src/schemas/database.schema.ts
💤 Files with no reviewable changes (2)
  • protocol/package.json
  • .claude/handoff.md

Comment thread docs/superpowers/specs/2026-04-03-mcp-oauth-plugin-design.md Outdated
@yanekyuk yanekyuk force-pushed the feat/mcp-oauth-plugin branch from 94e25b8 to 4d8a165 Compare April 3, 2026 06:12
@yanekyuk yanekyuk merged commit 4d8a165 into indexnetwork:dev Apr 3, 2026
2 checks passed
@yanekyuk yanekyuk deleted the feat/mcp-oauth-plugin branch April 3, 2026 06:18
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.

1 participant