README.md

# Ectomancer

> Add an AI brain to your Phoenix app in one afternoon.

**Ectomancer** automatically exposes your Phoenix/Ecto app as an MCP (Model Context Protocol) server, making it conversationally operable by Claude and other LLMs with minimal configuration.

## What it does

Ectomancer sits on top of [anubis_mcp](https://hex.pm/packages/anubis_mcp) and provides three killer features:

1. **Auto-generates MCP tools** from your Ecto schemas — no hand-writing tool definitions
2. **Authorization system** — fine-grained control with inline functions or policy modules
3. **Threads the current user (actor)** through every tool call automatically — auth just works

## Installation

```elixir
def deps do
  [
    {:ectomancer, "~> 0.1.0-rc.2"}
  ]
end
```

## Quick Start

### 1. Create your MCP module

```elixir
defmodule MyApp.MCP do
  use Ectomancer,
    name: "myapp-mcp",
    version: "0.1.0"

  # Expose Ecto schemas as MCP tools
  expose MyApp.Accounts.User,
    actions: [:list, :get, :create, :update]

  # Custom tools with authorization
  tool :send_password_reset do
    description "Send a password reset email to a user"
    param :email, :string, required: true
    
    authorize fn actor, _action ->
      actor != nil  # Must be authenticated
    end

    handle fn %{"email" => email}, actor ->
      MyApp.Accounts.send_reset_email(email, actor)
      {:ok, %{sent: true}}
    end
  end
end
```

### 2. Add to your Application supervisor

```elixir
defmodule MyApp.Application do
  use Application

  def start(_type, _args) do
    children = [
      # ... other children ...
      
      # Start Anubis MCP server with your module
      {Anubis.Server.Supervisor, {MyApp.MCP, transport: {:streamable_http, start: true}}},
      
      MyAppWeb.Endpoint
    ]

    Supervisor.start_link(children, strategy: :one_for_one)
  end
end
```

### 3. Add the route to your router

```elixir
defmodule MyAppWeb.Router do
  use MyAppWeb, :router

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

### 4. Configure Ectomancer (optional)

```elixir
# config/config.exs
config :ectomancer,
  repo: MyApp.Repo,
  actor_from: fn conn ->
    # Extract current user from conn
    conn.assigns.current_user
  end
```

## Features

### Expose Ecto Schemas

Automatically generate CRUD tools from your schemas:

```elixir
# Basic usage - exposes all CRUD actions
expose MyApp.Accounts.User

# Limit actions
expose MyApp.Blog.Post, actions: [:list, :get]

# Filter fields
expose MyApp.Accounts.User, only: [:email, :name]
expose MyApp.Accounts.User, except: [:password_hash]

# Namespace to avoid collisions
expose MyApp.Accounts.User, namespace: :accounts
```

### Custom Tools

Define custom tools with parameters:

```elixir
tool :search_users do
  description "Search users by email"
  param :query, :string, required: true
  param :limit, :integer
  
  handle fn params, _actor ->
    users = MyApp.Accounts.search_users(params["query"], limit: params["limit"])
    {:ok, %{users: users}}
  end
end
```

### Authorization

Ectomancer provides flexible authorization with three strategies:

#### 1. Inline Function

Simple authorization with a function:

```elixir
tool :admin_stats do
  description "Get admin statistics"
  
  authorize fn actor, _action ->
    actor != nil && actor.role == :admin
  end
  
  handle fn _params, _actor ->
    {:ok, %{stats: calculate_stats()}}
  end
end
```

#### 2. Policy Module

Complex authorization with reusable policy modules:

```elixir
defmodule MyApp.Policies.UserPolicy do
  @behaviour Ectomancer.Authorization.Policy
  
  @impl true
  def authorize(actor, action, _opts) do
    case action do
      :list -> :ok  # Public
      :get when actor != nil -> :ok  # Authenticated only
      :create when actor.role == :admin -> :ok  # Admin only
      _ -> {:error, "Unauthorized"}
    end
  end
end

# Use in tool
tool :user_action do
  authorize with: MyApp.Policies.UserPolicy
  # ...
end
```

#### 3. Public Access

No authorization required:

```elixir
tool :public_status do
  description "Get system status"
  authorize :none
  
  handle fn _params, _actor ->
    {:ok, %{status: "operational"}}
  end
end
```

### Schema-Level Authorization

Apply authorization to all actions of a schema:

```elixir
# Global authorization for all actions
expose MyApp.Accounts.User,
  actions: [:list, :get, :create],
  authorize: fn actor, _action -> actor.role == :admin end
```

### Action-Specific Authorization

Fine-grained control per action:

```elixir
expose MyApp.Accounts.User,
  actions: [:list, :get, :create, :update],
  authorize: [
    list: :none,           # Public
    get: fn actor, _ -> actor != nil end,  # Authenticated
    create: :admin_only,    # Admin only
    update: with: MyApp.Policies.UserPolicy  # Policy module
  ]
```

### Binary ID / UUID Support

Ectomancer automatically handles binary_id and UUID primary keys:

```elixir
defmodule MyApp.Accounts.User do
  use Ecto.Schema
  
  @primary_key {:id, :binary_id, autogenerate: true}
  schema "users" do
    field :email, :string
    # ...
  end
end

# Works seamlessly with expose
expose MyApp.Accounts.User  # get_user, create_user, etc. all work with UUIDs
```

## What Claude gains access to

Once connected, Claude can:

- Query data in natural language ("show me users who signed up this week")
- Run multi-step workflows ("create account, assign plan, send welcome email")
- Give support agents a conversational admin interface
- Serve as a lightweight BI layer over your data
- Inspect queue depth and background workers (with Oban integration)

## Documentation

- [HexDocs](https://hexdocs.pm/ectomancer)
- Full documentation and examples at [GitHub](https://github.com/GustavoZiaugra/ectomancer)

## Testing

Ectomancer includes comprehensive test coverage:

- **172 tests** covering all features
- **35 authorization-specific tests**
- Full integration tested with Phoenix apps
- Zero compiler warnings
- Full Credo and Dialyzer compliance

Run tests:
```bash
mix test
```

## Status

This project is in active development. Phase 2 (Authorization) is complete.

## License

MIT License - see LICENSE file for details.