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
- Schema → MCP tools — auto-generates CRUD tools from
expose MyApp.Accounts.User - Route introspection —
expose_routes MyAppWeb.Routerturns HTTP endpoints into callable tools - Authorization system — inline functions, policy modules, or action-specific rules
- Actor threading — the current user flows through every tool call automatically
- Custom tools —
tool :search_users do ... endwith typed params - Custom resources —
resource :system_status do ... endwith URI templates, MIME types, and authorization - Rate limiting — configurable token bucket per tool or globally
- Multi-repo support — expose schemas from different repos simultaneously
- MCP Resources — schemas auto-register at
ectomancer://schemas/{name}, plus custom resources with URI templates, MIME types, and read handlers - Browser playground — zero-dep HTML client at
priv/ectomancer.html, no build step - Oban integration — optional bridge for inspecting queue depth and workers
- Interactive installer —
mix ectomancer.setupauto-discovers schemas and patches your project
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.
Click the image above to watch the interactive terminal demo.
The demo shows:
- 3 Ecto schemas → 15 MCP tools (list, get, create, update, destroy) with zero boilerplate
- Advanced filtering —
list_posts(title_contains: "hello")with automatic LIKE operators - Association support — create posts linked to users through MCP tool calls
- Real-time interaction — query, create, and update records conversationally
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"}
]
endQuick 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
end2. 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
end4. Configure actor extraction (optional)
config :ectomancer,
repo: MyApp.Repo,
actor_from: fn conn ->
conn.assigns.current_user
endDone. Claude can now query your database through natural language at /mcp.
Authorization
Three strategies, choose what fits:
| Style | Example | Use case |
|---|---|---|
| Inline | authorize fn actor, _ -> actor.role == :admin end | Quick rules |
| Policy module | authorize with: MyApp.Policies.UserPolicy | Complex logic, reusable |
| None | authorize :none | Public 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.RepoActor 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
endRate 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.ReplicaRepoPages
| Path | Description |
|---|---|
/mcp | MCP 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