ExMCP
A complete Elixir implementation of the Model Context Protocol (MCP) and Agent Client Protocol (ACP)
Getting Started | User Guide | API Docs | Examples | Changelog
Overview
ExMCP is a comprehensive Elixir implementation of the Model Context Protocol and the Agent Client Protocol, enabling AI models to securely interact with local and remote resources through standardized protocols. It provides both client and server implementations with multiple transport options, including native Phoenix integration via Plug compatibility, plus the ability to control coding agents like Gemini CLI, Claude Code, and Codex via ACP.
Key Features
- Full MCP compliance -- protocol versions 2024-11-05, 2025-03-26, 2025-06-18, and 2025-11-25
- 100% MCP conformance -- 226/226 client checks, 39/39 server checks (official test suite)
- Multiple transports -- HTTP/SSE, stdio, and BEAM-local MCP (~15μs local calls)
- Phoenix Plug -- native Phoenix integration with
ExMCP.HttpPlug - DSL and Handler APIs -- declarative tool/resource/prompt definitions or callback-based handlers
- OAuth 2.1 -- automatic 401→discover→PKCE→token flow, scope step-up, CIMD, JWT client auth (
private_key_jwt), enterprise SSO (ID-JAG), token revocation (RFC 7009), pluggable auth providers - OTP-native -- supervision trees, auto-reconnection with exponential backoff, 88 telemetry events
- Agent Client Protocol (ACP) -- control coding agents and build native Elixir ACP agents
- 3100+ tests -- comprehensive suite including official MCP conformance, integration, and performance
Installation
Add ex_mcp to your dependencies in mix.exs:
def deps do
[
{:ex_mcp, "~> 1.0.0-rc.0"}
]
end
Quick Start
Phoenix Integration
Add MCP server capabilities to your Phoenix app:
# In your Phoenix router
defmodule MyAppWeb.Router do
use MyAppWeb, :router
scope "/api/mcp" do
forward "/", ExMCP.HttpPlug,
handler: MyApp.MCPHandler,
server_info: %{name: "my-phoenix-app", version: "1.0.0"},
sse_enabled: true,
cors_enabled: true
end
end
# Create your MCP handler
defmodule MyApp.MCPHandler do
use ExMCP.Server.Handler
@impl true
def init(_args), do: {:ok, %{}}
@impl true
def handle_initialize(_params, state) do
{:ok, %{
name: "my-phoenix-app",
version: "1.0.0",
capabilities: %{tools: %{}, resources: %{}}
}, state}
end
@impl true
def handle_list_tools(_cursor, state) do
tools = [
%{
name: "get_user_count",
description: "Get total number of users",
inputSchema: %{type: "object", properties: %{}}
}
]
{:ok, tools, nil, state}
end
@impl true
def handle_call_tool("get_user_count", _args, state) do
count = MyApp.Accounts.count_users()
{:ok, [%{type: "text", text: "Total users: #{count}"}], state}
end
end
DSL Server
Define tools, resources, and prompts next to their handlers:
defmodule MyServer do
use ExMCP.Server.Handler
use ExMCP.Server.DSL
tool "greet", "Greets a person by name" do
title "Greeting"
param :name, :string, required: true, description: "Person to greet"
run fn %{name: name}, state ->
{:ok, %{text: "Hello, #{name}!"}, state}
end
end
resource "info://about", "Server information" do
title "About"
mime_type "text/plain"
read fn %{uri: uri}, state ->
{:ok, %{uri: uri, text: "MyServer v1.0", mimeType: "text/plain"}, state}
end
end
prompt "motivate", "Create a short motivational message" do
arg :topic, required: true, description: "Topic to encourage"
render fn %{topic: topic}, state ->
{:ok,
%{
messages: [
%{role: "user", content: %{type: "text", text: "Encourage me about #{topic}"}}
]
}, state}
end
end
end
See the DSL Guide and examples for more patterns.
Standalone Client
# Connect to a stdio-based server
{:ok, client} = ExMCP.Client.start_link(
transport: :stdio,
command: ["node", "my-mcp-server.js"]
)
# List available tools
{:ok, tools} = ExMCP.Client.list_tools(client)
# Call a tool
{:ok, result} = ExMCP.Client.call_tool(client, "search", %{
query: "Elixir programming",
limit: 10
})
BEAM-Local MCP
For trusted Elixir clusters, use BEAM-local MCP services:
defmodule MyToolService do
use ExMCP.Service, name: :my_tools
@impl true
def handle_mcp_request("list_tools", _params, state) do
tools = [
%{
"name" => "ping",
"description" => "Test tool",
"inputSchema" => %{"type" => "object", "properties" => %{}}
}
]
{:ok, %{"tools" => tools}, state}
end
@impl true
def handle_mcp_request("tools/call", %{"name" => "ping"}, state) do
{:ok, %{"content" => [%{"type" => "text", "text" => "Pong!"}]}, state}
end
end
# Start your service (automatically registers with ExMCP.Native)
{:ok, _} = MyToolService.start_link()
# Direct service calls (~15us latency)
{:ok, tools} = ExMCP.Native.call(:my_tools, "list_tools", %{})
ACP: Control and Build Coding Agents
Use the Agent Client Protocol to control coding agents programmatically or expose an Elixir process as an ACP agent:
# Native ACP agents over stdio (Gemini CLI, Hermes, OpenCode, Qwen Code, etc.)
{:ok, client} = ExMCP.ACP.start_client(command: ["gemini", "--acp"])
# Create a session and send a prompt
{:ok, %{"sessionId" => sid}} = ExMCP.ACP.Client.new_session(client, "/my/project")
{:ok, %{"stopReason" => _}} = ExMCP.ACP.Client.prompt(client, sid, "Fix the failing tests")
# Claude Code via the SDK-compatible adapter
{:ok, client} = ExMCP.ACP.start_client(
command: ["claude"],
adapter: ExMCP.ACP.Adapters.ClaudeSDK,
adapter_opts: [model: "sonnet", cwd: "/my/project"]
)
# Legacy Claude stream-json adapter remains available as ExMCP.ACP.Adapters.Claude.
# Pi coding agent through the ACP-native adapter
{:ok, client} = ExMCP.ACP.start_client(
command: ["pi"],
adapter: ExMCP.ACP.Adapters.Pi,
adapter_opts: [model: "anthropic/claude-sonnet-4", thinking_level: "medium"]
)
# Native Elixir ACP agent over stdio
{:ok, agent} = ExMCP.ACP.start_agent(
handler: MyApp.AgentHandler,
agent_info: %{"name" => "my-agent", "version" => "1.0.0"}
)
See the ACP Guide for full details.
Transport Performance
| Transport | Latency | Best For |
|---|---|---|
| BEAM-local | ~15us | Elixir cluster communication |
| stdio | ~1-5ms | Subprocess communication |
| HTTP/SSE | ~5-20ms | Web applications, remote APIs |
Documentation
Getting Started
- Quick Start Guide -- Get running in 5 minutes
- Migration Guide -- Version upgrade instructions
Guides
- User Guide -- Complete feature walkthrough
- Phoenix Integration -- Detailed Phoenix/Plug integration
- DSL Guide -- Declarative server definitions
- ACP Guide -- Agent Client Protocol for controlling coding agents
- Transport Guide -- Transport selection and optimization
- Configuration -- All configuration options
- Security -- Authentication, TLS, and best practices
- Troubleshooting -- Common issues and solutions
Development & API
- Development Guide -- Setup, testing, and contributing
- API Documentation -- Complete API reference
- Architecture -- Internal design decisions
- Examples -- Real-world patterns
Contributing
Contributions welcome! See the Development Guide for setup and testing instructions.
- Fork the repository
- Create a feature branch
- Run
make qualityto ensure code quality - Submit a pull request
License
MIT -- see LICENSE.
Acknowledgments
- The Model Context Protocol and Agent Client Protocol specification creators
- The Elixir community for excellent tooling and libraries
- Contributors and early adopters providing feedback