dapr/dapr · critical

failed to build HTTP client for %q: %w

Error message

failed to build HTTP client for %q: %w

What it means

Thrown by SessionHolder.connect when mcpauth.BuildHTTPClient fails while creating the HTTP client for the MCP server. BuildHTTPClient resolves the auth config on the MCPServer manifest: OAuth2 client credentials, OIDC discovery, mTLS/CA material from secret refs, and SPIFFE identity via the security handler. Since NewSessionHolder connects eagerly, this error surfaces at MCP-server registration time (analogous to component Init failure) and again on every reconnect attempt.

Source

Thrown at pkg/runtime/wfengine/inprocess/mcp/v1/session.go:149

	if !h.closed.CompareAndSwap(false, true) {
		return
	}
	h.lifecycleCancel()
	h.mu.Lock()
	defer h.mu.Unlock()
	if s := h.session.Load(); s != nil {
		(*s).Close()
		h.session.Store(nil)
	}
}

// connect builds an HTTP client, transport, and MCP session.
// The caller's context controls the connection deadline.
// The lifecycleCtx is passed separately for background work (token refresh) that must outlive the connect call.
func (h *SessionHolder) connect(ctx context.Context) (*mcp.ClientSession, error) {
	httpClient, err := mcpauth.BuildHTTPClient(ctx, h.lifecycleCtx, h.server, h.store, h.sec)
	if err != nil {
		return nil, fmt.Errorf("failed to build HTTP client for %q: %w", h.server.Name, err)
	}

	transport, err := buildTransport(h.server, httpClient)
	if err != nil {
		return nil, fmt.Errorf("failed to build transport for %q: %w", h.server.Name, err)
	}

	workerLog.Debugf("connecting to MCP server %q", h.server.Name)
	c := mcp.NewClient(&mcp.Implementation{Name: mcpClientName, Version: mcpClientVersion}, &mcp.ClientOptions{
		KeepAlive: keepAliveInterval,
	})
	session, err := c.Connect(ctx, transport, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to MCP server %q: %w", h.server.Name, err)
	}
	return session, nil
}

View on GitHub (pinned to 74ad417027)

Solutions

  1. Check the wrapped error — BuildHTTPClient names the failing step (secret fetch, discovery, cert parse)
  2. Verify every secretKeyRef in the MCPServer manifest exists in the declared secret store (kubectl get secret / dapr components)
  3. From inside the daprd pod, curl the issuer/token endpoint to confirm reachability and valid TLS
  4. Validate the caCert/clientCert PEM blocks decode (openssl x509 -noout -text)
  5. Fix credentials if the wrapped error is an auth rejection from the identity provider
  6. Temporarily strip auth from the manifest to confirm connectivity, then re-add auth piecewise

Example fix

# before: secret ref that does not exist
spec:
  endpoint:
    streamableHTTP:
      url: https://mcp.internal/tools
      auth:
        oauth2:
          clientSecret:
            secretKeyRef:
              name: mcp-secrets
              key: client-secret-typo

# after: key matches the actual secret entry
              key: client-secret
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight the auth surface before registering the MCPServer
func preflightAuth(ctx context.Context, srv *mcpserverapi.MCPServer, store *compstore.ComponentStore) error {
    for _, ref := range collectSecretRefs(srv) {
        if _, err := store.GetSecret(ctx, ref); err != nil {
            return fmt.Errorf("secret %q unresolvable: %w", ref, err)
        }
    }
    return nil
}

Type guard

func isHTTPClientBuildFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to build HTTP client")
}

Try / catch

holder, err := NewSessionHolder(ctx, srv, store, sec)
if err != nil && isHTTPClientBuildFailure(err) {
    // config errors are permanent; network blips during OIDC discovery are not —
    // retry a bounded number of times before surfacing
    for range 3 {
        time.Sleep(2 * time.Second)
        if holder, err = NewSessionHolder(ctx, srv, store, sec); err == nil { break }
        if !isHTTPClientBuildFailure(err) { break }
    }
}
if err != nil { return err }

Prevention

When it happens

Trigger: MCPServer spec declares auth (e.g. endpoint.streamableHTTP with oauth2/static bearer via secretKeyRefs) and any of: the secret reference cannot be resolved from the configured secret store; the OIDC issuer/token endpoint is unreachable or returns invalid metadata; client credentials are rejected at discovery; caCert PEM is invalid; the security handler is required but unavailable.

Common situations: secretName/secretKey typo in the manifest; secret store component not loaded before the MCPServer; issuer URL blocked by network policy from the daprd pod; expired/rotated client secret; wrong tokenEndpoint URL; missing SPIFFE/SPIRE setup when workload identity is enabled.

Related errors


AI-assisted analysis of dapr/dapr@74ad417027 (2026-08-16). Data as JSON: /api/errors/0a00a420fcc43b87. Report an issue: GitHub.