Packages

Reusable Streamable HTTP MCP runtime and Phoenix router DSL

Current section

Files

Jump to
mcp_kit lib mcp_kit tool.ex
Raw

lib/mcp_kit/tool.ex

defmodule MCPKit.Tool do
@moduledoc """
Behaviour and schema DSL for MCP tool modules.
A tool module declares its JSON-schema-like input contract with `schema/1`
and implements `execute/2` to return an `MCPKit.Response`.
Example:
defmodule MyApp.MCP.Tools.Ping do
use MCPKit.Tool
alias MCPKit.Response
schema do
field :message, :string, required: true
end
def execute(arguments, context) do
{:reply, Response.tool() |> Response.structured(arguments), context}
end
end
Supported schema nodes today:
- `field/2`
- `field/3`
- `embeds_many/2`
- `embeds_many/3`
"""
@doc """
Returns the description shown in `tools/list`.
"""
@callback description() :: String.t() | nil
@doc """
Returns the JSON-schema-like MCP input schema generated from `schema/1`.
"""
@callback input_schema() :: map()
@doc """
Validates and normalizes incoming tool arguments.
This callback is generated by `schema/1` unless overridden.
"""
@callback validate_arguments(map()) :: {:ok, map()} | {:error, String.t()}
@doc """
Executes the tool and returns an `MCPKit.Response`.
The context map currently includes at least `:session`.
"""
@callback execute(map(), map()) :: {:reply, MCPKit.Response.t(), map()}
defmacro __using__(_opts) do
quote do
@behaviour MCPKit.Tool
import MCPKit.Tool, only: [schema: 1]
def description, do: MCPKit.Tool.module_description(@moduledoc)
defoverridable description: 0
end
end
@doc """
Declares the MCP input schema for a tool.
This macro generates `input_schema/0` and `validate_arguments/1` for the tool
module.
"""
defmacro schema(do: block) do
spec = MCPKit.ArgumentSchema.parse_schema_block(block)
quote do
@mcp_argument_spec unquote(Macro.escape(spec))
def input_schema, do: MCPKit.ArgumentSchema.to_json_schema(@mcp_argument_spec)
def validate_arguments(arguments),
do: MCPKit.ArgumentSchema.validate_arguments(arguments, @mcp_argument_spec)
end
end
@doc false
def module_description({_, value}), do: module_description(value)
@doc false
def module_description(value) when is_binary(value), do: String.trim(value)
@doc false
def module_description(_value), do: nil
end