langchain-ai/deepagents · error · ValueError

MCPServerInfo {self.name!r}: status='ok' cannot carry an err

Error message

MCPServerInfo {self.name!r}: status='ok' cannot carry an error (got {self.error!r})

What it means

MCPServerInfo's __post_init__ enforces a consistent status/error pairing: a server reporting status='ok' succeeded and must not also carry an error message. Constructing an MCPServerInfo with status='ok' and a non-None error fails this invariant with a ValueError.

Source

Thrown at libs/code/deepagents_code/mcp_tools.py:178

    already covered by `needs_attention()`.
    """

    def __post_init__(self) -> None:
        """Enforce the status/error/tools consistency invariant.

        Raises:
            ValueError: If any of: `status='ok'` with a non-`None` error;
                non-`ok` status without an error message; non-`ok` status
                carrying tools; or `pending_reconnect` set without
                `status='disabled'`.
        """
        if self.status == "ok":
            if self.error is not None:
                msg = (
                    f"MCPServerInfo {self.name!r}: status='ok' cannot carry "
                    f"an error (got {self.error!r})"
                )
                raise ValueError(msg)
        else:
            if self.error is None:
                msg = (
                    f"MCPServerInfo {self.name!r}: status={self.status!r} "
                    "requires an error message"
                )
                raise ValueError(msg)
            if self.tools:
                msg = (
                    f"MCPServerInfo {self.name!r}: status={self.status!r} "
                    "cannot carry tools"
                )
                raise ValueError(msg)
        if self.pending_reconnect and self.status != "disabled":
            msg = (
                f"MCPServerInfo {self.name!r}: pending_reconnect requires "
                f"status='disabled' (got {self.status!r})"
            )

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Set error=None when status is 'ok'; keep the message only for failure statuses.
  2. If you want to surface a non-fatal warning, encode it in a dedicated field or in tools metadata, not in `error`.
  3. Fix fixture/adapter code that copies `error` across status transitions.

Example fix

// before
MCPServerInfo(name="github", status="ok", error="timeout earlier")
// after
MCPServerInfo(name="github", status="ok", error=None)
Defensive patterns

Strategy: type-guard

Validate before calling

def make_server_info(name: str, status: str, error: str | None = None, tools: list | None = None) -> MCPServerInfo:
    if status == "ok" and error is not None:
        raise ValueError("status='ok' cannot carry an error")
    return MCPServerInfo(name=name, status=status, error=error, tools=tools or [])

Type guard

def is_consistent(info: MCPServerInfo) -> bool:
    return (info.error is None) if info.status == "ok" else (info.error is not None)

Try / catch

try:
    info = MCPServerInfo(name=name, status="ok", error=err)
except ValueError as exc:
    print(f"dropping inconsistent server info: {exc}")
    info = MCPServerInfo(name=name, status="ok", error=None)

Prevention

When it happens

Trigger: Programmatically constructing `MCPServerInfo(name=..., status='ok', error=<message>)` in code that aggregates MCP server health (e.g. custom tooling or tests around mcp_tools listing).

Common situations: Adapters wrapping third-party results that set both a success status and a residual error string; refactors that populate error unconditionally; test fixtures copying fields from a failed server info and only changing status.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/7a14455d61b91785. Report an issue: GitHub.