rohitg00/ai-engineering-from-scratch · error · ValueError

name must be a non-empty string

Error message

name must be a non-empty string

What it means

_call_tool validates params['name'] before anything else: it must be a non-empty string. The guard rejects wrong-shape requests before registry lookup, mirroring JSON-RPC -32602 invalid params. It is purely a type/emptiness check, not a lookup failure.

Source

Thrown at certifications/claude/lessons/11-mcp-server-design-and-integration/code/main.py:311

                raise ValueError("name must be a string")
            if name not in self.prompts:
                raise ValueError("unknown prompt")
            return self._complete(
                messages=[
                    {
                        "role": "user",
                        "content": {"type": "text", "text": self.prompts[name]},
                    }
                ]
            ), []
        raise LookupError(f"Method not found: {method}")

    def _call_tool(
        self, params: dict[str, Any], metadata: dict[str, Any]
    ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
        name = params["name"]
        if not isinstance(name, str) or not name:
            raise ValueError("name must be a non-empty string")
        tool = self.tools.get(name)
        if tool is None:
            raise ValueError("unknown tool")
        arguments = tool.validate_arguments(params.get("arguments", {}))
        if name == "prepare_review":
            return self._prepare_review(params, metadata, arguments), []

        token = metadata.get("progressToken")
        notifications: list[dict[str, Any]] = []
        if token is not None:
            if not isinstance(token, (str, int)) or isinstance(token, bool):
                raise ValueError("progressToken must be a string or integer")
            notifications = [
                self._progress(token, 0, 1, "starting"),
                self._progress(token, 1, 1, "complete"),
            ]
        value = tool.handler(arguments)
        return self._complete(

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Pass the tool name as a non-empty string, e.g. {"name": "prepare_review", "arguments": {...}}
  2. Validate the name type at the client boundary before sending the request
  3. Fetch tools/list and copy an advertised name verbatim

Example fix

# before
params = {"name": "", "arguments": {}}

# after
params = {"name": "prepare_review", "arguments": {"topic": "api"}}
Defensive patterns

Strategy: type-guard

Validate before calling

name = payload.get("name")
if not isinstance(name, str) or not name:
    raise ValueError("tools/call name must be a non-empty string")
server.exchange("tools/call", {"name": name, "arguments": payload.get("arguments", {})})

Type guard

def is_nonempty_str(value: object) -> bool:
    return isinstance(value, str) and len(value) > 0

Try / catch

try:
    result = server.exchange("tools/call", params)
except ValueError as exc:
    respond_error(request_id, -32602, str(exc))

Prevention

When it happens

Trigger: tools/call with params like {"name": 123}, {"name": ""}, {"name": ["prepare_review"]}, or a name key holding None.

Common situations: Client builds the tool name from unvalidated user input or config; an SDK serializes name as a non-string; test fixtures pass an empty string to probe validation.

Related errors


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/ca7e250349e3abfd. Report an issue: GitHub.