One operation a module offers through the API (REST today, MCP later).
A module returns a list of tools from the optional PhoenixKit.Module.api_tools/0
callback. Core collects them (PhoenixKit.API.Registry), checks the caller's rights,
validates the input against input_schema, calls the handler and shapes the answer.
Fields
:name-"<namespace>.<verb>_<noun>", lowercase, stable ("ecommerce.set_prices"), at most 100 characters.:audience-:admin(a key with scopes) or:public(no key).:scope- the scope a key must carry,"<module>.<resource>:<action>".nilfor public tools and for core's ownsystem.*tools, which every valid key may call. A public tool must have no scope: it is called without a key, so one could never be met, andvalidate/1refuses the tool. Thesystem.namespace belongs to core: a tool a module returns fromapi_tools/0under that name is ignored, so a module cannot declare a tool that skips the scope and permission checks.:permission- the role permission the key's owner must hold right now ("shop.manage_catalog").nilunder the same rule as:scope; a public tool must have no permission either.:title,:description- written for a person and for an agent. The title defaults to the name, the description to"".:input_schema- JSON Schema (string keys) of the input, limited to the keywordsPhoenixKit.API.Validatorenforces (Validator.check_schema/2refuses the rest). Defaults to an object with no properties andadditionalProperties: false. Keep it static: build it from literals, not from data that changes at runtime (an enum of database values, say). The result of the schema check is remembered per distinct schema for the life of the node and never erased, so a schema that keeps changing adds an entry each time.:output_schema- JSON Schema of a success answer, ornil.:handler-{module, function}, called asapply(module, function, [input, ctx]).:module- the module that declared the tool. Filled in by the registry.:annotations- hints for clients::read_only,:destructive,:idempotent,:open_world(allfalseunless given).:danger- when a call needs a person's confirmation:nil(never, except the generic batch-size rule for admin writes),:always, or{module, function}, a checkfunction(input, ctx) :: boolean()called with a dry-run context. Any other value is refused byvalidate/1.:max_items- the largest batch the tool accepts, ornil.:cost- the rate-limit units one call costs.nil(default) means the executor's rule: 1, or for a non-read-only admin toolmax(1, ceil(longest list / 50)). A positive integer up to 1000 is a fixed price.{Module, :function}is called asModule.function(input)with the raw, unvalidated input and returns a positive integer (a failure falls back to the default rule). Use it for a batch read:cost: {MyModule.API, :batch_cost}.
Building a tool
Always build it with new!/1 (or new/1 when the caller wants the error list) so the
defaults above are filled in and the shape is checked:
PhoenixKit.API.Tool.new!(
name: "catalogue.list_products",
audience: :admin,
scope: "catalogue.products:read",
permission: "catalogue",
title: "List products",
handler: {MyModule.API, :list_products},
annotations: %{read_only: true}
)A tool returned from api_tools/0 that fails validate/1 is ignored with one warning
per module; the module's other tools still work.
Summary
Types
Who may call the tool: :admin needs a key, :public needs nothing.
{module, function}, called as apply(module, function, [input, ctx]).
Functions
Builds a tool from a map or keyword list, fills the defaults and validates it.
Like new/1, but raises ArgumentError listing every problem.
True for core's own system.* tools.
Checks the shape of a tool struct (the registry runs this on everything a module returns).
Types
@type audience() :: :admin | :public
Who may call the tool: :admin needs a key, :public needs nothing.
{module, function}, called as apply(module, function, [input, ctx]).
Functions
Builds a tool from a map or keyword list, fills the defaults and validates it.
Returns {:ok, tool} or {:error, messages} (every problem found, not only the first).
Like new/1, but raises ArgumentError listing every problem.
True for core's own system.* tools.
Checks the shape of a tool struct (the registry runs this on everything a module returns).
An :admin tool needs a :scope and a :permission, except a core system.* tool,
which every valid key may call. This check does not know who built the tool: that the
system. namespace is core's alone is enforced where module tools are collected
(PhoenixKit.ModuleRegistry.all_api_tools/0).