Batch
idā the model'stool_call_idfor this slot. Used only to re-stamp a de-duplicated answer, which is what keeps every result filed under the id of the call it answers.concurrentā whether this call may run beside its neighbours. OneFalsemakes the whole batch serial.keyā the de-duplication identity from #sub argument-identity, or an undefinedStrfor a slot that must always run on its own.workā the thunk that produces the answer. Called exactly once per distinct key in a concurrent batch, once per slot in a serial one, and on whatever thread the scheduler picked.
NAME
MCP::Server::Batch - how a batch of LLM tool calls is executed
DESCRIPTION
The scheduler behind MCP::Server.execute-tool-calls and
MCP::Client.execute-tool-calls. It is shared so that the two bridges cannot
drift: a toolkit plugged into a local server and the same toolkit reached over
MCP have to answer a batch the same way, in the same order, with the same
number of executions.
The contract
Results come back one per slot, in the caller's order, always. Order is never a consequence of how fast a tool answered.
A batch runs serially unless every call in it is annotated
readOnlyHintandidempotentHint(concurrency-safe). One unannotated call ā a write, a shell command, a question for the user ā and the whole batch keeps the one-at-a-time ordering it has always had.An eligible batch runs its distinct calls on at most
:$concurrencyworkers, and identical calls execute once, the answer copied into every slot that asked for it with that slot's owntool_call_id.Nothing is lazy. Every thunk has run by the time this returns.
EXAMPLES
use MCP::Server::Batch;
my @slots = @calls.map(-> %call {
my %arguments = %call<arguments>;
%(
id => %call<id>,
concurrent => concurrency-safe(%annotations{%call<name>}),
key => argument-identity(%call<name>, %arguments),
work => -> { run-the-tool(%call<name>, %arguments) },
);
});
my @results = run-tool-batch(@slots, concurrency => 4);
SEE ALSO
MCP::Server (the local bridge), MCP::Client (the remote one).