charmbracelet/crush · error
url: %w
Error message
url: %w
What it means
MCPConfig.ResolvedURL resolves the MCP server's remote endpoint URL through VariableResolver.ResolveValue, expanding template variables in the configured URL string. Any resolver failure is wrapped as `url: <cause>` — again typically an undefined environment variable or malformed reference inside {{...}}.
Source
Thrown at internal/config/config.go:482
// ResolvedURL returns m.URL expanded through the given resolver. The
// receiver is not mutated. Errors from the resolver are already
// sanitized by ResolveValue and are wrapped with %w for errors.Is/As.
//
// URLs run through the same shell-expansion pipeline as the other
// fields, so a literal '$' (e.g. OData query strings containing
// $filter/$select) must be escaped as '\$' or '${DOLLAR:-$}' to avoid
// being interpreted as a variable reference. Same constraint already
// applies to command, args, env, and headers.
//
// See ResolvedEnv for guidance on picking a resolver.
func (m MCPConfig) ResolvedURL(r VariableResolver) (string, error) {
if m.URL == "" {
return "", nil
}
v, err := r.ResolveValue(m.URL)
if err != nil {
return "", fmt.Errorf("url: %w", err)
}
return v, nil
}
// knownSessionlessMCPs is the set of MCP endpoint URLs (normalized, no
// trailing slash) that are known not to maintain an MCP session — they
// never issue a Mcp-Session-Id and reject the SEP-2575
// "subscriptions/listen" stream. Add an entry when a server is confirmed to
// behave this way.
var knownSessionlessMCPs = map[string]struct{}{
"https://api.github.com/mcp": {},
"https://api.githubcopilot.com/mcp": {},
}
// IsSessionless reports whether the server should be treated as sessionless.
// An explicit Sessionless value wins; when unset, the resolved URL is matched
// against knownSessionlessMCPs (trailing slash ignored). The URL is resolved
// through r so $VAR-expanded endpoints are detected too; on a resolutionView on GitHub (pinned to 7944b8e522)
Solutions
- Read the wrapped cause to see which variable failed to resolve.
- Export the referenced environment variable in the environment where crush runs.
- Alternatively hardcode the URL or supply the value through crush's secrets configuration.
- Correct the {{...}} reference spelling in the MCP url in crushrc.
Example fix
// before (crushrc)
mcp remote --url 'https://{{env.MCP_HOST}}/mcp' // MCP_HOST unset
// after
export MCP_HOST=mcp.example.com # then rerun crush Defensive patterns
Strategy: validation
Validate before calling
# ensure env vars used in MCP urls are set before loading config
grep -o '{{env\.[A-Z_]*}}' crushrc | sed 's/{{env\.//;s/}}//' | sort -u | while read -r v; do
[ -n "${!v+x}" ] || echo "missing env var: $v"
done Try / catch
url, err := m.ResolvedURL(resolver)
if err != nil {
return fmt.Errorf("cannot resolve MCP endpoint url: %w", err)
} Prevention
- Export MCP host/token env vars wherever crush runs, including CI.
- Keep URLs simple; template only the pieces that truly vary by environment.
- Re-test remote MCP configs after rotating secrets or renaming vars.
- Document required env vars next to the MCP block in crushrc.
When it happens
Trigger: Loading/resolving an MCP config with a `url` field containing an unresolvable variable, e.g. url = "https://{{env.MCP_HOST}}/mcp" where MCP_HOST is unset, or an invalid template reference.
Common situations: Remote (HTTP/SSE) MCP endpoints configured with host/token fragments sourced from env vars missing in the current environment; CI runners without the secret; typo in the variable name.
Related errors
- arg %d: %w
- header %s: %w
- invalid mcp command: %w
- oauth_client_id: %w
- mcp stdio config requires a non-empty 'command' field
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/08b4664821e8db71.
Report an issue: GitHub.