Behaviour and DSL for authoring MCP servers.
There are two ways to define a server:
DSL
defmodule Demo do
use Urchin.Server, name: "demo", version: "1.0.0"
tool "echo",
description: "Echo the message back",
input_schema: %{
"type" => "object",
"properties" => %{"message" => %{"type" => "string"}},
"required" => ["message"]
} do
{:ok, [Urchin.Content.text(args["message"])]}
end
resource "config://app", name: "config", mime_type: "application/json" do
{:ok, [Urchin.Content.text_resource("config://app", ~s({"ok":true}))]}
end
prompt "greet", arguments: [%{name: "name", required: true}] do
{:ok, [Urchin.Prompt.user_message(Urchin.Content.text("Hello " <> args["name"]))]}
end
endInside a tool/prompt block the bindings args (the decoded arguments map) and
ctx (an Urchin.Context) are available. Inside a resource/resource_template
block only ctx is available; for templates ctx.uri and ctx.params are set.
Capabilities are derived automatically from the declared features.
Behaviour
Implement the callbacks directly for full control or stateful servers. All
callbacks except server_info/0 are optional; a feature is considered supported
only when its callbacks are implemented (or declared via the DSL).
Handler return values
list_*callbacks:{:ok, items}or{:ok, items, next_cursor}call_tool/3:{:ok, content},{:ok, content, opts}(with:structured_content/:is_error), anUrchin.Result.CallToolstruct, or{:error, reason}read_resource/2:{:ok, contents}or{:error, reason}get_prompt/3:{:ok, messages}or{:ok, messages, description}
Any {:error, reason} where reason is a string or Urchin.Error becomes a JSON-RPC
error; raised exceptions become internal errors.
Summary
Functions
Declares a prompt. The do block receives args and ctx bindings and must return
a get_prompt/3 result.
Declares a static resource. The do block receives ctx (with ctx.uri set) and
must return a read_resource/2 result. Defaults :name to the URI.
Declares a resource template (RFC 6570). The do block receives ctx with
ctx.uri and ctx.params (extracted template variables). Defaults :name to the
template.
Declares a tool. The do block receives args and ctx bindings and must return
a call_tool/3 result.
Types
@type call_result() :: {:ok, [map()]} | {:ok, [map()], keyword()} | {:ok, Urchin.Result.CallTool.t()} | {:error, Urchin.Error.t() | String.t()}
@type cursor() :: String.t() | nil
@type list_result(item) :: {:ok, [item]} | {:ok, [item], cursor()} | {:error, Urchin.Error.t() | String.t()}
Callbacks
@callback call_tool(name :: String.t(), args :: map(), Urchin.Context.t()) :: call_result()
@callback capabilities() :: map()
@callback complete( ref :: map(), argument :: map(), completion_context :: map(), Urchin.Context.t() ) :: {:ok, map()} | {:error, Urchin.Error.t() | String.t()}
@callback get_prompt(name :: String.t(), args :: map(), Urchin.Context.t()) :: {:ok, [map()]} | {:ok, [map()], String.t() | nil} | {:error, Urchin.Error.t() | String.t()}
@callback instructions() :: String.t() | nil
@callback list_prompts(cursor(), Urchin.Context.t()) :: list_result(Urchin.Prompt.t())
@callback list_resource_templates(cursor(), Urchin.Context.t()) :: list_result(Urchin.ResourceTemplate.t())
@callback list_resources(cursor(), Urchin.Context.t()) :: list_result(Urchin.Resource.t())
@callback list_tools(cursor(), Urchin.Context.t()) :: list_result(Urchin.Tool.t())
@callback read_resource(uri :: String.t(), Urchin.Context.t()) :: {:ok, [map()]} | {:error, Urchin.Error.t() | String.t()}
@callback server_info() :: map()
@callback set_log_level(level :: String.t(), Urchin.Context.t()) :: :ok | {:error, term()}
@callback subscribe_resource(uri :: String.t(), Urchin.Context.t()) :: :ok | {:error, term()}
@callback unsubscribe_resource(uri :: String.t(), Urchin.Context.t()) :: :ok | {:error, term()}
Functions
Declares a prompt. The do block receives args and ctx bindings and must return
a get_prompt/3 result.
Declares a static resource. The do block receives ctx (with ctx.uri set) and
must return a read_resource/2 result. Defaults :name to the URI.
Declares a resource template (RFC 6570). The do block receives ctx with
ctx.uri and ctx.params (extracted template variables). Defaults :name to the
template.
Declares a tool. The do block receives args and ctx bindings and must return
a call_tool/3 result.