PrefectHQ/fastmcp · error · KeyError
Tool {name!r} not found
Error message
Tool {name!r} not found What it means
remove_tool(name, version=None) scans LocalProvider storage for Tool components matching the given name; when version is None and no matching Tool exists, it raises KeyError(f"Tool {name!r} not found"). The library throws this so callers get a clear signal that removal was a no-op rather than silently succeeding.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/local_provider.py:247
def remove_tool(self, name: str, version: str | None = None) -> None:
"""Remove tool(s) from this provider's storage.
Args:
name: The tool name.
version: If None, removes ALL versions. If specified, removes only that version.
Raises:
KeyError: If no matching tool is found.
"""
if version is None:
# Remove all versions
keys_to_remove = [
k
for k, c in self._components.items()
if isinstance(c, Tool) and c.name == name
]
if not keys_to_remove:
raise KeyError(f"Tool {name!r} not found")
for key in keys_to_remove:
self._remove_component(key)
else:
# Remove specific version - key format is "tool:name@version"
key = f"{Tool.make_key(name)}@{version}"
if key not in self._components:
raise KeyError(f"Tool {name!r} version {version!r} not found")
self._remove_component(key)
def remove_resource(self, uri: str, version: str | None = None) -> None:
"""Remove resource(s) from this provider's storage.
Args:
uri: The resource URI.
version: If None, removes ALL versions. If specified, removes only that version.
Raises:
KeyError: If no matching resource is found.View on GitHub (pinned to 1f02114297)
Solutions
- Check the tool exists first (e.g. via _get_tool or list_tools) before removing
- Confirm the tool is registered on this LocalProvider and not a mounted/proxied provider
- Catch KeyError to make removal idempotent
- Verify exact name spelling and case
Example fix
// before
provider.remove_tool("get_weather") # KeyError if absent
// after
if await provider._get_tool("get_weather") is not None:
provider.remove_tool("get_weather") Defensive patterns
Strategy: try-catch
Validate before calling
names = {t.name for t in provider._list_tools_sync() if hasattr(provider, '_list_tools_sync')} or await list_tools(provider)
if "my_tool" not in names:
return Type guard
def tool_exists(provider, name: str) -> bool:
return any(isinstance(c, Tool) and c.name == name
for c in provider._components.values()) Try / catch
try:
provider.remove_tool("my_tool")
except KeyError:
logger.warning("tool my_tool not present; skipping removal") Prevention
- Confirm the tool is registered on this provider, not a mounted one
- Keep registration and removal names in a shared constant
- Make test/teardown removals idempotent
When it happens
Trigger: Calling provider.remove_tool("name") when no tool with that name was ever added, when it was added to a different provider/server, when it was already removed, or when the name casing/prefix differs from the registered tool name.
Common situations: Teardown code removing a tool that failed to register; test fixtures removing tools mounted elsewhere (mounted providers are not local storage); renaming a tool but not updating cleanup code.
Related errors
- Tool {name!r} version {version!r} not found
- Component {key!r} not found
- Resource {uri!r} not found
- Resource {uri!r} version {version!r} not found
- Template {uri_template!r} not found
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/a2d56af31fe6d5e8.
Report an issue: GitHub.