NexusMCP.Server.Tool (NexusMCP v0.5.0)

Copy Markdown View Source

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

deftool(name, description, opts_or_params \\ [], do_block \\ [])

(macro)

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

Inside 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}}
end

Supported 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"}}
end

When 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 promised structuredContent. Unstructured content may accompany a structured result, but cannot replace it; a tool that needs to return content blocks directly should omit output_schema.

nexus_mcp does not validate result contents against the schema — matching properties and types remains a contract you are responsible for keeping.

format_changeset_errors(map)

Formats Ecto changeset errors into a human-readable error tuple.

validate_output_schema!(tool_name, schema)

@spec validate_output_schema!(String.t(), map()) :: :ok

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.