agent_sdk/mcp/
protocol.rs

1//! MCP JSON-RPC protocol types.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// JSON-RPC version string.
7pub const JSONRPC_VERSION: &str = "2.0";
8
9/// JSON-RPC request.
10#[derive(Clone, Debug, Serialize, Deserialize)]
11pub struct JsonRpcRequest {
12    /// JSON-RPC version (always "2.0").
13    pub jsonrpc: String,
14    /// Request method name.
15    pub method: String,
16    /// Request parameters.
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub params: Option<Value>,
19    /// Request ID.
20    pub id: RequestId,
21}
22
23impl JsonRpcRequest {
24    /// Create a new JSON-RPC request.
25    #[must_use]
26    pub fn new(method: impl Into<String>, params: Option<Value>, id: u64) -> Self {
27        Self {
28            jsonrpc: JSONRPC_VERSION.to_string(),
29            method: method.into(),
30            params,
31            id: RequestId::Number(id),
32        }
33    }
34}
35
36/// JSON-RPC request ID.
37#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
38#[serde(untagged)]
39pub enum RequestId {
40    /// Numeric ID.
41    Number(u64),
42    /// String ID.
43    String(String),
44}
45
46/// JSON-RPC response.
47#[derive(Clone, Debug, Serialize, Deserialize)]
48pub struct JsonRpcResponse {
49    /// JSON-RPC version (always "2.0").
50    pub jsonrpc: String,
51    /// Response result (success case).
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub result: Option<Value>,
54    /// Response error (error case).
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub error: Option<JsonRpcError>,
57    /// Request ID this response corresponds to.
58    pub id: RequestId,
59}
60
61impl JsonRpcResponse {
62    /// Check if this response is an error.
63    #[must_use]
64    pub const fn is_error(&self) -> bool {
65        self.error.is_some()
66    }
67
68    /// Get the result value, if present.
69    #[must_use]
70    pub const fn result(&self) -> Option<&Value> {
71        self.result.as_ref()
72    }
73}
74
75/// JSON-RPC error object.
76#[derive(Clone, Debug, Serialize, Deserialize)]
77pub struct JsonRpcError {
78    /// Error code.
79    pub code: i32,
80    /// Error message.
81    pub message: String,
82    /// Additional error data.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub data: Option<Value>,
85}
86
87/// Standard JSON-RPC error codes.
88pub mod error_codes {
89    /// Parse error - Invalid JSON.
90    pub const PARSE_ERROR: i32 = -32700;
91    /// Invalid Request - JSON is not a valid Request object.
92    pub const INVALID_REQUEST: i32 = -32600;
93    /// Method not found.
94    pub const METHOD_NOT_FOUND: i32 = -32601;
95    /// Invalid params.
96    pub const INVALID_PARAMS: i32 = -32602;
97    /// Internal error.
98    pub const INTERNAL_ERROR: i32 = -32603;
99}
100
101/// MCP tool definition from server.
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct McpToolDefinition {
104    /// Tool name.
105    pub name: String,
106    /// Tool description.
107    #[serde(default)]
108    pub description: Option<String>,
109    /// Input schema (JSON Schema).
110    #[serde(rename = "inputSchema")]
111    pub input_schema: Value,
112}
113
114/// MCP tool call result.
115#[derive(Clone, Debug, Serialize, Deserialize)]
116pub struct McpToolCallResult {
117    /// Content items returned by the tool.
118    pub content: Vec<McpContent>,
119    /// Whether this is an error result.
120    #[serde(default, rename = "isError")]
121    pub is_error: bool,
122}
123
124/// MCP content item.
125#[derive(Clone, Debug, Serialize, Deserialize)]
126#[serde(tag = "type")]
127pub enum McpContent {
128    /// Text content.
129    #[serde(rename = "text")]
130    Text {
131        /// The text content.
132        text: String,
133    },
134    /// Image content (base64 encoded).
135    #[serde(rename = "image")]
136    Image {
137        /// Base64 encoded image data.
138        data: String,
139        /// MIME type of the image.
140        #[serde(rename = "mimeType")]
141        mime_type: String,
142    },
143    /// Resource reference.
144    #[serde(rename = "resource")]
145    Resource {
146        /// Resource URI.
147        uri: String,
148        /// Resource MIME type.
149        #[serde(rename = "mimeType")]
150        mime_type: Option<String>,
151        /// Optional text content.
152        text: Option<String>,
153    },
154}
155
156/// MCP server capabilities.
157#[derive(Clone, Debug, Default, Serialize, Deserialize)]
158pub struct McpServerCapabilities {
159    /// Tool capabilities.
160    #[serde(default)]
161    pub tools: Option<McpToolsCapability>,
162    /// Resource capabilities.
163    #[serde(default)]
164    pub resources: Option<McpResourcesCapability>,
165    /// Prompt capabilities.
166    #[serde(default)]
167    pub prompts: Option<McpPromptsCapability>,
168}
169
170/// Tool capabilities.
171#[derive(Clone, Debug, Default, Serialize, Deserialize)]
172pub struct McpToolsCapability {
173    /// Whether tools list can change.
174    #[serde(default, rename = "listChanged")]
175    pub list_changed: bool,
176}
177
178/// Resource capabilities.
179#[derive(Clone, Debug, Default, Serialize, Deserialize)]
180pub struct McpResourcesCapability {
181    /// Whether subscriptions are supported.
182    #[serde(default)]
183    pub subscribe: bool,
184    /// Whether resource list can change.
185    #[serde(default, rename = "listChanged")]
186    pub list_changed: bool,
187}
188
189/// Prompt capabilities.
190#[derive(Clone, Debug, Default, Serialize, Deserialize)]
191pub struct McpPromptsCapability {
192    /// Whether prompts list can change.
193    #[serde(default, rename = "listChanged")]
194    pub list_changed: bool,
195}
196
197/// Initialize request params.
198#[derive(Clone, Debug, Serialize, Deserialize)]
199pub struct InitializeParams {
200    /// Protocol version.
201    #[serde(rename = "protocolVersion")]
202    pub protocol_version: String,
203    /// Client capabilities.
204    pub capabilities: ClientCapabilities,
205    /// Client info.
206    #[serde(rename = "clientInfo")]
207    pub client_info: ClientInfo,
208}
209
210/// Client capabilities.
211#[derive(Clone, Debug, Default, Serialize, Deserialize)]
212pub struct ClientCapabilities {
213    /// Roots capability.
214    #[serde(default)]
215    pub roots: Option<RootsCapability>,
216    /// Sampling capability.
217    #[serde(default)]
218    pub sampling: Option<SamplingCapability>,
219}
220
221/// Roots capability.
222#[derive(Clone, Debug, Default, Serialize, Deserialize)]
223pub struct RootsCapability {
224    /// Whether list can change.
225    #[serde(default, rename = "listChanged")]
226    pub list_changed: bool,
227}
228
229/// Sampling capability.
230#[derive(Clone, Debug, Default, Serialize, Deserialize)]
231pub struct SamplingCapability {}
232
233/// Client info.
234#[derive(Clone, Debug, Serialize, Deserialize)]
235pub struct ClientInfo {
236    /// Client name.
237    pub name: String,
238    /// Client version.
239    pub version: String,
240}
241
242/// Initialize response result.
243#[derive(Clone, Debug, Serialize, Deserialize)]
244pub struct InitializeResult {
245    /// Protocol version.
246    #[serde(rename = "protocolVersion")]
247    pub protocol_version: String,
248    /// Server capabilities.
249    pub capabilities: McpServerCapabilities,
250    /// Server info.
251    #[serde(rename = "serverInfo")]
252    pub server_info: ServerInfo,
253}
254
255/// Server info.
256#[derive(Clone, Debug, Serialize, Deserialize)]
257pub struct ServerInfo {
258    /// Server name.
259    pub name: String,
260    /// Server version.
261    #[serde(default)]
262    pub version: Option<String>,
263}
264
265/// Tools list response.
266#[derive(Clone, Debug, Serialize, Deserialize)]
267pub struct ToolsListResult {
268    /// List of available tools.
269    pub tools: Vec<McpToolDefinition>,
270}
271
272/// Tool call params.
273#[derive(Clone, Debug, Serialize, Deserialize)]
274pub struct ToolCallParams {
275    /// Tool name.
276    pub name: String,
277    /// Tool arguments.
278    #[serde(default)]
279    pub arguments: Option<Value>,
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_json_rpc_request_serialization() {
288        let request =
289            JsonRpcRequest::new("test_method", Some(serde_json::json!({"key": "value"})), 1);
290
291        let json = serde_json::to_string(&request).expect("serialize");
292        assert!(json.contains("test_method"));
293        assert!(json.contains("2.0"));
294    }
295
296    #[test]
297    fn test_json_rpc_response_success() {
298        let response = JsonRpcResponse {
299            jsonrpc: JSONRPC_VERSION.to_string(),
300            result: Some(serde_json::json!({"success": true})),
301            error: None,
302            id: RequestId::Number(1),
303        };
304
305        assert!(!response.is_error());
306        assert!(response.result().is_some());
307    }
308
309    #[test]
310    fn test_json_rpc_response_error() {
311        let response = JsonRpcResponse {
312            jsonrpc: JSONRPC_VERSION.to_string(),
313            result: None,
314            error: Some(JsonRpcError {
315                code: error_codes::METHOD_NOT_FOUND,
316                message: "Method not found".to_string(),
317                data: None,
318            }),
319            id: RequestId::Number(1),
320        };
321
322        assert!(response.is_error());
323        assert!(response.result().is_none());
324    }
325
326    #[test]
327    fn test_mcp_tool_definition_deserialization() {
328        let json = r#"{
329            "name": "test_tool",
330            "description": "A test tool",
331            "inputSchema": {
332                "type": "object",
333                "properties": {}
334            }
335        }"#;
336
337        let tool: McpToolDefinition = serde_json::from_str(json).expect("deserialize");
338        assert_eq!(tool.name, "test_tool");
339        assert_eq!(tool.description.as_deref(), Some("A test tool"));
340    }
341
342    #[test]
343    fn test_mcp_content_text() {
344        let content = McpContent::Text {
345            text: "Hello".to_string(),
346        };
347
348        let json = serde_json::to_string(&content).expect("serialize");
349        assert!(json.contains("text"));
350        assert!(json.contains("Hello"));
351    }
352
353    #[test]
354    fn test_request_id_variants() {
355        let num_id = RequestId::Number(42);
356        let str_id = RequestId::String("req-1".to_string());
357
358        let json_num = serde_json::to_string(&num_id).expect("serialize");
359        let json_str = serde_json::to_string(&str_id).expect("serialize");
360
361        assert_eq!(json_num, "42");
362        assert_eq!(json_str, "\"req-1\"");
363    }
364}