# MCP Integration

OpenResponses supports the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), allowing models to call tools served by any MCP-compatible server. MCP tools behave identically to hosted tools from the model's perspective — the client never sees the round-trip.

## How it works

When a request includes an MCP server reference in `tools`, the loop starts a `OpenResponses.MCP.Connection` GenServer for that server. Tool calls targeting MCP tools are proxied to the MCP server via HTTP, and results are submitted back into the loop as `function_call_output` items.

## Connecting to an MCP server

Include MCP server references in the `tools` list:

```json
{
  "model": "claude-opus-4-6",
  "input": [{"role": "user", "content": "Search our docs for authentication setup."}],
  "tools": [
    {
      "type": "mcp",
      "server_url": "https://docs-mcp.example.com",
      "headers": {
        "authorization": "Bearer your-mcp-server-token"
      }
    }
  ]
}
```

OpenResponses connects to the MCP server, lists its available tools, and makes them available to the model alongside any function tools in the request.

## Registering MCP servers in config

For servers used across many requests, register them once in config instead of including them in every request:

```elixir
config :open_responses, :mcp_servers, [
  %{
    name: "docs",
    url: "https://docs-mcp.example.com",
    headers: [{"authorization", "Bearer #{System.get_env("MCP_TOKEN")}"}]
  },
  %{
    name: "database",
    url: "http://localhost:3001"
  }
]
```

## Writing an MCP-compatible server

Any HTTP server that implements the MCP tool protocol works. The minimum implementation handles two endpoints:

```
POST /tools/list  → returns {"tools": [...]}
POST /tools/call  → receives {name, arguments}, returns {"content": [...]}
```

In Elixir/Phoenix:

```elixir
def list(conn, _params) do
  tools = [
    %{
      name: "search_docs",
      description: "Search the documentation",
      inputSchema: %{
        type: "object",
        properties: %{
          query: %{type: "string", description: "Search query"}
        },
        required: ["query"]
      }
    }
  ]

  json(conn, %{tools: tools})
end

def call(conn, %{"name" => "search_docs", "arguments" => %{"query" => q}}) do
  results = MyApp.Docs.search(q)

  json(conn, %{
    content: [%{type: "text", text: format_results(results)}]
  })
end
```

## Combining MCP and function tools

MCP tools and function tools can coexist in the same request. OpenResponses dispatches each tool call to the right destination based on whether the tool name is registered as a hosted tool, an MCP tool, or an external function.

```json
{
  "model": "gpt-4o",
  "tools": [
    {"type": "function", "name": "get_time", "parameters": {...}},
    {"type": "mcp", "server_url": "https://docs-mcp.example.com"}
  ],
  "input": [...]
}
```

- `get_time` → dispatched to the registered hosted tool
- MCP tools → proxied to the MCP server
- Any other function → returned to the client as an external tool call
