Phantom.Router behaviour (phantom_mcp v0.2.0)

View Source

A DSL for defining MCP servers. This module provides functions that define tools, resources, and prompts.

See Phantom for usage examples.

Telemetry

Telemetry is provided with these events:

  • [:phantom, :dispatch, :start] with meta: ~w[method params request session]a
  • [:phantom, :dispatch, :stop] with meta: ~w[method params request result session]a
  • [:phantom, :dispatch, :exception] with meta: ~w[method kind reason stacktrace params request session]a

Summary

Functions

Define a prompt that can be retrieved by the MCP client.

Reads the resource given its URI, primarily for embedded resources.

Define a resource that can be read by the MCP client.

Constructs a response map for the given resource with the provided parameters. This function is provided to your MCP Router that accepts the session instead.

Define a tool that can be called by the MCP client.

Callbacks

connect(t, headers)

@callback connect(Phantom.Session.t(), Plug.Conn.headers()) ::
  {:ok, Phantom.Session.t()}
  | {:unauthorized | 403,
     www_authenticate_header :: Phantom.Plug.www_authenticate()}
  | {:forbidden | 401, message :: String.t()}
  | {:error, any()}

disconnect(t)

@callback disconnect(Phantom.Session.t()) :: any()

dispatch_method(t, module, map, t)

@callback dispatch_method(String.t(), module(), map(), Phantom.Session.t()) ::
  {:reply, any(), Phantom.Session.t()}
  | {:noreply, Phantom.Session.t()}
  | {:error, %{code: neg_integer(), message: binary()}, Phantom.Session.t()}

instructions(t)

@callback instructions(Phantom.Session.t()) :: {:ok, String.t()}

list_resources(arg1, map, t)

@callback list_resources(String.t() | nil, map(), Phantom.Session.t()) ::
  {:reply, Resource.list(), Phantom.Session.t()}
  | {:noreply, Phantom.Session.t()}
  | {:error, any(), Phantom.Session.t()}

server_info(t)

@callback server_info(Phantom.Session.t()) ::
  {:ok, %{name: String.t(), version: String.t()}} | {:error, any()}

terminate(t)

@callback terminate(Phantom.Session.t()) :: {:ok, any()} | {:error, any()}

Functions

prompt(name, opts_or_handler \\ [])

(macro)

See prompt/3

prompt(name, handler, opts)

(macro)

Define a prompt that can be retrieved by the MCP client.

Examples

prompt :summarize,
  description: "A text prompt",
  completion_function: :summarize_complete,
  arguments: [
    %{
      name: "text",
      description: "The text to summarize",
    },
    %{
      name: "resource",
      description: "The resource to summarize",
    }
  ]
)

# ...

require Phantom.Prompt, as: Prompt
def summarize(args, _request, session) do
  {:reply, Prompt.response([
    assistant: Prompt.text("You're great"),
    user: Prompt.text("No you're great!")
  ], session}
end

def summarize_complete("text", _typed_value, session) do
  {:reply, ["many values"], session}
end

def summarize_complete("resource", _typed_value, session) do
  # list of IDs
  {:reply, ["123"], session}
end

read_resource(session, router, uri_struct)

@spec read_resource(Phantom.Session.t(), module(), URI.t()) ::
  {:ok, uri_string :: String.t(),
   Phantom.Resource.blob_content() | Phantom.Resource.text_content()}
  | {:error, error_response :: map()}

Reads the resource given its URI, primarily for embedded resources.

This is available on your router as: MyApp.MCP.Router.read_resource(session, name, params)

For example:

iex> MyApp.MCP.Router.read_resource(session, :my_resource, id: 321)
#=> {:ok, "myapp:///resources/123", %{
#   blob: "abc123"
#   uri: "myapp:///resources/123",
#   mimeType: "audio/wav",
#   name: "Some audio",
#   title: "Super audio"
# }

resource(pattern, handler)

(macro)

See resource/4

resource(pattern, handler, function_or_opts, opts \\ [])

(macro)

Define a resource that can be read by the MCP client.

Examples

resource "app:///studies/:id", MyApp.MCP, :read_study,
  description: "A study",
  mime_type: "application/json"

# ...

require Phantom.Resource, as: Resource
def read_study(%{"id" => id}, _request, session) do
  {:reply, Response.response(
    Response.text("IO.puts "Hi"")
  ), session}
end

resource_uri(router_or_templates, name, path_params \\ %{})

Constructs a response map for the given resource with the provided parameters. This function is provided to your MCP Router that accepts the session instead.

For example

iex> MyApp.MCP.Router.resource_uri(session, :my_resource, id: 123)
{:ok, "myapp:///my-resource/123"}

iex> MyApp.MCP.Router.resource_uri(session, :my_resource, foo: "error")
{:error, "Parameters don't match resource."}

iex> MyApp.MCP.Router.resource_uri(session, :unknown, id: 123)
{:error, "Router not found for resource"}

tool(name, opts_or_handler \\ [])

(macro)

See tool/3

tool(name, handler, opts)

(macro)

Define a tool that can be called by the MCP client.

Examples

tool :local_echo,
  description: "A test that echos your message",
  # or supply a `@description` before defining the tool
  input_schema: %{
    required: [:message],
    properties: %{
      message: %{
        type: "string",
        description: "message to echo"
      }
    }
  }

### handled by your function syncronously:

def local_echo(params, session) do
  # Maps will be JSON-encoded and also provided
  # as structured content.
  {:reply, Phantom.Tool.text(params), session}
end

# Or asyncronously:

def local_echo(params, session) do
  Task.async(fn ->
    Process.sleep(1000)
    Session.respond(session, Phantom.Tool.text(params))
  end)

  {:noreply, session}
end