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

ExMCP - Complete Elixir implementation of the Model Context Protocol.

ExMCP enables AI models to securely interact with local and remote resources through
a standardized protocol. It provides both client and server implementations with
multiple transport options.

## Public API

ExMCP provides a clean, focused public API. Only use these modules in your applications:

### Core Modules
- `ExMCP` - This module (convenience functions and metadata)
- `ExMCP.Client` - MCP client implementation
- `ExMCP.Server` - MCP server helper functions
- `ExMCP.Server.Handler` - Callback behaviour for MCP servers
- `ExMCP.Server.DSL` - Declarative tool/resource/prompt definitions
- `ExMCP.Transport` - Transport behaviour definition

### Optional Features
- `ExMCP.Authorization` - OAuth 2.1 authorization flows (MCP optional feature)
- `ExMCP.ACP` - Agent Client Protocol client and native agent helpers

### Supporting Modules
- `ExMCP.Content` - Content type helpers (builders; advanced transform/sanitize is experimental)
- `ExMCP.Types` - Type definitions (stable across versions)
- `ExMCP.HttpPlug` - Phoenix/Plug MCP endpoint
- `ExMCP.Error` / `ExMCP.Response` - Error and response helpers

### Deprecated (retained through 1.x; planned for removal in 2.0.0)
- `ExMCP.Server.Tools` and related modules — use `ExMCP.Server.DSL`

> #### Internal Modules {: .warning}
>
> All other modules under the `ExMCP` namespace are internal implementation details
> and may change without notice. Do not depend on them directly in your applications.

> #### Stability {: .info}
>
> **Stable for 1.0:** Client, Server Handler/DSL, documented transports, HttpPlug,
> Types, Content builders, Authorization entry points, ACP client/agent/adapters.
>
> **May change in minors:** experimental content transformers and anything marked
> deprecated. MCP 2026-07-28 is the latest stable revision and is available through
> `:prefer_modern` and `:modern_only`. Starting in rc.6, new connections default
> to `:prefer_modern`; `:legacy_only` preserves the legacy protocol era, not an
> exact rc.5 package rollback. The zero-arity
> compatibility helpers continue to report the newest initialize-compatible
> legacy revision, 2025-11-25.

## Quick Start

### Start a Client

    # Connect to stdio server
    {:ok, client} = ExMCP.start_client(
      transport: :stdio,
      command: ["python", "mcp-server.py"],
      protocol_mode: :prefer_modern
    )

    # Connect with HTTP
    {:ok, client} = ExMCP.start_client(
      transport: :http,
      url: "https://api.example.com",
      protocol_mode: :prefer_modern
    )

### Start a Server

    {:ok, server} = ExMCP.start_server(
      handler: MyApp.MCPHandler,
      transport: :stdio,
      protocol_mode: :prefer_modern
    )

### BEAM-Local Communication

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

    {:ok, client} = ExMCP.start_client(
      transport: :beam,
      server: server,
      protocol_mode: :prefer_modern
    )

    {:ok, tools} = ExMCP.Client.list_tools(client)

## Protocol Versions

ExMCP supports two wire-incompatible MCP eras:
- **2026-07-28** - Latest stable revision; stateless discovery, per-request
  context, result envelopes, MRTR, and `subscriptions/listen`
- **2025-11-25** - Newest legacy revision; tasks, icons, and URL elicitation
- **2025-06-18** - Structured output, OAuth 2.1, elicitation, no batch
- **2025-03-26** - Subscriptions, roots, logging, and batch support
- **2024-11-05** - Initial stable MCP revision

rc.7 defaults to `protocol_mode: :prefer_modern`, which tries the modern
revision first and retains evidence-based legacy fallback. Use
`protocol_mode: :modern_only` for a closed modern ecosystem or
`protocol_mode: :legacy_only` to preserve the legacy protocol era. Exact
rc.5 wire and session behavior still requires package rollback to
`1.0.0-rc.5`.

See the Configuration and Migration guides for the era comparison and
rollout policy.

## Features

- **Tools** - Register and execute functions with parameters
- **Resources** - List and read data from various sources
- **Prompts** - Manage reusable prompt templates
- **Sampling** - Protocol-deprecated in MCP 2026-07-28; retained throughout
  ExMCP 1.x for compatibility. Prefer direct LLM provider APIs for new code
- **Roots** - Protocol-deprecated in MCP 2026-07-28; retained throughout
  ExMCP 1.x. Prefer tool parameters, resource URIs, or server configuration
- **Subscriptions** - Monitor resources for changes
- **Progress** - Track long-running operations
- **Notifications** - Real-time updates for changes
- **BEAM-local MCP** - High-performance Elixir-to-Elixir communication

## Transport Options

- **stdio** - Process communication (standard MCP)
- **Streamable HTTP** - Web-friendly transport (standard MCP)
- **BEAM-local MCP** - Direct Erlang process communication (ExMCP extension)

## Examples

### Basic Client Usage

    {:ok, client} =
      ExMCP.start_client(
        transport: :stdio,
        command: ["mcp-server"],
        protocol_mode: :prefer_modern
      )

    # List and call tools
    {:ok, %{tools: tools}} = ExMCP.Client.list_tools(client)
    {:ok, result} = ExMCP.Client.call_tool(client, "search", %{query: "elixir"})

    # Read resources
    {:ok, content} = ExMCP.Client.read_resource(client, "file:///data.json")

### Basic Server Usage

> #### Tip
> Most servers are easier to write with the DSL:
>
> ```elixir
> defmodule MyServer do
>   use ExMCP.Server.Handler
>   use ExMCP.Server.DSL, name: "my-server", version: "1.0.0"
>
>   tool "echo", "Echo the message" do
>     param :message, :string, required: true
>     run fn %{message: msg}, state ->
>       {:ok, %{content: [%{type: "text", text: msg}]}, state}
>     end
>   end
> end
>
> {:ok, server} =
>   MyServer.start_link(transport: :stdio, protocol_mode: :prefer_modern)
> ```

    defmodule MyHandler do
      use ExMCP.Server.Handler

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

      @impl true
      def handle_list_tools(_cursor, state) do
        tools = [%{name: "echo", description: "Echo input", inputSchema: %{type: "object"}}]
        {:ok, tools, nil, state}
      end

      @impl true
      def handle_call_tool("echo", params, state) do
        {:ok, %{content: [%{type: "text", text: params["message"]}]}, state}
      end
    end

    {:ok, server} =
      ExMCP.start_server(
        handler: MyHandler,
        transport: :stdio,
        protocol_mode: :prefer_modern
      )

### BEAM-Local Service

    defmodule MyService do
      use ExMCP.Server.Handler
      use ExMCP.Server.DSL

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

    {:ok, server} =
      MyService.start_link(transport: :beam, protocol_mode: :prefer_modern)

    {:ok, client} =
      ExMCP.start_client(
        transport: :beam,
        server: server,
        protocol_mode: :prefer_modern
      )
    {:ok, result} = ExMCP.Client.call_tool(client, "ping", %{})

# `client`

```elixir
@type client() :: pid()
```

# `connection_spec`

```elixir
@type connection_spec() ::
  String.t() | {atom(), keyword()} | [any()] | ExMCP.ClientConfig.t()
```

# `call`

```elixir
@spec call(client(), String.t(), map(), keyword()) :: {:ok, any()} | {:error, any()}
```

Calls a tool on the connected server.

Returns `{:ok, result}` on success or `{:error, reason}` if the request
fails or the client is dead/unresponsive. With `normalize: true` (the
default) `result` is the extracted text content; with `normalize: false`
it is the raw response.

## Options

- `:timeout` - Request timeout in milliseconds (default: 30_000)
- `:normalize` - Whether to normalize the response (default: true)

## Examples

    # Simple call
    {:ok, result} = ExMCP.call(client, "calculator", %{op: "add", a: 1, b: 2})

    # With options
    {:ok, result} = ExMCP.call(client, "slow_tool", %{data: "..."}, timeout: 60_000)

# `connect`

```elixir
@spec connect(
  connection_spec(),
  keyword()
) :: {:ok, client()} | {:error, any()}
```

Connects to an MCP server using the unified client implementation.

This function provides a simplified interface to the MCP client with
automatic connection configuration and transport selection.

## Options

- `:timeout` - Connection timeout in milliseconds (default: 10_000)
- `:retry_attempts` - Number of retry attempts (default: 3)
- Transport-specific options (see ExMCP.Client docs)

## Examples

    # HTTP connection
    {:ok, client} = ExMCP.connect("http://localhost:8080")

    # Stdio connection
    {:ok, client} = ExMCP.connect({:stdio, command: "my-server"})

    # Multiple transports with fallback (uses first available)
    {:ok, client} = ExMCP.connect([
      "http://primary:8080",
      "http://backup:8080"
    ])

    # Using ClientConfig for advanced configuration
    config = ExMCP.ClientConfig.new(:production)
    |> ExMCP.ClientConfig.put_transport(:http, url: "https://api.example.com")
    |> ExMCP.ClientConfig.put_auth(:bearer, token: "secret")
    |> ExMCP.ClientConfig.put_retry_policy(max_attempts: 5)
    {:ok, client} = ExMCP.connect(config)

# `disconnect`

```elixir
@spec disconnect(client()) :: :ok
```

Disconnects from an MCP server.

# `info`

```elixir
@spec info() :: map()
```

Gets library configuration and capabilities.

# `ping`

```elixir
@spec ping(
  connection_spec(),
  keyword()
) :: :ok | {:error, any()}
```

Tests connectivity to an MCP server without establishing a persistent connection.

# `protocol_version`

```elixir
@spec protocol_version() :: String.t()
```

Returns the legacy protocol revision used by zero-arity compatibility paths.

This returns `"2025-11-25"`, the newest initialize-based legacy revision.
MCP `2026-07-28` is the latest stable revision but is selected through
`:protocol_mode`, not this scalar helper.

# `read`

```elixir
@spec read(client(), String.t(), keyword()) :: {:ok, any()} | {:error, any()}
```

Reads a resource from the connected server.

Returns `{:ok, content}` on success, or `{:error, reason}` if the request
fails or the client is dead/unresponsive.

## Options

- `:timeout` - Request timeout in milliseconds (default: 10_000)
- `:parse_json` - Automatically parse JSON content (default: false)

## Examples

    # Read text content
    {:ok, content} = ExMCP.read(client, "file://data.txt")

    # Read and parse JSON
    {:ok, data} = ExMCP.read(client, "file://config.json", parse_json: true)

# `resources`

```elixir
@spec resources(
  client(),
  keyword()
) :: {:ok, [map()]} | {:error, any()}
```

Lists available resources from the connected server.

Returns `{:ok, resources}` on success, or `{:error, reason}` if the
request fails or the client is dead/unresponsive.

# `start_acp_client`

Starts an ACP client connected to an agent subprocess.

See `ExMCP.ACP.start_client/1` for details.

# `start_client`

```elixir
@spec start_client(keyword()) :: {:ok, pid()} | {:error, term()}
```

Convenience function to start an MCP client.

This is equivalent to `ExMCP.Client.start_link/1` but provides a simpler
entry point for common use cases.

## Examples

    # stdio transport
    {:ok, client} = ExMCP.start_client(
      transport: :stdio,
      command: ["python", "mcp-server.py"]
    )

    # HTTP transport
    {:ok, client} = ExMCP.start_client(
      transport: :http,
      url: "https://api.example.com"
    )

# `start_server`

```elixir
@spec start_server(keyword()) :: {:ok, pid()} | {:error, term()}
```

Convenience function to start an MCP server.

This is equivalent to `ExMCP.Server.HandlerServer.start_link/1` but provides a simpler
entry point for common use cases.

## Examples

    {:ok, server} = ExMCP.start_server(
      handler: MyApp.Handler,
      transport: :stdio
    )

# `status`

```elixir
@spec status(client()) :: {:ok, map()} | {:error, any()}
```

Gets connection status and server information.

Returns `{:ok, status}` on success, or `{:error, reason}` if the client
is dead/unresponsive.

# `supported_versions`

```elixir
@spec supported_versions() :: [String.t()]
```

Returns the initialize-compatible legacy protocol revisions.

Modern `2026-07-28` support is enabled through `:prefer_modern` or
`:modern_only` and is intentionally not added to this legacy compatibility
list during the RC soak.

# `tools`

```elixir
@spec tools(
  client(),
  keyword()
) :: {:ok, [map()]} | {:error, any()}
```

Lists available tools from the connected server.

Returns `{:ok, tools}` where `tools` is a list of tool definitions with
their schemas and descriptions, or `{:error, reason}` if the request fails
or the client is dead/unresponsive.

# `version`

```elixir
@spec version() :: String.t()
```

Returns the version of the ExMCP library.

---

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