googleapis/mcp-toolbox · error

failed to read introspection response: %w

Error message

failed to read introspection response: %w

What it means

The introspection endpoint responded successfully (or with a non-401 status the library proceeds past), and validateOpaqueToken reads the response body — capped at 1 MiB via io.LimitReader — with io.ReadAll. If reading the body fails (connection reset mid-response, context cancelled, TLS error during body read), the error is wrapped with this message.

Source

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

	}
	req.Header.Set("Accept", "application/json")

	// Send request to auth server's introspection endpoint
	resp, err := a.client.Do(req)
	if err != nil {
		logger.ErrorContext(ctx, "failed to call introspection endpoint: %v", err)
		return nil, &MCPAuthError{Code: http.StatusInternalServerError, Message: fmt.Sprintf("failed to call introspection endpoint: %v", err), ScopesRequired: a.ScopesRequired}
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		logger.WarnContext(ctx, "introspection failed with status: %d", resp.StatusCode)
		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}

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Retry the request; transient connection resets often resolve on retry.
  2. Check connectivity/proxy/firewall between the toolbox and the introspection endpoint (curl the endpoint directly from the host).
  3. Increase any context/HTTP timeout so the body read is not cut off, and confirm the IdP is healthy.

Example fix

// before: short-lived context cut off mid-read
ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond)
// after
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
Defensive patterns

Strategy: retry

Validate before calling

// preflight reachability check before real traffic
resp, err := http.Head(strings.TrimRight(cfg.AuthorizationServer, "/") + "/.well-known/openid-configuration")
if err != nil {
    return fmt.Errorf("authorization server unreachable: %w", err)
}
resp.Body.Close()

Type guard

null

Try / catch

var claims map[string]any
var err error
for attempt := 0; attempt < 3; attempt++ {
    claims, err = svc.ValidateMCPAuth(ctx, header)
    if err == nil || !strings.Contains(err.Error(), "failed to read introspection response") {
        break
    }
    time.Sleep(time.Duration(1<<attempt) * 100 * time.Millisecond) // transient network error: backoff retry
}

Prevention

When it happens

Trigger: After sending the introspection request and reading resp.Body, io.ReadAll returns a network or context error — e.g. the authorization server closed the connection before the full body arrived or the request context was cancelled mid-read.

Common situations: Unstable network or proxy between toolbox and the IdP, authorization server timeouts killing the connection mid-body, aggressive load balancer idle timeouts, or a context deadline expiring during the read.

Related errors


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