unclecode/crawl4ai · error · HTTPException
tool not found
Error message
tool not found
What it means
The MCP bridge's call_tool handler raises HTTPException(404, 'tool not found') when the requested tool name is not in the tools registry built at attach_mcp time. The registry is fixed when the bridge is attached, so names are exactly those advertised by tools/list.
Source
Thrown at deploy/docker/mcp_bridge.py:149
return None
# MCP handlers
@mcp.list_tools()
async def _list_tools() -> List[t.Tool]:
out = []
for k, (proxy, orig_fn) in tools.items():
desc = getattr(orig_fn, "__mcp_description__", None) or inspect.getdoc(orig_fn) or ""
schema = getattr(orig_fn, "__mcp_schema__", None) or _schema(_body_model(orig_fn))
out.append(
t.Tool(name=k, description=desc, inputSchema=schema)
)
return out
@mcp.call_tool()
async def _call_tool(name: str, arguments: Dict | None) -> List[t.TextContent]:
if name not in tools:
raise HTTPException(404, "tool not found")
proxy, _ = tools[name]
try:
res = await proxy(**(arguments or {}))
except HTTPException as exc:
# map server‑side errors into MCP "text/error" payloads
err = {"error": exc.status_code, "detail": exc.detail}
return [t.TextContent(type = "text", text=json.dumps(err, ensure_ascii=False))]
return [t.TextContent(type = "text", text=json.dumps(res, default=str, ensure_ascii=False))]
@mcp.list_resources()
async def _list_resources() -> List[t.Resource]:
return [
t.Resource(name=k, description=inspect.getdoc(f) or "", mime_type="application/json")
for k, f in resources.items()
]
@mcp.read_resource()View on GitHub (pinned to 7e80152142)
Solutions
- Call tools/list first and use an exact name from the response.
- Refresh the client's tool cache after the server restarts or routes change.
- Ensure custom tools/routes are registered on the FastAPI app before attach_mcp() is called.
Example fix
# before
tools/call {"name": "Crawl", "arguments": {...}}
# after
tools/list -> ["crawl", ...]
tools/call {"name": "crawl", "arguments": {...}} Defensive patterns
Strategy: validation
Validate before calling
names = [t.name for t in (await session.list_tools()).tools]
if tool_name not in names:
raise ValueError(f"tool {tool_name!r} not in {names}") Type guard
def is_known_tool(name, advertised) -> bool:
return isinstance(name, str) and name in advertised Try / catch
result = await session.call_tool(name, args)
if is_error_payload(result):
err = json.loads(result.content[0].text)
if err.get("error") == 404 and "tool not found" in err.get("detail", ""):
advertised = await session.list_tools()
raise ToolNameError(f"{name!r} not in {[t.name for t in advertised.tools]}") Prevention
- Always tools/list before tools/call in new integrations.
- Invalidate the client tool cache on server reconnect.
- Register all tools before attach_mcp() in the server startup order.
When it happens
Trigger: Calling tools/call with a name that has a typo, different casing, or refers to a route/tool registered after attach_mcp ran; using a stale tool list from an earlier process.
Common situations: Client caches the tool list across a server restart where routes changed; renamed endpoints; assuming case-insensitive matching.
Related errors
- resource not found
- e.response.text
- [NSTProxy] API Error: {data.get('msg', 'Unknown error')}
- [NSTProxy] Invalid API response — expected a non-empty list
- Task not found
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/e51ca93fef4c3f4d.
Report an issue: GitHub.