PrefectHQ/fastmcp · error · TypeError
Protocol mode for server {name!r} must be a string
Error message
Protocol mode for server {name!r} must be a string What it means
ClientGroup.from_config reads each server's optional `mode` key (falling back to the group default). Because the value comes from untyped config (model_extra), it is validated: if a server's effective mode is not a string, this TypeError is raised naming the offending server. Mode selects the protocol era (legacy vs modern) for that client.
Source
Thrown at fastmcp_slim/fastmcp/client/group.py:82
cls,
config: MCPConfig | dict[str, Any],
*,
default_mode: ConnectMode = "auto",
) -> ClientGroup:
"""Create one independent client for each configured server.
A server entry may include a FastMCP-specific ``mode`` field. It applies
only to that server; entries without one use ``default_mode``.
"""
parsed = (
config if isinstance(config, MCPConfig) else MCPConfig.from_dict(config)
)
clients: dict[str, Client[Any]] = {}
for name, server in parsed.mcpServers.items():
configured_mode = (server.model_extra or {}).get("mode", default_mode)
if not isinstance(configured_mode, str):
raise TypeError(f"Protocol mode for server {name!r} must be a string")
clients[name] = Client(server.to_transport(), mode=configured_mode)
return cls(clients)
@property
def protocol_versions(self) -> dict[str, str | None]:
return {name: client.protocol_version for name, client in self._clients.items()}
async def __aenter__(self) -> ClientGroup:
if self._exit_stack is not None:
raise RuntimeError("ClientGroup is already connected")
# Claim the stack before the first await so a concurrent entry hits the
# guard above instead of racing past it and overwriting this one.
stack = contextlib.AsyncExitStack()
self._exit_stack = stack
await stack.__aenter__()
View on GitHub (pinned to 1f02114297)
Solutions
- Fix the config so each server's mode is a string, e.g. "mode": "legacy"
- Remove the mode key to inherit the (string) default_mode
- Validate the parsed config before calling from_config
Example fix
// before
{"mcpServers": {"local": {"command": "python", "args": ["srv.py"], "mode": 1}}}
// after
{"mcpServers": {"local": {"command": "python", "args": ["srv.py"], "mode": "legacy"}}} Defensive patterns
Strategy: validation
Validate before calling
mode = (server.model_extra or {}).get("mode", default_mode)
if not isinstance(mode, str):
raise TypeError(f"Server {name!r}: mode must be a string, got {type(mode).__name__}") Type guard
def is_valid_mode(v: object) -> TypeGuard[str]:
return isinstance(v, str) Try / catch
try:
group = ClientGroup.from_config(path)
except TypeError as e:
if "must be a string" in str(e):
logger.error("bad config: %s", e)
raise Prevention
- Quote mode values in YAML/JSON so they parse as strings
- Add schema validation on the config before from_config
- Keep mode values limited to documented strings ('legacy', 'server')
When it happens
Trigger: A config where a server entry has `"mode": 2`, `"mode": null` with no string default, or any non-string value; a non-string default_mode combined with servers lacking a mode key.
Common situations: YAML/JSON typo like `mode: [legacy]` or an accidentally numeric mode; programmatically generated config inserting a wrong-typed mode; misunderstanding that mode must be a string like 'legacy' or 'server'.
Related errors
- ClientGroup requires at least one client
- messages[{i}] must be Message, got {type(item).__name__}. Us
- messages must be str or list[Message], got {type(messages)._
- messages[{i}] must be Message or str, got {type(item).__name
- Invalid timeout type: {type(value)}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/7b6744c6f7c47516.
Report an issue: GitHub.