PrefectHQ/fastmcp · error · ToolError

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

Error message

Ambiguous app tool {tool_name!r}: {len(matches)} 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

When multiple providers aggregate the same app more than once, several tool components can share the identical tool hash. get_tool_by_hash cannot route a call to a single tool in that case, so it raises ToolError naming the tool, the number of matching components, and the colliding hash.

Source

Thrown at fastmcp_slim/fastmcp/server/providers/aggregate.py:253

        it looking for a missing registration instead of a duplicate one.
        """
        results = await gather(
            (p.get_tool_by_hash(tool_hash, tool_name) for p in self.providers),
            return_exceptions=True,
        )
        matches: list[Tool] = []
        for r in results:
            if isinstance(r, BaseException):
                if isinstance(r, ToolError) or self.provider_error_strategy == "raise":
                    raise r
                continue
            if r is not None:
                matches.append(r)

        if not matches:
            return None
        if len(matches) > 1:
            raise ToolError(
                f"Ambiguous app tool {tool_name!r}: {len(matches)} 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 matches[0]

    # -------------------------------------------------------------------------
    # Resources
    # -------------------------------------------------------------------------

    async def _list_resources(self) -> Sequence[Resource]:
        """List all resources from all providers."""
        results = await gather(
            (p.list_resources() for p in self.providers),
            return_exceptions=True,
        )
        return self._collect_list_results(results, "list_resources")

View on GitHub (pinned to 1f02114297)

Solutions

  1. Remove the duplicate composition so each app is aggregated once
  2. Give the duplicated app distinct identities/names per registration
  3. Catch ToolError and disambiguate by provider path if intentional multiplexing is needed

Example fix

// before
aggregate.include(app)
aggregate.include(app)  # duplicate -> ambiguous hash

// after
aggregate.include(app, name='app-a')
aggregate.include(app, name='app-b')  # or include only once
Defensive patterns

Strategy: try-catch

Validate before calling

tools = await client.list_tools()
names = [t.name for t in tools]
if len(names) != len(set(names)):
    dupes = {n for n in names if names.count(n) > 1}
    raise RuntimeError(f'Duplicate tool identities from repeated app composition: {dupes}')

Try / catch

try:
    tool = await aggregate.get_tool_by_hash(tool_name, tool_hash)
except ToolError as e:
    if 'Ambiguous app tool' in str(e):
        tool = await resolve_tool_by_provider_path(tool_name)  # disambiguate
    else:
        raise

Prevention

When it happens

Trigger: Calling get_tool_by_hash(tool_name, tool_hash) when the same underlying app was composed/registered into the aggregation more than once, producing len(matches) > 1.

Common situations: Mounting the same sub-app under multiple paths without deduplication; accidental double registration during refactor; composite providers including an app already included directly.

Related errors


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