[BUG] authServerMetadataUrl is ignored when MCP server discovery succeeds — should override, not fallback
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 configuring an MCP server with authServerMetadataUrl in the oauth config, Claude Code never fetches from the configured URL if the MCP server's standard .well-known discovery endpoints succeed.
We're connecting to Slack's MCP server (mcp.slack.com), which advertises 16 OAuth scopes via /.well-known/oauth-protected-resource (RFC 9728), including access to private channels, DMs, and group messages:
- search:read.private
- search:read.mpim
- search:read.im
- groups:history
- mpim:history
- im:history
Our organization cannot approve these scopes. We set up a proxy serving RFC 8414 authorization server metadata with only 9 approved scopes and configured authServerMetadataUrl to point to it.
Result: Claude Code never makes a request to our proxy (confirmed via server logs). The OAuth authorization URL still contains all 16 scopes from Slack's dynamic discovery. The authServerMetadataUrl is completely ignored.
However, when we block mcp.slack.com in /etc/hosts so that discovery fails, Claude Code DOES fall back to authServerMetadataUrl, our proxy receives the request, and the OAuth URL correctly contains only our 9 approved scopes.
This confirms authServerMetadataUrl only functions as a last-resort fallback when discovery fails — not as an override when explicitly configured.
The docs at https://code.claude.com/docs/en/mcp state:
"If your MCP server's standard OAuth metadata endpoints return errors but the server exposes a working OIDC endpoint, you can point Claude Code at a specific metadata URL to bypass the default discovery chain."
This means organizations cannot restrict OAuth scopes for any MCP server that has working discovery endpoints.
What Should Happen?
When authServerMetadataUrl is explicitly configured by the user, it should take priority over dynamic discovery from the MCP server's .well-known endpoints.
Neither RFC 9728 (Protected Resource Metadata) nor RFC 8414 (Authorization Server Metadata) prescribes a priority between pre-configured and discovered metadata — both leave it to the implementation. If a user has explicitly configured a metadata URL, that intent should be respected over automatic discovery.
Alternatively, an explicit scopes field in the oauth config object would solve this use case:
{
"oauth": {
"scopes": ["search:read.public", "chat:write", "channels:history"]
}
}
Error Messages/Logs
No error is shown. The behavior is silent — authServerMetadataUrl is simply never fetched.
Proxy server logs show zero incoming requests from Claude Code when discovery succeeds.
When discovery is blocked (via /etc/hosts), and using a self-signed cert on localhost, the following error appears:
Error: SDK auth failed: unable to verify the first certificate
This confirms Claude Code attempts to use authServerMetadataUrl only when discovery fails.
Steps to Reproduce
- Set up a proxy serving RFC 8414 authorization server metadata with a subset of scopes:
{
"issuer": "https://slack.com",
"authorization_endpoint": "https://slack.com/oauth/v2_user/authorize",
"token_endpoint": "https://slack.com/api/oauth.v2.user.access",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"code_challenge_methods_supported": ["S256"],
"scopes_supported": [
"search:read.public",
"search:read.files",
"search:read.users",
"chat:write",
"channels:history",
"canvases:read",
"canvases:write",
"users:read",
"users:read.email"
]
}
- Configure MCP in .mcp.json or a config file:
{
"mcpServers": {
"slack": {
"type": "http",
"url": "https://mcp.slack.com/mcp",
"oauth": {
"authServerMetadataUrl": "https://your-proxy/.well-known/oauth-authorization-server",
"clientId": "your-slack-client-id",
"callbackPort": 3118
}
}
}
}
- Run:
claude --strict-mcp-config --mcp-config mcp.json
- Observe the OAuth authorization URL opened in the browser —
scope=parameter contains all 16 scopes from Slack's discovery, not the 9 fromauthServerMetadataUrl.
- Check proxy logs — zero requests received from Claude Code.
- Now block mcp.slack.com:
sudo sh -c 'echo "127.0.0.1 mcp.slack.com" >> /etc/hosts'
- Flush DNS:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
- Run Claude Code again — now the OAuth URL contains only the 9 scopes from
authServerMetadataUrl.
- Clean up:
sudo sed -i '' '/mcp.slack.com/d' /etc/hosts
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.97
Platform
AWS Bedrock
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
Related issues:
- #42419 — Request for explicit scopes config option (Snowflake MCP has the same problem)
- #39618 — Docs update for RFC 9728 discovery behavior (resolved)
Current discovery priority (confirmed via testing):
- RFC 9728:
/.well-known/oauth-protected-resource— tried first - RFC 8414:
/.well-known/oauth-authorization-server— fallback authServerMetadataUrl— last resort, only if both above fail
Workaround: Block MCP server domain in /etc/hosts to force discovery failure. Not viable for production/enterprise use.
Enterprise use case: Organizations using Slack's MCP server need to restrict which OAuth scopes are requested to comply with internal security policies. The current behavior provides no way to do this when the MCP server has working discovery endpoints.
A scopes config option in the oauth object would also solve this:
{
"oauth": {
"scopes": ["search:read.public", "chat:write", "channels:history"]
}
}This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗