Macro for auto-generating CRUD tools from Ecto schemas.
This module provides the expose/2 macro that automatically generates
MCP tools for Ecto schema CRUD operations.
Example
defmodule MyApp.MCP do
use Ectomancer
expose MyApp.Accounts.User,
actions: [:list, :get, :create, :update],
only: [:id, :email, :name, :role]
expose MyApp.Blog.Post,
actions: [:list, :get],
except: [:internal_notes]
endOptions
:actions- List of actions to expose::list,:get,:create,:update,:destroy:only- Whitelist of fields to include:except- Blacklist of fields to exclude:filterable- Fields that allow advanced filter operators (defaults to all exposed fields):namespace- Prefix tools with namespace (e.g.,:accounts→accounts_list_users):as- Alternative name for the resource (e.g.,:admin_users→list_admin_users):readonly- Enable read-only mode (disables:create,:update,:destroy):authorize- Authorization configuration (function, policy module, or action-specific rules):preload- Ecto associations to eager-load on:listand:getresults
Generated Tools
For a schema MyApp.Accounts.User with actions [:list, :get, :create]:
list_users- List all users with optional filtersget_user- Get a user by IDcreate_user- Create a new user
Field Filtering
Fields can be filtered using :only or :except:
# Only expose specific fields
expose User, only: [:email, :name]
# Exclude sensitive fields
expose User, except: [:password_hash, :secret_token]Advanced Filtering Control
By default, all exposed fields get advanced filter operators (_gt, _contains, etc.).
Use :filterable to restrict which fields support these operators while still
exposing all fields for reading:
# Only allow advanced filtering on specific fields
expose User,
only: [:id, :email, :name, :role, :age],
filterable: [:email, :age]
# Email supports _contains, _icontains, etc.
# Age supports _gt, _gte, _lt, _lte, etc.
# Name and role only support exact-match filteringHandling Naming Collisions
When exposing multiple schemas that might have naming conflicts:
# Use namespace to prefix all tools
expose MyApp.Accounts.User, namespace: :accounts
# Generates: accounts_list_users, accounts_get_user, etc.
# Use 'as' to rename the resource entirely
expose MyApp.Accounts.User, as: :admin_users
# Generates: list_admin_users, get_admin_users, etc.Action Details
:list- Returns paginated list, supports filtering:get- Returns single record by primary key:create- Creates new record with provided attributes:update- Updates existing record by primary key:destroy- Deletes record by primary key
Authorization
You can add authorization to exposed schemas using the :authorize option:
# Global authorization for all actions
expose MyApp.Accounts.User,
authorize: fn actor, action -> actor.role == :admin end
# Policy module
expose MyApp.Accounts.User,
authorize: with: MyApp.Policies.UserPolicy
# Action-specific authorization
expose MyApp.Accounts.User,
actions: [:list, :get, :create],
authorize: [
list: :public,
get: :public,
create: fn actor, _action -> actor.role == :admin end
]Repo Configuration
The CRUD operations require an Ecto Repo. Configure it in your config:
config :ectomancer, :repo, MyApp.Repo
Summary
Functions
Exposes an Ecto schema as MCP tools.
Functions
Exposes an Ecto schema as MCP tools.
Parameters
schema_module- The Ecto schema module to exposeopts- Options for tool generation:actions- List of actions (default:[:list, :get, :create, :update, :destroy]):only- Whitelist fields:except- Blacklist fields:filterable- Fields that allow advanced filter operators (default: all exposed fields):readonly- Disable mutation operations (:create,:update,:destroy):namespace- Prefix tools with namespace:as- Alternative resource name:soft_delete- Enable soft-delete awareness (auto-detects:deleted_at/:archived_atfields):field_authorize- Dynamic field-level authorization callbackfn actor, field -> boolean :: boolean()
## Examples
expose MyApp.Accounts.User
# Generates: list_users, get_user, create_user, update_user, destroy_user
expose MyApp.Accounts.User, actions: [:list, :get]
# Generates: list_users, get_user
expose MyApp.Accounts.User, readonly: true
# Generates: list_users, get_user (mutation operations disabled)
expose MyApp.Accounts.User, only: [:email, :name]
# All tools only expose email and name fields
expose MyApp.Accounts.User, namespace: :accounts
# Generates: accounts_list_users, accounts_get_user, etc.
expose MyApp.Accounts.User, as: :admin_users
# Generates: list_admin_users, get_admin_users, etc.
expose MyApp.Accounts.User, filterable: [:email, :age]
# Email and age support advanced filter operators;
# all other exposed fields only support exact-match filtering.