Skip to main content

ServerConfig

Struct ServerConfig 

Source
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 in command.rs’s validate_header_value_string, which never echoes a header value into an error message.
  • args: every entry is replaced wholesale (via crate::RedactedItems) since an argument has no key/value split to preserve half of.
  • url: userinfo credentials and any query string are stripped (via crate::RedactedUrl); scheme, host, and path stay readable.
  • command/cwd: passed through crate::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

Source

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();
Source

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 { .. }));
Source

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);
Source

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"]);
Source

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()));
Source

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);
Source

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);
Source

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()));
Source

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));
Source

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

Source§

fn clone(&self) -> ServerConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ServerConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for ServerConfig

Source§

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.

Source§

impl Eq for ServerConfig

Source§

impl PartialEq for ServerConfig

Source§

fn eq(&self, other: &ServerConfig) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for ServerConfig

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for ServerConfig

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.