use anyhow::Result;
use std::path::{Path, PathBuf};
use tokio::{
fs, task,
time::{Duration, sleep},
};
use vtcode_core::config::loader::{ConfigManager, VTCodeConfig};
use vtcode_core::mcp::validate_mcp_config;
use vtcode_core::tools::registry::ToolRegistry;
use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle};
use super::async_mcp_manager::{AsyncMcpManager, McpInitStatus};
use crate::agent::runloop::mcp_events;
use crate::agent::runloop::welcome::SessionBootstrap;
#[path = "mcp_support_overview.rs"]
mod mcp_support_overview;
pub(crate) use mcp_support_overview::{
display_mcp_providers, display_mcp_status, display_mcp_tools, refresh_mcp_tools,
};
pub(crate) async fn display_mcp_config_summary(
renderer: &mut AnsiRenderer,
vt_cfg: Option<&VTCodeConfig>,
session_bootstrap: &SessionBootstrap,
async_mcp_manager: Option<&AsyncMcpManager>,
) -> Result<()> {
renderer.line(MessageStyle::Status, "MCP configuration summary:")?;
if let Some(cfg) = vt_cfg {
let mcp_cfg = &cfg.mcp;
renderer.line(
MessageStyle::Info,
&format!(" enabled: {}", if mcp_cfg.enabled { "yes" } else { "no" }),
)?;
renderer.line(
MessageStyle::Info,
&format!(" providers configured: {}", mcp_cfg.providers.len()),
)?;
renderer.line(
MessageStyle::Info,
&format!(
" max concurrent connections: {}",
mcp_cfg.max_concurrent_connections
),
)?;
renderer.line(
MessageStyle::Info,
&format!(" request timeout: {}s", mcp_cfg.request_timeout_seconds),
)?;
renderer.line(
MessageStyle::Info,
&format!(" retry attempts: {}", mcp_cfg.retry_attempts),
)?;
if let Some(seconds) = mcp_cfg.startup_timeout_seconds {
renderer.line(
MessageStyle::Info,
&format!(" startup timeout: {}s", seconds),
)?;
}
if let Some(seconds) = mcp_cfg.tool_timeout_seconds {
renderer.line(MessageStyle::Info, &format!(" tool timeout: {}s", seconds))?;
}
if mcp_cfg.server.enabled {
renderer.line(MessageStyle::Info, " MCP server exposure: enabled")?;
renderer.line(
MessageStyle::Info,
&format!(
" bind: {}:{}",
mcp_cfg.server.bind_address, mcp_cfg.server.port
),
)?;
} else {
renderer.line(MessageStyle::Info, " MCP server exposure: disabled")?;
}
if mcp_cfg.allowlist.enforce {
let provider_overrides = mcp_cfg.allowlist.providers.len();
renderer.line(
MessageStyle::Info,
&format!(
" allow list: enforced ({} provider override{})",
provider_overrides,
if provider_overrides == 1 { "" } else { "s" }
),
)?;
} else {
renderer.line(MessageStyle::Info, " allow list: not enforced")?;
}
if mcp_cfg.security.auth_enabled {
renderer.line(
MessageStyle::Info,
&format!(
" server auth: enabled (env: {})",
mcp_cfg.security.api_key_env.as_deref().unwrap_or("<unset>")
),
)?;
} else {
renderer.line(MessageStyle::Info, " server auth: disabled")?;
}
} else {
match session_bootstrap.mcp_enabled {
Some(true) => renderer.line(
MessageStyle::Info,
" MCP enabled via runtime defaults (vtcode.toml not loaded)",
)?,
Some(false) => {
renderer.line(MessageStyle::Info, " MCP disabled via runtime defaults")?
}
None => renderer.line(
MessageStyle::Info,
" No vtcode.toml found; using default MCP settings",
)?,
}
}
renderer.line(MessageStyle::Info, "Configured providers:")?;
display_mcp_providers(renderer, session_bootstrap, async_mcp_manager).await?;
Ok(())
}
pub(crate) async fn render_mcp_config_edit_guidance(
renderer: &mut AnsiRenderer,
workspace: &Path,
) -> Result<()> {
renderer.line(MessageStyle::Status, "MCP configuration editing:")?;
let workspace_path = workspace.to_path_buf();
let config_path = task::spawn_blocking({
let workspace_clone = workspace_path.clone();
move || -> Result<Option<PathBuf>> {
let manager = ConfigManager::load_from_workspace(&workspace_clone)?;
Ok(manager.config_path().map(Path::to_path_buf))
}
})
.await??;
let target_path = config_path.unwrap_or_else(|| workspace_path.join("vtcode.toml"));
let exists = fs::try_exists(&target_path).await.unwrap_or(false);
renderer.line(
MessageStyle::Info,
&format!(" File: {}", target_path.display()),
)?;
if exists {
renderer.line(
MessageStyle::Info,
" Open this file in your editor and update the [mcp] section.",
)?;
} else {
renderer.line(
MessageStyle::Info,
" File not found. Run `vtcode config bootstrap` or create it manually.",
)?;
}
renderer.line(
MessageStyle::Info,
" Reload providers with /mcp refresh after saving changes.",
)?;
Ok(())
}
pub(crate) async fn repair_mcp_runtime(
renderer: &mut AnsiRenderer,
async_mcp_manager: Option<&AsyncMcpManager>,
tool_registry: &mut ToolRegistry,
vt_cfg: Option<&VTCodeConfig>,
) -> Result<bool> {
renderer.line(MessageStyle::Status, "Repairing MCP runtime:")?;
if let Some(cfg) = vt_cfg {
match validate_mcp_config(&cfg.mcp) {
Ok(_) => renderer.line(MessageStyle::Info, " Configuration validation: ok")?,
Err(err) => {
renderer.line(
MessageStyle::Error,
&format!(" Configuration validation failed: {}", err),
)?;
renderer.line(
MessageStyle::Info,
" Update vtcode.toml and rerun /mcp repair.",
)?;
return Ok(false);
}
}
} else {
renderer.line(
MessageStyle::Info,
" vtcode.toml not detected; using default MCP settings.",
)?;
}
let Some(manager) = async_mcp_manager else {
renderer.line(
MessageStyle::Info,
" MCP runtime is not active in this session.",
)?;
renderer.line(
MessageStyle::Info,
" Enable MCP in vtcode.toml and restart the agent.",
)?;
return Ok(false);
};
if let McpInitStatus::Disabled = manager.get_status().await {
renderer.line(
MessageStyle::Info,
" MCP is disabled; update vtcode.toml to enable it.",
)?;
return Ok(false);
}
renderer.line(
MessageStyle::Info,
" Shutting down existing MCP connections…",
)?;
manager.shutdown().await?;
renderer.line(MessageStyle::Info, " Restarting MCP manager…")?;
if let Err(err) = manager.start_initialization() {
renderer.line(
MessageStyle::Error,
&format!(" Failed to restart MCP manager: {}", err),
)?;
return Ok(false);
}
const MAX_ATTEMPTS: usize = 20;
let mut stabilized = false;
for attempt in 0..MAX_ATTEMPTS {
match manager.get_status().await {
McpInitStatus::Ready { .. } => {
renderer.line(
MessageStyle::Info,
&format!(" MCP reinitialized after {} check(s).", attempt + 1),
)?;
stabilized = true;
break;
}
McpInitStatus::Error { message } => {
renderer.line(
MessageStyle::Error,
&format!(" MCP restart error: {}", message),
)?;
return Ok(false);
}
McpInitStatus::Disabled => {
renderer.line(
MessageStyle::Info,
" MCP disabled during restart; check vtcode.toml settings.",
)?;
return Ok(false);
}
_ => {
if attempt == MAX_ATTEMPTS - 1 {
renderer.line(
MessageStyle::Info,
" MCP still initializing; check /mcp status shortly.",
)?;
} else {
sleep(Duration::from_millis(250)).await;
}
}
}
}
let mut refreshed = false;
if stabilized {
refreshed = refresh_mcp_tools(renderer, tool_registry).await?;
}
renderer.line(
MessageStyle::Info,
" Repair complete. Use /mcp diagnose for additional checks.",
)?;
Ok(refreshed)
}
pub(crate) async fn diagnose_mcp(
renderer: &mut AnsiRenderer,
vt_cfg: Option<&VTCodeConfig>,
session_bootstrap: &SessionBootstrap,
async_mcp_manager: Option<&AsyncMcpManager>,
tool_registry: &mut ToolRegistry,
mcp_panel_state: &mcp_events::McpPanelState,
) -> Result<()> {
renderer.line(MessageStyle::Status, "Running MCP diagnostics:")?;
if let Some(cfg) = vt_cfg {
match validate_mcp_config(&cfg.mcp) {
Ok(_) => renderer.line(MessageStyle::Info, " Configuration validation: ok")?,
Err(err) => renderer.line(
MessageStyle::Error,
&format!(" Configuration validation failed: {}", err),
)?,
}
} else {
renderer.line(
MessageStyle::Info,
" vtcode.toml not detected; using default MCP settings.",
)?;
}
display_mcp_status(
renderer,
session_bootstrap,
tool_registry,
async_mcp_manager,
mcp_panel_state,
)
.await?;
display_mcp_providers(renderer, session_bootstrap, async_mcp_manager).await?;
display_mcp_tools(renderer, tool_registry).await?;
renderer.line(
MessageStyle::Info,
"Diagnostics complete. Use /mcp repair to restart providers if issues remain.",
)?;
Ok(())
}
pub(crate) fn render_mcp_login_guidance(
renderer: &mut AnsiRenderer,
provider: String,
is_login: bool,
) -> Result<()> {
let trimmed = provider.trim();
if trimmed.is_empty() {
renderer.line(
MessageStyle::Error,
"Provider name required. Usage: /mcp login <name> or /mcp logout <name>.",
)?;
return Ok(());
}
let action = if is_login { "login" } else { "logout" };
renderer.line(
MessageStyle::Status,
&format!(
"OAuth {} flow requested for provider '{}'.",
action, trimmed
),
)?;
renderer.line(
MessageStyle::Info,
"VT Code delegates OAuth to the CLI today.",
)?;
renderer.line(
MessageStyle::Info,
&format!("Run: vtcode mcp {} {}", action, trimmed),
)?;
renderer.line(
MessageStyle::Info,
"This command will walk you through the authentication flow in your shell.",
)?;
Ok(())
}