Skip to main content

bamboo_engine/mcp/protocol/
models.rs

1//! MCP Protocol Models
2//!
3//! This module contains data structures for the Model Context Protocol (MCP),
4//! which enables communication between AI agents and external tools/services.
5//!
6//! MCP follows a client-server architecture where:
7//! - The client (agent) sends requests to discover and invoke tools
8//! - The server provides tools, resources, and prompts
9//! - Communication is based on JSON-RPC 2.0
10//!
11//! # Protocol Flow
12//!
13//! 1. Client sends `McpInitializeRequest` to establish connection
14//! 2. Server responds with `McpInitializeResult` and capabilities
15//! 3. Client discovers available tools via `McpToolListRequest`
16//! 4. Client invokes tools using `McpToolCallRequest`
17//!
18//! # Example
19//!
20//! ```ignore
21//! use bamboo_agent::agent::mcp::protocol::models::*;
22//!
23//! // Create initialization request
24//! let init_request = McpInitializeRequest::default();
25//!
26//! // Call a tool
27//! let tool_call = McpToolCallRequest {
28//!     name: "read_file".to_string(),
29//!     arguments: Some(serde_json::json!({"path": "/test.txt"})),
30//! };
31//! ```
32
33use serde::{Deserialize, Serialize};
34use serde_json::Value;
35
36// JSON-RPC 2.0 base types
37
38/// A JSON-RPC 2.0 request message.
39///
40/// Represents a request sent from client to server, containing a method name
41/// and optional parameters. All MCP requests are wrapped in this structure.
42///
43/// # Fields
44///
45/// * `jsonrpc` - Protocol version, always "2.0"
46/// * `id` - Unique request identifier for matching responses
47/// * `method` - The method name to invoke (e.g., "tools/list")
48/// * `params` - Optional parameters for the method
49///
50/// # Example
51///
52/// ```ignore
53/// let request = JsonRpcRequest::new(1, "tools/list", None);
54/// ```
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct JsonRpcRequest {
57    /// JSON-RPC protocol version (always "2.0")
58    pub jsonrpc: String,
59    /// Unique identifier for this request
60    pub id: u64,
61    /// Name of the method to invoke
62    pub method: String,
63    /// Optional parameters for the method call
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub params: Option<Value>,
66}
67
68impl JsonRpcRequest {
69    /// Creates a new JSON-RPC request with the specified parameters.
70    ///
71    /// # Arguments
72    ///
73    /// * `id` - Unique identifier for this request
74    /// * `method` - The method name to invoke
75    /// * `params` - Optional parameters (can be None)
76    ///
77    /// # Example
78    ///
79    /// ```ignore
80    /// let request = JsonRpcRequest::new(1, "tools/call", Some(json!({"name": "test"})));
81    /// ```
82    pub fn new(id: u64, method: impl Into<String>, params: Option<Value>) -> Self {
83        Self {
84            jsonrpc: "2.0".to_string(),
85            id,
86            method: method.into(),
87            params,
88        }
89    }
90}
91
92/// A JSON-RPC 2.0 response message.
93///
94/// Represents a response from server to client, containing either a result
95/// or an error. The `id` field matches the corresponding request.
96///
97/// # Fields
98///
99/// * `jsonrpc` - Protocol version, always "2.0"
100/// * `id` - Request identifier this response corresponds to
101/// * `result` - Successful result (mutually exclusive with error)
102/// * `error` - Error information if the request failed
103///
104/// # Note
105///
106/// Either `result` or `error` will be present, never both.
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct JsonRpcResponse {
109    /// JSON-RPC protocol version (always "2.0")
110    pub jsonrpc: String,
111    /// Request identifier this response corresponds to
112    pub id: u64,
113    /// Successful result data (present on success)
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub result: Option<Value>,
116    /// Error information (present on failure)
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub error: Option<JsonRpcError>,
119}
120
121/// A JSON-RPC 2.0 error object.
122///
123/// Contains error details when a request fails, including an error code,
124/// human-readable message, and optional additional data.
125///
126/// # Standard Error Codes
127///
128/// - `-32700`: Parse error
129/// - `-32600`: Invalid request
130/// - `-32601`: Method not found
131/// - `-32602`: Invalid params
132/// - `-32603`: Internal error
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct JsonRpcError {
135    /// Error code indicating the type of error
136    pub code: i32,
137    /// Human-readable error message
138    pub message: String,
139    /// Additional error data (optional)
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub data: Option<Value>,
142}
143
144/// A JSON-RPC 2.0 notification message.
145///
146/// Represents a one-way message that doesn't expect a response.
147/// Used for server-initiated events like tool list changes.
148///
149/// # Fields
150///
151/// * `jsonrpc` - Protocol version, always "2.0"
152/// * `method` - The notification method name
153/// * `params` - Optional notification parameters
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct JsonRpcNotification {
156    /// JSON-RPC protocol version (always "2.0")
157    pub jsonrpc: String,
158    /// Name of the notification method
159    pub method: String,
160    /// Optional parameters for the notification
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub params: Option<Value>,
163}
164
165// MCP Protocol types
166
167/// MCP initialization request sent by the client.
168///
169/// This is the first message sent to establish an MCP connection.
170/// The client declares its protocol version, capabilities, and identity.
171///
172/// # Fields
173///
174/// * `protocol_version` - MCP protocol version (e.g., "2024-11-05")
175/// * `capabilities` - Features the client supports
176/// * `client_info` - Client implementation details (name and version)
177///
178/// # Example
179///
180/// ```ignore
181/// let request = McpInitializeRequest::default();
182/// // Uses bamboo-agent as client name and current package version
183/// ```
184#[derive(Debug, Clone, Serialize, Deserialize)]
185#[serde(rename_all = "camelCase")]
186pub struct McpInitializeRequest {
187    /// MCP protocol version being used
188    pub protocol_version: String,
189    /// Capabilities the client supports
190    pub capabilities: ClientCapabilities,
191    /// Information about the client implementation
192    pub client_info: Implementation,
193}
194
195impl Default for McpInitializeRequest {
196    /// Creates a default initialization request for the bamboo agent.
197    ///
198    /// Uses protocol version "2024-11-05" and the current package version.
199    fn default() -> Self {
200        Self {
201            protocol_version: "2024-11-05".to_string(),
202            capabilities: ClientCapabilities::default(),
203            client_info: Implementation {
204                name: "bamboo-agent".to_string(),
205                version: env!("CARGO_PKG_VERSION").to_string(),
206            },
207        }
208    }
209}
210
211/// MCP initialization result returned by the server.
212///
213/// Sent in response to an initialization request, declaring the server's
214/// protocol version, capabilities, and identity.
215///
216/// # Fields
217///
218/// * `protocol_version` - MCP protocol version the server is using
219/// * `capabilities` - Features the server supports (tools, resources, prompts)
220/// * `server_info` - Server implementation details (name and version)
221/// * `instructions` - Optional usage instructions for the client
222#[derive(Debug, Clone, Serialize, Deserialize)]
223#[serde(rename_all = "camelCase")]
224pub struct McpInitializeResult {
225    /// MCP protocol version being used by the server
226    pub protocol_version: String,
227    /// Capabilities the server supports
228    pub capabilities: ServerCapabilities,
229    /// Information about the server implementation
230    pub server_info: Implementation,
231    /// Optional instructions for using this server
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub instructions: Option<String>,
234}
235
236/// Client capabilities declaration.
237///
238/// Informs the server about which optional features the client supports.
239/// Currently minimal, as most MCP features are server-side.
240///
241/// # Fields
242///
243/// * `experimental` - Experimental capabilities (future use)
244/// * `sampling` - Support for LLM sampling requests (future use)
245#[derive(Debug, Clone, Serialize, Deserialize, Default)]
246pub struct ClientCapabilities {
247    /// Experimental capabilities (reserved for future use)
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub experimental: Option<Value>,
250    /// Support for LLM sampling requests (reserved for future use)
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub sampling: Option<Value>,
253}
254
255/// Server capabilities declaration.
256///
257/// Informs the client about which features the server provides.
258/// The client can then use this information to discover and use
259/// available tools, resources, and prompts.
260///
261/// # Fields
262///
263/// * `experimental` - Experimental capabilities (future use)
264/// * `logging` - Support for log message notifications
265/// * `prompts` - Support for prompt templates
266/// * `resources` - Support for resource access
267/// * `tools` - Support for tool invocation
268#[derive(Debug, Clone, Serialize, Deserialize, Default)]
269#[serde(rename_all = "camelCase")]
270pub struct ServerCapabilities {
271    /// Experimental capabilities (reserved for future use)
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub experimental: Option<Value>,
274    /// Support for logging notifications
275    #[serde(skip_serializing_if = "Option::is_none")]
276    pub logging: Option<Value>,
277    /// Support for prompt templates
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub prompts: Option<PromptsCapability>,
280    /// Support for resource access
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub resources: Option<ResourcesCapability>,
283    /// Support for tool invocation
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub tools: Option<ToolsCapability>,
286}
287
288/// Prompts capability configuration.
289///
290/// Indicates support for prompt templates and whether the server
291/// will notify clients when the prompt list changes.
292///
293/// # Fields
294///
295/// * `list_changed` - Whether the server sends notifications when prompts change
296#[derive(Debug, Clone, Serialize, Deserialize)]
297#[serde(rename_all = "camelCase")]
298pub struct PromptsCapability {
299    /// If true, server sends "prompts/list_changed" notifications
300    ///
301    /// Some MCP servers omit this field; treat missing as `false`.
302    #[serde(default, alias = "list_changed")]
303    pub list_changed: bool,
304}
305
306/// Resources capability configuration.
307///
308/// Indicates support for resource access, subscriptions, and change notifications.
309/// Resources allow servers to expose files, data, or other content.
310///
311/// # Fields
312///
313/// * `subscribe` - Whether clients can subscribe to resource updates
314/// * `list_changed` - Whether the server sends notifications when resources change
315#[derive(Debug, Clone, Serialize, Deserialize)]
316#[serde(rename_all = "camelCase")]
317pub struct ResourcesCapability {
318    /// If true, clients can subscribe to resource change notifications
319    ///
320    /// Some MCP servers omit this field; treat missing as `false`.
321    #[serde(default)]
322    pub subscribe: bool,
323    /// If true, server sends "resources/list_changed" notifications
324    ///
325    /// Some MCP servers omit this field; treat missing as `false`.
326    #[serde(default, alias = "list_changed")]
327    pub list_changed: bool,
328}
329
330/// Tools capability configuration.
331///
332/// Indicates support for tool invocation and whether the server
333/// will notify clients when the tool list changes.
334///
335/// # Fields
336///
337/// * `list_changed` - Whether the server sends notifications when tools change
338#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ToolsCapability {
341    /// If true, server sends "tools/list_changed" notifications
342    ///
343    /// Some MCP servers omit this field; treat missing as `false`.
344    #[serde(default, alias = "list_changed")]
345    pub list_changed: bool,
346}
347
348/// Implementation information for client or server.
349///
350/// Identifies the software implementation on either side of the connection.
351///
352/// # Fields
353///
354/// * `name` - Human-readable name of the implementation
355/// * `version` - Version string (e.g., "1.0.0")
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct Implementation {
358    /// Name of the implementation
359    pub name: String,
360    /// Version of the implementation
361    pub version: String,
362}
363
364/// Tool list request (empty parameters).
365///
366/// Sent by the client to discover all available tools on the server.
367/// The server responds with a `McpToolListResult` containing tool metadata.
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct McpToolListRequest {}
370
371/// Tool list result containing available tools.
372///
373/// Returned by the server in response to a tool list request.
374/// Contains metadata for each available tool including name, description,
375/// and input schema.
376///
377/// # Fields
378///
379/// * `tools` - List of available tools with their metadata
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct McpToolListResult {
382    /// List of available tools
383    pub tools: Vec<McpToolInfo>,
384}
385
386/// Tool metadata and schema information.
387///
388/// Describes a tool's name, purpose, and expected input parameters.
389/// Used in tool discovery to help clients understand how to invoke tools.
390///
391/// # Fields
392///
393/// * `name` - Unique identifier for the tool
394/// * `description` - Human-readable description of what the tool does
395/// * `input_schema` - JSON Schema describing expected parameters
396///
397/// # Example
398///
399/// ```ignore
400/// let tool = McpToolInfo {
401///     name: "read_file".to_string(),
402///     description: "Read contents of a file".to_string(),
403///     input_schema: Some(json!({
404///         "type": "object",
405///         "properties": {
406///             "path": {"type": "string"}
407///         },
408///         "required": ["path"]
409///     })),
410/// };
411/// ```
412#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct McpToolInfo {
414    /// Unique tool identifier
415    pub name: String,
416    /// Human-readable tool description
417    pub description: String,
418    /// JSON Schema for tool input parameters
419    #[serde(
420        rename = "inputSchema",
421        alias = "input_schema",
422        skip_serializing_if = "Option::is_none"
423    )]
424    pub input_schema: Option<Value>,
425}
426
427/// Tool call request to invoke a tool.
428///
429/// Sent by the client to execute a specific tool with the provided arguments.
430///
431/// # Fields
432///
433/// * `name` - Name of the tool to invoke
434/// * `arguments` - Tool-specific parameters matching the input schema
435///
436/// # Example
437///
438/// ```ignore
439/// let request = McpToolCallRequest {
440///     name: "read_file".to_string(),
441///     arguments: Some(json!({"path": "/test.txt"})),
442/// };
443/// ```
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct McpToolCallRequest {
446    /// Name of the tool to invoke
447    pub name: String,
448    /// Tool arguments matching its input schema
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub arguments: Option<Value>,
451}
452
453/// Tool call result containing execution output.
454///
455/// Returned by the server after tool execution, containing content items
456/// (text, images, or resources) and an error flag.
457///
458/// # Fields
459///
460/// * `content` - List of content items returned by the tool
461/// * `is_error` - Whether the tool execution failed
462///
463/// # Note
464///
465/// Even when `is_error` is true, the content may contain error messages
466/// or diagnostic information.
467#[derive(Debug, Clone, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase")]
469pub struct McpToolCallResult {
470    /// Content items returned by the tool
471    pub content: Vec<crate::mcp::types::McpContentItem>,
472    /// Whether the tool execution encountered an error
473    #[serde(default)]
474    pub is_error: bool,
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    #[test]
482    fn test_json_rpc_request() {
483        let request = JsonRpcRequest::new(1, "test", Some(serde_json::json!({"key": "value"})));
484        assert_eq!(request.jsonrpc, "2.0");
485        assert_eq!(request.id, 1);
486        assert_eq!(request.method, "test");
487        assert!(request.params.is_some());
488    }
489
490    #[test]
491    fn test_json_rpc_request_serialization() {
492        let request = JsonRpcRequest::new(1, "test", None);
493        let json = serde_json::to_string(&request).unwrap();
494        assert!(json.contains("\"jsonrpc\":\"2.0\""));
495        assert!(json.contains("\"id\":1"));
496        assert!(json.contains("\"method\":\"test\""));
497    }
498
499    #[test]
500    fn test_json_rpc_response_success() {
501        let json = r#"{"jsonrpc":"2.0","id":1,"result":{"status":"ok"}}"#;
502        let response: JsonRpcResponse = serde_json::from_str(json).unwrap();
503        assert_eq!(response.jsonrpc, "2.0");
504        assert_eq!(response.id, 1);
505        assert!(response.result.is_some());
506        assert!(response.error.is_none());
507    }
508
509    #[test]
510    fn test_json_rpc_response_error() {
511        let json =
512            r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request"}}"#;
513        let response: JsonRpcResponse = serde_json::from_str(json).unwrap();
514        assert_eq!(response.jsonrpc, "2.0");
515        assert_eq!(response.id, 1);
516        assert!(response.result.is_none());
517        assert!(response.error.is_some());
518        let error = response.error.unwrap();
519        assert_eq!(error.code, -32600);
520        assert_eq!(error.message, "Invalid Request");
521    }
522
523    #[test]
524    fn test_json_rpc_notification() {
525        let json = r#"{"jsonrpc":"2.0","method":"update","params":{"count":1}}"#;
526        let notification: JsonRpcNotification = serde_json::from_str(json).unwrap();
527        assert_eq!(notification.jsonrpc, "2.0");
528        assert_eq!(notification.method, "update");
529        assert!(notification.params.is_some());
530    }
531
532    #[test]
533    fn test_mcp_initialize_request_default() {
534        let request = McpInitializeRequest::default();
535        assert_eq!(request.protocol_version, "2024-11-05");
536        assert_eq!(request.client_info.name, "bamboo-agent");
537    }
538
539    #[test]
540    fn test_mcp_initialize_request_serialization() {
541        let request = McpInitializeRequest::default();
542        let json = serde_json::to_string(&request).unwrap();
543        assert!(json.contains("protocolVersion"));
544        assert!(json.contains("bamboo-agent"));
545    }
546
547    #[test]
548    fn test_mcp_initialize_result() {
549        let json = r#"{
550            "protocolVersion": "2024-11-05",
551            "capabilities": {},
552            "serverInfo": {
553                "name": "test-server",
554                "version": "1.0.0"
555            }
556        }"#;
557        let result: McpInitializeResult = serde_json::from_str(json).unwrap();
558        assert_eq!(result.protocol_version, "2024-11-05");
559        assert_eq!(result.server_info.name, "test-server");
560        assert_eq!(result.server_info.version, "1.0.0");
561    }
562
563    #[test]
564    fn test_client_capabilities_default() {
565        let caps = ClientCapabilities::default();
566        assert!(caps.experimental.is_none());
567        assert!(caps.sampling.is_none());
568    }
569
570    #[test]
571    fn test_server_capabilities_default() {
572        let caps = ServerCapabilities::default();
573        assert!(caps.experimental.is_none());
574        assert!(caps.tools.is_none());
575    }
576
577    #[test]
578    fn test_tools_capability() {
579        let caps = ToolsCapability { list_changed: true };
580        assert!(caps.list_changed);
581    }
582
583    #[test]
584    fn test_prompts_capability() {
585        let caps = PromptsCapability {
586            list_changed: false,
587        };
588        assert!(!caps.list_changed);
589    }
590
591    #[test]
592    fn test_resources_capability() {
593        let caps = ResourcesCapability {
594            subscribe: true,
595            list_changed: false,
596        };
597        assert!(caps.subscribe);
598        assert!(!caps.list_changed);
599    }
600
601    #[test]
602    fn test_tools_capability_missing_list_changed_defaults_false() {
603        let json = r#"{"tools": {}}"#;
604        let caps: ServerCapabilities = serde_json::from_str(json).unwrap();
605        assert!(caps.tools.is_some());
606        assert!(!caps.tools.unwrap().list_changed);
607    }
608
609    #[test]
610    fn test_tools_capability_accepts_snake_case_list_changed() {
611        let json = r#"{"tools": {"list_changed": true}}"#;
612        let caps: ServerCapabilities = serde_json::from_str(json).unwrap();
613        assert!(caps.tools.is_some());
614        assert!(caps.tools.unwrap().list_changed);
615    }
616
617    #[test]
618    fn test_resources_capability_missing_fields_defaults_false() {
619        let json = r#"{"resources": {}}"#;
620        let caps: ServerCapabilities = serde_json::from_str(json).unwrap();
621        let resources = caps.resources.unwrap();
622        assert!(!resources.subscribe);
623        assert!(!resources.list_changed);
624    }
625
626    #[test]
627    fn test_prompts_capability_missing_list_changed_defaults_false() {
628        let json = r#"{"prompts": {}}"#;
629        let caps: ServerCapabilities = serde_json::from_str(json).unwrap();
630        assert!(caps.prompts.is_some());
631        assert!(!caps.prompts.unwrap().list_changed);
632    }
633
634    #[test]
635    fn test_implementation() {
636        let impl_info = Implementation {
637            name: "test".to_string(),
638            version: "1.0.0".to_string(),
639        };
640        assert_eq!(impl_info.name, "test");
641        assert_eq!(impl_info.version, "1.0.0");
642    }
643
644    #[test]
645    fn test_mcp_tool_list_result() {
646        let json = r#"{
647            "tools": [
648                {
649                    "name": "test_tool",
650                    "description": "A test tool",
651                    "inputSchema": {"type": "object"}
652                }
653            ]
654        }"#;
655        let result: McpToolListResult = serde_json::from_str(json).unwrap();
656        assert_eq!(result.tools.len(), 1);
657        assert_eq!(result.tools[0].name, "test_tool");
658        assert_eq!(result.tools[0].description, "A test tool");
659    }
660
661    #[test]
662    fn test_mcp_tool_list_result_accepts_snake_case_input_schema() {
663        let json = r#"{
664            "tools": [
665                {
666                    "name": "test_tool",
667                    "description": "A test tool",
668                    "input_schema": {"type": "object"}
669                }
670            ]
671        }"#;
672        let result: McpToolListResult = serde_json::from_str(json).unwrap();
673        assert_eq!(result.tools.len(), 1);
674        assert_eq!(result.tools[0].name, "test_tool");
675        assert_eq!(result.tools[0].description, "A test tool");
676        assert!(result.tools[0].input_schema.is_some());
677    }
678
679    #[test]
680    fn test_mcp_tool_info() {
681        let tool = McpToolInfo {
682            name: "read_file".to_string(),
683            description: "Read a file".to_string(),
684            input_schema: Some(serde_json::json!({"type": "object"})),
685        };
686        assert_eq!(tool.name, "read_file");
687        assert_eq!(tool.description, "Read a file");
688        assert!(tool.input_schema.is_some());
689    }
690
691    #[test]
692    fn test_mcp_tool_call_request() {
693        let request = McpToolCallRequest {
694            name: "test_tool".to_string(),
695            arguments: Some(serde_json::json!({"path": "/test"})),
696        };
697        assert_eq!(request.name, "test_tool");
698        assert!(request.arguments.is_some());
699    }
700
701    #[test]
702    fn test_mcp_tool_call_request_serialization() {
703        let request = McpToolCallRequest {
704            name: "test".to_string(),
705            arguments: Some(serde_json::json!({"key": "value"})),
706        };
707        let json = serde_json::to_string(&request).unwrap();
708        assert!(json.contains("\"name\":\"test\""));
709        assert!(json.contains("\"arguments\""));
710    }
711
712    #[test]
713    fn test_mcp_tool_call_result() {
714        let result = McpToolCallResult {
715            content: vec![],
716            is_error: false,
717        };
718        assert!(!result.is_error);
719        assert!(result.content.is_empty());
720    }
721
722    #[test]
723    fn test_json_rpc_error() {
724        let error = JsonRpcError {
725            code: -32600,
726            message: "Invalid Request".to_string(),
727            data: Some(serde_json::json!({"details": "test"})),
728        };
729        assert_eq!(error.code, -32600);
730        assert_eq!(error.message, "Invalid Request");
731        assert!(error.data.is_some());
732    }
733}