rohitg00/ai-engineering-from-scratch · error · ValueError
unknown resource
Error message
unknown resource
What it means
The uri passed to resources/read is a string but is not a key in the server's in-memory self.resources table. The simulator registers a fixed set of study:// URIs at construction; anything else — http:// URLs, typos, or URIs listed by a different server instance — is rejected. List valid URIs first via resources/list to see what this instance serves.
Source
Thrown at certifications/claude/lessons/11-mcp-server-design-and-integration/code/main.py:270
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)
]
return self._complete(
prompts=prompts, ttlMs=300_000, cacheScope="public"View on GitHub (pinned to 39ea8a1c6d)
Solutions
- Call resources/list (or read server state) and use an exact returned URI
- Compare strings byte-for-byte — trailing slashes and case differences matter since this is a plain dict lookup
- Derive URIs dynamically from the list response instead of hardcoding them
Example fix
# before
server.exchange("resources/read", {"uri": "study://catalog/", "_meta": meta})
# after
listed = server.exchange("resources/list", {"_meta": meta})[0]
uri = listed["resources"][0]["uri"]
server.exchange("resources/read", {"uri": uri, "_meta": meta}) Defensive patterns
Strategy: validation
Validate before calling
listed = server.exchange("resources/list", {"_meta": meta})[0]
known = {r["uri"] for r in listed["resources"]}
if uri not in known:
raise LookupError(f"unknown resource {uri!r}; known: {sorted(known)}") Type guard
def uri_exists(server, uri: str) -> bool:
return uri in getattr(server, "resources", {}) Try / catch
try:
server.exchange("resources/read", params)
except ValueError as e:
if str(e) == "unknown resource":
params["uri"] = pick_from_resources_list() # reselect and retry once
else:
raise Prevention
- Never hardcode URIs; source them from resources/list at runtime
- Log the server's listed URIs on startup so drift is visible immediately
- Byte-compare URIs when debugging — trailing slashes and case are significant
When it happens
Trigger: Reading "study://catalog/typo" when the table has "study://catalog"; hardcoding a URI from an older server version whose registry changed; sending an https:// URL to a server that only serves study:// resources.
Common situations: Stale hardcoded URI lists after server updates; environment differences (dev server registers different resources than prod); copy-pasting example URIs from docs that do not match this deployment.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/b3ab86a16c20e629.
Report an issue: GitHub.