From d26a5d52fb273fbe01ac909d96cf5d03bd12b507 Mon Sep 17 00:00:00 2001 From: Felix Xia Date: Tue, 23 Jun 2026 17:22:35 +0100 Subject: [PATCH] Fix full-string MCP regex matching --- codex-rs/config/src/mcp_requirements.rs | 14 ++++++++------ codex-rs/config/src/mcp_requirements_tests.rs | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/codex-rs/config/src/mcp_requirements.rs b/codex-rs/config/src/mcp_requirements.rs index 132e5edf55..a420e1de54 100644 --- a/codex-rs/config/src/mcp_requirements.rs +++ b/codex-rs/config/src/mcp_requirements.rs @@ -13,23 +13,25 @@ pub enum McpServerValueMatcher { } impl McpServerValueMatcher { + fn compile_full_regex(expression: &str) -> Result { + Regex::new(&format!(r"\A(?:{expression})\z")) + .map_err(|err| format!("invalid regex `{expression}`: {err}")) + } + fn validate(&self) -> Result<(), String> { let Self::Regex { expression } = self else { return Ok(()); }; - Regex::new(expression) - .map(|_| ()) - .map_err(|err| format!("invalid regex `{expression}`: {err}")) + Self::compile_full_regex(expression).map(|_| ()) } fn matches(&self, candidate: &str) -> bool { match self { Self::Exact { value } => candidate == value, Self::Prefix { value } => candidate.starts_with(value), - Self::Regex { expression } => Regex::new(expression) + Self::Regex { expression } => Self::compile_full_regex(expression) .ok() - .and_then(|regex| regex.find(candidate)) - .is_some_and(|matched| matched.start() == 0 && matched.end() == candidate.len()), + .is_some_and(|regex| regex.is_match(candidate)), } } } diff --git a/codex-rs/config/src/mcp_requirements_tests.rs b/codex-rs/config/src/mcp_requirements_tests.rs index beed543877..23b5a339ce 100644 --- a/codex-rs/config/src/mcp_requirements_tests.rs +++ b/codex-rs/config/src/mcp_requirements_tests.rs @@ -72,6 +72,15 @@ fn regex_matcher_requires_a_full_value_match() { assert!(!matcher.matches("prefix-mcp")); } +#[test] +fn regex_matcher_allows_a_later_alternative_to_match_the_full_value() { + let matcher = McpServerValueMatcher::Regex { + expression: r"https://api\.example\.com|https://api\.example\.com/mcp".to_string(), + }; + + assert!(matcher.matches("https://api.example.com/mcp")); +} + #[test] fn matcher_deserializes_command_and_url_shapes() { let command: McpServerMatcher = toml::from_str(