[BUG] Notion MCP OAuth: complete_authentication fails when mcp.notion.com relay returns callback instead of localhost

Resolved 💬 2 comments Opened Apr 24, 2026 by mball028 Closed Apr 25, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When authenticating the Notion MCP server (https://mcp.notion.com/mcp), the browser ends up at mcp.notion.com/callback instead of being forwarded to the local Claude Code callback server. Passing the mcp.notion.com/callback URL to mcp__notion__complete_authentication (the documented fallback) fails with a state mismatch error, making it impossible to complete authentication.

What Should Happen?

mcp__notion__complete_authentication should accept https://mcp.notion.com/callback?code=...&state=<base64-JSON> as a valid callback URL by decoding the state JSON and validating the inner state field against the stored value.

Error Messages/Logs

Authentication failed for notion: OAuth state mismatch - possible CSRF attack

Steps to Reproduce

  1. Call mcp__notion__authenticate — receive an auth URL with redirect_uri=http://localhost:<port>/callback
  2. Open the URL in browser → mcp.notion.com redirects to https://www.notion.so/install-integration?...&redirect_uri=https://mcp.notion.com/callback&...
  3. Authorize on the Notion page
  4. Browser lands on https://mcp.notion.com/callback?code=...&state=<base64-JSON> — the relay does not forward to localhost
  5. Paste that URL into mcp__notion__complete_authentication
  6. Error: Authentication failed for notion: OAuth state mismatch - possible CSRF attack

Root cause: mcp.notion.com wraps the original state string inside a base64-encoded JSON object before sending the callback:

{
  "state": "<original-raw-state>",
  "redirectUri": "http://localhost:<port>/callback",
  "codeChallenge": "...",
  ...
}

complete_authentication compares the raw state query parameter against the stored state, but it receives the base64 JSON wrapper instead of the raw state — so validation fails even though the original state is present inside the JSON.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

N/A

Claude Code Version

2.1.119 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

The mcp__notion__complete_authentication tool documentation states it handles the case where "the browser shows a connection error on the redirect page." However, when using Notion MCP, the browser never reaches localhost at all — it stays at mcp.notion.com/callback. The tool needs to handle this relay pattern.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗