Skip to content

fix: use SDK OAuth provider for remote MCP#19

Merged
ian-pascoe merged 3 commits into
mainfrom
codex/sdk-oauth-remote-mcp
May 13, 2026
Merged

fix: use SDK OAuth provider for remote MCP#19
ian-pascoe merged 3 commits into
mainfrom
codex/sdk-oauth-remote-mcp

Conversation

@ian-pascoe
Copy link
Copy Markdown
Contributor

@ian-pascoe ian-pascoe commented May 12, 2026

Summary

  • Use the MCP SDK OAuth auth provider for remote OAuth MCP transports instead of precomputing static bearer headers.
  • Keep static bearer/header behavior for non-OAuth remote transports.
  • Add a remote HTTP MCP regression test proving stored OAuth tokens flow through the SDK provider.
  • Add a patch changeset.

Root Cause

Caplets was manually reading stored OAuth tokens and injecting a static Authorization header into remote MCP transports. That bypassed the MCP SDK's OAuth handling for refresh, resource metadata, and auth challenge/upscope flows.

Validation

  • pnpm vitest run test/downstream.test.ts test/auth.test.ts
  • pnpm typecheck
  • pnpm format:check
  • pnpm lint
  • pnpm schema:check
  • pnpm build
  • pnpm test
  • Pre-push hook: pnpm format:check && pnpm lint && pnpm typecheck && pnpm schema:check && pnpm test && pnpm build

Summary by CodeRabbit

  • New Features

    • Remote MCP OAuth now uses SDK-managed providers for token refresh, metadata, and auth challenge handling.
  • Bug Fixes

    • Authentication failures are now surfaced explicitly (AUTH_REQUIRED/AUTH_FAILED) with actionable next steps when credentials are missing or rejected.
  • Tests

    • Added integration tests for remote OAuth lifecycle, token persistence, 403 handling, and outgoing Authorization headers.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 043f7255-1a31-4897-98dd-b968a2443e07

📥 Commits

Reviewing files that changed from the base of the PR and between 9835c72 and 4967286.

📒 Files selected for processing (1)
  • src/downstream.ts

📝 Walkthrough

Walkthrough

Switches remote MCP transport auth from static bearer headers to an SDK-managed OAuth provider, preserves auth-remediation errors across downstream lifecycle methods, and adds tests for missing credentials, 403 classification, and sending stored tokens.

Changes

OAuth Auth Provider Integration

Layer / File(s) Summary
Provider integration and transport wiring
src/downstream.ts, .changeset/tidy-otters-brush.md
Changeset set to patch; downstream imports FileOAuthProvider/readTokenBundle, computes authProvider via oauthProvider(), passes it into HTTP/SSE transports, selects fetch wrapper based on provider presence, rethrows auth-remediation errors in callTool/refreshTools/connect, and adds isAuthRemediationError.
Remote OAuth lifecycle tests
test/downstream.test.ts
Adds tests that (1) missing OAuth creds produce AUTH_REQUIRED with nextAction: run_caplets_auth_login, (2) HTTP 403 is classified as AUTH_FAILED (includes status), and (3) stored OAuth token is sent as Authorization: Bearer ...; tests use temp auth directories and local HTTP fixtures and clean up resources.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibbled tokens in the night,
Swapped static crumbs for managed light,
A provider hops, refreshes true,
Headers march and errors cue,
Caplets hum and connections bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: use SDK OAuth provider for remote MCP' directly and accurately summarizes the main change: migrating remote MCP OAuth authentication to use the SDK's OAuth provider instead of static bearer headers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/sdk-oauth-remote-mcp

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

@ian-pascoe ian-pascoe changed the title [codex] Use SDK OAuth provider for remote MCP fix: use SDK OAuth provider for remote MCP May 12, 2026
@ian-pascoe ian-pascoe marked this pull request as ready for review May 12, 2026 22:51
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/downstream.ts`:
- Around line 282-303: The connect() method currently remaps all non-timeout
errors to "SERVER_UNAVAILABLE", which hides CapletsError("AUTH_REQUIRED") thrown
by oauthProvider(); update connect() to detect CapletsError with code
"AUTH_REQUIRED" (or error.code === "AUTH_REQUIRED") and rethrow it unchanged so
the original nextAction/login remediation remains available; for all other
non-timeout errors keep the existing remap to "SERVER_UNAVAILABLE". Ensure
references to oauthProvider, connect, CapletsError and the "SERVER_UNAVAILABLE"
remap are adjusted accordingly.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 438615b8-77ed-435b-8dff-504a81c67375

📥 Commits

Reviewing files that changed from the base of the PR and between 3f58fdd and 67fdaeb.

📒 Files selected for processing (3)
  • .changeset/tidy-otters-brush.md
  • src/downstream.ts
  • test/downstream.test.ts

Comment thread src/downstream.ts
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR replaces manual OAuth bearer header injection in remote MCP transports with the MCP SDK's FileOAuthProvider, enabling SDK-managed token refresh, resource metadata discovery, and auth challenge handling. isAuthRemediationError guards are added to connect(), refreshTools(), and callTool() so that AUTH_REQUIRED/AUTH_FAILED errors bubble up directly to callers rather than being silently re-wrapped as SERVER_UNAVAILABLE.

  • src/downstream.ts: oauthProvider() now builds a FileOAuthProvider when OAuth auth is configured; a separate fetchWithOAuthAuthClassification fetch wrapper is used for OAuth transports that intercepts 403s while letting 401s pass through to the SDK's refresh/challenge flow.
  • test/downstream.test.ts: Three new integration tests cover missing-credential surfacing, 403 classification as AUTH_FAILED, and end-to-end proof that stored OAuth tokens reach the Authorization header via the SDK provider.

Confidence Score: 5/5

Safe to merge — auth error propagation is correctly guarded at all three call sites and the SDK provider integration is verified end-to-end by integration tests.

The isAuthRemediationError guards in connect(), refreshTools(), and callTool() ensure AUTH_REQUIRED and AUTH_FAILED errors surface directly to callers before being re-wrapped. The OAuth fetch wrapper correctly intercepts 403s while leaving 401s for the SDK's refresh/challenge flow. Three integration tests validate each path end-to-end. No unaddressed correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
src/downstream.ts Core transport creation refactored: static OAuth headers replaced with FileOAuthProvider; isAuthRemediationError guards added to connect(), refreshTools(), and callTool() to surface AUTH_REQUIRED/AUTH_FAILED without wrapping.
test/downstream.test.ts Three integration tests added covering missing-credential AUTH_REQUIRED, 403-to-AUTH_FAILED classification, and end-to-end Authorization header presence for stored OAuth tokens.
.changeset/tidy-otters-brush.md Patch changeset entry accurately describing the OAuth provider migration.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant DownstreamManager
    participant oauthProvider
    participant FileOAuthProvider
    participant SDKTransport
    participant RemoteMCP

    Caller->>DownstreamManager: listTools(server)
    DownstreamManager->>DownstreamManager: connect(server)
    DownstreamManager->>oauthProvider: oauthProvider(server)
    oauthProvider->>oauthProvider: readTokenBundle(server)
    alt No access token AND no refresh token
        oauthProvider-->>Caller: throw CapletsError(AUTH_REQUIRED)
    else Token present
        oauthProvider->>FileOAuthProvider: new FileOAuthProvider(server, ...)
        FileOAuthProvider-->>DownstreamManager: authProvider
        DownstreamManager->>SDKTransport: "new StreamableHTTPClientTransport({authProvider, fetch: fetchWithOAuthAuthClassification})"
        SDKTransport->>FileOAuthProvider: tokens()
        FileOAuthProvider-->>SDKTransport: "{access_token, refresh_token, ...}"
        SDKTransport->>RemoteMCP: POST /mcp (Authorization: Bearer token)
        alt 200 OK
            RemoteMCP-->>SDKTransport: initialize result
            SDKTransport-->>DownstreamManager: connected
            DownstreamManager->>RemoteMCP: tools/list
            alt 200 OK
                RemoteMCP-->>Caller: Tool[]
            else 403 Forbidden
                RemoteMCP-->>SDKTransport: 403
                SDKTransport->>SDKTransport: fetchWithOAuthAuthClassification intercepts
                SDKTransport-->>Caller: throw CapletsError(AUTH_FAILED)
            end
        else 401 Unauthorized
            RemoteMCP-->>SDKTransport: 401
            SDKTransport->>FileOAuthProvider: redirectToAuthorization(url)
            FileOAuthProvider-->>Caller: throw CapletsError(AUTH_REQUIRED)
        end
    end
Loading

Reviews (3): Last reviewed commit: "fix: match OAuth redirect callback signa..." | Re-trigger Greptile

Comment thread src/downstream.ts
Comment thread src/downstream.ts
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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/downstream.ts`:
- Around line 305-329: In oauthProvider adjust the onRedirect callback passed
into the FileOAuthProvider constructor so it matches the expected signature
(url: URL) => void; replace the current no-arg lambda with one that accepts a
URL parameter (e.g., (url: URL) => { throw new CapletsError(... ) }) so
FileOAuthProvider.redirectToAuthorization can invoke it without a type/signature
mismatch; keep the thrown CapletsError payload the same and continue to use
this.options.authDir and server values as before.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4bef875f-a2d3-4060-a7d2-f530d1283008

📥 Commits

Reviewing files that changed from the base of the PR and between 67fdaeb and 9835c72.

📒 Files selected for processing (2)
  • src/downstream.ts
  • test/downstream.test.ts

Comment thread src/downstream.ts
@ian-pascoe ian-pascoe merged commit 85bfe0c into main May 13, 2026
5 checks passed
@ian-pascoe ian-pascoe deleted the codex/sdk-oauth-remote-mcp branch May 13, 2026 03:30
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