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

  1. Call the search tool directly, not through the proxy
  2. Only pass names of regular tools discovered via search_tools to the proxy
  3. 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

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


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