Skip to main content

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/// The MCP protocol revision this client prefers to negotiate.
10///
11/// MCP revisions are date-stamped. `2025-06-18` is the current stable
12/// revision and supersedes the original `2024-11-05` this client was
13/// previously pinned to. During the `initialize` handshake we advertise
14/// this version; the server replies with the revision it actually selected
15/// (which may be older), and we honour that for subsequent requests
16/// (notably the `MCP-Protocol-Version` HTTP header).
17pub const PREFERRED_PROTOCOL_VERSION: &str = "2025-06-18";
18
19/// The oldest MCP revision this client interoperates with.
20///
21/// Servers that only speak `2024-11-05` are still supported: the client
22/// adapts to whatever revision the server selects during initialization.
23pub const MIN_PROTOCOL_VERSION: &str = "2024-11-05";
24
25/// MCP protocol revisions this client knows about, newest first.
26///
27/// Used to decide whether a server-selected revision is one we recognise.
28/// An unknown revision is not fatal — the client still proceeds — but it is
29/// logged so operators can tell when a server negotiated something outside
30/// this set.
31pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &["2025-06-18", "2025-03-26", "2024-11-05"];
32
33/// Returns `true` if `version` is a revision this client explicitly knows.
34#[must_use]
35pub fn is_known_protocol_version(version: &str) -> bool {
36    SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
37}
38
39/// JSON-RPC request.
40#[derive(Clone, Debug, Serialize, Deserialize)]
41pub struct JsonRpcRequest {
42    /// JSON-RPC version (always "2.0").
43    pub jsonrpc: String,
44    /// Request method name.
45    pub method: String,
46    /// Request parameters.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub params: Option<Value>,
49    /// Request ID.
50    pub id: RequestId,
51}
52
53impl JsonRpcRequest {
54    /// Create a new JSON-RPC request.
55    #[must_use]
56    pub fn new(method: impl Into<String>, params: Option<Value>, id: u64) -> Self {
57        Self {
58            jsonrpc: JSONRPC_VERSION.to_string(),
59            method: method.into(),
60            params,
61            id: RequestId::Number(id),
62        }
63    }
64}
65
66/// JSON-RPC request ID.
67#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
68#[serde(untagged)]
69pub enum RequestId {
70    /// Numeric ID.
71    Number(u64),
72    /// String ID.
73    String(String),
74}
75
76/// JSON-RPC response.
77#[derive(Clone, Debug, Serialize, Deserialize)]
78pub struct JsonRpcResponse {
79    /// JSON-RPC version (always "2.0").
80    pub jsonrpc: String,
81    /// Response result (success case).
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub result: Option<Value>,
84    /// Response error (error case).
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub error: Option<JsonRpcError>,
87    /// Request ID this response corresponds to.
88    pub id: RequestId,
89}
90
91impl JsonRpcResponse {
92    /// Check if this response is an error.
93    #[must_use]
94    pub const fn is_error(&self) -> bool {
95        self.error.is_some()
96    }
97
98    /// Get the result value, if present.
99    #[must_use]
100    pub const fn result(&self) -> Option<&Value> {
101        self.result.as_ref()
102    }
103}
104
105/// JSON-RPC error object.
106#[derive(Clone, Debug, Serialize, Deserialize)]
107pub struct JsonRpcError {
108    /// Error code.
109    pub code: i32,
110    /// Error message.
111    pub message: String,
112    /// Additional error data.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub data: Option<Value>,
115}
116
117/// Standard JSON-RPC error codes.
118pub mod error_codes {
119    /// Parse error - Invalid JSON.
120    pub const PARSE_ERROR: i32 = -32700;
121    /// Invalid Request - JSON is not a valid Request object.
122    pub const INVALID_REQUEST: i32 = -32600;
123    /// Method not found.
124    pub const METHOD_NOT_FOUND: i32 = -32601;
125    /// Invalid params.
126    pub const INVALID_PARAMS: i32 = -32602;
127    /// Internal error.
128    pub const INTERNAL_ERROR: i32 = -32603;
129}
130
131/// MCP tool definition from server.
132#[derive(Clone, Debug, Serialize, Deserialize)]
133pub struct McpToolDefinition {
134    /// Tool name.
135    pub name: String,
136    /// Tool description.
137    #[serde(default)]
138    pub description: Option<String>,
139    /// Input schema (JSON Schema).
140    #[serde(rename = "inputSchema")]
141    pub input_schema: Value,
142}
143
144/// MCP tool call result.
145#[derive(Clone, Debug, Serialize, Deserialize)]
146pub struct McpToolCallResult {
147    /// Content items returned by the tool.
148    pub content: Vec<McpContent>,
149    /// Whether this is an error result.
150    #[serde(default, rename = "isError")]
151    pub is_error: bool,
152}
153
154/// MCP content item.
155#[derive(Clone, Debug, Serialize, Deserialize)]
156#[serde(tag = "type")]
157pub enum McpContent {
158    /// Text content.
159    #[serde(rename = "text")]
160    Text {
161        /// The text content.
162        text: String,
163    },
164    /// Image content (base64 encoded).
165    #[serde(rename = "image")]
166    Image {
167        /// Base64 encoded image data.
168        data: String,
169        /// MIME type of the image.
170        #[serde(rename = "mimeType")]
171        mime_type: String,
172    },
173    /// Resource reference.
174    #[serde(rename = "resource")]
175    Resource {
176        /// Resource URI.
177        uri: String,
178        /// Resource MIME type.
179        #[serde(rename = "mimeType")]
180        mime_type: Option<String>,
181        /// Optional text content.
182        text: Option<String>,
183    },
184}
185
186/// MCP server capabilities.
187#[derive(Clone, Debug, Default, Serialize, Deserialize)]
188pub struct McpServerCapabilities {
189    /// Tool capabilities.
190    #[serde(default)]
191    pub tools: Option<McpToolsCapability>,
192    /// Resource capabilities.
193    #[serde(default)]
194    pub resources: Option<McpResourcesCapability>,
195    /// Prompt capabilities.
196    #[serde(default)]
197    pub prompts: Option<McpPromptsCapability>,
198}
199
200/// Tool capabilities.
201#[derive(Clone, Debug, Default, Serialize, Deserialize)]
202pub struct McpToolsCapability {
203    /// Whether tools list can change.
204    #[serde(default, rename = "listChanged")]
205    pub list_changed: bool,
206}
207
208/// Resource capabilities.
209#[derive(Clone, Debug, Default, Serialize, Deserialize)]
210pub struct McpResourcesCapability {
211    /// Whether subscriptions are supported.
212    #[serde(default)]
213    pub subscribe: bool,
214    /// Whether resource list can change.
215    #[serde(default, rename = "listChanged")]
216    pub list_changed: bool,
217}
218
219/// Prompt capabilities.
220#[derive(Clone, Debug, Default, Serialize, Deserialize)]
221pub struct McpPromptsCapability {
222    /// Whether prompts list can change.
223    #[serde(default, rename = "listChanged")]
224    pub list_changed: bool,
225}
226
227/// Initialize request params.
228#[derive(Clone, Debug, Serialize, Deserialize)]
229pub struct InitializeParams {
230    /// Protocol version.
231    #[serde(rename = "protocolVersion")]
232    pub protocol_version: String,
233    /// Client capabilities.
234    pub capabilities: ClientCapabilities,
235    /// Client info.
236    #[serde(rename = "clientInfo")]
237    pub client_info: ClientInfo,
238}
239
240/// Client capabilities.
241#[derive(Clone, Debug, Default, Serialize, Deserialize)]
242pub struct ClientCapabilities {
243    /// Roots capability.
244    #[serde(default)]
245    pub roots: Option<RootsCapability>,
246    /// Sampling capability.
247    #[serde(default)]
248    pub sampling: Option<SamplingCapability>,
249}
250
251/// Roots capability.
252#[derive(Clone, Debug, Default, Serialize, Deserialize)]
253pub struct RootsCapability {
254    /// Whether list can change.
255    #[serde(default, rename = "listChanged")]
256    pub list_changed: bool,
257}
258
259/// Sampling capability.
260#[derive(Clone, Debug, Default, Serialize, Deserialize)]
261pub struct SamplingCapability {}
262
263/// Client info.
264#[derive(Clone, Debug, Serialize, Deserialize)]
265pub struct ClientInfo {
266    /// Client name.
267    pub name: String,
268    /// Client version.
269    pub version: String,
270}
271
272/// Initialize response result.
273#[derive(Clone, Debug, Serialize, Deserialize)]
274pub struct InitializeResult {
275    /// Protocol version.
276    #[serde(rename = "protocolVersion")]
277    pub protocol_version: String,
278    /// Server capabilities.
279    pub capabilities: McpServerCapabilities,
280    /// Server info.
281    #[serde(rename = "serverInfo")]
282    pub server_info: ServerInfo,
283}
284
285/// Server info.
286#[derive(Clone, Debug, Serialize, Deserialize)]
287pub struct ServerInfo {
288    /// Server name.
289    pub name: String,
290    /// Server version.
291    #[serde(default)]
292    pub version: Option<String>,
293}
294
295/// Tools list response.
296#[derive(Clone, Debug, Serialize, Deserialize)]
297pub struct ToolsListResult {
298    /// List of available tools.
299    pub tools: Vec<McpToolDefinition>,
300}
301
302/// Tool call params.
303#[derive(Clone, Debug, Serialize, Deserialize)]
304pub struct ToolCallParams {
305    /// Tool name.
306    pub name: String,
307    /// Tool arguments.
308    #[serde(default)]
309    pub arguments: Option<Value>,
310}
311
312// ── Resources ──────────────────────────────────────────────────────────
313
314/// An MCP resource descriptor as returned by `resources/list`.
315#[derive(Clone, Debug, Serialize, Deserialize)]
316pub struct McpResource {
317    /// Resource URI (used to read the resource via `resources/read`).
318    pub uri: String,
319    /// Human-readable resource name.
320    #[serde(default)]
321    pub name: Option<String>,
322    /// Optional resource description.
323    #[serde(default)]
324    pub description: Option<String>,
325    /// MIME type of the resource contents, if known.
326    #[serde(default, rename = "mimeType")]
327    pub mime_type: Option<String>,
328}
329
330/// `resources/list` response.
331#[derive(Clone, Debug, Serialize, Deserialize)]
332pub struct ResourcesListResult {
333    /// Available resources.
334    pub resources: Vec<McpResource>,
335    /// Opaque cursor for pagination, if the server returned more pages.
336    #[serde(default, rename = "nextCursor")]
337    pub next_cursor: Option<String>,
338}
339
340/// `resources/read` params.
341#[derive(Clone, Debug, Serialize, Deserialize)]
342pub struct ResourceReadParams {
343    /// URI of the resource to read.
344    pub uri: String,
345}
346
347/// The contents of a single resource returned by `resources/read`.
348#[derive(Clone, Debug, Serialize, Deserialize)]
349pub struct McpResourceContents {
350    /// Resource URI.
351    pub uri: String,
352    /// MIME type of the contents, if known.
353    #[serde(default, rename = "mimeType")]
354    pub mime_type: Option<String>,
355    /// Text contents (mutually exclusive with `blob`).
356    #[serde(default)]
357    pub text: Option<String>,
358    /// Base64-encoded binary contents (mutually exclusive with `text`).
359    #[serde(default)]
360    pub blob: Option<String>,
361}
362
363/// `resources/read` response.
364#[derive(Clone, Debug, Serialize, Deserialize)]
365pub struct ResourceReadResult {
366    /// One or more content blocks for the requested resource.
367    pub contents: Vec<McpResourceContents>,
368}
369
370// ── Prompts ────────────────────────────────────────────────────────────
371
372/// A prompt argument descriptor from `prompts/list`.
373#[derive(Clone, Debug, Serialize, Deserialize)]
374pub struct McpPromptArgument {
375    /// Argument name.
376    pub name: String,
377    /// Optional argument description.
378    #[serde(default)]
379    pub description: Option<String>,
380    /// Whether the argument is required.
381    #[serde(default)]
382    pub required: bool,
383}
384
385/// An MCP prompt descriptor as returned by `prompts/list`.
386#[derive(Clone, Debug, Serialize, Deserialize)]
387pub struct McpPrompt {
388    /// Prompt name (used to fetch the prompt via `prompts/get`).
389    pub name: String,
390    /// Optional prompt description.
391    #[serde(default)]
392    pub description: Option<String>,
393    /// Declared prompt arguments.
394    #[serde(default)]
395    pub arguments: Vec<McpPromptArgument>,
396}
397
398/// `prompts/list` response.
399#[derive(Clone, Debug, Serialize, Deserialize)]
400pub struct PromptsListResult {
401    /// Available prompts.
402    pub prompts: Vec<McpPrompt>,
403    /// Opaque cursor for pagination, if the server returned more pages.
404    #[serde(default, rename = "nextCursor")]
405    pub next_cursor: Option<String>,
406}
407
408/// `prompts/get` params.
409#[derive(Clone, Debug, Serialize, Deserialize)]
410pub struct PromptGetParams {
411    /// Name of the prompt to fetch.
412    pub name: String,
413    /// Arguments to interpolate into the prompt template.
414    #[serde(default, skip_serializing_if = "Option::is_none")]
415    pub arguments: Option<Value>,
416}
417
418/// The role of a prompt message.
419#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
420#[serde(rename_all = "lowercase")]
421pub enum McpRole {
422    /// A user-authored message.
423    User,
424    /// An assistant-authored message.
425    Assistant,
426}
427
428/// A single message in a rendered prompt.
429#[derive(Clone, Debug, Serialize, Deserialize)]
430pub struct McpPromptMessage {
431    /// Message role.
432    pub role: McpRole,
433    /// Message content block.
434    pub content: McpContent,
435}
436
437/// `prompts/get` response.
438#[derive(Clone, Debug, Serialize, Deserialize)]
439pub struct PromptGetResult {
440    /// Optional prompt description.
441    #[serde(default)]
442    pub description: Option<String>,
443    /// The rendered prompt messages.
444    pub messages: Vec<McpPromptMessage>,
445}
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    #[test]
452    fn test_json_rpc_request_serialization() {
453        let request =
454            JsonRpcRequest::new("test_method", Some(serde_json::json!({"key": "value"})), 1);
455
456        let json = serde_json::to_string(&request).expect("serialize");
457        assert!(json.contains("test_method"));
458        assert!(json.contains("2.0"));
459    }
460
461    #[test]
462    fn test_json_rpc_response_success() {
463        let response = JsonRpcResponse {
464            jsonrpc: JSONRPC_VERSION.to_string(),
465            result: Some(serde_json::json!({"success": true})),
466            error: None,
467            id: RequestId::Number(1),
468        };
469
470        assert!(!response.is_error());
471        assert!(response.result().is_some());
472    }
473
474    #[test]
475    fn test_json_rpc_response_error() {
476        let response = JsonRpcResponse {
477            jsonrpc: JSONRPC_VERSION.to_string(),
478            result: None,
479            error: Some(JsonRpcError {
480                code: error_codes::METHOD_NOT_FOUND,
481                message: "Method not found".to_string(),
482                data: None,
483            }),
484            id: RequestId::Number(1),
485        };
486
487        assert!(response.is_error());
488        assert!(response.result().is_none());
489    }
490
491    #[test]
492    fn test_mcp_tool_definition_deserialization() {
493        let json = r#"{
494            "name": "test_tool",
495            "description": "A test tool",
496            "inputSchema": {
497                "type": "object",
498                "properties": {}
499            }
500        }"#;
501
502        let tool: McpToolDefinition = serde_json::from_str(json).expect("deserialize");
503        assert_eq!(tool.name, "test_tool");
504        assert_eq!(tool.description.as_deref(), Some("A test tool"));
505    }
506
507    #[test]
508    fn test_mcp_content_text() {
509        let content = McpContent::Text {
510            text: "Hello".to_string(),
511        };
512
513        let json = serde_json::to_string(&content).expect("serialize");
514        assert!(json.contains("text"));
515        assert!(json.contains("Hello"));
516    }
517
518    #[test]
519    fn test_request_id_variants() {
520        let num_id = RequestId::Number(42);
521        let str_id = RequestId::String("req-1".to_string());
522
523        let json_num = serde_json::to_string(&num_id).expect("serialize");
524        let json_str = serde_json::to_string(&str_id).expect("serialize");
525
526        assert_eq!(json_num, "42");
527        assert_eq!(json_str, "\"req-1\"");
528    }
529
530    #[test]
531    fn preferred_protocol_version_is_newer_than_floor() {
532        // The preferred revision must be recognised and must not be the
533        // legacy floor — otherwise we never moved off `2024-11-05`.
534        assert_ne!(PREFERRED_PROTOCOL_VERSION, MIN_PROTOCOL_VERSION);
535        assert!(is_known_protocol_version(PREFERRED_PROTOCOL_VERSION));
536        assert!(is_known_protocol_version(MIN_PROTOCOL_VERSION));
537        assert!(!is_known_protocol_version("1999-01-01"));
538        // Newest-first ordering: the preferred revision leads the list.
539        assert_eq!(SUPPORTED_PROTOCOL_VERSIONS[0], PREFERRED_PROTOCOL_VERSION);
540    }
541
542    #[test]
543    fn test_resources_list_deserialization() {
544        let json = r#"{
545            "resources": [
546                {"uri": "file:///a.txt", "name": "A", "mimeType": "text/plain"},
547                {"uri": "mem://b"}
548            ],
549            "nextCursor": "page2"
550        }"#;
551        let parsed: ResourcesListResult = serde_json::from_str(json).expect("deserialize");
552        assert_eq!(parsed.resources.len(), 2);
553        assert_eq!(parsed.resources[0].uri, "file:///a.txt");
554        assert_eq!(parsed.resources[0].mime_type.as_deref(), Some("text/plain"));
555        assert_eq!(parsed.resources[1].name, None);
556        assert_eq!(parsed.next_cursor.as_deref(), Some("page2"));
557    }
558
559    #[test]
560    fn test_resource_read_text_and_blob() {
561        let json = r#"{
562            "contents": [
563                {"uri": "file:///a.txt", "mimeType": "text/plain", "text": "hello"},
564                {"uri": "file:///b.bin", "blob": "AAAA"}
565            ]
566        }"#;
567        let parsed: ResourceReadResult = serde_json::from_str(json).expect("deserialize");
568        assert_eq!(parsed.contents.len(), 2);
569        assert_eq!(parsed.contents[0].text.as_deref(), Some("hello"));
570        assert_eq!(parsed.contents[1].blob.as_deref(), Some("AAAA"));
571        assert_eq!(parsed.contents[1].text, None);
572    }
573
574    #[test]
575    fn test_prompts_list_deserialization() {
576        let json = r#"{
577            "prompts": [
578                {
579                    "name": "summarize",
580                    "description": "Summarize text",
581                    "arguments": [
582                        {"name": "text", "required": true},
583                        {"name": "tone", "description": "voice", "required": false}
584                    ]
585                }
586            ]
587        }"#;
588        let parsed: PromptsListResult = serde_json::from_str(json).expect("deserialize");
589        assert_eq!(parsed.prompts.len(), 1);
590        assert_eq!(parsed.prompts[0].name, "summarize");
591        assert_eq!(parsed.prompts[0].arguments.len(), 2);
592        assert!(parsed.prompts[0].arguments[0].required);
593        assert!(!parsed.prompts[0].arguments[1].required);
594    }
595
596    #[test]
597    fn test_prompt_get_messages() {
598        let json = r#"{
599            "description": "rendered",
600            "messages": [
601                {"role": "user", "content": {"type": "text", "text": "hi"}},
602                {"role": "assistant", "content": {"type": "text", "text": "hello"}}
603            ]
604        }"#;
605        let parsed: PromptGetResult = serde_json::from_str(json).expect("deserialize");
606        assert_eq!(parsed.messages.len(), 2);
607        assert_eq!(parsed.messages[0].role, McpRole::User);
608        assert_eq!(parsed.messages[1].role, McpRole::Assistant);
609        match &parsed.messages[0].content {
610            McpContent::Text { text } => assert_eq!(text, "hi"),
611            _ => panic!("expected text content"),
612        }
613    }
614}