A tool: use Sake.Tool with options, arg lines, an optional output
block, and execute/2.
defmodule MyApp.MCP.SearchIssues do
use Sake.Tool, name: "search_issues", description: "Search issues", scope: "issues:read"
arg :query, :string, required: true
@impl true
def execute(%{query: q}, ctx), do: {:ok, Issues.search(ctx.scope, q)}
endOptions: name (default: the module's last segment in snake case),
title, description, icons, scope (one or many), annotations
(read_only, destructive, idempotent, open_world), stream,
ttl, meta, input_schema (a schema map instead of arg lines),
task (:optional or true, or :required: the tool runs as a task
for a client that takes one, see Sake.Tasks), ui (a Sake.UI
module: the app the host shows with the tool's result), visibility
([:model, :app] by default; [:app] for a tool only an app calls,
which a client without MCP Apps does not see), authentication
([acr: ["mfa"], max_age: 300]: how the person must have signed in
to call it, see "Step-up authentication" in Sake.Server).
Summary
Callbacks
Runs before execute/2, in the request itself, and returns the
arguments execute/2 receives (or a tool error). A task tool asks the
client for input here when the answer decides whether the task should
start at all: inside the request, the question travels as a multi
round-trip request; inside the task, it would wait on the task.
Functions
Declares an argument (or, inside output, an output property).
Declares the output schema with arg lines.
Callbacks
@callback prepare(args :: map(), ctx :: Sake.Context.t()) :: {:ok, map()} | {:error, String.t()}
Runs before execute/2, in the request itself, and returns the
arguments execute/2 receives (or a tool error). A task tool asks the
client for input here when the answer decides whether the task should
start at all: inside the request, the question travels as a multi
round-trip request; inside the task, it would wait on the task.
@callback spec() :: Sake.Tool.Spec.t()