charmbracelet/crush · error
mcp sse config requires a non-empty 'url' field
Error message
mcp sse config requires a non-empty 'url' field
What it means
During MCP client transport construction for an SSE server, the resolved URL is checked after shell-variable resolution. If the URL is empty or only whitespace, startup aborts because an SSE client transport cannot dial a blank endpoint. This catches configs where the `url` key is missing, empty, or resolves (via $VAR/$(cmd)) to nothing.
Source
Thrown at internal/agent/tools/mcp/init.go:1137
if err != nil {
return nil, nil, err
}
client := &http.Client{
Transport: &headerRoundTripper{
headers: headers,
},
}
return &mcp.StreamableClientTransport{
Endpoint: url,
HTTPClient: client,
}, nil, nil
case config.MCPSSE:
url, err := m.ResolvedURL(resolver)
if err != nil {
return nil, nil, err
}
if strings.TrimSpace(url) == "" {
return nil, nil, fmt.Errorf("mcp sse config requires a non-empty 'url' field")
}
headers, err := m.ResolvedHeaders(resolver)
if err != nil {
return nil, nil, err
}
var transport http.RoundTripper = &headerRoundTripper{headers: headers}
var oauthHandler *mcpoauth.Handler
// SSE transports don't support the SDK's OAuthHandler natively,
// so we wrap the HTTP transport with our own round-tripper that
// injects bearer tokens and handles 401-triggered authorization.
// Based on Bruno Krugel's oauthRoundTripper from PR #3396.
if m.OAuth {
tokenSaver := func(tok *oauth.Token) {
if err := cfg.SetConfigField(config.ScopeGlobal, fmt.Sprintf("mcp.%s.oauth_token", name), tok); err != nil {
slog.Warn("Failed to persist MCP OAuth token", "name", name, "error", err)
} else {View on GitHub (pinned to 7944b8e522)
Solutions
- Set a non-empty `url` field for the sse MCP server in crushrc/crush.json, e.g. url = "https://example.com/sse"
- If url uses $VAR or $(cmd), verify the variable is exported and non-empty in the environment Crush launches from
- Run `crush` with the same shell profile that defines the variable, or inline the literal URL
- Use `echo $MCP_URL` in the same shell to confirm it expands to a value before starting Crush
Example fix
// before (crushrc)
mcp context7 {
type sse
url ""
}
// after
mcp context7 {
type sse
url "https://mcp.context7.com/sse"
} Defensive patterns
Strategy: validation
Validate before calling
// before starting crush, resolve like the library does
url := os.Getenv("MCP_URL")
if strings.TrimSpace(url) == "" {
return fmt.Errorf("mcp sse url must be set: MCP_URL=%q", url)
} Try / catch
// Go: wrap startup so a failing MCP does not kill the app
if _, _, err := mcp.CreateTransport(ctx, cfg, name); err != nil {
slog.Warn("MCP transport unavailable", "name", name, "error", err)
} Prevention
- Always set a literal or verified-expanding url for sse servers
- Echo any $VAR used in config values in the launch shell before starting
- Keep MCP config in one place and lint it for required fields
When it happens
Trigger: Defining an MCP server with type = "sse" whose `url` field is absent, an empty string, or a shell expression like "${MCP_URL}" that resolves to an empty value; `m.ResolvedURL` succeeds but TrimSpace(url) == "" at transport creation.
Common situations: Environment variable not exported in the shell Crush runs from; typo in the variable name inside the crushrc url field; user forgot to add the url key when converting an old HTTP proxy config to sse type.
Related errors
- mcp stdio config requires a non-empty 'command' field
- mcp http config requires a non-empty 'url' field
- unsupported mcp type: %s
- not a valid bedrock api key
- not a valid vercel api key
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/53d0acf282d1a225.
Report an issue: GitHub.