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;
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 Serialize for ElicitRequestParams {
132    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
133        match self {
134            Self::Form(params) => {
135                // Serialize form params with mode: "form"
136                let mut value = serde_json::to_value(params).map_err(serde::ser::Error::custom)?;
137                if let Some(obj) = value.as_object_mut() {
138                    obj.insert("mode".into(), Value::String("form".into()));
139                }
140                value.serialize(serializer)
141            }
142            Self::Url(params) => {
143                // Serialize URL params with mode: "url"
144                let mut value = serde_json::to_value(params).map_err(serde::ser::Error::custom)?;
145                if let Some(obj) = value.as_object_mut() {
146                    obj.insert("mode".into(), Value::String("url".into()));
147                }
148                value.serialize(serializer)
149            }
150        }
151    }
152}
153
154impl<'de> Deserialize<'de> for ElicitRequestParams {
155    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
156        let value = Value::deserialize(deserializer)?;
157        let mode = value.get("mode").and_then(|v| v.as_str()).unwrap_or("form");
158
159        match mode {
160            "url" => {
161                let params: ElicitRequestURLParams =
162                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
163                Ok(Self::Url(params))
164            }
165            _ => {
166                // Default to "form" when mode is absent or explicitly "form"
167                let params: ElicitRequestFormParams =
168                    serde_json::from_value(value).map_err(serde::de::Error::custom)?;
169                Ok(Self::Form(params))
170            }
171        }
172    }
173}
174
175/// Parameters for form-based elicitation.
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
177pub struct ElicitRequestFormParams {
178    /// Message to show the user.
179    pub message: String,
180    /// JSON Schema for the requested information.
181    #[serde(rename = "requestedSchema")]
182    pub requested_schema: Value,
183    /// Task metadata if this is a task-augmented request.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub task: Option<TaskMetadata>,
186    /// Extension metadata.
187    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
188    pub meta: Option<HashMap<String, Value>>,
189}
190
191/// Parameters for URL-based elicitation.
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
193pub struct ElicitRequestURLParams {
194    /// Message to show the user.
195    pub message: String,
196    /// URL the user should navigate to.
197    pub url: String,
198    /// Unique elicitation ID.
199    #[serde(rename = "elicitationId")]
200    pub elicitation_id: String,
201    /// Task metadata if this is a task-augmented request.
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub task: Option<TaskMetadata>,
204    /// Extension metadata.
205    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
206    pub meta: Option<HashMap<String, Value>>,
207}
208
209/// Result of an elicitation.
210#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
211pub struct ElicitResult {
212    /// Action taken by the user.
213    pub action: ElicitAction,
214    /// Form content (only if action is "accept").
215    /// Values are constrained to: string | number | boolean | string[]
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub content: Option<Value>,
218    /// Extension metadata.
219    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
220    pub meta: Option<HashMap<String, Value>>,
221}
222
223/// Action taken in response to elicitation.
224#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
225#[serde(rename_all = "lowercase")]
226pub enum ElicitAction {
227    /// User accepted the request.
228    Accept,
229    /// User declined the request.
230    Decline,
231    /// User cancelled or dismissed the request.
232    Cancel,
233}
234
235impl core::fmt::Display for ElicitAction {
236    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237        match self {
238            Self::Accept => f.write_str("accept"),
239            Self::Decline => f.write_str("decline"),
240            Self::Cancel => f.write_str("cancel"),
241        }
242    }
243}
244
245/// Notification that a URL elicitation has completed.
246///
247/// New in MCP 2025-11-25. Method: `notifications/elicitation/complete`.
248#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
249pub struct ElicitationCompleteNotification {
250    /// The elicitation ID that completed.
251    #[serde(rename = "elicitationId")]
252    pub elicitation_id: String,
253}
254
255// =============================================================================
256// Sampling (SEP-1577)
257// =============================================================================
258
259/// Parameters for a `sampling/createMessage` request.
260#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
261pub struct CreateMessageRequest {
262    /// Messages to include in the context.
263    #[serde(default)]
264    pub messages: Vec<SamplingMessage>,
265    /// Max tokens to sample (required per spec, defaults to 0 for builder pattern).
266    #[serde(rename = "maxTokens")]
267    pub max_tokens: u32,
268    /// Model selection preferences.
269    #[serde(rename = "modelPreferences", skip_serializing_if = "Option::is_none")]
270    pub model_preferences: Option<ModelPreferences>,
271    /// Optional system prompt.
272    #[serde(rename = "systemPrompt", skip_serializing_if = "Option::is_none")]
273    pub system_prompt: Option<String>,
274    /// Context inclusion preference (soft-deprecated for thisServer/allServers).
275    #[serde(rename = "includeContext", skip_serializing_if = "Option::is_none")]
276    pub include_context: Option<IncludeContext>,
277    /// Sampling temperature.
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub temperature: Option<f64>,
280    /// Stop sequences.
281    #[serde(rename = "stopSequences", skip_serializing_if = "Option::is_none")]
282    pub stop_sequences: Option<Vec<String>>,
283    /// Task metadata if this is a task-augmented request.
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub task: Option<TaskMetadata>,
286    /// Available tools for the model (requires client `sampling.tools` capability).
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub tools: Option<Vec<Tool>>,
289    /// Tool usage constraints (requires client `sampling.tools` capability).
290    #[serde(rename = "toolChoice", skip_serializing_if = "Option::is_none")]
291    pub tool_choice: Option<ToolChoice>,
292    /// Optional metadata to pass through to the LLM provider.
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub metadata: Option<Value>,
295    /// Extension metadata.
296    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
297    pub meta: Option<HashMap<String, Value>>,
298}
299
300/// Message in a sampling request.
301///
302/// Per MCP 2025-11-25, `content` can be a single `SamplingMessageContentBlock`
303/// or an array of them.
304#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
305pub struct SamplingMessage {
306    /// Message role.
307    pub role: Role,
308    /// Message content (single block or array per spec).
309    pub content: SamplingContentBlock,
310    /// Extension metadata.
311    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
312    pub meta: Option<HashMap<String, Value>>,
313}
314
315impl SamplingMessage {
316    /// Create a user message with text content.
317    #[must_use]
318    pub fn user(text: impl Into<String>) -> Self {
319        Self {
320            role: Role::User,
321            content: SamplingContent::text(text).into(),
322            meta: None,
323        }
324    }
325
326    /// Create an assistant message with text content.
327    #[must_use]
328    pub fn assistant(text: impl Into<String>) -> Self {
329        Self {
330            role: Role::Assistant,
331            content: SamplingContent::text(text).into(),
332            meta: None,
333        }
334    }
335}
336
337/// Preferences for model selection.
338#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
339pub struct ModelPreferences {
340    /// Hints for selecting a model.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub hints: Option<Vec<ModelHint>>,
343    /// Cost preference (0.0 to 1.0).
344    #[serde(rename = "costPriority", skip_serializing_if = "Option::is_none")]
345    pub cost_priority: Option<f64>,
346    /// Speed preference (0.0 to 1.0).
347    #[serde(rename = "speedPriority", skip_serializing_if = "Option::is_none")]
348    pub speed_priority: Option<f64>,
349    /// Intelligence preference (0.0 to 1.0).
350    #[serde(
351        rename = "intelligencePriority",
352        skip_serializing_if = "Option::is_none"
353    )]
354    pub intelligence_priority: Option<f64>,
355}
356
357/// Hint for model selection.
358///
359/// Per spec, `name` is optional and treated as a substring match against model names.
360#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
361pub struct ModelHint {
362    /// Name pattern for model selection (substring match).
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub name: Option<String>,
365}
366
367impl core::fmt::Display for IncludeContext {
368    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
369        match self {
370            Self::AllServers => f.write_str("allServers"),
371            Self::ThisServer => f.write_str("thisServer"),
372            Self::None => f.write_str("none"),
373        }
374    }
375}
376
377/// Context inclusion mode for sampling.
378///
379/// `thisServer` and `allServers` are soft-deprecated in 2025-11-25.
380#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
381pub enum IncludeContext {
382    /// Include context from all servers (soft-deprecated).
383    #[serde(rename = "allServers")]
384    AllServers,
385    /// Include context only from this server (soft-deprecated).
386    #[serde(rename = "thisServer")]
387    ThisServer,
388    /// Do not include additional context.
389    #[serde(rename = "none")]
390    None,
391}
392
393/// Tool usage constraints for sampling.
394///
395/// Per spec, `mode` is optional and defaults to `"auto"`.
396#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
397pub struct ToolChoice {
398    /// Controls the tool use ability of the model (defaults to auto).
399    #[serde(skip_serializing_if = "Option::is_none")]
400    pub mode: Option<ToolChoiceMode>,
401}
402
403/// Mode for tool choice.
404#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
405#[serde(rename_all = "lowercase")]
406pub enum ToolChoiceMode {
407    /// Model decides whether to use tools (default).
408    Auto,
409    /// Model MUST NOT use any tools.
410    None,
411    /// Model MUST use at least one tool.
412    Required,
413}
414
415impl core::fmt::Display for ToolChoiceMode {
416    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
417        match self {
418            Self::Auto => f.write_str("auto"),
419            Self::None => f.write_str("none"),
420            Self::Required => f.write_str("required"),
421        }
422    }
423}
424
425/// Result of a sampling request.
426///
427/// Per spec, extends both `Result` and `SamplingMessage`, so it has
428/// `role`, `content` (as SamplingContentBlock), `model`, and `stopReason`.
429#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
430pub struct CreateMessageResult {
431    /// The role of the generated message.
432    pub role: Role,
433    /// The sampled content (single block or array per SamplingMessage).
434    pub content: SamplingContentBlock,
435    /// The name of the model that generated the message.
436    pub model: String,
437    /// The reason why sampling stopped, if known.
438    #[serde(rename = "stopReason", skip_serializing_if = "Option::is_none")]
439    pub stop_reason: Option<String>,
440    /// Extension metadata.
441    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
442    pub meta: Option<HashMap<String, Value>>,
443}
444
445// =============================================================================
446// Capabilities
447// =============================================================================
448
449/// Capabilities supported by a client.
450#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
451pub struct ClientCapabilities {
452    /// Support for elicitation.
453    #[serde(skip_serializing_if = "Option::is_none")]
454    pub elicitation: Option<ElicitationCapabilities>,
455    /// Support for sampling.
456    #[serde(skip_serializing_if = "Option::is_none")]
457    pub sampling: Option<SamplingCapabilities>,
458    /// Support for roots.
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub roots: Option<RootsCapabilities>,
461    /// Support for tasks.
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub tasks: Option<ClientTaskCapabilities>,
464    /// Experimental capabilities.
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub experimental: Option<HashMap<String, Value>>,
467}
468
469/// Elicitation capabilities for a client.
470#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
471pub struct ElicitationCapabilities {
472    /// Support for form-based elicitation.
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub form: Option<HashMap<String, Value>>,
475    /// Support for URL-based elicitation (new in 2025-11-25).
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub url: Option<HashMap<String, Value>>,
478}
479
480/// Sampling capabilities for a client.
481#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
482pub struct SamplingCapabilities {
483    /// Support for context inclusion (soft-deprecated).
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub context: Option<HashMap<String, Value>>,
486    /// Support for tool use in sampling (new in 2025-11-25).
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub tools: Option<HashMap<String, Value>>,
489}
490
491/// Roots capabilities for a client.
492#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
493pub struct RootsCapabilities {
494    /// Support for roots/list_changed notifications.
495    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
496    pub list_changed: Option<bool>,
497}
498
499/// Task capabilities for a client.
500#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
501pub struct ClientTaskCapabilities {
502    /// Support for tasks/list.
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub list: Option<HashMap<String, Value>>,
505    /// Support for tasks/cancel.
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub cancel: Option<HashMap<String, Value>>,
508    /// Requests that can be augmented with tasks.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub requests: Option<ClientTaskRequests>,
511}
512
513/// Client-side task-augmented request capabilities.
514#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
515pub struct ClientTaskRequests {
516    /// Support for task-augmented sampling.
517    #[serde(skip_serializing_if = "Option::is_none")]
518    pub sampling: Option<ClientTaskSamplingRequests>,
519    /// Support for task-augmented elicitation.
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub elicitation: Option<ClientTaskElicitationRequests>,
522}
523
524/// Client task-augmented sampling request capabilities.
525#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
526pub struct ClientTaskSamplingRequests {
527    /// Support for task-augmented sampling/createMessage.
528    #[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
529    pub create_message: Option<HashMap<String, Value>>,
530}
531
532/// Client task-augmented elicitation request capabilities.
533#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
534pub struct ClientTaskElicitationRequests {
535    /// Support for task-augmented elicitation/create.
536    #[serde(skip_serializing_if = "Option::is_none")]
537    pub create: Option<HashMap<String, Value>>,
538}
539
540/// Capabilities supported by a server.
541///
542/// Per MCP 2025-11-25, server capabilities are:
543/// `tools`, `resources`, `prompts`, `logging`, `completions`, `tasks`, `experimental`
544#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
545pub struct ServerCapabilities {
546    /// Support for tools.
547    #[serde(skip_serializing_if = "Option::is_none")]
548    pub tools: Option<ToolCapabilities>,
549    /// Support for resources.
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub resources: Option<ResourceCapabilities>,
552    /// Support for prompts.
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub prompts: Option<PromptCapabilities>,
555    /// Support for logging.
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub logging: Option<HashMap<String, Value>>,
558    /// Support for argument autocompletion.
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub completions: Option<HashMap<String, Value>>,
561    /// Support for task-augmented requests (experimental in 2025-11-25).
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub tasks: Option<ServerTaskCapabilities>,
564    /// Experimental capabilities.
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub experimental: Option<HashMap<String, Value>>,
567}
568
569/// Tool capabilities for a server.
570#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
571pub struct ToolCapabilities {
572    /// Support for tools/list_changed notifications.
573    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
574    pub list_changed: Option<bool>,
575}
576
577/// Resource capabilities for a server.
578#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
579pub struct ResourceCapabilities {
580    /// Support for resources/subscribe and notifications/resources/updated.
581    #[serde(skip_serializing_if = "Option::is_none")]
582    pub subscribe: Option<bool>,
583    /// Support for resources/list_changed notifications.
584    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
585    pub list_changed: Option<bool>,
586}
587
588/// Prompt capabilities for a server.
589#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
590pub struct PromptCapabilities {
591    /// Support for prompts/list_changed notifications.
592    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
593    pub list_changed: Option<bool>,
594}
595
596/// Server-side task capabilities.
597///
598/// Per MCP 2025-11-25: `{ list?: object, cancel?: object, requests?: { tools?: { call?: object } } }`
599#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
600pub struct ServerTaskCapabilities {
601    /// Support for tasks/list.
602    #[serde(skip_serializing_if = "Option::is_none")]
603    pub list: Option<HashMap<String, Value>>,
604    /// Support for tasks/cancel.
605    #[serde(skip_serializing_if = "Option::is_none")]
606    pub cancel: Option<HashMap<String, Value>>,
607    /// Request types that can be augmented with tasks.
608    #[serde(skip_serializing_if = "Option::is_none")]
609    pub requests: Option<ServerTaskRequests>,
610}
611
612/// Server-side task-augmented request capabilities.
613///
614/// Per spec: `{ tools?: { call?: object } }`
615#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
616pub struct ServerTaskRequests {
617    /// Task support for tool-related requests.
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub tools: Option<ServerTaskToolRequests>,
620}
621
622/// Server task-augmented tool request capabilities.
623#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
624pub struct ServerTaskToolRequests {
625    /// Whether the server supports task-augmented tools/call requests.
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub call: Option<HashMap<String, Value>>,
628}
629
630// =============================================================================
631// Tests
632// =============================================================================
633
634#[cfg(test)]
635mod tests {
636    use super::*;
637
638    #[test]
639    fn test_include_context_serde() {
640        // Verify camelCase serialization
641        let json = serde_json::to_string(&IncludeContext::ThisServer).unwrap();
642        assert_eq!(json, "\"thisServer\"");
643
644        let json = serde_json::to_string(&IncludeContext::AllServers).unwrap();
645        assert_eq!(json, "\"allServers\"");
646
647        let json = serde_json::to_string(&IncludeContext::None).unwrap();
648        assert_eq!(json, "\"none\"");
649
650        // Round-trip
651        let parsed: IncludeContext = serde_json::from_str("\"thisServer\"").unwrap();
652        assert_eq!(parsed, IncludeContext::ThisServer);
653    }
654
655    #[test]
656    fn test_tool_choice_mode_optional() {
657        // mode is optional, should serialize empty when None
658        let tc = ToolChoice { mode: None };
659        let json = serde_json::to_string(&tc).unwrap();
660        assert_eq!(json, "{}");
661
662        // Explicit mode
663        let tc = ToolChoice {
664            mode: Some(ToolChoiceMode::Required),
665        };
666        let json = serde_json::to_string(&tc).unwrap();
667        assert!(json.contains("\"required\""));
668    }
669
670    #[test]
671    fn test_model_hint_name_optional() {
672        let hint = ModelHint { name: None };
673        let json = serde_json::to_string(&hint).unwrap();
674        assert_eq!(json, "{}");
675
676        let hint = ModelHint {
677            name: Some("claude".into()),
678        };
679        let json = serde_json::to_string(&hint).unwrap();
680        assert!(json.contains("\"claude\""));
681    }
682
683    #[test]
684    fn test_task_status_serde() {
685        let json = serde_json::to_string(&TaskStatus::InputRequired).unwrap();
686        assert_eq!(json, "\"input_required\"");
687
688        let json = serde_json::to_string(&TaskStatus::Working).unwrap();
689        assert_eq!(json, "\"working\"");
690    }
691
692    #[test]
693    fn test_create_message_request_default() {
694        // Verify Default works (used in builder pattern)
695        let req = CreateMessageRequest {
696            messages: vec![SamplingMessage::user("hello")],
697            max_tokens: 100,
698            ..Default::default()
699        };
700        assert_eq!(req.messages.len(), 1);
701        assert_eq!(req.max_tokens, 100);
702        assert!(req.tools.is_none());
703    }
704
705    #[test]
706    fn test_sampling_message_content_single_or_array() {
707        // Single content
708        let msg = SamplingMessage::user("hello");
709        let json = serde_json::to_string(&msg).unwrap();
710        // Single should be an object, not array
711        assert!(json.contains("\"text\":\"hello\""));
712
713        // Round-trip
714        let parsed: SamplingMessage = serde_json::from_str(&json).unwrap();
715        assert_eq!(parsed.content.as_text(), Some("hello"));
716
717        // Array content
718        let json_array = r#"{"role":"user","content":[{"type":"text","text":"hello"},{"type":"text","text":"world"}]}"#;
719        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
720        match &parsed.content {
721            SamplingContentBlock::Multiple(v) => assert_eq!(v.len(), 2),
722            _ => panic!("Expected multiple content blocks"),
723        }
724    }
725
726    #[test]
727    fn test_server_capabilities_structure() {
728        let caps = ServerCapabilities {
729            tasks: Some(ServerTaskCapabilities {
730                list: Some(HashMap::new()),
731                cancel: Some(HashMap::new()),
732                requests: Some(ServerTaskRequests {
733                    tools: Some(ServerTaskToolRequests {
734                        call: Some(HashMap::new()),
735                    }),
736                }),
737            }),
738            ..Default::default()
739        };
740        let json = serde_json::to_string(&caps).unwrap();
741        let v: Value = serde_json::from_str(&json).unwrap();
742        // Verify nested structure matches spec
743        assert!(v["tasks"]["requests"]["tools"]["call"].is_object());
744    }
745
746    // C-3: ElicitAction and ElicitResult serde
747    #[test]
748    fn test_elicit_action_serde() {
749        let cases = [
750            (ElicitAction::Accept, "\"accept\""),
751            (ElicitAction::Decline, "\"decline\""),
752            (ElicitAction::Cancel, "\"cancel\""),
753        ];
754        for (action, expected) in cases {
755            let json = serde_json::to_string(&action).unwrap();
756            assert_eq!(json, expected);
757            let parsed: ElicitAction = serde_json::from_str(expected).unwrap();
758            assert_eq!(parsed, action);
759        }
760    }
761
762    #[test]
763    fn test_elicit_result_round_trip() {
764        let result = ElicitResult {
765            action: ElicitAction::Accept,
766            content: Some(serde_json::json!({"name": "test"})),
767            meta: None,
768        };
769        let json = serde_json::to_string(&result).unwrap();
770        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
771        assert_eq!(parsed.action, ElicitAction::Accept);
772        assert!(parsed.content.is_some());
773
774        // Decline with no content
775        let decline = ElicitResult {
776            action: ElicitAction::Decline,
777            content: None,
778            meta: None,
779        };
780        let json = serde_json::to_string(&decline).unwrap();
781        assert!(!json.contains("\"content\""));
782        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
783        assert_eq!(parsed.action, ElicitAction::Decline);
784        assert!(parsed.content.is_none());
785    }
786
787    // H-7: ServerCapabilities must NOT contain elicitation or sampling
788    #[test]
789    fn test_server_capabilities_no_elicitation_or_sampling() {
790        let caps = ServerCapabilities::default();
791        let json = serde_json::to_string(&caps).unwrap();
792        assert!(!json.contains("elicitation"));
793        assert!(!json.contains("sampling"));
794
795        // Even fully populated
796        let caps = ServerCapabilities {
797            tools: Some(ToolCapabilities {
798                list_changed: Some(true),
799            }),
800            resources: Some(ResourceCapabilities {
801                subscribe: Some(true),
802                list_changed: Some(true),
803            }),
804            prompts: Some(PromptCapabilities {
805                list_changed: Some(true),
806            }),
807            logging: Some(HashMap::new()),
808            completions: Some(HashMap::new()),
809            tasks: Some(ServerTaskCapabilities::default()),
810            experimental: Some(HashMap::new()),
811        };
812        let json = serde_json::to_string(&caps).unwrap();
813        assert!(!json.contains("elicitation"));
814        assert!(!json.contains("sampling"));
815    }
816
817    // H-8: SamplingMessage array content round-trip preserves array
818    #[test]
819    fn test_sampling_message_array_content_round_trip() {
820        let json_array =
821            r#"{"role":"user","content":[{"type":"text","text":"a"},{"type":"text","text":"b"}]}"#;
822        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
823        let re_serialized = serde_json::to_string(&parsed).unwrap();
824        let re_parsed: Value = serde_json::from_str(&re_serialized).unwrap();
825        assert!(re_parsed["content"].is_array());
826        assert_eq!(re_parsed["content"].as_array().unwrap().len(), 2);
827    }
828
829    // H-10: All ToolChoiceMode variants
830    #[test]
831    fn test_tool_choice_mode_all_variants() {
832        let cases = [
833            (ToolChoiceMode::Auto, "\"auto\""),
834            (ToolChoiceMode::None, "\"none\""),
835            (ToolChoiceMode::Required, "\"required\""),
836        ];
837        for (mode, expected) in cases {
838            let json = serde_json::to_string(&mode).unwrap();
839            assert_eq!(json, expected);
840            let parsed: ToolChoiceMode = serde_json::from_str(expected).unwrap();
841            assert_eq!(parsed, mode);
842        }
843    }
844
845    // CRITICAL-2: ElicitRequestParams custom serde - optional mode field
846    #[test]
847    fn test_elicit_request_params_form_without_mode() {
848        // Per MCP 2025-11-25, mode is optional and defaults to "form"
849        let json = r#"{"message":"Enter name","requestedSchema":{"type":"object"}}"#;
850        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
851        match &parsed {
852            ElicitRequestParams::Form(params) => {
853                assert_eq!(params.message, "Enter name");
854            }
855            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
856        }
857    }
858
859    #[test]
860    fn test_elicit_request_params_form_with_explicit_mode() {
861        let json = r#"{"mode":"form","message":"Enter name","requestedSchema":{"type":"object"}}"#;
862        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
863        match &parsed {
864            ElicitRequestParams::Form(params) => {
865                assert_eq!(params.message, "Enter name");
866            }
867            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
868        }
869    }
870
871    #[test]
872    fn test_elicit_request_params_url_mode() {
873        let json = r#"{"mode":"url","message":"Authenticate","url":"https://example.com/auth","elicitationId":"e-123"}"#;
874        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
875        match &parsed {
876            ElicitRequestParams::Url(params) => {
877                assert_eq!(params.message, "Authenticate");
878                assert_eq!(params.url, "https://example.com/auth");
879                assert_eq!(params.elicitation_id, "e-123");
880            }
881            ElicitRequestParams::Form(_) => panic!("expected Url variant"),
882        }
883    }
884
885    #[test]
886    fn test_elicit_request_params_form_round_trip() {
887        let params = ElicitRequestParams::Form(ElicitRequestFormParams {
888            message: "Enter details".into(),
889            requested_schema: serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}}),
890            task: None,
891            meta: None,
892        });
893        let json = serde_json::to_string(&params).unwrap();
894        // Serialized output must include mode: "form"
895        let v: Value = serde_json::from_str(&json).unwrap();
896        assert_eq!(v["mode"], "form");
897        // Round-trip
898        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
899        assert_eq!(parsed, params);
900    }
901
902    #[test]
903    fn test_elicit_request_params_url_round_trip() {
904        let params = ElicitRequestParams::Url(ElicitRequestURLParams {
905            message: "Please authenticate".into(),
906            url: "https://example.com/oauth".into(),
907            elicitation_id: "elicit-456".into(),
908            task: None,
909            meta: None,
910        });
911        let json = serde_json::to_string(&params).unwrap();
912        let v: Value = serde_json::from_str(&json).unwrap();
913        assert_eq!(v["mode"], "url");
914        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
915        assert_eq!(parsed, params);
916    }
917
918    // M-6: All TaskStatus variants
919    #[test]
920    fn test_task_status_all_variants() {
921        let cases = [
922            (TaskStatus::Cancelled, "\"cancelled\""),
923            (TaskStatus::Completed, "\"completed\""),
924            (TaskStatus::Failed, "\"failed\""),
925            (TaskStatus::InputRequired, "\"input_required\""),
926            (TaskStatus::Working, "\"working\""),
927        ];
928        for (status, expected) in cases {
929            let json = serde_json::to_string(&status).unwrap();
930            assert_eq!(json, expected);
931            let parsed: TaskStatus = serde_json::from_str(expected).unwrap();
932            assert_eq!(parsed, status);
933        }
934    }
935}