MCP OAuth token exchange omits client_id in body, breaks public PKCE clients (Wrike)
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?
Authenticating the Wrike MCP server (https://mcp.wrike.com/app/mcp/stream) via /mcp consistently fails at the token exchange step. The browser-side authorization succeeds (Wrike shows "authentication successful"), but Claude Code's follow-up POST to Wrike's token endpoint is rejected because client_id is missing from the request body. The MCP server stays in "needs-auth" forever and its tools never load.
Per RFC 6749 §3.2.1, public OAuth clients (no client_secret, using PKCE) MUST include client_id in the body of the token request. Claude Code's MCP OAuth client picks up the client_id for the authorization URL correctly, but doesn't include it in the subsequent POST to the token endpoint. This would affect any MCP server using PKCE + a public client, not only Wrike.
What Should Happen?
Token exchange completes successfully and Wrike MCP tools become available in /mcp and at runtime.
Error Messages/Logs
From the Claude VSCode Output channel during /mcp Authenticate on Wrike:
[DEBUG] MCP server "wrike": Using pre-configured client ID
[DEBUG] MCP server "wrike": Authorization URL: https://login.wrike.com/oauth2/authorize?response_type=code&client_id=1RLU4US8&code_challenge=[REDACTED]&code_challenge_method=S256&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fcallback&state=[REDACTED]&scope=wsReadWrite&resource=https%3A%2F%2Fmcp.wrike.com%2Fapp%2Fmcp
[DEBUG] MCP server "wrike": Skipping browser open (skipBrowserOpen=true)
[DEBUG] MCP server "wrike": Initial auth result: REDIRECT
... (user completes browser flow, Wrike shows success) ...
[DEBUG] MCP server "wrike": MCP OAuth server cleaned up
[DEBUG] MCP server "wrike": Completing auth flow with authorization code
[DEBUG] MCP server "wrike": Using pre-configured client ID
[DEBUG] MCP server "wrike": Using scope from metadata: wsReadWrite
[DEBUG] MCP server "wrike": Returning code verifier
[DEBUG] MCP server "wrike": Using pre-configured client ID
[DEBUG] MCP server "wrike": Error during auth completion: XmH: client_id must be supplied
[ERROR] MCP OAuth failed for wrike: XmH: client_id must be supplied
After this, on every session start:
[DEBUG] MCP server "wrike": Skipping connection (cached needs-auth)
(The "Skipping connection (cached needs-auth)" loop appears related to #44830.)
Steps to Reproduce
- Add the Wrike MCP server (https://mcp.wrike.com/app/mcp/stream) to Claude Code.
- Open /mcp, find the Wrike entry, click Authenticate.
- Complete the Wrike OAuth flow in the browser — Wrike's page confirms "authentication successful."
- Return to /mcp. Wrike still shows "Needs Auth."
- Click "Check connection" — Server status: needs-auth.
- Open VS Code Output panel → "Claude VSCode" channel — observe "client_id must be supplied" error from Wrike's token endpoint.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
Unknown / N/A — never worked for this user
Claude Code Version
2.1.132 (Claude Code for VS Code extension)
Platform
Other
Operating System
macOS
Terminal/Shell
VS Code integrated terminal
Additional Information
Likely cause: Claude Code's MCP OAuth token-exchange code path does not include client_id in the application/x-www-form-urlencoded body of the POST to the token endpoint when the client is public (no client_secret). Per RFC 6749 §3.2.1, this is required for public clients. Wrike's token endpoint enforces it strictly.
Suggested fix: In the token-exchange request, always include client_id in the form-encoded body when there is no client_secret (i.e., public client / PKCE flow).
Workarounds attempted (none worked):
- Disable + re-enable Wrike entry in /mcp
- Reload VS Code window
- Manual mcp__wrike__authenticate / mcp__wrike__complete_authentication tool flow (failed for an unrelated reason — see #49043: OAuth flow state lost between tool calls)
Related issues:
- #44830 — "Discovery poisoning" causes the "Skipping connection (cached needs-auth)" log lines after token exchange has failed once, blocking retry.
- #49043 — Lost OAuth flow state between authenticate and complete_authentication tool calls. Blocks the tool-based workaround.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗