PrefectHQ/fastmcp · error · ToolError

Tool call limit exceeded: at most {max_tool_calls} call_tool

Error message

Tool call limit exceeded: at most {max_tool_calls} call_tool() invocations are allowed per execute().

What it means

The CodeMode execute tool wraps sandboxed code's call_tool() with a per-execute budget (max_tool_calls). Each invocation increments a counter; once it exceeds the limit a ToolError is raised, protecting the server from runaway generated code.

Source

Thrown at fastmcp_slim/fastmcp/experimental/transforms/code_mode.py:613

                str,
                Field(
                    description=(
                        "Python async code to execute tool calls via call_tool(name, arguments)"
                    )
                ),
            ],
            ctx: Context = None,  # type: ignore[assignment]  # ty:ignore[invalid-parameter-default]
        ) -> Any:
            """Execute tool calls using Python code."""

            call_count = 0

            async def call_tool(tool_name: str, params: dict[str, Any]) -> Any:
                nonlocal call_count
                if max_tool_calls is not None:
                    call_count += 1
                    if call_count > max_tool_calls:
                        raise ToolError(
                            f"Tool call limit exceeded: at most {max_tool_calls} "
                            "call_tool() invocations are allowed per execute()."
                        )

                backend_tools = await transform.get_tool_catalog(ctx)
                tool = transform._find_tool(tool_name, backend_tools)
                if tool is None:
                    raise NotFoundError(f"Unknown tool: {tool_name}")

                result = await ctx.fastmcp.call_tool(tool.name, params)
                return _unwrap_tool_result(result)

            return await transform.sandbox_provider.run(
                code,
                external_functions={"call_tool": call_tool},
            )

        return Tool.from_function(

View on GitHub (pinned to 1f02114297)

Solutions

  1. Raise max_tool_calls when constructing CodeModeTransform to accommodate legitimate workloads
  2. Set max_tool_calls=None to disable the limit (only if generated code is trusted)
  3. Refactor the calling code to batch work into fewer tool calls
  4. Handle ToolError in the caller and instruct the model to reduce tool usage

Example fix

// before
transform = CodeModeTransform(max_tool_calls=5)  # loop calls tool 50x

// after
transform = CodeModeTransform(max_tool_calls=100)  # or batch the loop into one call
Defensive patterns

Strategy: try-catch

Try / catch

try:
    result = await transform.execute(code)
except ToolError as e:
    if "Tool call limit exceeded" in str(e):
        # raise max_tool_calls or reduce tool usage in generated code
        ...
    raise

Prevention

When it happens

Trigger: Generated code inside the sandbox calls call_tool() more than max_tool_calls times during a single execute() — e.g. unbounded loops iterating a tool per item, or retry logic that keeps calling tools.

Common situations: LLM-generated code with while/for loops calling tools repeatedly; processing large datasets one call at a time; misconfigured (too low) max_tool_calls for legitimate workloads.

Related errors


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