ExMCP Architecture Guide
View SourceExMCP is organized around protocol boundaries: clients, servers, transports, HTTP Plug integration, ACP, authorization, and internal protocol helpers. Public APIs stay small; cross-cutting work is kept at transport or Plug boundaries.
Public Layers
MCP Client
ExMCP.Client owns the client process, initialization handshake, request IDs,
server capability state, retries, and request/response formatting.
Client operation modules under lib/ex_mcp/client/operations/ keep tool,
resource, and prompt calls focused while ExMCP.Client.ConnectionManager
normalizes transport startup.
MCP Server
Server implementations use ExMCP.Server.Handler. Most applications should add
ExMCP.Server.DSL for declarative tools, resources, and prompts:
defmodule MyServer do
use ExMCP.Server.Handler
use ExMCP.Server.DSL
tool "echo", "Echo input" do
param :message, :string, required: true
run fn %{message: message}, state ->
{:ok, %{content: [%{type: "text", text: message}]}, state}
end
end
endExMCP.Server.HandlerServer is the transport-aware process for in-memory and
BEAM-local handler execution. HTTP and stdio servers are started through
ExMCP.Server.Transport or the DSL-generated start_link/1.
Transports
Transport modules implement ExMCP.Transport:
ExMCP.Transport.Stdiofor newline-delimited JSON-RPC over subprocess stdio.ExMCP.Transport.HTTPfor streamable HTTP with optional SSE.ExMCP.Transport.Localfor BEAM-local MCP maps/lists passed as Elixir terms.ExMCP.Transport.Testfor in-memory tests.
BEAM-local MCP is selected with transport: :beam and requires a server PID:
{:ok, server} = MyServer.start_link(transport: :beam) # or HandlerServer.start_link(handler: MyHandler, ...)
{:ok, client} = ExMCP.Client.start_link(transport: :beam, server: server)The removed :native alias and direct dispatcher API are not part of the 1.0
public architecture.
HTTP Plug
ExMCP.HttpPlug is the HTTP server boundary. Request parsing, session
resolution, CORS/origin handling, response shaping, and SSE handling are split
under lib/ex_mcp/http_plug/.
Use normal Phoenix/Plug composition for HTTP edge concerns:
pipeline :mcp do
plug ExMCP.Plugs.DnsRebinding
plug MyApp.AuthenticateMCP
end
scope "/mcp" do
pipe_through :mcp
forward "/", ExMCP.HttpPlug,
handler: MyApp.MCPServer,
server_info: %{name: "my-app", version: "1.0.0"},
sse_enabled: true
endACP
ACP modules live under lib/ex_mcp/acp/. ExMCP.ACP.Client controls ACP
agents, ExMCP.ACP.Agent exposes native Elixir ACP agents, and adapter modules
bridge external CLIs such as Claude Code, Codex, and Pi.
ACP pooling is intentionally left to consumers. ExMCP provides the protocol, transport, adapter, and native-agent building blocks.
Internal Functional Cores
Pure transformation and validation logic is kept separate from process and I/O boundaries:
- Internal protocol and version modules handle message construction, parsing, and version rules.
- The message processor modules provide a Plug-like processing pipeline for server request dispatch.
ExMCP.Content.*modules normalize content, sanitize inputs, and validate schema-related data.- ACP mapper/protocol/session modules keep adapter-specific decoding separate from subprocess ports.
This structure keeps side effects at the edges: GenServers, Ports, HTTP requests, Plug connections, filesystem-backed session stores, and telemetry.
Resilience And Pipelines
ExMCP currently has three pipeline-style boundaries:
- HTTP server requests: normal Plug/Phoenix pipelines around
ExMCP.HttpPlug. - Server message processing:
ExMCP.MessageProcessor.run/2for internal Plug-like request processing. - Transport reliability:
ExMCP.Transport.ReliabilityWrapper, clientretry_policy, andExMCP.Reliability.*components.
HTTP client connection handling is transport-owned today. If ExMCP later adds a
public client middleware API, it should wrap request construction and transport
send/receive at the ExMCP.Client boundary rather than inside HTTP-specific
code, so stdio, HTTP, and BEAM-local can share the same cross-cutting behavior.
Module Map
lib/ex_mcp/
acp/ ACP protocol, client, native agent, adapters
authorization/ OAuth 2.1 and auth provider flows
client/ Client operations, handlers, state, connection setup
content/ Content builders, validation, sanitization
http_plug/ HTTP Plug functional core and SSE handling
internal/ Private protocol, map, version, and security helpers
message_processor/ Plug-like MCP request processing
plugs/ Reusable Plug security/auth components
protocol/ Public protocol utility modules
reliability/ Retry, circuit breaker, health check supervisor
server/ Handler behavior, DSL, transport startup
transport/ Stdio, HTTP, BEAM-local, test transportsTesting Architecture
The test suite covers unit, integration, interop, conformance, security, and
transport behavior. ExMCP.Transport.Test and transport: :beam keep local
server/client tests fast without starting subprocesses or network listeners.
External conformance scripts live in scripts/ and should be run for each
supported MCP spec version before release.
Design Rules
- Prefer
ExMCP.Server.HandlerplusExMCP.Server.DSLfor servers. - Use
transport: :beamfor local BEAM MCP, not a separate service dispatcher. - Put HTTP authorization, origin, and request-signing checks in Plug pipelines.
- Put transport failure handling in client retry/reliability options.
- Keep pure protocol transformations in functional modules and side effects in GenServer, Port, Plug, or filesystem boundaries.