Skip to main content

mcpkit_core/
capability.rs

1//! Capability flags for MCP clients and servers.
2//!
3//! Capabilities are negotiated during the initialization handshake.
4//! They determine what features are available in the session.
5
6use crate::extension::ExtensionRegistry;
7use crate::types::Icon;
8use crate::types::meta::Meta;
9use serde::{Deserialize, Serialize};
10
11/// Server capabilities advertised during initialization.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct ServerCapabilities {
14    /// Tool capabilities.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub tools: Option<ToolCapability>,
17    /// Resource capabilities.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub resources: Option<ResourceCapability>,
20    /// Prompt capabilities.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub prompts: Option<PromptCapability>,
23    /// Task capabilities (2025-11-25).
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub tasks: Option<TasksCapability>,
26    /// Logging capabilities.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub logging: Option<LoggingCapability>,
29    /// Completion capabilities.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub completions: Option<CompletionCapability>,
32    /// Experimental capabilities.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub experimental: Option<serde_json::Value>,
35}
36
37impl ServerCapabilities {
38    /// Create empty capabilities.
39    #[must_use]
40    pub fn new() -> Self {
41        Self::default()
42    }
43
44    /// Enable tool support.
45    #[must_use]
46    pub fn with_tools(mut self) -> Self {
47        self.tools = Some(ToolCapability::default());
48        self
49    }
50
51    /// Enable tool support with change notifications.
52    #[must_use]
53    pub const fn with_tools_and_changes(mut self) -> Self {
54        self.tools = Some(ToolCapability {
55            list_changed: Some(true),
56        });
57        self
58    }
59
60    /// Enable resource support.
61    #[must_use]
62    pub fn with_resources(mut self) -> Self {
63        self.resources = Some(ResourceCapability::default());
64        self
65    }
66
67    /// Enable resource support with subscriptions.
68    #[must_use]
69    pub const fn with_resources_and_subscriptions(mut self) -> Self {
70        self.resources = Some(ResourceCapability {
71            subscribe: Some(true),
72            list_changed: Some(true),
73        });
74        self
75    }
76
77    /// Enable prompt support.
78    #[must_use]
79    pub fn with_prompts(mut self) -> Self {
80        self.prompts = Some(PromptCapability::default());
81        self
82    }
83
84    /// Enable task support (`tasks/list` and `tasks/cancel`).
85    ///
86    /// This declares the base task operations only. It does **not** advertise
87    /// task augmentation for any request type — a server whose tools accept
88    /// task-augmented `tools/call` must additionally declare it via
89    /// [`with_task_tools`](Self::with_task_tools).
90    #[must_use]
91    pub fn with_tasks(mut self) -> Self {
92        let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
93        tasks.list = Some(serde_json::json!({}));
94        tasks.cancel = Some(serde_json::json!({}));
95        self
96    }
97
98    /// Declare task-augmented `tools/call` support
99    /// (`tasks.requests.tools.call`).
100    #[must_use]
101    pub fn with_task_tools(mut self) -> Self {
102        self.tasks
103            .get_or_insert_with(TasksCapability::default)
104            .requests
105            .get_or_insert_with(TaskRequestsCapability::default)
106            .tools
107            .get_or_insert_with(ToolsTaskCapability::default)
108            .call = Some(serde_json::json!({}));
109        self
110    }
111
112    /// Enable logging support.
113    #[must_use]
114    pub const fn with_logging(mut self) -> Self {
115        self.logging = Some(LoggingCapability {});
116        self
117    }
118
119    /// Enable completion support.
120    #[must_use]
121    pub const fn with_completions(mut self) -> Self {
122        self.completions = Some(CompletionCapability {});
123        self
124    }
125
126    /// Check if tools are supported.
127    #[must_use]
128    pub const fn has_tools(&self) -> bool {
129        self.tools.is_some()
130    }
131
132    /// Check if resources are supported.
133    #[must_use]
134    pub const fn has_resources(&self) -> bool {
135        self.resources.is_some()
136    }
137
138    /// Check if prompts are supported.
139    #[must_use]
140    pub const fn has_prompts(&self) -> bool {
141        self.prompts.is_some()
142    }
143
144    /// Check if tasks are supported.
145    #[must_use]
146    pub const fn has_tasks(&self) -> bool {
147        self.tasks.is_some()
148    }
149
150    /// Check if completions are supported.
151    #[must_use]
152    pub const fn has_completions(&self) -> bool {
153        self.completions.is_some()
154    }
155
156    /// Check if logging is supported.
157    #[must_use]
158    pub const fn has_logging(&self) -> bool {
159        self.logging.is_some()
160    }
161
162    /// Check if resource subscriptions are supported.
163    #[must_use]
164    pub fn has_resource_subscribe(&self) -> bool {
165        self.resources
166            .as_ref()
167            .and_then(|r| r.subscribe)
168            .unwrap_or(false)
169    }
170
171    /// Set extensions from an extension registry.
172    ///
173    /// This populates the `experimental` field with extension declarations.
174    ///
175    /// # Arguments
176    ///
177    /// * `registry` - The extension registry containing extensions to advertise
178    ///
179    /// # Example
180    ///
181    /// ```rust
182    /// use mcpkit_core::capability::ServerCapabilities;
183    /// use mcpkit_core::extension::{Extension, ExtensionRegistry};
184    ///
185    /// let registry = ExtensionRegistry::new()
186    ///     .register(Extension::new("com.example.myext").with_version("0.1.0"));
187    ///
188    /// let caps = ServerCapabilities::new()
189    ///     .with_tools()
190    ///     .with_extensions(registry);
191    ///
192    /// assert!(caps.has_extension("com.example.myext"));
193    /// ```
194    #[must_use]
195    pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
196        if !registry.is_empty() {
197            self.experimental = Some(registry.to_experimental());
198        }
199        self
200    }
201
202    /// Check if a specific extension is supported.
203    ///
204    /// # Arguments
205    ///
206    /// * `name` - The extension name to check
207    #[must_use]
208    pub fn has_extension(&self, name: &str) -> bool {
209        self.experimental
210            .as_ref()
211            .and_then(ExtensionRegistry::from_experimental)
212            .is_some_and(|registry| registry.has(name))
213    }
214
215    /// Get the extension registry from capabilities.
216    ///
217    /// Returns `None` if no extensions are declared or if parsing fails.
218    #[must_use]
219    pub fn extensions(&self) -> Option<ExtensionRegistry> {
220        self.experimental
221            .as_ref()
222            .and_then(ExtensionRegistry::from_experimental)
223    }
224}
225
226/// Client capabilities advertised during initialization.
227#[derive(Debug, Clone, Default, Serialize, Deserialize)]
228pub struct ClientCapabilities {
229    /// Roots (file system access) capabilities.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub roots: Option<RootsCapability>,
232    /// Sampling capabilities.
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub sampling: Option<SamplingCapability>,
235    /// Elicitation capabilities.
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub elicitation: Option<ElicitationCapability>,
238    /// Task capabilities (2025-11-25): which client-handled requests accept
239    /// task augmentation.
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub tasks: Option<TasksCapability>,
242    /// Experimental capabilities.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub experimental: Option<serde_json::Value>,
245}
246
247impl ClientCapabilities {
248    /// Create empty capabilities.
249    #[must_use]
250    pub fn new() -> Self {
251        Self::default()
252    }
253
254    /// Enable roots support.
255    #[must_use]
256    pub fn with_roots(mut self) -> Self {
257        self.roots = Some(RootsCapability::default());
258        self
259    }
260
261    /// Enable roots support with change notifications.
262    #[must_use]
263    pub const fn with_roots_and_changes(mut self) -> Self {
264        self.roots = Some(RootsCapability {
265            list_changed: Some(true),
266        });
267        self
268    }
269
270    /// Enable sampling support.
271    ///
272    /// Not `const`: `SamplingCapability` now holds `Option<serde_json::Value>`
273    /// sub-capabilities, whose destructor is not const-evaluable.
274    #[must_use]
275    pub fn with_sampling(mut self) -> Self {
276        self.sampling = Some(SamplingCapability::default());
277        self
278    }
279
280    /// Enable sampling with tool-use support (declares `sampling.tools`).
281    #[must_use]
282    pub fn with_sampling_tools(mut self) -> Self {
283        let sampling = self
284            .sampling
285            .get_or_insert_with(SamplingCapability::default);
286        sampling.tools = Some(serde_json::json!({}));
287        self
288    }
289
290    /// Enable sampling with context-inclusion support (declares
291    /// `sampling.context`).
292    #[must_use]
293    pub fn with_sampling_context(mut self) -> Self {
294        let sampling = self
295            .sampling
296            .get_or_insert_with(SamplingCapability::default);
297        sampling.context = Some(serde_json::json!({}));
298        self
299    }
300
301    /// Enable elicitation support (form mode).
302    ///
303    /// This declares `elicitation: {}`, which is form-capable (the 2025-06-18
304    /// behaviour). Use [`with_url_elicitation`](Self::with_url_elicitation) to
305    /// additionally declare URL-mode support.
306    #[must_use]
307    pub fn with_elicitation(mut self) -> Self {
308        self.elicitation = Some(ElicitationCapability {
309            form: None,
310            url: None,
311        });
312        self
313    }
314
315    /// Declare form-mode elicitation support explicitly (`elicitation.form`).
316    #[must_use]
317    pub fn with_form_elicitation(mut self) -> Self {
318        self.elicitation
319            .get_or_insert_with(ElicitationCapability::default)
320            .form = Some(serde_json::json!({}));
321        self
322    }
323
324    /// Declare URL-mode elicitation support (`elicitation.url`).
325    #[must_use]
326    pub fn with_url_elicitation(mut self) -> Self {
327        self.elicitation
328            .get_or_insert_with(ElicitationCapability::default)
329            .url = Some(serde_json::json!({}));
330        self
331    }
332
333    /// Enable task support (`tasks/list` and `tasks/cancel`).
334    ///
335    /// This declares the base task operations only; declare which requests
336    /// accept task augmentation via
337    /// [`with_task_sampling`](Self::with_task_sampling).
338    #[must_use]
339    pub fn with_tasks(mut self) -> Self {
340        let tasks = self.tasks.get_or_insert_with(TasksCapability::default);
341        tasks.list = Some(serde_json::json!({}));
342        tasks.cancel = Some(serde_json::json!({}));
343        self
344    }
345
346    /// Declare task-augmented `sampling/createMessage` support
347    /// (`tasks.requests.sampling.createMessage`).
348    ///
349    /// Declare plain sampling support separately via
350    /// [`with_sampling`](Self::with_sampling).
351    #[must_use]
352    pub fn with_task_sampling(mut self) -> Self {
353        self.tasks
354            .get_or_insert_with(TasksCapability::default)
355            .requests
356            .get_or_insert_with(TaskRequestsCapability::default)
357            .sampling
358            .get_or_insert_with(SamplingTaskCapability::default)
359            .create_message = Some(serde_json::json!({}));
360        self
361    }
362
363    /// Check if task-augmented `sampling/createMessage` is declared.
364    #[must_use]
365    pub fn has_task_sampling(&self) -> bool {
366        self.tasks
367            .as_ref()
368            .and_then(|t| t.requests.as_ref())
369            .and_then(|r| r.sampling.as_ref())
370            .is_some_and(|s| s.create_message.is_some())
371    }
372
373    /// Check if form-mode elicitation is supported.
374    ///
375    /// True when `elicitation.form` is declared, or when `elicitation` is present
376    /// but empty (`{}`), which is form-capable for backwards compatibility.
377    #[must_use]
378    pub fn has_form_elicitation(&self) -> bool {
379        self.elicitation
380            .as_ref()
381            .is_some_and(ElicitationCapability::has_form)
382    }
383
384    /// Check if URL-mode elicitation is supported (`elicitation.url` declared).
385    #[must_use]
386    pub fn has_url_elicitation(&self) -> bool {
387        self.elicitation
388            .as_ref()
389            .is_some_and(ElicitationCapability::has_url)
390    }
391
392    /// Check if roots are supported.
393    #[must_use]
394    pub const fn has_roots(&self) -> bool {
395        self.roots.is_some()
396    }
397
398    /// Check if sampling is supported.
399    #[must_use]
400    pub const fn has_sampling(&self) -> bool {
401        self.sampling.is_some()
402    }
403
404    /// Whether the client declared tool-use support in sampling
405    /// (`sampling.tools`).
406    #[must_use]
407    pub const fn has_sampling_tools(&self) -> bool {
408        matches!(&self.sampling, Some(s) if s.tools.is_some())
409    }
410
411    /// Whether the client declared context-inclusion support in sampling
412    /// (`sampling.context`).
413    #[must_use]
414    pub const fn has_sampling_context(&self) -> bool {
415        matches!(&self.sampling, Some(s) if s.context.is_some())
416    }
417
418    /// Check if elicitation is supported.
419    #[must_use]
420    pub const fn has_elicitation(&self) -> bool {
421        self.elicitation.is_some()
422    }
423
424    /// Set extensions from an extension registry.
425    ///
426    /// This populates the `experimental` field with extension declarations.
427    #[must_use]
428    pub fn with_extensions(mut self, registry: ExtensionRegistry) -> Self {
429        if !registry.is_empty() {
430            self.experimental = Some(registry.to_experimental());
431        }
432        self
433    }
434
435    /// Check if a specific extension is supported.
436    #[must_use]
437    pub fn has_extension(&self, name: &str) -> bool {
438        self.experimental
439            .as_ref()
440            .and_then(ExtensionRegistry::from_experimental)
441            .is_some_and(|registry| registry.has(name))
442    }
443
444    /// Get the extension registry from capabilities.
445    #[must_use]
446    pub fn extensions(&self) -> Option<ExtensionRegistry> {
447        self.experimental
448            .as_ref()
449            .and_then(ExtensionRegistry::from_experimental)
450    }
451}
452
453/// Tool capability flags.
454#[derive(Debug, Clone, Default, Serialize, Deserialize)]
455pub struct ToolCapability {
456    /// If true, the server will send tool list changed notifications.
457    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
458    pub list_changed: Option<bool>,
459}
460
461/// Resource capability flags.
462#[derive(Debug, Clone, Default, Serialize, Deserialize)]
463pub struct ResourceCapability {
464    /// If true, the server supports resource subscriptions.
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub subscribe: Option<bool>,
467    /// If true, the server will send resource list changed notifications.
468    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
469    pub list_changed: Option<bool>,
470}
471
472/// Prompt capability flags.
473#[derive(Debug, Clone, Default, Serialize, Deserialize)]
474pub struct PromptCapability {
475    /// If true, the server will send prompt list changed notifications.
476    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
477    pub list_changed: Option<bool>,
478}
479
480/// `capabilities.tasks` (2025-11-25).
481///
482/// Declared by whichever side *receives* task-augmented requests: servers
483/// declare it for `tools/call`, clients for `sampling/createMessage` /
484/// `elicitation/create`. Per the spec, the `requests` set is exhaustive —
485/// a request type not listed does not support task augmentation.
486#[derive(Debug, Clone, Default, Serialize, Deserialize)]
487pub struct TasksCapability {
488    /// Whether this party supports `tasks/list`.
489    #[serde(skip_serializing_if = "Option::is_none")]
490    pub list: Option<serde_json::Value>,
491    /// Whether this party supports `tasks/cancel`.
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub cancel: Option<serde_json::Value>,
494    /// Which request types can be augmented with tasks.
495    #[serde(skip_serializing_if = "Option::is_none")]
496    pub requests: Option<TaskRequestsCapability>,
497}
498
499/// `capabilities.tasks.requests`: the request types that accept a `task`
500/// field, by category.
501#[derive(Debug, Clone, Default, Serialize, Deserialize)]
502pub struct TaskRequestsCapability {
503    /// Task support for sampling requests (declared by clients).
504    #[serde(skip_serializing_if = "Option::is_none")]
505    pub sampling: Option<SamplingTaskCapability>,
506    /// Task support for elicitation requests (declared by clients).
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub elicitation: Option<ElicitationTaskCapability>,
509    /// Task support for tool requests (declared by servers).
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub tools: Option<ToolsTaskCapability>,
512}
513
514/// `capabilities.tasks.requests.sampling`.
515#[derive(Debug, Clone, Default, Serialize, Deserialize)]
516pub struct SamplingTaskCapability {
517    /// Whether task-augmented `sampling/createMessage` is supported.
518    #[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
519    pub create_message: Option<serde_json::Value>,
520}
521
522/// `capabilities.tasks.requests.elicitation`.
523#[derive(Debug, Clone, Default, Serialize, Deserialize)]
524pub struct ElicitationTaskCapability {
525    /// Whether task-augmented `elicitation/create` is supported.
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub create: Option<serde_json::Value>,
528}
529
530/// `capabilities.tasks.requests.tools`.
531#[derive(Debug, Clone, Default, Serialize, Deserialize)]
532pub struct ToolsTaskCapability {
533    /// Whether task-augmented `tools/call` is supported.
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub call: Option<serde_json::Value>,
536}
537
538/// Logging capability flags.
539#[derive(Debug, Clone, Default, Serialize, Deserialize)]
540pub struct LoggingCapability {}
541
542/// Completion capability flags.
543#[derive(Debug, Clone, Default, Serialize, Deserialize)]
544pub struct CompletionCapability {}
545
546/// Roots capability flags.
547#[derive(Debug, Clone, Default, Serialize, Deserialize)]
548pub struct RootsCapability {
549    /// If true, the client will send roots list changed notifications.
550    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
551    pub list_changed: Option<bool>,
552}
553
554/// Sampling capability flags.
555#[derive(Debug, Clone, Default, Serialize, Deserialize)]
556pub struct SamplingCapability {
557    /// Declared support for context inclusion (the `includeContext` parameter).
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub context: Option<serde_json::Value>,
560    /// Declared support for tool use in sampling requests.
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub tools: Option<serde_json::Value>,
563}
564
565/// Elicitation capability flags.
566#[derive(Debug, Clone, Default, Serialize, Deserialize)]
567pub struct ElicitationCapability {
568    /// Declared support for form-mode elicitation. An absent `form` and `url`
569    /// (an empty `{}`) is treated as form-capable for backwards compatibility.
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub form: Option<serde_json::Value>,
572    /// Declared support for URL-mode elicitation.
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub url: Option<serde_json::Value>,
575}
576
577impl ElicitationCapability {
578    /// Whether form-mode elicitation is supported (`form` declared, or empty
579    /// `{}` which is form-capable for backwards compatibility).
580    #[must_use]
581    pub const fn has_form(&self) -> bool {
582        self.form.is_some() || self.url.is_none()
583    }
584
585    /// Whether URL-mode elicitation is supported (`url` declared).
586    #[must_use]
587    pub const fn has_url(&self) -> bool {
588        self.url.is_some()
589    }
590}
591
592/// Server information provided during initialization.
593#[derive(Debug, Clone, Serialize, Deserialize)]
594pub struct ServerInfo {
595    /// Server name.
596    pub name: String,
597    /// Optional human-readable display title.
598    #[serde(skip_serializing_if = "Option::is_none")]
599    pub title: Option<String>,
600    /// Server version.
601    pub version: String,
602    /// Protocol version supported.
603    #[serde(rename = "protocolVersion", skip_serializing_if = "Option::is_none")]
604    pub protocol_version: Option<String>,
605    /// Optional human-readable description of what this server does.
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub description: Option<String>,
608    /// Optional URL of the server's website.
609    #[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
610    pub website_url: Option<String>,
611    /// Optional icons the client can display for this server.
612    #[serde(skip_serializing_if = "Option::is_none")]
613    pub icons: Option<Vec<Icon>>,
614}
615
616impl ServerInfo {
617    /// Create new server info.
618    #[must_use]
619    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
620        Self {
621            name: name.into(),
622            title: None,
623            version: version.into(),
624            protocol_version: Some(PROTOCOL_VERSION.to_string()),
625            description: None,
626            website_url: None,
627            icons: None,
628        }
629    }
630
631    /// Set the server's description.
632    #[must_use]
633    pub fn description(mut self, description: impl Into<String>) -> Self {
634        self.description = Some(description.into());
635        self
636    }
637
638    /// Set the server's website URL.
639    #[must_use]
640    pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
641        self.website_url = Some(website_url.into());
642        self
643    }
644
645    /// Set the server's display title.
646    #[must_use]
647    pub fn title(mut self, title: impl Into<String>) -> Self {
648        self.title = Some(title.into());
649        self
650    }
651
652    /// Add an icon the client can display for this server.
653    #[must_use]
654    pub fn icon(mut self, icon: Icon) -> Self {
655        self.icons.get_or_insert_with(Vec::new).push(icon);
656        self
657    }
658
659    /// Set the server's icons, replacing any already set.
660    #[must_use]
661    pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
662        self.icons = Some(icons.into_iter().collect());
663        self
664    }
665}
666
667/// Client information provided during initialization.
668#[derive(Debug, Clone, Serialize, Deserialize)]
669pub struct ClientInfo {
670    /// Client name.
671    pub name: String,
672    /// Optional human-readable display title.
673    #[serde(skip_serializing_if = "Option::is_none")]
674    pub title: Option<String>,
675    /// Client version.
676    pub version: String,
677    /// Optional human-readable description of this client.
678    #[serde(skip_serializing_if = "Option::is_none")]
679    pub description: Option<String>,
680    /// Optional URL of the client's website.
681    #[serde(rename = "websiteUrl", skip_serializing_if = "Option::is_none")]
682    pub website_url: Option<String>,
683    /// Optional icons the server can display for this client.
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub icons: Option<Vec<Icon>>,
686}
687
688impl ClientInfo {
689    /// Create new client info.
690    #[must_use]
691    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
692        Self {
693            name: name.into(),
694            title: None,
695            version: version.into(),
696            description: None,
697            website_url: None,
698            icons: None,
699        }
700    }
701
702    /// Set the client's description.
703    #[must_use]
704    pub fn description(mut self, description: impl Into<String>) -> Self {
705        self.description = Some(description.into());
706        self
707    }
708
709    /// Set the client's website URL.
710    #[must_use]
711    pub fn website_url(mut self, website_url: impl Into<String>) -> Self {
712        self.website_url = Some(website_url.into());
713        self
714    }
715
716    /// Set the client's display title.
717    #[must_use]
718    pub fn title(mut self, title: impl Into<String>) -> Self {
719        self.title = Some(title.into());
720        self
721    }
722
723    /// Add an icon the server can display for this client.
724    #[must_use]
725    pub fn icon(mut self, icon: Icon) -> Self {
726        self.icons.get_or_insert_with(Vec::new).push(icon);
727        self
728    }
729
730    /// Set the client's icons, replacing any already set.
731    #[must_use]
732    pub fn icons(mut self, icons: impl IntoIterator<Item = Icon>) -> Self {
733        self.icons = Some(icons.into_iter().collect());
734        self
735    }
736}
737
738/// Initialize request parameters.
739#[derive(Debug, Clone, Serialize, Deserialize)]
740pub struct InitializeRequest {
741    /// Protocol version the client supports.
742    #[serde(rename = "protocolVersion")]
743    pub protocol_version: String,
744    /// Client capabilities.
745    pub capabilities: ClientCapabilities,
746    /// Client information.
747    #[serde(rename = "clientInfo")]
748    pub client_info: ClientInfo,
749    /// Optional protocol metadata (`_meta`).
750    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
751    pub meta: Option<Meta>,
752}
753
754impl InitializeRequest {
755    /// Create a new initialize request.
756    #[must_use]
757    pub fn new(client_info: ClientInfo, capabilities: ClientCapabilities) -> Self {
758        Self {
759            protocol_version: PROTOCOL_VERSION.to_string(),
760            capabilities,
761            client_info,
762            meta: None,
763        }
764    }
765}
766
767/// Initialize response.
768#[derive(Debug, Clone, Serialize, Deserialize)]
769pub struct InitializeResult {
770    /// Protocol version the server supports.
771    #[serde(rename = "protocolVersion")]
772    pub protocol_version: String,
773    /// Server capabilities.
774    pub capabilities: ServerCapabilities,
775    /// Server information.
776    #[serde(rename = "serverInfo")]
777    pub server_info: ServerInfo,
778    /// Optional instructions for using this server.
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub instructions: Option<String>,
781    /// Optional protocol metadata (`_meta`).
782    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
783    pub meta: Option<Meta>,
784}
785
786impl InitializeResult {
787    /// Create a new initialize result.
788    #[must_use]
789    pub fn new(server_info: ServerInfo, capabilities: ServerCapabilities) -> Self {
790        Self {
791            protocol_version: PROTOCOL_VERSION.to_string(),
792            capabilities,
793            server_info,
794            instructions: None,
795            meta: None,
796        }
797    }
798
799    /// Set instructions.
800    #[must_use]
801    pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
802        self.instructions = Some(instructions.into());
803        self
804    }
805}
806
807/// The latest protocol version supported by this implementation.
808///
809/// This is the preferred version that clients and servers will advertise during initialization.
810pub const PROTOCOL_VERSION: &str = "2025-11-25";
811
812/// All protocol versions supported by this implementation.
813///
814/// The SDK supports multiple protocol versions for backward compatibility:
815/// - `2025-11-25`: Latest version with tasks, parallel tools, agent loops
816/// - `2025-06-18`: Elicitation, structured output, resource links
817/// - `2025-03-26`: OAuth 2.1, Streamable HTTP, tool annotations, audio
818/// - `2024-11-05`: Original MCP specification, widely deployed
819///
820/// Version negotiation happens during initialization:
821/// 1. Client sends its preferred (latest) version
822/// 2. Server responds with the same version if supported, or its own preferred version
823/// 3. Client must support the server's version or disconnect
824///
825/// For type-safe version handling, use [`crate::protocol_version::ProtocolVersion`].
826///
827/// # Example
828///
829/// ```
830/// use mcpkit_core::capability::{SUPPORTED_PROTOCOL_VERSIONS, is_version_supported};
831///
832/// assert!(is_version_supported("2025-11-25"));
833/// assert!(is_version_supported("2025-06-18"));
834/// assert!(is_version_supported("2025-03-26"));
835/// assert!(is_version_supported("2024-11-05"));
836/// assert!(!is_version_supported("1.0.0"));
837/// ```
838pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
839    "2025-11-25", // Latest - tasks, parallel tools, agent loops
840    "2025-06-18", // Elicitation, structured output, resource links
841    "2025-03-26", // OAuth 2.1, Streamable HTTP, tool annotations
842    "2024-11-05", // Original MCP spec - widely deployed
843];
844
845/// Check if a protocol version is supported by this implementation.
846///
847/// # Arguments
848///
849/// * `version` - The protocol version string to check
850///
851/// # Returns
852///
853/// `true` if the version is in the list of supported versions, `false` otherwise.
854///
855/// # Example
856///
857/// ```
858/// use mcpkit_core::capability::is_version_supported;
859///
860/// assert!(is_version_supported("2025-11-25"));
861/// assert!(!is_version_supported("0.9.0"));
862/// ```
863#[must_use]
864pub fn is_version_supported(version: &str) -> bool {
865    SUPPORTED_PROTOCOL_VERSIONS.contains(&version)
866}
867
868/// Negotiate a protocol version between client and server.
869///
870/// Per the MCP specification:
871/// - If the requested version is supported, return it
872/// - Otherwise, return the server's preferred (latest) version
873///
874/// The client is then responsible for determining if it can support
875/// the returned version, and disconnecting if not.
876///
877/// # Arguments
878///
879/// * `requested_version` - The version requested by the client
880///
881/// # Returns
882///
883/// The negotiated protocol version string.
884///
885/// # Example
886///
887/// ```
888/// use mcpkit_core::capability::{negotiate_version, PROTOCOL_VERSION};
889///
890/// // Client requests a supported version - gets it back
891/// assert_eq!(negotiate_version("2024-11-05"), "2024-11-05");
892///
893/// // Client requests the latest version - gets it back
894/// assert_eq!(negotiate_version("2025-11-25"), "2025-11-25");
895///
896/// // Client requests unknown version - gets server's preferred version
897/// assert_eq!(negotiate_version("1.0.0"), PROTOCOL_VERSION);
898/// ```
899#[must_use]
900pub fn negotiate_version(requested_version: &str) -> &'static str {
901    if is_version_supported(requested_version) {
902        // Return the requested version if we support it
903        SUPPORTED_PROTOCOL_VERSIONS
904            .iter()
905            .find(|&&v| v == requested_version)
906            .copied()
907            .unwrap_or(PROTOCOL_VERSION)
908    } else {
909        // Return our preferred (latest) version
910        PROTOCOL_VERSION
911    }
912}
913
914/// Protocol version negotiation result.
915///
916/// Used internally to track the outcome of version negotiation.
917#[derive(Debug, Clone, PartialEq, Eq)]
918pub enum VersionNegotiationResult {
919    /// The requested version is supported and will be used.
920    Accepted(String),
921    /// The requested version is not supported; the server offers an alternative.
922    /// Client should check if it supports this alternative version.
923    CounterOffer {
924        /// The version requested by the client.
925        requested: String,
926        /// The version offered by the server.
927        offered: String,
928    },
929}
930
931impl VersionNegotiationResult {
932    /// Get the effective protocol version to use.
933    #[must_use]
934    pub fn version(&self) -> &str {
935        match self {
936            Self::Accepted(v) => v,
937            Self::CounterOffer { offered, .. } => offered,
938        }
939    }
940
941    /// Check if the negotiation was an exact match.
942    #[must_use]
943    pub const fn is_exact_match(&self) -> bool {
944        matches!(self, Self::Accepted(_))
945    }
946}
947
948/// Perform version negotiation and return detailed result.
949///
950/// This is useful when you need to know whether the negotiation
951/// resulted in an exact match or a counter-offer.
952///
953/// # Arguments
954///
955/// * `requested_version` - The version requested by the client
956///
957/// # Returns
958///
959/// A [`VersionNegotiationResult`] indicating whether the version was
960/// accepted or a counter-offer was made.
961///
962/// # Example
963///
964/// ```
965/// use mcpkit_core::capability::{negotiate_version_detailed, VersionNegotiationResult};
966///
967/// let result = negotiate_version_detailed("2024-11-05");
968/// assert!(result.is_exact_match());
969///
970/// let result = negotiate_version_detailed("unknown-version");
971/// assert!(!result.is_exact_match());
972/// ```
973#[must_use]
974pub fn negotiate_version_detailed(requested_version: &str) -> VersionNegotiationResult {
975    if is_version_supported(requested_version) {
976        VersionNegotiationResult::Accepted(requested_version.to_string())
977    } else {
978        VersionNegotiationResult::CounterOffer {
979            requested: requested_version.to_string(),
980            offered: PROTOCOL_VERSION.to_string(),
981        }
982    }
983}
984
985/// Initialized notification (sent by client after receiving initialize result).
986#[derive(Debug, Clone, Default, Serialize, Deserialize)]
987pub struct InitializedNotification {
988    /// Optional protocol metadata (`_meta`).
989    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
990    pub meta: Option<Meta>,
991}
992
993/// Ping request for keep-alive.
994#[derive(Debug, Clone, Default, Serialize, Deserialize)]
995pub struct PingRequest {
996    /// Optional protocol metadata (`_meta`).
997    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
998    pub meta: Option<Meta>,
999}
1000
1001/// Ping response.
1002#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1003pub struct PingResult {
1004    /// Optional protocol metadata (`_meta`).
1005    #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
1006    pub meta: Option<Meta>,
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011    #[test]
1012    fn elicitation_form_url_capability_semantics() {
1013        use super::ClientCapabilities;
1014        // Empty `{}` (legacy `with_elicitation`) is form-capable, not url.
1015        let form = ClientCapabilities::default().with_elicitation();
1016        assert!(form.has_elicitation());
1017        assert!(form.has_form_elicitation());
1018        assert!(!form.has_url_elicitation());
1019        assert_eq!(
1020            serde_json::to_value(&form.elicitation).unwrap(),
1021            serde_json::json!({}),
1022            "empty elicitation must still serialize as {{}} for compatibility"
1023        );
1024
1025        // URL-only: url present, form not.
1026        let url = ClientCapabilities::default().with_url_elicitation();
1027        assert!(url.has_url_elicitation());
1028        assert!(!url.has_form_elicitation());
1029
1030        // Both.
1031        let both = ClientCapabilities::default()
1032            .with_form_elicitation()
1033            .with_url_elicitation();
1034        assert!(both.has_form_elicitation());
1035        assert!(both.has_url_elicitation());
1036    }
1037
1038    use super::*;
1039
1040    #[test]
1041    fn test_server_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
1042        let caps = ServerCapabilities::new()
1043            .with_tools()
1044            .with_resources_and_subscriptions()
1045            .with_prompts()
1046            .with_tasks();
1047
1048        assert!(caps.has_tools());
1049        assert!(caps.has_resources());
1050        assert!(caps.has_prompts());
1051        assert!(caps.has_tasks());
1052        assert!(
1053            caps.resources
1054                .ok_or("Expected resources")?
1055                .subscribe
1056                .ok_or("Expected subscribe")?
1057        );
1058        Ok(())
1059    }
1060
1061    #[test]
1062    fn test_client_capabilities_builder() -> Result<(), Box<dyn std::error::Error>> {
1063        let caps = ClientCapabilities::new()
1064            .with_roots_and_changes()
1065            .with_sampling()
1066            .with_elicitation();
1067
1068        assert!(caps.has_roots());
1069        assert!(caps.has_sampling());
1070        assert!(caps.has_elicitation());
1071        assert!(
1072            caps.roots
1073                .ok_or("Expected roots")?
1074                .list_changed
1075                .ok_or("Expected list_changed")?
1076        );
1077        Ok(())
1078    }
1079
1080    #[test]
1081    fn test_initialize_request() {
1082        let client = ClientInfo::new("test-client", "1.0.0");
1083        let caps = ClientCapabilities::new().with_sampling();
1084        let request = InitializeRequest::new(client, caps);
1085
1086        assert_eq!(request.protocol_version, PROTOCOL_VERSION);
1087        assert_eq!(request.client_info.name, "test-client");
1088    }
1089
1090    #[test]
1091    fn test_initialize_result() {
1092        let server = ServerInfo::new("test-server", "1.0.0");
1093        let caps = ServerCapabilities::new().with_tools();
1094        let result =
1095            InitializeResult::new(server, caps).instructions("Use this server to do things");
1096
1097        assert_eq!(result.protocol_version, PROTOCOL_VERSION);
1098        assert!(result.instructions.is_some());
1099    }
1100
1101    #[test]
1102    fn test_serialization() -> Result<(), Box<dyn std::error::Error>> {
1103        let caps = ServerCapabilities::new()
1104            .with_tools_and_changes()
1105            .with_resources();
1106
1107        let json = serde_json::to_string(&caps)?;
1108        assert!(json.contains("\"tools\""));
1109        assert!(json.contains("\"listChanged\":true"));
1110        Ok(())
1111    }
1112
1113    #[test]
1114    fn implementation_description_and_website_url_round_trip()
1115    -> Result<(), Box<dyn std::error::Error>> {
1116        let s = ServerInfo::new("s", "1.0")
1117            .description("does things")
1118            .website_url("https://example.com");
1119        let json = serde_json::to_value(&s)?;
1120        assert_eq!(json["description"], "does things");
1121        assert_eq!(json["websiteUrl"], "https://example.com");
1122
1123        let c: ClientInfo = serde_json::from_value(serde_json::json!({
1124            "name": "c", "version": "2.0",
1125            "description": "a client", "websiteUrl": "https://client.example"
1126        }))?;
1127        assert_eq!(c.description.as_deref(), Some("a client"));
1128        assert_eq!(c.website_url.as_deref(), Some("https://client.example"));
1129        Ok(())
1130    }
1131}