mcp_trace_validator/checks/
support.rs1use serde_json::Value;
9
10use crate::context::TraceContext;
11
12pub(super) fn server_capability(context: &TraceContext<'_>, path: &[&str]) -> Option<bool> {
18 capability_in(context.server_capabilities(), path, context)
19}
20
21pub(super) fn client_capability(context: &TraceContext<'_>, path: &[&str]) -> Option<bool> {
24 capability_in(context.client_capabilities(), path, context)
25}
26
27fn capability_in(
28 capabilities: Option<&Value>,
29 path: &[&str],
30 context: &TraceContext<'_>,
31) -> Option<bool> {
32 context.initialize().result?;
35 let Some(mut current) = capabilities else {
36 return Some(false);
37 };
38 for segment in path {
39 match current.get(segment) {
40 Some(next) => current = next,
41 None => return Some(false),
42 }
43 }
44 Some(!(current.is_null() || matches!(current, Value::Bool(false))))
45}
46
47pub(super) fn is_base64(text: &str) -> bool {
50 let bytes = text.as_bytes();
51 if bytes.len() % 4 != 0 {
52 return false;
53 }
54 let padding = bytes.iter().rev().take_while(|&&b| b == b'=').count();
55 if padding > 2 {
56 return false;
57 }
58 let content = &bytes[..bytes.len() - padding];
59 content
60 .iter()
61 .all(|&b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/')
62}
63
64pub(super) fn has_rfc3986_scheme(uri: &str) -> bool {
68 let Some((scheme, _)) = uri.split_once(':') else {
69 return false;
70 };
71 let mut chars = scheme.chars();
72 let Some(first) = chars.next() else {
73 return false;
74 };
75 first.is_ascii_alphabetic()
76 && chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'))
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn base64_validation_is_exact() {
85 for valid in ["", "aGk=", "aGV5", "aGV5bw==", "AB+/", "QUJDRA=="] {
86 assert!(is_base64(valid), "{valid:?} should validate");
87 }
88 for invalid in [
89 "aGk", "aGk =", "aGk!", "====", "aG=k", "aGV5bw=", ] {
96 assert!(!is_base64(invalid), "{invalid:?} should not validate");
97 }
98 }
99
100 #[test]
101 fn rfc3986_scheme_validation_is_exact() {
102 for valid in ["https://x", "file:///a", "git://r", "a:", "z+ssh.2-x:rest"] {
103 assert!(has_rfc3986_scheme(valid), "{valid:?} should validate");
104 }
105 for invalid in [
106 "", "no-colon", ":rest", "1https://x", "ht tp://x", "ht_tp://x", ] {
113 assert!(
114 !has_rfc3986_scheme(invalid),
115 "{invalid:?} should not validate"
116 );
117 }
118 }
119}