Condensed rules for AI agents and developers using
mcp_elixir_sdk. For full documentation see hexdocs.
What This Package Does
mcp_elixir_sdk is an Elixir SDK for the Model Context Protocol (MCP) — an open protocol for integrating LLM applications with external data sources and tools. It provides both client and server implementations with pluggable transports. Protocol version: 2025-11-25.
Public modules live under the MCP.* namespace (e.g. MCP.Client, MCP.Server).
Installation
# mix.exs deps
{:mcp_elixir_sdk, "~> 1.0"}
# Also add these for Streamable HTTP transport:
{:req, "~> 0.5"} # HTTP client
{:plug, "~> 1.16"} # HTTP framework
{:bandit, "~> 1.5"} # HTTP serverThe stdio transport requires no additional dependencies.
Client Rules
Starting a Client
Always provide :transport and :client_info:
{:ok, client} = MCP.Client.start_link(
transport: {MCP.Transport.Stdio, command: "mcp-server", args: []},
client_info: %{name: "my_app", version: "1.0.0"}
)Connection Lifecycle
You must call connect/1 before any other operation. It performs the MCP initialization handshake:
{:ok, info} = MCP.Client.connect(client)
# info contains server_info, capabilities, protocol_version, instructionsCall MCP.Client.close/1 when done.
Available Client Operations
| Function | Purpose |
|---|---|
connect/1 | Initialize handshake (required first) |
list_tools/1,2 | List server tools (with optional cursor) |
call_tool/3,4 | Call a tool by name with arguments |
list_resources/1,2 | List server resources |
read_resource/2 | Read a resource by URI |
list_prompts/1,2 | List server prompts |
get_prompt/3 | Get a prompt by name with arguments |
list_all_tools/1 | Auto-paginate all tools |
list_all_resources/1 | Auto-paginate all resources |
list_all_prompts/1 | Auto-paginate all prompts |
close/1 | Disconnect and clean up |
Client Feature Callbacks
To support server-initiated requests, pass callback options to start_link/1:
MCP.Client.start_link(
transport: {transport_mod, transport_opts},
client_info: %{name: "my_app", version: "1.0.0"},
on_sampling: fn params -> {:ok, %{"role" => "assistant", "content" => %{"type" => "text", "text" => "response"}, "model" => "my-model", "stopReason" => "endTurn"}} end,
on_roots_list: fn _params -> {:ok, %{"roots" => [%{"uri" => "file:///path", "name" => "Root"}]}} end,
on_elicitation: fn params -> {:ok, %{"action" => "accept", "content" => %{}}} end,
notification_handler: fn method, params -> IO.puts("#{method}") end
)Capabilities are auto-advertised when callbacks are provided.
Server Rules
Implementing a Handler
Create a module with @behaviour MCP.Server.Handler. Only init/1 is required — implement additional callbacks to enable features. The server auto-detects capabilities based on which callbacks you implement.
defmodule MyHandler do
@behaviour MCP.Server.Handler
@impl true
def init(_opts), do: {:ok, %{}}
@impl true
def handle_list_tools(_cursor, state) do
tools = [
%{
"name" => "my_tool",
"description" => "Does something",
"inputSchema" => %{
"type" => "object",
"properties" => %{"arg" => %{"type" => "string"}},
"required" => ["arg"]
}
}
]
{:ok, tools, nil, state}
end
@impl true
def handle_call_tool("my_tool", %{"arg" => arg}, state) do
{:ok, [%{"type" => "text", "text" => "Result: #{arg}"}], state}
end
endHandler Callback Signatures
| Callback | Arity | Return |
|---|---|---|
init/1 | 1 | {:ok, state} |
handle_list_tools/2 | 2 | {:ok, tools, next_cursor, state} |
handle_call_tool/3 | 3 | {:ok, content, state} or {:error, code, message, state} |
handle_call_tool/4 | 4 | Same as /3, but receives ToolContext for async ops |
handle_list_resources/2 | 2 | {:ok, resources, next_cursor, state} |
handle_read_resource/2 | 2 | {:ok, contents, state} or {:error, code, message, state} |
handle_list_prompts/2 | 2 | {:ok, prompts, next_cursor, state} |
handle_get_prompt/3 | 3 | {:ok, result, state} or {:error, code, message, state} |
handle_complete/3 | 3 | {:ok, completion, state} |
handle_set_log_level/2 | 2 | {:ok, state} |
handle_subscribe/2 | 2 | {:ok, state} or {:error, code, message, state} |
handle_unsubscribe/2 | 2 | {:ok, state} or {:error, code, message, state} |
handle_list_resource_templates/2 | 2 | {:ok, templates, next_cursor, state} |
Starting a Server
Stdio:
{:ok, server} = MCP.Server.start_link(
transport: {MCP.Transport.Stdio, mode: :server},
handler: {MyHandler, []},
server_info: %{name: "my_server", version: "1.0.0"}
)Streamable HTTP (Plug + Bandit):
plug_config = MCP.Transport.StreamableHTTP.Plug.init(
server_mod: MyHandler,
server_opts: [server_info: %{name: "my_server", version: "1.0.0"}]
)
{:ok, _bandit} = Bandit.start_link(
plug: {MCP.Transport.StreamableHTTP.Plug, plug_config},
port: 8080,
ip: {127, 0, 0, 1}
)Async Tool Execution with ToolContext
Implement handle_call_tool/4 (4-arity) instead of /3 to get a ToolContext for sending log messages, progress updates, and making server-to-client requests:
def handle_call_tool("analyze", args, ctx, state) do
ToolContext.log(ctx, "info", "Starting analysis")
ToolContext.send_progress(ctx, 0, 100)
{:ok, result} = ToolContext.request_sampling(ctx, %{
"messages" => [%{"role" => "user", "content" => %{"type" => "text", "text" => "Analyze: #{args["code"]}"}}],
"maxTokens" => 1000
})
ToolContext.send_progress(ctx, 100, 100)
{:ok, [%{"type" => "text", "text" => result["content"]["text"]}], state}
endToolContext functions: log/3, send_progress/3, send_notification/3, request/4, request_sampling/2, request_elicitation/2.
Transport Rules
Stdio Transport
- Client mode:
{MCP.Transport.Stdio, command: "cmd", args: ["arg1"]} - Server mode:
{MCP.Transport.Stdio, mode: :server} - Messages are newline-delimited JSON-RPC. Must NOT contain embedded newlines.
- Works with zero additional dependencies.
Streamable HTTP Transport
- Client:
{MCP.Transport.StreamableHTTP.Client, url: "http://host:port/mcp"} - Server: Use
MCP.Transport.StreamableHTTP.Plugwith Bandit (see above) - Requires
:req,:plug, and:banditdependencies. - Uses POST for sending, GET for SSE listening,
MCP-Session-Idheader for stateful sessions.
Critical Gotchas
Always call
connect/1first. No operations work before the initialization handshake completes (except ping).Sampling over HTTP times out. When using
ToolContext.request_sampling/2over Streamable HTTP, the client'sReq.postblocks until the SSE stream completes, so the client cannot respond to the sampling request. The server'srequest_timeout(default 30s) returns{:error, :timeout}. Always handle the error case. Sampling works correctly over stdio.Content is always a list of maps. Tool results, resource contents, and prompt messages use
[%{"type" => "text", "text" => "..."}]format. Never return a bare string.Tool input schemas use JSON Schema. The
"inputSchema"field must be a valid JSON Schema object with"type" => "object".Cursors for pagination. List callbacks receive a cursor and must return
{:ok, items, next_cursor, state}. Returnnilfor next_cursor when there are no more pages.Error tuples include state. All error returns are
{:error, code, message, state}— don't forget to return the handler state.Capability auto-detection. The server only advertises capabilities for callbacks your handler implements. No need to configure capabilities manually.
JSON-RPC 2.0 compliance. All messages are valid JSON-RPC 2.0. IDs are unique per session, never null.
Common Error Codes
| Code | Meaning |
|---|---|
-32700 | Parse error |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid params |
-32603 | Internal error |
-32002 | Resource not found (MCP-specific) |
Module Reference
| Module | Purpose |
|---|---|
MCP.Client | High-level client API |
MCP.Server | High-level server API |
MCP.Server.Handler | Server handler behaviour |
MCP.Server.ToolContext | Async tool execution context |
MCP.Transport | Transport behaviour |
MCP.Transport.Stdio | Stdio transport |
MCP.Transport.StreamableHTTP.Client | HTTP client transport |
MCP.Transport.StreamableHTTP.Plug | HTTP server transport (Plug) |
MCP.Protocol | JSON-RPC 2.0 framing and ID generation |
MCP.Protocol.Types.* | MCP type structs (Tool, Resource, Prompt, Content, etc.) |
MCP.Protocol.Messages.* | Request/response/notification structs |
MCP.Protocol.Capabilities.* | Client and server capability structs |