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}