EMCP

An minimal Elixir MCP (Model Context Protocol) server.

Limitations (for now)

Usage

1. Define a tool

defmodule MyApp.Tools.Echo do
  @behaviour EMCP.Tool

  @impl EMCP.Tool
  def name, do: "echo"

  @impl EMCP.Tool
  def description, do: "Echoes back the provided message"

  @impl EMCP.Tool
  def input_schema do
    %{
      type: :object,
      properties: %{
        message: %{type: :string},
        count: %{type: :integer},
        temperature: %{type: :number},
        verbose: %{type: :boolean},
        tags: %{type: :array, items: %{type: :string}},
        options: %{
          type: :object,
          properties: %{
            format: %{type: :string}
          }
        }
      },
      required: [:message]
    }
  end

  @impl EMCP.Tool
  def call(%{"message" => message}) do
    EMCP.Tool.response([%{"type" => "text", "text" => message}])
  end

  # Return errors with:
  # EMCP.Tool.error("something went wrong")
end

2. Configure the server

# config/config.exs
config :emcp,
  name: "my-app",
  version: "1.0.0",
  tools: [MyApp.Tools.Echo]

3. Mount the transport

Add the StreamableHTTP transport to your Phoenix router:

forward "/mcp", EMCP.Transport.StreamableHTTP

Sessions are managed automatically with a configurable TTL (default 60 minutes):

config :emcp, session_ttl: to_timeout(minute: 60)

STDIO Transport

For local development or CLI tools, you can use the STDIO transport instead. It reads JSON-RPC messages from stdin and writes responses to stdout.

Add it to your supervision tree:

children = [
  EMCP.Transport.STDIO
]

Configure it in Claude Code via .claude/settings.json:

{
  "mcpServers": {
    "my-app": {
      "command": "mix",
      "args": ["run", "--no-halt"],
      "cwd": "/path/to/your/elixir/project"
    }
  }
}

Acknowledgements

Based on the official Ruby MCP SDK reference implementation.

Development

The e2e tests use the MCP Inspector CLI. Install it before running tests:

cd test/inspector && bun install

Then run the tests:

mix test