Converts between a ConduitMcp.Client.ToolSet and the Anthropic Messages
API tool format. Pure functions over string-keyed maps; no HTTP.
{toolset, _report} = ConduitMcp.Client.Naming.build([{"files", tools}])
request = %{"model" => model, "messages" => messages,
"tools" => Anthropic.tools(toolset, cache_control: :last)}
# ... the response stops with "tool_use" ...
{calls, rejected} = Anthropic.tool_calls(response, toolset)
# run each %ToolCall{} through the client pipeline (or let
# ConduitMcp.Client.Adapters.execute_tool_calls/4 do all of this), then:
results = Enum.map(outcomes, fn {call, outcome} -> Anthropic.tool_result(call.call_id, outcome) end)
# next user turn: %{"role" => "user", "content" => rejected ++ results ++ other_blocks}Tool definitions
tools/2 emits %{"name", "description", "input_schema"} per tool in
toolset order. Names are the toolset's exposed names, which satisfy
Anthropic's ^[a-zA-Z0-9_-]{1,128}$. Descriptions have invisible
characters (bidi, zero-width, tag, other controls) removed and are capped
at 4 KiB (PI-1). Only tools in the toolset are emitted, so tools removed by
a filter, a collision or a down server never reach the model (PO-7).
No forced tool_choice
This module never builds a tool_choice of type any or tool. Current
Claude models (Opus 5.5, Sonnet 5.5, Fable 5.1, Mythos 5.1) reject forced
tool use with HTTP 400; leave tool_choice unset (or auto/none).
Source: https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use
(verified 2026-10-17).
Results
tool_result/3 maps MCP content into tool_result.content:
| MCP content | Anthropic block |
|---|---|
text | text |
image (JPEG/PNG/GIF/WebP) | image with a base64 source |
other images (e.g. SVG), audio | text placeholder "[image: mime, n bytes]" |
resource_link | text "[resource] name <uri> (mime)" |
embedded text resource | text with a [resource: uri] header |
embedded blob resource | image if an accepted image type, else a placeholder |
is_error is true for a ToolResult with is_error: true and for any
ConduitMcp.Client.Error. tool_result blocks must come first in the
next user turn.
Summary
Functions
Extracts the tool calls from a response.
Builds the tool_result block answering tool_use_id.
The tools request parameter for toolset.
Functions
@spec tool_calls(map() | [map()], ConduitMcp.Client.ToolSet.t()) :: {[ConduitMcp.Client.ToolCall.t()], [map()]}
Extracts the tool calls from a response.
Accepts a Messages API response or assistant message (anything with a
"content" list) or the content block list itself. Every tool_use block
whose name resolves in toolset becomes a ConduitMcp.Client.ToolCall
for the tool's server and MCP name, with call_id set to the block's id.
A tool_use naming a tool that is not in the toolset, or whose input is
not an object, produces no call (nothing may be sent to a server, HB-2).
Instead it yields an is_error tool_result block, returned in the
second list, to put in the next user turn. Other block types are ignored.
@spec tool_result( String.t(), ConduitMcp.Client.ToolResult.t() | ConduitMcp.Client.Error.t(), keyword() ) :: map()
Builds the tool_result block answering tool_use_id.
Options
:structured- howstructuredContentis used::auto(default, only whencontentis empty),:prefer,:appendor:ignore. It is rendered as JSON text.
@spec tools(ConduitMcp.Client.ToolSet.t(), keyword()) :: [map()]
The tools request parameter for toolset.
Options
:cache_control-:lastsets"cache_control" => %{"type" => "ephemeral"}on the last tool, caching the whole tool list as a prompt prefix. Defaultnil(no cache marker).