{"record":{"id":"70ad37ab5d0f7b52","repo":"langchain-ai/deepagents","slug":"str-exc","errorCode":null,"errorMessage":"{str(exc)}","messagePattern":"\\{str\\(exc\\)\\}","errorType":"exception","errorClass":"MCPConfigError","httpStatus":null,"severity":"error","filePath":"libs/code/deepagents_code/mcp_auth.py","lineNumber":2200,"sourceCode":"    # OAuth login is discovery-based (RFC 9728), so it works for any remote\n    # http/sse server — whether the config opted in with `auth: oauth` or the\n    # server was auto-detected as needing auth via a 401 challenge. Only the\n    # transport needs gating; stdio servers can't speak OAuth.\n    transport = _resolve_server_type(server_config)\n    if transport not in {\"http\", \"sse\"}:\n        msg = (\n            f\"Server '{server_name}' uses {transport!r} transport; \"\n            \"OAuth login is only valid for http/sse.\"\n        )\n        raise ValueError(msg)\n    try:\n        resolved_config = resolve_mcp_server_env(server_name, server_config)\n    except (RuntimeError, TypeError) as exc:\n        # Re-raise as MCPConfigError (a ValueError) so callers' existing\n        # config-error handling catches it, and `format_login_failure`\n        # preserves the actionable, field-scoped message instead of\n        # collapsing it to a bare \"RuntimeError\"/\"TypeError\".\n        raise MCPConfigError(str(exc)) from exc\n\n    from deepagents_code.mcp_providers import resolve_provider\n\n    storage = FileTokenStorage(server_name, server_url=resolved_config[\"url\"])\n    policy = resolve_provider(resolved_config[\"url\"])\n    result = await policy.run_login(\n        server_name=server_name,\n        server_url=resolved_config[\"url\"],\n        storage=storage,\n        ui=ui,\n    )\n\n    success_message = f\"Logged in to MCP server '{server_name}'.\"\n    if is_env_truthy(DEBUG):\n        success_message += f\" Tokens saved to {storage.path}.\"\n\n    if result.completed:\n        await ui.show_success(success_message)","sourceCodeStart":2182,"sourceCodeEnd":2218,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/code/deepagents_code/mcp_auth.py#L2182-L2218","documentation":"`login` resolves the server's `${VAR}` env references and field types via `resolve_mcp_server_env` before starting the OAuth flow. If that resolution raises `RuntimeError` (unset env var / malformed `${...}`) or `TypeError` (wrong field type), login re-raises it as `MCPConfigError` (a `ValueError`) so existing config-error handling and `format_login_failure` keep the actionable, field-scoped message instead of collapsing it to a bare `RuntimeError`/`TypeError`.","triggerScenarios":"Calling `login(server_name, server_config, ui)` for an http/sse server whose config contains a `${VAR}` reference to an unset env var, a malformed `${...}` reference, or a supported field (`command`, `url`, `args`, `env`, `headers`) with the wrong type.","commonSituations":"Running `/mcp login` (or `dcode mcp login`) against a server entry whose `url` or `headers` interpolates an env var that is not exported in the current shell, or whose config was hand-edited and a field type drifted (e.g. `args` as a string).","solutions":["Fix the underlying config error named in the message: export the missing env var, correct the `${...}` syntax to `${VAR}` or `${VAR:-default}`, or fix the field's type.","Catch `MCPConfigError` (it is a `ValueError`) around `login` and surface the message via `format_login_failure`, which already renders the field-scoped detail.","Add a `${VAR:-default}` fallback in the config if the variable is optional."],"exampleFix":"// before\nawait login(\"myserver\", {\"url\": \"${MY_API_URL}\"}, ui)  # MY_API_URL unset\n// after\n$ export MY_API_URL=https://api.example.com\n# or in config:\n{\"url\": \"${MY_API_URL:-https://api.example.com}\"}","handlingStrategy":"try-catch","validationCode":"import os, re\nREF = re.compile(r\"\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^{}]*))?\\}\")\ndef validate_config(cfg: dict) -> list[str]:\n    errs = []\n    for f in (\"command\", \"url\"):\n        if f in cfg and not isinstance(cfg[f], str):\n            errs.append(f\"{f} must be a string\")\n    if not isinstance(cfg.get(\"args\", []), list):\n        errs.append(\"args must be a list\")\n    for f in (\"env\", \"headers\"):\n        for k, v in cfg.get(f, {}).items():\n            if not isinstance(v, str):\n                errs.append(f\"{f}.{k} must be a string\")\n    return errs","typeGuard":"def is_str_or_str_list(v: object) -> TypeGuard[str | list[str]]:\n    return isinstance(v, str) or (isinstance(v, list) and all(isinstance(x, str) for x in v))","tryCatchPattern":"try:\n    await login(server_name, server_config, ui)\nexcept (MCPConfigError, ValueError) as exc:  # MCPConfigError subclasses ValueError\n    ui.show_error(format_login_failure(server_name, exc))\nexcept RuntimeError as exc:  # device-flow/handshake failures\n    ...","preventionTips":["Run `resolve_mcp_server_env` on your config in a preflight check before triggering OAuth","Use `${VAR:-default}` for optional variables","Keep the env vars referenced by mcpServers entries in your shell profile so they are set in every session"],"tags":["mcp","oauth","config-validation","env-var"],"backgroundTag":"mcp-config-env-interpolation-failed","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}