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

  1. Log/inspect the raw response body to see what was actually returned
  2. Bypass or fix the intercepting proxy so oauth2.googleapis.com responses arrive unmodified
  3. Retry; if persistent, check Google tokeninfo endpoint status
  4. 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

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

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/6b5b7e46126f362a. Report an issue: GitHub.