From f396e1a25328b71e73c67abbaae19b47c4990aa1 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 20 Jun 2026 16:54:48 +0900 Subject: [PATCH] mcp: redact stdio server spec debug --- crates/mcp/src/stdio.rs | 20 +++++++++++++++++++- crates/mcp/tests/stdio_lifecycle.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/mcp/src/stdio.rs b/crates/mcp/src/stdio.rs index 26171ed0..2e953712 100644 --- a/crates/mcp/src/stdio.rs +++ b/crates/mcp/src/stdio.rs @@ -52,7 +52,7 @@ impl Default for McpStdioLimits { } /// A resolved, explicit local stdio MCP server process specification. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct McpStdioServerSpec { pub name: String, pub command: String, @@ -107,6 +107,24 @@ impl McpStdioServerSpec { } } +impl fmt::Debug for McpStdioServerSpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let env: BTreeMap<&str, &str> = self + .env + .keys() + .map(|name| (name.as_str(), "[redacted]")) + .collect(); + f.debug_struct("McpStdioServerSpec") + .field("name", &self.name) + .field("command", &self.command) + .field("args", &self.args) + .field("cwd", &self.cwd) + .field("env", &env) + .field("redaction_count", &self.redactions.len()) + .finish() + } +} + /// Resolve one explicitly named stdio server from typed MCP config. pub fn resolve_named_stdio_server( config: &McpConfig, diff --git a/crates/mcp/tests/stdio_lifecycle.rs b/crates/mcp/tests/stdio_lifecycle.rs index d805c57e..f4a51a92 100644 --- a/crates/mcp/tests/stdio_lifecycle.rs +++ b/crates/mcp/tests/stdio_lifecycle.rs @@ -19,6 +19,34 @@ fn tight_limits() -> McpStdioLimits { } } +#[test] +fn stdio_server_spec_debug_redacts_resolved_env_values() { + let spec = McpStdioServerSpec::new("debug-mock", "/bin/mock-mcp") + .arg("--stdio") + .cwd("/tmp/mock-mcp") + .env("LITERAL_VALUE", "literal-plaintext") + .env("INHERITED_VALUE", "inherited-plaintext") + .env("ENV_REF_VALUE", "env-ref-plaintext") + .env("SECRET_REF_VALUE", "secret-ref-plaintext"); + + let debug = format!("{spec:?}"); + + assert!(debug.contains("debug-mock")); + assert!(debug.contains("/bin/mock-mcp")); + assert!(debug.contains("--stdio")); + assert!(debug.contains("/tmp/mock-mcp")); + assert!(debug.contains("LITERAL_VALUE")); + assert!(debug.contains("INHERITED_VALUE")); + assert!(debug.contains("ENV_REF_VALUE")); + assert!(debug.contains("SECRET_REF_VALUE")); + assert!(debug.contains("[redacted]")); + + assert!(!debug.contains("literal-plaintext")); + assert!(!debug.contains("inherited-plaintext")); + assert!(!debug.contains("env-ref-plaintext")); + assert!(!debug.contains("secret-ref-plaintext")); +} + #[tokio::test] async fn initializes_mock_stdio_server() { let mut client = McpStdioClient::connect(mock_server("success"), tight_limits())