Screencast
32. Build a MCP server in Phoenix
The Model Context Protocol (MCP) is an emerging standard that enables AI agents to interact with your application’s data through a structured interface. Rather than building custom APIs for each AI integration, MCP provides a standardized way for language models like Claude or GPT to query and modify your data using natural language.
In this implementation, we integrate the Hermes MCP library into a Phoenix application to expose brewery data through three distinct tools: listing all breweries, retrieving specific brewery details, and updating brewery information. The MCP server runs as a supervised process within your Phoenix application, handling the protocol communication while you focus on defining the tools and their schemas. The video demonstrates the complete setup process, from adding dependencies to testing with Claude, showing how an AI assistant can naturally interact with your Phoenix contexts without requiring custom API endpoints.
defmodule Tutorial.MCPServer do
use Hermes.Server,
name: "Tutorial MCP",
version: "1.0.0",
capabilities: [:tools]
component(Tutorial.MCP.Tools.ListBreweries)
component(Tutorial.MCP.Tools.ShowBrewery)
component(Tutorial.MCP.Tools.UpdateBrewery)
end
Setting Up MCP Server Transport in Phoenix Router
Configuring the transport layer correctly is essential for MCP communication. Unlike traditional HTTP APIs, MCP uses a combination of JSON-RPC messages for structured requests and Server-Sent Events (SSE) for streaming responses back to AI clients. Phoenix doesn’t recognize the text/event-stream MIME type by default, so we need to configure it explicitly.
The router setup creates a dedicated pipeline that accepts the necessary content types and forwards all MCP requests to the Hermes transport plug. This separation keeps MCP traffic isolated from your regular browser and API routes. The streamable HTTP transport is particularly well-suited for Phoenix applications because it works over standard HTTP connections while supporting the bidirectional communication patterns that AI agents require. The video walks through the configuration in both the router and config files, ensuring your server can handle the protocol’s streaming nature.
pipeline :mcp do
plug :accepts, ["json", "sse", "text/event-stream"]
end
scope "/mcp" do
pipe_through :mcp
forward "/", Hermes.Server.Transport.StreamableHTTP.Plug, server: Tutorial.MCPServer
end
Creating MCP Tools with Schema Validation
Each MCP tool is defined as a separate module using Hermes server components. The schema block defines what parameters the tool accepts, their types, and whether they’re required. This declarative approach lets the AI assistant understand what information it needs to provide when calling your tools. The module documentation is crucial—it’s exposed to the AI and helps it decide when to use each tool.
The execute/2 function is where your Phoenix contexts come into play. You’re not writing new business logic; instead, you’re creating a thin wrapper around your existing context functions. The update brewery tool demonstrates this pattern well: it accepts optional parameters, filters out nil values, and delegates to the existing Breweries.update_brewery/2 function. This means all your validation logic, database constraints, and business rules remain centralized in your contexts. The video shows how to structure these tools for both read and write operations, including proper error handling when validations fail.
defmodule Tutorial.MCP.Tools.UpdateBrewery do
@moduledoc "Update a brewery"
use Hermes.Server.Component, type: :tool
schema do
field :id, {:required, :integer}, description: "ID of the brewery to update"
field :name, :string, description: "Name of the brewery"
field :city, :string, description: "City where the brewery is located"
end
@impl Hermes.Server.Component.Tool
def execute(%{id: id} = params, frame) do
brewery = Breweries.get_brewery!(id)
update_params = params |> Enum.reject(fn {_k, v} -> is_nil(v) end) |> Map.new()
case Breweries.update_brewery(brewery, update_params) do
{:ok, updated} -> {:reply, Response.json(Response.tool(), updated), frame}
{:error, changeset} -> {:reply, Response.json(Response.tool(), %{errors: inspect(changeset.errors)}), frame}
end
end
end
What You Will Learn
- Install and configure the Hermes MCP library in a Phoenix application with proper supervision tree integration
- Set up MCP transport routes with the correct MIME types and pipeline configuration for JSON-RPC and Server-Sent Events
- Define MCP tools using schemas that specify parameters, types, and descriptions that AI assistants can understand
- Integrate existing Phoenix contexts with MCP tools without duplicating business logic or validation rules
- Test your MCP server using the official MCP Inspector tool to verify tool execution and responses
- Connect AI assistants like Claude to your Phoenix application for natural language data queries and updates