Skip to main content

liteforge/mcp/
types.rs

1//! MCP (Model Context Protocol) message types.
2//!
3//! This module defines the core message types for the MCP protocol,
4//! which uses JSON-RPC 2.0 for communication.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10/// JSON-RPC version constant.
11pub const JSONRPC_VERSION: &str = "2.0";
12
13/// MCP protocol version.
14pub const MCP_VERSION: &str = "2024-11-05";
15
16/// A JSON-RPC request ID.
17#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[serde(untagged)]
19pub enum RequestId {
20    /// Numeric ID.
21    Number(i64),
22    /// String ID.
23    String(String),
24}
25
26impl From<i64> for RequestId {
27    fn from(n: i64) -> Self {
28        Self::Number(n)
29    }
30}
31
32impl From<String> for RequestId {
33    fn from(s: String) -> Self {
34        Self::String(s)
35    }
36}
37
38impl From<&str> for RequestId {
39    fn from(s: &str) -> Self {
40        Self::String(s.to_string())
41    }
42}
43
44/// A JSON-RPC request message.
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct JsonRpcRequest {
47    /// JSON-RPC version (always "2.0").
48    pub jsonrpc: String,
49    /// Request ID.
50    pub id: RequestId,
51    /// Method name.
52    pub method: String,
53    /// Method parameters.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub params: Option<Value>,
56}
57
58impl JsonRpcRequest {
59    /// Create a new JSON-RPC request.
60    pub fn new(id: impl Into<RequestId>, method: impl Into<String>) -> Self {
61        Self {
62            jsonrpc: JSONRPC_VERSION.to_string(),
63            id: id.into(),
64            method: method.into(),
65            params: None,
66        }
67    }
68
69    /// Set the request parameters.
70    pub fn with_params(mut self, params: Value) -> Self {
71        self.params = Some(params);
72        self
73    }
74}
75
76/// A JSON-RPC response message.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct JsonRpcResponse {
79    /// JSON-RPC version (always "2.0").
80    pub jsonrpc: String,
81    /// Request ID this is responding to.
82    pub id: RequestId,
83    /// Result (mutually exclusive with error).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub result: Option<Value>,
86    /// Error (mutually exclusive with result).
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub error: Option<JsonRpcError>,
89}
90
91impl JsonRpcResponse {
92    /// Create a success response.
93    pub fn success(id: impl Into<RequestId>, result: Value) -> Self {
94        Self {
95            jsonrpc: JSONRPC_VERSION.to_string(),
96            id: id.into(),
97            result: Some(result),
98            error: None,
99        }
100    }
101
102    /// Create an error response.
103    pub fn error(id: impl Into<RequestId>, error: JsonRpcError) -> Self {
104        Self {
105            jsonrpc: JSONRPC_VERSION.to_string(),
106            id: id.into(),
107            result: None,
108            error: Some(error),
109        }
110    }
111
112    /// Check if this is an error response.
113    pub fn is_error(&self) -> bool {
114        self.error.is_some()
115    }
116}
117
118/// A JSON-RPC error object.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct JsonRpcError {
121    /// Error code.
122    pub code: i32,
123    /// Error message.
124    pub message: String,
125    /// Additional error data.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub data: Option<Value>,
128}
129
130impl JsonRpcError {
131    /// Create a new error.
132    pub fn new(code: i32, message: impl Into<String>) -> Self {
133        Self {
134            code,
135            message: message.into(),
136            data: None,
137        }
138    }
139
140    /// Parse error (-32700).
141    pub fn parse_error(message: impl Into<String>) -> Self {
142        Self::new(-32700, message)
143    }
144
145    /// Invalid request (-32600).
146    pub fn invalid_request(message: impl Into<String>) -> Self {
147        Self::new(-32600, message)
148    }
149
150    /// Method not found (-32601).
151    pub fn method_not_found(message: impl Into<String>) -> Self {
152        Self::new(-32601, message)
153    }
154
155    /// Invalid params (-32602).
156    pub fn invalid_params(message: impl Into<String>) -> Self {
157        Self::new(-32602, message)
158    }
159
160    /// Internal error (-32603).
161    pub fn internal_error(message: impl Into<String>) -> Self {
162        Self::new(-32603, message)
163    }
164}
165
166impl std::fmt::Display for JsonRpcError {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(f, "[{}] {}", self.code, self.message)
169    }
170}
171
172impl std::error::Error for JsonRpcError {}
173
174/// A JSON-RPC notification (request without ID).
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct JsonRpcNotification {
177    /// JSON-RPC version (always "2.0").
178    pub jsonrpc: String,
179    /// Method name.
180    pub method: String,
181    /// Method parameters.
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub params: Option<Value>,
184}
185
186impl JsonRpcNotification {
187    /// Create a new notification.
188    pub fn new(method: impl Into<String>) -> Self {
189        Self {
190            jsonrpc: JSONRPC_VERSION.to_string(),
191            method: method.into(),
192            params: None,
193        }
194    }
195
196    /// Set the notification parameters.
197    pub fn with_params(mut self, params: Value) -> Self {
198        self.params = Some(params);
199        self
200    }
201}
202
203// ============================================================================
204// MCP Primitives
205// ============================================================================
206
207/// An MCP tool definition.
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct McpTool {
210    /// Tool name.
211    pub name: String,
212    /// Tool description.
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub description: Option<String>,
215    /// JSON Schema for tool parameters.
216    #[serde(rename = "inputSchema")]
217    pub input_schema: Value,
218}
219
220/// An MCP resource definition.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct McpResource {
223    /// Resource URI.
224    pub uri: String,
225    /// Resource name.
226    pub name: String,
227    /// Resource description.
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub description: Option<String>,
230    /// MIME type.
231    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
232    pub mime_type: Option<String>,
233}
234
235/// An MCP resource template.
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct McpResourceTemplate {
238    /// URI template.
239    #[serde(rename = "uriTemplate")]
240    pub uri_template: String,
241    /// Template name.
242    pub name: String,
243    /// Template description.
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub description: Option<String>,
246    /// MIME type.
247    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
248    pub mime_type: Option<String>,
249}
250
251/// An MCP prompt definition.
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct McpPrompt {
254    /// Prompt name.
255    pub name: String,
256    /// Prompt description.
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub description: Option<String>,
259    /// Prompt arguments.
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub arguments: Option<Vec<McpPromptArgument>>,
262}
263
264/// An argument for an MCP prompt.
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct McpPromptArgument {
267    /// Argument name.
268    pub name: String,
269    /// Argument description.
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub description: Option<String>,
272    /// Whether the argument is required.
273    #[serde(skip_serializing_if = "Option::is_none")]
274    pub required: Option<bool>,
275}
276
277// ============================================================================
278// MCP Messages
279// ============================================================================
280
281/// Server capabilities reported during initialization.
282#[derive(Debug, Clone, Default, Serialize, Deserialize)]
283pub struct ServerCapabilities {
284    /// Tool capabilities.
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub tools: Option<ToolCapabilities>,
287    /// Resource capabilities.
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub resources: Option<ResourceCapabilities>,
290    /// Prompt capabilities.
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub prompts: Option<PromptCapabilities>,
293    /// Logging capabilities.
294    #[serde(skip_serializing_if = "Option::is_none")]
295    pub logging: Option<LoggingCapabilities>,
296}
297
298/// Tool-related capabilities.
299#[derive(Debug, Clone, Default, Serialize, Deserialize)]
300pub struct ToolCapabilities {
301    /// Whether the server supports listing tools.
302    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
303    pub list_changed: Option<bool>,
304}
305
306/// Resource-related capabilities.
307#[derive(Debug, Clone, Default, Serialize, Deserialize)]
308pub struct ResourceCapabilities {
309    /// Whether the server supports subscribing to resources.
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub subscribe: Option<bool>,
312    /// Whether the server supports listing resources.
313    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
314    pub list_changed: Option<bool>,
315}
316
317/// Prompt-related capabilities.
318#[derive(Debug, Clone, Default, Serialize, Deserialize)]
319pub struct PromptCapabilities {
320    /// Whether the server supports listing prompts.
321    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
322    pub list_changed: Option<bool>,
323}
324
325/// Logging capabilities.
326#[derive(Debug, Clone, Default, Serialize, Deserialize)]
327pub struct LoggingCapabilities {}
328
329/// Client capabilities sent during initialization.
330#[derive(Debug, Clone, Default, Serialize, Deserialize)]
331pub struct ClientCapabilities {
332    /// Root capabilities.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub roots: Option<RootCapabilities>,
335    /// Sampling capabilities.
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub sampling: Option<SamplingCapabilities>,
338}
339
340/// Root capabilities.
341#[derive(Debug, Clone, Default, Serialize, Deserialize)]
342pub struct RootCapabilities {
343    /// Whether the client supports listing roots.
344    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
345    pub list_changed: Option<bool>,
346}
347
348/// Sampling capabilities.
349#[derive(Debug, Clone, Default, Serialize, Deserialize)]
350pub struct SamplingCapabilities {}
351
352/// Initialize request parameters.
353#[derive(Debug, Clone, Serialize, Deserialize)]
354pub struct InitializeParams {
355    /// Protocol version.
356    #[serde(rename = "protocolVersion")]
357    pub protocol_version: String,
358    /// Client capabilities.
359    pub capabilities: ClientCapabilities,
360    /// Client info.
361    #[serde(rename = "clientInfo")]
362    pub client_info: ClientInfo,
363}
364
365impl Default for InitializeParams {
366    fn default() -> Self {
367        Self {
368            protocol_version: MCP_VERSION.to_string(),
369            capabilities: ClientCapabilities::default(),
370            client_info: ClientInfo::default(),
371        }
372    }
373}
374
375/// Client information.
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct ClientInfo {
378    /// Client name.
379    pub name: String,
380    /// Client version.
381    pub version: String,
382}
383
384impl Default for ClientInfo {
385    fn default() -> Self {
386        Self {
387            name: "liteforge".to_string(),
388            version: env!("CARGO_PKG_VERSION").to_string(),
389        }
390    }
391}
392
393/// Initialize response result.
394#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct InitializeResult {
396    /// Protocol version.
397    #[serde(rename = "protocolVersion")]
398    pub protocol_version: String,
399    /// Server capabilities.
400    pub capabilities: ServerCapabilities,
401    /// Server info.
402    #[serde(rename = "serverInfo")]
403    pub server_info: ServerInfo,
404}
405
406/// Server information.
407#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct ServerInfo {
409    /// Server name.
410    pub name: String,
411    /// Server version.
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub version: Option<String>,
414}
415
416/// Tool call request parameters.
417#[derive(Debug, Clone, Serialize, Deserialize)]
418pub struct CallToolParams {
419    /// Tool name.
420    pub name: String,
421    /// Tool arguments.
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub arguments: Option<HashMap<String, Value>>,
424}
425
426/// Tool call result.
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct CallToolResult {
429    /// Result content.
430    pub content: Vec<ToolResultContent>,
431    /// Whether the call resulted in an error.
432    #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
433    pub is_error: Option<bool>,
434}
435
436/// Content in a tool result.
437#[derive(Debug, Clone, Serialize, Deserialize)]
438#[serde(tag = "type")]
439pub enum ToolResultContent {
440    /// Text content.
441    #[serde(rename = "text")]
442    Text { text: String },
443    /// Image content.
444    #[serde(rename = "image")]
445    Image { data: String, mime_type: String },
446    /// Resource content.
447    #[serde(rename = "resource")]
448    Resource { resource: McpResource, text: String },
449}
450
451/// List tools result.
452#[derive(Debug, Clone, Serialize, Deserialize)]
453pub struct ListToolsResult {
454    /// Available tools.
455    pub tools: Vec<McpTool>,
456}
457
458/// List resources result.
459#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct ListResourcesResult {
461    /// Available resources.
462    pub resources: Vec<McpResource>,
463}
464
465/// List prompts result.
466#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct ListPromptsResult {
468    /// Available prompts.
469    pub prompts: Vec<McpPrompt>,
470}
471
472/// Read resource result.
473#[derive(Debug, Clone, Serialize, Deserialize)]
474pub struct ReadResourceResult {
475    /// Resource contents.
476    pub contents: Vec<ResourceContent>,
477}
478
479/// Resource content.
480#[derive(Debug, Clone, Serialize, Deserialize)]
481pub struct ResourceContent {
482    /// Resource URI.
483    pub uri: String,
484    /// MIME type.
485    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
486    pub mime_type: Option<String>,
487    /// Text content.
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub text: Option<String>,
490    /// Binary content (base64 encoded).
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub blob: Option<String>,
493}
494
495/// Get prompt result.
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct GetPromptResult {
498    /// Prompt description.
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub description: Option<String>,
501    /// Prompt messages.
502    pub messages: Vec<PromptMessage>,
503}
504
505/// A message in a prompt.
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct PromptMessage {
508    /// Message role.
509    pub role: String,
510    /// Message content.
511    pub content: PromptContent,
512}
513
514/// Content in a prompt message.
515#[derive(Debug, Clone, Serialize, Deserialize)]
516#[serde(tag = "type")]
517pub enum PromptContent {
518    /// Text content.
519    #[serde(rename = "text")]
520    Text { text: String },
521    /// Image content.
522    #[serde(rename = "image")]
523    Image { data: String, mime_type: String },
524    /// Resource content.
525    #[serde(rename = "resource")]
526    Resource { resource: McpResource },
527}
528
529#[cfg(test)]
530mod tests {
531    use super::*;
532    use serde_json::json;
533
534    #[test]
535    fn test_request_id_from() {
536        let id: RequestId = 123.into();
537        assert_eq!(id, RequestId::Number(123));
538
539        let id: RequestId = "abc".into();
540        assert_eq!(id, RequestId::String("abc".to_string()));
541    }
542
543    #[test]
544    fn test_jsonrpc_request() {
545        let req = JsonRpcRequest::new(1, "tools/list");
546        assert_eq!(req.jsonrpc, "2.0");
547        assert_eq!(req.method, "tools/list");
548        assert!(req.params.is_none());
549
550        let req = req.with_params(json!({"cursor": null}));
551        assert!(req.params.is_some());
552    }
553
554    #[test]
555    fn test_jsonrpc_response_success() {
556        let resp = JsonRpcResponse::success(1, json!({"tools": []}));
557        assert!(!resp.is_error());
558        assert!(resp.result.is_some());
559    }
560
561    #[test]
562    fn test_jsonrpc_response_error() {
563        let err = JsonRpcError::method_not_found("Unknown method");
564        let resp = JsonRpcResponse::error(1, err);
565        assert!(resp.is_error());
566        assert!(resp.error.is_some());
567        assert_eq!(resp.error.unwrap().code, -32601);
568    }
569
570    #[test]
571    fn test_jsonrpc_error_display() {
572        let err = JsonRpcError::invalid_params("Missing required field");
573        assert_eq!(err.to_string(), "[-32602] Missing required field");
574    }
575
576    #[test]
577    fn test_mcp_tool_serialization() {
578        let tool = McpTool {
579            name: "read_file".to_string(),
580            description: Some("Read a file".to_string()),
581            input_schema: json!({
582                "type": "object",
583                "properties": {
584                    "path": {"type": "string"}
585                },
586                "required": ["path"]
587            }),
588        };
589
590        let json_str = serde_json::to_string(&tool).unwrap();
591        assert!(json_str.contains("inputSchema"));
592    }
593
594    #[test]
595    fn test_initialize_params_default() {
596        let params = InitializeParams::default();
597        assert_eq!(params.protocol_version, MCP_VERSION);
598        assert_eq!(params.client_info.name, "liteforge");
599    }
600
601    #[test]
602    fn test_tool_result_content() {
603        let content = ToolResultContent::Text {
604            text: "Hello".to_string(),
605        };
606        let json = serde_json::to_value(&content).unwrap();
607        assert_eq!(json["type"], "text");
608        assert_eq!(json["text"], "Hello");
609    }
610}