Phantom.Router behaviour (phantom v0.1.1)

View Source

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

Usage

defmodule MyApp.MCP.Router do
  use Phantom.Router,
    name: "MyApp",
    vsn: "1.0"

  # Call MyApp.MCP.hello/3
  tool :hello, MyApp.MCP

  # Call MyApp.MCP.Router.hello/3
  tool :hello

  # Call MyApp.MCP.code_review/3
  prompt :code_review, MyApp.MCP

  # Call MyApp.MCP.studies/3
  resource "my_app:///studies/:id", MyApp.MCP, :studies,
    name: "Studies",
    mime_type: "application/json"

  # Call MyApp.MCP.questions/3
  resource "my_app:///questions/:id", MyApp.MCP, :questions,
    name: "Questions",
    mime_type: "application/html"
end

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.

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()} | {: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, any(), 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",
    }
  ]
)

# ...

def summarize(args, _request, session) do
  {:reply, %{}, session}
end

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

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

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"

# ...

def read_study(%{"id" => id}, _request, session) do
  {:reply, %{
    uri: "file:///project/lib/application.ex",
    mime_type: "text/x-elixir",
    text: "IO.puts "Hi""
  }, session}
end

resource_for(resource_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> MyMCPRouter.resource_for(session, :name_of_resource, id: 123)
%{
  uri: "myapp:///my-resource/123",
  mimeType: "application/json"
  text: "name of my 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",
  input_schema: %{
    required: [:message],
    properties: %{
      message: %{
        type: "string",
        description: "message to echo"
      }
    }
  }

###

def local_echo(params, _request, session) do
  {:reply, %{type: "text", text: params["message"]}, session}
end