Custom tool DSL for defining MCP tools.
Tools are defined with:
- description: What the tool does
- params: Input parameters (with types and requirements)
- handle: The function that executes the tool logic
The DSL generates a tool module that integrates with Anubis MCP. Validation is handled internally rather than by Anubis's Peri validator to avoid JSON encoding issues with Peri's tuple-based schema format.
Summary
Functions
Defines authorization for a tool.
Flattens mapped changeset errors into a single map with concatenated messages.
Formats error reasons into proper MCP error format with descriptive messages. This function is called from generated tool modules.
Formats a field name for display in error messages. Converts snake_case to Title Case.
Infers the validation type from changeset errors.
Maps Ecto changeset errors to a structured format suitable for MCP responses.
Defines a new tool within an Ectomancer module.
Functions
Defines authorization for a tool.
Examples
# Inline function
authorize fn actor, action ->
actor.role == :admin or action in [:list, :get]
end
# Policy module
authorize with: MyApp.Policies.UserPolicy
# No authorization (public)
authorize :none
Flattens mapped changeset errors into a single map with concatenated messages.
Examples
errors = %{email: ["can't be blank"], name: ["is invalid"]}
Ectomancer.Tool.flatten_errors(errors)
# %{email: "can't be blank", name: "is invalid"}
Formats error reasons into proper MCP error format with descriptive messages. This function is called from generated tool modules.
Formats a field name for display in error messages. Converts snake_case to Title Case.
@spec infer_validation_type( %{required(atom()) => String.t()} | %{required(atom()) => [String.t()]} ) :: atom()
Infers the validation type from changeset errors.
Accepts either a map with list values (from traverse_errors) or a map with string values.
@spec map_changeset_errors(Ecto.Changeset.t()) :: %{required(atom()) => [String.t()]}
Maps Ecto changeset errors to a structured format suitable for MCP responses.
Returns a map where keys are field names (atoms) and values are lists of error strings.
Examples
changeset = %Ecto.Changeset{
errors: [email: {"can't be blank", []}],
...
}
Ectomancer.Tool.map_changeset_errors(changeset)
# %{email: ["can't be blank"]}
Defines a new tool within an Ectomancer module.
Example
tool :greet do
description("Greet someone by name")
param(:name, :string, required: true)
handle(fn params, _actor ->
name = params["name"]
{:ok, "Hello, #{name}!"}
end)
endAuthorization
Tools can have authorization to control access:
# Inline function
tool :admin_only do
description("Admin only action")
authorize(fn actor, _action -> actor.role == :admin end)
handle(fn _params, _actor ->
{:ok, "Secret data"}
end)
end
# Policy module
tool :with_policy do
description("Uses policy module")
authorize(with: MyApp.Policies.MyPolicy)
handle(fn _params, _actor ->
{:ok, "Protected data"}
end)
end
# No authorization (public)
tool :public do
description("Public endpoint")
authorize(:none)
handle(fn _params, _actor ->
{:ok, "Public data"}
end)
end