A client for Model Context Protocol servers
over the stdio transport, exposing a server's tools as ExAgent.Tools.
An MCP server is an external process that speaks JSON-RPC 2.0 over stdin/stdout
(e.g. npx -y @modelcontextprotocol/server-filesystem ./). This client spawns
it, performs the initialize handshake, lists its tools, and turns each into an
ExAgent.Tool whose execution forwards a tools/call back to the server.
Example
# 1) start the server + handshake
{:ok, client} =
ExAgent.MCP.Client.start_link(
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "./data"]
)
# 2) discover its tools as ExAgent tools
tools = ExAgent.MCP.Client.tools(client)
# 3) use them like any ExAgent tool
agent = ExAgent.new(model: "anthropic:claude-3-5-haiku", tools: tools)
ExAgent.run(agent, "list the files")Why a single client per server
MCP servers are stateful and not generally concurrency-safe; this client
serializes tools/call requests through one GenServer, which is the safe
default. Tool execution in ExAgent already runs tools concurrently — if a
particular MCP server is safe to call in parallel, give each agent its own
client (and thus its own server process).
Testing seam
The transport is pluggable: pass transport: {send_fun, ref} (mostly for
tests) to inject a fake transport instead of spawning a real process. The
client receives data as messages shaped {ref, {:data, binary}}.
Summary
Functions
Call a tool on the server by name. Returns {:ok, text} (the concatenated
content text) or {:error, reason}. Used by the generated ExAgent.Tools.
Returns a specification to start this module under a supervisor.
Shut the client and its server process down.
Start the client, spawn the MCP server (stdio), and perform the initialize
handshake.
List the server's tools as ExAgent.Tools. Each tool's call forwards a
tools/call to the server and returns its text result.
Types
@type t() :: GenServer.server()
Functions
@spec call_tool(GenServer.server(), String.t(), map()) :: {:ok, String.t()} | {:error, term()}
Call a tool on the server by name. Returns {:ok, text} (the concatenated
content text) or {:error, reason}. Used by the generated ExAgent.Tools.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec close(GenServer.server()) :: :ok
Shut the client and its server process down.
@spec start_link(keyword()) :: GenServer.on_start()
Start the client, spawn the MCP server (stdio), and perform the initialize
handshake.
Options
:command— executable to spawn (required for stdio).:args— list of args passed to the command (default[]).:env— list of{bin, bin}env vars (default[]).:timeout— handshake / per-request timeout in ms (default5000).:cd— working directory for the spawned server.:transport—{send_fun, ref}test seam (see moduledoc). When set, no process is spawned;send_fun.(ref, iodata)must deliver bytes to the server and responses must arrive as{ref, {:data, binary}}messages.
@spec tools(GenServer.server()) :: {:ok, [ExAgent.Tool.t()]} | {:error, term()}
List the server's tools as ExAgent.Tools. Each tool's call forwards a
tools/call to the server and returns its text result.