Skip to content

feat: support OAuth 2.1 for streamable-http MCP servers#1

Open
edelauna wants to merge 27 commits intomainfrom
feat/mcp-oauth-streamable-http
Open

feat: support OAuth 2.1 for streamable-http MCP servers#1
edelauna wants to merge 27 commits intomainfrom
feat/mcp-oauth-streamable-http

Conversation

@edelauna
Copy link
Copy Markdown

@edelauna edelauna commented Apr 24, 2026

Related GitHub Issue

Closes: RooCodeInc#8119

Description

Implements OAuth 2.1 support for streamable-http MCP servers per RFC 9728 (Protected Resource Metadata), RFC 8414 (Authorization Server Metadata), RFC 7591 (Dynamic Client Registration), and RFC 8707 (Resource Indicators).

Key design decisions:

  • Non-blocking OAuth: When client.connect() throws UnauthorizedError, the OAuth browser flow runs in the background via _initiateOAuthFlow() so the extension (chat window, other servers) isn't blocked waiting for the user's browser session. A persistent VS Code progress notification gives the user a visible Cancel button for the duration of the flow.

  • SDK workarounds: The MCP SDK's discoverOAuthMetadata() constructs the RFC 8414 well-known URL incorrectly for auth servers with path components (e.g., https://example.com/auth/public). It uses new URL("/.well-known/oauth-authorization-server", issuer) which is origin-relative and discards the path. This causes cascading failures: metadata discovery fails → dynamic client registration uses a wrong fallback URL → 404. See upstream issues:

    We work around this by:

    1. Performing our own RFC 9728 + RFC 8414 discovery (utils/oauth.ts)
    2. Pre-registering the client using the correct registration_endpoint
    3. Correcting the authorization URL in redirectToAuthorization()
    4. Exchanging auth codes directly via exchangeCodeForTokens() using the correct token_endpoint
  • Token persistence: Tokens are stored in VS Code's SecretStorage via SecretStorageService, keyed by server host. Cached tokens are reused on reconnect without re-running the full OAuth flow.

  • Reconnect after auth: After token exchange, the stale connection is deleted and connectToServer() is called fresh — the SDK's transport can't be reused after UnauthorizedError because its internal _abortController is already set.

  • Mid-session re-auth: If a 401 arrives during a tool call, the extension shows a confirmation toast before opening the browser, so the user is always in control. The Promise.race pattern in _completeOAuthFlow ensures the Cancel button on the progress bar aborts waitForAuthCode() if the user changes their mind after clicking Authenticate.

  • Negative cache: An in-memory TTL cache (isKnownNonOAuth) skips the OAuth discovery round-trip for servers already known not to require auth.

  • Multi-window coordination: SecretStorage.onDidChange is used to detect when another VS Code window completes the OAuth flow, so the current window reconnects without prompting again.

New files:

  • McpOAuthClientProvider.ts — Implements the SDK's OAuthClientProvider interface
  • SecretStorageService.ts — Thin wrapper around VS Code SecretStorage for OAuth tokens
  • utils/oauth.ts — RFC 8414-compliant metadata discovery (replaces SDK's broken implementation)
  • utils/callbackServer.ts — Local HTTP server for OAuth redirect callback with CSRF state validation

Test Procedure

Unit tests (across 4 files):

npx vitest run services/mcp/tests/McpOAuthClientProvider.spec.ts services/mcp/tests/SecretStorageService.spec.ts services/mcp/utils/tests/oauth.spec.ts services/mcp/utils/tests/callbackServer.spec.ts
  • McpOAuthClientProvider.spec.ts — covers create, clientMetadata, token storage/expiry, PKCE, redirect, auth code, close
  • SecretStorageService.spec.ts — covers CRUD, key isolation, malformed data
  • oauth.spec.ts — covers RFC 8414 URL construction for path/no-path issuers, error handling
  • callbackServer.spec.ts — covers callback handling and CSRF state validation
  • McpHub.spec.ts — covers _initiateOAuthFlow: persistent notification, cross-window tokens, cancellation, timeout, concurrent watcher deduplication

Manual testing:
Tested against real OAuth-protected MCP servers (https://temporal.mcp.kapa.ai and https://mcp.figma.com/mcp) — full flow completes successfully.

"temporal-kapa": {
  "type": "streamable-http",
  "url": "https://temporal.mcp.kapa.ai"
},
"Claude Code (figma)": {
  "type": "streamable-http",
  "url": "https://mcp.figma.com/mcp"
}

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue.
  • Scope: My changes are focused on the linked issue.
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes.
  • Documentation Impact: I have considered if my changes require documentation updates.
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Documentation Updates

  • No documentation updates are required.

Additional Notes

  • The SDK workarounds can be removed once the upstream issues are resolved (primarily typescript-sdk#545).
  • MCP_OAUTH_TEST_MODE=true env var enables headless e2e testing by making the callback server resolve immediately with a test auth code.
  • The client_name in OAuth client registration uses the MCP server config key (e.g., "figma") so providers see a meaningful name.
  • Ported from RooCodeInc/Roo-Code#11874.

Get in Touch

Summary by CodeRabbit

  • New Features

    • Browser-based OAuth sign-in for MCP servers with persistent token storage, automatic refresh, and reconnection handling.
    • Localized OAuth callback and flow UI copy added across many languages.
    • Local callback server and discovery for browser-based auth flows.
  • Improvements

    • More robust connection handling and retry behavior after authorization events.
    • Safer build sequencing on Windows to reduce file access conflicts.
  • Tests

    • Extensive E2E and unit tests for OAuth flows, token storage/refresh, callback handling, and reconnection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end OAuth 2.x support for HTTP MCP servers: RFC 9728 protected-resource discovery, RFC 8414/OIDC AS discovery, optional RFC 7591 dynamic client registration, PKCE-based auth-code flow with RFC 8707 resource indicator, local callback server, secure token persistence in VS Code secrets, token refresh, and integration into MCP connection lifecycle with tests and i18n.

Changes

Cohort / File(s) Summary
E2E Test
apps/vscode-e2e/src/suite/mcp-oauth.test.ts
New VS Code E2E suite: spins up mock OAuth+MCP server, validates full OAuth flow (401 → protected-resource discovery → AS discovery → DCR → auth code → token exchange → authenticated MCP request), verifies token caching on reconnect, and performs cleanup.
Build
src/esbuild.mjs
Changed non-watch rebuild ordering from concurrent to sequential to avoid concurrent asset copying races on Windows.
i18n Locales
src/i18n/locales/.../mcp.json
src/i18n/locales/ca/mcp.json, .../de/..., .../en/..., .../es/..., .../fr/..., .../hi/..., .../id/..., .../it/..., .../ja/..., .../ko/..., .../nl/..., .../pl/..., .../pt-BR/..., .../ru/..., .../tr/..., .../vi/..., .../zh-CN/..., .../zh-TW/...
Added a top-level oauth namespace (subkeys callback and flow) across many locale files for new OAuth UI copy.
MCP Hub Integration
src/services/mcp/McpHub.ts, src/services/mcp/McpServerManager.ts, src/services/mcp/constants.ts
Integrated OAuth into connection lifecycle: McpHub now accepts SecretStorageService, attaches authProvider to connections, intercepts UnauthorizedError to trigger background re-auth with progress/cancel, watches secret changes, reconnects after auth, manages auth-provider lifecycle, and added token/flow timeout constants. Constructor signature updated to accept secret storage.
OAuth Client Provider
src/services/mcp/McpOAuthClientProvider.ts
New OAuth client provider implementing OAuthClientProvider: discovery (with negative-cache), RFC 8414/OIDC discovery, dynamic client registration, PKCE state/verifier handling, local callback server orchestration, token persistence/refresh via secret storage, authorization URL corrections (scope/resource), and helpers for exchanging/refreshing tokens.
Secret Storage
src/services/mcp/SecretStorageService.ts
New service to persist MCP OAuth data in VS Code secrets with deterministic per-server keys, get/save/delete/has methods, and onDidChange watcher scoped to the server key. Exports StoredMcpOAuthData type.
Callback Server & Discovery Utils
src/services/mcp/utils/callbackServer.ts, src/services/mcp/utils/oauth.ts
Added local OAuth callback server util (start/stop, state validation, timeout, test-mode behavior, localized HTML response) and RFC 9728 + RFC 8414 discovery helper returning AS metadata and resource indicator (with timeouts and failure-safe returns).
Tests — Provider & Flow
src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts, src/services/mcp/__tests__/McpHub.spec.ts
Extensive Vitest suites for McpOAuthClientProvider and McpHub covering discovery, DCR, PKCE, token exchange/refresh, callback lifecycle, negative-cache behavior, and hub integration with progress/cancel/watchers/reconnect.
Tests — Storage & Utils
src/services/mcp/__tests__/SecretStorageService.spec.ts, src/services/mcp/utils/__tests__/oauth.spec.ts, src/services/mcp/utils/__tests__/callbackServer.spec.ts
Unit tests for secret key derivation/watchers, OAuth discovery URL/response handling, and callback server start/stop and result parsing (including test-mode).

Sequence Diagram

sequenceDiagram
    participant Client as Roo Client
    participant MCPServer as MCP Server
    participant AuthServer as Authorization Server
    participant CallbackServer as Local Callback<br/>(OAuth)
    participant TokenStore as VS Code<br/>SecretStorage

    Client->>MCPServer: POST /mcp (initial request)
    MCPServer-->>Client: 401 Unauthorized (WWW-Authenticate)
    Client->>MCPServer: GET /.well-known/oauth-protected-resource
    MCPServer-->>Client: protected resource metadata (authorization_servers, resource)
    Client->>AuthServer: GET /.well-known/oauth-authorization-server
    AuthServer-->>Client: authorization server metadata (endpoints, PKCE support)
    Client->>AuthServer: POST /register (DCR) [optional]
    AuthServer-->>Client: client_id (±client_secret)
    Client->>AuthServer: GET /authorize?code_challenge...&resource=...
    Client->>User: open browser for login
    AuthServer->>CallbackServer: Redirect with code + state
    CallbackServer-->>Client: resolves auth code result
    Client->>AuthServer: POST /token (code, code_verifier, resource)
    AuthServer-->>Client: access_token, refresh_token
    Client->>TokenStore: persist tokens & client_info
    Client->>MCPServer: POST /mcp (Authorization: Bearer token)
    MCPServer-->>Client: 200 OK (authenticated MCP response)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🐰 A hoppy OAuth chase beneath the moonlight bright,
PKCE codes and tokens tucked away for the night,
Discovery, register, browser hops—what a feat,
Secrets snug in VS Code, the flow now complete.
Thump-thump, binky! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
Title check ✅ Passed Title 'feat: support OAuth 2.1 for streamable-http MCP servers' clearly and concisely summarizes the main change—adding OAuth 2.1 support for HTTP MCP servers, which is the primary feature of this changeset.
Description check ✅ Passed Description comprehensively covers the PR scope, explains key design decisions (non-blocking OAuth, SDK workarounds, token persistence, reconnect logic), details test procedures, and includes a pre-submission checklist with all items checked.
Linked Issues check ✅ Passed The PR implements all key objectives from issue #8119: RFC 9728 resource metadata parsing, RFC 8414 OAuth server discovery, RFC 7591 dynamic client registration, RFC 8707 resource indicators, PKCE S256 support, secure token storage, and comprehensive unit/integration tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to OAuth 2.1 support: new OAuth client provider, secret storage, metadata discovery utilities, callback server, updated McpHub for OAuth flow, translation keys, and comprehensive tests. Minor esbuild change is focused fix for Windows asset-copy race condition, not unrelated scope creep.

✏️ 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 feat/mcp-oauth-streamable-http

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

@edelauna edelauna changed the title Feat/mcp oauth streamable http feat: support OAuth 2.1 for streamable-http MCP servers Apr 24, 2026
@edelauna edelauna marked this pull request as ready for review April 24, 2026 04:13
Copy link
Copy Markdown

@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: 11

🧹 Nitpick comments (1)
src/services/mcp/utils/__tests__/callbackServer.spec.ts (1)

34-35: Avoid silent fallback when request listener is missing.

Using a no-op fallback (vi.fn()) masks registration failures and weakens diagnostics. Assert listener existence explicitly.

Suggested fix
-		const requestCall = mockServer.on.mock.calls.find((call) => call[0] === "request")
-		const requestHandler = requestCall ? requestCall[1] : vi.fn()
+		const requestCall = mockServer.on.mock.calls.find((call) => call[0] === "request")
+		expect(requestCall).toBeDefined()
+		const requestHandler = requestCall![1]

Apply the same change in both test cases.

Also applies to: 72-73

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

In `@src/services/mcp/utils/__tests__/callbackServer.spec.ts` around lines 34 -
35, The test currently falls back to a no-op handler when the "request" listener
isn't registered (mockServer.on.mock.calls.find(...) ?: vi.fn()), which hides
registration failures; change the logic to assert the listener exists instead of
using vi.fn() — for example, after finding requestCall assert/requestCall is
defined (e.g. expect(requestCall).toBeDefined() or throw an error) and then
extract requestHandler = requestCall[1]; make this change for both occurrences
(the requestCall/requestHandler extraction in both tests).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/vscode-e2e/src/suite/mcp-oauth.test.ts`:
- Around line 241-244: The teardown currently calls fs.rm(..., { recursive: true
}) on workspaceDir + "/.roo" which can delete a user's real workspace; change
the logic in the teardown to only remove the ".roo" directory when it's inside
the ephemeral test tempDir (use workspaceDir.startsWith(tempDir) or compare
equality to tempDir) or when the workspace was created by the test, otherwise
skip deletion; update the code around workspaceDir, tempDir and the fs.rm(...)
call to perform this conditional check before invoking fs.rm with
recursive:true.
- Around line 327-333: The test "Should reuse stored token on reconnect without
re-running the full OAuth flow" must not rely on previous tests; instead, before
triggering the reconnect, create a valid mock OAuth token and persist it into
the same SecretStorage key the SDK expects (so the SDK will read it), then clear
endpointsHit and proceed with the reconnect assertions; reference the test name
and the endpointsHit variable to locate where to insert the token setup and use
the same SecretStorage API (the in-test secret storage instance) and token key
the SDK uses so the reconnect path is exercised in isolation.

In `@src/i18n/locales/ca/mcp.json`:
- Around line 29-49: Translate the remaining English strings into Catalan by
updating the "callback" keys ("success", "failed", "auth_failed",
"auth_success", "server_connection_complete", "tab_closing_in", "safe_to_close",
"invalid_state" as needed) and the "flow" key "dismissedHint" so the OAuth UI is
fully localized; preserve interpolation tokens like {{count}} and {{name}} and
any HTML tags (e.g., <span id="count">) and ensure punctuation/accents match
Catalan conventions.

In `@src/services/mcp/McpHub.ts`:
- Around line 1057-1065: Race occurs because we call
secretStorage.getOAuthData(serverUrl) before we register the cross-window token
change watcher; register the watcher (the onDidChange-style listener used
elsewhere in this class) before reading tokens or, if you prefer minimal change,
immediately re-check secretStorage.getOAuthData(serverUrl) after registering the
watcher and treat the second read as authoritative; if the second read returns a
non-expired token (compare with TOKEN_EXPIRY_BUFFER_MS) then close authProvider
via authProvider.close(), call deleteConnection(name, source), call
connectToServer(name, config, source), call notifyWebviewOfServerChanges(), and
return. Apply the same fix at the other occurrence that uses getOAuthData (the
block around the connect flow that mirrors this logic).

In `@src/services/mcp/McpOAuthClientProvider.ts`:
- Around line 124-129: The code currently picks tokenEndpointAuthMethod from
authServerMeta.token_endpoint_auth_methods_supported and may choose
"client_secret_basic" which we do not implement; update the selection logic in
tokenEndpointAuthMethod (and analogous logic at the other occurrences) to only
ever pick supported methods we implement—specifically allow only "none" or
"client_secret_post" (fall back to "client_secret_post" when nothing present) by
filtering authMethods to those two values before selecting; also ensure any
advertised/returned metadata uses that filtered tokenEndpointAuthMethod so
downstream clients are not told to use client_secret_basic.
- Around line 436-457: The token request builders drop the RFC 8707 "resource"
indicator that redirectToAuthorization included, causing servers that bind token
requests to a resource to reject exchanges; update the code that builds the POST
body for the authorization-code exchange (where params includes grant_type,
code, redirect_uri, client_id, code_verifier) to add params.resource when a
resource was set during redirectToAuthorization, and likewise add the same
resource parameter in the refresh token request builder (the refresh flow around
the refresh token handling). Ensure you pull the same resource value stored on
the instance (the same property redirectToAuthorization used), include it only
when present, and keep the client_secret logic and content-type headers
unchanged so token_endpoint requests continue to be form-encoded.

In `@src/services/mcp/McpServerManager.ts`:
- Around line 40-43: McpHub is constructed before SecretStorageService is
injected, allowing async init in McpHub to start and attempt connections that
fail with "SecretStorageService not initialized"; change McpHub to accept
SecretStorageService (or its interface) in its constructor and assign it before
any initialization/async startup runs, then update the call site to create
SecretStorageService(context) first and pass it into new McpHub(provider,
secretStorage) instead of calling hub.setSecretStorage afterwards; ensure
McpHub's constructor removes or delays any async work until after the injected
secret storage is assigned.

In `@src/services/mcp/SecretStorageService.ts`:
- Around line 30-36: The current _key(serverUrl: string) loses information by
replacing non-alphanumerics in sanitizedPath, causing collisions (e.g., /a-b,
/a_b, /a/b). Replace the lossy sanitization with a collision-free encoding of
the pathname in _key: compute a safe deterministic token from url.pathname (for
example a URL-safe base64 or a short hex digest like SHA-256 of the pathname),
then use that encoded token as the pathSuffix (e.g.,
`${this._namespace}${url.host}.${encodedPath}.data`), preserving the namespace
and host logic but ensuring unique keys for distinct pathnames. Ensure the
encoding is deterministic, URL-safe, and trimmed so the final key contains only
filesystem/key-store safe characters.

In `@src/services/mcp/utils/__tests__/callbackServer.spec.ts`:
- Around line 10-12: Tests assume non-test OAuth path but can be affected if
MCP_OAUTH_TEST_MODE is set by other suites; update the beforeEach (and
optionally afterEach) in this spec to clear or reset
process.env.MCP_OAUTH_TEST_MODE so the suite always runs in the non-test-mode
branch — modify the existing beforeEach (where vi.restoreAllMocks() is called)
to delete or set process.env.MCP_OAUTH_TEST_MODE = undefined (or delete
process.env.MCP_OAUTH_TEST_MODE) before running tests to harden isolation.

In `@src/services/mcp/utils/__tests__/oauth.spec.ts`:
- Around line 11-13: The test file overwrites global.fetch with mockFetch at
module scope (mockFetch and global.fetch) and never restores it, risking
cross-test pollution; modify the test suite to save the original global.fetch
before assigning mockFetch (e.g., const originalFetch = global.fetch) and
restore it in a cleanup hook (afterEach or afterAll) that resets global.fetch =
originalFetch and clears/mockReset the vi mock to avoid leaking the mock into
other tests.

In `@src/services/mcp/utils/callbackServer.ts`:
- Around line 50-59: The timeout created for resultPromise (const timeout =
setTimeout(...)) is not cleared when the server is closed, leaving a dangling
timer that can later reject; update the callback flow so every code path that
closes the server or completes the flow (including stopCallbackServer(), the
server.close() call in the timeout handler, and any other resolve/reject paths
around resultPromise and the similar block at the other location) also clears
the timeout (clearTimeout(timeout)), sets the resolved flag, and avoids calling
rejectResult/resolveResult again; ensure both instances (the Promise around
resultPromise and the similar Promise at the other spot referenced) clear their
timers on early cancellation/cleanup and when resolving to prevent late
unhandled rejections.

---

Nitpick comments:
In `@src/services/mcp/utils/__tests__/callbackServer.spec.ts`:
- Around line 34-35: The test currently falls back to a no-op handler when the
"request" listener isn't registered (mockServer.on.mock.calls.find(...) ?:
vi.fn()), which hides registration failures; change the logic to assert the
listener exists instead of using vi.fn() — for example, after finding
requestCall assert/requestCall is defined (e.g.
expect(requestCall).toBeDefined() or throw an error) and then extract
requestHandler = requestCall[1]; make this change for both occurrences (the
requestCall/requestHandler extraction in both tests).
🪄 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 Plus

Run ID: d7243221-b2f9-4754-bebe-4948e2e43fa4

📥 Commits

Reviewing files that changed from the base of the PR and between 96d6e43 and ba5345c.

📒 Files selected for processing (32)
  • apps/vscode-e2e/src/suite/mcp-oauth.test.ts
  • src/esbuild.mjs
  • src/i18n/locales/ca/mcp.json
  • src/i18n/locales/de/mcp.json
  • src/i18n/locales/en/mcp.json
  • src/i18n/locales/es/mcp.json
  • src/i18n/locales/fr/mcp.json
  • src/i18n/locales/hi/mcp.json
  • src/i18n/locales/id/mcp.json
  • src/i18n/locales/it/mcp.json
  • src/i18n/locales/ja/mcp.json
  • src/i18n/locales/ko/mcp.json
  • src/i18n/locales/nl/mcp.json
  • src/i18n/locales/pl/mcp.json
  • src/i18n/locales/pt-BR/mcp.json
  • src/i18n/locales/ru/mcp.json
  • src/i18n/locales/tr/mcp.json
  • src/i18n/locales/vi/mcp.json
  • src/i18n/locales/zh-CN/mcp.json
  • src/i18n/locales/zh-TW/mcp.json
  • src/services/mcp/McpHub.ts
  • src/services/mcp/McpOAuthClientProvider.ts
  • src/services/mcp/McpServerManager.ts
  • src/services/mcp/SecretStorageService.ts
  • src/services/mcp/__tests__/McpHub.spec.ts
  • src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts
  • src/services/mcp/__tests__/SecretStorageService.spec.ts
  • src/services/mcp/constants.ts
  • src/services/mcp/utils/__tests__/callbackServer.spec.ts
  • src/services/mcp/utils/__tests__/oauth.spec.ts
  • src/services/mcp/utils/callbackServer.ts
  • src/services/mcp/utils/oauth.ts

Comment thread apps/vscode-e2e/src/suite/mcp-oauth.test.ts Outdated
Comment on lines +327 to +333
test("Should reuse stored token on reconnect without re-running the full OAuth flow", async function () {
// This test runs after the previous one, so a token is already stored in SecretStorage.
// Trigger another reconnect — the SDK should inject the cached token directly and skip the
// browser-based auth flow (no new register or token endpoints should be hit).

// Clear only mcp-related hit tracking (token endpoint should NOT be re-hit)
endpointsHit.clear()
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the token-reuse test self-contained (avoid cross-test state dependency).

This test currently depends on the previous test having already persisted a token, which makes isolated/sharded execution unreliable.

Suggested fix
 test("Should reuse stored token on reconnect without re-running the full OAuth flow", async function () {
-		// This test runs after the previous one, so a token is already stored in SecretStorage.
+		// Ensure precondition in this test: acquire and persist token first.
+		await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 })
 		// Trigger another reconnect — the SDK should inject the cached token directly and skip the
 		// browser-based auth flow (no new register or token endpoints should be hit).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test("Should reuse stored token on reconnect without re-running the full OAuth flow", async function () {
// This test runs after the previous one, so a token is already stored in SecretStorage.
// Trigger another reconnect — the SDK should inject the cached token directly and skip the
// browser-based auth flow (no new register or token endpoints should be hit).
// Clear only mcp-related hit tracking (token endpoint should NOT be re-hit)
endpointsHit.clear()
test("Should reuse stored token on reconnect without re-running the full OAuth flow", async function () {
// Ensure precondition in this test: acquire and persist token first.
await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 })
// Trigger another reconnect — the SDK should inject the cached token directly and skip the
// browser-based auth flow (no new register or token endpoints should be hit).
// Clear only mcp-related hit tracking (token endpoint should NOT be re-hit)
endpointsHit.clear()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode-e2e/src/suite/mcp-oauth.test.ts` around lines 327 - 333, The test
"Should reuse stored token on reconnect without re-running the full OAuth flow"
must not rely on previous tests; instead, before triggering the reconnect,
create a valid mock OAuth token and persist it into the same SecretStorage key
the SDK expects (so the SDK will read it), then clear endpointsHit and proceed
with the reconnect assertions; reference the test name and the endpointsHit
variable to locate where to insert the token setup and use the same
SecretStorage API (the in-test secret storage instance) and token key the SDK
uses so the reconnect path is exercised in isolation.

Copy link
Copy Markdown
Author

@edelauna edelauna Apr 24, 2026

Choose a reason for hiding this comment

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

Fixed in 05b399b — the test now calls await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 }) before clearing endpointsHit, making it self-contained regardless of execution order.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

rude

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@coderabbitai Fixed in 05b399b — the test now calls await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 }) before clearing endpointsHit, making it self-contained regardless of execution order.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@coderabbitai Fixed in 05b399b — the test now calls await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 }) before clearing endpointsHit, making it self-contained regardless of execution order.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only users with a collaborator, contributor, member, or owner role can interact with CodeRabbit.

Comment thread src/i18n/locales/ca/mcp.json
Comment thread src/services/mcp/McpHub.ts Outdated
Comment thread src/services/mcp/McpOAuthClientProvider.ts
Comment thread src/services/mcp/McpServerManager.ts Outdated
Comment thread src/services/mcp/SecretStorageService.ts
Comment thread src/services/mcp/utils/__tests__/callbackServer.spec.ts
Comment thread src/services/mcp/utils/__tests__/oauth.spec.ts
Comment thread src/services/mcp/utils/callbackServer.ts Outdated
Copy link
Copy Markdown

@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: 2

🧹 Nitpick comments (1)
src/services/mcp/SecretStorageService.ts (1)

17-21: Docstring is slightly misleading — tokens are keyed by host AND path.

The comment says "keyed by host" and "different servers on the same host share credentials," but the implementation at lines 30-36 actually uses both host AND pathname (base64url-encoded) for the key. This is the correct behavior for isolating different MCP servers, but the docstring suggests credentials are shared by host only.

📝 Proposed documentation fix
 /**
  * Thin wrapper around VS Code SecretStorage for persisting MCP OAuth tokens.
- * Tokens are stored per-server (keyed by host) so different servers on the
- * same host share credentials, which is the common case for multi-path APIs.
+ * Tokens are stored per-server, keyed by host and URL path, so different MCP
+ * endpoints on the same host maintain separate credentials.
  */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/services/mcp/SecretStorageService.ts` around lines 17 - 21, Update the
class docstring in SecretStorageService to accurately state that tokens are
keyed by both host and path rather than host only: mention that
SecretStorageService (and the key-building logic that base64url-encodes the
pathname) constructs keys using the host plus the encoded pathname to isolate
credentials for different MCP servers on the same host, and remove or adjust the
sentence implying different servers on the same host share credentials.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts`:
- Around line 23-24: The test suite overwrites global.fetch with mockFetch at
module scope and never restores it, risking cross-test pollution; capture the
original fetch (e.g., const originalFetch = global.fetch), assign global.fetch =
mockFetch inside a beforeAll or beforeEach, and then restore it in an afterAll
(or afterEach) by setting global.fetch = originalFetch and clearing/resetting
mockFetch (mockFetch.mockReset() or vi.resetAllMocks()) so the mock does not
leak into other tests; reference the mockFetch and global.fetch symbols and
update McpOAuthClientProvider.spec to follow the same setup/teardown pattern
used in oauth.spec.ts.

In `@src/services/mcp/__tests__/SecretStorageService.spec.ts`:
- Around line 120-164: The onDidChange test suite is incorrectly nested inside
the deleteOAuthData suite; move the entire describe("onDidChange", ...) block so
it is a sibling of describe("deleteOAuthData", ...) (i.e., close the
deleteOAuthData describe before the onDidChange describe) to make tests for
deleteOAuthData and onDidChange separate; locate the blocks by the unique
identifiers deleteOAuthData and onDidChange in SecretStorageService.spec.ts and
adjust the braces/closing parentheses accordingly so onDidChange is not nested.

---

Nitpick comments:
In `@src/services/mcp/SecretStorageService.ts`:
- Around line 17-21: Update the class docstring in SecretStorageService to
accurately state that tokens are keyed by both host and path rather than host
only: mention that SecretStorageService (and the key-building logic that
base64url-encodes the pathname) constructs keys using the host plus the encoded
pathname to isolate credentials for different MCP servers on the same host, and
remove or adjust the sentence implying different servers on the same host share
credentials.
🪄 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 Plus

Run ID: ca1f72dd-a91b-49a2-85b7-111dc7364fe0

📥 Commits

Reviewing files that changed from the base of the PR and between ba5345c and f0e1121.

📒 Files selected for processing (11)
  • src/i18n/locales/ca/mcp.json
  • src/services/mcp/McpHub.ts
  • src/services/mcp/McpOAuthClientProvider.ts
  • src/services/mcp/McpServerManager.ts
  • src/services/mcp/SecretStorageService.ts
  • src/services/mcp/__tests__/McpHub.spec.ts
  • src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts
  • src/services/mcp/__tests__/SecretStorageService.spec.ts
  • src/services/mcp/utils/__tests__/callbackServer.spec.ts
  • src/services/mcp/utils/__tests__/oauth.spec.ts
  • src/services/mcp/utils/callbackServer.ts
✅ Files skipped from review due to trivial changes (2)
  • src/services/mcp/tests/McpHub.spec.ts
  • src/services/mcp/McpOAuthClientProvider.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/mcp/McpServerManager.ts

Comment thread src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts
Comment thread src/services/mcp/__tests__/SecretStorageService.spec.ts
Copy link
Copy Markdown

@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.

♻️ Duplicate comments (2)
apps/vscode-e2e/src/suite/mcp-oauth.test.ts (2)

200-215: ⚠️ Potential issue | 🟠 Major

Preserve pre-existing .roo/mcp.json instead of overwriting/deleting it blindly.

Line 214 overwrites workspace config, and Lines 233–239 later delete it unconditionally. If the file existed before the test, local user config can be lost.

💡 Proposed fix
 suite("Roo Code MCP OAuth", function () {
   setDefaultSuiteTimeout(this)

   let tempDir: string
   let testFiles: { mcpConfig: string }
+  let originalMcpConfig: string | undefined
+  let hadOriginalMcpConfig = false
   let mockServer: http.Server
   let mockServerPort: number

@@
   suiteSetup(async () => {
@@
     testFiles = { mcpConfig: path.join(rooDir, "mcp.json") }
+    try {
+      originalMcpConfig = await fs.readFile(testFiles.mcpConfig, "utf8")
+      hadOriginalMcpConfig = true
+    } catch {
+      hadOriginalMcpConfig = false
+    }
     await fs.writeFile(testFiles.mcpConfig, JSON.stringify(mcpConfig, null, 2))
@@
   suiteTeardown(async () => {
@@
-    for (const filePath of Object.values(testFiles)) {
-      try {
-        await fs.unlink(filePath)
-      } catch {
-        // ignore
-      }
-    }
+    try {
+      if (hadOriginalMcpConfig && originalMcpConfig !== undefined) {
+        await fs.writeFile(testFiles.mcpConfig, originalMcpConfig, "utf8")
+      } else {
+        await fs.unlink(testFiles.mcpConfig)
+      }
+    } catch {
+      // ignore
+    }

Also applies to: 233-239

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

In `@apps/vscode-e2e/src/suite/mcp-oauth.test.ts` around lines 200 - 215, The test
currently unconditionally writes and later deletes the workspace .roo/mcp.json
(variables workspaceDir, rooDir, testFiles.mcpConfig), risking loss of a user's
config; modify the test to detect if testFiles.mcpConfig already exists and
preserve it—either skip writing if present or create a temporary backup copy
before writing—and on cleanup restore the original from the backup (or only
delete the file if the test created it), ensuring teardown only removes
test-created artifacts and not pre-existing .roo/mcp.json.

331-335: ⚠️ Potential issue | 🟠 Major

Token-reuse precondition is flaky and still order-dependent.

Line 334 waits for "token" before triggering any flow in this test, while setup() clears endpointsHit. In isolated/sharded runs this can timeout.

💡 Proposed fix (self-contained bootstrap inside this test)
 test("Should reuse stored token on reconnect without re-running the full OAuth flow", async function () {
-  // Ensure a token is in SecretStorage before testing reuse — this makes the
-  // test self-contained regardless of execution order.
-  await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 })
+  // Bootstrap once in this test to ensure token is persisted.
+  const workspaceDir = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || tempDir
+  const mcpConfigPath = path.join(workspaceDir, ".roo", "mcp.json")
+  await fs.writeFile(
+    mcpConfigPath,
+    JSON.stringify({
+      mcpServers: {
+        "test-oauth-server": { type: "streamable-http", url: `http://localhost:${mockServerPort}/mcp` },
+      },
+    }, null, 2),
+  )
+  await waitFor(() => endpointsHit.has("token"), { timeout: 30_000 })

   // Clear hit tracking so we can assert the token endpoint is NOT re-hit.
   endpointsHit.clear()

-  const workspaceDir = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || tempDir
-  const mcpConfigPath = path.join(workspaceDir, ".roo", "mcp.json")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/vscode-e2e/src/suite/mcp-oauth.test.ts` around lines 331 - 335, The test
is flakey because it waits for endpointsHit to already contain "token" even
though setup() clears endpointsHit; make the test self-contained by pre-seeding
the token and endpoint-hit state before the waitFor. Specifically, in the
"Should reuse stored token..." test, write a valid token into the SecretStorage
(or call the existing helper that stores a token) and add "token" to
endpointsHit (e.g., endpointsHit.add("token")) or run the minimal bootstrap
logic used elsewhere to simulate the token endpoint being hit, then proceed with
the rest of the test so it no longer depends on previous tests' order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/vscode-e2e/src/suite/mcp-oauth.test.ts`:
- Around line 200-215: The test currently unconditionally writes and later
deletes the workspace .roo/mcp.json (variables workspaceDir, rooDir,
testFiles.mcpConfig), risking loss of a user's config; modify the test to detect
if testFiles.mcpConfig already exists and preserve it—either skip writing if
present or create a temporary backup copy before writing—and on cleanup restore
the original from the backup (or only delete the file if the test created it),
ensuring teardown only removes test-created artifacts and not pre-existing
.roo/mcp.json.
- Around line 331-335: The test is flakey because it waits for endpointsHit to
already contain "token" even though setup() clears endpointsHit; make the test
self-contained by pre-seeding the token and endpoint-hit state before the
waitFor. Specifically, in the "Should reuse stored token..." test, write a valid
token into the SecretStorage (or call the existing helper that stores a token)
and add "token" to endpointsHit (e.g., endpointsHit.add("token")) or run the
minimal bootstrap logic used elsewhere to simulate the token endpoint being hit,
then proceed with the rest of the test so it no longer depends on previous
tests' order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff648812-a244-4b28-95e4-252c7493eedd

📥 Commits

Reviewing files that changed from the base of the PR and between f0e1121 and 05b399b.

📒 Files selected for processing (3)
  • apps/vscode-e2e/src/suite/mcp-oauth.test.ts
  • src/services/mcp/__tests__/McpOAuthClientProvider.spec.ts
  • src/services/mcp/__tests__/SecretStorageService.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/mcp/tests/SecretStorageService.spec.ts

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.

[ENHANCEMENT] Support MCP OAuth 2.1 for HTTP MCP servers (discovery + PKCE)

2 participants