langchain-ai/deepagents · error · ValueError
MCPServerInfo {self.name!r}: status={self.status!r} cannot c
Error message
MCPServerInfo {self.name!r}: status={self.status!r} cannot carry tools What it means
MCPServerInfo is a dataclass whose __post_init__ enforces state invariants. A server whose status is not a live/connectable state (e.g. 'disabled' or 'unauthenticated') must not be constructed with a non-empty tools list, because tools from a non-functional server are meaningless or misleading.
Source
Thrown at libs/code/deepagents_code/mcp_tools.py:191
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})"
)
raise ValueError(msg)
def needs_attention(self) -> bool:
"""Return whether this server is blocked on user login."""
return self.status == "unauthenticated"
_SUPPORTED_REMOTE_TYPES = {"sse", "http"}
"""Supported transport types for remote MCP servers (SSE and HTTP)."""
_TRANSPORT_ALIASES = {"streamable_http": "http", "streamable-http": "http"}
"""Aliases that normalize to canonical transport names.
View on GitHub (pinned to a1af029e6e)
Solutions
- Clear the tools list when the server status is not live (set tools=[])
- Set status to a live state only after tools have actually been loaded
- Check status before constructing MCPServerInfo and drop stale tools
Example fix
// before MCPServerInfo(name='fs', status='disabled', tools=[tool_a]) // after MCPServerInfo(name='fs', status='disabled', tools=[])
Defensive patterns
Strategy: validation
Validate before calling
def can_carry_tools(info) -> bool:
return info.status not in ('disabled', 'unauthenticated')
# before constructing: tools=tools if can_live else [] Type guard
def is_live_server(info: MCPServerInfo) -> bool:
return info.status == 'connected' Try / catch
try:
info = MCPServerInfo(name=name, status=status, tools=tools)
except ValueError as e:
log.warning('dropping tools for non-live server: %s', e)
info = MCPServerInfo(name=name, status=status, tools=[]) Prevention
- Derive tools from live connections only; never persist tools alongside status snapshots
- Add a helper constructor that clears tools for non-live statuses
- Assert status/tools invariants in serialization round-trip tests
When it happens
Trigger: Constructing or rebuilding MCPServerInfo (directly or via from-config/load paths) with status set to 'disabled'/'unauthenticated'/similar while passing a non-empty tools list.
Common situations: Persisting a server snapshot to disk and restoring it with stale tools attached; UI code caching tools across a status change to disabled; merging config state with previously loaded tool lists.
Related errors
- Invalid MCP server name {server_name!r}: token storage names
- Server '{server_name}' config must be a dictionary
- Server '{server_name}' with type '{server_type}' missing req
- Server '{server_name}' has type '{server_type}' (remote) but
- {what} must be absolute: {path}
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/fbf5e4acfa49eb5c.
Report an issue: GitHub.