googleapis/mcp-toolbox · error

failed to construct introspection URL: %w

Error message

failed to construct introspection URL: %w

What it means

When no explicit introspection URL is configured, validateOpaqueToken derives one by joining the configured AuthorizationServer base URL with the 'introspect' path using url.JoinPath. If the join fails (malformed base URL, control characters, invalid escape), the library wraps the error with this message and aborts validation.

Source

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

	err = a.validateClaims(ctx, iss, aud, scopeClaim)
	if err != nil {
		return nil, err
	}
	return claims, nil
}

// validateOpaqueToken validates an opaque token by calling the introspection endpoint
func (a AuthService) validateOpaqueToken(ctx context.Context, tokenStr string) (map[string]any, error) {
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get logger from context: %w", err)
	}

	introspectionURL := a.introspectionURL
	if introspectionURL == "" {
		introspectionURL, err = url.JoinPath(a.AuthorizationServer, "introspect")
		if err != nil {
			return nil, fmt.Errorf("failed to construct introspection URL: %w", err)
		}
	}

	paramName := a.IntrospectionParamName
	if paramName == "" {
		paramName = "token"
	}

	var req *http.Request
	if a.IntrospectionMethod == "GET" {
		u, err := url.Parse(introspectionURL)
		if err != nil {
			return nil, fmt.Errorf("failed to parse introspection URL: %w", err)
		}
		q := u.Query()
		q.Set(paramName, tokenStr)
		u.RawQuery = q.Encode()
		req, err = http.NewRequestWithContext(ctx, "GET", u.String(), nil)

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Check the configured authorizationServer/authUrl value and fix the malformed base URL (include scheme, valid host).
  2. Echo/print the resolved env var to catch stray spaces, quotes, or newline characters.
  3. Alternatively set the explicit introspection URL in the auth config to bypass JoinPath derivation.

Example fix

// before
genericAuth:
  authorizationServer: "idp.example.com/oauth2"  # missing scheme
// after
genericAuth:
  authorizationServer: "https://idp.example.com/oauth2"
Defensive patterns

Strategy: validation

Validate before calling

base := cfg.AuthorizationServer
u, err := url.Parse(base)
if err != nil || u.Scheme == "" || u.Host == "" {
    return fmt.Errorf("authorizationServer %q is not a valid absolute URL", base)
}

Type guard

func isValidAbsoluteURL(s string) bool {
    u, err := url.Parse(s)
    return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
}

Try / catch

claims, err := svc.ValidateMCPAuth(ctx, header)
if err != nil {
    if strings.Contains(err.Error(), "failed to construct introspection URL") {
        log.Fatalf("bad authorizationServer config: %v", err) // startup config bug, not runtime
    }
    return claims, err
}

Prevention

When it happens

Trigger: AuthService.Config.IntrospectionURL is empty and url.JoinPath(a.AuthorizationServer, "introspect") returns an error, e.g. AuthorizationServer set to an invalid value like 'http://[::1' or containing control characters.

Common situations: Typo or truncation in the AuthorizationServer config value, environment variable interpolation injecting whitespace or quotes, or forgetting the scheme (e.g. 'idp.example.com' instead of 'https://idp.example.com').

Related errors


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