# ExMCP User Guide

A practical guide to building MCP clients and servers with ExMCP.

## Table Of Contents

1. [Installation](#installation)
2. [Server DSL](#server-dsl)
3. [Low-Level Handlers](#low-level-handlers)
4. [BEAM-Local MCP](#beam-local-mcp)
5. [Clients](#clients)
6. [Transports](#transports)
7. [Resilience And Pipelines](#resilience-and-pipelines)
8. [Troubleshooting](#troubleshooting)

## Installation

```elixir
def deps do
  [
    {:ex_mcp, "~> 1.0.0-rc.1"}
  ]
end
```

## Server DSL

Use `ExMCP.Server.Handler` with `ExMCP.Server.DSL` for most servers:

```elixir
defmodule MyServer do
  use ExMCP.Server.Handler
  use ExMCP.Server.DSL, name: "my-server", version: "1.0.0"

  tool "echo", "Echoes the input message" do
    param :message, :string, required: true

    run fn %{message: message}, state ->
      {:ok, %{content: [%{type: "text", text: message}]}, state}
    end
  end

  resource "config://app", "Application configuration" do
    mime_type "application/json"

    read fn _params, state ->
      {:ok, %{text: Jason.encode!(%{debug: false})}, state}
    end
  end

  prompt "summarize", "Summarize text" do
    arg :text, required: true

    render fn %{text: text}, state ->
      {:ok,
       %{
         messages: [
           %{role: "user", content: %{type: "text", text: "Summarize: #{text}"}}
         ]
       }, state}
    end
  end
end
```

Start it with the transport you need:

```elixir
{:ok, server} = MyServer.start_link(transport: :beam)
```

## Low-Level Handlers

Use handwritten callbacks when capabilities are fully dynamic or you need
custom behavior. For nearly all cases, the DSL is simpler and recommended:

```elixir
defmodule MyServer do
  use ExMCP.Server.Handler
  use ExMCP.Server.DSL, name: "my-server", version: "1.0.0"

  tool "ping", "Health check" do
    run fn _args, state ->
      {:ok, %{content: [%{type: "text", text: "pong"}]}, state}
    end
  end
end

{:ok, server} = MyServer.start_link(transport: :beam)
```

### Raw Callback Example

```elixir
defmodule DynamicServer do
  use ExMCP.Server.Handler

  @impl true
  def handle_initialize(_params, state) do
    {:ok,
     %{
       protocolVersion: ExMCP.protocol_version(),
       serverInfo: %{name: "dynamic", version: "1.0.0"},
       capabilities: %{tools: %{}}
     }, state}
  end

  @impl true
  def handle_list_tools(_cursor, state) do
    tools = [
      %{
        name: "ping",
        description: "Health check",
        inputSchema: %{type: "object", properties: %{}}
      }
    ]

    {:ok, tools, nil, state}
  end

  @impl true
  def handle_call_tool("ping", _args, state) do
    {:ok, %{content: [%{type: "text", text: "pong"}]}, state}
  end
end

# Start a raw handler (no DSL):
{:ok, server} =
  ExMCP.Server.HandlerServer.start_link(
    handler: DynamicServer,
    transport: :beam
  )
# Or the convenience:
# {:ok, server} = ExMCP.start_server(handler: DynamicServer, transport: :beam)
```

## BEAM-Local MCP

Use `transport: :beam` when both sides are Elixir processes in the same VM.
When using the DSL the server module gets a `start_link/1`:

```elixir
{:ok, server} = MyServer.start_link(transport: :beam)

{:ok, client} =
  ExMCP.Client.start_link(
    transport: :beam,
    server: server
  )

{:ok, tools} = ExMCP.Client.list_tools(client)
{:ok, result} = ExMCP.Client.call_tool(client, "echo", %{"message" => "hello"})
```

For a raw handler (no DSL) use `ExMCP.Server.HandlerServer.start_link(handler: MyHandler, ...)` (or `ExMCP.start_server/1`).

**Tip:** `mix examples.getting_started` (after `mix compile`) gives a fast local run of these DSL + Client patterns for quick verification.

BEAM-local MCP uses the normal initialize handshake, request IDs, capabilities,
and handler callbacks. The transport passes MCP-shaped maps/lists as Elixir
terms instead of JSON strings.

## Clients

Connect to stdio:

```elixir
{:ok, client} =
  ExMCP.Client.start_link(
    transport: :stdio,
    command: ["node", "server.js"],
    cd: "/path/to/project",
    env: [{"NODE_ENV", "production"}]
  )
```

Connect to HTTP/SSE:

```elixir
{:ok, client} =
  ExMCP.Client.start_link(
    transport: :http,
    url: "https://api.example.com/mcp",
    use_sse: true,
    headers: [{"Authorization", "Bearer #{token}"}]
  )
```

Call server features:

```elixir
{:ok, tools} = ExMCP.Client.list_tools(client)
{:ok, result} = ExMCP.Client.call_tool(client, "search", %{"query" => "Elixir"})
{:ok, resources} = ExMCP.Client.list_resources(client)
{:ok, content} = ExMCP.Client.read_resource(client, "file:///docs/readme.md")
{:ok, prompts} = ExMCP.Client.list_prompts(client)
```

## Transports

| Transport | Use When |
|-----------|----------|
| `:stdio` | Spawning an MCP subprocess |
| `:http` | Talking to a remote or Phoenix-hosted MCP server |
| `:beam` | Connecting local Elixir client/server processes |
| `:test` | Unit/integration tests |

## Resilience And Pipelines

Use client retries for transient connection/request failures:

```elixir
{:ok, client} =
  ExMCP.Client.start_link(
    transport: :http,
    url: "https://api.example.com/mcp",
    retry_policy: [max_attempts: 3, initial_delay: 100, max_delay: 2_000]
  )
```

Use transport reliability when a circuit breaker or health check belongs at the
connection boundary:

```elixir
{:ok, client} =
  ExMCP.Client.start_link(
    transport: :http,
    url: "https://api.example.com/mcp",
    reliability: [
      circuit_breaker: [failure_threshold: 5, reset_timeout: 30_000],
      health_check: [check_interval: 60_000]
    ]
  )
```

For HTTP servers, put side-effecting concerns such as authentication, request
signing, CORS, and DNS rebinding protection in the Plug/Phoenix pipeline before
`ExMCP.HttpPlug`.

## Troubleshooting

**BEAM-local client cannot connect**

```elixir
Process.alive?(server)
ExMCP.Client.start_link(transport: :beam, server: server)
```

**stdio server exits immediately**

Make sure `command` includes the executable and arguments as a list, and use
`cd`/`env` if the subprocess needs a specific working directory or environment.

**HTTP connection refused**

Verify the URL path matches the server endpoint. `ExMCP.Transport.HTTP` extracts
the path from `url` unless `endpoint:` is provided explicitly.

**Need HTTP auth or validation**

Use `headers`, `auth`, `auth_provider`, `security`, or Plug composition around
`ExMCP.HttpPlug` depending on whether the concern is client-side or server-side.
