toolloop
NAME
toolloop.raku - one tool namespace over a local toolkit and a remote MCP server
SYNOPSIS
raku -Ilib -It/lib -I../MCP-Server/lib -I../LLM-Chat/lib -I../Template-Jinja2/lib \
examples/toolloop.raku
DESCRIPTION
An end-to-end run of the thing this distribution exists for: a model asks for tools, some of them run in this process and some of them run inside a server that is a separate operating-system process, and neither the model nor the loop driving it can tell which is which.
Three parts:
A local toolkit ā an ordinary
MCP::Serverwith two tools and no transport at all. It never serialises anything; the registry calls its bridge methods directly.A remote server ā
t/lib/fixture-server.raku, spawned as a child process and spoken to over stdio by anMCP::Client. Its tools come fromMCP::Client::Test::TestKit.A scripted model ā a
LLM::Chat::Backendthat emits a fixed sequence of tool calls instead of talking to an API, so the run is deterministic and costs nothing.
MCP::Client::Registry puts the first two behind one prefixed namespace and
hands the pair of bridge methods to LLM::Chat::ToolLoop.
LLM::Chat is deliberately not a dependency of this distribution ā the bridge
is a shape (tools-for-llm / execute-tool-calls), not a coupling. That is
why this example is run with an explicit -I../LLM-Chat/lib rather than
against an installed MCP::Client.
Why a scripted backend
LLM::Chat::Backend::Mock replays canned text: its @.responses are
Str, and nothing in it sets tool_calls or a tool_calls finish reason,
so it can never drive a tool round. ScriptedBackend below is the smallest
backend that can ā one step per backend call, each step either a list of tool
calls or the final text ā and is adapted from the one in LLM-Chat's own
t/17-tool-loop.rakutest.