Behaviour for defining an MCP server.
Supports the three MCP server primitives:
- Tools — model-controlled functions (
deftool) - Prompts — user-controlled message templates (
defprompt) - Resources — application-controlled context (
defresource,defresource_template)
Usage
Tools (DSL style)
defmodule MyApp.MCP do
use NexusMCP.Server,
name: "my-app",
version: "1.0.0"
deftool "get_page", "Get a page by ID",
params: [id: {:string!, "Page ID"}] do
page = CMS.get_page!(params["id"])
{:ok, Map.take(page, [:id, :title, :slug, :body])}
end
endPrompts
defprompt "code_review", "Ask the model to review code",
arguments: [code: {:string!, "The code to review"}] do
{:ok, [
%{role: "user", content: %{type: "text",
text: "Please review this code:\n" <> params["code"]}}
]}
endResources
defresource "config://app",
name: "app_config",
description: "Application configuration",
mime_type: "application/json" do
{:ok, Jason.encode!(MyApp.config())}
end
defresource_template "file:///{path}",
name: "project_files",
description: "Files in the project directory",
mime_type: "text/plain" do
{:ok, File.read!(params["path"])}
endOptions
:name- Server name (required):version- Server version (required):idle_timeout- Session idle timeout in ms (default: 7_200_000 / 2 hours):hibernate_after- Hibernate a session once it has been quiet for this many ms (default: 60_000). Set to:infinityto disable hibernation.
Session memory
A session process keeps the heap it grew while handling requests, and the BEAM does not shrink it again on its own. A session that returned a few large tool results therefore keeps holding that heap for as long as it lives, even while completely idle - with many concurrent sessions that dominates memory use.
Hibernating collapses the heap to a minimum and the process re-grows it on
the next message. Doing that after every call costs real time (roughly 2x
on a trivial call), so it is debounced: each request re-arms the timer, an
active session never hibernates, and only one that has gone quiet pays for
it. Set hibernate_after: :infinity to opt out.
Summary
Callbacks
Handle a prompts/get call. Receives the prompt name, arguments, and session.
Handle a resources/read call. Receives the URI, the params captured from the
URI template (empty map for static resources), and the session.
Handle a tool call. Receives the tool name, params, and session state.
How long a session must be quiet before it hibernates, in milliseconds.
Returns the idle timeout in milliseconds.
Per-session initialization. Called when a new session is created.
Receives the session map and should return {:ok, session} or {:error, reason}.
Returns the list of prompt definitions.
Returns the list of resource template definitions.
Returns the list of static resource definitions.
Returns server info (name, version). Generated by use NexusMCP.Server.
Returns the list of tool definitions.
Wraps every tool call execution. Runs in the Task process before the handler. Override to set up process-local state (e.g. tenant context) or rescue errors.
Types
Callbacks
@callback handle_prompt_get(name :: String.t(), arguments :: map(), session :: session()) :: {:ok, [map()]} | {:error, String.t() | :not_found}
Handle a prompts/get call. Receives the prompt name, arguments, and session.
Should return {:ok, messages} where messages is a list of
%{role: "user" | "assistant", content: %{...}} maps.
@callback handle_resource_read(uri :: String.t(), params :: map(), session :: session()) :: {:ok, String.t() | map()} | {:error, String.t() | :not_found}
Handle a resources/read call. Receives the URI, the params captured from the
URI template (empty map for static resources), and the session.
Should return {:ok, content} where content is one of:
- A string — wrapped as
textif the resource'smimeTypeis textual, otherwise base64-encoded asblob %{text: string}or%{blob: base64}— passed through
Return {:error, :not_found} for unknown URIs.
@callback handle_tool_call(name :: String.t(), params :: map(), session :: session()) :: {:ok, term()} | {:error, String.t()}
Handle a tool call. Receives the tool name, params, and session state.
@callback hibernate_after() :: non_neg_integer() | :infinity
How long a session must be quiet before it hibernates, in milliseconds.
:infinity disables hibernation.
Optional. use NexusMCP.Server defines it from the :hibernate_after
option; a module implementing this behaviour by hand may leave it out, in
which case sessions fall back to the 60s default.
@callback idle_timeout() :: non_neg_integer()
Returns the idle timeout in milliseconds.
Per-session initialization. Called when a new session is created.
Receives the session map and should return {:ok, session} or {:error, reason}.
@callback prompts() :: [map()]
Returns the list of prompt definitions.
@callback resource_templates() :: [map()]
Returns the list of resource template definitions.
@callback resources() :: [map()]
Returns the list of static resource definitions.
Returns server info (name, version). Generated by use NexusMCP.Server.
@callback tools() :: [map()]
Returns the list of tool definitions.
@callback wrap_tool_call( session :: session(), fun :: (-> {:ok, term()} | {:error, String.t()}) ) :: {:ok, term()} | {:error, String.t()}
Wraps every tool call execution. Runs in the Task process before the handler. Override to set up process-local state (e.g. tenant context) or rescue errors.
Default implementation just calls fun.().