-
Notifications
You must be signed in to change notification settings - Fork 360
fix: surface MCP OAuth errors to browser instead of silently swallowing #850
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 10bc7f5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Claude Code ReviewIssues Found:
Testing:
Overall: |
commit: |
|
cc @mattzcarey |
Updates documentation to reflect improved OAuth error handling from cloudflare/agents#850. Changes: - Update customHandler examples to properly check result.authSuccess - Document MCPClientOAuthResult type with authSuccess and authError fields - Add configureOAuthCallback() API reference documentation - Document default error page behavior when no callback config is provided - Add error field to MCPServersState type for failed connections - Add examples showing proper error handling in React applications Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* docs: update MCP OAuth error handling documentation Updates documentation to reflect improved OAuth error handling from cloudflare/agents#850. Changes: - Update customHandler examples to properly check result.authSuccess - Document MCPClientOAuthResult type with authSuccess and authError fields - Add configureOAuthCallback() API reference documentation - Document default error page behavior when no callback config is provided - Add error field to MCPServersState type for failed connections - Add examples showing proper error handling in React applications Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: prevent XSS in OAuth error handling examples Use JSON.stringify + textContent for HTML examples to avoid injecting untrusted authError strings into the DOM via innerHTML. Add explicit content-type: text/plain to API reference plain-text error responses to prevent browser content sniffing. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
|
/bonk review this PR |
|
Now I have a complete picture. Here's my review: PR Review: fix: surface MCP OAuth errors to browser instead of silently swallowingOverall AssessmentThis is a well-motivated fix. The core problem -- OAuth errors being silently swallowed via a redirect to origin -- is a real usability issue, and the approach of catching thrown errors + rendering a visible error page is the right one. The test coverage is thorough. There are a few issues worth addressing before merge. Issues1. XSS in the example
const error = result.authError || "Unknown error";
// ...
<p style="color:#666;">${error}</p>
2. Confirmed double-escaping (Medium) The PR description acknowledges this, but to be concrete about the chain:
If the OAuth provider returns 3.
Good things
SummaryThe core fix is solid and the test coverage is thorough. The main actionable item is the XSS in the example's |
packages/agents/src/index.ts
Outdated
| <body style="font-family:system-ui,sans-serif;max-width:500px;margin:80px auto;padding:0 20px;"> | ||
| <h1 style="font-size:1.5em;">MCP Authentication Failed</h1> | ||
| <p style="color:#666;">${safeError}</p> | ||
| <a href="${escapeHtml(baseOrigin)}" style="color:#0066cc;">Return to application</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential XSS vulnerability: baseOrigin is being HTML-escaped here but it comes from new URL(request.url).origin which should already be safe. However, the double-escaping could cause display issues. More importantly, if this is truly necessary for security, it suggests baseOrigin might contain user-controlled data, which would be concerning.
Consider either:
- Remove the escaping if
baseOriginis guaranteed safe (recommended) - Or document why user input could affect the origin
packages/agents/src/index.ts
Outdated
| } catch (err) { | ||
| // handleCallbackRequest throws when it can't find the in-memory connection | ||
| // (e.g., after hibernation). Catch here so we return a proper HTTP error | ||
| // response instead of letting it bubble as a raw 500. | ||
| const errorMessage = err instanceof Error ? err.message : String(err); | ||
| const url = new URL(request.url); | ||
| const state = url.searchParams.get("state"); | ||
| const serverId = state?.split(".")[1] ?? "unknown"; | ||
| result = { serverId, authSuccess: false, authError: errorMessage }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
State parsing could be fragile: The code assumes state?.split(".")[1] will always contain the server ID, but if the state format changes or is malformed, this could break. Consider adding validation or using a more robust parsing method.
const serverId = (() => {
try {
const parts = state?.split(".");
return parts && parts.length > 1 ? parts[1] : "unknown";
} catch {
return "unknown";
}
})();
examples/mcp-client/src/server.ts
Outdated
| `<!DOCTYPE html> | ||
| <html> | ||
| <head><title>Authentication Failed</title></head> | ||
| <body style="font-family:system-ui,sans-serif;max-width:400px;margin:80px auto;padding:0 20px;text-align:center;"> | ||
| <h2 style="color:#c00;">Authentication Failed</h2> | ||
| <p style="color:#666;">${error}</p> | ||
| <p style="color:#999;font-size:0.85em;">This window will close automatically...</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing HTML escaping in error display: The error message is being directly interpolated into HTML without escaping, which could lead to XSS if the error message contains user-controlled content.
const safeError = escapeHtml(error);You'll need to import escapeHtml from 'escape-html' at the top of the file.
Review SummaryThis PR effectively fixes the critical issue of silently swallowed MCP OAuth errors. The implementation is solid with comprehensive test coverage. Key improvements:
Issues to address:
The core logic and architecture align well with project conventions. Once the security issue in the example is fixed, this will be ready to merge. |
85e034b to
12bed03
Compare
3d4fb15 to
10bc7f5
Compare
Surface MCP OAuth errors instead of swallowing them
Previously, when an OAuth callback failed (e.g. user denied access, invalid state, missing code),
handleCallbackRequestwould throw an unhandled exception that bubbled up as a raw 500 in the browser with no useful information.Problem
handleCallbackRequestmixed two patterns: returningMCPOAuthCallbackResultobjects for some paths and throwing exceptions for others. The thrown errors were never caught by the Agent's callback handler, so they became opaque 500s. OAuth errors from the authorization server (likeaccess_denied) also weren't routed throughfailConnection(), leaving the connection stuck in"authenticating"instead of properly transitioning to"failed".What changed
validateCallbackRequest()method inMCPClientManagerthat returns structured results instead of throwing. Validation errors with an active connection now route throughfailConnection()so state properly transitions to"failed".onMcpUpdate/server.error).MCPOAuthCallbackResultandMCPClientOAuthResulttypes updated soserverIdis optional on error results.customHandlerand display errors inline in the UI.Generated by OpenCode