mcp_host/protocol/
capabilities.rs

1//! MCP capability negotiation types
2//!
3//! Capabilities define what features both client and server support.
4//! These are exchanged during the initialize handshake.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10use super::types::Implementation;
11
12/// Server capabilities
13#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct ServerCapabilities {
16    /// Logging capability
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub logging: Option<LoggingCapability>,
19
20    /// Tools capability
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub tools: Option<ToolsCapability>,
23
24    /// Resources capability
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub resources: Option<ResourcesCapability>,
27
28    /// Prompts capability
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub prompts: Option<PromptsCapability>,
31
32    /// Completion capability
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub completion: Option<CompletionCapability>,
35
36    /// Tasks capability (supports async task execution)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub tasks: Option<TasksCapability>,
39
40    /// Experimental/custom capabilities
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub experimental: Option<HashMap<String, Value>>,
43}
44
45/// Client capabilities
46#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48pub struct ClientCapabilities {
49    /// Roots capability
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub roots: Option<RootsCapability>,
52
53    /// Sampling capability (client can perform LLM sampling)
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub sampling: Option<SamplingCapability>,
56
57    /// Elicitation capability (client can request user input)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub elicitation: Option<ElicitationCapability>,
60
61    /// Tasks capability (supports async task execution)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub tasks: Option<TasksCapability>,
64
65    /// Experimental/custom capabilities
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub experimental: Option<HashMap<String, Value>>,
68}
69
70/// Logging capability
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct LoggingCapability {}
73
74/// Tools capability
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct ToolsCapability {
78    /// Whether tools support list changes notification
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub list_changed: Option<bool>,
81}
82
83/// Resources capability
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct ResourcesCapability {
87    /// Whether resources support subscription
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub subscribe: Option<bool>,
90
91    /// Whether resources support list changes notification
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub list_changed: Option<bool>,
94
95    /// Whether resource templates are supported
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub list_templates: Option<bool>,
98}
99
100/// Prompts capability
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct PromptsCapability {
104    /// Whether prompts support list changes notification
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub list_changed: Option<bool>,
107}
108
109/// Completion capability (autocomplete)
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct CompletionCapability {}
112
113/// Roots capability (client-provided workspace roots)
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct RootsCapability {
117    /// Whether roots support list changes notification
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub list_changed: Option<bool>,
120}
121
122/// Sampling capability
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct SamplingCapability {}
125
126/// Elicitation capability
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ElicitationCapability {}
129
130/// Tasks capability
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct TasksCapability {
134    /// Whether this party supports tasks/list
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub list: Option<EmptyObject>,
137
138    /// Whether this party supports tasks/cancel
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub cancel: Option<EmptyObject>,
141
142    /// Specifies which request types can be augmented with tasks
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub requests: Option<TasksRequestsCapability>,
145}
146
147/// Requests that can be task-augmented
148#[derive(Debug, Clone, Default, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct TasksRequestsCapability {
151    /// Task support for tool-related requests (server-side)
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub tools: Option<TasksToolsCapability>,
154
155    /// Task support for sampling-related requests (client-side)
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub sampling: Option<TasksSamplingCapability>,
158
159    /// Task support for elicitation-related requests (client-side)
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub elicitation: Option<TasksElicitationCapability>,
162}
163
164/// Task support for tool requests
165#[derive(Debug, Clone, Default, Serialize, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct TasksToolsCapability {
168    /// Whether tools/call supports task-augmented execution
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub call: Option<EmptyObject>,
171}
172
173/// Task support for sampling requests
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase")]
176pub struct TasksSamplingCapability {
177    /// Whether sampling/createMessage supports task-augmented execution
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub create_message: Option<EmptyObject>,
180}
181
182/// Task support for elicitation requests
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct TasksElicitationCapability {
186    /// Whether elicitation/create supports task-augmented execution
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub create: Option<EmptyObject>,
189}
190
191/// Empty object placeholder (used for boolean-like capabilities)
192#[derive(Debug, Clone, Default, Serialize, Deserialize)]
193pub struct EmptyObject {}
194
195/// Initialize request
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct InitializeRequest {
199    /// Protocol version
200    pub protocol_version: String,
201
202    /// Client capabilities
203    pub capabilities: ClientCapabilities,
204
205    /// Client implementation info
206    pub client_info: Implementation,
207}
208
209/// Initialize result
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct InitializeResult {
213    /// Protocol version (negotiated)
214    pub protocol_version: String,
215
216    /// Server capabilities
217    pub capabilities: ServerCapabilities,
218
219    /// Server implementation info
220    pub server_info: Implementation,
221
222    /// Optional server instructions
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub instructions: Option<String>,
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_server_capabilities_default() {
233        let caps = ServerCapabilities::default();
234        assert!(caps.tools.is_none());
235        assert!(caps.resources.is_none());
236    }
237
238    #[test]
239    fn test_server_capabilities_serialization() {
240        let caps = ServerCapabilities {
241            tools: Some(ToolsCapability {
242                list_changed: Some(true),
243            }),
244            resources: Some(ResourcesCapability {
245                subscribe: Some(true),
246                list_changed: Some(true),
247                list_templates: Some(true),
248            }),
249            ..Default::default()
250        };
251
252        let json = serde_json::to_value(&caps).unwrap();
253        assert!(json["tools"].is_object());
254        assert!(json["resources"].is_object());
255        assert_eq!(json["tools"]["listChanged"], true);
256        assert_eq!(json["resources"]["listTemplates"], true);
257    }
258
259    #[test]
260    fn test_initialize_request_deserialization() {
261        let json = serde_json::json!({
262            "protocolVersion": "2025-11-25",
263            "capabilities": {},
264            "clientInfo": {
265                "name": "test-client",
266                "version": "1.0.0"
267            }
268        });
269
270        let req: InitializeRequest = serde_json::from_value(json).unwrap();
271        assert_eq!(req.protocol_version, "2025-11-25");
272        assert_eq!(req.client_info.name, "test-client");
273    }
274}