shareAI-lab/learn-claude-code · error · ValueError
MCP names cannot normalize to an empty string
Error message
MCP names cannot normalize to an empty string
What it means
normalize_mcp_name substitutes every character outside [a-zA-Z0-9_-] with '_' and raises ValueError if the result is empty. An empty result can only occur when the input string is empty, since substitution always yields at least one character per input character. It guards the mcp__<server>__<tool> namespace from empty segments.
Source
Thrown at s15_integrated_harness/code.py:2467
mcp_clients: dict[str, MCPClient] = {}
_DISALLOWED_CHARS = re.compile(r"[^a-zA-Z0-9_-]")
# Authorization comes from host configuration, never server descriptions.
MCP_HOST_POLICY = {
("docs", "search"): "allow",
("docs", "get_version"): "allow",
("deploy", "status"): "allow",
("deploy", "trigger"): "confirm",
}
def normalize_mcp_name(name: str) -> str:
"""Replace characters outside the model tool-name alphabet."""
normalized = _DISALLOWED_CHARS.sub("_", name)
if not normalized:
raise ValueError("MCP names cannot normalize to an empty string")
return normalized
def _mock_server_docs() -> MCPClient:
client = MCPClient("docs")
client.register(
tool_defs=[
{"name": "search", "description": "Search the documentation.",
"inputSchema": {"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]},
"annotations": {"readOnlyHint": True}},
{"name": "get_version",
"description": "Get the documentation API version.",
"inputSchema": {"type": "object", "properties": {},
"required": []},
"annotations": {"readOnlyHint": True}},
],View on GitHub (pinned to 985456f4ad)
Solutions
- Check the server name before adding the client: skip or fail fast on empty/blank names
- Default unset environment variables to a real name instead of ""
- Validate config-derived names with the same [a-zA-Z0-9_-] alphabet before constructing clients
Example fix
# before
server_name = os.environ.get("MCP_SERVER_NAME", "")
mcp_clients[server_name] = client
# after
server_name = os.environ.get("MCP_SERVER_NAME")
if not server_name:
raise ValueError("MCP_SERVER_NAME must be set")
mcp_clients[server_name] = client Defensive patterns
Strategy: validation
Validate before calling
import re
ALLOWED = re.compile(r"^[a-zA-Z0-9_-]+$")
def safe_server_name(name: str) -> bool:
return isinstance(name, str) and bool(ALLOWED.fullmatch(name))
assert safe_server_name(server_name), f"bad MCP server name: {server_name!r}"
mcp_clients[server_name] = client Type guard
def is_normalizable_name(name) -> bool:
return isinstance(name, str) and name != "" Try / catch
try:
safe = normalize_mcp_name(server_name)
except ValueError:
# empty name: skip or default before wiring the client
safe = "default"
mcp_clients[safe] = client Prevention
- Reject blank config values at load time instead of defaulting to ""
- Keep server names to [a-zA-Z0-9_-] so normalization is the identity
- Assert non-empty names in test fixtures
When it happens
Trigger: Passing an empty server name to the mcp_clients dict (mcp_clients = {"": client}) or registering a tool whose name is the empty string (tool_defs=[{"name": "", ...}]) — though register() itself already rejects empty names, an MCP client constructed or mutated without register() can still hold one.
Common situations: A server name built from a config variable or environment override that is unset (os.environ.get("MCP_SERVER_NAME", ""))). Loading server names from a config file with a missing key. Dynamic server names generated from user input that can be blank.
Related errors
- MCP tool name is longer than 64 characters: {prefixed}
- MCP tool name collision after normalization: {prefixed!r} ma
- Every MCP tool needs a non-empty name
- Duplicate MCP tool name on server {self.name!r}
- Missing MCP handlers: {', '.join(missing)}
AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14).
Data as JSON: /api/errors/7278da4839886afe.
Report an issue: GitHub.