Files
codex/prs/bolinfest/PR-1591.md
2025-09-02 15:17:45 -07:00

3.9 KiB

PR #1591: Added mcp-server name validation

Description

This PR implements server name validation for MCP (Model Context Protocol) servers to ensure they conform to the required pattern ^[a-zA-Z0-9_-]+$. This addresses the TODO comment in mcp_connection_manager.rs:82.

  • Added validation before spawning MCP client tasks
  • Invalid server names are added to errors map with descriptive messages

I have read the CLA Document and I hereby sign the CLA

Full Diff

diff --git a/codex-rs/core/src/mcp_connection_manager.rs b/codex-rs/core/src/mcp_connection_manager.rs
index 6ae1865f16..7cf6762752 100644
--- a/codex-rs/core/src/mcp_connection_manager.rs
+++ b/codex-rs/core/src/mcp_connection_manager.rs
@@ -79,9 +79,19 @@ impl McpConnectionManager {
 
         // Launch all configured servers concurrently.
         let mut join_set = JoinSet::new();
+        let mut errors = ClientStartErrors::new();
 
         for (server_name, cfg) in mcp_servers {
-            // TODO: Verify server name: require `^[a-zA-Z0-9_-]+$`?
+            // Validate server name before spawning
+            if !is_valid_mcp_server_name(&server_name) {
+                let error = anyhow::anyhow!(
+                    "invalid server name '{}': must match pattern ^[a-zA-Z0-9_-]+$",
+                    server_name
+                );
+                errors.insert(server_name, error);
+                continue;
+            }
+
             join_set.spawn(async move {
                 let McpServerConfig { command, args, env } = cfg;
                 let client_res = McpClient::new_stdio_client(command, args, env).await;
@@ -117,7 +127,6 @@ impl McpConnectionManager {
 
         let mut clients: HashMap<String, std::sync::Arc<McpClient>> =
             HashMap::with_capacity(join_set.len());
-        let mut errors = ClientStartErrors::new();
 
         while let Some(res) = join_set.join_next().await {
             let (server_name, client_res) = res?; // JoinError propagation
@@ -208,3 +217,10 @@ pub async fn list_all_tools(
 
     Ok(aggregated)
 }
+
+fn is_valid_mcp_server_name(server_name: &str) -> bool {
+    !server_name.is_empty()
+        && server_name
+            .chars()
+            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
+}

Review Comments

codex-rs/core/src/mcp_connection_manager.rs

@@ -79,9 +80,17 @@ impl McpConnectionManager {
 
         // Launch all configured servers concurrently.
         let mut join_set = JoinSet::new();
+        let mut errors = ClientStartErrors::new();
 
         for (server_name, cfg) in mcp_servers {
-            // TODO: Verify server name: require `^[a-zA-Z0-9_-]+$`?
+            // Validate server name before spawning
+            if !is_valid_server_name(&server_name) {
+                let error = anyhow::anyhow!("invalid server name '{}': must match pattern ^[a-zA-Z0-9_-]+$", server_name);
+                errors.insert(server_name, error);
+                continue;
+            }
+            
+            let server_name_cloned = server_name.clone();

Is this necessary? If we have reached this point, nothing else has taken ownership of server_name, correct?

codex-rs/core/src/util.rs

@@ -64,3 +64,9 @@ pub fn is_inside_git_repo(config: &Config) -> bool {
 
     false
 }
+
+pub fn is_valid_server_name(server_name: &str) -> bool {

I don't really like having generic "util" files because it's not clear what goes in there: please keep this in mcp_connection_manager.rs since that is the only place it is used.