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

uri must be a string

Error message

uri must be a string

What it means

For resources/read, the server does params["uri"] and requires the value to be a str before consulting its resource table. A missing uri raises KeyError instead, so this specific message means uri exists but is the wrong type — number, dict, list, bool, or None. The check happens before the existence check, so a well-typed unknown URI gives error 107 instead.

Source

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

                for tool in sorted(self.tools.values(), key=lambda item: item.name)
            ]
            return self._complete(
                tools=tools, ttlMs=300_000, cacheScope="public"
            ), []
        if method == "tools/call":
            return self._call_tool(params, metadata)
        if method == "resources/list":
            resources = [
                {"uri": uri, "name": uri.removeprefix("config://")}
                for uri in sorted(self.resources)
            ]
            return self._complete(
                resources=resources, ttlMs=60_000, cacheScope="private"
            ), []
        if method == "resources/read":
            uri = params["uri"]
            if not isinstance(uri, str):
                raise ValueError("uri must be a string")
            if uri not in self.resources:
                raise ValueError("unknown resource")
            return self._complete(
                contents=[
                    {
                        "uri": uri,
                        "mimeType": "application/json",
                        "text": self.resources[uri],
                    }
                ],
                ttlMs=30_000,
                cacheScope="private",
            ), []
        if method == "prompts/list":
            prompts = [
                {"name": name, "description": self.prompts[name]}
                for name in sorted(self.prompts)
            ]

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Pass the URI as a plain string: "study://catalog"
  2. If the URI comes from structured input, extract the string field first (uri["href"], str(uri))
  3. Validate with isinstance(uri, str) before calling exchange

Example fix

# before
server.exchange("resources/read", {"uri": {"scheme": "study", "path": "/catalog"}, "_meta": meta})
# after
server.exchange("resources/read", {"uri": "study://catalog", "_meta": meta})
Defensive patterns

Strategy: type-guard

Validate before calling

uri = payload.get("uri")
if not isinstance(uri, str):
    raise TypeError("uri must be a string")
payload["uri"] = uri

Type guard

def is_valid_uri_param(params: dict) -> bool:
    return isinstance(params.get("uri"), str)

Prevention

When it happens

Trigger: resources/read with uri set to 42, None, {"href": "..."}, or a URL object that was never stringified; JSON where the value is an array of URIs.

Common situations: Passing an identifier object instead of its string field; config-driven URIs loaded as non-strings; f-string templating bugs producing nested structures.

Related errors


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