turul_mcp_protocol_2025_06_18/
initialize.rs

1//! MCP Initialize Protocol Types
2//!
3//! This module defines the types used for the MCP initialization handshake.
4
5use std::collections::HashMap;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use crate::version::McpVersion;
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}
22
23impl Implementation {
24    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
25        Self {
26            name: name.into(),
27            version: version.into(),
28            title: None,
29        }
30    }
31
32    pub fn with_title(mut self, title: impl Into<String>) -> Self {
33        self.title = Some(title.into());
34        self
35    }
36}
37
38/// Capabilities related to root listing support
39#[derive(Debug, Clone, Serialize, Deserialize, Default)]
40#[serde(rename_all = "camelCase")]
41pub struct RootsCapabilities {
42    /// Whether the client supports notifications for root list changes
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub list_changed: Option<bool>,
45}
46
47/// Capabilities related to sampling support
48#[derive(Debug, Clone, Serialize, Deserialize, Default)]
49#[serde(rename_all = "camelCase")]  
50pub struct SamplingCapabilities {
51    /// Whether the client supports sampling
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub enabled: Option<bool>,
54}
55
56/// Capabilities related to elicitation support
57#[derive(Debug, Clone, Serialize, Deserialize, Default)]
58#[serde(rename_all = "camelCase")]
59pub struct ElicitationCapabilities {
60    /// Whether the client supports elicitation
61    #[serde(skip_serializing_if = "Option::is_none")]
62    pub enabled: Option<bool>,
63}
64
65/// Capabilities that a client may support
66#[derive(Debug, Clone, Serialize, Deserialize, Default)]
67#[serde(rename_all = "camelCase")]
68pub struct ClientCapabilities {
69    /// Root directory capabilities
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub roots: Option<RootsCapabilities>,
72    /// Sampling capabilities
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub sampling: Option<SamplingCapabilities>,
75    /// Elicitation capabilities
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub elicitation: Option<ElicitationCapabilities>,
78    /// Experimental capabilities
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub experimental: Option<HashMap<String, Value>>,
81}
82
83/// Capabilities for prompts provided by the server
84#[derive(Debug, Clone, Serialize, Deserialize, Default)]
85#[serde(rename_all = "camelCase")]
86pub struct PromptsCapabilities {
87    /// Whether the server supports prompt list change notifications
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub list_changed: Option<bool>,
90}
91
92/// Capabilities for tools provided by the server
93#[derive(Debug, Clone, Serialize, Deserialize, Default)]
94#[serde(rename_all = "camelCase")]
95pub struct ToolsCapabilities {
96    /// Whether the server supports tool list change notifications
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub list_changed: Option<bool>,
99}
100
101/// Capabilities for resources provided by the server
102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103#[serde(rename_all = "camelCase")]
104pub struct ResourcesCapabilities {
105    /// Whether the server supports resource subscriptions
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub subscribe: Option<bool>,
108    /// Whether the server supports resource list change notifications
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub list_changed: Option<bool>,
111}
112
113/// Capabilities for logging provided by the server
114#[derive(Debug, Clone, Serialize, Deserialize, Default)]
115#[serde(rename_all = "camelCase")]
116pub struct LoggingCapabilities {
117    /// Whether the server supports logging
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub enabled: Option<bool>,
120    /// Supported log levels
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub levels: Option<Vec<String>>,
123}
124
125/// Capabilities for completions provided by the server
126#[derive(Debug, Clone, Serialize, Deserialize, Default)]
127#[serde(rename_all = "camelCase")]
128pub struct CompletionsCapabilities {
129    /// Whether the server supports completions
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub enabled: Option<bool>,
132}
133
134/// Capabilities that a server may support
135#[derive(Debug, Clone, Serialize, Deserialize, Default)]
136#[serde(rename_all = "camelCase")]
137pub struct ServerCapabilities {
138    /// Logging capabilities
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub logging: Option<LoggingCapabilities>,
141    /// Completion capabilities
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub completions: Option<CompletionsCapabilities>,
144    /// Prompt capabilities
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub prompts: Option<PromptsCapabilities>,
147    /// Resource capabilities
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub resources: Option<ResourcesCapabilities>,
150    /// Tool capabilities
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub tools: Option<ToolsCapabilities>,
153    /// Elicitation capabilities (server can make elicitation requests to clients)
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub elicitation: Option<ElicitationCapabilities>,
156    /// Experimental capabilities
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub experimental: Option<HashMap<String, Value>>,
159}
160
161/// Parameters for initialize request
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct InitializeRequest {
165    /// The protocol version the client wants to use
166    pub protocol_version: String,
167    /// Capabilities the client supports
168    pub capabilities: ClientCapabilities,
169    /// Information about the client implementation
170    pub client_info: Implementation,
171}
172
173impl InitializeRequest {
174    pub fn new(
175        protocol_version: McpVersion,
176        capabilities: ClientCapabilities,
177        client_info: Implementation,
178    ) -> Self {
179        Self {
180            protocol_version: protocol_version.as_str().to_string(),
181            capabilities,
182            client_info,
183        }
184    }
185
186    /// Get the protocol version as a parsed enum
187    pub fn protocol_version(&self) -> Result<McpVersion, crate::McpError> {
188        McpVersion::from_str(&self.protocol_version).ok_or_else(|| {
189            crate::McpError::VersionMismatch {
190                expected: McpVersion::CURRENT.as_str().to_string(),
191                actual: self.protocol_version.clone(),
192            }
193        })
194    }
195}
196
197/// Result payload for initialize (per MCP spec)
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct InitializeResult {
201    /// The protocol version the server supports
202    pub protocol_version: String,
203    /// Capabilities the server supports
204    pub capabilities: ServerCapabilities,
205    /// Information about the server implementation
206    pub server_info: Implementation,
207    /// Optional instructions for the client
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub instructions: Option<String>,
210}
211
212impl InitializeResult {
213    pub fn new(
214        protocol_version: McpVersion,
215        capabilities: ServerCapabilities,
216        server_info: Implementation,
217    ) -> Self {
218        Self {
219            protocol_version: protocol_version.as_str().to_string(),
220            capabilities,
221            server_info,
222            instructions: None,
223        }
224    }
225
226    pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
227        self.instructions = Some(instructions.into());
228        self
229    }
230
231    /// Get the protocol version as a parsed enum
232    pub fn protocol_version(&self) -> Result<McpVersion, crate::McpError> {
233        McpVersion::from_str(&self.protocol_version).ok_or_else(|| {
234            crate::McpError::VersionMismatch {
235                expected: McpVersion::CURRENT.as_str().to_string(),
236                actual: self.protocol_version.clone(),
237            }
238        })
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_implementation_creation() {
248        let impl_info = Implementation::new("test-client", "1.0.0")
249            .with_title("Test Client");
250
251        assert_eq!(impl_info.name, "test-client");
252        assert_eq!(impl_info.version, "1.0.0");
253        assert_eq!(impl_info.title, Some("Test Client".to_string()));
254    }
255
256    #[test]
257    fn test_initialize_request_serialization() {
258        let client_info = Implementation::new("test-client", "1.0.0");
259        let capabilities = ClientCapabilities::default();
260        let request = InitializeRequest::new(
261            McpVersion::V2025_06_18,
262            capabilities,
263            client_info,
264        );
265
266        let json = serde_json::to_string(&request).unwrap();
267        assert!(json.contains("2025-06-18"));
268        assert!(json.contains("test-client"));
269    }
270
271    #[test]
272    fn test_initialize_response_creation() {
273        let server_info = Implementation::new("test-server", "1.0.0");
274        let capabilities = ServerCapabilities::default();
275        let response = InitializeResult::new(
276            McpVersion::V2025_06_18,
277            capabilities,
278            server_info,
279        ).with_instructions("Welcome to the test server!");
280
281        assert_eq!(response.protocol_version, "2025-06-18");
282        assert!(response.instructions.is_some());
283    }
284}