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