[BUG] MCP client ignores MacOS keychain client secret, only uses value of MCP_CLIENT_SECRET environment variable
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 adding an HTTP MCP server with --client-secret, Claude Code correctly stores the client secret in the macOS keychain under "Claude Code-credentials". However, during the OAuth token exchange, the client_secret parameter is not included in the POST to the authorization server's /token endpoint, causing the exchange to fail.
The secret is present in the keychain but never retrieved at runtime.
This is for an oauth server with a ASM document with content:
{
"issuer": "<host>",
"authorization_endpoint": "<host>/authorize",
"token_endpoint": "<host>/token",
"jwks_uri": "<host>/.well-known/jwks.json",
"token_endpoint_auth_methods_supported": [
"client_secret_post"
],
"grant_types_supported": [
"authorization_code"
],
"code_challenge_methods_supported": [
"S256"
],
"response_types_supported": [
"code"
]
}
Note that this OAuth server does not support DCR.
What Should Happen?
The client_secret stored in the keychain is retrieved and included as client_secret in the POST body of the token exchange request, satisfying client_secret_post authentication.
Error Messages/Logs
# Log from oauth server
[token] request {
grant_type: 'authorization_code',
client_id: '<client id>',
redirect_uri: '<callback uri>',
has_code: true,
has_verifier: true,
has_secret: false
}
Steps to Reproduce
- Add an HTTP MCP server that requires client_secret_post OAuth authentication:
````
MCP_CLIENT_SECRET="my-secret" claude mcp add-json my-server \
'{"type":"http","url":"http://localhost:abc/","oauth":{"clientId":"my-client-id"}}' \
--client-secret
- Confirm the secret was stored — it appears in the keychain:
````
security find-generic-password -s "Claude Code-credentials" -w
# Returns JSON containing:
# { "mcpOAuthClientConfig": { "my-server|<hash>": { "clientSecret": "..." } } }
- Start Claude Code and trigger the OAuth flow via /mcp → Authenticate.
- Complete the browser-based authorization and observe the token exchange request on the authorization server. The client_secret field is absent from the POST body.
The token exchange POST is sent without client_secret. Authorization servers enforcing client_secret_post reject the request with invalid_request / missing required parameters.
Environment
- macOS (Darwin 25.4.0)
- Claude Code (claude-sonnet-4-6)
- Transport: HTTP with OAuth (client_secret_post)
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.116
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Workaround
Setting MCP_CLIENT_SECRET in the shell environment at Claude Code launch time works:
MCP_CLIENT_SECRET="my-secret" claude
or via ~/.zprofile:
export MCP_CLIENT_SECRET="my-secret"
However this is a single global variable, so it cannot support multiple MCP servers with different client secrets simultaneously.
4 Comments
Same problem for me, but workaround doesn't work for some reason. MacOS, iTerm2, Claude Code v2.1.116
Wanted to add alternative workaround, use mcp-remote as a stdio proxy. The underlying @modelcontextprotocol/sdk OAuth client correctly sends credentials in the body, so this avoids the Basic Auth issue entirely:
Important note with mcp-remote:
Callback path is hardcoded to /oauth/callback (not /callback), so the OAuth app's redirect URI must be http://localhost:8080/oauth/callback.
This appears to still be an issue as of Claude 2.1.123, despite release logs indicating that this fix shipped in 2.1.119.
I had Claude introspect its bundled node modules, and it had this to say:
---
⏺ Found the root cause. The project's MCP SDK (1.29.0) has the determineAuthMethod/applyPostAuth logic that sends
client_secret, but those functions don't exist in the MCP SDK bundled inside the Claude Code binary. So despite
being on Claude Code 2.1.123, the bundled SDK predates that feature.
Here's the full picture of what we verified:
Keychain ✓ — correct secret is stored at
my-server|**redacted**:**redacted**~/.claude.json ✓ — all three servers correctly configured with clientId and callbackPort, no stale mcpOAuth
registration overriding the lookup.
Claude Code's clientInformation() ✓ — correctly returns {client_id: O, client_secret: K} using the keychain secret
via the SP() key derivation.
The gap: The project's MCP SDK 1.29.0 has determineAuthMethod() which checks clientInformation.client_secret !==
undefined and selects client_secret_post — but the version bundled in the Claude Code 2.1.123 binary doesn't have
determineAuthMethod, applyPostAuth, or hasClientSecret at all. The token request is sent without authenticating the
client.
The "fix in 2.1.119" either applied to the XAA flow only, or hasn't propagated into the binary's bundled SDK yet.
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.