{"record":{"id":"7278da4839886afe","repo":"shareAI-lab/learn-claude-code","slug":"mcp-names-cannot-normalize-to-an-empty-string-7278da","errorCode":null,"errorMessage":"MCP names cannot normalize to an empty string","messagePattern":"MCP names cannot normalize to an empty string","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s15_integrated_harness/code.py","lineNumber":2467,"sourceCode":"\n\nmcp_clients: dict[str, MCPClient] = {}\n_DISALLOWED_CHARS = re.compile(r\"[^a-zA-Z0-9_-]\")\n\n# Authorization comes from host configuration, never server descriptions.\nMCP_HOST_POLICY = {\n    (\"docs\", \"search\"): \"allow\",\n    (\"docs\", \"get_version\"): \"allow\",\n    (\"deploy\", \"status\"): \"allow\",\n    (\"deploy\", \"trigger\"): \"confirm\",\n}\n\n\ndef normalize_mcp_name(name: str) -> str:\n    \"\"\"Replace characters outside the model tool-name alphabet.\"\"\"\n    normalized = _DISALLOWED_CHARS.sub(\"_\", name)\n    if not normalized:\n        raise ValueError(\"MCP names cannot normalize to an empty string\")\n    return normalized\n\n\ndef _mock_server_docs() -> MCPClient:\n    client = MCPClient(\"docs\")\n    client.register(\n        tool_defs=[\n            {\"name\": \"search\", \"description\": \"Search the documentation.\",\n             \"inputSchema\": {\"type\": \"object\",\n                             \"properties\": {\"query\": {\"type\": \"string\"}},\n                             \"required\": [\"query\"]},\n             \"annotations\": {\"readOnlyHint\": True}},\n            {\"name\": \"get_version\",\n             \"description\": \"Get the documentation API version.\",\n             \"inputSchema\": {\"type\": \"object\", \"properties\": {},\n                             \"required\": []},\n             \"annotations\": {\"readOnlyHint\": True}},\n        ],","sourceCodeStart":2449,"sourceCodeEnd":2485,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s15_integrated_harness/code.py#L2449-L2485","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nserver_name = os.environ.get(\"MCP_SERVER_NAME\", \"\")\nmcp_clients[server_name] = client\n\n# after\nserver_name = os.environ.get(\"MCP_SERVER_NAME\")\nif not server_name:\n    raise ValueError(\"MCP_SERVER_NAME must be set\")\nmcp_clients[server_name] = client","handlingStrategy":"validation","validationCode":"import re\nALLOWED = re.compile(r\"^[a-zA-Z0-9_-]+$\")\n\ndef safe_server_name(name: str) -> bool:\n    return isinstance(name, str) and bool(ALLOWED.fullmatch(name))\n\nassert safe_server_name(server_name), f\"bad MCP server name: {server_name!r}\"\nmcp_clients[server_name] = client","typeGuard":"def is_normalizable_name(name) -> bool:\n    return isinstance(name, str) and name != \"\"","tryCatchPattern":"try:\n    safe = normalize_mcp_name(server_name)\nexcept ValueError:\n    # empty name: skip or default before wiring the client\n    safe = \"default\"\nmcp_clients[safe] = client","preventionTips":["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"],"tags":["mcp","validation","naming","configuration"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}