PrefectHQ/fastmcp · error · KeyError
Tool {name!r} version {version!r} not found
Error message
Tool {name!r} version {version!r} not found What it means
When remove_tool is called with an explicit version, LocalProvider builds the versioned key f"{Tool.make_key(name)}@{version}" and raises KeyError if that exact key is absent. This means the tool name exists in concept but that specific version string does not match any registered version.
Source
Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/local_provider.py:254
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.
"""
if version is None:
# Remove all versions
keys_to_remove = [
k
for k, c in self._components.items()
if isinstance(c, Resource) and str(c.uri) == uriView on GitHub (pinned to 1f02114297)
Solutions
- List the tool's registered versions (list all Tool components with that name) and remove an existing version string
- Omit the version argument to remove ALL versions of the tool
- Catch KeyError for idempotent teardown
- Ensure the version string used at removal exactly matches the one used at registration
Example fix
// before
provider.remove_tool("get_weather", "v1") # registered as "1.0.0" -> KeyError
// after
provider.remove_tool("get_weather", "1.0.0") # exact version match Defensive patterns
Strategy: validation
Validate before calling
versions = [c.version for c in provider._components.values()
if isinstance(c, Tool) and c.name == "my_tool"]
if "1.0.0" not in versions:
provider.remove_tool("my_tool") # remove all instead Type guard
def version_registered(provider, name: str, version: str) -> bool:
key = f"{Tool.make_key(name)}@{version}"
return key in provider._components Try / catch
try:
provider.remove_tool("my_tool", "1.0.0")
except KeyError:
provider.remove_tool("my_tool") # fall back to removing all versions Prevention
- Use one canonical version string constant for both add and remove
- Never pass a version unless you know the tool was registered versioned
- Prefer omitting version (removes all) in teardown code
When it happens
Trigger: provider.remove_tool("name", "1.0.0") where the tool was registered with version "1.0" (no patch), unversioned, or under a different version string entirely.
Common situations: Version-string mismatches between registration and removal (e.g. "v1" vs "1.0.0"); removing a version after an earlier teardown already deleted it; assuming a default version exists when the tool was added unversioned.
Related errors
- Tool {name!r} not found
- Resource {uri!r} version {version!r} not found
- Template {uri_template!r} version {version!r} not found
- Prompt {name!r} version {version!r} not found
- Resource {uri!r} not found
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/5b1311f1d1de76e5.
Report an issue: GitHub.