MCPKit is a reusable MCP server runtime for Phoenix applications.
It gives host applications a small set of host-facing contracts for building policy-aware MCP servers over Streamable HTTP, with support for tools, prompts, resources, completions, persistent sessions, and a host-started runtime for active sessions.
MCP Support Matrix
| Area | Status | Notes |
|---|---|---|
Streamable HTTP transport (POST) | ✅ | Supported |
SSE session stream (GET) | ✅ | Supported for active sessions |
Session termination (DELETE) | ✅ | Supported |
Lifecycle (initialize, ping, notifications/initialized) | ✅ | Supported |
Tools (tools/list, tools/call) | ✅ | Supported |
Prompts (prompts/list, prompts/get) | ✅ | Supported |
Resources (resources/list, resources/read, resources/templates/list) | ✅ | Supported |
Utilities (completion/complete, notifications/cancelled) | ✅ | Supported |
| Request-time policy enforcement | ✅ | Via MCPKit.Policy |
| Resource subscriptions | ❌ | Not implemented |
| Resource update notifications | ❌ | Not implemented |
| Logging capability | ❌ | Not implemented |
| Progress notifications | ❌ | Not implemented |
| List-changed notifications | ❌ | Not implemented |
| Public host API for runtime-initiated client requests | ❌ | Internal plumbing exists, but no public host API yet |
Installation
def deps do
[
{:mcp_kit, "~> 0.2.4"}
]
endQuickstart
1. Define the host contract
defmodule MyApp.MCP.Definition do
@behaviour MCPKit.Definition
@impl true
def server_info do
%{"name" => "my-app", "version" => "0.1.0"}
end
@impl true
def session_store, do: MyApp.MCP.SessionStore
@impl true
def policy, do: MyApp.MCP.Policy
endMCPKit owns the MCP transport version and advertises its built-in current
protocol version automatically. Host applications do not configure this.
2. Implement persistent session storage
defmodule MyApp.MCP.SessionStore do
@behaviour MCPKit.SessionStore
@impl true
def create_session(attrs) do
{:ok,
%{
id: Ecto.UUID.generate(),
initialized: false,
protocol_version: attrs.protocol_version,
client_info: attrs.client_info,
client_capabilities: attrs.client_capabilities
}}
end
@impl true
def fetch_session(session_id) do
# Load from your database or cache.
{:error, :not_found}
end
@impl true
def touch_session(session), do: {:ok, session}
@impl true
def mark_initialized(session), do: {:ok, %{session | initialized: true}}
@impl true
def delete_session(_session_id), do: {:error, :not_found}
end3. Start the runtime
children = [
{MCPKit.Runtime, definition: MyApp.MCP.Definition}
]4. Add a policy
defmodule MyApp.MCP.Policy do
@behaviour MCPKit.Policy
@impl true
def authorize(_action, _context), do: :allow
end5. Define a tool
defmodule MyApp.MCP.Tools.Ping do
use MCPKit.Tool
alias MCPKit.Response
schema do
field :message, :string, required: true
end
@impl true
def execute(arguments, context) do
{:reply, Response.tool() |> Response.structured(arguments), context}
end
end6. Mount the MCP route
defmodule MyAppWeb.Router do
use MyAppWeb, :router
import MCPKit.Router
mcp_scope "/mcp", MyApp.MCP do
tool "ping", Tools.Ping
end
endmcp_scope/2 infers the definition module as MyApp.MCP.Definition and the
runtime name as MyApp.MCP.Runtime. Override them with definition: and
runtime: if needed.
Inside mcp_scope, module references follow normal Phoenix scope aliasing, so
Tools.Ping resolves as MyApp.MCP.Tools.Ping and remains visible to editor
tooling.
Host Contracts
The host application is responsible for:
MCPKit.Definition: protocol version, server info, session store, and optional policyMCPKit.SessionStore: durable session lifecycleMCPKit.Runtime: active in-memory session coordinationMCPKit.Policy: request-time authorization and visibility decisions
See the guides for fuller examples:
Router DSL
Use MCPKit.Router.mcp_scope/2 or mcp_scope/3 inside a Phoenix router scope.
mcp_scope "/mcp", MyApp.MCP do
tool "project_create", Tools.ProjectCreate
prompt "draft_release_notes", Prompts.DraftReleaseNotes
resource "project", Resources.Project
endInside mcp_scope, you can declare:
tool/2prompt/2resource/2
Policy
MCPKit.Policy is evaluated on every request. It supports these decisions:
:allow{:deny, :not_found}{:deny, :forbidden}
Lists are filtered item-by-item, while direct calls, prompt gets, resource reads, and completions are enforced separately.
completion/complete is also policy-aware and only returns suggestions for
visible prompt arguments and resource templates.
Authoring APIs
Tools
Tool modules use MCPKit.Tool to declare input schemas and return
MCPKit.Response values.
Prompts
Prompt modules use MCPKit.Prompt to declare argument schemas, render MCP
messages, and optionally implement complete/3 for argument suggestions.
Resources
Resource modules use MCPKit.Resource to declare concrete resource entries,
URI templates, URI reads, and optionally implement complete/3 for template
variable suggestions.
Supported MCP Surface
Currently implemented methods:
initializepingcompletion/completeprompts/listprompts/getresources/listresources/readresources/templates/listtools/listtools/callnotifications/initializednotifications/cancelledDELETEsession terminationGETSSE session stream
For one canonical compatibility summary, see Supported MCP Surface.
Publishing Checklist
- GitHub Actions publishes tagged releases automatically from
v*tags - configure the
HEX_API_KEYrepository secret before pushing a release tag - update version in
mix.exs - confirm package links and source URL
- run
mix test - run
mix docs - run
mix hex.build