Skip to content

Commit 1f39bee

Browse files
committed
Add MCP requirements enforcement and update related configurations
- Introduce `mcp.requirements` section in configuration for allowed stdio commands and HTTP endpoints. - Implement enforcement logic in MCP client to skip providers not matching requirements. - Update documentation to reflect new configuration options and usage. - Enhance tool naming conventions for MCP tools to support new requirements. - Adjust UI styles for better distinction between assistant and PTY output.
1 parent 8d2900a commit 1f39bee

File tree

27 files changed

+445
-84
lines changed

27 files changed

+445
-84
lines changed

docs/config/CONFIG_FIELD_REFERENCE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,11 @@ python3 scripts/generate_config_field_reference.py
239239
| `mcp.max_concurrent_connections` | `integer` | no | `5` | Maximum number of concurrent MCP connections |
240240
| `mcp.providers` | `array` | no | `[]` | Configured MCP providers |
241241
| `mcp.providers[]` | `object` | no | `-` | Configuration for a single MCP provider |
242+
| `mcp.requirements.allowed_http_endpoints` | `array` | no | `[]` | Allowed HTTP endpoints when MCP requirements enforcement is enabled |
243+
| `mcp.requirements.allowed_http_endpoints[]` | `string` | no | `-` | - |
244+
| `mcp.requirements.allowed_stdio_commands` | `array` | no | `[]` | Allowed stdio command names when MCP requirements enforcement is enabled |
245+
| `mcp.requirements.allowed_stdio_commands[]` | `string` | no | `-` | - |
246+
| `mcp.requirements.enforce` | `boolean` | no | `false` | Enforce provider admission against transport identity allowlists |
242247
| `mcp.request_timeout_seconds` | `integer` | no | `30` | Request timeout in seconds |
243248
| `mcp.retry_attempts` | `integer` | no | `3` | Connection retry attempts |
244249
| `mcp.security.api_key_env` | `null \| string` | no | `null` | API key for MCP server authentication (environment variable name) |

docs/guides/mcp-integration.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ following table:
4949
| `startup_timeout_seconds` | `null` | Optional provider start handshake timeout; inherits `request_timeout_seconds` when unset. |
5050
| `tool_timeout_seconds` | `null` | Optional per-tool guard for long-running operations. |
5151
| `experimental_use_rmcp_client` | `true` | Enables the Rust MCP client so HTTP/streamable transports are available. |
52+
| `requirements` | `enforce = false` | Optional transport-identity allowlist for admitting providers during initialization. |
5253
| `ui` | `mode = "compact"`, `max_events = 50`, `show_provider_names = true` | Controls event rendering inside the TUI. |
5354

5455
Configure overrides as needed:
@@ -62,11 +63,18 @@ retry_attempts = 2
6263
startup_timeout_seconds = 90 # optional: provider startup handshake window
6364
tool_timeout_seconds = 45 # optional: per-tool execution deadline
6465
experimental_use_rmcp_client = true
66+
67+
[mcp.requirements]
68+
enforce = false
69+
allowed_stdio_commands = ["uvx", "npx"]
70+
allowed_http_endpoints = ["https://mcp.example.com/api"]
6571
```
6672

6773
`startup_timeout_seconds` defaults to `None`, which falls back to the global `request_timeout_seconds`
6874
value. Setting it to `0` disables the startup timeout entirely—handy for first-run npm downloads.
6975
`tool_timeout_seconds` behaves the same way for individual tool calls.
76+
When `mcp.requirements.enforce = true`, providers whose stdio `command` or HTTP `endpoint` is not
77+
allowlisted are skipped during MCP initialization.
7078

7179
### UI configuration
7280

docs/mcp/MCP_INTEGRATION_GUIDE.md

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
This document outlines how VT Code implements Model Context Protocol (MCP) based on Claude's official MCP specifications and best practices.
44

5+
> Status: this file is a high-level architecture reference. For accurate, current configuration and behavior, use `docs/guides/mcp-integration.md`.
6+
57
## Overview
68

79
VT Code integrates MCP to connect with external tools, databases, and APIs through the `vtcode-core/src/mcp/` module. MCP provides a standardized interface for AI agents to access tools beyond their native capabilities.
@@ -45,30 +47,7 @@ mcp/
4547

4648
### Configuration Files
4749

48-
**Project-Scoped** (checked into source control):
49-
```json
50-
// .mcp.json
51-
{
52-
"mcpServers": {
53-
"server_name": {
54-
"command": "npx",
55-
"args": ["mcp-server-name"],
56-
"type": "stdio",
57-
"env": {
58-
"API_KEY": "value"
59-
}
60-
}
61-
}
62-
}
63-
```
64-
65-
**User-Scoped** (personal, cross-project):
66-
```
67-
~/.claude.json
68-
// Under "mcpServers" field
69-
```
70-
71-
**Runtime Configuration**:
50+
**Runtime Configuration (`vtcode.toml`)**:
7251
```toml
7352
# vtcode.toml
7453
[mcp]
@@ -137,7 +116,7 @@ transport = {
137116
**Implementation**: `rmcp_transport.rs:create_http_transport()`
138117
- Requires `experimental_use_rmcp_client = true`
139118
- Remote server integration
140-
- OAuth 2.0 authentication support (via `/mcp` CLI command)
119+
- OAuth CLI login/logout flows are not implemented in VT Code yet
141120

142121
### 3. Child Process Transport (Advanced)
143122

@@ -174,10 +153,9 @@ prompts = ["prompt_name"]
174153

175154
### Access Control
176155

177-
**Token Management**:
178-
- Stored securely on disk
179-
- Automatically refreshed
180-
- Revocable via `/mcp` CLI
156+
**Token/API Key Management**:
157+
- Use `api_key_env` or `env_http_headers` to source credentials from environment variables
158+
- Avoid embedding secrets directly in config files
181159

182160
**API Key Handling**:
183161
- Read from environment variables (recommended)
@@ -352,8 +330,9 @@ fn get_status(&self) -> McpClientStatus
352330
# Configure server
353331
/mcp config <name> --env KEY=value
354332

355-
# Authenticate remote server
356-
/mcp authenticate <name>
333+
# OAuth login/logout placeholders (currently return "not implemented")
334+
/mcp login <name>
335+
/mcp logout <name>
357336

358337
# Remove server
359338
/mcp remove <name>
@@ -464,9 +443,8 @@ pub struct McpProvider {
464443
- Review output size in tool design
465444

466445
**4. Authentication Failures**
467-
- Use `/mcp authenticate <name>` for OAuth
468-
- Verify API keys in environment variables
469-
- Check token refresh status
446+
- Verify API keys and header env vars are set
447+
- Confirm `api_key_env` / `env_http_headers` entries match exported variable names
470448

471449
### Diagnostic Commands
472450

@@ -536,9 +514,8 @@ Implement additional transport types by extending `rmcp_transport.rs`:
536514
- Monitor token usage with `MAX_MCP_OUTPUT_TOKENS`
537515

538516
4. **Configuration Management**
539-
- Use `.mcp.json` for project-specific servers
540-
- Use `~/.claude.json` for personal utilities
541-
- Use `vtcode.toml` for runtime configuration
517+
- Configure MCP providers in `vtcode.toml`
518+
- Keep secrets in environment variables
542519
- Document custom servers in project README
543520

544521
5. **Testing**

docs/project/TODO.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,10 @@ https://sw.kovidgoyal.net/kitty/graphics-protocol/
9696
2. consider showing image as popup and on demand only to save memory and reduce token usage, instead of embedding image data directly in the prompt or response.
9797
3. suggestion, when an image is detected in the chat user input. show as a [image #N] tag in the user prompt, similiar to [pasted text #N] for pasted text. then the agent can choose to request the image data if needed, and the system can provide the image data in a separate tool call or context update, rather than embedding it directly in the prompt. This would allow for more efficient handling of images without bloating the prompt with large base64 data.
9898
4. also allow keyboard shortcut to view the image in a popup viewer, so that users can easily view the image without it taking up space in the main chat interface. like keyboard and click on the [image #N] tag to open the image in a popup.
99+
100+
---
101+
102+
increase assistant agent message ansi brighter.
103+
current it look identical to PTY output message " All checks passed. No changes required." '/Users/vinhnguyenxuan/Desktop/Screenshot 2026-03-05 at 9.16.19 PM.png'
104+
105+
make sure PTY output message is dimmer and assistant agent message is brighter, so that it's easier to visually distinguish between the two types of messages in the chat interface. This will improve readability and help users quickly identify which messages are from the assistant agent versus which are outputs from PTY commands. Consider using different ANSI color codes or brightness levels to achieve this visual distinction effectively.

src/agent/runloop/unified/session_setup/mcp_tools.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use vtcode_core::llm::provider as uni;
22
use vtcode_core::mcp::McpToolInfo;
3+
use vtcode_core::tools::mcp::model_visible_mcp_tool_name;
34

45
fn build_single_mcp_tool_definition(tool: &McpToolInfo) -> uni::ToolDefinition {
56
let parameters = vtcode_core::llm::providers::gemini::sanitize_function_parameters(
@@ -14,7 +15,11 @@ fn build_single_mcp_tool_definition(tool: &McpToolInfo) -> uni::ToolDefinition {
1415
)
1516
};
1617

17-
uni::ToolDefinition::function(format!("mcp_{}", tool.name), description, parameters)
18+
uni::ToolDefinition::function(
19+
model_visible_mcp_tool_name(&tool.provider, &tool.name),
20+
description,
21+
parameters,
22+
)
1823
}
1924

2025
pub fn build_mcp_tool_definitions(tools: &[McpToolInfo]) -> Vec<uni::ToolDefinition> {

src/agent/runloop/unified/tool_output_handler.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ async fn handle_success_common(
165165
ctx.session_stats.record_tool(name);
166166

167167
if let Some(tool_name) = name.strip_prefix("mcp_") {
168+
let tool_name = tool_name.trim_start_matches('_');
169+
let tool_name = tool_name.split("__").last().unwrap_or(tool_name);
168170
record_mcp_success_event(ctx.mcp_panel_state, tool_name, args_val);
169171
} else {
170172
render_tool_output_common(

src/agent/runloop/unified/tool_summary.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,9 @@ pub(crate) fn describe_tool_action(tool_name: &str, args: &Value) -> (String, Ha
474474
tool_name.starts_with("mcp::") || tool_name.starts_with("mcp_") || tool_name == "fetch";
475475

476476
// For the actual matching, we need to use the tool name without the "mcp_" prefix
477-
let actual_tool_name = if let Some(stripped) = tool_name.strip_prefix("mcp_") {
477+
let actual_tool_name = if tool_name.starts_with("mcp__") {
478+
tool_name.split("__").last().unwrap_or(tool_name)
479+
} else if let Some(stripped) = tool_name.strip_prefix("mcp_") {
478480
stripped
479481
} else if tool_name.starts_with("mcp::") {
480482
// For tools in mcp::provider::name format, extract just the tool name

src/agent/runloop/unified/turn/session.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ pub(crate) async fn handle_tool_failure(
6565

6666
// MCP event
6767
if let Some(tool_name) = details.name.strip_prefix("mcp_") {
68+
let tool_name = tool_name.trim_start_matches('_');
69+
let tool_name = tool_name.split("__").last().unwrap_or(tool_name);
6870
ctx.renderer
6971
.line_if_not_empty(vtcode_core::utils::ansi::MessageStyle::Output)?;
7072
ctx.renderer.line(

vtcode-config/src/mcp.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ pub struct McpClientConfig {
1919
#[serde(default)]
2020
pub providers: Vec<McpProviderConfig>,
2121

22+
/// Optional transport-identity requirements for provider admission
23+
#[serde(default)]
24+
pub requirements: McpRequirementsConfig,
25+
2226
/// MCP server configuration (for vtcode to expose tools)
2327
#[serde(default)]
2428
pub server: McpServerConfig,
@@ -155,6 +159,7 @@ impl Default for McpClientConfig {
155159
enabled: default_mcp_enabled(),
156160
ui: McpUiConfig::default(),
157161
providers: Vec::new(),
162+
requirements: McpRequirementsConfig::default(),
158163
server: McpServerConfig::default(),
159164
allowlist: McpAllowListConfig::default(),
160165
max_concurrent_connections: default_max_concurrent_connections(),
@@ -171,6 +176,33 @@ impl Default for McpClientConfig {
171176
}
172177
}
173178

179+
/// Requirements used to admit MCP providers by transport identity.
180+
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
181+
#[derive(Debug, Clone, Deserialize, Serialize)]
182+
pub struct McpRequirementsConfig {
183+
/// Whether requirement checks are enforced
184+
#[serde(default = "default_mcp_requirements_enforce")]
185+
pub enforce: bool,
186+
187+
/// Allowed stdio command names when enforcement is enabled
188+
#[serde(default)]
189+
pub allowed_stdio_commands: Vec<String>,
190+
191+
/// Allowed HTTP endpoints when enforcement is enabled
192+
#[serde(default)]
193+
pub allowed_http_endpoints: Vec<String>,
194+
}
195+
196+
impl Default for McpRequirementsConfig {
197+
fn default() -> Self {
198+
Self {
199+
enforce: default_mcp_requirements_enforce(),
200+
allowed_stdio_commands: Vec::new(),
201+
allowed_http_endpoints: Vec::new(),
202+
}
203+
}
204+
}
205+
174206
/// UI configuration for MCP display
175207
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
176208
#[derive(Debug, Clone, Deserialize, Serialize)]
@@ -746,6 +778,10 @@ fn default_max_argument_size() -> u32 {
746778
1024 * 1024 // 1MB
747779
}
748780

781+
fn default_mcp_requirements_enforce() -> bool {
782+
false
783+
}
784+
749785
#[cfg(test)]
750786
mod tests {
751787
use super::*;
@@ -764,6 +800,9 @@ mod tests {
764800
assert_eq!(config.request_timeout_seconds, 30);
765801
assert_eq!(config.retry_attempts, 3);
766802
assert!(config.providers.is_empty());
803+
assert!(!config.requirements.enforce);
804+
assert!(config.requirements.allowed_stdio_commands.is_empty());
805+
assert!(config.requirements.allowed_http_endpoints.is_empty());
767806
assert!(!config.server.enabled);
768807
assert!(!config.allowlist.enforce);
769808
assert!(config.allowlist.default.tools.is_none());

vtcode-core/src/mcp/cli.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,14 @@ async fn run_get(get_args: GetArgs) -> Result<()> {
384384

385385
async fn run_login(login_args: LoginArgs) -> Result<()> {
386386
let _ = login_args;
387-
bail!("MCP OAuth login is not yet supported in VT Code.")
387+
bail!(
388+
"MCP OAuth login is not implemented in VT Code yet. Configure HTTP auth with --bearer-token-env-var or provider api_key_env."
389+
)
388390
}
389391

390392
async fn run_logout(logout_args: LogoutArgs) -> Result<()> {
391393
let _ = logout_args;
392-
bail!("MCP OAuth logout is not yet supported in VT Code.")
394+
bail!("MCP OAuth logout is not implemented in VT Code yet.")
393395
}
394396

395397
fn build_stdio_transport(args: AddMcpStdioArgs) -> Result<McpTransportConfig> {

0 commit comments

Comments
 (0)