1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//! Multi-client protocol compatibility: the server is the memory backend for
//! several MCP clients (Claude Code, Pi, Codex), and those clients pin
//! different MCP protocol versions. The handshake must succeed — negotiate a
//! version and advertise the tool surface — for every real version a client
//! sends, not just the single string the rest of the suite happens to use.
mod common;
use common::{Server, DEFAULT_TIMEOUT};
/// Protocol versions the MCP spec has shipped and that real clients pin.
const CLIENT_VERSIONS: &[&str] = &["2024-11-05", "2025-03-26", "2025-06-18"];
#[test]
fn negotiates_every_protocol_version_a_real_client_sends() {
for &version in CLIENT_VERSIONS {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("proto.redb");
let mut server = Server::spawn(&db_path, &[]);
let init = server.initialize_with_version(version, DEFAULT_TIMEOUT);
// The handshake must return a usable session for this client: a
// negotiated protocolVersion and the tools capability, so the client
// can go on to list and call memory tools.
let negotiated = init.get("protocolVersion").and_then(|v| v.as_str());
assert!(
negotiated.is_some(),
"client version {version}: initialize must return a protocolVersion, got {init}"
);
assert!(
init.get("capabilities")
.and_then(|c| c.get("tools"))
.is_some(),
"client version {version}: session must advertise the tools capability, got {init}"
);
// And the memory surface must actually be reachable after that
// handshake — a successful init that exposed no tools would be useless
// to an agent.
let tools = server.tools_list(DEFAULT_TIMEOUT);
let names: Vec<&str> = tools
.iter()
.filter_map(|t| t.get("name").and_then(|n| n.as_str()))
.collect();
for must in ["remember", "search_memories", "traverse"] {
assert!(
names.contains(&must),
"client version {version}: '{must}' must be listed, got {names:?}"
);
}
}
}