File size: 4,818 Bytes
17f328f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use std::collections::HashMap;

use anyhow::Result;
use async_channel::Sender;
use codex_api::SharedAuthProvider;
use codex_config::McpServerAuth;
use codex_config::McpServerConfig;
use codex_config::McpServerTransportConfig;
use codex_protocol::protocol::Event;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::McpStartupFailureReason;
use codex_protocol::protocol::McpStartupUpdateEvent;
use codex_rmcp_client::McpAuthState;
use codex_rmcp_client::McpLoginRequirement;

use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
use crate::rmcp_client::DEFAULT_STARTUP_TIMEOUT;
use crate::rmcp_client::StartupOutcomeError;
use crate::server::EffectiveMcpServer;

/// Makes ChatGPT authentication available to servers that explicitly opt in.
pub(super) fn chatgpt_auth_provider_for_server(
    server: &EffectiveMcpServer,
    chatgpt_auth_provider: Option<SharedAuthProvider>,
) -> Option<SharedAuthProvider> {
    if !matches!(&server.config().auth, McpServerAuth::ChatGpt)
        || !server.config().is_local_environment()
    {
        return None;
    }
    chatgpt_auth_provider
}

pub(super) fn should_share_codex_apps_tools_cache(
    server_name: &str,
    uses_env_bearer_token: bool,
) -> bool {
    server_name == CODEX_APPS_MCP_SERVER_NAME && !uses_env_bearer_token
}

pub(super) async fn emit_update(
    submit_id: &str,
    tx_event: &Sender<Event>,
    update: McpStartupUpdateEvent,
) -> Result<(), async_channel::SendError<Event>> {
    tx_event
        .send(Event {
            id: submit_id.to_string(),
            msg: EventMsg::McpStartupUpdate(update),
        })
        .await
}

pub(super) fn mcp_startup_failure_reason(
    auth_state: Option<McpAuthState>,
    error: &StartupOutcomeError,
) -> Option<McpStartupFailureReason> {
    if !error.is_authentication_required() {
        return None;
    }
    match auth_state {
        Some(
            McpAuthState::LoggedOut(McpLoginRequirement::Reauthentication) | McpAuthState::OAuth,
        ) => Some(McpStartupFailureReason::ReauthenticationRequired),
        Some(
            McpAuthState::Unsupported
            | McpAuthState::Unknown
            | McpAuthState::LoggedOut(McpLoginRequirement::Login)
            | McpAuthState::BearerToken,
        )
        | None => None,
    }
}

pub(super) fn mcp_init_error_display(
    server_name: &str,
    config: Option<&McpServerConfig>,
    error: &StartupOutcomeError,
    reason: Option<McpStartupFailureReason>,
) -> String {
    let server_key = if !server_name.is_empty()
        && server_name
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
    {
        server_name.to_string()
    } else {
        serde_json::Value::String(server_name.to_string()).to_string()
    };
    if let Some(McpServerTransportConfig::StreamableHttp {
        url,
        bearer_token_env_var,
        http_headers,
        ..
    }) = config.map(|config| &config.transport)
        && url == "https://api.githubcopilot.com/mcp/"
        && bearer_token_env_var.is_none()
        && http_headers.as_ref().map(HashMap::is_empty).unwrap_or(true)
    {
        format!(
            "GitHub MCP does not support OAuth. Log in by adding a personal access token (https://github.com/settings/personal-access-tokens) to your environment and config.toml:\n[mcp_servers.{server_key}]\nbearer_token_env_var = CODEX_GITHUB_PERSONAL_ACCESS_TOKEN"
        )
    } else if error.is_authentication_required() {
        let recovery_hint = if config.is_some_and(|config| !config.is_local_environment()) {
            "Use your client's MCP OAuth sign-in flow.".to_string()
        } else {
            format!("Run `codex mcp login {server_name}`.")
        };
        let auth_status = match reason {
            Some(McpStartupFailureReason::ReauthenticationRequired) => {
                "requires OAuth reauthentication"
            }
            None => "is not logged in",
        };
        format!("The {server_name} MCP server {auth_status}. {recovery_hint}")
    } else if matches!(
        error,
        StartupOutcomeError::Failed { error, .. }
            if error.contains("request timed out")
                || error.contains("timed out handshaking with MCP server")
                || error.contains("MCP client startup timed out")
    ) {
        let startup_timeout_secs = config
            .and_then(|config| config.startup_timeout_sec)
            .unwrap_or(DEFAULT_STARTUP_TIMEOUT)
            .as_secs();
        format!(
            "MCP client for `{server_name}` timed out after {startup_timeout_secs} seconds. Add or adjust `startup_timeout_sec` in your config.toml:\n[mcp_servers.{server_key}]\nstartup_timeout_sec = XX"
        )
    } else {
        format!("MCP client for `{server_name}` failed to start: {error:#}")
    }
}