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
- Remove the duplicate composition so each app is aggregated once
- Give the duplicated app distinct identities/names per registration
- 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
- Ensure each app is composed into an aggregate exactly once
- Use distinct names/namespaces when intentionally including an app multiple times
- Add a startup assertion that tool hashes are unique across the aggregate
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
- Authorization failed for tool '{tool_name}': not found or no
- To decorate a classmethod, use @classmethod above @tool. See
- The function '{fn_name}' has '{params[0]}' as its first para
- Cannot specify both a name as first argument and as keyword
- First argument to @tool must be a function, string, or None,
AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29).
Data as JSON: /api/errors/1d8e5fdaa12a0fc7.
Report an issue: GitHub.