googleapis/mcp-toolbox · error
failed to decode Google tokeninfo response: %w
Error message
failed to decode Google tokeninfo response: %w
What it means
ValidateMCPAuth unmarshals the tokeninfo response body into a struct with aud/azp/scope fields. If the body is not valid JSON (empty body, HTML error page from a proxy, truncated response), json.Unmarshal fails and the error is wrapped with this message.
Source
Thrown at internal/auth/google/google.go:214
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, &auth.MCPAuthError{Code: http.StatusUnauthorized, Message: fmt.Sprintf("Google token validation failed with status: %d", resp.StatusCode), ScopesRequired: a.ScopesRequired}
}
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, fmt.Errorf("failed to read Google tokeninfo response: %w", err)
}
var tokenInfo struct {
Aud string `json:"aud"`
Azp string `json:"azp"`
Scope string `json:"scope"`
}
if err := json.Unmarshal(body, &tokenInfo); err != nil {
return nil, fmt.Errorf("failed to decode Google tokeninfo response: %w", err)
}
aud := tokenInfo.Aud
if aud == "" {
aud = tokenInfo.Azp
}
audLimit := a.Audience
if audLimit == "" {
audLimit = a.ClientID
}
if audLimit != "" && aud != audLimit {
return nil, &auth.MCPAuthError{Code: http.StatusUnauthorized, Message: "audience validation failed", ScopesRequired: a.ScopesRequired}
}
if len(a.ScopesRequired) > 0 {
tokenScopes := strings.Fields(tokenInfo.Scope)View on GitHub (pinned to 8cc6e09de2)
Solutions
- Log/inspect the raw response body to see what was actually returned
- Bypass or fix the intercepting proxy so oauth2.googleapis.com responses arrive unmodified
- Retry; if persistent, check Google tokeninfo endpoint status
- Capture the wrapped inner error for the exact JSON parse position
Defensive patterns
Strategy: retry
Try / catch
claims, err := svc.ValidateMCPAuth(ctx, h)
if err != nil && strings.Contains(err.Error(), "failed to decode Google tokeninfo response") {
// likely proxy/HTML injection; do not retry blindly — log body and fail auth
return fmt.Errorf("tokeninfo returned non-JSON: %w", err)
} Prevention
- Disable captive-portal/HTML injection on the egress path for API hosts
- Use a dedicated HTTP client with TLS verification; avoid mutating proxies
- Check Content-Type of upstream responses in gateway logs
- Monitor for proxy software injecting error pages with 200 status
When it happens
Trigger: Google returns a 200 with non-JSON or empty body; an intercepting proxy or captive portal injects HTML; response was truncated at the 1 MiB limit mid-JSON.
Common situations: Corporate proxies returning login pages with 200 status; rare Google-side malformed responses; misconfigured service mesh altering bodies.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to create Google tokeninfo request: %w
- failed to read Google tokeninfo response: %w
- `audience` or `clientId` is required when `mcpEnabled` is tr
- `audience` is not allowed when `mcpEnabled` is false
- `scopesRequired` is not allowed when `mcpEnabled` is false
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/6b5b7e46126f362a.
Report an issue: GitHub.