feat(mcpclient): fail-fast on empty bearer token
mcpclient.New previously accepted an empty token and silently omitted the Authorization header at request time. When the env var sourcing the token was missing from a Kubernetes Secret (envFrom doesn't warn on missing keys), this surfaced as an opaque 401 from the upstream MCP server with no log trail — see hyperguild #13 and brain entry "mcpclient-empty-token-silent-401-envfrom-missing-key". mcpclient.New now returns ErrTokenRequired when token is empty. The routing pod's project_create init checks the error and exits with a clear message pointing at routing-secrets, turning a runtime 401 storm into a startup crashloop the operator can fix immediately. Tests pass a dummy "test" token (httptest servers don't enforce bearer auth, so any non-empty value works). Added a regression test asserting empty-token construction returns ErrTokenRequired. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -20,13 +21,27 @@ type Client struct {
|
||||
http *http.Client
|
||||
}
|
||||
|
||||
// New returns a Client. token may be empty for unauthenticated servers.
|
||||
func New(url, token string) *Client {
|
||||
// ErrTokenRequired is returned by New when token is empty. Empty token
|
||||
// causes mcpclient to omit the Authorization header at request time,
|
||||
// which is silently misread as 401 by bearer-auth servers — see
|
||||
// hyperguild #13 and the brain entry on the failure mode.
|
||||
var ErrTokenRequired = errors.New("mcpclient: token required")
|
||||
|
||||
// New returns a Client. Returns ErrTokenRequired when token is empty:
|
||||
// every MCP server we talk to today is bearer-protected, and an empty
|
||||
// token is always a configuration bug (typically a Kubernetes Secret
|
||||
// missing the expected key, see hyperguild #13). Callers that genuinely
|
||||
// need an unauthenticated client should construct &Client{} directly in
|
||||
// tests, not call New.
|
||||
func New(url, token string) (*Client, error) {
|
||||
if token == "" {
|
||||
return nil, ErrTokenRequired
|
||||
}
|
||||
return &Client{
|
||||
url: url,
|
||||
token: token,
|
||||
http: &http.Client{Timeout: 60 * time.Second},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithHTTPClient overrides the underlying HTTP client (test injection).
|
||||
|
||||
Reference in New Issue
Block a user