# `ExMCP.Server.Handler`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/server/handler.ex#L1)

This module implements the standard MCP specification.

Behaviour for implementing MCP server handlers.

This behaviour defines callbacks for handling MCP protocol operations,
including tools, resources, prompts, and retained compatibility features.

> #### Protocol-deprecated callbacks {: .warning}
>
> MCP 2026-07-28 deprecated Roots, Sampling, and protocol Logging. ExMCP
> retains `handle_list_roots/1`, `handle_create_message/2`, and
> `handle_set_log_level/2` throughout 1.x. For new implementations, pass
> directories explicitly, call LLM provider APIs directly, and use stderr or
> OpenTelemetry for logging.

The handler behaviour pattern is an implementation detail but all callbacks
correspond to official MCP protocol methods.

## Metadata (_meta) Support

Handlers receive metadata passed by clients through the `_meta` field:

- For `handle_call_tool/3`: The `_meta` field is included in the arguments map
- For list operations: The cursor parameter may be a map containing `_meta`
- For other operations: Check the params for `_meta` field

Modern handlers can inspect the validated callback context and report
progress on the originating request's response stream:

    def handle_call_tool("my_tool", arguments, state) do
      if ExMCP.Server.Context.progress_token() do
        :ok = ExMCP.Server.Context.report_progress(25, 100, "Working")
      end

      # Process arguments and return the final response...
    end

## Required client capabilities

A modern handler that cannot continue without a capability declared in the
request may return the standard `-32021` error reason:

    {:error,
     ExMCP.Error.missing_required_client_capability(%{"sampling" => %{}}),
     state}

ExMCP preserves that protocol error, including its
`data.requiredCapabilities` field, across every server transport.

## Basic Example

> #### Tip
> For most servers, prefer the declarative DSL instead of implementing
> these callbacks by hand:
>
> ```elixir
> defmodule MyServer do
>   use ExMCP.Server.Handler
>   use ExMCP.Server.DSL, name: "my-server", version: "1.0.0"
>
>   tool "calculate", "Perform calculations" do
>     param :expression, :string, required: true
>     run fn %{expression: expr}, state ->
>       # ... compute ...
>       {:ok, %{content: [%{type: "text", text: "Result: ..."}]}, state}
>     end
>   end
> end
> ```
>
> Raw callbacks are useful when capabilities are fully dynamic.

    defmodule MyServer do
      use ExMCP.Server.Handler

      @impl true
      def handle_initialize(params, state) do
        # Check client's protocol version
        client_version = params["protocolVersion"]

        # Accept 2025-03-26 or propose 2024-11-05 as fallback
        negotiated_version = case client_version do
          "2025-03-26" -> "2025-03-26"
          "2024-11-05" -> "2024-11-05"
          _ -> "2025-03-26"  # Propose latest as default
        end

        {:ok, %{
          protocolVersion: negotiated_version,
          serverInfo: %{
            name: "my-server",
            version: "1.0.0"
          },
          capabilities: %{
            tools: %{},
            resources: %{},
            prompts: %{},
            sampling: %{}  # Enable LLM features
          }
        }, state}
      end

      @impl true
      def handle_list_tools(_cursor, state) do
        tools = [
          %{
            name: "calculate",
            description: "Perform calculations",
            inputSchema: %{
              type: "object",
              properties: %{
                expression: %{type: "string"}
              },
              required: ["expression"]
            }
          }
        ]
        {:ok, tools, nil, state}
      end

      @impl true
      def handle_call_tool("calculate", params, state) do
        # Your tool implementation
        case eval_expression(params["expression"]) do
          {:ok, result} ->
            # Send progress updates if token provided
            if ExMCP.Server.Context.progress_token() do
              :ok = ExMCP.Server.Context.report_progress(100, 100, "Complete")
            end

            {:ok, %{content: [%{type: "text", text: "Result: #{result}"}]}, state}

          {:error, reason} ->
            # Return tool execution error with isError flag
            error_result = %{
              content: [%{type: "text", text: "Calculation failed: #{reason}"}],
              isError: true
            }
            {:ok, error_result, state}
        end
      end
    end

## Advanced Features

### Structured Tool Output (Draft Feature)

> #### Draft Feature {: .info}
> This implements the MCP specification feature from version 2025-06-18.

Example implementation:

    defmodule WeatherServer do
      use ExMCP.Server.Handler

      @impl true
      def handle_list_tools(_cursor, state) do
        tools = [
          %{
            name: "get_weather",
            description: "Get current weather data",
            inputSchema: %{
              type: "object",
              properties: %{
                location: %{type: "string", description: "City name"}
              },
              required: ["location"]
            },
            # Draft feature: declare expected output structure
            outputSchema: %{
              type: "object",
              properties: %{
                temperature: %{type: "number", description: "Temperature in Celsius"},
                conditions: %{type: "string", description: "Weather conditions"},
                humidity: %{type: "number", description: "Humidity percentage"}
              },
              required: ["temperature", "conditions"]
            }
          }
        ]
        {:ok, tools, nil, state}
      end

      @impl true
      def handle_call_tool("get_weather", %{"location" => location}, state) do
        # Fetch weather data (example implementation)
        # In real code, this would call an actual weather API
        temp = 22.5
        conditions = "Partly cloudy"
        humidity = 65

        # Return both unstructured and structured content
        result = %{
          content: [%{
            type: "text",
            text: "Current weather in #{location}: #{temp}°C, #{conditions}"
          }],
          # Draft feature: structured content matching outputSchema
          structuredContent: %{
            "temperature" => temp,
            "conditions" => conditions,
            "humidity" => humidity
          }
        }

        {:ok, result, state}
      end

      # ... other callbacks ...
    end

### Sampling/LLM Integration

    @impl ExMCP.Server.Handler
    def handle_create_message(params, state) do
      messages = params["messages"]
      model_prefs = params["modelPreferences"]

      # Integrate with your LLM provider
      response = call_llm_api(messages, model_prefs)

      result = %{
        content: %{type: "text", text: response.text},
        model: response.model,
        stopReason: "stop"
      }

      {:ok, result, state}
    end

### Progress Notifications

For a modern streamable-HTTP request, report progress synchronously from the
callback. Each notification is written to that request's SSE response before
the final JSON-RPC response. The callback context is intentionally not
inherited by detached processes, because they could outlive or lose the
association with the originating request.

    @impl true
    def handle_call_tool("process_file", params, state) do
      file_path = params["path"]

      if ExMCP.Server.Context.progress_token() do
        :ok = ExMCP.Server.Context.report_progress(10, 100, "Starting")
      end

      result = process_file(file_path)

      if ExMCP.Server.Context.progress_token() do
        :ok = ExMCP.Server.Context.report_progress(100, 100, "Complete")
      end

      {:ok, %{content: [%{type: "text", text: result}]}, state}
    end

Transport-aware legacy servers may continue to use
`ExMCP.Server.notify_progress/4` with their server process and explicit
progress token.

### Dynamic Content Notifications

Notify clients when your server's content changes:

    def add_new_tool(server, tool_def) do
      # Add tool to your server state
      # Then notify clients
      ExMCP.Server.notify_tools_changed(server)
    end

    def update_resource(server, uri) do
      # Update the resource
      # Then notify clients
      ExMCP.Server.notify_resource_update(server, uri)
    end

## Callback Reference

The `use` macro provides default implementations for optional callbacks.
You only need to implement the callbacks for features your server supports.

# `initialize_result`

```elixir
@type initialize_result() :: ExMCP.Types.initialize_result()
```

# `input_required_return`

```elixir
@type input_required_return() ::
  {:input_required, %{required(String.t()) =&gt; map()}, state()}
  | {:input_required, %{required(String.t()) =&gt; map()}, request_state :: term(),
     state()}
```

# `prompt`

```elixir
@type prompt() :: ExMCP.Types.prompt()
```

# `resource`

```elixir
@type resource() :: ExMCP.Types.resource()
```

# `state`

```elixir
@type state() :: any()
```

# `tool`

```elixir
@type tool() :: ExMCP.Types.tool()
```

# `handle_call_tool`

```elixir
@callback handle_call_tool(name :: String.t(), arguments :: map(), state()) ::
  {:ok,
   ExMCP.Types.tool_result() | [map()] | ExMCP.Server.MRTR.InputRequired.t(),
   state()}
  | {:error, any(), state()}
  | input_required_return()
```

Handles a tool call.

The result can be returned in multiple formats:

1. Simple format (array of content items):
    {:ok, [%{type: "text", text: "Success"}], state}

2. Extended format (with isError flag):
    {:ok, %{content: [%{type: "text", text: "Error occurred"}], isError: true}, state}

3. Structured output format (2025-06-18 feature):
    {:ok, %{
      content: [%{type: "text", text: "Weather data"}],
      structuredContent: %{
        "temperature" => 22.5,
        "conditions" => "Partly cloudy",
        "humidity" => 65
      }
    }, state}

> #### Draft Feature {: .info}
> Structured tool output is available in MCP specification 2025-06-18.

Use the extended format with `isError: true` to indicate tool execution errors
that should be reported to the client as part of the result (not protocol errors).

When returning structured content, tools should provide both unstructured content
(for backwards compatibility) and structured content that conforms to the tool's
declared outputSchema.

# `handle_complete`
*optional* 

```elixir
@callback handle_complete(ref :: String.t(), params :: map(), state()) ::
  {:ok, result :: map(), state()} | {:error, any(), state()}
```

Handles a completion request for argument autocompletion.

This callback is invoked when a client requests completion suggestions
for tool arguments, resource URIs, or prompt arguments.

## Parameters
  - ref: Reference type (e.g., "argument")
  - params: Map containing:
    - name: The argument/parameter name to complete
    - value: The partial value to complete

## Return Value
  Should return a map with:
  - completion: List of completion suggestion strings

## Example

    def handle_complete("argument", %{"name" => "file_path", "value" => "/home/"}, state) do
      completions = ["/home/user/", "/home/documents/", "/home/downloads/"]
      {:ok, %{completion: completions}, state}
    end

Note: Servers should declare the `completion` capability to advertise support.

# `handle_create_message`
*optional* 

```elixir
@callback handle_create_message(params :: ExMCP.Types.create_message_params(), state()) ::
  {:ok, ExMCP.Types.create_message_result(), state()} | {:error, any(), state()}
```

Handles a sampling create message request.

MCP Sampling is deprecated as of 2026-07-28 and retained throughout ExMCP
1.x. New implementations should integrate directly with an LLM provider API.

# `handle_elicitation_complete`
*optional* 

```elixir
@callback handle_elicitation_complete(elicitation_id :: String.t(), state()) ::
  {:ok, state()} | {:error, any(), state()}
```

Handles a notifications/elicitation/complete notification.

Called when the client notifies that a URL-mode elicitation has completed.

# `handle_get_prompt`
*optional* 

```elixir
@callback handle_get_prompt(name :: String.t(), arguments :: map(), state()) ::
  {:ok, ExMCP.Types.prompt_message() | ExMCP.Server.MRTR.InputRequired.t(),
   state()}
  | {:error, any(), state()}
  | input_required_return()
```

Handles getting a prompt.

# `handle_initialize`

```elixir
@callback handle_initialize(params :: map(), state()) ::
  {:ok, initialize_result(), state()} | {:error, any(), state()}
```

Handles the initialize request from a client.

The params map contains:
- `"protocolVersion"` - The client's requested protocol version
- `"capabilities"` - The client's declared capabilities
- `"clientInfo"` - Information about the client implementation

## Version Negotiation

The server should check the client's protocol version and either:
1. Accept it by returning the same version
2. Propose an alternative supported version
3. Return an error if no compatible version exists

## Example

    def handle_initialize(params, state) do
      client_version = params["protocolVersion"]

      # Accept supported versions or propose latest
      negotiated_version = case client_version do
        "2025-03-26" -> "2025-03-26"
        "2024-11-05" -> "2024-11-05"
        _ -> "2025-03-26"  # Propose latest for unknown versions
      end

      # Use version-aware capabilities
      capabilities = ExMCP.Server.Capabilities.build_capabilities(__MODULE__, negotiated_version)

      {:ok, %{
        protocolVersion: negotiated_version,
        serverInfo: %{name: "my-server", version: "1.0.0"},
        capabilities: capabilities
      }, state}
    end

# `handle_list_prompts`
*optional* 

```elixir
@callback handle_list_prompts(cursor :: String.t() | nil, state()) ::
  {:ok, prompts :: [prompt()], next_cursor :: String.t() | nil, state()}
  | {:error, any(), state()}
```

Handles listing available prompts.

Supports pagination via optional cursor parameter.
Should return prompts and optional nextCursor for pagination.

# `handle_list_resource_templates`
*optional* 

```elixir
@callback handle_list_resource_templates(cursor :: String.t() | nil, state()) ::
  {:ok, resource_templates :: [ExMCP.Types.resource_template()],
   next_cursor :: String.t() | nil, state()}
  | {:error, any(), state()}
```

Handles listing resource templates.

Supports pagination via optional cursor parameter.
Should return resource templates and optional nextCursor for pagination.

# `handle_list_resources`
*optional* 

```elixir
@callback handle_list_resources(cursor :: String.t() | nil, state()) ::
  {:ok, resources :: [resource()], next_cursor :: String.t() | nil, state()}
  | {:error, any(), state()}
```

Handles listing available resources.

Supports pagination via optional cursor parameter.
Should return resources and optional nextCursor for pagination.

# `handle_list_roots`
*optional* 

```elixir
@callback handle_list_roots(state()) ::
  {:ok, [ExMCP.Types.root()], state()} | {:error, any(), state()}
```

Handles listing available roots.

MCP Roots is deprecated as of 2026-07-28 and retained throughout ExMCP 1.x.
Prefer tool parameters, resource URIs, or server configuration for new
implementations.

# `handle_list_tools`

```elixir
@callback handle_list_tools(cursor :: String.t() | nil, state()) ::
  {:ok, tools :: [tool()], next_cursor :: String.t() | nil, state()}
  | {:error, any(), state()}
```

Handles listing available tools.

Supports pagination via optional cursor parameter.
Should return tools and optional nextCursor for pagination.

# `handle_read_resource`
*optional* 

```elixir
@callback handle_read_resource(uri :: String.t(), state()) ::
  {:ok, ExMCP.Types.resource_contents() | ExMCP.Server.MRTR.InputRequired.t(),
   state()}
  | {:error, any(), state()}
  | input_required_return()
```

Handles reading a resource.

# `handle_set_log_level`
*optional* 

```elixir
@callback handle_set_log_level(level :: String.t(), state()) ::
  {:ok, state()} | {:error, any(), state()}
```

Handles setting the log level for the server.

This callback is called when the client sends a logging/setLevel request.
The level parameter will be one of: "debug", "info", "warning", "error".

The implementation should adjust the server's logging verbosity accordingly.

> #### Protocol-deprecated feature {: .warning}
>
> MCP protocol Logging is deprecated as of 2026-07-28 and retained
> throughout ExMCP 1.x. `logging/setLevel` remains applicable to legacy
> connections. Prefer stderr for stdio or OpenTelemetry for new
> observability integrations.

@doc api: :public

# `handle_subscribe_resource`
*optional* 

```elixir
@callback handle_subscribe_resource(uri :: String.t(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles resource subscription.

# `handle_task_cancel`
*optional* 

```elixir
@callback handle_task_cancel(task_id :: String.t(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles a tasks/cancel request.

Cancels a running task.

# `handle_task_get`
*optional* 

```elixir
@callback handle_task_get(task_id :: String.t(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles a tasks/get request.

Returns the current state of a task by ID.

# `handle_task_list`
*optional* 

```elixir
@callback handle_task_list(cursor :: String.t() | nil, state()) ::
  {:ok, tasks :: [map()], next_cursor :: String.t() | nil, state()}
  | {:error, any(), state()}
```

Handles a tasks/list request.

Returns a list of known tasks.

# `handle_task_result`
*optional* 

```elixir
@callback handle_task_result(task_id :: String.t(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles a tasks/result request.

Returns the result of a completed task.

# `handle_task_update`
*optional* 

```elixir
@callback handle_task_update(task_id :: String.t(), input_responses :: map(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles a modern tasks/update request.

Accepts client responses to the task's currently outstanding input requests.
A successful update is acknowledged with an empty modern result.

# `handle_unsubscribe_resource`
*optional* 

```elixir
@callback handle_unsubscribe_resource(uri :: String.t(), state()) ::
  {:ok, map(), state()} | {:error, any(), state()}
```

Handles resource unsubscription.

> #### ExMCP Extension {: .info}
> This callback handles the resources/unsubscribe method which is an ExMCP extension.
> The MCP specification does not define this method.

# `build_capabilities`

```elixir
@spec build_capabilities(module()) :: map()
```

Builds server capabilities based on which callbacks are implemented.

This is a convenience function that can be used in your handle_initialize/2
callback to automatically generate capabilities based on your handler's
implemented functions.

## Example

    def handle_initialize(params, state) do
      capabilities = ExMCP.Server.Handler.build_capabilities(__MODULE__)

      {:ok, %{
        protocolVersion: "2025-03-26",
        serverInfo: %{name: "my-server", version: "1.0.0"},
        capabilities: capabilities
      }, state}
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
