Current section
Files
Jump to
Current section
Files
lib/codex/app_server/mcp.ex
defmodule Codex.AppServer.Mcp do
@moduledoc """
MCP (Model Context Protocol) server management for app-server connections.
This module provides functions to interact with MCP servers configured in
the Codex app-server, including listing server status and handling OAuth
authentication flows.
"""
alias Codex.AppServer.Connection
alias Codex.AppServer.Params
@type connection :: pid()
@doc """
Lists configured MCP servers with their tools, resources, and auth status.
Supports cursor-based pagination via `:cursor` and `:limit` options.
## Compatibility
This function tries the new `mcpServerStatus/list` method first. If the server
returns a "method not found" (`-32601`) or "unknown variant" (`-32600`) error
(older servers), it falls back to the legacy `mcpServers/list` method
automatically.
"""
@spec list_servers(connection(), keyword()) :: {:ok, map()} | {:error, term()}
def list_servers(conn, opts \\ []) when is_pid(conn) and is_list(opts) do
params =
%{}
|> Params.put_optional("cursor", Keyword.get(opts, :cursor))
|> Params.put_optional("limit", Keyword.get(opts, :limit))
case Connection.request(conn, "mcpServerStatus/list", params, timeout_ms: 30_000) do
{:error, %{"code" => -32_601}} ->
Connection.request(conn, "mcpServers/list", params, timeout_ms: 30_000)
{:error, %{code: -32_601}} ->
Connection.request(conn, "mcpServers/list", params, timeout_ms: 30_000)
{:error, error} ->
if unknown_variant_mcp_server_status_list?(error) do
Connection.request(conn, "mcpServers/list", params, timeout_ms: 30_000)
else
{:error, error}
end
result ->
result
end
end
@doc """
Alias for `list_servers/2`. Returns MCP server status information.
"""
@spec list_server_statuses(connection(), keyword()) :: {:ok, map()} | {:error, term()}
def list_server_statuses(conn, opts \\ []), do: list_servers(conn, opts)
@spec oauth_login(connection(), keyword()) :: {:ok, map()} | {:error, term()}
def oauth_login(conn, opts) when is_pid(conn) and is_list(opts) do
name = Keyword.fetch!(opts, :name)
params =
%{"name" => name}
|> Params.put_optional("scopes", Keyword.get(opts, :scopes))
|> Params.put_optional("timeoutSecs", Keyword.get(opts, :timeout_secs))
Connection.request(conn, "mcpServer/oauth/login", params, timeout_ms: 30_000)
end
defp unknown_variant_mcp_server_status_list?(%{"code" => -32_600, "message" => message})
when is_binary(message) do
String.contains?(message, "unknown variant") and
String.contains?(message, "mcpServerStatus/list")
end
defp unknown_variant_mcp_server_status_list?(%{code: -32_600, message: message})
when is_binary(message) do
String.contains?(message, "unknown variant") and
String.contains?(message, "mcpServerStatus/list")
end
defp unknown_variant_mcp_server_status_list?(_error), do: false
end