MCP Integration

View Source

OpenResponses supports the Model Context Protocol (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:

{
  "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:

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:

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.

{
  "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