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:
@@ -14,6 +14,13 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew_EmptyTokenFailsFast(t *testing.T) {
|
||||
c, err := mcpclient.New("http://example.invalid", "")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, c)
|
||||
require.ErrorIs(t, err, mcpclient.ErrTokenRequired)
|
||||
}
|
||||
|
||||
func TestCallTool_Success(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, http.MethodPost, r.Method)
|
||||
@@ -30,13 +37,13 @@ func TestCallTool_Success(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := mcpclient.New(srv.URL, "tok")
|
||||
c, err := mcpclient.New(srv.URL, "tok")
|
||||
require.NoError(t, err)
|
||||
var out struct {
|
||||
OK bool `json:"ok"`
|
||||
N int `json:"n"`
|
||||
}
|
||||
err := c.CallTool(context.Background(), "x_y", map[string]any{"a": 1}, &out)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.CallTool(context.Background(), "x_y", map[string]any{"a": 1}, &out))
|
||||
assert.True(t, out.OK)
|
||||
assert.Equal(t, 7, out.N)
|
||||
}
|
||||
@@ -48,8 +55,9 @@ func TestCallTool_RPCError(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := mcpclient.New(srv.URL, "")
|
||||
err := c.CallTool(context.Background(), "x", nil, nil)
|
||||
c, err := mcpclient.New(srv.URL, "test")
|
||||
require.NoError(t, err)
|
||||
err = c.CallTool(context.Background(), "x", nil, nil)
|
||||
require.Error(t, err)
|
||||
var me *mcpclient.Error
|
||||
require.True(t, errors.As(err, &me))
|
||||
@@ -64,8 +72,9 @@ func TestCallTool_HTTPError(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := mcpclient.New(srv.URL, "")
|
||||
err := c.CallTool(context.Background(), "x", nil, nil)
|
||||
c, err := mcpclient.New(srv.URL, "test")
|
||||
require.NoError(t, err)
|
||||
err = c.CallTool(context.Background(), "x", nil, nil)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "401")
|
||||
}
|
||||
@@ -77,6 +86,7 @@ func TestCallTool_NilResult(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := mcpclient.New(srv.URL, "")
|
||||
c, err := mcpclient.New(srv.URL, "test")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.CallTool(context.Background(), "x", nil, nil))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user