use std::collections::BTreeMap;
use mcp_conformance_core::message::MessageKind;
use mcp_conformance_core::trace::Direction;
use super::super::FindingSink;
use crate::context::TraceContext;
#[cfg(test)]
mod tests;
const DISCOVER: &str = "server/discover";
const INITIALIZE: &str = "initialize";
const PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
const METHOD_NOT_FOUND: i64 = -32601;
pub(in crate::checks) fn implemented(context: &TraceContext<'_>, sink: &mut FindingSink) {
let probes: BTreeMap<String, u64> = context
.messages()
.filter_map(|(event, kind, _)| match kind {
MessageKind::Request { method, id }
if *method == DISCOVER && event.direction == Direction::ClientToServer =>
{
Some((id.to_string(), event.seq))
}
_ => None,
})
.collect();
if probes.is_empty() {
return;
}
for (event, kind, _) in context.messages() {
if event.direction != Direction::ServerToClient {
continue;
}
let (id, error) = match kind {
MessageKind::Result { id: Some(id) } => (*id, None),
MessageKind::Error {
id: Some(id),
error,
} => (*id, Some(*error)),
_ => continue,
};
if !probes.contains_key(&id.to_string()) {
continue;
}
sink.examined();
if error.and_then(|error| error.get("code")?.as_i64()) == Some(METHOD_NOT_FOUND) {
sink.push(
Some(event.seq),
"`server/discover` was answered with -32601 (method not found); \
2026-07-28 servers must implement it"
.to_owned(),
);
}
}
}
pub(in crate::checks) fn dual_era_probe_first(context: &TraceContext<'_>, sink: &mut FindingSink) {
let mut first: Option<(u64, &str)> = None;
let mut legacy = false;
let mut modern = false;
for (event, kind, _) in context.messages() {
if event.direction != Direction::ClientToServer {
continue;
}
let MessageKind::Request { method, .. } = kind else {
continue;
};
if first.is_none() {
first = Some((event.seq, method));
}
legacy |= *method == INITIALIZE;
modern |= event
.message_payload()
.and_then(|payload| payload.get("params")?.get("_meta")?.get(PROTOCOL_VERSION))
.is_some();
}
let Some((seq, method)) = first else {
return;
};
if !legacy || !modern {
return; }
sink.examined();
if method != DISCOVER {
sink.push(
Some(seq),
format!(
"the client's first request is `{method}`, but this session shows both \
eras (an `initialize` request and a modern `_meta` protocol version), \
so it should have probed with `server/discover` first"
),
);
}
}