Phantom.Tool.JSONSchema behaviour (phantom_mcp v0.4.4)

Copy Markdown View Source

JSON Schema for tool input and output schemas.

Provides an Ecto-like DSL for defining input schemas that both generate JSON Schema for clients AND validate/coerce incoming params at dispatch time.

Router usage

When defining tools in a Phantom.Router, use a do block with field declarations:

tool :search, description: "Search for stuff" do
  field :query, :string, required: true
  field :limit, :integer, default: 10
  field :tags, {:array, :string}
end

This generates the equivalent JSON Schema sent to clients and validates incoming arguments at dispatch time, returning structured errors for invalid input.

You can still use the map-based input_schema option for full control:

tool :search,
  description: "Search for stuff",
  input_schema: %{
    required: ~w[query],
    properties: %{
      query: %{type: "string", description: "Search query"},
      limit: %{type: "integer", description: "Max results"}
    }
  }

The map-based form skips server-side validation — it only advertises the schema to clients.

Type system

DSL typeElixir guardJSON Schema type
:stringis_binary"string"
:integeris_integer"integer"
:numberis_number"number"
:booleanis_boolean"boolean"
{:array, subtype}is_list + validate each"array" with items
:map with do blockis_map + validate nested"object" with properties
ModuleNamedelegate to module schemadelegate to module schema

Field options

Every field accepts the following options:

OptionTypeDescription
:requiredbooleanMark the field as required (default: false)
:defaultanyDefault value injected when the field is absent
:descriptionstringDescription sent to clients in the JSON Schema
:enum or :inlistRestrict to an allowed set of values
:exclusionlistReject specific values
:minimum or :greater_than_or_equal_tonumberMinimum value (inclusive)
:maximum or :less_than_or_equal_tonumberMaximum value (inclusive)
:greater_thannumberExclusive minimum
:less_thannumberExclusive maximum
:min_lengthintegerMinimum string length or list length
:max_lengthintegerMaximum string length or list length
:lengthintegerExact string length or list length
:pattern or :formatstring or RegexRegex pattern the string must match
:messagestringCustom error message on validation failure
:validatefunctionCustom validator (see below)

Options like :in, :greater_than_or_equal_to, :less_than_or_equal_to, and :format are Ecto-style aliases for their JSON Schema equivalents.

Nested objects

Use a do block on a :map field to define nested properties:

tool :search, description: "Search" do
  field :query, :string, required: true

  field :filters, :map do
    field :category, :string, in: ~w[books movies music]
    field :min_price, :number, minimum: 0
  end
end

Custom validators

The :validate option accepts an anonymous function, a local function name, or an MFA tuple. The function receives the field value and must return :ok or {:error, reason}:

tool :create_user, description: "Create a user" do
  field :email, :string,
    required: true,
    pattern: ~r/@/,
    validate: fn value ->
      if String.contains?(value, "@"),
        do: :ok,
        else: {:error, "must be a valid email"}
    end

  # Or reference a local function by name:
  field :username, :string, required: true, validate: :validate_username
end

def validate_username(value) do
  if String.length(value) >= 3, do: :ok, else: {:error, "too short"}
end

Reusable schemas

Define reusable schemas as modules with use Phantom.Tool.JSONSchema:

defmodule MyApp.MCP.Schemas.Filters do
  use Phantom.Tool.JSONSchema

  input_schema do
    field :category, :string
    field :min_price, :number, minimum: 0
  end
end

Then reference the module as a field type:

tool :search, description: "Search" do
  field :query, :string, required: true
  field :filters, MyApp.MCP.Schemas.Filters
end

Summary

Functions

Build a field map from name, type, and opts.

Build a %JSONSchema{} from a list of field maps, pre-computing JSON Schema properties.

Define an input schema. Returns a %JSONSchema{} struct with validation metadata that is consumed by the following tool macro via the @phantom_input_schema attribute.

Passthrough when no schema is present, or when schema has no DSL fields.

Validate params against the field definitions. Returns {:ok, params_with_defaults} or {:error, [error_messages]}.

Types

field()

@type field() :: %{
  name: atom(),
  type: atom() | {:array, atom()} | module(),
  required: boolean(),
  default: any(),
  description: String.t() | nil,
  message: String.t() | nil,
  validate:
    (any() -> :ok | {:error, String.t()})
    | {module(), atom(), list()}
    | atom()
    | nil,
  enum: list() | nil,
  minimum: number() | nil,
  maximum: number() | nil,
  greater_than: number() | nil,
  less_than: number() | nil,
  min_length: non_neg_integer() | nil,
  max_length: non_neg_integer() | nil,
  length: non_neg_integer() | nil,
  pattern: String.t() | nil,
  exclusion: list() | nil,
  children: [field()] | nil
}

json()

@type json() :: %{
  :type => String.t(),
  optional(:required) => [String.t()],
  optional(:properties) => map()
}

t()

@type t() :: %Phantom.Tool.JSONSchema{
  fields: [field()] | nil,
  properties: map(),
  required: [String.t()],
  type: String.t()
}

Functions

build(schema)

build_field(name, type, opts \\ [])

Build a field map from name, type, and opts.

build_from_fields(fields)

Build a %JSONSchema{} from a list of field maps, pre-computing JSON Schema properties.

input_schema(list)

(macro)

Define an input schema. Returns a %JSONSchema{} struct with validation metadata that is consumed by the following tool macro via the @phantom_input_schema attribute.

input_schema do
  field :query, :string, required: true
  field :limit, :integer, default: 10
end

tool :search, description: "Search"

maybe_validate(arg1, params)

Passthrough when no schema is present, or when schema has no DSL fields.

to_json(schema)

validate(schema_or_fields, params, handler \\ nil)

Validate params against the field definitions. Returns {:ok, params_with_defaults} or {:error, [error_messages]}.