agentox_core/checks/conformance/
capability_negotiation.rs1use crate::checks::runner::{Check, CheckContext};
4use crate::checks::types::{CheckCategory, CheckResult, Severity};
5
6pub struct CapabilityNegotiation;
7
8#[async_trait::async_trait]
9impl Check for CapabilityNegotiation {
10 fn id(&self) -> &str {
11 "CONF-008"
12 }
13
14 fn name(&self) -> &str {
15 "Capability negotiation"
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 capabilities must match actually supported methods";
24 let mut results = Vec::new();
25
26 let caps = match &ctx.init_result {
27 Some(init) => &init.capabilities,
28 None => {
29 return vec![CheckResult::fail(
30 self.id(),
31 self.name(),
32 self.category(),
33 Severity::High,
34 desc,
35 "No initialize result available",
36 )];
37 }
38 };
39
40 if caps.tools.is_some() {
42 match ctx.session.list_tools().await {
43 Ok(tools) => {
44 ctx.tools = Some(tools);
45 }
46 Err(e) => {
47 results.push(CheckResult::fail(
48 self.id(),
49 self.name(),
50 self.category(),
51 Severity::Medium,
52 desc,
53 format!("Server declares tools capability but tools/list failed: {e}"),
54 ));
55 }
56 }
57 }
58
59 if caps.tools.is_none() {
61 match ctx.session.list_tools().await {
62 Ok(tools) if !tools.is_empty() => {
63 results.push(CheckResult::fail(
64 self.id(),
65 self.name(),
66 self.category(),
67 Severity::Medium,
68 desc,
69 format!(
70 "Server does not declare tools capability but tools/list returned {} tools",
71 tools.len()
72 ),
73 ));
74 }
75 _ => {} }
77 }
78
79 if results.is_empty() {
80 results.push(CheckResult::pass(
81 self.id(),
82 self.name(),
83 self.category(),
84 desc,
85 ));
86 }
87
88 results
89 }
90}