The Model Context Protocol (MCP) allows servers to expose tools that can be invoked by language models. Tools enable models to interact with external systems, such as querying databases, calling APIs, or performing computations. Each tool is uniquely identified by a name and includes metadata describing its schema.
https://modelcontextprotocol.io/specification/2025-03-26/server/tools
sequenceDiagram
participant LLM
participant Client
participant Server
Note over Client,Server: Discovery
Client->>Server: tools/list
Server-->>Client: List of tools
Note over Client,LLM: Tool Selection
LLM->>Client: Select tool to use
Note over Client,Server: Invocation
Client->>Server: tools/call
Server-->>Client: Tool result
Client->>LLM: Process result
Note over Client,Server: Updates
Server--)Client: tools/list_changed
Client->>Server: tools/list
Server-->>Client: Updated tools
Summary
Functions
Tool response as audio content
Build a tool spec. Be intentional with the name and description when defining the tool since it will inform the LLM when to use the tool.
Embedded resource response.
Tool response as image content
Tool response indicating the server needs more input from the client before
it can complete this call (MCP 2026-07-28 stateless-core flow).
Build an input_required result from an Elicit struct and an opaque
requestState term.
Resource link reponse.
Formats the response from an MCP Router to the MCP specification
Return any JSON value as structured tool content.
Represent a Tool spec as json when listing the available tools to clients.
Types
@type embedded_resource_response() :: %{ content: [ %{ type: :resource, resource: embedded_text_resource() | embedded_blob_resource() } ] }
@type error_response() :: %{ isError: true, content: [%{type: :text, text: String.t()}] }
@type json() :: %{ :name => String.t(), :description => String.t(), :inputSchema => Phantom.Tool.JSONSchema.json(), optional(:outputSchema) => Phantom.Tool.JSONSchema.json(), optional(:annotations) => Phantom.Tool.Annotation.json() }
@type response() :: audio_response() | embedded_resource_response() | error_response() | image_response() | resource_link_response() | structured_response() | text_response()
@type t() :: %Phantom.Tool{ annotations: Phantom.Tool.Annotation.t(), description: String.t(), function: atom(), handler: module(), icons: [Phantom.Icon.t()] | nil, input_schema: Phantom.Tool.JSONSchema.t() | nil, meta: map(), mime_type: String.t(), name: String.t(), output_schema: Phantom.Tool.JSONSchema.t() | nil, ui: Phantom.UI.t() | nil }
@type text_response() :: %{content: [%{type: :text, text: String.t()}]}
Functions
Tool response as audio content
The :mime_type will be fetched from the current tool within the scope of
the request if not provided, but you will need to provide the rest.
binary- Binary data.:mime_type(optional) MIME type. Defaults to"application/octet-stream"
For example:
Phantom.Tool.audio(File.read!("game-over.wav"))
Phantom.Tool.audio(
File.read!("game-over.wav"),
mime_type: "audio/wav"
)
Build a tool spec. Be intentional with the name and description when defining the tool since it will inform the LLM when to use the tool.
The Phantom.Router.tool/4 macro will build these specs.
Fields:
:name- The name of the tool.:titleA human-readable title for the tool, useful for UI display.:description- The description of the tool and when to use it.:mime_type- the MIME type of the results.:handler- The module to call.:function- The function to call on the handler module.:output_schema- the JSON schema of the results.:input_schema- The JSON schema of the input arguments.:read_onlyIftrue, indicates the tool does not modify its environment.:destructiveIftrue, the tool may perform destructive updates (only meaningful when:read_onlyisfalse).:idempotentIftrue, calling the tool repeatedly with the same arguments has no additional effect (only meaningful when:read_onlyisfalse).:open_worldIftrue, the tool may interact with an "open world" of external entities.
@spec embedded_resource(string_uri :: String.t(), map()) :: embedded_resource_response()
Embedded resource response.
Typically used with your router's read_resource/3 function.
See Phantom.Router.read_resource/3 for more information
@spec error(message :: String.t()) :: error_response()
Tool response as image content
The :mime_type will be fetched from the current tool within the scope of
the request if not provided, but you will need to provide the rest.
binary- Binary data.:mime_type(optional) MIME type. Defaults to"application/octet-stream"
For example:
Phantom.Tool.image(File.read!("tower.png"))
Phantom.Tool.audio(
File.read!("tower.png"),
mime_type: "image/png"
)
Tool response indicating the server needs more input from the client before
it can complete this call (MCP 2026-07-28 stateless-core flow).
Returns a result map with the raw requestState term. The Plug dispatcher
encrypts that term with Phantom.RequestState before it leaves the server;
the client echoes the resulting opaque token back in _meta.requestState
on the follow-up tools/call, and Phantom restores it onto
session.state so the handler can resume.
At least one of these options is required:
:input_requests— non-empty map of stable request keys to embedded MCP requests:state— any term the handler needs to resume work; arrives atsession.stateon the follow-up call
See Phantom for the full multi-round-trip pattern.
@spec input_required(Phantom.Elicit.t(), term()) :: map()
Build an input_required result from an Elicit struct and an opaque
requestState term.
The request_state is passed through verbatim; the dispatcher encrypts
it with Phantom.RequestState before the response leaves the server.
@spec resource_link(string_uri :: String.t(), Phantom.ResourceTemplate.t(), map()) :: resource_link_response()
Resource link reponse.
Typically used with your router's resource_uri/3 function.
See Phantom.Router.resource_uri/3 for more information.
Formats the response from an MCP Router to the MCP specification
@spec structured(term()) :: structured_response()
Return any JSON value as structured tool content.
@spec text(map()) :: structured_response()
@spec text(String.t()) :: text_response()
Represent a Tool spec as json when listing the available tools to clients.