{"record":{"id":"bed0e4e22576eae5","repo":"shareAI-lab/learn-claude-code","slug":"every-mcp-tool-needs-a-non-empty-name-bed0e4","errorCode":null,"errorMessage":"Every MCP tool needs a non-empty name","messagePattern":"Every MCP tool needs a non-empty name","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"s15_integrated_harness/code.py","lineNumber":2432,"sourceCode":"\n\n# -- MCP System --\n\n# MCP is modeled as late-bound tools: connect first, then discovered server\n# tools are merged into the normal tool pool with mcp__server__tool names.\nclass MCPClient:\n    \"\"\"Small in-process stand-in for MCP tools/list and tools/call.\"\"\"\n\n    def __init__(self, name: str):\n        self.name = name\n        self.tools: list[dict] = []\n        self._handlers: dict[str, callable] = {}\n\n    def register(self, tool_defs: list[dict],\n                 handlers: dict[str, callable]):\n        names = [tool.get(\"name\") for tool in tool_defs]\n        if any(not isinstance(name, str) or not name for name in names):\n            raise ValueError(\"Every MCP tool needs a non-empty name\")\n        if len(set(names)) != len(names):\n            raise ValueError(f\"Duplicate MCP tool name on server {self.name!r}\")\n        missing = [name for name in names if name not in handlers]\n        if missing:\n            raise ValueError(f\"Missing MCP handlers: {', '.join(missing)}\")\n        self.tools = list(tool_defs)\n        self._handlers = dict(handlers)\n\n    def call_tool(self, tool_name: str, args: dict) -> str:\n        handler = self._handlers.get(tool_name)\n        if not handler:\n            return f\"MCP error: unknown tool '{tool_name}'\"\n        try:\n            return str(handler(**args))\n        except Exception as exc:\n            return f\"MCP error: {type(exc).__name__}: {exc}\"\n\n","sourceCodeStart":2414,"sourceCodeEnd":2450,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s15_integrated_harness/code.py#L2414-L2450","documentation":"MCPClient.register() sanity-checks the tool list before accepting it: every tool_def must carry a 'name' that is a non-empty string. This catches malformed registrations (in-process test servers, proxies, or hand-built tool lists) before any tool is exposed, because a nameless tool could never be addressed by call_tool.","triggerScenarios":"Calling register(tool_defs, handlers) where a tool dict lacks 'name', has name=\"\" or None, or a non-string value; JSON tool lists where the key was typo'd ('Name', 'tool_name').","commonSituations":"Building a fake MCP server for tests and forgetting the name field; a proxy renaming keys between wire format and dict; upstream schema change from 'name' to another field.","solutions":["Ensure every tool definition has \"name\": <non-empty string>.","Filter or fix malformed entries before register(): keep only dicts with valid names.","If proxying a real server, pass through tools/list entries unmodified."],"exampleFix":"// before\nclient.register([{\"description\": \"echo\"}], {\"echo\": echo_fn})\n\n// after\nclient.register([{\"name\": \"echo\", \"description\": \"echo\"}], {\"echo\": echo_fn})","handlingStrategy":"validation","validationCode":"def tools_well_named(tool_defs) -> bool:\n    return all(isinstance(t.get(\"name\"), str) and t[\"name\"] for t in tool_defs)\n\ntool_defs = [t for t in tool_defs if isinstance(t.get(\"name\"), str) and t[\"name\"]]","typeGuard":"def has_valid_name(tool_def: dict) -> bool:\n    return isinstance(tool_def, dict) and isinstance(tool_def.get(\"name\"), str) and bool(tool_def[\"name\"].strip())","tryCatchPattern":null,"preventionTips":["Build tool lists with a shared helper that always sets 'name'.","Validate tool_defs in unit tests before register().","Pass through real tools/list payloads instead of hand-writing them."],"tags":["mcp","tool-registration","validation"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}