-
Notifications
You must be signed in to change notification settings - Fork 196
Description
Bug description
Connecting ToolHive to a remote MCP server that uses a strict OAuth 2.1 implementation (such as Datadog's mcp.datadoghq.com) fails during the PKCE token exchange. The user completes the browser-based authorization successfully, but the token exchange silently fails.
When constructing oauth2.Endpoint structs, ToolHive does not set AuthStyle, which defaults to oauth2.AuthStyleAutoDetect (the zero value). Go's golang.org/x/oauth2 library then uses the following auto-detect strategy:
- First attempt: HTTP Basic Auth — sends
Authorization: Basic base64(client_id:)(empty password for public clients) - If that fails: Retry with
client_idin the POST body
For public PKCE clients registered with token_endpoint_auth_method=none, strict OAuth 2.1 servers reject the Basic Auth attempt — but consume the single-use authorization code in the process. The library's retry with client_id in the POST body then fails with invalid_grant because the code was already burned by the first attempt.
This affects three code paths where oauth2.Endpoint is constructed without AuthStyle:
pkg/auth/oauth/flow.go— authorization code exchange (the primary failure path)pkg/auth/remote/handler.go— token refresh from cached tokenspkg/registry/auth/oauth_token_source.go— registry auth token refresh
Steps to reproduce
- Run a remote MCP server that uses a strict OAuth 2.1 implementation (e.g., Datadog):
thv run --name datadog-mcp https://mcp.datadoghq.com/api/unstable/mcp-server/mcp - Complete the browser-based OAuth authorization when prompted
- Observe the result of the token exchange
Expected behavior
Token exchange succeeds on the first attempt by sending client_id in the POST body, matching the token_endpoint_auth_method=none registration.
Actual behavior
Token exchange fails with:
invalid_grant: authorization code has already been used
The auto-detect logic sends HTTP Basic Auth first, which the strict server rejects while consuming the authorization code. The retry with the correct auth style fails because the code is single-use.
Environment
- ToolHive version: Reproduced on v0.12.1
- OS: macOS (likely all platforms — the issue is in Go library behavior, not OS-specific)