Skip to main content

turul_mcp_protocol_2025_11_25/
initialize.rs

1//! MCP Initialize Protocol Types
2//!
3//! This module defines the types used for the MCP initialization handshake.
4
5use crate::version::McpVersion;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10/// Describes the name and version of an MCP implementation
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct Implementation {
14    /// Machine-readable name
15    pub name: String,
16    /// Version string (e.g., "1.0.0")
17    pub version: String,
18    /// Optional human-friendly display title
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub title: Option<String>,
21    /// Optional human-readable description of this implementation
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub description: Option<String>,
24    /// Optional URL for the implementation's website
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub website_url: Option<String>,
27    /// Optional icons for display. Most implementations do not need icons.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub icons: Option<Vec<crate::icons::Icon>>,
30}
31
32impl Implementation {
33    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
34        Self {
35            name: name.into(),
36            version: version.into(),
37            title: None,
38            description: None,
39            website_url: None,
40            icons: None,
41        }
42    }
43
44    pub fn with_title(mut self, title: impl Into<String>) -> Self {
45        self.title = Some(title.into());
46        self
47    }
48
49    pub fn with_description(mut self, description: impl Into<String>) -> Self {
50        self.description = Some(description.into());
51        self
52    }
53
54    pub fn with_website_url(mut self, url: impl Into<String>) -> Self {
55        self.website_url = Some(url.into());
56        self
57    }
58
59    pub fn with_icons(mut self, icons: Vec<crate::icons::Icon>) -> Self {
60        self.icons = Some(icons);
61        self
62    }
63}
64
65/// Capabilities related to root listing support
66#[derive(Debug, Clone, Serialize, Deserialize, Default)]
67#[serde(rename_all = "camelCase")]
68pub struct RootsCapabilities {
69    /// Whether the client supports notifications for root list changes
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub list_changed: Option<bool>,
72}
73
74/// Capabilities related to sampling support (per MCP 2025-11-25)
75///
76/// Presence of this field indicates sampling support. Empty `{}` is valid.
77#[derive(Debug, Clone, Serialize, Deserialize, Default)]
78#[serde(rename_all = "camelCase")]
79pub struct SamplingCapabilities {
80    /// Additional opaque capability data
81    #[serde(flatten)]
82    pub extra: HashMap<String, Value>,
83}
84
85/// Capabilities related to elicitation support (per MCP 2025-11-25)
86///
87/// Presence of this field indicates elicitation support. Empty `{}` is valid.
88#[derive(Debug, Clone, Serialize, Deserialize, Default)]
89#[serde(rename_all = "camelCase")]
90pub struct ElicitationCapabilities {
91    /// Additional opaque capability data
92    #[serde(flatten)]
93    pub extra: HashMap<String, Value>,
94}
95
96/// Capabilities that a client may support (per MCP 2025-11-25)
97#[derive(Debug, Clone, Serialize, Deserialize, Default)]
98#[serde(rename_all = "camelCase")]
99pub struct ClientCapabilities {
100    /// Root directory capabilities
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub roots: Option<RootsCapabilities>,
103    /// Sampling capabilities (client can handle sampling requests from server)
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub sampling: Option<SamplingCapabilities>,
106    /// Elicitation capabilities (client can handle elicitation requests from server)
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub elicitation: Option<ElicitationCapabilities>,
109    /// Task capabilities (client supports task-based execution)
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub tasks: Option<TasksCapabilities>,
112    /// Experimental capabilities
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub experimental: Option<HashMap<String, Value>>,
115}
116
117/// Capabilities for prompts provided by the server
118#[derive(Debug, Clone, Serialize, Deserialize, Default)]
119#[serde(rename_all = "camelCase")]
120pub struct PromptsCapabilities {
121    /// Whether the server supports prompt list change notifications
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub list_changed: Option<bool>,
124}
125
126/// Capabilities for tools provided by the server
127#[derive(Debug, Clone, Serialize, Deserialize, Default)]
128#[serde(rename_all = "camelCase")]
129pub struct ToolsCapabilities {
130    /// Whether the server supports tool list change notifications
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub list_changed: Option<bool>,
133}
134
135/// Capabilities for tasks (per MCP 2025-11-25)
136///
137/// Structured capability advertisement for task operations.
138/// Presence of a sub-field (e.g., `list: Some(...)`) signals that the capability is supported.
139#[derive(Debug, Clone, Serialize, Deserialize, Default)]
140#[serde(rename_all = "camelCase")]
141pub struct TasksCapabilities {
142    /// Supports tasks/list operation
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub list: Option<TasksListCapabilities>,
145    /// Supports tasks/cancel operation
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub cancel: Option<TasksCancelCapabilities>,
148    /// Supports task-augmented requests
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub requests: Option<TasksRequestCapabilities>,
151    /// Forward-compatible extension fields
152    #[serde(flatten)]
153    pub extra: HashMap<String, Value>,
154}
155
156/// Signals support for tasks/list — presence means supported, extensible via extra
157#[derive(Debug, Clone, Serialize, Deserialize, Default)]
158#[serde(rename_all = "camelCase")]
159pub struct TasksListCapabilities {
160    /// Forward-compatible extension fields
161    #[serde(flatten)]
162    pub extra: HashMap<String, Value>,
163}
164
165/// Signals support for tasks/cancel — presence means supported, extensible via extra
166#[derive(Debug, Clone, Serialize, Deserialize, Default)]
167#[serde(rename_all = "camelCase")]
168pub struct TasksCancelCapabilities {
169    /// Forward-compatible extension fields
170    #[serde(flatten)]
171    pub extra: HashMap<String, Value>,
172}
173
174/// Describes which request types support task augmentation
175#[derive(Debug, Clone, Serialize, Deserialize, Default)]
176#[serde(rename_all = "camelCase")]
177pub struct TasksRequestCapabilities {
178    /// Tool request task capabilities
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub tools: Option<TasksToolCapabilities>,
181    /// Forward-compatible extension fields
182    #[serde(flatten)]
183    pub extra: HashMap<String, Value>,
184}
185
186/// Tool-level task capabilities
187#[derive(Debug, Clone, Serialize, Deserialize, Default)]
188#[serde(rename_all = "camelCase")]
189pub struct TasksToolCapabilities {
190    /// Supports task-augmented tools/call
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub call: Option<TasksToolCallCapabilities>,
193    /// Forward-compatible extension fields
194    #[serde(flatten)]
195    pub extra: HashMap<String, Value>,
196}
197
198/// Signals support for task-augmented tools/call — presence means supported
199#[derive(Debug, Clone, Serialize, Deserialize, Default)]
200#[serde(rename_all = "camelCase")]
201pub struct TasksToolCallCapabilities {
202    /// Forward-compatible extension fields
203    #[serde(flatten)]
204    pub extra: HashMap<String, Value>,
205}
206
207/// Capabilities for resources provided by the server
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
209#[serde(rename_all = "camelCase")]
210pub struct ResourcesCapabilities {
211    /// Whether the server supports resource subscriptions
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub subscribe: Option<bool>,
214    /// Whether the server supports resource list change notifications
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub list_changed: Option<bool>,
217}
218
219/// Capabilities for logging provided by the server
220#[derive(Debug, Clone, Serialize, Deserialize, Default)]
221#[serde(rename_all = "camelCase")]
222pub struct LoggingCapabilities {
223    /// Whether the server supports logging
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub enabled: Option<bool>,
226    /// Supported log levels
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub levels: Option<Vec<String>>,
229}
230
231/// Capabilities for completions provided by the server
232#[derive(Debug, Clone, Serialize, Deserialize, Default)]
233#[serde(rename_all = "camelCase")]
234pub struct CompletionsCapabilities {
235    /// Whether the server supports completions
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub enabled: Option<bool>,
238}
239
240/// Capabilities that a server may support
241#[derive(Debug, Clone, Serialize, Deserialize, Default)]
242#[serde(rename_all = "camelCase")]
243pub struct ServerCapabilities {
244    /// Logging capabilities
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub logging: Option<LoggingCapabilities>,
247    /// Completion capabilities
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub completions: Option<CompletionsCapabilities>,
250    /// Prompt capabilities
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub prompts: Option<PromptsCapabilities>,
253    /// Resource capabilities
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub resources: Option<ResourcesCapabilities>,
256    /// Tool capabilities
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub tools: Option<ToolsCapabilities>,
259    /// Task capabilities
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub tasks: Option<TasksCapabilities>,
262    /// Experimental capabilities
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub experimental: Option<HashMap<String, Value>>,
265}
266
267/// Parameters for initialize request
268#[derive(Debug, Clone, Serialize, Deserialize)]
269#[serde(rename_all = "camelCase")]
270pub struct InitializeRequest {
271    /// The protocol version the client wants to use
272    pub protocol_version: String,
273    /// Capabilities the client supports
274    pub capabilities: ClientCapabilities,
275    /// Information about the client implementation
276    pub client_info: Implementation,
277}
278
279impl InitializeRequest {
280    pub fn new(
281        protocol_version: McpVersion,
282        capabilities: ClientCapabilities,
283        client_info: Implementation,
284    ) -> Self {
285        Self {
286            protocol_version: protocol_version.as_str().to_string(),
287            capabilities,
288            client_info,
289        }
290    }
291
292    /// Get the protocol version as a parsed enum
293    pub fn protocol_version(&self) -> Result<McpVersion, crate::McpError> {
294        self.protocol_version
295            .parse::<McpVersion>()
296            .map_err(|_| crate::McpError::VersionMismatch {
297                expected: McpVersion::CURRENT.as_str().to_string(),
298                actual: self.protocol_version.clone(),
299            })
300    }
301}
302
303/// Result payload for initialize (per MCP spec)
304#[derive(Debug, Clone, Serialize, Deserialize)]
305#[serde(rename_all = "camelCase")]
306pub struct InitializeResult {
307    /// The protocol version the server supports
308    pub protocol_version: String,
309    /// Capabilities the server supports
310    pub capabilities: ServerCapabilities,
311    /// Information about the server implementation
312    pub server_info: Implementation,
313    /// Optional instructions for the client
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub instructions: Option<String>,
316}
317
318impl InitializeResult {
319    pub fn new(
320        protocol_version: McpVersion,
321        capabilities: ServerCapabilities,
322        server_info: Implementation,
323    ) -> Self {
324        Self {
325            protocol_version: protocol_version.as_str().to_string(),
326            capabilities,
327            server_info,
328            instructions: None,
329        }
330    }
331
332    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
333        self.instructions = Some(instructions.into());
334        self
335    }
336
337    /// Get the protocol version as a parsed enum
338    pub fn protocol_version(&self) -> Result<McpVersion, crate::McpError> {
339        self.protocol_version
340            .parse::<McpVersion>()
341            .map_err(|_| crate::McpError::VersionMismatch {
342                expected: McpVersion::CURRENT.as_str().to_string(),
343                actual: self.protocol_version.clone(),
344            })
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_implementation_creation() {
354        let impl_info = Implementation::new("test-client", "1.0.0").with_title("Test Client");
355
356        assert_eq!(impl_info.name, "test-client");
357        assert_eq!(impl_info.version, "1.0.0");
358        assert_eq!(impl_info.title, Some("Test Client".to_string()));
359    }
360
361    #[test]
362    fn test_initialize_request_serialization() {
363        let client_info = Implementation::new("test-client", "1.0.0");
364        let capabilities = ClientCapabilities::default();
365        let request = InitializeRequest::new(McpVersion::V2025_06_18, capabilities, client_info);
366
367        let json = serde_json::to_string(&request).unwrap();
368        assert!(json.contains("2025-06-18"));
369        assert!(json.contains("test-client"));
370    }
371
372    #[test]
373    fn test_initialize_response_creation() {
374        let server_info = Implementation::new("test-server", "1.0.0");
375        let capabilities = ServerCapabilities::default();
376        let response = InitializeResult::new(McpVersion::V2025_06_18, capabilities, server_info)
377            .with_instructions("Welcome to the test server!");
378
379        assert_eq!(response.protocol_version, "2025-06-18");
380        assert!(response.instructions.is_some());
381    }
382}