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}