A complete Elixir implementation of the Model Context Protocol (MCP)
Getting Started | User Guide | API Docs | Examples | Changelog
ExMCP is a comprehensive Elixir implementation of the Model Context Protocol, enabling AI models to securely interact with local and remote resources through a standardized protocol. It provides both client and server implementations with multiple transport options, including native Phoenix integration via Plug compatibility.
- Full MCP compliance -- protocol versions 2024-11-05, 2025-03-26, 2025-06-18, and 2025-11-25
- Multiple transports -- HTTP/SSE, stdio, and native BEAM (~15us 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 -- Resource Server, JWT client auth (private_key_jwt), enterprise SSO (ID-JAG)
- OTP-native -- supervision trees, auto-reconnection with exponential backoff, telemetry
- 2600+ tests -- comprehensive suite including TypeScript SDK interop
Add ex_mcp to your dependencies in mix.exs:
def deps do
[
{:ex_mcp, "~> 0.7.4"}
]
endAdd 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
endDefine tools, resources, and prompts declaratively:
defmodule MyServer do
use ExMCP.Server
deftool "greet" do
meta do
name "Greet"
description "Greets a person by name"
end
input_schema %{
type: "object",
properties: %{name: %{type: "string", description: "Person to greet"}},
required: ["name"]
}
end
defresource "info://about" do
meta do
name "About"
description "Server information"
end
mime_type "text/plain"
end
@impl true
def handle_tool_call("greet", %{"name" => name}, state) do
{:ok, %{content: [text("Hello, #{name}!")]}, state}
end
@impl true
def handle_resource_read("info://about", _uri, state) do
{:ok, [text("MyServer v1.0")], state}
end
endSee the DSL Guide and examples for more patterns.
# 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
})For trusted Elixir clusters, use the native BEAM transport:
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", %{})| Transport | Latency | Best For |
|---|---|---|
| Native BEAM | ~15us | Elixir cluster communication |
| stdio | ~1-5ms | Subprocess communication |
| HTTP/SSE | ~5-20ms | Web applications, remote APIs |
- Quick Start Guide -- Get running in 5 minutes
- Migration Guide -- Version upgrade instructions
- User Guide -- Complete feature walkthrough
- Phoenix Integration -- Detailed Phoenix/Plug integration
- DSL Guide -- Declarative server definitions
- Transport Guide -- Transport selection and optimization
- Configuration -- All configuration options
- Security -- Authentication, TLS, and best practices
- Troubleshooting -- Common issues and solutions
- Development Guide -- Setup, testing, and contributing
- API Documentation -- Complete API reference
- Architecture -- Internal design decisions
- Examples -- Real-world patterns
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
MIT -- see LICENSE.
- The Model Context Protocol specification creators
- The Elixir community for excellent tooling and libraries
- Contributors and early adopters providing feedback