ConduitMcp.Client.Adapters.Anthropic (ConduitMCP v0.11.0)

Copy Markdown View Source

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 contentAnthropic block
texttext
image (JPEG/PNG/GIF/WebP)image with a base64 source
other images (e.g. SVG), audiotext placeholder "[image: mime, n bytes]"
resource_linktext "[resource] name <uri> (mime)"
embedded text resourcetext with a [resource: uri] header
embedded blob resourceimage 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

tool_calls(response, toolset)

@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.

tool_result(tool_use_id, result, opts \\ [])

Builds the tool_result block answering tool_use_id.

Options

  • :structured - how structuredContent is used: :auto (default, only when content is empty), :prefer, :append or :ignore. It is rendered as JSON text.

tools(toolset, opts \\ [])

@spec tools(ConduitMcp.Client.ToolSet.t(), keyword()) :: [map()]

The tools request parameter for toolset.

Options

  • :cache_control - :last sets "cache_control" => %{"type" => "ephemeral"} on the last tool, caching the whole tool list as a prompt prefix. Default nil (no cache marker).