Provides the deftool macro for declaring MCP tools alongside their handlers.
Summary
Functions
Defines a tool with its schema and handler in one place.
Formats Ecto changeset errors into a human-readable error tuple.
Raises unless schema is a valid MCP output schema.
Functions
Defines a tool with its schema and handler in one place.
Example
deftool "get_page", "Get a page by ID",
params: [id: {:string!, "Page ID"}] do
page = CMS.get_page!(params["id"])
{:ok, Map.take(page, [:id, :title, :slug])}
endInside the do block, params and session are bound.
Annotations
Pass annotations to provide hints about the tool's behavior to MCP clients:
deftool "delete_item", "Delete an item",
params: [id: {:string!, "Item ID"}],
annotations: %{readOnlyHint: false, destructiveHint: true, idempotentHint: true} do
Items.delete!(params["id"])
{:ok, %{deleted: true}}
endSupported keys: readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title.
Output schema
Pass output_schema with a JSON Schema describing the shape of the tool's
result. It is advertised to clients as outputSchema in tools/list. MCP
2025-11-25 restricts it to type: "object" at the root:
deftool "get_weather", "Get current weather",
params: [city: {:string!, "City name"}],
output_schema: %{
type: "object",
properties: %{
temperature: %{type: "number"},
conditions: %{type: "string"}
},
required: ["temperature", "conditions"]
} do
{:ok, %{temperature: 22.5, conditions: "Partly cloudy"}}
endWhen a tool declares an output schema, successful map results are also returned
in the structuredContent field of tools/call, alongside the serialized JSON
in a text content block for backwards compatibility. The handler's return value
is unchanged — the same {:ok, result} is used for both fields.
Per the MCP specification, servers MUST provide structured results that conform to the declared schema. Two consequences follow:
- The schema's root type must be
"object"— the spec restricts output schemas to objects, so anything else raises when the tool is defined. - A tool declaring a schema must return a map. Anything else — a list, a
scalar, or pre-formatted content blocks — cannot conform, so it becomes a
tool execution error (
isError: true) rather than a successful response missing the promisedstructuredContent. Unstructured content may accompany a structured result, but cannot replace it; a tool that needs to return content blocks directly should omitoutput_schema.
nexus_mcp does not validate result contents against the schema — matching
properties and types remains a contract you are responsible for keeping.
Formats Ecto changeset errors into a human-readable error tuple.
Raises unless schema is a valid MCP output schema.
MCP 2025-11-25 restricts outputSchema to type: "object" at the root, since
structuredContent is typed as a JSON object. Advertising an array or scalar
schema would promise clients a result shape the protocol cannot carry, so it is
rejected when the tool is defined rather than when it is first called.