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    /// Draft extensions supported by the client.
465    #[serde(skip_serializing_if = "Option::is_none")]
466    pub extensions: Option<HashMap<String, Value>>,
467    /// Experimental capabilities.
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub experimental: Option<HashMap<String, Value>>,
470}
471
472/// Elicitation capabilities for a client.
473#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
474pub struct ElicitationCapabilities {
475    /// Support for form-based elicitation.
476    #[serde(skip_serializing_if = "Option::is_none")]
477    pub form: Option<HashMap<String, Value>>,
478    /// Support for URL-based elicitation (new in 2025-11-25).
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub url: Option<HashMap<String, Value>>,
481}
482
483/// Sampling capabilities for a client.
484#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
485pub struct SamplingCapabilities {
486    /// Support for context inclusion (soft-deprecated).
487    #[serde(skip_serializing_if = "Option::is_none")]
488    pub context: Option<HashMap<String, Value>>,
489    /// Support for tool use in sampling (new in 2025-11-25).
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub tools: Option<HashMap<String, Value>>,
492}
493
494/// Roots capabilities for a client.
495#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
496pub struct RootsCapabilities {
497    /// Support for roots/list_changed notifications.
498    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
499    pub list_changed: Option<bool>,
500}
501
502/// Task capabilities for a client.
503#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
504pub struct ClientTaskCapabilities {
505    /// Support for tasks/list.
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub list: Option<HashMap<String, Value>>,
508    /// Support for tasks/cancel.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub cancel: Option<HashMap<String, Value>>,
511    /// Requests that can be augmented with tasks.
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub requests: Option<ClientTaskRequests>,
514}
515
516/// Client-side task-augmented request capabilities.
517#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
518pub struct ClientTaskRequests {
519    /// Support for task-augmented sampling.
520    #[serde(skip_serializing_if = "Option::is_none")]
521    pub sampling: Option<ClientTaskSamplingRequests>,
522    /// Support for task-augmented elicitation.
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub elicitation: Option<ClientTaskElicitationRequests>,
525}
526
527/// Client task-augmented sampling request capabilities.
528#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
529pub struct ClientTaskSamplingRequests {
530    /// Support for task-augmented sampling/createMessage.
531    #[serde(rename = "createMessage", skip_serializing_if = "Option::is_none")]
532    pub create_message: Option<HashMap<String, Value>>,
533}
534
535/// Client task-augmented elicitation request capabilities.
536#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
537pub struct ClientTaskElicitationRequests {
538    /// Support for task-augmented elicitation/create.
539    #[serde(skip_serializing_if = "Option::is_none")]
540    pub create: Option<HashMap<String, Value>>,
541}
542
543/// Capabilities supported by a server.
544///
545/// Per MCP 2025-11-25, server capabilities are:
546/// `tools`, `resources`, `prompts`, `logging`, `completions`, `tasks`, `experimental`
547#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
548pub struct ServerCapabilities {
549    /// Support for tools.
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub tools: Option<ToolCapabilities>,
552    /// Support for resources.
553    #[serde(skip_serializing_if = "Option::is_none")]
554    pub resources: Option<ResourceCapabilities>,
555    /// Support for prompts.
556    #[serde(skip_serializing_if = "Option::is_none")]
557    pub prompts: Option<PromptCapabilities>,
558    /// Support for logging.
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub logging: Option<HashMap<String, Value>>,
561    /// Support for argument autocompletion.
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub completions: Option<HashMap<String, Value>>,
564    /// Support for task-augmented requests (experimental in 2025-11-25).
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub tasks: Option<ServerTaskCapabilities>,
567    /// Draft extensions supported by the server.
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub extensions: Option<HashMap<String, Value>>,
570    /// Experimental capabilities.
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub experimental: Option<HashMap<String, Value>>,
573}
574
575/// Tool capabilities for a server.
576#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
577pub struct ToolCapabilities {
578    /// Support for tools/list_changed notifications.
579    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
580    pub list_changed: Option<bool>,
581}
582
583/// Resource capabilities for a server.
584#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
585pub struct ResourceCapabilities {
586    /// Support for resources/subscribe and notifications/resources/updated.
587    #[serde(skip_serializing_if = "Option::is_none")]
588    pub subscribe: Option<bool>,
589    /// Support for resources/list_changed notifications.
590    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
591    pub list_changed: Option<bool>,
592}
593
594/// Prompt capabilities for a server.
595#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
596pub struct PromptCapabilities {
597    /// Support for prompts/list_changed notifications.
598    #[serde(rename = "listChanged", skip_serializing_if = "Option::is_none")]
599    pub list_changed: Option<bool>,
600}
601
602/// Server-side task capabilities.
603///
604/// Per MCP 2025-11-25: `{ list?: object, cancel?: object, requests?: { tools?: { call?: object } } }`
605#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
606pub struct ServerTaskCapabilities {
607    /// Support for tasks/list.
608    #[serde(skip_serializing_if = "Option::is_none")]
609    pub list: Option<HashMap<String, Value>>,
610    /// Support for tasks/cancel.
611    #[serde(skip_serializing_if = "Option::is_none")]
612    pub cancel: Option<HashMap<String, Value>>,
613    /// Request types that can be augmented with tasks.
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub requests: Option<ServerTaskRequests>,
616}
617
618/// Server-side task-augmented request capabilities.
619///
620/// Per spec: `{ tools?: { call?: object } }`
621#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
622pub struct ServerTaskRequests {
623    /// Task support for tool-related requests.
624    #[serde(skip_serializing_if = "Option::is_none")]
625    pub tools: Option<ServerTaskToolRequests>,
626}
627
628/// Server task-augmented tool request capabilities.
629#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
630pub struct ServerTaskToolRequests {
631    /// Whether the server supports task-augmented tools/call requests.
632    #[serde(skip_serializing_if = "Option::is_none")]
633    pub call: Option<HashMap<String, Value>>,
634}
635
636// =============================================================================
637// Tests
638// =============================================================================
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643
644    #[test]
645    fn test_include_context_serde() {
646        // Verify camelCase serialization
647        let json = serde_json::to_string(&IncludeContext::ThisServer).unwrap();
648        assert_eq!(json, "\"thisServer\"");
649
650        let json = serde_json::to_string(&IncludeContext::AllServers).unwrap();
651        assert_eq!(json, "\"allServers\"");
652
653        let json = serde_json::to_string(&IncludeContext::None).unwrap();
654        assert_eq!(json, "\"none\"");
655
656        // Round-trip
657        let parsed: IncludeContext = serde_json::from_str("\"thisServer\"").unwrap();
658        assert_eq!(parsed, IncludeContext::ThisServer);
659    }
660
661    #[test]
662    fn test_tool_choice_mode_optional() {
663        // mode is optional, should serialize empty when None
664        let tc = ToolChoice { mode: None };
665        let json = serde_json::to_string(&tc).unwrap();
666        assert_eq!(json, "{}");
667
668        // Explicit mode
669        let tc = ToolChoice {
670            mode: Some(ToolChoiceMode::Required),
671        };
672        let json = serde_json::to_string(&tc).unwrap();
673        assert!(json.contains("\"required\""));
674    }
675
676    #[test]
677    fn test_model_hint_name_optional() {
678        let hint = ModelHint { name: None };
679        let json = serde_json::to_string(&hint).unwrap();
680        assert_eq!(json, "{}");
681
682        let hint = ModelHint {
683            name: Some("claude".into()),
684        };
685        let json = serde_json::to_string(&hint).unwrap();
686        assert!(json.contains("\"claude\""));
687    }
688
689    #[test]
690    fn test_task_status_serde() {
691        let json = serde_json::to_string(&TaskStatus::InputRequired).unwrap();
692        assert_eq!(json, "\"input_required\"");
693
694        let json = serde_json::to_string(&TaskStatus::Working).unwrap();
695        assert_eq!(json, "\"working\"");
696    }
697
698    #[test]
699    fn test_create_message_request_default() {
700        // Verify Default works (used in builder pattern)
701        let req = CreateMessageRequest {
702            messages: vec![SamplingMessage::user("hello")],
703            max_tokens: 100,
704            ..Default::default()
705        };
706        assert_eq!(req.messages.len(), 1);
707        assert_eq!(req.max_tokens, 100);
708        assert!(req.tools.is_none());
709    }
710
711    #[test]
712    fn test_sampling_message_content_single_or_array() {
713        // Single content
714        let msg = SamplingMessage::user("hello");
715        let json = serde_json::to_string(&msg).unwrap();
716        // Single should be an object, not array
717        assert!(json.contains("\"text\":\"hello\""));
718
719        // Round-trip
720        let parsed: SamplingMessage = serde_json::from_str(&json).unwrap();
721        assert_eq!(parsed.content.as_text(), Some("hello"));
722
723        // Array content
724        let json_array = r#"{"role":"user","content":[{"type":"text","text":"hello"},{"type":"text","text":"world"}]}"#;
725        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
726        match &parsed.content {
727            SamplingContentBlock::Multiple(v) => assert_eq!(v.len(), 2),
728            _ => panic!("Expected multiple content blocks"),
729        }
730    }
731
732    #[test]
733    fn test_server_capabilities_structure() {
734        let caps = ServerCapabilities {
735            tasks: Some(ServerTaskCapabilities {
736                list: Some(HashMap::new()),
737                cancel: Some(HashMap::new()),
738                requests: Some(ServerTaskRequests {
739                    tools: Some(ServerTaskToolRequests {
740                        call: Some(HashMap::new()),
741                    }),
742                }),
743            }),
744            extensions: Some(HashMap::from([(
745                "trace".to_string(),
746                serde_json::json!({"version": "1"}),
747            )])),
748            ..Default::default()
749        };
750        let json = serde_json::to_string(&caps).unwrap();
751        let v: Value = serde_json::from_str(&json).unwrap();
752        // Verify nested structure matches spec
753        assert!(v["tasks"]["requests"]["tools"]["call"].is_object());
754        assert!(v["extensions"]["trace"].is_object());
755    }
756
757    // C-3: ElicitAction and ElicitResult serde
758    #[test]
759    fn test_elicit_action_serde() {
760        let cases = [
761            (ElicitAction::Accept, "\"accept\""),
762            (ElicitAction::Decline, "\"decline\""),
763            (ElicitAction::Cancel, "\"cancel\""),
764        ];
765        for (action, expected) in cases {
766            let json = serde_json::to_string(&action).unwrap();
767            assert_eq!(json, expected);
768            let parsed: ElicitAction = serde_json::from_str(expected).unwrap();
769            assert_eq!(parsed, action);
770        }
771    }
772
773    #[test]
774    fn test_elicit_result_round_trip() {
775        let result = ElicitResult {
776            action: ElicitAction::Accept,
777            content: Some(serde_json::json!({"name": "test"})),
778            meta: None,
779        };
780        let json = serde_json::to_string(&result).unwrap();
781        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
782        assert_eq!(parsed.action, ElicitAction::Accept);
783        assert!(parsed.content.is_some());
784
785        // Decline with no content
786        let decline = ElicitResult {
787            action: ElicitAction::Decline,
788            content: None,
789            meta: None,
790        };
791        let json = serde_json::to_string(&decline).unwrap();
792        assert!(!json.contains("\"content\""));
793        let parsed: ElicitResult = serde_json::from_str(&json).unwrap();
794        assert_eq!(parsed.action, ElicitAction::Decline);
795        assert!(parsed.content.is_none());
796    }
797
798    // H-7: ServerCapabilities must NOT contain elicitation or sampling
799    #[test]
800    fn test_server_capabilities_no_elicitation_or_sampling() {
801        let caps = ServerCapabilities::default();
802        let json = serde_json::to_string(&caps).unwrap();
803        assert!(!json.contains("elicitation"));
804        assert!(!json.contains("sampling"));
805
806        // Even fully populated
807        let caps = ServerCapabilities {
808            tools: Some(ToolCapabilities {
809                list_changed: Some(true),
810            }),
811            resources: Some(ResourceCapabilities {
812                subscribe: Some(true),
813                list_changed: Some(true),
814            }),
815            prompts: Some(PromptCapabilities {
816                list_changed: Some(true),
817            }),
818            logging: Some(HashMap::new()),
819            completions: Some(HashMap::new()),
820            tasks: Some(ServerTaskCapabilities::default()),
821            extensions: Some(HashMap::from([(
822                "trace".to_string(),
823                serde_json::json!({"version": "1"}),
824            )])),
825            experimental: Some(HashMap::new()),
826        };
827        let json = serde_json::to_string(&caps).unwrap();
828        assert!(!json.contains("elicitation"));
829        assert!(!json.contains("sampling"));
830        assert!(json.contains("extensions"));
831    }
832
833    // H-8: SamplingMessage array content round-trip preserves array
834    #[test]
835    fn test_sampling_message_array_content_round_trip() {
836        let json_array =
837            r#"{"role":"user","content":[{"type":"text","text":"a"},{"type":"text","text":"b"}]}"#;
838        let parsed: SamplingMessage = serde_json::from_str(json_array).unwrap();
839        let re_serialized = serde_json::to_string(&parsed).unwrap();
840        let re_parsed: Value = serde_json::from_str(&re_serialized).unwrap();
841        assert!(re_parsed["content"].is_array());
842        assert_eq!(re_parsed["content"].as_array().unwrap().len(), 2);
843    }
844
845    // H-10: All ToolChoiceMode variants
846    #[test]
847    fn test_tool_choice_mode_all_variants() {
848        let cases = [
849            (ToolChoiceMode::Auto, "\"auto\""),
850            (ToolChoiceMode::None, "\"none\""),
851            (ToolChoiceMode::Required, "\"required\""),
852        ];
853        for (mode, expected) in cases {
854            let json = serde_json::to_string(&mode).unwrap();
855            assert_eq!(json, expected);
856            let parsed: ToolChoiceMode = serde_json::from_str(expected).unwrap();
857            assert_eq!(parsed, mode);
858        }
859    }
860
861    // CRITICAL-2: ElicitRequestParams custom serde - optional mode field
862    #[test]
863    fn test_elicit_request_params_form_without_mode() {
864        // Per MCP 2025-11-25, mode is optional and defaults to "form"
865        let json = r#"{"message":"Enter name","requestedSchema":{"type":"object"}}"#;
866        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
867        match &parsed {
868            ElicitRequestParams::Form(params) => {
869                assert_eq!(params.message, "Enter name");
870            }
871            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
872        }
873    }
874
875    #[test]
876    fn test_elicit_request_params_form_with_explicit_mode() {
877        let json = r#"{"mode":"form","message":"Enter name","requestedSchema":{"type":"object"}}"#;
878        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
879        match &parsed {
880            ElicitRequestParams::Form(params) => {
881                assert_eq!(params.message, "Enter name");
882            }
883            ElicitRequestParams::Url(_) => panic!("expected Form variant"),
884        }
885    }
886
887    #[test]
888    fn test_elicit_request_params_url_mode() {
889        let json = r#"{"mode":"url","message":"Authenticate","url":"https://example.com/auth","elicitationId":"e-123"}"#;
890        let parsed: ElicitRequestParams = serde_json::from_str(json).unwrap();
891        match &parsed {
892            ElicitRequestParams::Url(params) => {
893                assert_eq!(params.message, "Authenticate");
894                assert_eq!(params.url, "https://example.com/auth");
895                assert_eq!(params.elicitation_id, "e-123");
896            }
897            ElicitRequestParams::Form(_) => panic!("expected Url variant"),
898        }
899    }
900
901    #[test]
902    fn test_elicit_request_params_form_round_trip() {
903        let params = ElicitRequestParams::Form(ElicitRequestFormParams {
904            message: "Enter details".into(),
905            requested_schema: serde_json::json!({"type": "object", "properties": {"name": {"type": "string"}}}),
906            task: None,
907            meta: None,
908        });
909        let json = serde_json::to_string(&params).unwrap();
910        // Serialized output must include mode: "form"
911        let v: Value = serde_json::from_str(&json).unwrap();
912        assert_eq!(v["mode"], "form");
913        // Round-trip
914        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
915        assert_eq!(parsed, params);
916    }
917
918    #[test]
919    fn test_elicit_request_params_url_round_trip() {
920        let params = ElicitRequestParams::Url(ElicitRequestURLParams {
921            message: "Please authenticate".into(),
922            url: "https://example.com/oauth".into(),
923            elicitation_id: "elicit-456".into(),
924            task: None,
925            meta: None,
926        });
927        let json = serde_json::to_string(&params).unwrap();
928        let v: Value = serde_json::from_str(&json).unwrap();
929        assert_eq!(v["mode"], "url");
930        let parsed: ElicitRequestParams = serde_json::from_str(&json).unwrap();
931        assert_eq!(parsed, params);
932    }
933
934    // M-6: All TaskStatus variants
935    #[test]
936    fn test_task_status_all_variants() {
937        let cases = [
938            (TaskStatus::Cancelled, "\"cancelled\""),
939            (TaskStatus::Completed, "\"completed\""),
940            (TaskStatus::Failed, "\"failed\""),
941            (TaskStatus::InputRequired, "\"input_required\""),
942            (TaskStatus::Working, "\"working\""),
943        ];
944        for (status, expected) in cases {
945            let json = serde_json::to_string(&status).unwrap();
946            assert_eq!(json, expected);
947            let parsed: TaskStatus = serde_json::from_str(expected).unwrap();
948            assert_eq!(parsed, status);
949        }
950    }
951}