From bcb254f3d93341ed272e189ee16a2399a9a7fdc4 Mon Sep 17 00:00:00 2001 From: Vinh Nguyen <1097578+vinhnx@users.noreply.github.com> Date: Wed, 8 Oct 2025 00:12:45 +0700 Subject: [PATCH 1/2] fix(mcp): inject context7 api key configuration --- config.toml | 4 + src/agent/runloop/tool_output.rs | 34 +++++--- src/agent/runloop/unified/session_setup.rs | 1 + vtcode-core/src/config/constants.rs | 11 +++ vtcode-core/src/config/mcp.rs | 91 ++++++++++++++++++++++ vtcode-core/src/mcp_client.rs | 24 ++++++ vtcode-core/tests/mcp_basic_test.rs | 4 + vtcode-core/tests/mcp_context7_manual.rs | 2 + vtcode-core/tests/mcp_integration_e2e.rs | 4 + vtcode-core/tests/mcp_integration_test.rs | 29 +++++++ vtcode.toml | 6 ++ 11 files changed, 200 insertions(+), 10 deletions(-) diff --git a/config.toml b/config.toml index 86551a8b1..930703f69 100644 --- a/config.toml +++ b/config.toml @@ -2,6 +2,10 @@ sse_servers = [ ] shttp_servers = [ ] +[mcp.ui.renderers] +context7 = "context7" +sequential-thinking = "sequential-thinking" + [[mcp.stdio_servers]] name = "context7" command = "npx" diff --git a/src/agent/runloop/tool_output.rs b/src/agent/runloop/tool_output.rs index 3cb8132f5..e729f611a 100644 --- a/src/agent/runloop/tool_output.rs +++ b/src/agent/runloop/tool_output.rs @@ -5,6 +5,7 @@ use std::collections::{HashMap, VecDeque}; use vtcode_core::config::ToolOutputMode; use vtcode_core::config::constants::{defaults, tools}; use vtcode_core::config::loader::VTCodeConfig; +use vtcode_core::config::mcp::McpRendererProfile; use vtcode_core::tools::{PlanCompletionState, StepStatus, TaskPlan}; use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle}; @@ -23,11 +24,16 @@ pub(crate) fn render_tool_output( renderer.line(MessageStyle::Info, notice)?; } - if let Some(tool) = tool_name { - if tool.starts_with("mcp_context7") { - render_mcp_context7_output(renderer, val)?; - } else if tool.starts_with("mcp_sequentialthinking") { - render_mcp_sequential_output(renderer, val)?; + if let Some(tool) = tool_name + && let Some(profile) = resolve_mcp_renderer_profile(tool, vt_config) + { + match profile { + McpRendererProfile::Context7 => { + render_mcp_context7_output(renderer, val)?; + } + McpRendererProfile::SequentialThinking => { + render_mcp_sequential_output(renderer, val)?; + } } } @@ -240,7 +246,7 @@ fn render_mcp_sequential_output(renderer: &mut AnsiRenderer, val: &Value) -> Res let has_errors = val .get("errors") .and_then(|value| value.as_array()) - .map_or(false, |errors| !errors.is_empty()); + .is_some_and(|errors| !errors.is_empty()); let base_style = sequential_tool_status_style(status, has_errors); let header_style = base_style.bold(); @@ -365,6 +371,14 @@ fn levenshtein(a: &str, b: &str) -> usize { prev[b_len] } +fn resolve_mcp_renderer_profile( + tool_name: &str, + vt_config: Option<&VTCodeConfig>, +) -> Option { + let config = vt_config?; + config.mcp.ui.renderer_for_tool(tool_name) +} + fn render_plan_panel(renderer: &mut AnsiRenderer, plan: &TaskPlan) -> Result<()> { renderer.line( MessageStyle::Tool, @@ -472,7 +486,7 @@ fn resolve_stdout_tail_limit(config: Option<&VTCodeConfig>) -> usize { .unwrap_or(defaults::DEFAULT_PTY_STDOUT_TAIL_LINES) } -fn tail_lines<'a>(text: &'a str, limit: usize) -> (Vec<&'a str>, usize) { +fn tail_lines(text: &str, limit: usize) -> (Vec<&str>, usize) { if text.is_empty() { return (Vec::new(), 0); } @@ -493,12 +507,12 @@ fn tail_lines<'a>(text: &'a str, limit: usize) -> (Vec<&'a str>, usize) { (ring.into_iter().collect(), total) } -fn select_stream_lines<'a>( - content: &'a str, +fn select_stream_lines( + content: &str, mode: ToolOutputMode, tail_limit: usize, prefer_full: bool, -) -> (Vec<&'a str>, usize, bool) { +) -> (Vec<&str>, usize, bool) { if content.is_empty() { return (Vec::new(), 0, false); } diff --git a/src/agent/runloop/unified/session_setup.rs b/src/agent/runloop/unified/session_setup.rs index 6b33cdb30..d1008b658 100644 --- a/src/agent/runloop/unified/session_setup.rs +++ b/src/agent/runloop/unified/session_setup.rs @@ -211,6 +211,7 @@ pub(crate) async fn initialize_session( mode: cfg.mcp.ui.mode, max_events: cfg.mcp.ui.max_events, show_provider_names: cfg.mcp.ui.show_provider_names, + renderers: cfg.mcp.ui.renderers.clone(), }; mcp_events::McpPanelState::new(cfg.mcp.ui.max_events) } else { diff --git a/vtcode-core/src/config/constants.rs b/vtcode-core/src/config/constants.rs index fdf51dd09..dc3ff10d2 100644 --- a/vtcode-core/src/config/constants.rs +++ b/vtcode-core/src/config/constants.rs @@ -270,6 +270,12 @@ pub mod env { } } } + + /// Model Context Protocol specific environment keys + pub mod mcp { + /// Upstash Context7 API key environment variable + pub const CONTEXT7_API_KEY: &str = "CONTEXT7_API_KEY"; + } } /// Default configuration values @@ -433,6 +439,11 @@ pub mod tools { pub const WILDCARD_ALL: &str = "*"; } +pub mod mcp { + pub const RENDERER_CONTEXT7: &str = "context7"; + pub const RENDERER_SEQUENTIAL_THINKING: &str = "sequential-thinking"; +} + pub mod project_doc { pub const DEFAULT_MAX_BYTES: usize = 16 * 1024; } diff --git a/vtcode-core/src/config/mcp.rs b/vtcode-core/src/config/mcp.rs index 91ba84633..a1a1f55a3 100644 --- a/vtcode-core/src/config/mcp.rs +++ b/vtcode-core/src/config/mcp.rs @@ -67,6 +67,10 @@ pub struct McpUiConfig { /// Show MCP provider names in UI #[serde(default = "default_show_provider_names")] pub show_provider_names: bool, + + /// Custom renderer profiles for provider-specific output formatting + #[serde(default)] + pub renderers: HashMap, } impl Default for McpUiConfig { @@ -75,7 +79,33 @@ impl Default for McpUiConfig { mode: default_mcp_ui_mode(), max_events: default_max_mcp_events(), show_provider_names: default_show_provider_names(), + renderers: HashMap::new(), + } + } +} + +impl McpUiConfig { + /// Resolve renderer profile for a provider or tool identifier + pub fn renderer_for_identifier(&self, identifier: &str) -> Option { + let normalized_identifier = normalize_mcp_identifier(identifier); + if normalized_identifier.is_empty() { + return None; } + + self.renderers.iter().find_map(|(key, profile)| { + let normalized_key = normalize_mcp_identifier(key); + if normalized_identifier.starts_with(&normalized_key) { + Some(*profile) + } else { + None + } + }) + } + + /// Resolve renderer profile for a fully qualified tool name + pub fn renderer_for_tool(&self, tool_name: &str) -> Option { + let identifier = tool_name.strip_prefix("mcp_").unwrap_or(tool_name); + self.renderer_for_identifier(identifier) } } @@ -104,6 +134,16 @@ impl Default for McpUiMode { } } +/// Named renderer profiles for MCP tool output formatting +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub enum McpRendererProfile { + /// Context7 knowledge base renderer + Context7, + /// Sequential thinking trace renderer + SequentialThinking, +} + /// Configuration for a single MCP provider #[derive(Debug, Clone, Deserialize, Serialize)] pub struct McpProviderConfig { @@ -118,6 +158,14 @@ pub struct McpProviderConfig { #[serde(default)] pub env: HashMap, + /// Environment variable that contains an API key for the provider + #[serde(default)] + pub api_key_env: Option, + + /// Command-line flag used to pass the API key to the provider + #[serde(default)] + pub api_key_arg: Option, + /// Whether this provider is enabled #[serde(default = "default_provider_enabled")] pub enabled: bool, @@ -133,6 +181,8 @@ impl Default for McpProviderConfig { name: String::new(), transport: McpTransportConfig::Stdio(McpStdioServerConfig::default()), env: HashMap::new(), + api_key_env: None, + api_key_arg: None, enabled: default_provider_enabled(), max_concurrent_requests: default_provider_max_concurrent(), } @@ -538,9 +588,18 @@ fn default_mcp_server_version() -> String { env!("CARGO_PKG_VERSION").to_string() } +fn normalize_mcp_identifier(value: &str) -> String { + value + .chars() + .filter(|ch| ch.is_ascii_alphanumeric()) + .map(|ch| ch.to_ascii_lowercase()) + .collect() +} + #[cfg(test)] mod tests { use super::*; + use crate::config::constants::mcp as mcp_constants; use std::collections::BTreeMap; #[test] @@ -550,6 +609,7 @@ mod tests { assert_eq!(config.ui.mode, McpUiMode::Compact); assert_eq!(config.ui.max_events, 50); assert!(config.ui.show_provider_names); + assert!(config.ui.renderers.is_empty()); assert_eq!(config.max_concurrent_connections, 5); assert_eq!(config.request_timeout_seconds, 30); assert_eq!(config.retry_attempts, 3); @@ -649,4 +709,35 @@ mod tests { assert!(config.is_logging_channel_allowed(Some("other"), "info")); assert!(!config.is_logging_channel_allowed(Some("other"), "trace")); } + + #[test] + fn test_mcp_ui_renderer_resolution() { + let mut config = McpUiConfig::default(); + config.renderers.insert( + mcp_constants::RENDERER_CONTEXT7.to_string(), + McpRendererProfile::Context7, + ); + config.renderers.insert( + mcp_constants::RENDERER_SEQUENTIAL_THINKING.to_string(), + McpRendererProfile::SequentialThinking, + ); + + assert_eq!( + config.renderer_for_tool("mcp_context7_lookup"), + Some(McpRendererProfile::Context7) + ); + assert_eq!( + config.renderer_for_tool("mcp_context7lookup"), + Some(McpRendererProfile::Context7) + ); + assert_eq!( + config.renderer_for_tool("mcp_sequentialthinking_run"), + Some(McpRendererProfile::SequentialThinking) + ); + assert_eq!( + config.renderer_for_identifier("sequential-thinking-analyze"), + Some(McpRendererProfile::SequentialThinking) + ); + assert_eq!(config.renderer_for_tool("mcp_unknown"), None); + } } diff --git a/vtcode-core/src/mcp_client.rs b/vtcode-core/src/mcp_client.rs index 2393ea1fa..ddfa44f8a 100644 --- a/vtcode-core/src/mcp_client.rs +++ b/vtcode-core/src/mcp_client.rs @@ -1625,6 +1625,30 @@ impl McpProvider { command.envs(&self.config.env); } + // Inject API key argument when configured + if let Some(api_key_env) = &self.config.api_key_env { + match std::env::var(api_key_env) { + Ok(api_key) => { + if let Some(api_key_flag) = self.config.api_key_arg.as_deref() { + if !api_key_flag.is_empty() { + debug!( + "Passing API key for provider '{}' via env '{}'", + provider_name, api_key_env + ); + command.arg(api_key_flag); + command.arg(api_key); + } + } + } + Err(_) => { + warn!( + "API key environment variable '{}' not found for provider '{}'", + api_key_env, provider_name + ); + } + } + } + // Create new process group to ensure proper cleanup (Unix only) #[cfg(unix)] { diff --git a/vtcode-core/tests/mcp_basic_test.rs b/vtcode-core/tests/mcp_basic_test.rs index 7ec8d8823..18e17273f 100644 --- a/vtcode-core/tests/mcp_basic_test.rs +++ b/vtcode-core/tests/mcp_basic_test.rs @@ -42,6 +42,8 @@ mod tests { assert_eq!(provider_config.max_concurrent_requests, 3); assert!(provider_config.env.is_empty()); assert!(provider_config.name.is_empty()); + assert!(provider_config.api_key_env.is_none()); + assert!(provider_config.api_key_arg.is_none()); } #[test] @@ -107,6 +109,8 @@ mod tests { working_directory: None, }), env: env_vars, + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_context7_manual.rs b/vtcode-core/tests/mcp_context7_manual.rs index a9d2713c0..f7a44bb71 100644 --- a/vtcode-core/tests/mcp_context7_manual.rs +++ b/vtcode-core/tests/mcp_context7_manual.rs @@ -16,6 +16,8 @@ async fn context7_list_tools_smoke() { working_directory: None, }), env: HashMap::new(), + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_integration_e2e.rs b/vtcode-core/tests/mcp_integration_e2e.rs index 1c4fa8524..f72a64e54 100644 --- a/vtcode-core/tests/mcp_integration_e2e.rs +++ b/vtcode-core/tests/mcp_integration_e2e.rs @@ -42,6 +42,8 @@ mod tests { working_directory: Some(workspace.to_string_lossy().to_string()), }), env: HashMap::new(), + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 3, }; @@ -245,6 +247,8 @@ max_concurrent_requests = 1 working_directory: None, }), env: env_vars, + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_integration_test.rs b/vtcode-core/tests/mcp_integration_test.rs index 8195cdaa4..e2fef996b 100644 --- a/vtcode-core/tests/mcp_integration_test.rs +++ b/vtcode-core/tests/mcp_integration_test.rs @@ -100,6 +100,8 @@ max_concurrent_requests = 1 name: "context7".to_string(), transport: McpTransportConfig::Stdio(stdio_config), env: HashMap::new(), + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 2, }; @@ -202,6 +204,31 @@ max_concurrent_requests = 1 assert_eq!(serena_provider.max_concurrent_requests, 1); } + #[test] + fn test_provider_api_key_configuration() { + let toml_content = r#" +[mcp] +enabled = true + +[[mcp.providers]] +name = "context7" +enabled = true +command = "npx" +args = ["-y", "@upstash/context7-mcp@latest"] +api_key_env = "CONTEXT7_API_KEY" +api_key_arg = "--api-key" + "#; + + let config: VTCodeConfig = toml::from_str(toml_content).unwrap(); + assert!(config.mcp.enabled); + assert_eq!(config.mcp.providers.len(), 1); + + let provider = &config.mcp.providers[0]; + assert_eq!(provider.name, "context7"); + assert_eq!(provider.api_key_env.as_deref(), Some("CONTEXT7_API_KEY")); + assert_eq!(provider.api_key_arg.as_deref(), Some("--api-key")); + } + #[tokio::test] async fn test_mcp_client_initialization() { let config = McpClientConfig { @@ -341,6 +368,8 @@ max_concurrent_requests = 1 working_directory: None, }), env: env_vars, + api_key_env: None, + api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode.toml b/vtcode.toml index 1752f2ec4..698a49c74 100644 --- a/vtcode.toml +++ b/vtcode.toml @@ -125,6 +125,10 @@ show_timeline_pane = false # Local MCP clients executed via stdio transports enabled = true +[mcp.ui.renderers] +context7 = "context7" +sequential-thinking = "sequential-thinking" + [[mcp.providers]] # Official Model Context Protocol time server name = "time" @@ -139,6 +143,8 @@ name = "context7" enabled = true command = "npx" args = ["-y", "@upstash/context7-mcp@latest"] +api_key_env = "CONTEXT7_API_KEY" +api_key_arg = "--api-key" max_concurrent_requests = 3 [[mcp.providers]] From 084f0389910159daaa0e1d849550374d101e739b Mon Sep 17 00:00:00 2001 From: Vinh Nguyen <1097578+vinhnx@users.noreply.github.com> Date: Wed, 8 Oct 2025 07:12:49 +0700 Subject: [PATCH 2/2] fix(mcp): remove context7 api key requirement --- config.toml | 5 +++ vtcode-core/src/config/constants.rs | 6 --- vtcode-core/src/config/mcp.rs | 10 ----- vtcode-core/src/mcp_client.rs | 24 ------------ vtcode-core/tests/mcp_basic_test.rs | 4 -- vtcode-core/tests/mcp_context7_manual.rs | 2 - vtcode-core/tests/mcp_integration_e2e.rs | 4 -- vtcode-core/tests/mcp_integration_test.rs | 45 ++++------------------- vtcode.toml | 10 ++++- 9 files changed, 21 insertions(+), 89 deletions(-) diff --git a/config.toml b/config.toml index 930703f69..492a2a0f4 100644 --- a/config.toml +++ b/config.toml @@ -11,6 +11,11 @@ sequential-thinking = "sequential-thinking" command = "npx" args = [ "-y", "@upstash/context7-mcp@latest" ] + [[mcp.stdio_servers]] + name = "fetch" + command = "uvx" + args = [ "mcp-server-fetch" ] + [[mcp.stdio_servers]] name = "time" command = "uvx" diff --git a/vtcode-core/src/config/constants.rs b/vtcode-core/src/config/constants.rs index dc3ff10d2..59d9b05b3 100644 --- a/vtcode-core/src/config/constants.rs +++ b/vtcode-core/src/config/constants.rs @@ -270,12 +270,6 @@ pub mod env { } } } - - /// Model Context Protocol specific environment keys - pub mod mcp { - /// Upstash Context7 API key environment variable - pub const CONTEXT7_API_KEY: &str = "CONTEXT7_API_KEY"; - } } /// Default configuration values diff --git a/vtcode-core/src/config/mcp.rs b/vtcode-core/src/config/mcp.rs index a1a1f55a3..10eb65752 100644 --- a/vtcode-core/src/config/mcp.rs +++ b/vtcode-core/src/config/mcp.rs @@ -158,14 +158,6 @@ pub struct McpProviderConfig { #[serde(default)] pub env: HashMap, - /// Environment variable that contains an API key for the provider - #[serde(default)] - pub api_key_env: Option, - - /// Command-line flag used to pass the API key to the provider - #[serde(default)] - pub api_key_arg: Option, - /// Whether this provider is enabled #[serde(default = "default_provider_enabled")] pub enabled: bool, @@ -181,8 +173,6 @@ impl Default for McpProviderConfig { name: String::new(), transport: McpTransportConfig::Stdio(McpStdioServerConfig::default()), env: HashMap::new(), - api_key_env: None, - api_key_arg: None, enabled: default_provider_enabled(), max_concurrent_requests: default_provider_max_concurrent(), } diff --git a/vtcode-core/src/mcp_client.rs b/vtcode-core/src/mcp_client.rs index ddfa44f8a..2393ea1fa 100644 --- a/vtcode-core/src/mcp_client.rs +++ b/vtcode-core/src/mcp_client.rs @@ -1625,30 +1625,6 @@ impl McpProvider { command.envs(&self.config.env); } - // Inject API key argument when configured - if let Some(api_key_env) = &self.config.api_key_env { - match std::env::var(api_key_env) { - Ok(api_key) => { - if let Some(api_key_flag) = self.config.api_key_arg.as_deref() { - if !api_key_flag.is_empty() { - debug!( - "Passing API key for provider '{}' via env '{}'", - provider_name, api_key_env - ); - command.arg(api_key_flag); - command.arg(api_key); - } - } - } - Err(_) => { - warn!( - "API key environment variable '{}' not found for provider '{}'", - api_key_env, provider_name - ); - } - } - } - // Create new process group to ensure proper cleanup (Unix only) #[cfg(unix)] { diff --git a/vtcode-core/tests/mcp_basic_test.rs b/vtcode-core/tests/mcp_basic_test.rs index 18e17273f..7ec8d8823 100644 --- a/vtcode-core/tests/mcp_basic_test.rs +++ b/vtcode-core/tests/mcp_basic_test.rs @@ -42,8 +42,6 @@ mod tests { assert_eq!(provider_config.max_concurrent_requests, 3); assert!(provider_config.env.is_empty()); assert!(provider_config.name.is_empty()); - assert!(provider_config.api_key_env.is_none()); - assert!(provider_config.api_key_arg.is_none()); } #[test] @@ -109,8 +107,6 @@ mod tests { working_directory: None, }), env: env_vars, - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_context7_manual.rs b/vtcode-core/tests/mcp_context7_manual.rs index f7a44bb71..a9d2713c0 100644 --- a/vtcode-core/tests/mcp_context7_manual.rs +++ b/vtcode-core/tests/mcp_context7_manual.rs @@ -16,8 +16,6 @@ async fn context7_list_tools_smoke() { working_directory: None, }), env: HashMap::new(), - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_integration_e2e.rs b/vtcode-core/tests/mcp_integration_e2e.rs index f72a64e54..1c4fa8524 100644 --- a/vtcode-core/tests/mcp_integration_e2e.rs +++ b/vtcode-core/tests/mcp_integration_e2e.rs @@ -42,8 +42,6 @@ mod tests { working_directory: Some(workspace.to_string_lossy().to_string()), }), env: HashMap::new(), - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 3, }; @@ -247,8 +245,6 @@ max_concurrent_requests = 1 working_directory: None, }), env: env_vars, - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode-core/tests/mcp_integration_test.rs b/vtcode-core/tests/mcp_integration_test.rs index e2fef996b..1be926077 100644 --- a/vtcode-core/tests/mcp_integration_test.rs +++ b/vtcode-core/tests/mcp_integration_test.rs @@ -100,8 +100,6 @@ max_concurrent_requests = 1 name: "context7".to_string(), transport: McpTransportConfig::Stdio(stdio_config), env: HashMap::new(), - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 2, }; @@ -173,10 +171,10 @@ args = ["-y", "@upstash/context7-mcp@latest"] max_concurrent_requests = 2 [[mcp.providers]] -name = "serena" -enabled = false +name = "fetch" +enabled = true command = "uvx" -args = ["serena", "start-mcp-server"] +args = ["mcp-server-fetch"] max_concurrent_requests = 1 "#; @@ -197,36 +195,11 @@ max_concurrent_requests = 1 assert!(context7_provider.enabled); assert_eq!(context7_provider.max_concurrent_requests, 2); - // Check third provider (serena - disabled) - let serena_provider = &config.mcp.providers[2]; - assert_eq!(serena_provider.name, "serena"); - assert!(!serena_provider.enabled); - assert_eq!(serena_provider.max_concurrent_requests, 1); - } - - #[test] - fn test_provider_api_key_configuration() { - let toml_content = r#" -[mcp] -enabled = true - -[[mcp.providers]] -name = "context7" -enabled = true -command = "npx" -args = ["-y", "@upstash/context7-mcp@latest"] -api_key_env = "CONTEXT7_API_KEY" -api_key_arg = "--api-key" - "#; - - let config: VTCodeConfig = toml::from_str(toml_content).unwrap(); - assert!(config.mcp.enabled); - assert_eq!(config.mcp.providers.len(), 1); - - let provider = &config.mcp.providers[0]; - assert_eq!(provider.name, "context7"); - assert_eq!(provider.api_key_env.as_deref(), Some("CONTEXT7_API_KEY")); - assert_eq!(provider.api_key_arg.as_deref(), Some("--api-key")); + // Check third provider (fetch) + let fetch_provider = &config.mcp.providers[2]; + assert_eq!(fetch_provider.name, "fetch"); + assert!(fetch_provider.enabled); + assert_eq!(fetch_provider.max_concurrent_requests, 1); } #[tokio::test] @@ -368,8 +341,6 @@ api_key_arg = "--api-key" working_directory: None, }), env: env_vars, - api_key_env: None, - api_key_arg: None, enabled: true, max_concurrent_requests: 1, }; diff --git a/vtcode.toml b/vtcode.toml index 698a49c74..2ae0698db 100644 --- a/vtcode.toml +++ b/vtcode.toml @@ -143,8 +143,14 @@ name = "context7" enabled = true command = "npx" args = ["-y", "@upstash/context7-mcp@latest"] -api_key_env = "CONTEXT7_API_KEY" -api_key_arg = "--api-key" +max_concurrent_requests = 3 + +[[mcp.providers]] +# Fetch-based fallback MCP provider for HTTP requests +name = "fetch" +enabled = true +command = "uvx" +args = ["mcp-server-fetch"] max_concurrent_requests = 3 [[mcp.providers]]