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() replaces every character outside the model's tool-name alphabet with '_' (_DISALLOWED_CHARS substitution). If the input consists entirely of disallowed characters (or is empty), the normalized result is '' — not a usable tool-name component — so it raises ValueError instead of producing a garbage name like 'mcp_____'.

Source

Thrown at s14_mcp_plugin/code.py:207

mcp_clients: dict[str, MCPClient] = {}
mcp_tool_policies: dict[str, str] = {}
_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:
    server = MCPClient("docs")
    server.register(
        tool_defs=[
            {
                "name": "search",
                "description": "Search the documentation.",
                "inputSchema": {
                    "type": "object",
                    "properties": {"query": {"type": "string"}},
                    "required": ["query"],
                },
                "annotations": {"readOnlyHint": True},
            },
            {

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Ensure server and tool names contain at least one allowed character (letter/digit) before registration.
  2. Skip and log tool defs with empty/all-symbol names instead of registering them.
  3. Validate config values with a non-empty-after-normalization check at load time.

Example fix

// before
name = normalize_mcp_name(raw_name)  // ValueError if raw_name is '***'

// after
if not re.sub(r'[^A-Za-z0-9_-]', '_', raw_name or '').strip('_'):
    continue  # skip unusable tool name
name = normalize_mcp_name(raw_name)
Defensive patterns

Strategy: type-guard

Validate before calling

import re

def normalizes_nonempty(name: str) -> bool:
    return bool(re.sub(r'[^A-Za-z0-9_-]', '_', name or '').strip('_')) or bool(re.sub(r'[^A-Za-z0-9_-]', '_', name or ''))

Type guard

import re
from typing import TypeGuard

def normalizable_mcp_name(value: object) -> TypeGuard[str]:
    if not isinstance(value, str):
        return False
    return bool(re.sub(r'[^A-Za-z0-9_-]', '_', value))

Prevention

When it happens

Trigger: normalize_mcp_name('') (empty server or tool name); normalize_mcp_name('***') or any all-punctuation string; a tool def whose name is only whitespace/CJK/emoji after stripping.

Common situations: Loading MCP server names or tool names from external config where a value is blank or purely symbolic; generated tool names from descriptions; trimming that empties a string.

Related errors


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