protocol_droid/domain/mcp/client_methods.rs
1//! Which client-to-server request methods each revision defines.
2//!
3//! # Why a table and not the feature matrix
4//!
5//! The feature matrix answers "may this session use behaviour X", and a feature
6//! that switched off is still a feature: `ping` has a row because something can
7//! depend on it being refused. Three of the methods `2026-07-28` drops are not
8//! like that. `logging/setLevel` and `resources/subscribe` were *replaced* —
9//! by the per-request `io.modelcontextprotocol/logLevel` key and by
10//! `subscriptions/listen` — so the behaviour they carried survives under
11//! another name, and a feature row would say the capability is still there.
12//! What changed is the method set, and the method set is a schema fact:
13//! membership of each revision's `ClientRequest` union.
14//!
15//! A method a revision does not define is answered `-32601`, and on the
16//! `2026-07-28` HTTP transport with `404 Not Found`. Answering it as though it
17//! were merely ungated would tell a client the method exists and refused, when
18//! the revision it chose has no such method at all.
19//!
20//! # Sources
21//!
22//! Each list is the `ClientRequest` union of the vendored `schema.json` for
23//! that revision, with one correction: the `2025-03-26` union omits
24//! `resources/templates/list` although the same schema defines
25//! `ListResourceTemplatesRequest` and its result. The omission is an upstream
26//! slip, not a removal — the method exists on both sides of that revision — so
27//! the table follows the defined types rather than the union.
28//!
29//! - <https://modelcontextprotocol.io/specification/2024-11-05/changelog>
30//! - <https://modelcontextprotocol.io/specification/2025-11-25/changelog> —
31//! SEP-1686 adds `tasks/get`, `tasks/result`, `tasks/cancel`, `tasks/list`.
32//! - <https://modelcontextprotocol.io/specification/2026-07-28/changelog> —
33//! SEP-2575 removes `initialize`, `ping`, `logging/setLevel`,
34//! `resources/subscribe` and `resources/unsubscribe`, and adds
35//! `server/discover` and `subscriptions/listen`; SEP-2663 moves `tasks/*` to
36//! the `io.modelcontextprotocol/tasks` extension.
37
38use crate::domain::ProtocolVersion;
39
40/// The methods every revision through `2025-11-25` shares.
41const STATEFUL: &[&str] = &[
42 "completion/complete",
43 "initialize",
44 "logging/setLevel",
45 "ping",
46 "prompts/get",
47 "prompts/list",
48 "resources/list",
49 "resources/read",
50 "resources/subscribe",
51 "resources/templates/list",
52 "resources/unsubscribe",
53 "tools/call",
54 "tools/list",
55];
56
57/// SEP-1686's task methods, core only in `2025-11-25`.
58const TASKS: &[&str] = &["tasks/cancel", "tasks/get", "tasks/list", "tasks/result"];
59
60/// The `2026-07-28` method set, and every later draft's.
61const STATELESS: &[&str] = &[
62 "completion/complete",
63 "prompts/get",
64 "prompts/list",
65 "resources/list",
66 "resources/read",
67 "resources/templates/list",
68 "server/discover",
69 "subscriptions/listen",
70 "tools/call",
71 "tools/list",
72];
73
74/// Whether `method` is a client-to-server request `version` defines.
75///
76/// A revision newer than this build answers with the `2026-07-28` set, for the
77/// reason [`discover_defined_in`](crate::domain::discover_defined_in) gives:
78/// SEP-2575 is not something a later draft would take back.
79///
80/// # Examples
81///
82/// ```
83/// use protocol_droid::domain::{ProtocolVersion, client_method_defined_in};
84///
85/// assert!(client_method_defined_in(ProtocolVersion::V2025_11_25, "ping"));
86/// // SEP-2575 removed `ping` and replaced `logging/setLevel`.
87/// assert!(!client_method_defined_in(ProtocolVersion::V2026_07_28, "ping"));
88/// assert!(!client_method_defined_in(ProtocolVersion::V2026_07_28, "logging/setLevel"));
89/// assert!(client_method_defined_in(ProtocolVersion::V2026_07_28, "subscriptions/listen"));
90/// ```
91#[must_use]
92pub fn client_method_defined_in(version: ProtocolVersion, method: &str) -> bool {
93 match version {
94 ProtocolVersion::V2024_11_05
95 | ProtocolVersion::V2025_03_26
96 | ProtocolVersion::V2025_06_18 => STATEFUL.contains(&method),
97 ProtocolVersion::V2025_11_25 => STATEFUL.contains(&method) || TASKS.contains(&method),
98 ProtocolVersion::V2026_07_28 | ProtocolVersion::Unknown(_) => STATELESS.contains(&method),
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::client_method_defined_in;
105 use crate::domain::ProtocolVersion;
106
107 #[test]
108 fn the_handshake_exists_exactly_where_the_negotiation_says_it_does() {
109 for version in [
110 ProtocolVersion::V2024_11_05,
111 ProtocolVersion::V2025_03_26,
112 ProtocolVersion::V2025_06_18,
113 ProtocolVersion::V2025_11_25,
114 ProtocolVersion::V2026_07_28,
115 ] {
116 assert_eq!(
117 client_method_defined_in(version, "initialize"),
118 crate::domain::handshake_defined_in(version),
119 "initialize at {version}"
120 );
121 assert_eq!(
122 client_method_defined_in(version, "server/discover"),
123 crate::domain::discover_defined_in(version),
124 "server/discover at {version}"
125 );
126 }
127 }
128
129 #[test]
130 fn tasks_are_core_only_in_2025_11_25() {
131 assert!(client_method_defined_in(
132 ProtocolVersion::V2025_11_25,
133 "tasks/get"
134 ));
135 assert!(!client_method_defined_in(
136 ProtocolVersion::V2025_06_18,
137 "tasks/get"
138 ));
139 assert!(!client_method_defined_in(
140 ProtocolVersion::V2026_07_28,
141 "tasks/get"
142 ));
143 }
144
145 #[test]
146 fn the_replaced_methods_are_gone_in_2026_07_28() {
147 for method in [
148 "ping",
149 "logging/setLevel",
150 "resources/subscribe",
151 "resources/unsubscribe",
152 ] {
153 assert!(client_method_defined_in(
154 ProtocolVersion::V2025_11_25,
155 method
156 ));
157 assert!(
158 !client_method_defined_in(ProtocolVersion::V2026_07_28, method),
159 "{method}"
160 );
161 }
162 }
163
164 #[test]
165 fn an_unknown_method_is_defined_nowhere() {
166 assert!(!client_method_defined_in(
167 ProtocolVersion::V2025_11_25,
168 "unknown/method"
169 ));
170 assert!(!client_method_defined_in(
171 ProtocolVersion::V2026_07_28,
172 "unknown/method"
173 ));
174 }
175}