Google's hosted Gmail/Drive MCP servers advertise no registration_endpoint
in their ASM, so the mcp SDK's create_client_registration_request falls
back to guessing POST {server_origin}/register. Those servers answer with
an opaque 404 (OAuthRegistrationError: Registration failed: 404) and the
gateway burns the reconnect ladder on an unrecoverable failure while the
CLI looks healthy because tools/list is unauthenticated on Google servers.
- Guard: HermesMCPOAuthProvider intercepts the fabricated registration
POST in the bidirectional auth_flow bridge and raises an actionable
OAuthRegistrationError (create an OAuth client, add oauth.client_id/
client_secret under mcp_servers.<name>.oauth in config.yaml) instead of
sending the request. Fires only when the URL is the exact SDK fallback
AND the ASM lacks a registration_endpoint, so servers advertising a
real RFC 7591 endpoint pass through untouched.
- Surfacing: needs_reauth tool errors now include the registration
guidance, and the generic 'MCP call failed' path unwraps TaskGroup
exception groups so the root cause reaches the model instead of
'unhandled errors in a TaskGroup (1 sub-exception)'.
- humanize_oauth_registration_error: 404 registration failures get the
same pre-registered-client guidance as 403 (CLI login path).
Tests: full discovery dance (401 -> PRM -> ASM without registration_endpoint
-> fabricated POST) drives the bridge and asserts the guard raises before
any network send; humanize 404 + passthrough cases; _auth_error_detail and
group unwrap surfacing.
Rebuilt on current main, where mcp_tool.py was decomposed and mcp 2.0
rides on httpx2: the surfacing helpers live in tools/mcp_tool_errors.py
and feed the shared _dispatch / _NEEDS_REAUTH_MSG paths in
tools/mcp_tool_handlers.py; the guard compares with the request's own URL
type and only fires once ASM was actually discovered (an undiscovered
ASM keeps main's discovery-context error, #113771), and closes the inner
SDK generator before raising so context.lock is released immediately.