Skip to main content

turbomcp_types/
protocol.rs

1//! MCP Protocol types for Tasks, Elicitation, and Sampling (MCP 2025-11-25).
2//!
3//! This module provides the specialized types introduced in the MCP 2025-11-25
4//! specification for advanced protocol features.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[cfg(not(feature = "std"))]
10use alloc::{collections::BTreeMap as HashMap, format, string::String, vec::Vec};
11#[cfg(feature = "std")]
12use std::collections::HashMap;
13
14use crate::content::{Role, SamplingContent, SamplingContentBlock};
15use crate::definitions::Tool;
16
17// =============================================================================
18// Tasks (SEP-1686)
19// =============================================================================
20
21/// Metadata for augmenting a request with task execution.
22#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
23pub struct TaskMetadata {
24    /// Requested duration in milliseconds to retain task from creation.
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub ttl: Option<u64>,
27}
28
29/// Data associated with a task.
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
31pub struct Task {
32    /// The task identifier.
33    #[serde(rename = "taskId")]
34    pub task_id: String,
35    /// Current task state.
36    pub status: TaskStatus,
37    /// Optional human-readable message describing the current task state.
38    #[serde(rename = "statusMessage", skip_serializing_if = "Option::is_none")]
39    pub status_message: Option<String>,
40    /// ISO 8601 timestamp when the task was created.
41    #[serde(rename = "createdAt")]
42    pub created_at: String,
43    /// ISO 8601 timestamp when the task was last updated.
44    #[serde(rename = "lastUpdatedAt")]
45    pub last_updated_at: String,
46    /// Actual retention duration from creation in milliseconds, null for unlimited.
47    pub ttl: Option<u64>,
48    /// Suggested polling interval in milliseconds.
49    #[serde(rename = "pollInterval", skip_serializing_if = "Option::is_none")]
50    pub poll_interval: Option<u64>,
51}
52
53/// The status of a task.
54#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
55#[serde(rename_all = "snake_case")]
56pub enum TaskStatus {
57    /// Task was cancelled.
58    Cancelled,
59    /// Task completed successfully.
60    Completed,
61    /// Task failed.
62    Failed,
63    /// Task requires additional input from the user.
64    InputRequired,
65    /// Task is currently running.
66    Working,
67}
68
69impl core::fmt::Display for TaskStatus {
70    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
71        match self {
72            Self::Cancelled => f.write_str("cancelled"),
73            Self::Completed => f.write_str("completed"),
74            Self::Failed => f.write_str("failed"),
75            Self::InputRequired => f.write_str("input_required"),
76            Self::Working => f.write_str("working"),
77        }
78    }
79}
80
81/// Result of a task-augmented request.
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83pub struct CreateTaskResult {
84    /// The created task.
85    pub task: Task,
86    /// Extension metadata.
87    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
88    pub meta: Option<HashMap<String, Value>>,
89}
90
91/// Result of a request to list tasks.
92#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
93pub struct ListTasksResult {
94    /// List of tasks.
95    pub tasks: Vec<Task>,
96    /// Opaque token for pagination.
97    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
98    pub next_cursor: Option<String>,
99    /// Extension metadata.
100    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
101    pub meta: Option<HashMap<String, Value>>,
102}
103
104/// Metadata for associating messages with a task.
105///
106/// Include in `_meta` under key `io.modelcontextprotocol/related-task`.
107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
108pub struct RelatedTaskMetadata {
109    /// The task identifier this message is associated with.
110    #[serde(rename = "taskId")]
111    pub task_id: String,
112}
113
114// =============================================================================
115// Elicitation (SEP-1036)
116// =============================================================================
117
118/// Parameters for an elicitation request.
119///
120/// Per MCP 2025-11-25, `mode` is optional for form requests (defaults to `"form"`)
121/// but required for URL requests. `Serialize` and `Deserialize` are implemented
122/// manually to handle the optional `mode` tag on the form variant.
123#[derive(Debug, Clone, PartialEq)]
124pub enum ElicitRequestParams {
125    /// Form elicitation (structured input)
126    Form(ElicitRequestFormParams),
127    /// URL elicitation (out-of-band interaction)
128    Url(ElicitRequestURLParams),
129}
130
131impl ElicitRequestParams {
132    /// Create a form-mode elicitation request with no task/meta.
133    #[must_use]
134    pub fn form(message: impl Into<String>, requested_schema: Value) -> Self {
135        Self::Form(ElicitRequestFormParams {
136            message: message.into(),
137            requested_schema,
138            task: None,
139            meta: None,
140        })
141    }
142
143    /// Create a URL-mode elicitation request with no task/meta.
144    #[must_use]
145    pub fn url(
146        message: impl Into<String>,
147        url: impl Into<String>,
148        elicitation_id: impl Into<String>,
149    ) -> Self {
150        Self::Url(ElicitRequestURLParams {
151            message: message.into(),
152            url: url.into(),
153            elicitation_id: elicitation_id.into(),
154            task: None,
155            meta: None,
156        })
157    }
158
159    /// Human-readable message common to both variants.
160    #[must_use]
161    pub fn message(&self) -> &str {
162        match self {
163            Self::Form(p) => &p.message,
164            Self::Url(p) => &p.message,
165        }
166    }
167
168    /// Task metadata common to both variants.
169    #[must_use]
170    pub fn task(&self) -> Option<&TaskMetadata> {
171        match self {
172            Self::Form(p) => p.task.as_ref(),
173            Self::Url(p) => p.task.as_ref(),
174        }
175    }
176
177    /// Extension metadata (`_meta`) common to both variants.
178    #[must_use]
179    pub fn meta(&self) -> Option<&HashMap<String, Value>> {
180        match self {
181            Self::Form(p) => p.meta.as_ref(),
182            Self::Url(p) => p.meta.as_ref(),
183        }
184    }
185}
186
187impl Serialize for ElicitRequestParams {
188    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
189        match self {
190            Self::Form(params) => {
191                // Serialize form params with mode: "form"
192                let mut value = serde_json::to_value(params).map_err(serde::ser::Error::custom)?;
193                if let Some(obj) = value.as_object_mut() {
194                    obj.insert("mode".into(), Value::String("form".into()));
195                }
196                value.serialize(serializer)
197            }
198            Self::Url(params) => {
199                // Serialize URL params with mode: "url"
200                let mut value = serde_json::to_value(params).map_err(serde::ser::Error::custom)?;
201                if let Some(obj) = value.as_object_mut() {
202                    obj.insert("mode".into(), Value::String("url".into()));
203                }
204                value.serialize(serializer)
205            }
206        }
207    }
208}
209
210impl<'de> Deserialize<'de> for ElicitRequestParams {
211    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
212        let value = Value::deserialize(deserializer)?;
213
214        match value.get("mode") {
215            None => {
216                let params: ElicitRequestFormParams =
217                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
218                Ok(Self::Form(params))
219            }
220            Some(Value::String(mode)) if mode == "form" => {
221                let params: ElicitRequestFormParams =
222                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
223                Ok(Self::Form(params))
224            }
225            Some(Value::String(mode)) if mode == "url" => {
226                let params: ElicitRequestURLParams =
227                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
228                Ok(Self::Url(params))
229            }
230            Some(Value::String(mode)) => Err(serde::de::Error::custom(format!(
231                "unsupported elicitation mode `{mode}`"
232            ))),
233            Some(_) => Err(serde::de::Error::custom(
234                "elicitation mode must be a string when present",
235            )),
236        }
237    }
238}
239
240/// Parameters for form-based elicitation.
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
242pub struct ElicitRequestFormParams {
243    /// Message to show the user.
244    pub message: String,
245    /// JSON Schema for the requested information.
246    #[serde(rename = "requestedSchema")]
247    pub requested_schema: Value,
248    /// Task metadata if this is a task-augmented request.
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub task: Option<TaskMetadata>,
251    /// Extension metadata.
252    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
253    pub meta: Option<HashMap<String, Value>>,
254}
255
256/// Parameters for URL-based elicitation.
257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
258pub struct ElicitRequestURLParams {
259    /// Message to show the user.
260    pub message: String,
261    /// URL the user should navigate to.
262    pub url: String,
263    /// Unique elicitation ID.
264    #[serde(rename = "elicitationId")]
265    pub elicitation_id: String,
266    /// Task metadata if this is a task-augmented request.
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub task: Option<TaskMetadata>,
269    /// Extension metadata.
270    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
271    pub meta: Option<HashMap<String, Value>>,
272}
273
274/// Result of an elicitation.
275#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
276pub struct ElicitResult {
277    /// Action taken by the user.
278    pub action: ElicitAction,
279    /// Form content (only if action is "accept").
280    /// Values are constrained to: string | number | boolean | string[]
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub content: Option<Value>,
283    /// Extension metadata.
284    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
285    pub meta: Option<HashMap<String, Value>>,
286}
287
288/// Action taken in response to elicitation.
289#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
290#[serde(rename_all = "lowercase")]
291pub enum ElicitAction {
292    /// User accepted the request.
293    Accept,
294    /// User declined the request.
295    Decline,
296    /// User cancelled or dismissed the request.
297    Cancel,
298}
299
300impl core::fmt::Display for ElicitAction {
301    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
302        match self {
303            Self::Accept => f.write_str("accept"),
304            Self::Decline => f.write_str("decline"),
305            Self::Cancel => f.write_str("cancel"),
306        }
307    }
308}
309
310/// Notification that a URL elicitation has completed.
311///
312/// New in MCP 2025-11-25. Method: `notifications/elicitation/complete`.
313#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
314pub struct ElicitationCompleteNotification {
315    /// The elicitation ID that completed.
316    #[serde(rename = "elicitationId")]
317    pub elicitation_id: String,
318}
319
320// =============================================================================
321// Sampling (SEP-1577)
322// =============================================================================
323
324/// Parameters for a `sampling/createMessage` request.
325#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
326pub struct CreateMessageRequest {
327    /// Messages to include in the context.
328    #[serde(default)]
329    pub messages: Vec<SamplingMessage>,
330    /// Max tokens to sample (required per spec, defaults to 0 for builder pattern).
331    #[serde(rename = "maxTokens")]
332    pub max_tokens: u32,
333    /// Model selection preferences.
334    #[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
335    pub model_preferences: Option<ModelPreferences>,
336    /// Optional system prompt.
337    #[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
338    pub system_prompt: Option<String>,
339    /// Context inclusion preference (soft-deprecated for thisServer/allServers).
340    #[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
341    pub include_context: Option<IncludeContext>,
342    /// Sampling temperature.
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub temperature: Option<f64>,
345    /// Stop sequences.
346    #[serde(rename = "stopSequences", skip_serializing_if = "Option::is_none")]
347    pub stop_sequences: Option<Vec<String>>,
348    /// Task metadata if this is a task-augmented request.
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub task: Option<TaskMetadata>,
351    /// Available tools for the model (requires client `sampling.tools` capability).
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub tools: Option<Vec<Tool>>,
354    /// Tool usage constraints (requires client `sampling.tools` capability).
355    #[serde(rename = "toolChoice", skip_serializing_if = "Option::is_none")]
356    pub tool_choice: Option<ToolChoice>,
357    /// Optional metadata to pass through to the LLM provider.
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub metadata: Option<Value>,
360    /// Extension metadata.
361    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
362    pub meta: Option<HashMap<String, Value>>,
363}
364
365/// Message in a sampling request.
366///
367/// Per MCP 2025-11-25, `content` can be a single `SamplingMessageContentBlock`
368/// or an array of them.
369#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
370pub struct SamplingMessage {
371    /// Message role.
372    pub role: Role,
373    /// Message content (single block or array per spec).
374    pub content: SamplingContentBlock,
375    /// Extension metadata.
376    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
377    pub meta: Option<HashMap<String, Value>>,
378}
379
380impl SamplingMessage {
381    /// Create a user message with text content.
382    #[must_use]
383    pub fn user(text: impl Into<String>) -> Self {
384        Self {
385            role: Role::User,
386            content: SamplingContent::text(text).into(),
387            meta: None,
388        }
389    }
390
391    /// Create an assistant message with text content.
392    #[must_use]
393    pub fn assistant(text: impl Into<String>) -> Self {
394        Self {
395            role: Role::Assistant,
396            content: SamplingContent::text(text).into(),
397            meta: None,
398        }
399    }
400}
401
402/// Preferences for model selection.
403#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
404pub struct ModelPreferences {
405    /// Hints for selecting a model.
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub hints: Option<Vec<ModelHint>>,
408    /// Cost preference (0.0 to 1.0).
409    #[serde(rename = "costPriority", skip_serializing_if = "Option::is_none")]
410    pub cost_priority: Option<f64>,
411    /// Speed preference (0.0 to 1.0).
412    #[serde(rename = "speedPriority", skip_serializing_if = "Option::is_none")]
413    pub speed_priority: Option<f64>,
414    /// Intelligence preference (0.0 to 1.0).
415    #[serde(
416        rename = "intelligencePriority",
417        skip_serializing_if = "Option::is_none"
418    )]
419    pub intelligence_priority: Option<f64>,
420}
421
422/// Hint for model selection.
423///
424/// Per spec, `name` is optional and treated as a substring match against model names.
425#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
426pub struct ModelHint {
427    /// Name pattern for model selection (substring match).
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub name: Option<String>,
430}
431
432impl core::fmt::Display for IncludeContext {
433    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
434        match self {
435            Self::AllServers => f.write_str("allServers"),
436            Self::ThisServer => f.write_str("thisServer"),
437            Self::None => f.write_str("none"),
438        }
439    }
440}
441
442/// Context inclusion mode for sampling.
443///
444/// `thisServer` and `allServers` are soft-deprecated in 2025-11-25.
445#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
446pub enum IncludeContext {
447    /// Include context from all servers (soft-deprecated).
448    #[serde(rename = "allServers")]
449    AllServers,
450    /// Include context only from this server (soft-deprecated).
451    #[serde(rename = "thisServer")]
452    ThisServer,
453    /// Do not include additional context.
454    #[serde(rename = "none")]
455    None,
456}
457
458/// Tool usage constraints for sampling.
459///
460/// Per spec, `mode` is optional and defaults to `"auto"`.
461#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
462pub struct ToolChoice {
463    /// Controls the tool use ability of the model (defaults to auto).
464    #[serde(skip_serializing_if = "Option::is_none")]
465    pub mode: Option<ToolChoiceMode>,
466}
467
468/// Mode for tool choice.
469#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
470#[serde(rename_all = "lowercase")]
471pub enum ToolChoiceMode {
472    /// Model decides whether to use tools (default).
473    Auto,
474    /// Model MUST NOT use any tools.
475    None,
476    /// Model MUST use at least one tool.
477    Required,
478}
479
480impl core::fmt::Display for ToolChoiceMode {
481    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
482        match self {
483            Self::Auto => f.write_str("auto"),
484            Self::None => f.write_str("none"),
485            Self::Required => f.write_str("required"),
486        }
487    }
488}
489
490/// Result of a sampling request.
491///
492/// Per spec, extends both `Result` and `SamplingMessage`, so it has
493/// `role`, `content` (as `SamplingContentBlock`), `model`, and `stopReason`.
494#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
495pub struct CreateMessageResult {
496    /// The role of the generated message.
497    pub role: Role,
498    /// The sampled content (single block or array per `SamplingMessage`).
499    pub content: SamplingContentBlock,
500    /// The name of the model that generated the message.
501    pub model: String,
502    /// The reason why sampling stopped, if known.
503    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
504    pub stop_reason: Option<String>,
505    /// Extension metadata.
506    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
507    pub meta: Option<HashMap<String, Value>>,
508}
509
510// =============================================================================
511// Capabilities
512// =============================================================================
513
514/// Capabilities supported by a client.
515///
516/// Per MCP 2025-11-25: `roots`, `sampling`, `elicitation`, `tasks`, `experimental`.
517#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
518pub struct ClientCapabilities {
519    /// Support for listing roots.
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub roots: Option<RootsCapabilities>,
522    /// Support for LLM sampling.
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub sampling: Option<SamplingCapabilities>,
525    /// Support for elicitation.
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub elicitation: Option<ElicitationCapabilities>,
528    /// Support for the Tasks API (MCP 2025-11-25 draft, SEP-1686).
529    ///
530    /// When present, indicates the client can receive task-augmented requests
531    /// (e.g. `sampling/createMessage`, `elicitation/create`).
532    #[serde(skip_serializing_if = "Option::is_none")]
533    pub tasks: Option<ClientTasksCapabilities>,
534    /// Draft extensions supported by the client.
535    #[serde(skip_serializing_if = "Option::is_none")]
536    pub extensions: Option<HashMap<String, Value>>,
537    /// Experimental, non-standard capabilities.
538    #[serde(skip_serializing_if = "Option::is_none")]
539    pub experimental: Option<HashMap<String, Value>>,
540}
541
542/// Elicitation capabilities per MCP 2025-11-25.
543///
544/// Supports two modes:
545/// - `form`: in-band structured data collection (default if empty object).
546/// - `url`: out-of-band interactions (OAuth, credentials, payments).
547///
548/// Per spec, an empty object (`{}`) is equivalent to declaring support for
549/// `form` mode only.
550#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
551pub struct ElicitationCapabilities {
552    /// Form-mode elicitation support.
553    ///
554    /// Per spec, an empty capabilities object defaults to form support.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub form: Option<ElicitationFormCapabilities>,
557    /// URL-mode elicitation support (MCP 2025-11-25).
558    ///
559    /// For sensitive interactions (OAuth, credentials, payments).
560    #[serde(skip_serializing_if = "Option::is_none")]
561    pub url: Option<ElicitationUrlCapabilities>,
562    /// Whether the client performs JSON schema validation on elicitation responses.
563    ///
564    /// When `true`, the client validates user input against the provided schema
565    /// before sending. (TurboMCP extension — not part of the MCP specification.)
566    #[serde(rename = "schemaValidation", skip_serializing_if = "Option::is_none")]
567    pub schema_validation: Option<bool>,
568}
569
570impl ElicitationCapabilities {
571    /// Both form and URL support.
572    #[must_use]
573    pub fn full() -> Self {
574        Self {
575            form: Some(ElicitationFormCapabilities {}),
576            url: Some(ElicitationUrlCapabilities {}),
577            schema_validation: None,
578        }
579    }
580
581    /// Form-mode support only.
582    #[must_use]
583    pub fn form_only() -> Self {
584        Self {
585            form: Some(ElicitationFormCapabilities {}),
586            url: None,
587            schema_validation: None,
588        }
589    }
590
591    /// Whether form-mode elicitation is supported.
592    ///
593    /// Per spec, an empty capabilities object defaults to form support.
594    #[must_use]
595    pub fn supports_form(&self) -> bool {
596        self.form.is_some() || (self.form.is_none() && self.url.is_none())
597    }
598
599    /// Whether URL-mode elicitation is supported.
600    #[must_use]
601    pub fn supports_url(&self) -> bool {
602        self.url.is_some()
603    }
604
605    /// Enable schema validation (TurboMCP extension).
606    #[must_use]
607    pub fn with_schema_validation(mut self) -> Self {
608        self.schema_validation = Some(true);
609        self
610    }
611
612    /// Disable schema validation (TurboMCP extension).
613    #[must_use]
614    pub fn without_schema_validation(mut self) -> Self {
615        self.schema_validation = Some(false);
616        self
617    }
618}
619
620/// Form-mode elicitation support.
621#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
622pub struct ElicitationFormCapabilities {}
623
624/// URL-mode elicitation support.
625#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
626pub struct ElicitationUrlCapabilities {}
627
628/// Sampling capabilities for a client.
629///
630/// Per MCP 2025-11-25: `{ context?: {}, tools?: {} }`.
631#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
632pub struct SamplingCapabilities {
633    /// Support for context inclusion (soft-deprecated).
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub context: Option<HashMap<String, Value>>,
636    /// Support for tool use in sampling (new in 2025-11-25).
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub tools: Option<HashMap<String, Value>>,
639}
640
641/// Roots capabilities for a client.
642#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
643pub struct RootsCapabilities {
644    /// Support for `roots/list_changed` notifications.
645    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
646    pub list_changed: Option<bool>,
647}
648
649/// Client-side Tasks capabilities (MCP 2025-11-25 draft, SEP-1686).
650///
651/// Indicates which task operations and request types the client supports.
652#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
653pub struct ClientTasksCapabilities {
654    /// Support for `tasks/list`.
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub list: Option<TasksListCapabilities>,
657    /// Support for `tasks/cancel`.
658    #[serde(skip_serializing_if = "Option::is_none")]
659    pub cancel: Option<TasksCancelCapabilities>,
660    /// Support for task-augmented requests.
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub requests: Option<ClientTasksRequestsCapabilities>,
663}
664
665/// Client-side task-augmented request capabilities.
666#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
667pub struct ClientTasksRequestsCapabilities {
668    /// Support for task-augmented `sampling/createMessage`.
669    #[serde(skip_serializing_if = "Option::is_none")]
670    pub sampling: Option<TasksSamplingCapabilities>,
671    /// Support for task-augmented `elicitation/create`.
672    #[serde(skip_serializing_if = "Option::is_none")]
673    pub elicitation: Option<TasksElicitationCapabilities>,
674}
675
676/// Task-augmented sampling request capabilities.
677#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
678pub struct TasksSamplingCapabilities {
679    /// Support for task-augmented `sampling/createMessage`.
680    #[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
681    pub create_message: Option<TasksSamplingCreateMessageCapabilities>,
682}
683
684/// Task-augmented `sampling/createMessage` capability marker.
685#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
686pub struct TasksSamplingCreateMessageCapabilities {}
687
688/// Task-augmented elicitation request capabilities.
689#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
690pub struct TasksElicitationCapabilities {
691    /// Support for task-augmented `elicitation/create`.
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub create: Option<TasksElicitationCreateCapabilities>,
694}
695
696/// Task-augmented `elicitation/create` capability marker.
697#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
698pub struct TasksElicitationCreateCapabilities {}
699
700/// Capabilities supported by a server.
701///
702/// Per MCP 2025-11-25: `tools`, `resources`, `prompts`, `logging`, `completions`,
703/// `tasks`, `experimental`.
704#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
705pub struct ServerCapabilities {
706    /// Support for tools.
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub tools: Option<ToolsCapabilities>,
709    /// Support for resources.
710    #[serde(skip_serializing_if = "Option::is_none")]
711    pub resources: Option<ResourcesCapabilities>,
712    /// Support for prompts.
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub prompts: Option<PromptsCapabilities>,
715    /// Support for logging.
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub logging: Option<LoggingCapabilities>,
718    /// Support for argument autocompletion.
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub completions: Option<CompletionCapabilities>,
721    /// Support for the Tasks API (MCP 2025-11-25 draft, SEP-1686).
722    ///
723    /// When present, indicates the server can receive task-augmented requests
724    /// (e.g. `tools/call`).
725    #[serde(skip_serializing_if = "Option::is_none")]
726    pub tasks: Option<ServerTasksCapabilities>,
727    /// Draft extensions supported by the server.
728    #[serde(skip_serializing_if = "Option::is_none")]
729    pub extensions: Option<HashMap<String, Value>>,
730    /// Experimental, non-standard capabilities.
731    #[serde(skip_serializing_if = "Option::is_none")]
732    pub experimental: Option<HashMap<String, Value>>,
733}
734
735/// Tools capabilities for a server.
736#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
737pub struct ToolsCapabilities {
738    /// Support for `tools/list_changed` notifications.
739    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
740    pub list_changed: Option<bool>,
741}
742
743/// Resources capabilities for a server.
744#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
745pub struct ResourcesCapabilities {
746    /// Support for `resources/subscribe` and `notifications/resources/updated`.
747    #[serde(skip_serializing_if = "Option::is_none")]
748    pub subscribe: Option<bool>,
749    /// Support for `resources/list_changed` notifications.
750    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
751    pub list_changed: Option<bool>,
752}
753
754/// Prompts capabilities for a server.
755#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
756pub struct PromptsCapabilities {
757    /// Support for `prompts/list_changed` notifications.
758    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
759    pub list_changed: Option<bool>,
760}
761
762/// Logging capability marker.
763#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
764pub struct LoggingCapabilities {}
765
766/// Argument autocompletion capability marker.
767#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
768pub struct CompletionCapabilities {}
769
770/// Server-side Tasks capabilities (MCP 2025-11-25 draft, SEP-1686).
771///
772/// Indicates which task operations and request types the server supports.
773#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
774pub struct ServerTasksCapabilities {
775    /// Support for `tasks/list`.
776    #[serde(skip_serializing_if = "Option::is_none")]
777    pub list: Option<TasksListCapabilities>,
778    /// Support for `tasks/cancel`.
779    #[serde(skip_serializing_if = "Option::is_none")]
780    pub cancel: Option<TasksCancelCapabilities>,
781    /// Support for task-augmented requests.
782    #[serde(skip_serializing_if = "Option::is_none")]
783    pub requests: Option<ServerTasksRequestsCapabilities>,
784}
785
786/// Server-side task-augmented request capabilities.
787#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
788pub struct ServerTasksRequestsCapabilities {
789    /// Support for task-augmented `tools/call`.
790    #[serde(skip_serializing_if = "Option::is_none")]
791    pub tools: Option<TasksToolsCapabilities>,
792}
793
794/// Task-augmented tools capabilities.
795#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
796pub struct TasksToolsCapabilities {
797    /// Support for task-augmented `tools/call`.
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub call: Option<TasksToolsCallCapabilities>,
800}
801
802/// Task-augmented `tools/call` capability marker.
803#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
804pub struct TasksToolsCallCapabilities {}
805
806/// `tasks/list` capability marker.
807#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
808pub struct TasksListCapabilities {}
809
810/// `tasks/cancel` capability marker.
811#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
812pub struct TasksCancelCapabilities {}
813
814// =============================================================================
815// Tests
816// =============================================================================
817
818#[cfg(test)]
819mod tests {
820    use super::*;
821
822    #[test]
823    fn test_include_context_serde() {
824        // Verify camelCase serialization
825        let json = serde_json::to_string(&IncludeContext::ThisServer).unwrap();
826        assert_eq!(json, "\"thisServer\"");
827
828        let json = serde_json::to_string(&IncludeContext::AllServers).unwrap();
829        assert_eq!(json, "\"allServers\"");
830
831        let json = serde_json::to_string(&IncludeContext::None).unwrap();
832        assert_eq!(json, "\"none\"");
833
834        // Round-trip
835        let parsed: IncludeContext = serde_json::from_str("\"thisServer\"").unwrap();
836        assert_eq!(parsed, IncludeContext::ThisServer);
837    }
838
839    #[test]
840    fn test_tool_choice_mode_optional() {
841        // mode is optional, should serialize empty when None
842        let tc = ToolChoice { mode: None };
843        let json = serde_json::to_string(&tc).unwrap();
844        assert_eq!(json, "{}");
845
846        // Explicit mode
847        let tc = ToolChoice {
848            mode: Some(ToolChoiceMode::Required),
849        };
850        let json = serde_json::to_string(&tc).unwrap();
851        assert!(json.contains("\"required\""));
852    }
853
854    #[test]
855    fn test_model_hint_name_optional() {
856        let hint = ModelHint { name: None };
857        let json = serde_json::to_string(&hint).unwrap();
858        assert_eq!(json, "{}");
859
860        let hint = ModelHint {
861            name: Some("claude".into()),
862        };
863        let json = serde_json::to_string(&hint).unwrap();
864        assert!(json.contains("\"claude\""));
865    }
866
867    #[test]
868    fn test_task_status_serde() {
869        let json = serde_json::to_string(&TaskStatus::InputRequired).unwrap();
870        assert_eq!(json, "\"input_required\"");
871
872        let json = serde_json::to_string(&TaskStatus::Working).unwrap();
873        assert_eq!(json, "\"working\"");
874    }
875
876    #[test]
877    fn test_create_message_request_default() {
878        // Verify Default works (used in builder pattern)
879        let req = CreateMessageRequest {
880            messages: vec![SamplingMessage::user("hello")],
881            max_tokens: 100,
882            ..Default::default()
883        };
884        assert_eq!(req.messages.len(), 1);
885        assert_eq!(req.max_tokens, 100);
886        assert!(req.tools.is_none());
887    }
888
889    #[test]
890    fn test_sampling_message_content_single_or_array() {
891        // Single content
892        let msg = SamplingMessage::user("hello");
893        let json = serde_json::to_string(&msg).unwrap();
894        // Single should be an object, not array
895        assert!(json.contains("\"text\":\"hello\""));
896
897        // Round-trip
898        let parsed: SamplingMessage = serde_json::from_str(&json).unwrap();
899        assert_eq!(parsed.content.as_text(), Some("hello"));
900
901        // Array content
902        let json_array = r#"{"role":"user","content":[{"type":"text","text":"hello"},{"type":"text","text":"world"}]}"#;
903        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
904        match &parsed.content {
905            SamplingContentBlock::Multiple(v) => assert_eq!(v.len(), 2),
906            _ => panic!("Expected multiple content blocks"),
907        }
908    }
909
910    #[test]
911    fn test_server_capabilities_structure() {
912        let caps = ServerCapabilities {
913            tasks: Some(ServerTasksCapabilities {
914                list: Some(TasksListCapabilities {}),
915                cancel: Some(TasksCancelCapabilities {}),
916                requests: Some(ServerTasksRequestsCapabilities {
917                    tools: Some(TasksToolsCapabilities {
918                        call: Some(TasksToolsCallCapabilities {}),
919                    }),
920                }),
921            }),
922            extensions: Some(HashMap::from([(
923                "trace".to_string(),
924                serde_json::json!({"version": "1"}),
925            )])),
926            ..Default::default()
927        };
928        let json = serde_json::to_string(&caps).unwrap();
929        let v: Value = serde_json::from_str(&json).unwrap();
930        // Verify nested structure matches spec
931        assert!(v["tasks"]["requests"]["tools"]["call"].is_object());
932        assert!(v["extensions"]["trace"].is_object());
933    }
934
935    // C-3: ElicitAction and ElicitResult serde
936    #[test]
937    fn test_elicit_action_serde() {
938        let cases = [
939            (ElicitAction::Accept, "\"accept\""),
940            (ElicitAction::Decline, "\"decline\""),
941            (ElicitAction::Cancel, "\"cancel\""),
942        ];
943        for (action, expected) in cases {
944            let json = serde_json::to_string(&action).unwrap();
945            assert_eq!(json, expected);
946            let parsed: ElicitAction = serde_json::from_str(expected).unwrap();
947            assert_eq!(parsed, action);
948        }
949    }
950
951    #[test]
952    fn test_elicit_result_round_trip() {
953        let result = ElicitResult {
954            action: ElicitAction::Accept,
955            content: Some(serde_json::json!({"name": "test"})),
956            meta: None,
957        };
958        let json = serde_json::to_string(&result).unwrap();
959        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
960        assert_eq!(parsed.action, ElicitAction::Accept);
961        assert!(parsed.content.is_some());
962
963        // Decline with no content
964        let decline = ElicitResult {
965            action: ElicitAction::Decline,
966            content: None,
967            meta: None,
968        };
969        let json = serde_json::to_string(&decline).unwrap();
970        assert!(!json.contains("\"content\""));
971        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
972        assert_eq!(parsed.action, ElicitAction::Decline);
973        assert!(parsed.content.is_none());
974    }
975
976    // H-7: ServerCapabilities must NOT contain elicitation or sampling
977    #[test]
978    fn test_server_capabilities_no_elicitation_or_sampling() {
979        let caps = ServerCapabilities::default();
980        let json = serde_json::to_string(&caps).unwrap();
981        assert!(!json.contains("elicitation"));
982        assert!(!json.contains("sampling"));
983
984        // Even fully populated
985        let caps = ServerCapabilities {
986            tools: Some(ToolsCapabilities {
987                list_changed: Some(true),
988            }),
989            resources: Some(ResourcesCapabilities {
990                subscribe: Some(true),
991                list_changed: Some(true),
992            }),
993            prompts: Some(PromptsCapabilities {
994                list_changed: Some(true),
995            }),
996            logging: Some(LoggingCapabilities {}),
997            completions: Some(CompletionCapabilities {}),
998            tasks: Some(ServerTasksCapabilities::default()),
999            extensions: Some(HashMap::from([(
1000                "trace".to_string(),
1001                serde_json::json!({"version": "1"}),
1002            )])),
1003            experimental: Some(HashMap::new()),
1004        };
1005        let json = serde_json::to_string(&caps).unwrap();
1006        assert!(!json.contains("elicitation"));
1007        assert!(!json.contains("sampling"));
1008        assert!(json.contains("extensions"));
1009    }
1010
1011    // H-8: SamplingMessage array content round-trip preserves array
1012    #[test]
1013    fn test_sampling_message_array_content_round_trip() {
1014        let json_array =
1015            r#"{"role":"user","content":[{"type":"text","text":"a"},{"type":"text","text":"b"}]}"#;
1016        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
1017        let re_serialized = serde_json::to_string(&parsed).unwrap();
1018        let re_parsed: Value = serde_json::from_str(&re_serialized).unwrap();
1019        assert!(re_parsed["content"].is_array());
1020        assert_eq!(re_parsed["content"].as_array().unwrap().len(), 2);
1021    }
1022
1023    // H-10: All ToolChoiceMode variants
1024    #[test]
1025    fn test_tool_choice_mode_all_variants() {
1026        let cases = [
1027            (ToolChoiceMode::Auto, "\"auto\""),
1028            (ToolChoiceMode::None, "\"none\""),
1029            (ToolChoiceMode::Required, "\"required\""),
1030        ];
1031        for (mode, expected) in cases {
1032            let json = serde_json::to_string(&mode).unwrap();
1033            assert_eq!(json, expected);
1034            let parsed: ToolChoiceMode = serde_json::from_str(expected).unwrap();
1035            assert_eq!(parsed, mode);
1036        }
1037    }
1038
1039    // CRITICAL-2: ElicitRequestParams custom serde - optional mode field
1040    #[test]
1041    fn test_elicit_request_params_form_without_mode() {
1042        // Per MCP 2025-11-25, mode is optional and defaults to "form"
1043        let json = r#"{"message":"Enter name","requestedSchema":{"type":"object"}}"#;
1044        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
1045        match &parsed {
1046            ElicitRequestParams::Form(params) => {
1047                assert_eq!(params.message, "Enter name");
1048            }
1049            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
1050        }
1051    }
1052
1053    #[test]
1054    fn test_elicit_request_params_form_with_explicit_mode() {
1055        let json = r#"{"mode":"form","message":"Enter name","requestedSchema":{"type":"object"}}"#;
1056        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
1057        match &parsed {
1058            ElicitRequestParams::Form(params) => {
1059                assert_eq!(params.message, "Enter name");
1060            }
1061            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
1062        }
1063    }
1064
1065    #[test]
1066    fn test_elicit_request_params_url_mode() {
1067        let json = r#"{"mode":"url","message":"Authenticate","url":"https://example.com/auth","elicitationId":"e-123"}"#;
1068        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
1069        match &parsed {
1070            ElicitRequestParams::Url(params) => {
1071                assert_eq!(params.message, "Authenticate");
1072                assert_eq!(params.url, "https://example.com/auth");
1073                assert_eq!(params.elicitation_id, "e-123");
1074            }
1075            ElicitRequestParams::Form(_) => panic!("expected Url variant"),
1076        }
1077    }
1078
1079    #[test]
1080    fn test_elicit_request_params_rejects_unknown_mode() {
1081        let json =
1082            r#"{"mode":"unknown","message":"Enter name","requestedSchema":{"type":"object"}}"#;
1083        let err = serde_json::from_str::<ElicitRequestParams>(json).unwrap_err();
1084        assert!(err.to_string().contains("unsupported elicitation mode"));
1085    }
1086
1087    #[test]
1088    fn test_elicit_request_params_rejects_non_string_mode() {
1089        let json = r#"{"mode":true,"message":"Enter name","requestedSchema":{"type":"object"}}"#;
1090        let err = serde_json::from_str::<ElicitRequestParams>(json).unwrap_err();
1091        assert!(err.to_string().contains("mode must be a string"));
1092    }
1093
1094    #[test]
1095    fn test_elicit_request_params_form_round_trip() {
1096        let params = ElicitRequestParams::Form(ElicitRequestFormParams {
1097            message: "Enter details".into(),
1098            requested_schema: serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}}),
1099            task: None,
1100            meta: None,
1101        });
1102        let json = serde_json::to_string(&params).unwrap();
1103        // Serialized output must include mode: "form"
1104        let v: Value = serde_json::from_str(&json).unwrap();
1105        assert_eq!(v["mode"], "form");
1106        // Round-trip
1107        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
1108        assert_eq!(parsed, params);
1109    }
1110
1111    #[test]
1112    fn test_elicit_request_params_url_round_trip() {
1113        let params = ElicitRequestParams::Url(ElicitRequestURLParams {
1114            message: "Please authenticate".into(),
1115            url: "https://example.com/oauth".into(),
1116            elicitation_id: "elicit-456".into(),
1117            task: None,
1118            meta: None,
1119        });
1120        let json = serde_json::to_string(&params).unwrap();
1121        let v: Value = serde_json::from_str(&json).unwrap();
1122        assert_eq!(v["mode"], "url");
1123        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
1124        assert_eq!(parsed, params);
1125    }
1126
1127    // M-6: All TaskStatus variants
1128    #[test]
1129    fn test_task_status_all_variants() {
1130        let cases = [
1131            (TaskStatus::Cancelled, "\"cancelled\""),
1132            (TaskStatus::Completed, "\"completed\""),
1133            (TaskStatus::Failed, "\"failed\""),
1134            (TaskStatus::InputRequired, "\"input_required\""),
1135            (TaskStatus::Working, "\"working\""),
1136        ];
1137        for (status, expected) in cases {
1138            let json = serde_json::to_string(&status).unwrap();
1139            assert_eq!(json, expected);
1140            let parsed: TaskStatus = serde_json::from_str(expected).unwrap();
1141            assert_eq!(parsed, status);
1142        }
1143    }
1144}