Agentic chat loop with tool use — an Elixir port of @huggingface/mcp-client Agent.
Connects to one or more MCP (Model Context Protocol) servers, discovers their tools, and runs a multi-turn agentic loop where the LLM can call tools as needed to answer the user's query.
Quick start
agent = HuggingfaceClient.Agent.new(
model: "meta-llama/Llama-3.1-8B-Instruct",
provider: "groq",
access_token: "hf_...",
servers: [
%{type: "http", url: "https://my-mcp-server.example.com/mcp"}
]
)
# Stream the response
{:ok, stream} = HuggingfaceClient.Agent.run(agent, "What files are in the current directory?")
Enum.each(stream, fn
{:content, text} -> IO.write(text)
{:tool_call, name, result} -> IO.puts("\n[#{name}]: #{result}\n")
:done -> IO.puts("\n--- done ---")
end)MCP server types
%{type: "http", url: "https://..."}— HTTP/SSE MCP server%{type: "sse", url: "https://..."}— SSE-only MCP server
Agent loop
The agent runs up to max_turns (default: 10) rounds of:
- Send messages + available tools to the LLM
- Stream the response, collecting tool calls
- Execute each tool call against the appropriate MCP server
- Add tool results to the conversation
- Repeat until no more tool calls, or
task_complete/ask_questiontools are called
System prompt
A default prompt instructs the LLM to use tools proactively and keep going until
the task is fully resolved. Override with the :prompt option.
Summary
Functions
Discovers tools from all configured MCP servers.
Creates a new agent struct.
Runs the agentic loop for a user input string.
Types
@type t() :: %HuggingfaceClient.Agent{ access_token: String.t() | nil, endpoint_url: String.t() | nil, max_turns: non_neg_integer(), model: String.t(), prompt: String.t(), provider: String.t() | nil, servers: [server_config()], tools: [map()] }
Functions
Discovers tools from all configured MCP servers.
Makes HTTP requests to each server's /tools/list endpoint and merges
the results into the agent's tool list. Returns an updated agent struct.
Example
agent = HuggingfaceClient.Agent.new(model: "...", servers: [...])
{:ok, agent} = HuggingfaceClient.Agent.load_tools(agent)
IO.puts("Available tools: #{length(agent.tools)}")
Creates a new agent struct.
Options
:model— model ID (required):provider— inference provider (e.g."groq","together"):endpoint_url— custom endpoint URL (alternative to provider):access_token— HF or provider API token:servers— list of MCP server configs:prompt— system prompt override:max_turns— max agentic loop iterations (default: 10)
@spec run(t(), String.t() | [map()]) :: {:ok, Enumerable.t()} | {:error, Exception.t()}
Runs the agentic loop for a user input string.
Returns {:ok, stream} where each stream element is one of:
{:delta, text}— streamed text token from the LLM{:tool_start, tool_id, tool_name, args_json}— tool call started{:tool_result, tool_id, tool_name, result}— tool call result received:done— agent turn complete
Example
{:ok, agent} = HuggingfaceClient.Agent.load_tools(agent)
{:ok, stream} = HuggingfaceClient.Agent.run(agent, "List all Python files in the repo")
Enum.each(stream, fn
{:delta, text} -> IO.write(text)
{:tool_result, _, name, result} -> IO.puts("\n[#{name}]: #{result}")
:done -> :ok
end)