# `ConduitMcp.Server`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.10.1/lib/conduit_mcp/server.ex#L1)

Behaviour for implementing stateless MCP servers.

An MCP server provides tools, resources, and prompts to LLM clients.
Servers implement callbacks to handle client requests concurrently.

## Architecture

Server modules are stateless — just pure compiled functions:

- No GenServer, no Agent, no process overhead
- No supervision tree required
- Callbacks receive the `Plug.Conn` for request context
- Each HTTP request runs in parallel (limited only by Bandit's process pool)

## Example (Using DSL - Recommended)

    defmodule MyApp.MCPServer do
      use ConduitMcp.Server

      tool "echo", "Echo back the input" do
        param :message, :string, "Message to echo", required: true

        handle fn _conn, %{"message" => msg} ->
          text(msg)
        end
      end

      tool "calculate", "Perform calculations" do
        param :operation, :string, "Math operation", enum: ~w(add sub mul div), required: true
        param :a, :number, "First number", required: true
        param :b, :number, "Second number", required: true

        handle MyMath, :calculate
      end
    end

## Example (Manual - Advanced)

    defmodule MyApp.MCPServer do
      use ConduitMcp.Server, dsl: false  # Disable DSL

      @tools [
        %{
          "name" => "echo",
          "description" => "Echo back the input",
          "inputSchema" => %{
            "type" => "object",
            "properties" => %{
              "message" => %{"type" => "string", "description" => "Message to echo"}
            },
            "required" => ["message"]
          }
        }
      ]

      @impl true
      def handle_list_tools(_conn) do
        {:ok, %{"tools" => @tools}}
      end

      @impl true
      def handle_call_tool(_conn, "echo", %{"message" => msg}) do
        {:ok, %{"content" => [%{"type" => "text", "text" => msg}]}}
      end
    end

Then in your supervision tree, just pass the module:

    {Bandit,
     plug: {ConduitMcp.Transport.StreamableHTTP, server_module: MyApp.MCPServer},
     port: 4001}

## Using Connection Context

The `conn` parameter allows access to request metadata:

    def handle_call_tool(conn, "private_data", _params) do
      # Check authentication
      user_id = conn.assigns[:user_id]

      # Access headers
      auth_header = Plug.Conn.get_req_header(conn, "authorization")

      {:ok, %{"content" => [%{"type" => "text", "text" => "Data for #{user_id}"}]}}
    end

## Mutable State

If you need mutable state, use external mechanisms:

    # Option 1: ETS
    def handle_call_tool(_conn, "increment", _params) do
      :ets.update_counter(:my_counter, :count, 1)
      count = :ets.lookup_element(:my_counter, :count, 2)
      {:ok, %{"content" => [%{"type" => "text", "text" => "Count: #{count}"}]}}
    end

    # Option 2: Agent/GenServer
    def handle_call_tool(_conn, "get_cache", %{"key" => key}) do
      value = MyApp.Cache.get(key)
      {:ok, %{"content" => [%{"type" => "text", "text" => value}]}}
    end

# `conn`

```elixir
@type conn() :: Plug.Conn.t()
```

# `prompt_args`

```elixir
@type prompt_args() :: map()
```

# `prompt_name`

```elixir
@type prompt_name() :: String.t()
```

# `tool_name`

```elixir
@type tool_name() :: String.t()
```

# `tool_params`

```elixir
@type tool_params() :: map()
```

# `uri`

```elixir
@type uri() :: String.t()
```

# `handle_call_tool`
*optional* 

```elixir
@callback handle_call_tool(conn(), tool_name(), tool_params()) ::
  {:ok, result :: map()} | {:error, error :: map()}
```

Handle tool execution.

# `handle_complete`
*optional* 

```elixir
@callback handle_complete(conn(), ref :: map(), argument :: map()) ::
  {:ok, map()} | {:error, map()}
```

Handle autocompletion for prompt arguments or resource template parameters.

Receives the reference type (`:prompt` or `:resource`), the reference name,
the argument/parameter name, and the partial value typed so far.

Should return `{:ok, %{"completion" => %{"values" => [...], "hasMore" => false}}}`.

# `handle_get_prompt`
*optional* 

```elixir
@callback handle_get_prompt(conn(), prompt_name(), prompt_args()) ::
  {:ok, messages :: map()} | {:error, error :: map()}
```

Handle getting a prompt.

# `handle_list_prompts`
*optional* 

```elixir
@callback handle_list_prompts(conn()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

Handle listing available prompts.

The arity-2 variant receives request params for pagination support.

# `handle_list_prompts`
*optional* 

```elixir
@callback handle_list_prompts(conn(), params :: map()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

# `handle_list_resource_templates`
*optional* 

```elixir
@callback handle_list_resource_templates(conn()) ::
  {:ok, %{required(String.t()) =&gt; [map()]}} | {:error, map()}
```

Handle listing resource templates (resources with `{param}` placeholders).

Returns a map with `"resourceTemplates"` key containing a list of template
descriptors, each with at least `"uriTemplate"`.

Implemented automatically by DSL and Endpoint modes; manual-mode servers
can implement this to advertise URI templates to clients.

# `handle_list_resources`
*optional* 

```elixir
@callback handle_list_resources(conn()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

Handle listing available resources.

The arity-2 variant receives request params for pagination support.

# `handle_list_resources`
*optional* 

```elixir
@callback handle_list_resources(conn(), params :: map()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

# `handle_list_tools`
*optional* 

```elixir
@callback handle_list_tools(conn()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

Handle listing available tools.

The arity-2 variant receives request params (e.g., `%{"cursor" => "..."}`)
for pagination support. Implement arity-2 to support cursor-based pagination.

# `handle_list_tools`
*optional* 

```elixir
@callback handle_list_tools(conn(), params :: map()) ::
  {:ok, %{optional(String.t()) =&gt; any()}} | {:error, map()}
```

# `handle_read_resource`
*optional* 

```elixir
@callback handle_read_resource(conn(), uri()) ::
  {:ok, content :: map()} | {:error, error :: map()}
```

Handle reading a resource.

# `handle_set_log_level`
*optional* 

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

Handle setting the log level for server-to-client logging.

Receives the desired log level as a string (e.g., "debug", "info", "warning", "error").

# `handle_subscribe_resource`
*optional* 

```elixir
@callback handle_subscribe_resource(conn(), uri()) :: {:ok, map()} | {:error, map()}
```

Handle subscribing to resource changes.

Receives the resource URI to subscribe to.

# `handle_unsubscribe_resource`
*optional* 

```elixir
@callback handle_unsubscribe_resource(conn(), uri()) :: {:ok, map()} | {:error, map()}
```

Handle unsubscribing from resource changes.

Receives the resource URI to unsubscribe from.

---

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