Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/guides/mcp-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,22 @@ allowlists control tool access, and how to troubleshoot common configuration iss
command = "uvx"
args = ["mcp-server-time"]
max_concurrent_requests = 2

[[mcp.providers]]
name = "context7"
enabled = true
command = "npx"
args = ["-y", "@upstash/context7-mcp@latest"]
auth = { api_key_env = "CONTEXT7_API_KEY", arg = "--api-key" }
```

For HTTP transports, specify the endpoint and headers in place of the stdio fields. The
configuration loader automatically deserializes either transport variant.

The optional `auth` table lets you forward API keys from environment variables to stdio
transports. VT Code reads the `api_key_env` variable at runtime and appends the resulting
value with the specified `arg`. When omitted, the flag defaults to `--api-key`.

## Allowlist Behaviour

MCP access is gated by pattern-based allowlists. The defaults apply to every provider unless the
Expand Down
15 changes: 15 additions & 0 deletions vtcode-core/src/config/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,21 @@ pub mod models {
pub const DEEPSEEK_REASONER: &str = deepseek::DEEPSEEK_REASONER;
}

/// MCP-related constants to avoid scattering provider identifiers
pub mod mcp {
pub mod providers {
pub const CONTEXT7: &str = "context7";
}

pub mod env {
pub const CONTEXT7_API_KEY: &str = "CONTEXT7_API_KEY";
}

pub mod auth {
pub const DEFAULT_API_KEY_FLAG: &str = "--api-key";
}
}

/// Prompt caching defaults shared across features and providers
pub mod prompt_cache {
pub const DEFAULT_ENABLED: bool = true;
Expand Down
31 changes: 31 additions & 0 deletions vtcode-core/src/config/mcp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::config::constants::mcp::auth::DEFAULT_API_KEY_FLAG;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
Expand Down Expand Up @@ -118,6 +119,10 @@ pub struct McpProviderConfig {
#[serde(default)]
pub env: HashMap<String, String>,

/// Authentication configuration for the provider
#[serde(default)]
pub auth: Option<McpProviderAuthConfig>,

/// Whether this provider is enabled
#[serde(default = "default_provider_enabled")]
pub enabled: bool,
Expand All @@ -133,12 +138,38 @@ impl Default for McpProviderConfig {
name: String::new(),
transport: McpTransportConfig::Stdio(McpStdioServerConfig::default()),
env: HashMap::new(),
auth: None,
enabled: default_provider_enabled(),
max_concurrent_requests: default_provider_max_concurrent(),
}
}
}

/// Authentication configuration for MCP providers
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct McpProviderAuthConfig {
/// Environment variable containing the API key
#[serde(default)]
pub api_key_env: Option<String>,

/// CLI flag to use when passing the API key to stdio transports
#[serde(default = "default_auth_arg")]
pub arg: String,
}

impl Default for McpProviderAuthConfig {
fn default() -> Self {
Self {
api_key_env: None,
arg: default_auth_arg(),
}
}
}

fn default_auth_arg() -> String {
DEFAULT_API_KEY_FLAG.to_string()
}

/// Allow list configuration for MCP providers
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct McpAllowListConfig {
Expand Down
5 changes: 3 additions & 2 deletions vtcode-core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,9 @@ pub use core::{
pub use defaults::{ContextStoreDefaults, PerformanceDefaults, ScenarioDefaults};
pub use loader::{ConfigManager, VTCodeConfig};
pub use mcp::{
McpAllowListConfig, McpAllowListRules, McpClientConfig, McpHttpServerConfig, McpProviderConfig,
McpStdioServerConfig, McpTransportConfig, McpUiConfig, McpUiMode,
McpAllowListConfig, McpAllowListRules, McpClientConfig, McpHttpServerConfig,
McpProviderAuthConfig, McpProviderConfig, McpStdioServerConfig, McpTransportConfig,
McpUiConfig, McpUiMode,
};
pub use router::{ComplexityModelMap, ResourceBudget, RouterConfig};
pub use telemetry::TelemetryConfig;
Expand Down
5 changes: 1 addition & 4 deletions vtcode-core/src/core/agent/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,7 @@ impl AgentRunner {
parallel_tool_config: Some(
crate::llm::provider::ParallelToolConfig::anthropic_optimized(),
),
reasoning_effort: if self
.provider_client
.supports_reasoning_effort(&self.model)
{
reasoning_effort: if self.provider_client.supports_reasoning_effort(&self.model) {
self.reasoning_effort
} else {
None
Expand Down
22 changes: 22 additions & 0 deletions vtcode-core/src/mcp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,27 @@ impl McpProvider {
command.envs(&self.config.env);
}

if let Some(auth) = &self.config.auth {
if let Some(api_key_env) = &auth.api_key_env {
match std::env::var(api_key_env) {
Ok(api_key) => {
debug!(
"Injecting API key for provider '{}' using flag '{}'",
provider_name, auth.arg
);
command.arg(&auth.arg);
command.arg(api_key);
}
Err(error) => {
warn!(
"API key environment variable '{}' not available for provider '{}': {}",
api_key_env, provider_name, error
);
}
}
}
}

// Create new process group to ensure proper cleanup (Unix only)
#[cfg(unix)]
{
Expand Down Expand Up @@ -1908,6 +1929,7 @@ mod tests {
working_directory: None,
}),
env: HashMap::new(),
auth: None,
enabled: true,
max_concurrent_requests: 3,
};
Expand Down
40 changes: 38 additions & 2 deletions vtcode-core/tests/mcp_basic_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
//! These tests verify that MCP configuration and basic functionality work correctly.

use std::collections::HashMap;
use vtcode_core::config::constants::mcp::auth::DEFAULT_API_KEY_FLAG;
use vtcode_core::config::constants::mcp::env::CONTEXT7_API_KEY;
use vtcode_core::config::mcp::{
McpClientConfig, McpProviderConfig, McpStdioServerConfig, McpTransportConfig, McpUiConfig,
McpUiMode,
McpClientConfig, McpProviderAuthConfig, McpProviderConfig, McpStdioServerConfig,
McpTransportConfig, McpUiConfig, McpUiMode,
};
use vtcode_core::mcp_client::McpClient;

Expand Down Expand Up @@ -107,6 +109,7 @@ mod tests {
working_directory: None,
}),
env: env_vars,
auth: None,
enabled: true,
max_concurrent_requests: 1,
};
Expand All @@ -119,6 +122,39 @@ mod tests {
assert_eq!(provider_config.env.get("DEBUG"), Some(&"true".to_string()));
}

#[test]
fn test_provider_auth_config_defaults() {
let auth = McpProviderAuthConfig::default();
assert!(auth.api_key_env.is_none());
assert_eq!(auth.arg, DEFAULT_API_KEY_FLAG);
}

#[test]
fn test_provider_auth_config_from_toml() {
let toml_content = format!(
r#"
[mcp]
enabled = true

[[mcp.providers]]
name = "context7"
command = "npx"
args = ["-y", "@upstash/context7-mcp@latest"]
auth = {{ api_key_env = "{}" }}
"#,
CONTEXT7_API_KEY
);

let config: vtcode_core::config::VTCodeConfig = toml::from_str(&toml_content).unwrap();
let provider = &config.mcp.providers[0];
let auth = provider
.auth
.as_ref()
.expect("auth config should be present");
assert_eq!(auth.api_key_env.as_deref(), Some(CONTEXT7_API_KEY));
assert_eq!(auth.arg, DEFAULT_API_KEY_FLAG);
}

#[tokio::test]
async fn test_mcp_client_initialization() {
let config = McpClientConfig {
Expand Down
1 change: 1 addition & 0 deletions vtcode-core/tests/mcp_context7_manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ async fn context7_list_tools_smoke() {
working_directory: None,
}),
env: HashMap::new(),
auth: None,
enabled: true,
max_concurrent_requests: 1,
};
Expand Down
2 changes: 2 additions & 0 deletions vtcode-core/tests/mcp_integration_e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ mod tests {
working_directory: Some(workspace.to_string_lossy().to_string()),
}),
env: HashMap::new(),
auth: None,
enabled: true,
max_concurrent_requests: 3,
};
Expand Down Expand Up @@ -245,6 +246,7 @@ max_concurrent_requests = 1
working_directory: None,
}),
env: env_vars,
auth: None,
enabled: true,
max_concurrent_requests: 1,
};
Expand Down
2 changes: 2 additions & 0 deletions vtcode-core/tests/mcp_integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ max_concurrent_requests = 1
name: "context7".to_string(),
transport: McpTransportConfig::Stdio(stdio_config),
env: HashMap::new(),
auth: None,
enabled: true,
max_concurrent_requests: 2,
};
Expand Down Expand Up @@ -341,6 +342,7 @@ max_concurrent_requests = 1
working_directory: None,
}),
env: env_vars,
auth: None,
enabled: true,
max_concurrent_requests: 1,
};
Expand Down
1 change: 1 addition & 0 deletions vtcode.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ name = "context7"
enabled = true
command = "npx"
args = ["-y", "@upstash/context7-mcp@latest"]
auth = { api_key_env = "CONTEXT7_API_KEY", arg = "--api-key" }
max_concurrent_requests = 3

[[mcp.providers]]
Expand Down
Loading