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 resolution

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the wrapped cause to see which variable failed to resolve.
  2. Export the referenced environment variable in the environment where crush runs.
  3. Alternatively hardcode the URL or supply the value through crush's secrets configuration.
  4. 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

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


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/08b4664821e8db71. Report an issue: GitHub.