pub struct ConfigFingerprint(/* private fields */);Expand description
A stable fingerprint of the ServerConfig used to connect to and introspect a server,
recorded so a later comparison can detect that connection parameters changed.
Newtype over a 64-character lowercase-hex String (a SHA-256 digest), rather than a bare
String, for the same reason crate::ServerId/crate::ToolName are newtypes: two
same-shaped hex strings sitting adjacent in crate::metadata::GenerationProvenance are
trivially swappable by accident, and a single-field newtype still serializes transparently
as a JSON string. Deserialize is routed through TryFrom<String> (via
#[serde(try_from = "String")]), so a value read back from disk is validated the same way
crate::ServerId/crate::ToolName are — see DigestFormatError.
§Examples
use mcp_execution_core::provenance::ConfigFingerprint;
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder().command("docker".to_string()).build().unwrap();
let fingerprint = ConfigFingerprint::compute(&config);
assert_eq!(fingerprint.as_str().len(), 64);
assert!(fingerprint.as_str().chars().all(|c| c.is_ascii_hexdigit()));Implementations§
Source§impl ConfigFingerprint
impl ConfigFingerprint
Sourcepub fn compute(config: &ServerConfig) -> Self
pub fn compute(config: &ServerConfig) -> Self
Computes a fingerprint of config, sufficient to detect that connection parameters
changed, without persisting any secret-bearing value.
The preimage carries, in a fixed order: a domain tag; the transport discriminant
(stdio/http/sse); for stdio, command, cwd presence, argument count, and
every environment variable name (sorted); for http/sse, the URL’s canonical
scheme://authority/path form, its query-parameter names (deduplicated, sorted), a
userinfo-present marker, and every header name (ASCII-lowercased, sorted). No argument
value, environment/header value, query-parameter value, or userinfo is ever fed — see
the module docs.
ServerConfig::connect_timeout/discover_timeout are deliberately excluded: they bound
how long the client waits for a response, not what the server exposes, so changing one
must not register as a change to the server’s identity or tool surface.
Residual collision, documented rather than fixed: every URL split_url cannot parse —
including two configs whose only difference is inside the ambiguous userinfo case it
rejects, e.g. https://u:p/w@a.com vs. https://u:p/w@b.com — collapses onto the same
URL_UNPARSEABLE marker and therefore the same fingerprint. This is the same class of
secrecy-over-precision tradeoff as the other residual collisions in this family (query
values, userinfo credentials): the input a real fingerprint would need to distinguish
them is exactly the text this function refuses to hash.
§Examples
Configs differing only in secret-bearing values fingerprint identically:
use mcp_execution_core::provenance::ConfigFingerprint;
use mcp_execution_core::ServerConfig;
let a = ServerConfig::builder()
.command("docker".to_string())
.env("TOKEN".to_string(), "secret-a".to_string())
.build()
.unwrap();
let b = ServerConfig::builder()
.command("docker".to_string())
.env("TOKEN".to_string(), "secret-b".to_string())
.build()
.unwrap();
assert_eq!(ConfigFingerprint::compute(&a), ConfigFingerprint::compute(&b));Sourcepub fn as_str(&self) -> &str
pub fn as_str(&self) -> &str
Returns the fingerprint as a 64-character lowercase-hex string slice.
§Examples
use mcp_execution_core::provenance::ConfigFingerprint;
use mcp_execution_core::ServerConfig;
let config = ServerConfig::builder().command("docker".to_string()).build().unwrap();
let fingerprint = ConfigFingerprint::compute(&config);
assert!(!fingerprint.as_str().is_empty());Trait Implementations§
Source§impl Clone for ConfigFingerprint
impl Clone for ConfigFingerprint
Source§fn clone(&self) -> ConfigFingerprint
fn clone(&self) -> ConfigFingerprint
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more