Ectomancer.Expose (Ectomancer v1.2.1)

Copy Markdown View Source

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]
end

Options

  • :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., :accountsaccounts_list_users)
  • :as - Alternative name for the resource (e.g., :admin_userslist_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 :list and :get results

Generated Tools

For a schema MyApp.Accounts.User with actions [:list, :get, :create]:

  • list_users - List all users with optional filters
  • get_user - Get a user by ID
  • create_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 filtering

Handling 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

expose(schema_module, opts \\ [])

(macro)

Exposes an Ecto schema as MCP tools.

Parameters

  • schema_module - The Ecto schema module to expose
  • opts - 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_at fields)
    • :field_authorize - Dynamic field-level authorization callback fn 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.