Skip to main content

agentox_core/checks/conformance/
protocol_version.rs

1//! CONF-009: Server handles protocol version negotiation correctly.
2
3use crate::checks::runner::{Check, CheckContext};
4use crate::checks::types::{CheckCategory, CheckResult, Severity};
5
6pub struct ProtocolVersionValidation;
7
8#[async_trait::async_trait]
9impl Check for ProtocolVersionValidation {
10    fn id(&self) -> &str {
11        "CONF-009"
12    }
13
14    fn name(&self) -> &str {
15        "Protocol version validation"
16    }
17
18    fn category(&self) -> CheckCategory {
19        CheckCategory::Conformance
20    }
21
22    async fn run(&self, ctx: &mut CheckContext) -> Vec<CheckResult> {
23        let desc = "Server must handle version negotiation correctly and not echo bogus versions";
24        let mut results = Vec::new();
25
26        // Check that the negotiated version is a known MCP version
27        let known_versions = ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
28
29        match &ctx.init_result {
30            Some(init) => {
31                if !known_versions.contains(&init.protocol_version.as_str()) {
32                    results.push(CheckResult::fail(
33                        self.id(),
34                        self.name(),
35                        self.category(),
36                        Severity::Medium,
37                        desc,
38                        format!(
39                            "Server returned unrecognized protocol version: \"{}\"",
40                            init.protocol_version
41                        ),
42                    ));
43                }
44
45                // Test with a bogus version using a disposable session
46                match ctx.disposable_session().await {
47                    Ok(mut _session) => {
48                        // The disposable_session already runs initialize with our version.
49                        // For a thorough test, we would send a raw initialize with a bogus version.
50                        // This is simplified for v0.1 — we just check the primary version.
51                    }
52                    Err(_) => {
53                        // Non-critical — we still have the primary version check
54                    }
55                }
56            }
57            None => {
58                results.push(CheckResult::fail(
59                    self.id(),
60                    self.name(),
61                    self.category(),
62                    Severity::High,
63                    desc,
64                    "No initialize result available",
65                ));
66            }
67        }
68
69        if results.is_empty() {
70            results.push(CheckResult::pass(
71                self.id(),
72                self.name(),
73                self.category(),
74                desc,
75            ));
76        }
77
78        results
79    }
80}