Urchin.Server behaviour (Urchin v0.1.0)

Copy Markdown View Source

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
end

Inside 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), an Urchin.Result.CallTool struct, 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

call_result()

@type call_result() ::
  {:ok, [map()]}
  | {:ok, [map()], keyword()}
  | {:ok, Urchin.Result.CallTool.t()}
  | {:error, Urchin.Error.t() | String.t()}

cursor()

@type cursor() :: String.t() | nil

list_result(item)

@type list_result(item) ::
  {:ok, [item]}
  | {:ok, [item], cursor()}
  | {:error, Urchin.Error.t() | String.t()}

Callbacks

call_tool(name, args, t)

(optional)
@callback call_tool(name :: String.t(), args :: map(), Urchin.Context.t()) ::
  call_result()

capabilities()

(optional)
@callback capabilities() :: map()

complete(ref, argument, completion_context, t)

(optional)
@callback complete(
  ref :: map(),
  argument :: map(),
  completion_context :: map(),
  Urchin.Context.t()
) :: {:ok, map()} | {:error, Urchin.Error.t() | String.t()}

get_prompt(name, args, t)

(optional)
@callback get_prompt(name :: String.t(), args :: map(), Urchin.Context.t()) ::
  {:ok, [map()]}
  | {:ok, [map()], String.t() | nil}
  | {:error, Urchin.Error.t() | String.t()}

init(arg)

(optional)
@callback init(arg :: term()) :: {:ok, term()} | {:error, term()}

instructions()

(optional)
@callback instructions() :: String.t() | nil

list_prompts(cursor, t)

(optional)
@callback list_prompts(cursor(), Urchin.Context.t()) :: list_result(Urchin.Prompt.t())

list_resource_templates(cursor, t)

(optional)
@callback list_resource_templates(cursor(), Urchin.Context.t()) ::
  list_result(Urchin.ResourceTemplate.t())

list_resources(cursor, t)

(optional)
@callback list_resources(cursor(), Urchin.Context.t()) :: list_result(Urchin.Resource.t())

list_tools(cursor, t)

(optional)
@callback list_tools(cursor(), Urchin.Context.t()) :: list_result(Urchin.Tool.t())

read_resource(uri, t)

(optional)
@callback read_resource(uri :: String.t(), Urchin.Context.t()) ::
  {:ok, [map()]} | {:error, Urchin.Error.t() | String.t()}

server_info()

@callback server_info() :: map()

set_log_level(level, t)

(optional)
@callback set_log_level(level :: String.t(), Urchin.Context.t()) :: :ok | {:error, term()}

subscribe_resource(uri, t)

(optional)
@callback subscribe_resource(uri :: String.t(), Urchin.Context.t()) ::
  :ok | {:error, term()}

unsubscribe_resource(uri, t)

(optional)
@callback unsubscribe_resource(uri :: String.t(), Urchin.Context.t()) ::
  :ok | {:error, term()}

Functions

prompt(name, opts \\ [], list)

(macro)

Declares a prompt. The do block receives args and ctx bindings and must return a get_prompt/3 result.

resource(uri, opts \\ [], list)

(macro)

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.

resource_template(uri_template, opts \\ [], list)

(macro)

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.

tool(name, opts \\ [], list)

(macro)

Declares a tool. The do block receives args and ctx bindings and must return a call_tool/3 result.