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

  1. Check the tool exists first (e.g. via _get_tool or list_tools) before removing
  2. Confirm the tool is registered on this LocalProvider and not a mounted/proxied provider
  3. Catch KeyError to make removal idempotent
  4. 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

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


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