Skip to main content

turul_mcp_protocol_2026_07_28/
initialize.rs

1//! Shared capability and implementation identity types for MCP 2026-07-28.
2//!
3//! 2026-07-28 is stateless (SEP-2567, SEP-2575) — there is no
4//! `initialize`/`notifications/initialized` handshake. These types
5//! ([`Implementation`], [`ClientCapabilities`], [`ServerCapabilities`])
6//! survive because they are referenced by
7//! [`crate::meta::RequestMetaObject`] (per-request negotiation) and
8//! [`crate::discover::DiscoverResult`].
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13
14/// Describes the name and version of an MCP implementation
15#[derive(Debug, Clone, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct Implementation {
18    /// Machine-readable name
19    pub name: String,
20    /// Version string (e.g., "1.0.0")
21    pub version: String,
22    /// Optional human-friendly display title
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub title: Option<String>,
25    /// Optional human-readable description of this implementation
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub description: Option<String>,
28    /// Optional URL for the implementation's website
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub website_url: Option<String>,
31    /// Optional icons for display. Most implementations do not need icons.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub icons: Option<Vec<crate::icons::Icon>>,
34}
35
36impl Implementation {
37    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
38        Self {
39            name: name.into(),
40            version: version.into(),
41            title: None,
42            description: None,
43            website_url: None,
44            icons: None,
45        }
46    }
47
48    pub fn with_title(mut self, title: impl Into<String>) -> Self {
49        self.title = Some(title.into());
50        self
51    }
52
53    pub fn with_description(mut self, description: impl Into<String>) -> Self {
54        self.description = Some(description.into());
55        self
56    }
57
58    pub fn with_website_url(mut self, url: impl Into<String>) -> Self {
59        self.website_url = Some(url.into());
60        self
61    }
62
63    pub fn with_icons(mut self, icons: Vec<crate::icons::Icon>) -> Self {
64        self.icons = Some(icons);
65        self
66    }
67}
68
69/// Capabilities related to root listing support.
70///
71/// Schema: `ClientCapabilities.roots?: {}` — an empty object; presence alone
72/// means the client supports listing roots. `notifications/roots/list_changed`
73/// was removed in this revision, so there is no `listChanged` sub-field.
74///
75/// **Deprecated** per SEP-2577 alongside the Roots feature.
76#[deprecated(
77    since = "0.4.0",
78    note = "Deprecated per SEP-2577 (2026-07-28). \
79            Replacement: pass directories or files via tool parameters, resource URIs, or server configuration. \
80            Earliest removal: first release on/after 2027-07-28."
81)]
82#[derive(Debug, Clone, Serialize, Deserialize, Default)]
83#[serde(rename_all = "camelCase")]
84pub struct RootsCapabilities {}
85
86/// Capabilities related to sampling support.
87///
88/// 2026-07-28 adds two named sub-capabilities:
89/// - `context` — client supports `includeContext` parameter (soft-deprecated)
90/// - `tools`   — client supports `tools` and `toolChoice` parameters
91///
92/// Presence of the parent `sampling` field indicates baseline sampling support;
93/// presence of a sub-field declares the specific sub-capability. Empty `{}` is valid.
94///
95/// **Deprecated** per SEP-2577 alongside the Sampling feature.
96#[deprecated(
97    since = "0.4.0",
98    note = "Deprecated per SEP-2577 (2026-07-28). \
99            Replacement: integrate directly with LLM provider APIs. \
100            Earliest removal: first release on/after 2027-07-28."
101)]
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103#[serde(rename_all = "camelCase")]
104pub struct SamplingCapabilities {
105    /// Whether the client supports context inclusion via `includeContext`.
106    /// Server MAY use `includeContext: "thisServer"`/`"allServers"` only if this
107    /// is declared.
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub context: Option<HashMap<String, Value>>,
110
111    /// Whether the client supports tool use in sampling.
112    /// Server MUST get an error if it sends `tools`/`toolChoice` without this
113    /// declared.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub tools: Option<HashMap<String, Value>>,
116
117    /// Additional forward-compatible capability data.
118    #[serde(flatten)]
119    pub extra: HashMap<String, Value>,
120}
121
122/// Capabilities related to elicitation support.
123///
124/// 2026-07-28 adds two named sub-capabilities:
125/// - `form` — client supports form-mode elicitation
126/// - `url`  — client supports URL-mode elicitation
127///
128/// Presence of the parent `elicitation` field indicates baseline elicitation
129/// support. Empty `{}` is valid (implicit form mode).
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131#[serde(rename_all = "camelCase")]
132pub struct ElicitationCapabilities {
133    /// Form-mode elicitation support.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub form: Option<HashMap<String, Value>>,
136
137    /// URL-mode elicitation support.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub url: Option<HashMap<String, Value>>,
140
141    /// Additional forward-compatible capability data.
142    #[serde(flatten)]
143    pub extra: HashMap<String, Value>,
144}
145
146/// Capabilities that a client may support.
147///
148/// 2026-07-28 shape:
149/// - `experimental?: { [k]: JSONObject }`
150/// - `roots?: {}`                — presence means client supports listing roots
151/// - `sampling?: { context?, tools? }`
152/// - `elicitation?: { form?, url? }`
153/// - `extensions?: { [k]: JSONObject }`  — reverse-DNS keyed extension capability map
154///
155/// Note: `tasks` field is NOT present — tasks moved entirely to extension
156/// in 2026-07-28 (SEP-2663). Advertise tasks support via
157/// `extensions["io.modelcontextprotocol/tasks"]`.
158#[derive(Debug, Clone, Serialize, Deserialize, Default)]
159#[serde(rename_all = "camelCase")]
160#[allow(deprecated)]
161pub struct ClientCapabilities {
162    /// Root directory capabilities. **Deprecated** per SEP-2577.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub roots: Option<RootsCapabilities>,
165    /// Sampling capabilities (client can handle sampling requests from server).
166    /// **Deprecated** per SEP-2577.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub sampling: Option<SamplingCapabilities>,
169    /// Elicitation capabilities (client can handle elicitation requests from server).
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub elicitation: Option<ElicitationCapabilities>,
172    /// Experimental capabilities.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub experimental: Option<HashMap<String, Value>>,
175    /// MCP extensions the client supports.
176    /// Keys are reverse-DNS extension identifiers (e.g.
177    /// `"io.modelcontextprotocol/oauth-client-credentials"`); values are
178    /// per-extension settings. Empty `{}` means support without settings.
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub extensions: Option<HashMap<String, Value>>,
181}
182
183/// Capabilities for prompts provided by the server
184#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185#[serde(rename_all = "camelCase")]
186pub struct PromptsCapabilities {
187    /// Whether the server supports prompt list change notifications
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub list_changed: Option<bool>,
190}
191
192/// Capabilities for tools provided by the server
193#[derive(Debug, Clone, Serialize, Deserialize, Default)]
194#[serde(rename_all = "camelCase")]
195pub struct ToolsCapabilities {
196    /// Whether the server supports tool list change notifications
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub list_changed: Option<bool>,
199}
200
201// Tasks capabilities live in the tasks extension (SEP-2663). Advertise via
202// `extensions["io.modelcontextprotocol/tasks"]` on `ServerCapabilities`.
203
204/// Capabilities for resources provided by the server
205#[derive(Debug, Clone, Serialize, Deserialize, Default)]
206#[serde(rename_all = "camelCase")]
207pub struct ResourcesCapabilities {
208    /// Whether the server supports resource subscriptions
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub subscribe: Option<bool>,
211    /// Whether the server supports resource list change notifications
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub list_changed: Option<bool>,
214}
215
216/// Capabilities for logging provided by the server.
217///
218/// Wire shape: an opaque `JSONObject` — PRESENCE means the server can send
219/// `notifications/message`; the object carries no defined keys.
220#[derive(Debug, Clone, Serialize, Deserialize, Default)]
221pub struct LoggingCapabilities {
222    /// Opaque content per the schema's `JSONObject` (usually empty).
223    #[serde(flatten)]
224    pub extra: HashMap<String, Value>,
225}
226
227/// Capabilities for completions provided by the server
228#[derive(Debug, Clone, Serialize, Deserialize, Default)]
229#[serde(rename_all = "camelCase")]
230pub struct CompletionsCapabilities {
231    /// Opaque content per the schema's `JSONObject` (usually empty) —
232    /// PRESENCE means `completion/complete` is supported.
233    #[serde(flatten)]
234    pub extra: HashMap<String, Value>,
235}
236
237/// Capabilities that a server may support.
238///
239/// 2026-07-28 shape:
240/// - `experimental?: { [k]: JSONObject }`
241/// - `logging?: JSONObject`            — opaque object; presence means server can send `notifications/message`
242/// - `completions?: JSONObject`        — opaque object; presence means `completion/complete` is supported
243/// - `prompts?: { listChanged? }`
244/// - `resources?: { subscribe?, listChanged? }`
245/// - `tools?: { listChanged? }`
246/// - `extensions?: { [k]: JSONObject }`  — reverse-DNS keyed extension capability map
247#[derive(Debug, Clone, Serialize, Deserialize, Default)]
248#[serde(rename_all = "camelCase")]
249pub struct ServerCapabilities {
250    /// Logging capabilities.
251    #[serde(skip_serializing_if = "Option::is_none")]
252    #[deprecated(
253        since = "0.4.0",
254        note = "Deprecated per SEP-2577 (2026-07-28) — the Logging capability is being \
255                phased out. Earliest removal: first release on/after 2027-07-28."
256    )]
257    pub logging: Option<LoggingCapabilities>,
258    /// Completion capabilities.
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub completions: Option<CompletionsCapabilities>,
261    /// Prompt capabilities.
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub prompts: Option<PromptsCapabilities>,
264    /// Resource capabilities.
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub resources: Option<ResourcesCapabilities>,
267    /// Tool capabilities.
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub tools: Option<ToolsCapabilities>,
270    /// Experimental capabilities.
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub experimental: Option<HashMap<String, Value>>,
273    /// MCP extensions the server supports.
274    /// Keys are reverse-DNS extension identifiers (e.g.
275    /// `"io.modelcontextprotocol/apps"`); values are per-extension settings.
276    /// Empty `{}` means support without settings.
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub extensions: Option<HashMap<String, Value>>,
279}
280
281// 2026-07-28 is stateless (SEP-2567, SEP-2575) — there is no initialize
282// handshake. Client info and capabilities travel in `RequestMetaObject` on
283// every request; server info and capabilities come from `DiscoverResult`
284// (see [`crate::discover`]).
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_implementation_creation() {
292        let impl_info = Implementation::new("test-client", "1.0.0").with_title("Test Client");
293
294        assert_eq!(impl_info.name, "test-client");
295        assert_eq!(impl_info.version, "1.0.0");
296        assert_eq!(impl_info.title, Some("Test Client".to_string()));
297    }
298
299    #[test]
300    #[allow(deprecated)]
301    fn roots_capabilities_matches_schema_empty_object() {
302        // Schema: `ClientCapabilities.roots?: {}` — no `listChanged` sub-field.
303        let v = serde_json::to_value(RootsCapabilities::default()).unwrap();
304        assert_eq!(
305            v,
306            serde_json::json!({}),
307            "RootsCapabilities must serialize as an empty object, not carry `listChanged`"
308        );
309    }
310
311    #[test]
312    #[allow(deprecated)]
313    fn roots_capabilities_ignores_unknown_fields_on_deserialize() {
314        // Forward-compat: extra keys (including the removed `listChanged`) must not
315        // reject deserialization.
316        let wire = serde_json::json!({"listChanged": true});
317        let parsed: Result<RootsCapabilities, _> = serde_json::from_value(wire);
318        assert!(parsed.is_ok());
319    }
320}