ExAgent.MCP.Client (ExAgent v1.1.0)

Copy Markdown View Source

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

t()

@type t() :: GenServer.server()

Functions

call_tool(client, name, arguments)

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

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

close(client)

@spec close(GenServer.server()) :: :ok

Shut the client and its server process down.

start_link(opts)

@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 (default 5000).
  • :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.

tools(client)

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