Skip to main content

bamboo_agent/agent/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    pub list_changed: bool,
301}
302
303/// Resources capability configuration.
304///
305/// Indicates support for resource access, subscriptions, and change notifications.
306/// Resources allow servers to expose files, data, or other content.
307///
308/// # Fields
309///
310/// * `subscribe` - Whether clients can subscribe to resource updates
311/// * `list_changed` - Whether the server sends notifications when resources change
312#[derive(Debug, Clone, Serialize, Deserialize)]
313#[serde(rename_all = "camelCase")]
314pub struct ResourcesCapability {
315    /// If true, clients can subscribe to resource change notifications
316    pub subscribe: bool,
317    /// If true, server sends "resources/list_changed" notifications
318    pub list_changed: bool,
319}
320
321/// Tools capability configuration.
322///
323/// Indicates support for tool invocation and whether the server
324/// will notify clients when the tool list changes.
325///
326/// # Fields
327///
328/// * `list_changed` - Whether the server sends notifications when tools change
329#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(rename_all = "camelCase")]
331pub struct ToolsCapability {
332    /// If true, server sends "tools/list_changed" notifications
333    pub list_changed: bool,
334}
335
336/// Implementation information for client or server.
337///
338/// Identifies the software implementation on either side of the connection.
339///
340/// # Fields
341///
342/// * `name` - Human-readable name of the implementation
343/// * `version` - Version string (e.g., "1.0.0")
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct Implementation {
346    /// Name of the implementation
347    pub name: String,
348    /// Version of the implementation
349    pub version: String,
350}
351
352/// Tool list request (empty parameters).
353///
354/// Sent by the client to discover all available tools on the server.
355/// The server responds with a `McpToolListResult` containing tool metadata.
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct McpToolListRequest {}
358
359/// Tool list result containing available tools.
360///
361/// Returned by the server in response to a tool list request.
362/// Contains metadata for each available tool including name, description,
363/// and input schema.
364///
365/// # Fields
366///
367/// * `tools` - List of available tools with their metadata
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct McpToolListResult {
370    /// List of available tools
371    pub tools: Vec<McpToolInfo>,
372}
373
374/// Tool metadata and schema information.
375///
376/// Describes a tool's name, purpose, and expected input parameters.
377/// Used in tool discovery to help clients understand how to invoke tools.
378///
379/// # Fields
380///
381/// * `name` - Unique identifier for the tool
382/// * `description` - Human-readable description of what the tool does
383/// * `input_schema` - JSON Schema describing expected parameters
384///
385/// # Example
386///
387/// ```ignore
388/// let tool = McpToolInfo {
389///     name: "read_file".to_string(),
390///     description: "Read contents of a file".to_string(),
391///     input_schema: Some(json!({
392///         "type": "object",
393///         "properties": {
394///             "path": {"type": "string"}
395///         },
396///         "required": ["path"]
397///     })),
398/// };
399/// ```
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct McpToolInfo {
402    /// Unique tool identifier
403    pub name: String,
404    /// Human-readable tool description
405    pub description: String,
406    /// JSON Schema for tool input parameters
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub input_schema: Option<Value>,
409}
410
411/// Tool call request to invoke a tool.
412///
413/// Sent by the client to execute a specific tool with the provided arguments.
414///
415/// # Fields
416///
417/// * `name` - Name of the tool to invoke
418/// * `arguments` - Tool-specific parameters matching the input schema
419///
420/// # Example
421///
422/// ```ignore
423/// let request = McpToolCallRequest {
424///     name: "read_file".to_string(),
425///     arguments: Some(json!({"path": "/test.txt"})),
426/// };
427/// ```
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct McpToolCallRequest {
430    /// Name of the tool to invoke
431    pub name: String,
432    /// Tool arguments matching its input schema
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub arguments: Option<Value>,
435}
436
437/// Tool call result containing execution output.
438///
439/// Returned by the server after tool execution, containing content items
440/// (text, images, or resources) and an error flag.
441///
442/// # Fields
443///
444/// * `content` - List of content items returned by the tool
445/// * `is_error` - Whether the tool execution failed
446///
447/// # Note
448///
449/// Even when `is_error` is true, the content may contain error messages
450/// or diagnostic information.
451#[derive(Debug, Clone, Serialize, Deserialize)]
452#[serde(rename_all = "camelCase")]
453pub struct McpToolCallResult {
454    /// Content items returned by the tool
455    pub content: Vec<crate::agent::mcp::types::McpContentItem>,
456    /// Whether the tool execution encountered an error
457    #[serde(default)]
458    pub is_error: bool,
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464
465    #[test]
466    fn test_json_rpc_request() {
467        let request = JsonRpcRequest::new(1, "test", Some(serde_json::json!({"key": "value"})));
468        assert_eq!(request.jsonrpc, "2.0");
469        assert_eq!(request.id, 1);
470        assert_eq!(request.method, "test");
471        assert!(request.params.is_some());
472    }
473
474    #[test]
475    fn test_json_rpc_request_serialization() {
476        let request = JsonRpcRequest::new(1, "test", None);
477        let json = serde_json::to_string(&request).unwrap();
478        assert!(json.contains("\"jsonrpc\":\"2.0\""));
479        assert!(json.contains("\"id\":1"));
480        assert!(json.contains("\"method\":\"test\""));
481    }
482
483    #[test]
484    fn test_json_rpc_response_success() {
485        let json = r#"{"jsonrpc":"2.0","id":1,"result":{"status":"ok"}}"#;
486        let response: JsonRpcResponse = serde_json::from_str(json).unwrap();
487        assert_eq!(response.jsonrpc, "2.0");
488        assert_eq!(response.id, 1);
489        assert!(response.result.is_some());
490        assert!(response.error.is_none());
491    }
492
493    #[test]
494    fn test_json_rpc_response_error() {
495        let json =
496            r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"Invalid Request"}}"#;
497        let response: JsonRpcResponse = serde_json::from_str(json).unwrap();
498        assert_eq!(response.jsonrpc, "2.0");
499        assert_eq!(response.id, 1);
500        assert!(response.result.is_none());
501        assert!(response.error.is_some());
502        let error = response.error.unwrap();
503        assert_eq!(error.code, -32600);
504        assert_eq!(error.message, "Invalid Request");
505    }
506
507    #[test]
508    fn test_json_rpc_notification() {
509        let json = r#"{"jsonrpc":"2.0","method":"update","params":{"count":1}}"#;
510        let notification: JsonRpcNotification = serde_json::from_str(json).unwrap();
511        assert_eq!(notification.jsonrpc, "2.0");
512        assert_eq!(notification.method, "update");
513        assert!(notification.params.is_some());
514    }
515
516    #[test]
517    fn test_mcp_initialize_request_default() {
518        let request = McpInitializeRequest::default();
519        assert_eq!(request.protocol_version, "2024-11-05");
520        assert_eq!(request.client_info.name, "bamboo-agent");
521    }
522
523    #[test]
524    fn test_mcp_initialize_request_serialization() {
525        let request = McpInitializeRequest::default();
526        let json = serde_json::to_string(&request).unwrap();
527        assert!(json.contains("protocolVersion"));
528        assert!(json.contains("bamboo-agent"));
529    }
530
531    #[test]
532    fn test_mcp_initialize_result() {
533        let json = r#"{
534            "protocolVersion": "2024-11-05",
535            "capabilities": {},
536            "serverInfo": {
537                "name": "test-server",
538                "version": "1.0.0"
539            }
540        }"#;
541        let result: McpInitializeResult = serde_json::from_str(json).unwrap();
542        assert_eq!(result.protocol_version, "2024-11-05");
543        assert_eq!(result.server_info.name, "test-server");
544        assert_eq!(result.server_info.version, "1.0.0");
545    }
546
547    #[test]
548    fn test_client_capabilities_default() {
549        let caps = ClientCapabilities::default();
550        assert!(caps.experimental.is_none());
551        assert!(caps.sampling.is_none());
552    }
553
554    #[test]
555    fn test_server_capabilities_default() {
556        let caps = ServerCapabilities::default();
557        assert!(caps.experimental.is_none());
558        assert!(caps.tools.is_none());
559    }
560
561    #[test]
562    fn test_tools_capability() {
563        let caps = ToolsCapability { list_changed: true };
564        assert!(caps.list_changed);
565    }
566
567    #[test]
568    fn test_prompts_capability() {
569        let caps = PromptsCapability {
570            list_changed: false,
571        };
572        assert!(!caps.list_changed);
573    }
574
575    #[test]
576    fn test_resources_capability() {
577        let caps = ResourcesCapability {
578            subscribe: true,
579            list_changed: false,
580        };
581        assert!(caps.subscribe);
582        assert!(!caps.list_changed);
583    }
584
585    #[test]
586    fn test_implementation() {
587        let impl_info = Implementation {
588            name: "test".to_string(),
589            version: "1.0.0".to_string(),
590        };
591        assert_eq!(impl_info.name, "test");
592        assert_eq!(impl_info.version, "1.0.0");
593    }
594
595    #[test]
596    fn test_mcp_tool_list_result() {
597        let json = r#"{
598            "tools": [
599                {
600                    "name": "test_tool",
601                    "description": "A test tool",
602                    "inputSchema": {"type": "object"}
603                }
604            ]
605        }"#;
606        let result: McpToolListResult = serde_json::from_str(json).unwrap();
607        assert_eq!(result.tools.len(), 1);
608        assert_eq!(result.tools[0].name, "test_tool");
609        assert_eq!(result.tools[0].description, "A test tool");
610    }
611
612    #[test]
613    fn test_mcp_tool_info() {
614        let tool = McpToolInfo {
615            name: "read_file".to_string(),
616            description: "Read a file".to_string(),
617            input_schema: Some(serde_json::json!({"type": "object"})),
618        };
619        assert_eq!(tool.name, "read_file");
620        assert_eq!(tool.description, "Read a file");
621        assert!(tool.input_schema.is_some());
622    }
623
624    #[test]
625    fn test_mcp_tool_call_request() {
626        let request = McpToolCallRequest {
627            name: "test_tool".to_string(),
628            arguments: Some(serde_json::json!({"path": "/test"})),
629        };
630        assert_eq!(request.name, "test_tool");
631        assert!(request.arguments.is_some());
632    }
633
634    #[test]
635    fn test_mcp_tool_call_request_serialization() {
636        let request = McpToolCallRequest {
637            name: "test".to_string(),
638            arguments: Some(serde_json::json!({"key": "value"})),
639        };
640        let json = serde_json::to_string(&request).unwrap();
641        assert!(json.contains("\"name\":\"test\""));
642        assert!(json.contains("\"arguments\""));
643    }
644
645    #[test]
646    fn test_mcp_tool_call_result() {
647        let result = McpToolCallResult {
648            content: vec![],
649            is_error: false,
650        };
651        assert!(!result.is_error);
652        assert!(result.content.is_empty());
653    }
654
655    #[test]
656    fn test_json_rpc_error() {
657        let error = JsonRpcError {
658            code: -32600,
659            message: "Invalid Request".to_string(),
660            data: Some(serde_json::json!({"details": "test"})),
661        };
662        assert_eq!(error.code, -32600);
663        assert_eq!(error.message, "Invalid Request");
664        assert!(error.data.is_some());
665    }
666}