Packages

MCP (Model Context Protocol) server and client for Raxol. Provides JSON-RPC 2.0 protocol handling, tool/resource registry, stdio and SSE transports. Auto-derives agent tools from your widget tree.

Current section

Files

Jump to
raxol_mcp lib raxol mcp tool_provider.ex
Raw

lib/raxol/mcp/tool_provider.ex

defmodule Raxol.MCP.ToolProvider do
@moduledoc """
Behaviour for widgets that expose MCP tools.
Each interactive widget can implement this behaviour to declare what
semantic actions (tools) are available given its current state. The
`TreeWalker` traverses the view tree, calls `mcp_tools/1` on each
widget that implements this behaviour, and registers the resulting
tools with the MCP Registry.
## Tool Specs
`mcp_tools/1` returns a list of tool spec maps:
%{
name: "click",
description: "Click the Submit button",
inputSchema: %{type: "object", properties: %{}}
}
The `name` is the action name (e.g., "click", "type_into"). The
TreeWalker namespaces it with the widget ID: `"submit_btn.click"`.
## Handle Tool Call
`handle_tool_call/3` executes the action. It receives the action name,
arguments map, and a context map with `:widget_id`, `:widget_state`,
and `:dispatcher_pid`.
Return values:
- `{:ok, result}` -- pure read, no state change
- `{:ok, result, [message]}` -- result + TEA messages to dispatch
- `{:error, reason}` -- failure
## Example
defmodule MyApp.Button do
@behaviour Raxol.MCP.ToolProvider
@impl Raxol.MCP.ToolProvider
def mcp_tools(%{attrs: %{disabled: true}}), do: []
def mcp_tools(state) do
[%{
name: "click",
description: "Click the '\#{state[:attrs][:label] || "Button"}' button",
inputSchema: %{type: "object", properties: %{}}
}]
end
@impl Raxol.MCP.ToolProvider
def handle_tool_call("click", _args, context) do
{:ok, "Clicked", [context.widget_state[:attrs][:on_click]]}
end
end
"""
@type tool_spec :: %{
name: String.t(),
description: String.t(),
inputSchema: map()
}
@type context :: %{
widget_id: String.t(),
widget_state: map(),
dispatcher_pid: pid() | nil
}
@type result ::
{:ok, term()}
| {:ok, term(), [term()]}
| {:error, term()}
@doc """
Returns the list of MCP tool specs for the widget's current state.
Called by `TreeWalker` during view tree traversal. The widget state
is the raw map from the view tree (the output of `view/1`), not the
internal component struct.
"""
@callback mcp_tools(widget_state :: map()) :: [tool_spec()]
@doc """
Handles an MCP tool call for this widget.
`name` is the action name (e.g., "click", "type_into") without the
widget ID namespace prefix.
`args` is the arguments map from the MCP client.
`context` contains `:widget_id`, `:widget_state`, and `:dispatcher_pid`.
"""
@callback handle_tool_call(
name :: String.t(),
args :: map(),
context :: context()
) :: result()
@doc """
Returns true if the given module implements the ToolProvider behaviour.
"""
@spec tool_provider?(module()) :: boolean()
def tool_provider?(module) do
Code.ensure_loaded?(module) and
function_exported?(module, :mcp_tools, 1) and
function_exported?(module, :handle_tool_call, 3)
end
end