PrefectHQ/fastmcp · error · ToolError

Ambiguous app tool {tool_name!r}: {len(distinct)} components

Error message

Ambiguous app tool {tool_name!r}: {len(distinct)} components share the identity {tool_hash!r}. The same app is composed more than once, so this call cannot be routed to a single tool.

What it means

ProxyProvider.get_tool_by_hash() resolves an app tool by its identity hash. If more than one distinct tool name maps to the same hash, the same app was composed more than once and the proxy cannot deterministically route a call to a single tool, so it raises ToolError. The hash encodes identity, and duplicate composition breaks that 1:1 mapping.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/proxy.py:996

        for tool in cache.items:
            meta = tool.meta or {}
            fastmcp_meta = meta.get("fastmcp")
            ui_meta = meta.get("ui")
            visibility = (
                ui_meta.get("visibility", []) if isinstance(ui_meta, dict) else []
            )
            if (
                isinstance(fastmcp_meta, dict)
                and fastmcp_meta.get(TOOL_HASH_META_KEY) == tool_hash
                and "app" in visibility
            ):
                matches.append(tool)

        if not matches:
            return None
        distinct = {tool.name for tool in matches}
        if len(distinct) > 1:
            raise ToolError(
                f"Ambiguous app tool {tool_name!r}: {len(distinct)} components share "
                f"the identity {tool_hash!r}. The same app is composed more than "
                f"once, so this call cannot be routed to a single tool."
            )
        return max(matches, key=version_sort_key)

    # -------------------------------------------------------------------------
    # Resource methods
    # -------------------------------------------------------------------------

    async def _list_resources(self) -> Sequence[Resource]:
        """List all resources from the remote server."""
        try:
            client = await self._get_client()
            async with client:
                mcp_resources = await client.list_resources()
                resources = [
                    ProxyResource.from_mcp_resource(self.client_factory, r)

View on GitHub (pinned to 1f02114297)

Solutions

  1. Remove the duplicate app composition so the app is registered exactly once under one name
  2. If intentional versioning is desired, give the composed tools distinct identities/versions so hashes differ
  3. Enumerate tools sharing the hash (filter your tool list by tool_hash) and delete/consolidate the redundant entries before re-registering

Example fix

// before: same app composed twice
server.add_app(my_app, name='app_a')
server.add_app(my_app, name='app_b')
// after: compose once
server.add_app(my_app, name='app_a')
Defensive patterns

Strategy: validation

Validate before calling

from collections import Counter
hashes = Counter(t.key for t in server_tools if is_app_tool(t))
dupes = [h for h, n in hashes.items() if n > 1]
if dupes:
    raise RuntimeError(f'Apps composed more than once: {dupes}')

Try / catch

try:
    tool = await provider.get_tool_by_hash(name, tool_hash)
except ToolError as e:
    if 'Ambiguous app tool' in str(e):
        tool = pick_canonical_tool(name)  # explicit disambiguation policy

Prevention

When it happens

Trigger: Calling get_tool_by_hash() (e.g. during tool-call routing for composed apps) when multiple tools with different names share the same tool_hash because the same app was mounted/composed multiple times on the server.

Common situations: An app (e.g. a Claude-app composed tool set) accidentally registered twice under different names — often from re-running registration code, duplicate mounting in config, or loading the same app from two providers.

Related errors


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