Ectomancer

CIHex.pmHex DocsDownloadsLicense

Auto-generate MCP tools from your Ecto schemas — your Phoenix app, now conversationally operable by Claude and any LLM.

Ectomancer sits on top of anubis_mcp and turns your database schemas into live MCP tools. Add it to your router, start the server, and your AI assistant can query, create, and update records through natural language — no hand-written tool definitions, no boilerplate.

Features

Demo

Watch Ectomancer in action — a full Phoenix app with User, Post, and Comment schemas exposed as MCP tools, running on the Streamable HTTP transport.

asciicast

Click the image above to watch the interactive terminal demo.

The demo shows:

Try it yourself:

git clone https://github.com/GustavoZiaugra/ectomancer_demo
cd ectomancer_demo
mix setup
mix phx.server
# Connect your MCP client to http://localhost:4000/mcp

Installation

def deps do
[
{:ectomancer, "~> 1.2"}
]
end

Quick Start

1. Create your MCP module

defmodule MyApp.MCP do
use Ectomancer,
name: "myapp-mcp",
version: "0.1.0"
expose MyApp.Accounts.User,
actions: [:list, :get, :create, :update]
expose MyApp.Blog.Post,
actions: [:list, :get]
tool :search_users do
description "Search users by email"
param :query, :string, required: true
param :limit, :integer
handle fn %{"query" => q, "limit" => l}, _actor ->
{:ok, MyApp.Accounts.search_users(q, limit: l || 10)}
end
end
resource :system_status do
description "Current system health metrics"
uri "metrics://status"
mime_type "application/json"
read fn _params, _actor ->
{:ok, Jason.encode!(%{status: "healthy", uptime: System.uptime()})}
end
end
end

2. Start the MCP server

Add to your Application supervisor:

children = [
# ... other children ...
{Anubis.Server.Supervisor, {MyApp.MCP, transport: {:streamable_http, start: true}}},
MyAppWeb.Endpoint
]

3. Mount in your router

scope "/mcp" do
pipe_through :api
forward "/", Ectomancer.Plug, server: MyApp.MCP
end

4. Configure actor extraction (optional)

config :ectomancer,
repo: MyApp.Repo,
actor_from: fn conn ->
conn.assigns.current_user
end

Done. Claude can now query your database through natural language at /mcp.

Authorization

Three strategies, choose what fits:

StyleExampleUse case
Inlineauthorize fn actor, _ -> actor.role == :admin endQuick rules
Policy moduleauthorize with: MyApp.Policies.UserPolicyComplex logic, reusable
Noneauthorize :nonePublic endpoints

Schema-level and action-specific rules work too:

expose MyApp.Accounts.User,
actions: [:list, :get, :create, :update],
authorize: [
list: :none,
get: fn actor, _ -> actor != nil end,
create: :admin_only,
update: with: MyApp.Policies.UserPolicy
]

Configuration

Sources

config :ectomancer, repo: MyApp.Repo

Actor extraction

config :ectomancer,
actor_from: fn conn ->
case Plug.Conn.get_req_header(conn, "authorization") do
["Bearer " <> token] -> MyApp.Auth.verify_token(token)
_ -> {:error, :unauthorized}
end
end

Rate limiting

config :ectomancer, :rate_limits,
enabled: true,
global: [max_requests: 100, time_window_ms: 60_000],
per_tool: [search_users: [max_requests: 10, time_window_ms: 60_000]]

Multi-repo

expose MyApp.OtherSchema, repo: MyApp.ReplicaRepo

Pages

PathDescription
/mcpMCP endpoint (SSE + JSON-RPC)

Open priv/ectomancer.html in a browser for a visual playground — browse tools, fill params, call them, and inspect results. No build step, no npm install, no dependencies.

Documentation

Testing

mix test

Zero compiler warnings, full Credo and Dialyzer compliance.

Current version: 1.3.0

License

MIT