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
- Remove the duplicate app composition so the app is registered exactly once under one name
- If intentional versioning is desired, give the composed tools distinct identities/versions so hashes differ
- 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
- Register each app exactly once; guard registration code against re-execution
- Add a startup assertion that tool hashes are unique across the server
- When composing variants, give them distinct identities/versions
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
- Ambiguous app tool {tool_name!r}: {len(matches)} components
- Component already exists: {component.key}
- Remote server returned empty content for {backend_uri}
- Unsupported content type: {type(item)}
- Remote server returned empty content for {parameterized_uri}
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/0da332151a1a16e6.
Report an issue: GitHub.