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

3.4 KiB

PR #1578: Added mcp-server name validation with regex

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/Cargo.toml

@@ -24,7 +24,9 @@ fs2 = "0.4.3"
 futures = "0.3"
 mcp-types = { path = "../mcp-types" }
 mime_guess = "2.0"
+once_cell = "1"
 rand = "0.9"
+regex-lite = "0.1"

I know that regex-lite is not as heavyweight as the regex crate, but it feels like it would be good to avoid adding this dependency for this one feature given that it seems it can be easily avoided. I asked chat for a solution and it gave me:

fn is_valid(s: &str) -> bool {
    !s.is_empty() && s.chars().all(|c| {
        c.is_ascii_alphanumeric() || c == '_' || c == '-'
    })
}

fn main() {
    let test_cases = ["abc123", "abc_123", "abc-123", "abc$", ""];

    for &s in &test_cases {
        println!("{s}: {}", is_valid(s));
    }
}

do you want to update this PR or should I just do this in a separate one?