Packages
Official-style Elixir SDK for the Model Context Protocol (MCP) — client and server with stdio and Streamable HTTP transports.
Current section
Files
Jump to
Current section
Files
lib/mcp/server/tool_context.ex
defmodule MCP.Server.ToolContext do
@moduledoc """
Context passed to `handle_call_tool/4` handlers during tool execution.
Provides an API for tool handlers to interact with the MCP server
while the tool is running — sending notifications (logging, progress)
and making server-to-client requests (sampling, elicitation).
## Example
def handle_call_tool("my_tool", args, ctx, state) do
ToolContext.log(ctx, "info", "Starting tool execution")
ToolContext.send_progress(ctx, 0, 100)
# Request LLM sampling from the client
{:ok, result} = ToolContext.request_sampling(ctx, %{
"messages" => [%{"role" => "user", "content" => %{"type" => "text", "text" => "Hello"}}],
"maxTokens" => 100
})
ToolContext.send_progress(ctx, 100, 100)
{:ok, [%{"type" => "text", "text" => "Done"}], state}
end
"""
defstruct [:server_pid, :request_id, :meta]
@type t :: %__MODULE__{
server_pid: pid(),
request_id: term(),
meta: map() | nil
}
@doc """
Sends a JSON-RPC notification to the client during tool execution.
The notification is routed through the MCP server and delivered
to the client on the same SSE stream as the tool call response.
"""
@spec send_notification(t(), String.t(), map()) :: :ok
def send_notification(%__MODULE__{} = ctx, method, params) do
GenServer.call(ctx.server_pid, {:context_notify, ctx.request_id, method, params})
end
@doc """
Sends a server-to-client JSON-RPC request and waits for the response.
Used for sampling, elicitation, and other bidirectional operations
during tool execution. The request is routed to the client on the
same SSE stream, and the client responds via a new POST.
Returns `{:ok, result}` or `{:error, reason}`.
"""
@spec request(t(), String.t(), map(), timeout()) :: {:ok, map()} | {:error, term()}
def request(%__MODULE__{} = ctx, method, params, timeout \\ 60_000) do
GenServer.call(ctx.server_pid, {:context_request, ctx.request_id, method, params}, timeout)
end
@doc """
Sends a log notification to the client.
Convenience wrapper around `send_notification/3` for logging.
"""
@spec log(t(), String.t(), term(), String.t() | nil) :: :ok
def log(%__MODULE__{} = ctx, level, data, logger \\ nil) do
params = %{"level" => level, "data" => data}
params = if logger, do: Map.put(params, "logger", logger), else: params
send_notification(ctx, "notifications/message", params)
end
@doc """
Sends a progress notification to the client.
Uses the `progressToken` from `_meta` if available.
"""
@spec send_progress(t(), number(), number() | nil) :: :ok
def send_progress(%__MODULE__{} = ctx, progress, total \\ nil) do
token = get_progress_token(ctx)
params = %{"progressToken" => token, "progress" => progress}
params = if total, do: Map.put(params, "total", total), else: params
send_notification(ctx, "notifications/progress", params)
end
@doc """
Sends a `sampling/createMessage` request to the client.
Returns `{:ok, result}` or `{:error, reason}`.
"""
@spec request_sampling(t(), map(), timeout()) :: {:ok, map()} | {:error, term()}
def request_sampling(%__MODULE__{} = ctx, params, timeout \\ 60_000) do
request(ctx, "sampling/createMessage", params, timeout)
end
@doc """
Sends an `elicitation/create` request to the client.
Returns `{:ok, result}` or `{:error, reason}`.
"""
@spec request_elicitation(t(), map(), timeout()) :: {:ok, map()} | {:error, term()}
def request_elicitation(%__MODULE__{} = ctx, params, timeout \\ 60_000) do
request(ctx, "elicitation/create", params, timeout)
end
defp get_progress_token(%__MODULE__{meta: meta}) when is_map(meta) do
Map.get(meta, "progressToken", 0)
end
defp get_progress_token(_ctx), do: 0
end