Skip to main content

brainwires_mcp/
types.rs

1//! MCP Protocol Types
2//!
3//! This module provides type definitions for the Model Context Protocol.
4//! It now uses the official `rmcp` crate with compatibility aliases for
5//! backward compatibility during migration.
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10// Re-export rmcp types with compatibility aliases (native only)
11#[cfg(feature = "native")]
12pub use rmcp::model::{
13    CallToolRequestParams, CallToolResult, Content, Prompt as RmcpPrompt, ProtocolVersion,
14    Resource as RmcpResource, Tool as RmcpTool,
15};
16
17// Re-export capabilities (native only)
18#[cfg(feature = "native")]
19pub use rmcp::model::{
20    ClientCapabilities as RmcpClientCapabilities, PromptsCapability, ResourcesCapability,
21    ServerCapabilities as RmcpServerCapabilities, ToolsCapability,
22};
23
24// ===========================================================================
25// BACKWARD COMPATIBILITY ALIASES (native only - require rmcp)
26// ===========================================================================
27
28#[cfg(feature = "native")]
29/// Compatibility alias for Tool
30pub type McpTool = RmcpTool;
31
32#[cfg(feature = "native")]
33/// Compatibility alias for Resource
34pub type McpResource = RmcpResource;
35
36#[cfg(feature = "native")]
37/// Compatibility alias for Prompt
38pub type McpPrompt = RmcpPrompt;
39
40#[cfg(feature = "native")]
41/// Compatibility alias for CallToolParams
42pub type CallToolParams = CallToolRequestParams;
43
44#[cfg(feature = "native")]
45/// Compatibility alias for ServerCapabilities
46pub type ServerCapabilities = RmcpServerCapabilities;
47
48#[cfg(feature = "native")]
49/// Compatibility alias for ClientCapabilities
50pub type ClientCapabilities = RmcpClientCapabilities;
51
52// ===========================================================================
53// ADDITIONAL TYPES NOT DIRECTLY PROVIDED BY RMCP
54// ===========================================================================
55// These types are still custom as they handle JSON-RPC layer or are
56// specific to our implementation
57
58/// JSON-RPC 2.0 Request
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct JsonRpcRequest {
61    /// JSON-RPC version (always "2.0").
62    pub jsonrpc: String,
63    /// Request identifier.
64    pub id: serde_json::Value,
65    /// Method name.
66    pub method: String,
67    /// Optional parameters.
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub params: Option<Value>,
70}
71
72impl JsonRpcRequest {
73    /// Create a new JSON-RPC request.
74    /// Returns an error if params cannot be serialized to JSON.
75    pub fn new<T: Serialize>(
76        id: impl Into<Value>,
77        method: String,
78        params: Option<T>,
79    ) -> Result<Self, serde_json::Error> {
80        let params_value = match params {
81            Some(p) => Some(serde_json::to_value(p)?),
82            None => None,
83        };
84        Ok(Self {
85            jsonrpc: "2.0".to_string(),
86            id: id.into(),
87            method,
88            params: params_value,
89        })
90    }
91
92    /// Create a new JSON-RPC request, panicking if serialization fails.
93    /// Use this only when you're certain serialization cannot fail.
94    pub fn new_unchecked<T: Serialize>(
95        id: impl Into<Value>,
96        method: String,
97        params: Option<T>,
98    ) -> Self {
99        Self::new(id, method, params).expect("Failed to serialize JSON-RPC request params")
100    }
101}
102
103/// JSON-RPC 2.0 Response
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct JsonRpcResponse {
106    /// JSON-RPC version (always "2.0").
107    pub jsonrpc: String,
108    /// Response identifier matching the request.
109    pub id: serde_json::Value,
110    /// Result value on success.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub result: Option<Value>,
113    /// Error object on failure.
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub error: Option<JsonRpcError>,
116}
117
118/// JSON-RPC 2.0 Error
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct JsonRpcError {
121    /// Error code.
122    pub code: i32,
123    /// Error message.
124    pub message: String,
125    /// Optional additional data.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub data: Option<Value>,
128}
129
130/// JSON-RPC 2.0 Notification
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct JsonRpcNotification {
133    /// JSON-RPC version (always "2.0").
134    pub jsonrpc: String,
135    /// Notification method name.
136    pub method: String,
137    /// Optional parameters.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub params: Option<Value>,
140}
141
142impl JsonRpcNotification {
143    /// Create a new JSON-RPC notification (no id field).
144    /// Returns an error if params cannot be serialized to JSON.
145    pub fn new<T: Serialize>(
146        method: impl Into<String>,
147        params: Option<T>,
148    ) -> Result<Self, serde_json::Error> {
149        let params_value = match params {
150            Some(p) => Some(serde_json::to_value(p)?),
151            None => None,
152        };
153        Ok(Self {
154            jsonrpc: "2.0".to_string(),
155            method: method.into(),
156            params: params_value,
157        })
158    }
159
160    /// Create a new JSON-RPC notification, panicking if serialization fails.
161    /// Use this only when you're certain serialization cannot fail.
162    pub fn new_unchecked<T: Serialize>(method: impl Into<String>, params: Option<T>) -> Self {
163        Self::new(method, params).expect("Failed to serialize JSON-RPC notification params")
164    }
165}
166
167/// Generic JSON-RPC message that could be a response or notification
168/// Used for bidirectional MCP communication where servers can send notifications
169#[derive(Debug, Clone)]
170pub enum JsonRpcMessage {
171    /// A response to a request (has id field)
172    Response(JsonRpcResponse),
173    /// A server-initiated notification (no id field)
174    Notification(JsonRpcNotification),
175}
176
177impl JsonRpcMessage {
178    /// Check if this is a response
179    pub fn is_response(&self) -> bool {
180        matches!(self, JsonRpcMessage::Response(_))
181    }
182
183    /// Check if this is a notification
184    pub fn is_notification(&self) -> bool {
185        matches!(self, JsonRpcMessage::Notification(_))
186    }
187
188    /// Try to get the response if this is one
189    pub fn as_response(self) -> Option<JsonRpcResponse> {
190        match self {
191            JsonRpcMessage::Response(r) => Some(r),
192            _ => None,
193        }
194    }
195
196    /// Try to get the notification if this is one
197    pub fn as_notification(self) -> Option<JsonRpcNotification> {
198        match self {
199            JsonRpcMessage::Notification(n) => Some(n),
200            _ => None,
201        }
202    }
203}
204
205// ===========================================================================
206// MCP PROGRESS NOTIFICATION TYPES
207// ===========================================================================
208
209/// Progress notification parameters from MCP server
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct ProgressParams {
212    /// Token identifying which request this progress is for
213    #[serde(rename = "progressToken")]
214    pub progress_token: String,
215    /// Current progress value
216    pub progress: f64,
217    /// Total expected value (for calculating percentage)
218    pub total: Option<f64>,
219    /// Human-readable progress message
220    pub message: Option<String>,
221}
222
223/// Parsed MCP notification types
224#[derive(Debug, Clone)]
225pub enum McpNotification {
226    /// Progress update for a long-running operation
227    Progress(ProgressParams),
228    /// Unknown/unhandled notification type
229    Unknown {
230        /// The notification method name.
231        method: String,
232        /// Optional notification parameters.
233        params: Option<Value>,
234    },
235}
236
237impl McpNotification {
238    /// Parse a JsonRpcNotification into a typed McpNotification
239    pub fn from_notification(notif: &JsonRpcNotification) -> Self {
240        match notif.method.as_str() {
241            "notifications/progress" => {
242                if let Some(ref params) = notif.params
243                    && let Ok(progress) = serde_json::from_value::<ProgressParams>(params.clone())
244                {
245                    return McpNotification::Progress(progress);
246                }
247                McpNotification::Unknown {
248                    method: notif.method.clone(),
249                    params: notif.params.clone(),
250                }
251            }
252            _ => McpNotification::Unknown {
253                method: notif.method.clone(),
254                params: notif.params.clone(),
255            },
256        }
257    }
258}
259
260// ===========================================================================
261// MCP INITIALIZATION TYPES
262// ===========================================================================
263
264// ===========================================================================
265// MCP TYPES (require rmcp - native only)
266// ===========================================================================
267
268#[cfg(feature = "native")]
269/// MCP Initialize Request Parameters
270#[derive(Debug, Clone, Serialize, Deserialize)]
271pub struct InitializeParams {
272    /// Protocol version string.
273    #[serde(rename = "protocolVersion")]
274    pub protocol_version: String,
275    /// Client capabilities.
276    pub capabilities: ClientCapabilities,
277    /// Client identification info.
278    #[serde(rename = "clientInfo")]
279    pub client_info: ClientInfo,
280}
281
282/// MCP client identification.
283#[cfg(feature = "native")]
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ClientInfo {
286    /// Client name.
287    pub name: String,
288    /// Client version.
289    pub version: String,
290}
291
292#[cfg(feature = "native")]
293/// MCP Initialize Result
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct InitializeResult {
296    /// Protocol version string.
297    #[serde(rename = "protocolVersion")]
298    pub protocol_version: String,
299    /// Server capabilities.
300    pub capabilities: ServerCapabilities,
301    /// Server identification info.
302    #[serde(rename = "serverInfo")]
303    pub server_info: ServerInfo,
304}
305
306/// MCP server identification.
307#[cfg(feature = "native")]
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub struct ServerInfo {
310    /// Server name.
311    pub name: String,
312    /// Server version.
313    pub version: String,
314}
315
316#[cfg(feature = "native")]
317/// Tools List Response
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct ListToolsResult {
320    /// List of available tools.
321    pub tools: Vec<McpTool>,
322}
323
324#[cfg(feature = "native")]
325/// Resources List Response
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct ListResourcesResult {
328    /// List of available resources.
329    pub resources: Vec<McpResource>,
330}
331
332#[cfg(feature = "native")]
333/// Prompts List Response
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct ListPromptsResult {
336    /// List of available prompts.
337    pub prompts: Vec<McpPrompt>,
338}
339
340#[cfg(feature = "native")]
341/// Resource Read Request
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ReadResourceParams {
344    /// Resource URI to read.
345    pub uri: String,
346}
347
348#[cfg(feature = "native")]
349/// Resource Read Result
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct ReadResourceResult {
352    /// Resource contents.
353    pub contents: Vec<ResourceContent>,
354}
355
356/// Content of a resource (text or binary blob).
357#[cfg(feature = "native")]
358#[derive(Debug, Clone, Serialize, Deserialize)]
359#[serde(tag = "type", rename_all = "lowercase")]
360pub enum ResourceContent {
361    /// Text content.
362    Text {
363        /// Resource URI.
364        uri: String,
365        /// MIME type.
366        mime_type: Option<String>,
367        /// Text content.
368        text: String,
369    },
370    /// Binary blob content (base64-encoded).
371    Blob {
372        /// Resource URI.
373        uri: String,
374        /// MIME type.
375        mime_type: Option<String>,
376        /// Base64-encoded blob data.
377        blob: String,
378    },
379}
380
381#[cfg(feature = "native")]
382/// Prompt Get Request
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct GetPromptParams {
385    /// Prompt name.
386    pub name: String,
387    /// Optional arguments for the prompt.
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub arguments: Option<Value>,
390}
391
392#[cfg(feature = "native")]
393/// Prompt Get Result
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct GetPromptResult {
396    /// Prompt description.
397    pub description: String,
398    /// Prompt messages.
399    pub messages: Vec<PromptMessage>,
400}
401
402/// A message within a prompt.
403#[cfg(feature = "native")]
404#[derive(Debug, Clone, Serialize, Deserialize)]
405pub struct PromptMessage {
406    /// Message role (e.g., "user", "assistant").
407    pub role: String,
408    /// Message content.
409    pub content: PromptContent,
410}
411
412/// Content type within a prompt message.
413#[cfg(feature = "native")]
414#[derive(Debug, Clone, Serialize, Deserialize)]
415#[serde(tag = "type", rename_all = "lowercase")]
416pub enum PromptContent {
417    /// Text content.
418    Text {
419        /// The text.
420        text: String,
421    },
422    /// Image content.
423    Image {
424        /// Base64-encoded image data.
425        data: String,
426        /// MIME type of the image.
427        mime_type: String,
428    },
429    /// Embedded resource content.
430    Resource {
431        /// The resource.
432        resource: McpResource,
433    },
434}
435
436#[cfg(feature = "native")]
437/// Prompt Argument Definition
438#[derive(Debug, Clone, Serialize, Deserialize)]
439pub struct PromptArgument {
440    /// Argument name.
441    pub name: String,
442    /// Argument description.
443    pub description: String,
444    /// Whether the argument is required.
445    pub required: bool,
446}
447
448/// Content type within a tool result.
449#[cfg(feature = "native")]
450#[derive(Debug, Clone, Serialize, Deserialize)]
451#[serde(tag = "type", rename_all = "lowercase")]
452pub enum ToolResultContent {
453    /// Text result.
454    Text {
455        /// The text.
456        text: String,
457    },
458    /// Image result.
459    Image {
460        /// Base64-encoded image data.
461        data: String,
462        /// MIME type of the image.
463        mime_type: String,
464    },
465    /// Resource result.
466    Resource {
467        /// The resource.
468        resource: McpResource,
469    },
470}
471
472// ===========================================================================
473// TESTS
474// ===========================================================================
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use serde_json::json;
480
481    #[test]
482    fn test_json_rpc_request_new() {
483        let request =
484            JsonRpcRequest::new(1, "test_method".to_string(), Some(json!({"key": "value"})))
485                .unwrap();
486
487        assert_eq!(request.jsonrpc, "2.0");
488        assert_eq!(request.id, json!(1));
489        assert_eq!(request.method, "test_method");
490        assert!(request.params.is_some());
491    }
492
493    #[test]
494    fn test_json_rpc_request_serialization() {
495        let request = JsonRpcRequest::new(1, "test".to_string(), None::<()>).unwrap();
496        let json = serde_json::to_string(&request).unwrap();
497
498        assert!(json.contains("jsonrpc"));
499        assert!(json.contains("2.0"));
500        assert!(json.contains("test"));
501    }
502
503    #[test]
504    fn test_json_rpc_response_success() {
505        let response = JsonRpcResponse {
506            jsonrpc: "2.0".to_string(),
507            id: json!(1),
508            result: Some(json!({"status": "ok"})),
509            error: None,
510        };
511
512        assert!(response.result.is_some());
513        assert!(response.error.is_none());
514    }
515
516    #[test]
517    fn test_json_rpc_response_error() {
518        let response = JsonRpcResponse {
519            jsonrpc: "2.0".to_string(),
520            id: json!(1),
521            result: None,
522            error: Some(JsonRpcError {
523                code: -32600,
524                message: "Invalid Request".to_string(),
525                data: None,
526            }),
527        };
528
529        assert!(response.result.is_none());
530        assert!(response.error.is_some());
531    }
532
533    #[cfg(feature = "native")]
534    #[test]
535    fn test_type_aliases_work() {
536        // Test that our type aliases are properly set up
537        let _tool: McpTool;
538        let _resource: McpResource;
539        let _prompt: McpPrompt;
540        // If this compiles, the aliases are working
541    }
542}