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

  1. Check the server name before adding the client: skip or fail fast on empty/blank names
  2. Default unset environment variables to a real name instead of ""
  3. 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

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


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/7278da4839886afe. Report an issue: GitHub.