PrefectHQ/fastmcp · error
'{name}' is a synthetic search tool and cannot be called via
Error message
'{name}' is a synthetic search tool and cannot be called via the call_tool proxy What it means
The search_tools transform exposes a call_tool proxy tool for invoking discovered tools, but the synthetic tools it generates (the search tool itself and the proxy) are excluded from that proxy to prevent self-invocation loops. Calling them through the proxy raises ValueError.
Source
Thrown at fastmcp_slim/fastmcp/server/transforms/search/base.py:241
...
def _make_call_tool(self) -> Tool:
"""Create the call_tool proxy that executes discovered tools."""
transform = self
async def call_tool(
name: Annotated[str, "The name of the tool to call"],
arguments: Annotated[
dict[str, Any] | None, "Arguments to pass to the tool"
] = None,
ctx: Context = None, # type: ignore[assignment] # ty:ignore[invalid-parameter-default]
) -> ToolResult:
"""Call a tool by name with the given arguments.
Use this to execute tools discovered via search_tools.
"""
if name in {transform._call_tool_name, transform._search_tool_name}:
raise ValueError(
f"'{name}' is a synthetic search tool and cannot be called via the call_tool proxy"
)
# The name comes from the model, so this proxy is a second way
# into the server that no host mediates. It may reach only what
# the model was allowed to discover.
if not any(
tool.name == name for tool in await transform.get_tool_catalog(ctx)
):
raise NotFoundError(f"Unknown tool: {name!r}")
return await ctx.fastmcp.call_tool(name, arguments)
return Tool.from_function(fn=call_tool, name=self._call_tool_name)
# ------------------------------------------------------------------
# Serialization
# ------------------------------------------------------------------
async def _render_results(self, tools: Sequence[Tool]) -> Any:View on GitHub (pinned to 1f02114297)
Solutions
- Call the search tool directly, not through the proxy
- Only pass names of regular tools discovered via search_tools to the proxy
- Filter synthetic names out of any automated dispatch list
Example fix
// before await call_tool(search_tool_name, args) # rejected // after results = await search_tools(query) # invoke the search tool itself
Defensive patterns
Strategy: validation
Validate before calling
synthetic = {transform._call_tool_name, transform._search_tool_name}
if name in synthetic:
raise ValueError(f"{name!r} must not go through the proxy") Try / catch
try:
await call_tool(name, args)
except ValueError as e:
logger.warning("proxy rejected name: %s", e) Prevention
- Exclude synthetic tool names from automated dispatch
- Have LLM callers invoke the search tool directly
- Validate names against the real tool catalog before proxy calls
When it happens
Trigger: Invoking the proxy call_tool with name equal to the search tool's name or the proxy tool's own name (transform._call_tool_name / transform._search_tool_name).
Common situations: An LLM passing the search tool's name back into the proxy; programmatic code iterating the tool catalog and blindly calling every name through the proxy.
Related errors
- Unknown tool: {name!r}
- 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/a607c17255284226.
Report an issue: GitHub.