pub struct ServerConfig { /* private fields */ }Expand description
MCP server configuration with command, arguments, and environment.
Represents the configuration needed to communicate with an MCP server, supporting both stdio (subprocess) and HTTP transports.
§Transport Types
- Stdio: Launches a subprocess and communicates via stdin/stdout
- HTTP: Connects to an HTTP/HTTPS API endpoint
§Security
Both fields are private and the type’s Deserialize impl is hand-written (see below), so
a ServerConfig cannot be obtained — via ServerConfigBuilder::build, a struct literal,
or serde_json::from_str — without having passed validate_server_config. This closes
the gap that existed when every field was pub and Deserialize was derived: a
hand-edited mcp.json or a directly-constructed struct literal could previously skip
validation entirely.
headers, env, args, and url routinely carry secrets (e.g. an
Authorization: Bearer <token> header, a GITHUB_PERSONAL_ACCESS_TOKEN
environment variable, an --api-key sk-...-style argument, or a
?token=-style query string), so this type’s Debug implementation is
hand-written to redact them (this guarantee is Debug-only — see
“Serialization Hazard” below for Serialize, which does not redact
anything):
headers/env: keys stay visible, values are replaced — a legitimately configured key (a chosen header or env var name, e.g."Authorization") is not itself a secret and remains useful for debugging. This mirrors the discipline already applied to header values incommand.rs’svalidate_header_value_string, which never echoes a header value into an error message.args: every entry is replaced wholesale (viacrate::RedactedItems) since an argument has no key/value split to preserve half of.url: userinfo credentials and any query string are stripped (viacrate::RedactedUrl); scheme, host, and path stay readable.command/cwd: passed throughcrate::sanitize_path_for_error— not a secret, but an absolute path leaks the OS username, and the program name itself (docker,npx) is worth keeping readable for telling server entries apart in a log.
This is a narrower guarantee than command.rs’s header-name validation
errors, which redact the name too: those fire on input that has not yet
been confirmed well-formed (e.g. a Name=Value CLI argument mis-split on
the wrong separator can leave a secret value sitting in the name
position), so any name reaching that error path must be treated as
secret-shaped. Once validate_server_config has accepted a config,
its headers/env keys are the caller’s own identifiers rather than
unvalidated split output, so trusting them for Debug output is not in
tension with distrusting an unvalidated name in an error message.
ServerConfigBuilder carries the same Debug treatment for
consistency, but that guarantee does not extend to it: the builder is
populated before validate_server_config runs, so a caller that
feeds it unvalidated input (e.g. a mis-split CLI argument) can still end
up with a secret-shaped key in format!("{builder:?}"). Keys are shown
there deliberately regardless — redacting them would defeat the point of
a debug impl for a type whose purpose is to be inspected before
build().
§Serialization Hazard
Serialize is a separate code path from Debug and is deliberately
left unredacted: whatever consumes the serialized form must still be able
to round-trip real values, so serde_json::to_string(&config) (or any
other Serialize-driven output) emits every field, including secrets, in
full. The Debug-redaction guarantee documented above does not extend
to Serialize in any way — do not assume “this type redacts secrets”
covers both.
Consequently, a serialized ServerConfig must never be logged or printed
directly. It exists for whatever trusted persistence an embedder builds
on top of this crate (a caller-owned config store, for instance) — not
for a specific path this crate implements itself; as of this writing no
non-test code in this workspace serializes a ServerConfig and then
logs or prints the result.
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("docker".to_string())
.env(
"GITHUB_PERSONAL_ACCESS_TOKEN".to_string(),
"ghp_supersecretvalue".to_string(),
)
.build()
.unwrap();
// Unlike `Debug`, `Serialize` does not redact anything.
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("ghp_supersecretvalue"));§Examples
use mcp_execution_core::ServerConfig;
// Stdio transport
let config = ServerConfig::builder()
.command("docker".to_string())
.arg("run".to_string())
.arg("mcp-server".to_string())
.build().unwrap();
assert_eq!(config.command(), Some("docker"));
assert_eq!(config.args().len(), 2);
// HTTP transport
let config = ServerConfig::builder()
.http_transport("https://api.example.com/mcp".to_string())
.header("Authorization".to_string(), "Bearer token".to_string())
.build().unwrap();Debug output redacts header/env values but keeps keys:
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.http_transport("https://api.example.com/mcp".to_string())
.header("Authorization".to_string(), "Bearer sk-secret-value".to_string())
.build();
let debug_output = format!("{config:?}");
assert!(debug_output.contains("Authorization"));
assert!(!debug_output.contains("sk-secret-value"));Debug output redacts args wholesale and strips URL userinfo/query,
while keeping command and the URL host/path readable:
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("docker".to_string())
.arg("--api-key".to_string())
.arg("sk-secret-arg".to_string())
.build();
let debug_output = format!("{config:?}");
assert!(debug_output.contains("docker"));
assert!(!debug_output.contains("sk-secret-arg"));
let config = ServerConfig::builder()
.http_transport("https://user:sk-secret@api.example.com/mcp?token=sk-secret".to_string())
.build();
let debug_output = format!("{config:?}");
assert!(debug_output.contains("api.example.com/mcp"));
assert!(!debug_output.contains("sk-secret"));Implementations§
Source§impl ServerConfig
impl ServerConfig
Sourcepub fn builder() -> ServerConfigBuilder
pub fn builder() -> ServerConfigBuilder
Creates a new builder for ServerConfig.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("docker".to_string())
.build().unwrap();Sourcepub const fn transport(&self) -> &Transport
pub const fn transport(&self) -> &Transport
Returns the transport-specific configuration.
§Examples
use mcp_execution_core::{ServerConfig, Transport};
let config = ServerConfig::builder()
.command("test".to_string())
.build().unwrap();
assert!(matches!(config.transport(), Transport::Stdio { .. }));Sourcepub fn command(&self) -> Option<&str>
pub fn command(&self) -> Option<&str>
Returns the command as a string slice, or None for a config that isn’t
Transport::Stdio.
Mirrors Self::url, which is None for Transport::Stdio and Some for the
other two variants — command only ever exists for Stdio, so absence is
represented the same way rather than as an empty string a caller would have to
remember to check for.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("test-server".to_string())
.build().unwrap();
assert_eq!(config.command(), Some("test-server"));
let config = ServerConfig::builder()
.http_transport("https://api.example.com/mcp".to_string())
.build().unwrap();
assert_eq!(config.command(), None);Sourcepub fn args(&self) -> &[String]
pub fn args(&self) -> &[String]
Returns a slice of arguments, or an empty slice for a config that isn’t
Transport::Stdio.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("server".to_string())
.args(vec!["--port".to_string(), "8080".to_string()])
.build().unwrap();
assert_eq!(config.args(), &["--port", "8080"]);Sourcepub fn env(&self) -> &HashMap<String, String>
pub fn env(&self) -> &HashMap<String, String>
Returns a reference to the environment variables map, or an empty map for a config
that isn’t Transport::Stdio.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("server".to_string())
.env("VAR_NAME".to_string(), "var_value".to_string())
.build().unwrap();
assert_eq!(config.env().get("VAR_NAME"), Some(&"var_value".to_string()));Sourcepub const fn cwd(&self) -> Option<&PathBuf>
pub const fn cwd(&self) -> Option<&PathBuf>
Returns the working directory, if set. Always None for a config that isn’t
Transport::Stdio.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("server".to_string())
.build().unwrap();
assert_eq!(config.cwd(), None);Sourcepub fn url(&self) -> Option<&str>
pub fn url(&self) -> Option<&str>
Returns the URL for HTTP/SSE transport, if set. Always None for
Transport::Stdio.
§Examples
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder()
.command("server".to_string())
.build().unwrap();
assert_eq!(config.url(), None);Sourcepub fn headers(&self) -> &HashMap<String, String>
pub fn headers(&self) -> &HashMap<String, String>
Returns a reference to the HTTP headers map, or an empty map for
Transport::Stdio.
§Examples
use mcp_execution_core::ServerConfig;
use std::collections::HashMap;
let mut headers_map = HashMap::new();
headers_map.insert("Authorization".to_string(), "Bearer token".to_string());
let config = ServerConfig::builder()
.http_transport("https://api.example.com/mcp".to_string())
.headers(headers_map)
.build().unwrap();
assert_eq!(config.headers().get("Authorization"), Some(&"Bearer token".to_string()));Sourcepub const fn connect_timeout(&self) -> Duration
pub const fn connect_timeout(&self) -> Duration
Returns the connection (handshake) timeout.
§Examples
use mcp_execution_core::ServerConfig;
use std::time::Duration;
let config = ServerConfig::builder()
.command("docker".to_string())
.build().unwrap();
assert_eq!(config.connect_timeout(), Duration::from_secs(30));Sourcepub const fn discover_timeout(&self) -> Duration
pub const fn discover_timeout(&self) -> Duration
Returns the tool discovery timeout.
§Examples
use mcp_execution_core::ServerConfig;
use std::time::Duration;
let config = ServerConfig::builder()
.command("docker".to_string())
.build().unwrap();
assert_eq!(config.discover_timeout(), Duration::from_secs(30));Trait Implementations§
Source§impl Clone for ServerConfig
impl Clone for ServerConfig
Source§fn clone(&self) -> ServerConfig
fn clone(&self) -> ServerConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for ServerConfig
impl Debug for ServerConfig
Source§impl<'de> Deserialize<'de> for ServerConfig
impl<'de> Deserialize<'de> for ServerConfig
Source§fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where
D: Deserializer<'de>,
Deserializes into a private shadow shape and then runs
validate_server_config before returning, so a hand-edited mcp.json (or any other
JSON source) can never produce an unvalidated ServerConfig — see the “Security”
section on ServerConfig’s own doc comment.