OBLETHdocs

Tool loop

Give a model the tools of registered MCP servers and let the gateway run its tool calls, so a plain chat client gets an answer grounded in live results.

3 min readUpdated for v2.4.0Edit on GitHub

You needA registered MCP server and a model with function calling
Turn it onSettings › Boons › Tool loop, then the model's page › Tool servers
It costsEach follow-up model turn, billed as tool_loop

The client sends an ordinary chat request with no tools. The gateway adds the granted servers' tools, runs each tool call the model makes against its MCP server, appends the result, and asks the model again until it answers. Registering servers and calling them directly are covered in MCP gateway.

Turn it on

  1. 1

    Register the MCP server

    Add the server on the MCP Servers page. The page checks each server when it opens and lists its tools, so you can see it answers before granting it.

  2. 2

    Turn the Tool loop on

    Open Settings › Boons, switch Tool loop on and Open its row. Set Turns, Tool timeout and Request deadline under Limits, and edit the Tool nudge if you need to. Save with the save bar.

  3. 3

    Grant the server to the model

    On the model's page, turn on Function calling and Tool choice under Native; the dashboard lets you grant tools only when both are on. Then tick the server under Tool servers and save. You can also grant it from the server's panel on the MCP Servers page.

Or through the Management API:

curl -X PUT http://localhost:9180/api/v1/settings/boons \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tool_loop_enabled": true,
    "tool_loop_max_turns": 4,
    "tool_loop_tool_timeout_ms": 30000,
    "tool_loop_deadline_secs": 300
  }'

curl -X PUT http://localhost:9180/api/v1/models/$MODEL_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "upstream_model": "Qwen/Qwen3-8B",
    "api_base": "http://inference.example.internal:8000/v1",
    "supports_function_calling": true,
    "tool_servers": ["searxng"]
  }'

Ask something that needs a tool

With a web-search server granted, a plain question is enough:

curl http://localhost/v1/chat/completions \
  -H "Authorization: Bearer $OBLETH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "glm-5-3",
    "stream": true,
    "messages": [{"role": "user", "content": "What is the latest vLLM release?"}]
  }'

The response carries x-obleth-boons: tool_loop and contains the final answer only, never tool_calls. The final answer streams token by token. While tools run between turns, the stream sends a one-line marker naming each tool call on the reasoning channel (reasoning_content), not in the answer.

What the model sees

  • The granted servers' tools, merged into the request's tools.
  • For a client that sent no tools of its own, the Tool nudge as a system message: it tells the model it has tools and to use them for current or external facts rather than guessing.
  • After each tool call, the result as a tool message, cut to 16,000 characters.

A client that brings its own tools, such as an IDE assistant, keeps them. The granted tools are added alongside, the gateway runs only its own, and any call to the client's tools goes back to the client unchanged. No nudge is added for such clients.

Settings

Settings labelFieldDefault
Tool loop ontool_loop_enabledfalse
Turnstool_loop_max_turns4, at most 8
Tool timeouttool_loop_tool_timeout_ms30000 ms, per tool call
Request deadlinetool_loop_deadline_secs300 s for the whole loop, 1 to 3600
Tool nudgetool_loop_nudgebuilt-in text; "" puts the default back

A 0 for any number keeps the stored value.

When it applies

The loop runs on a chat request when the Tool loop is on in Settings, the model has at least one server in its tool_servers list, and the model is flagged supports_function_calling. A model granted servers without function calling gets no tools, and the gateway logs a warning.

Deleting an MCP server removes it from every model's tool_servers.

Limits and timeouts
  • Turns. When the model is still asking for tools after the last turn, the gateway removes the tools and asks it to answer from what it has gathered.
  • Calls per turn. At most 16 tool calls run from one model reply; each call past that gets an error result instead.
  • Request deadline. Once it passes, the loop dispatches nothing more. Without streaming, it returns the last completion and adds x-obleth-boons-warning: tool_loop_deadline_exceeded. In a stream, the turn it can't dispatch ends the stream as under Errors.
  • Sessions. One MCP session is opened per server for the whole request. A session that fails is not retried within that request, and a server whose tool list can't be fetched is skipped for 45 seconds.
  • Errors. A failed tool call becomes a text result the model can read and work around. Without streaming, a failed model turn returns the last completion with a warning. In a stream, a turn the upstream cuts off, or a follow-up or final turn that can't be dispatched, ends the stream with an error event and an aborted response, recorded as 502; see When a stream is cut short. An error event the upstream sends is forwarded, followed by [DONE]. The tool calls of a failed turn are not run.
Billing and caching

The first model call is billed as the request itself. Each follow-up turn, including a forced final answer, is a usage row with request_type = tool_loop against the calling tenant and key, and counts toward term budgets. Tool calls to the MCP server are not billed. Answers from the tool loop are never stored in the response cache, since they depend on live tool results.