PrefectHQ/fastmcp · error · KeyError

Resource {uri!r} not found

Error message

Resource {uri!r} not found

What it means

remove_resource(uri, version=None) scans LocalProvider storage for Resource components whose string URI matches; when no match is found it raises KeyError(f"Resource {uri!r} not found"). It signals the caller that removal found nothing to delete.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/local_provider/local_provider.py:275

    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
            ]
            if not keys_to_remove:
                raise KeyError(f"Resource {uri!r} not found")
            for key in keys_to_remove:
                self._remove_component(key)
        else:
            # Remove specific version
            key = f"{Resource.make_key(uri)}@{version}"
            if key not in self._components:
                raise KeyError(f"Resource {uri!r} version {version!r} not found")
            self._remove_component(key)

    def remove_template(self, uri_template: str, version: str | None = None) -> None:
        """Remove resource template(s) from this provider's storage.

        Args:
            uri_template: The template URI pattern.
            version: If None, removes ALL versions. If specified, removes only that version.

        Raises:
            KeyError: If no matching template is found.

View on GitHub (pinned to 1f02114297)

Solutions

  1. Compare the exact str(resource.uri) as registered — check for scheme/slash/case differences first
  2. List existing resources to confirm the exact URI before removal
  3. Catch KeyError to make removal idempotent

Example fix

// before
provider.remove_resource("config://app/")  # registered as "config://app"
// after
provider.remove_resource("config://app")  # exact registered URI
Defensive patterns

Strategy: try-catch

Validate before calling

uris = {str(c.uri) for c in provider._components.values() if isinstance(c, Resource)}
if "config://app" not in uris:
    return

Type guard

def resource_exists(provider, uri: str) -> bool:
    return any(isinstance(c, Resource) and str(c.uri) == uri
               for c in provider._components.values())

Try / catch

try:
    provider.remove_resource("config://app")
except KeyError:
    pass  # idempotent teardown

Prevention

When it happens

Trigger: provider.remove_resource("config://app") when the resource URI differs (scheme, trailing slash, normalization), was never added, was already removed, or lives in another provider.

Common situations: URI stringification mismatches (AnyUrl vs str, case, trailing slash); removing a resource mounted on a different provider; refactoring URIs without updating removal code.

Related errors


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