{"record":{"id":"53d0acf282d1a225","repo":"charmbracelet/crush","slug":"mcp-sse-config-requires-a-non-empty-url-field","errorCode":null,"errorMessage":"mcp sse config requires a non-empty 'url' field","messagePattern":"mcp sse config requires a non-empty 'url' field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/mcp/init.go","lineNumber":1137,"sourceCode":"\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tclient := &http.Client{\n\t\t\tTransport: &headerRoundTripper{\n\t\t\t\theaders: headers,\n\t\t\t},\n\t\t}\n\t\treturn &mcp.StreamableClientTransport{\n\t\t\tEndpoint:   url,\n\t\t\tHTTPClient: client,\n\t\t}, nil, nil\n\tcase config.MCPSSE:\n\t\turl, err := m.ResolvedURL(resolver)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif strings.TrimSpace(url) == \"\" {\n\t\t\treturn nil, nil, fmt.Errorf(\"mcp sse config requires a non-empty 'url' field\")\n\t\t}\n\t\theaders, err := m.ResolvedHeaders(resolver)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tvar transport http.RoundTripper = &headerRoundTripper{headers: headers}\n\t\tvar oauthHandler *mcpoauth.Handler\n\n\t\t// SSE transports don't support the SDK's OAuthHandler natively,\n\t\t// so we wrap the HTTP transport with our own round-tripper that\n\t\t// injects bearer tokens and handles 401-triggered authorization.\n\t\t// Based on Bruno Krugel's oauthRoundTripper from PR #3396.\n\t\tif m.OAuth {\n\t\t\ttokenSaver := func(tok *oauth.Token) {\n\t\t\t\tif err := cfg.SetConfigField(config.ScopeGlobal, fmt.Sprintf(\"mcp.%s.oauth_token\", name), tok); err != nil {\n\t\t\t\t\tslog.Warn(\"Failed to persist MCP OAuth token\", \"name\", name, \"error\", err)\n\t\t\t\t} else {","sourceCodeStart":1119,"sourceCodeEnd":1155,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/mcp/init.go#L1119-L1155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (crushrc)\nmcp context7 {\n  type sse\n  url \"\"\n}\n// after\nmcp context7 {\n  type sse\n  url \"https://mcp.context7.com/sse\"\n}","handlingStrategy":"validation","validationCode":"// before starting crush, resolve like the library does\nurl := os.Getenv(\"MCP_URL\")\nif strings.TrimSpace(url) == \"\" {\n    return fmt.Errorf(\"mcp sse url must be set: MCP_URL=%q\", url)\n}","typeGuard":null,"tryCatchPattern":"// Go: wrap startup so a failing MCP does not kill the app\nif _, _, err := mcp.CreateTransport(ctx, cfg, name); err != nil {\n    slog.Warn(\"MCP transport unavailable\", \"name\", name, \"error\", err)\n}","preventionTips":["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"],"tags":["mcp","config","sse","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}