googleapis/mcp-toolbox · error

failed to parse introspection response: %w

Error message

failed to parse introspection response: %w

What it means

The introspection response body was read, but json.Unmarshal into the expected shape ({active, scope, aud, audience, exp, iss}) failed, meaning the body is not valid JSON or does not match. The library requires RFC 7662-style introspection JSON to decide whether the token is active.

Source

Thrown at internal/auth/generic/generic.go:414

		return nil, &MCPAuthError{Code: http.StatusUnauthorized, Message: fmt.Sprintf("introspection 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 introspection response: %w", err)
	}

	var introspectResp struct {
		Active   *bool           `json:"active"`
		Scope    string          `json:"scope"`
		Aud      json.RawMessage `json:"aud"`
		Audience json.RawMessage `json:"audience"`
		Exp      json.Number     `json:"exp"`
		Iss      string          `json:"iss"`
	}

	if err := json.Unmarshal(body, &introspectResp); err != nil {
		return nil, fmt.Errorf("failed to parse introspection response: %w", err)
	}

	if introspectResp.Active == nil || !*introspectResp.Active {
		logger.InfoContext(ctx, "token is not active")
		return nil, &MCPAuthError{Code: http.StatusUnauthorized, Message: "token is not active", ScopesRequired: a.ScopesRequired}
	}

	var expVal int64
	if introspectResp.Exp != "" {
		expVal, err = introspectResp.Exp.Int64()
		if err != nil {
			logger.WarnContext(ctx, "failed to parse exp claim in introspection response: %v", err)
			return nil, &MCPAuthError{Code: http.StatusUnauthorized, Message: "invalid exp claim", ScopesRequired: a.ScopesRequired}
		}
	}

	// Verify expiration (with 1 minute leeway)
	const leeway = 60

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. curl the introspection endpoint with a valid token and confirm the body is RFC 7662 JSON containing an 'active' boolean.
  2. Fix the introspectionUrl/authorizationServer config so it points at the actual introspection API, not a login or error page.
  3. Check the auth server's status/logs — a 200-with-HTML response usually signals a gateway or misrouting problem.

Example fix

// before: endpoint returns HTML
<html><body>404 Not Found</body></html>
// after: correct introspection endpoint returns RFC 7662 JSON
{"active":true,"scope":"read write","exp":1893456000}
Defensive patterns

Strategy: validation

Validate before calling

resp, err := http.PostForm(cfg.IntrospectionURL, url.Values{"token": {testToken}})
if err != nil { return err }
defer resp.Body.Close()
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
    return fmt.Errorf("introspection endpoint returned %q, expected JSON — check the URL points at the RFC 7662 API", ct)
}

Type guard

func isRFC7662Response(body []byte) bool {
    var r struct{ Active *bool `json:"active"` }
    return json.Unmarshal(body, &r) == nil && r.Active != nil
}

Try / catch

claims, err := svc.ValidateMCPAuth(ctx, header)
if err != nil {
    if strings.Contains(err.Error(), "failed to parse introspection response") {
        // introspection endpoint returned non-JSON: verify URL points at the introspection API
        return claims, fmt.Errorf("introspection endpoint misconfigured: %w", err)
    }
    return claims, err
}

Prevention

When it happens

Trigger: The configured introspection endpoint returns non-JSON content (HTML login/error page, empty body, plain text), or JSON whose structure breaks decoding into the anonymous struct — then the wrapped error is returned from validateOpaqueToken.

Common situations: introspectionUrl pointing at a UI route instead of the token-introspection API, an auth-server error page (502/503 HTML) returned with 200, wrong path (e.g. '/userinfo' instead of '/introspect'), or a proxy injecting HTML.

Understand the failure class

Related errors


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