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) == uri

View on GitHub (pinned to 1f02114297)

Solutions

  1. List the tool's registered versions (list all Tool components with that name) and remove an existing version string
  2. Omit the version argument to remove ALL versions of the tool
  3. Catch KeyError for idempotent teardown
  4. 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

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


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/5b1311f1d1de76e5. Report an issue: GitHub.