Problem
On release/3.x (through v3.4.6, the current pip install fastmcp), OAuthProxy builds the expected audience for a CIMD private_key_jwt client assertion as f"{self.base_url}/token" (fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:2061). base_url is a pydantic AnyHttpUrl, which renders a bare origin with a trailing slash, so the proxy expects aud=https://host//token while its own metadata advertises token_endpoint=https://host/token. A spec-following client signs the advertised value, so every private_key_jwt token exchange fails with 401 invalid_client ("audience mismatch"). Clients whose CIMD document uses token_endpoint_auth_method: none are unaffected, which makes this look client-specific.
Real-world impact: ChatGPT's MCP connector uses CIMD with private_key_jwt, so 100% of ChatGPT logins fail against a stock bare-origin deployment while Claude (none) works. Hit this in production on 2026-08-08; server logs showed audience mismatch (got 'https://<host>/token', expected 'https://<host>//token').
This was fixed on main by #4659 (shipped in v4.0.0b1), but the fix never landed on release/3.x — v3.4.5 was tagged hours after it merged without it, and v3.4.6 still has the original line. A backport PR already exists (#4783) and was auto-closed only for lack of a linked issue; this issue is filed to unblock it.
MRE
# fastmcp==3.4.6 (condensed from #4783's reproduction)
from unittest.mock import Mock
from key_value.aio.stores.memory import MemoryStore
from starlette.applications import Starlette
from starlette.testclient import TestClient
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
verifier = Mock(spec=JWTVerifier)
verifier.required_scopes = []
proxy = OAuthProxy(
upstream_authorization_endpoint="https://auth.example.com/authorize",
upstream_token_endpoint="https://auth.example.com/token",
upstream_client_id="client-123",
upstream_client_secret="secret-456",
token_verifier=verifier,
base_url="https://api.example.com", # bare origin — the common case
jwt_signing_key="test-secret-value",
client_storage=MemoryStore(),
)
app = Starlette(routes=proxy.get_routes(mcp_path="/mcp"))
with TestClient(app) as c:
advertised = c.get("/.well-known/oauth-authorization-server").json()["token_endpoint"]
expected_aud = f"{proxy.base_url}/token" # the expression proxy.py:2061 uses
print(advertised) # https://api.example.com/token
print(expected_aud) # https://api.example.com//token
assert advertised == expected_aud # AssertionError on v3.4.6
Expected
The expected assertion audience equals the advertised token_endpoint, as on main since #4659.
Ask
Reopen/merge #4783 (a straight backport of #4659) and cut a 3.x patch release, since every stable install with CIMD enabled (the default) rejects all private_key_jwt clients.
Problem
On
release/3.x(throughv3.4.6, the currentpip install fastmcp),OAuthProxybuilds the expected audience for a CIMDprivate_key_jwtclient assertion asf"{self.base_url}/token"(fastmcp_slim/fastmcp/server/auth/oauth_proxy/proxy.py:2061).base_urlis a pydanticAnyHttpUrl, which renders a bare origin with a trailing slash, so the proxy expectsaud=https://host//tokenwhile its own metadata advertisestoken_endpoint=https://host/token. A spec-following client signs the advertised value, so everyprivate_key_jwttoken exchange fails with 401invalid_client("audience mismatch"). Clients whose CIMD document usestoken_endpoint_auth_method: noneare unaffected, which makes this look client-specific.Real-world impact: ChatGPT's MCP connector uses CIMD with
private_key_jwt, so 100% of ChatGPT logins fail against a stock bare-origin deployment while Claude (none) works. Hit this in production on 2026-08-08; server logs showedaudience mismatch (got 'https://<host>/token', expected 'https://<host>//token').This was fixed on
mainby #4659 (shipped inv4.0.0b1), but the fix never landed onrelease/3.x—v3.4.5was tagged hours after it merged without it, andv3.4.6still has the original line. A backport PR already exists (#4783) and was auto-closed only for lack of a linked issue; this issue is filed to unblock it.MRE
Expected
The expected assertion audience equals the advertised
token_endpoint, as onmainsince #4659.Ask
Reopen/merge #4783 (a straight backport of #4659) and cut a 3.x patch release, since every stable install with CIMD enabled (the default) rejects all
private_key_jwtclients.