Phantom.Router behaviour (phantom_mcp v0.2.0)
View SourceA 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
See prompt/3
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
@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()}
@callback disconnect(Phantom.Session.t()) :: any()
@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()}
@callback instructions(Phantom.Session.t()) :: {:ok, String.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()}
@callback server_info(Phantom.Session.t()) :: {:ok, %{name: String.t(), version: String.t()}} | {:error, any()}
@callback terminate(Phantom.Session.t()) :: {:ok, any()} | {:error, any()}
Functions
See prompt/3
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
@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"
# }
See resource/4
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
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"}
See tool/3
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