Skip to main content

fastmcp_protocol/
types.rs

1//! MCP protocol types.
2//!
3//! Core types used in MCP communication.
4
5use base64::Engine as _;
6use serde::{Deserialize, Serialize};
7
8/// MCP protocol version.
9pub const PROTOCOL_VERSION: &str = "2024-11-05";
10
11/// Server capabilities advertised during initialization.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct ServerCapabilities {
14    /// Tool-related capabilities.
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub tools: Option<ToolsCapability>,
17    /// Resource-related capabilities.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub resources: Option<ResourcesCapability>,
20    /// Prompt-related capabilities.
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub prompts: Option<PromptsCapability>,
23    /// Logging capability.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub logging: Option<LoggingCapability>,
26    /// Background tasks capability (Docket/SEP-1686).
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub tasks: Option<TasksCapability>,
29}
30
31/// Tool capabilities.
32#[derive(Debug, Clone, Default, Serialize, Deserialize)]
33pub struct ToolsCapability {
34    /// Whether the server supports tool list changes.
35    #[serde(
36        default,
37        rename = "listChanged",
38        skip_serializing_if = "std::ops::Not::not"
39    )]
40    pub list_changed: bool,
41}
42
43/// Resource capabilities.
44#[derive(Debug, Clone, Default, Serialize, Deserialize)]
45pub struct ResourcesCapability {
46    /// Whether the server supports resource subscriptions.
47    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
48    pub subscribe: bool,
49    /// Whether the server supports resource list changes.
50    #[serde(
51        default,
52        rename = "listChanged",
53        skip_serializing_if = "std::ops::Not::not"
54    )]
55    pub list_changed: bool,
56}
57
58/// Prompt capabilities.
59#[derive(Debug, Clone, Default, Serialize, Deserialize)]
60pub struct PromptsCapability {
61    /// Whether the server supports prompt list changes.
62    #[serde(
63        default,
64        rename = "listChanged",
65        skip_serializing_if = "std::ops::Not::not"
66    )]
67    pub list_changed: bool,
68}
69
70/// Logging capability.
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
72pub struct LoggingCapability {}
73
74/// Client capabilities.
75#[derive(Debug, Clone, Default, Serialize, Deserialize)]
76pub struct ClientCapabilities {
77    /// Sampling capability.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub sampling: Option<SamplingCapability>,
80    /// Elicitation capability (user input requests).
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub elicitation: Option<ElicitationCapability>,
83    /// Roots capability (filesystem roots).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub roots: Option<RootsCapability>,
86}
87
88/// Sampling capability.
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct SamplingCapability {}
91
92/// Capability for form mode elicitation.
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct FormElicitationCapability {}
95
96/// Capability for URL mode elicitation.
97#[derive(Debug, Clone, Default, Serialize, Deserialize)]
98pub struct UrlElicitationCapability {}
99
100/// Elicitation capability.
101///
102/// Clients must support at least one mode (form or url).
103#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104pub struct ElicitationCapability {
105    /// Present if the client supports form mode elicitation.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub form: Option<FormElicitationCapability>,
108    /// Present if the client supports URL mode elicitation.
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub url: Option<UrlElicitationCapability>,
111}
112
113impl ElicitationCapability {
114    /// Creates a form-mode elicitation capability.
115    #[must_use]
116    pub fn form() -> Self {
117        Self {
118            form: Some(FormElicitationCapability {}),
119            url: None,
120        }
121    }
122
123    /// Creates a URL-mode elicitation capability.
124    #[must_use]
125    pub fn url() -> Self {
126        Self {
127            form: None,
128            url: Some(UrlElicitationCapability {}),
129        }
130    }
131
132    /// Creates an elicitation capability supporting both modes.
133    #[must_use]
134    pub fn both() -> Self {
135        Self {
136            form: Some(FormElicitationCapability {}),
137            url: Some(UrlElicitationCapability {}),
138        }
139    }
140
141    /// Returns true if form mode is supported.
142    #[must_use]
143    pub fn supports_form(&self) -> bool {
144        self.form.is_some()
145    }
146
147    /// Returns true if URL mode is supported.
148    #[must_use]
149    pub fn supports_url(&self) -> bool {
150        self.url.is_some()
151    }
152}
153
154/// Roots capability.
155#[derive(Debug, Clone, Default, Serialize, Deserialize)]
156pub struct RootsCapability {
157    /// Whether the client supports list changes notifications.
158    #[serde(
159        rename = "listChanged",
160        default,
161        skip_serializing_if = "std::ops::Not::not"
162    )]
163    pub list_changed: bool,
164}
165
166/// A root definition representing a filesystem location.
167///
168/// Roots define the boundaries of where servers can operate within the filesystem,
169/// allowing them to understand which directories and files they have access to.
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
171pub struct Root {
172    /// Unique identifier for the root. Must be a `file://` URI.
173    pub uri: String,
174    /// Optional human-readable name for display purposes.
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub name: Option<String>,
177}
178
179impl Root {
180    /// Creates a new root with the given URI.
181    #[must_use]
182    pub fn new(uri: impl Into<String>) -> Self {
183        Self {
184            uri: uri.into(),
185            name: None,
186        }
187    }
188
189    /// Creates a new root with a name.
190    #[must_use]
191    pub fn with_name(uri: impl Into<String>, name: impl Into<String>) -> Self {
192        Self {
193            uri: uri.into(),
194            name: Some(name.into()),
195        }
196    }
197}
198
199/// Server information.
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct ServerInfo {
202    /// Server name.
203    pub name: String,
204    /// Server version.
205    pub version: String,
206}
207
208/// Client information.
209#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct ClientInfo {
211    /// Client name.
212    pub name: String,
213    /// Client version.
214    pub version: String,
215}
216
217// ============================================================================
218// Icon Metadata
219// ============================================================================
220
221/// Icon metadata for visual representation of components.
222///
223/// Icons provide visual representation for tools, resources, and prompts
224/// in client UIs. All fields are optional to support various use cases.
225#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
226#[serde(rename_all = "camelCase")]
227pub struct Icon {
228    /// URL or data URI for the icon.
229    ///
230    /// Can be:
231    /// - HTTP/HTTPS URL: `https://example.com/icon.png`
232    /// - Data URI: `data:image/png;base64,iVBORw0KGgo...`
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub src: Option<String>,
235
236    /// MIME type of the icon (e.g., "image/png", "image/svg+xml").
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub mime_type: Option<String>,
239
240    /// Size hints for the icon (e.g., "32x32", "16x16 32x32 64x64").
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub sizes: Option<String>,
243}
244
245impl Icon {
246    /// Creates a new icon with just a source URL.
247    #[must_use]
248    pub fn new(src: impl Into<String>) -> Self {
249        Self {
250            src: Some(src.into()),
251            mime_type: None,
252            sizes: None,
253        }
254    }
255
256    /// Creates a new icon with source and MIME type.
257    #[must_use]
258    pub fn with_mime_type(src: impl Into<String>, mime_type: impl Into<String>) -> Self {
259        Self {
260            src: Some(src.into()),
261            mime_type: Some(mime_type.into()),
262            sizes: None,
263        }
264    }
265
266    /// Creates a new icon with all fields.
267    #[must_use]
268    pub fn full(
269        src: impl Into<String>,
270        mime_type: impl Into<String>,
271        sizes: impl Into<String>,
272    ) -> Self {
273        Self {
274            src: Some(src.into()),
275            mime_type: Some(mime_type.into()),
276            sizes: Some(sizes.into()),
277        }
278    }
279
280    /// Returns true if this icon has a source.
281    #[must_use]
282    pub fn has_src(&self) -> bool {
283        self.src.is_some()
284    }
285
286    /// Returns true if the source is a data URI.
287    #[must_use]
288    pub fn is_data_uri(&self) -> bool {
289        self.src.as_ref().is_some_and(|s| s.starts_with("data:"))
290    }
291}
292
293// ============================================================================
294// Component Definitions
295// ============================================================================
296
297/// Tool annotations for additional metadata.
298///
299/// These annotations provide hints about tool behavior to help clients
300/// make informed decisions about tool usage.
301#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
302pub struct ToolAnnotations {
303    /// Whether the tool may cause destructive side effects.
304    /// True means the tool modifies external state (e.g., deleting files).
305    #[serde(skip_serializing_if = "Option::is_none")]
306    pub destructive: Option<bool>,
307    /// Whether the tool is idempotent (safe to retry without side effects).
308    /// True means calling the tool multiple times has the same effect as calling it once.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub idempotent: Option<bool>,
311    /// Whether the tool is read-only (has no side effects).
312    /// True means the tool only reads data without modifying anything.
313    #[serde(rename = "readOnly", skip_serializing_if = "Option::is_none")]
314    pub read_only: Option<bool>,
315    /// Hint about the tool's behavior with unknown inputs.
316    /// Can be used to indicate how the tool handles inputs not explicitly defined.
317    #[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
318    pub open_world_hint: Option<String>,
319}
320
321impl ToolAnnotations {
322    /// Creates a new empty annotations struct.
323    #[must_use]
324    pub fn new() -> Self {
325        Self::default()
326    }
327
328    /// Sets the destructive annotation.
329    #[must_use]
330    pub fn destructive(mut self, value: bool) -> Self {
331        self.destructive = Some(value);
332        self
333    }
334
335    /// Sets the idempotent annotation.
336    #[must_use]
337    pub fn idempotent(mut self, value: bool) -> Self {
338        self.idempotent = Some(value);
339        self
340    }
341
342    /// Sets the read_only annotation.
343    #[must_use]
344    pub fn read_only(mut self, value: bool) -> Self {
345        self.read_only = Some(value);
346        self
347    }
348
349    /// Sets the open_world_hint annotation.
350    #[must_use]
351    pub fn open_world_hint(mut self, hint: impl Into<String>) -> Self {
352        self.open_world_hint = Some(hint.into());
353        self
354    }
355
356    /// Returns true if any annotation is set.
357    #[must_use]
358    pub fn is_empty(&self) -> bool {
359        self.destructive.is_none()
360            && self.idempotent.is_none()
361            && self.read_only.is_none()
362            && self.open_world_hint.is_none()
363    }
364}
365
366/// Tool definition.
367#[derive(Debug, Clone, Serialize, Deserialize)]
368pub struct Tool {
369    /// Tool name.
370    pub name: String,
371    /// Tool description.
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub description: Option<String>,
374    /// Input schema (JSON Schema).
375    #[serde(rename = "inputSchema")]
376    pub input_schema: serde_json::Value,
377    /// Output schema (JSON Schema) describing the tool's result structure.
378    #[serde(rename = "outputSchema", skip_serializing_if = "Option::is_none")]
379    pub output_schema: Option<serde_json::Value>,
380    /// Icon for visual representation.
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub icon: Option<Icon>,
383    /// Component version (semver-like string).
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub version: Option<String>,
386    /// Tags for filtering and organization.
387    #[serde(default, skip_serializing_if = "Vec::is_empty")]
388    pub tags: Vec<String>,
389    /// Tool annotations providing behavioral hints.
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub annotations: Option<ToolAnnotations>,
392}
393
394/// Resource definition.
395#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct Resource {
397    /// Resource URI.
398    pub uri: String,
399    /// Resource name.
400    pub name: String,
401    /// Resource description.
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub description: Option<String>,
404    /// MIME type.
405    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
406    pub mime_type: Option<String>,
407    /// Icon for visual representation.
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub icon: Option<Icon>,
410    /// Component version (semver-like string).
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub version: Option<String>,
413    /// Tags for filtering and organization.
414    #[serde(default, skip_serializing_if = "Vec::is_empty")]
415    pub tags: Vec<String>,
416}
417
418/// Resource template definition.
419#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct ResourceTemplate {
421    /// URI template (RFC 6570).
422    #[serde(rename = "uriTemplate")]
423    pub uri_template: String,
424    /// Template name.
425    pub name: String,
426    /// Template description.
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub description: Option<String>,
429    /// MIME type.
430    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
431    pub mime_type: Option<String>,
432    /// Icon for visual representation.
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub icon: Option<Icon>,
435    /// Component version (semver-like string).
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub version: Option<String>,
438    /// Tags for filtering and organization.
439    #[serde(default, skip_serializing_if = "Vec::is_empty")]
440    pub tags: Vec<String>,
441}
442
443/// Prompt definition.
444#[derive(Debug, Clone, Serialize, Deserialize)]
445pub struct Prompt {
446    /// Prompt name.
447    pub name: String,
448    /// Prompt description.
449    #[serde(skip_serializing_if = "Option::is_none")]
450    pub description: Option<String>,
451    /// Prompt arguments.
452    #[serde(default, skip_serializing_if = "Vec::is_empty")]
453    pub arguments: Vec<PromptArgument>,
454    /// Icon for visual representation.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub icon: Option<Icon>,
457    /// Component version (semver-like string).
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub version: Option<String>,
460    /// Tags for filtering and organization.
461    #[serde(default, skip_serializing_if = "Vec::is_empty")]
462    pub tags: Vec<String>,
463}
464
465/// Prompt argument definition.
466#[derive(Debug, Clone, Serialize, Deserialize)]
467pub struct PromptArgument {
468    /// Argument name.
469    pub name: String,
470    /// Argument description.
471    #[serde(skip_serializing_if = "Option::is_none")]
472    pub description: Option<String>,
473    /// Whether the argument is required.
474    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
475    pub required: bool,
476}
477
478/// Content types in MCP messages.
479#[derive(Debug, Clone, Serialize, Deserialize)]
480#[serde(tag = "type", rename_all = "lowercase")]
481pub enum Content {
482    /// Text content.
483    Text {
484        /// The text content.
485        text: String,
486    },
487    /// Image content.
488    Image {
489        /// Base64-encoded image data.
490        data: String,
491        /// MIME type (e.g., "image/png").
492        #[serde(rename = "mimeType")]
493        mime_type: String,
494    },
495    /// Audio content.
496    Audio {
497        /// Base64-encoded audio data.
498        data: String,
499        /// MIME type (e.g., "audio/wav").
500        #[serde(rename = "mimeType")]
501        mime_type: String,
502    },
503    /// Resource content.
504    Resource {
505        /// The resource being referenced.
506        resource: ResourceContent,
507    },
508}
509
510impl Content {
511    /// Creates text content.
512    #[must_use]
513    pub fn text(text: impl Into<String>) -> Self {
514        Self::Text { text: text.into() }
515    }
516
517    /// Creates image content from base64-encoded data.
518    #[must_use]
519    pub fn image_base64(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
520        Self::Image {
521            data: data.into(),
522            mime_type: mime_type.into(),
523        }
524    }
525
526    /// Creates image content from raw bytes (base64-encodes internally).
527    #[must_use]
528    pub fn image_bytes(bytes: impl AsRef<[u8]>, mime_type: impl Into<String>) -> Self {
529        let data = base64::engine::general_purpose::STANDARD.encode(bytes.as_ref());
530        Self::image_base64(data, mime_type)
531    }
532
533    /// Creates audio content from base64-encoded data.
534    #[must_use]
535    pub fn audio_base64(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
536        Self::Audio {
537            data: data.into(),
538            mime_type: mime_type.into(),
539        }
540    }
541
542    /// Creates audio content from raw bytes (base64-encodes internally).
543    #[must_use]
544    pub fn audio_bytes(bytes: impl AsRef<[u8]>, mime_type: impl Into<String>) -> Self {
545        let data = base64::engine::general_purpose::STANDARD.encode(bytes.as_ref());
546        Self::audio_base64(data, mime_type)
547    }
548
549    /// Creates an embedded resource content with text payload.
550    #[must_use]
551    pub fn resource_text(
552        uri: impl Into<String>,
553        mime_type: Option<String>,
554        text: impl Into<String>,
555    ) -> Self {
556        Self::Resource {
557            resource: ResourceContent {
558                uri: uri.into(),
559                mime_type,
560                text: Some(text.into()),
561                blob: None,
562            },
563        }
564    }
565
566    /// Creates an embedded resource content with base64 blob payload.
567    #[must_use]
568    pub fn resource_blob_base64(
569        uri: impl Into<String>,
570        mime_type: Option<String>,
571        blob: impl Into<String>,
572    ) -> Self {
573        Self::Resource {
574            resource: ResourceContent {
575                uri: uri.into(),
576                mime_type,
577                text: None,
578                blob: Some(blob.into()),
579            },
580        }
581    }
582
583    /// Creates an embedded resource content with raw bytes payload (base64-encodes internally).
584    #[must_use]
585    pub fn resource_blob_bytes(
586        uri: impl Into<String>,
587        mime_type: Option<String>,
588        bytes: impl AsRef<[u8]>,
589    ) -> Self {
590        let blob = base64::engine::general_purpose::STANDARD.encode(bytes.as_ref());
591        Self::resource_blob_base64(uri, mime_type, blob)
592    }
593}
594
595/// Resource content in a message.
596#[derive(Debug, Clone, Serialize, Deserialize)]
597pub struct ResourceContent {
598    /// Resource URI.
599    pub uri: String,
600    /// MIME type.
601    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
602    pub mime_type: Option<String>,
603    /// Text content (if text).
604    #[serde(skip_serializing_if = "Option::is_none")]
605    pub text: Option<String>,
606    /// Binary content (if blob, base64).
607    #[serde(skip_serializing_if = "Option::is_none")]
608    pub blob: Option<String>,
609}
610
611/// Role in prompt messages.
612#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
613#[serde(rename_all = "lowercase")]
614pub enum Role {
615    /// User role.
616    User,
617    /// Assistant role.
618    Assistant,
619}
620
621/// A message in a prompt.
622#[derive(Debug, Clone, Serialize, Deserialize)]
623pub struct PromptMessage {
624    /// Message role.
625    pub role: Role,
626    /// Message content.
627    pub content: Content,
628}
629
630// ============================================================================
631// Background Tasks (Docket/SEP-1686)
632// ============================================================================
633
634/// Task identifier.
635///
636/// Unique identifier for background tasks.
637#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
638pub struct TaskId(pub String);
639
640impl TaskId {
641    /// Creates a new random task ID.
642    #[must_use]
643    pub fn new() -> Self {
644        use std::time::{SystemTime, UNIX_EPOCH};
645        let timestamp = SystemTime::now()
646            .duration_since(UNIX_EPOCH)
647            .unwrap_or_default()
648            .as_nanos();
649        Self(format!("task-{timestamp:x}"))
650    }
651
652    /// Creates a task ID from a string.
653    #[must_use]
654    pub fn from_string(s: impl Into<String>) -> Self {
655        Self(s.into())
656    }
657
658    /// Returns the task ID as a string.
659    #[must_use]
660    pub fn as_str(&self) -> &str {
661        &self.0
662    }
663}
664
665impl Default for TaskId {
666    fn default() -> Self {
667        Self::new()
668    }
669}
670
671impl std::fmt::Display for TaskId {
672    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
673        write!(f, "{}", self.0)
674    }
675}
676
677impl From<String> for TaskId {
678    fn from(s: String) -> Self {
679        Self(s)
680    }
681}
682
683impl From<&str> for TaskId {
684    fn from(s: &str) -> Self {
685        Self(s.to_owned())
686    }
687}
688
689/// Status of a background task.
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
691#[serde(rename_all = "lowercase")]
692pub enum TaskStatus {
693    /// Task is queued but not yet started.
694    Pending,
695    /// Task is currently running.
696    Running,
697    /// Task completed successfully.
698    Completed,
699    /// Task failed with an error.
700    Failed,
701    /// Task was cancelled.
702    Cancelled,
703}
704
705impl TaskStatus {
706    /// Returns true if the task is in a terminal state.
707    #[must_use]
708    pub fn is_terminal(&self) -> bool {
709        matches!(
710            self,
711            TaskStatus::Completed | TaskStatus::Failed | TaskStatus::Cancelled
712        )
713    }
714
715    /// Returns true if the task is still active.
716    #[must_use]
717    pub fn is_active(&self) -> bool {
718        matches!(self, TaskStatus::Pending | TaskStatus::Running)
719    }
720}
721
722/// Information about a background task.
723#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct TaskInfo {
725    /// Unique task identifier.
726    pub id: TaskId,
727    /// Task type (identifies the kind of work).
728    #[serde(rename = "taskType")]
729    pub task_type: String,
730    /// Current status.
731    pub status: TaskStatus,
732    /// Progress (0.0 to 1.0, if known).
733    #[serde(skip_serializing_if = "Option::is_none")]
734    pub progress: Option<f64>,
735    /// Progress message.
736    #[serde(skip_serializing_if = "Option::is_none")]
737    pub message: Option<String>,
738    /// Task creation timestamp (ISO 8601).
739    #[serde(rename = "createdAt")]
740    pub created_at: String,
741    /// Task start timestamp (ISO 8601), if started.
742    #[serde(rename = "startedAt", skip_serializing_if = "Option::is_none")]
743    pub started_at: Option<String>,
744    /// Task completion timestamp (ISO 8601), if completed.
745    #[serde(rename = "completedAt", skip_serializing_if = "Option::is_none")]
746    pub completed_at: Option<String>,
747    /// Error message if failed.
748    #[serde(skip_serializing_if = "Option::is_none")]
749    pub error: Option<String>,
750}
751
752/// Task result payload.
753#[derive(Debug, Clone, Serialize, Deserialize)]
754pub struct TaskResult {
755    /// Task identifier.
756    pub id: TaskId,
757    /// Whether the task succeeded.
758    pub success: bool,
759    /// Result data (if successful).
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub data: Option<serde_json::Value>,
762    /// Error message (if failed).
763    #[serde(skip_serializing_if = "Option::is_none")]
764    pub error: Option<String>,
765}
766
767/// Task capability for server capabilities.
768#[derive(Debug, Clone, Default, Serialize, Deserialize)]
769pub struct TasksCapability {
770    /// Whether the server supports task list changes notifications.
771    #[serde(
772        default,
773        rename = "listChanged",
774        skip_serializing_if = "std::ops::Not::not"
775    )]
776    pub list_changed: bool,
777}
778
779// ============================================================================
780// Sampling Protocol Types
781// ============================================================================
782
783/// Message content for sampling requests.
784///
785/// Can contain text, images, or tool-related content.
786#[derive(Debug, Clone, Serialize, Deserialize)]
787#[serde(tag = "type", rename_all = "lowercase")]
788pub enum SamplingContent {
789    /// Text content.
790    Text {
791        /// The text content.
792        text: String,
793    },
794    /// Image content.
795    Image {
796        /// Base64-encoded image data.
797        data: String,
798        /// MIME type (e.g., "image/png").
799        #[serde(rename = "mimeType")]
800        mime_type: String,
801    },
802}
803
804/// A message in a sampling conversation.
805#[derive(Debug, Clone, Serialize, Deserialize)]
806pub struct SamplingMessage {
807    /// Message role (user or assistant).
808    pub role: Role,
809    /// Message content.
810    pub content: SamplingContent,
811}
812
813impl SamplingMessage {
814    /// Creates a new user message with text content.
815    #[must_use]
816    pub fn user(text: impl Into<String>) -> Self {
817        Self {
818            role: Role::User,
819            content: SamplingContent::Text { text: text.into() },
820        }
821    }
822
823    /// Creates a new assistant message with text content.
824    #[must_use]
825    pub fn assistant(text: impl Into<String>) -> Self {
826        Self {
827            role: Role::Assistant,
828            content: SamplingContent::Text { text: text.into() },
829        }
830    }
831}
832
833/// Model preferences for sampling requests.
834#[derive(Debug, Clone, Default, Serialize, Deserialize)]
835pub struct ModelPreferences {
836    /// Hints for model selection (model names or patterns).
837    #[serde(default, skip_serializing_if = "Vec::is_empty")]
838    pub hints: Vec<ModelHint>,
839    /// Priority for cost (0.0 = lowest priority, 1.0 = highest).
840    #[serde(rename = "costPriority", skip_serializing_if = "Option::is_none")]
841    pub cost_priority: Option<f64>,
842    /// Priority for speed (0.0 = lowest priority, 1.0 = highest).
843    #[serde(rename = "speedPriority", skip_serializing_if = "Option::is_none")]
844    pub speed_priority: Option<f64>,
845    /// Priority for intelligence (0.0 = lowest priority, 1.0 = highest).
846    #[serde(
847        rename = "intelligencePriority",
848        skip_serializing_if = "Option::is_none"
849    )]
850    pub intelligence_priority: Option<f64>,
851}
852
853/// A hint for model selection.
854#[derive(Debug, Clone, Serialize, Deserialize)]
855pub struct ModelHint {
856    /// Model name or pattern.
857    #[serde(skip_serializing_if = "Option::is_none")]
858    pub name: Option<String>,
859}
860
861/// Stop reason for sampling responses.
862#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
863#[serde(rename_all = "camelCase")]
864pub enum StopReason {
865    /// End of natural turn.
866    #[default]
867    EndTurn,
868    /// Hit stop sequence.
869    StopSequence,
870    /// Hit max tokens limit.
871    MaxTokens,
872}
873
874// ============================================================================
875// Tests
876// ============================================================================
877
878#[cfg(test)]
879mod tests {
880    use super::*;
881    use serde_json::json;
882
883    // ========================================================================
884    // ServerCapabilities Tests
885    // ========================================================================
886
887    #[test]
888    fn server_capabilities_default_serialization() {
889        let caps = ServerCapabilities::default();
890        let value = serde_json::to_value(&caps).expect("serialize");
891        // All None fields should be omitted
892        assert_eq!(value, json!({}));
893    }
894
895    #[test]
896    fn server_capabilities_full_serialization() {
897        let caps = ServerCapabilities {
898            tools: Some(ToolsCapability { list_changed: true }),
899            resources: Some(ResourcesCapability {
900                subscribe: true,
901                list_changed: true,
902            }),
903            prompts: Some(PromptsCapability { list_changed: true }),
904            logging: Some(LoggingCapability {}),
905            tasks: Some(TasksCapability { list_changed: true }),
906        };
907        let value = serde_json::to_value(&caps).expect("serialize");
908        assert_eq!(value["tools"]["listChanged"], true);
909        assert_eq!(value["resources"]["subscribe"], true);
910        assert_eq!(value["resources"]["listChanged"], true);
911        assert_eq!(value["prompts"]["listChanged"], true);
912        assert!(value.get("logging").is_some());
913        assert_eq!(value["tasks"]["listChanged"], true);
914    }
915
916    #[test]
917    fn server_capabilities_partial_serialization() {
918        let caps = ServerCapabilities {
919            tools: Some(ToolsCapability::default()),
920            ..Default::default()
921        };
922        let value = serde_json::to_value(&caps).expect("serialize");
923        assert!(value.get("tools").is_some());
924        assert!(value.get("resources").is_none());
925        assert!(value.get("prompts").is_none());
926        assert!(value.get("logging").is_none());
927        assert!(value.get("tasks").is_none());
928    }
929
930    #[test]
931    fn server_capabilities_round_trip() {
932        let caps = ServerCapabilities {
933            tools: Some(ToolsCapability { list_changed: true }),
934            resources: Some(ResourcesCapability {
935                subscribe: false,
936                list_changed: true,
937            }),
938            prompts: None,
939            logging: Some(LoggingCapability {}),
940            tasks: None,
941        };
942        let json_str = serde_json::to_string(&caps).expect("serialize");
943        let deserialized: ServerCapabilities =
944            serde_json::from_str(&json_str).expect("deserialize");
945        assert!(deserialized.tools.is_some());
946        assert!(deserialized.tools.as_ref().unwrap().list_changed);
947        assert!(deserialized.resources.is_some());
948        assert!(!deserialized.resources.as_ref().unwrap().subscribe);
949        assert!(deserialized.prompts.is_none());
950        assert!(deserialized.logging.is_some());
951        assert!(deserialized.tasks.is_none());
952    }
953
954    // ========================================================================
955    // ToolsCapability Tests
956    // ========================================================================
957
958    #[test]
959    fn tools_capability_default_omits_false() {
960        let cap = ToolsCapability::default();
961        let value = serde_json::to_value(&cap).expect("serialize");
962        // list_changed defaults to false and should be omitted
963        assert!(value.get("listChanged").is_none());
964    }
965
966    #[test]
967    fn tools_capability_list_changed() {
968        let cap = ToolsCapability { list_changed: true };
969        let value = serde_json::to_value(&cap).expect("serialize");
970        assert_eq!(value["listChanged"], true);
971    }
972
973    // ========================================================================
974    // ResourcesCapability Tests
975    // ========================================================================
976
977    #[test]
978    fn resources_capability_default() {
979        let cap = ResourcesCapability::default();
980        let value = serde_json::to_value(&cap).expect("serialize");
981        assert!(value.get("subscribe").is_none());
982        assert!(value.get("listChanged").is_none());
983    }
984
985    #[test]
986    fn resources_capability_full() {
987        let cap = ResourcesCapability {
988            subscribe: true,
989            list_changed: true,
990        };
991        let value = serde_json::to_value(&cap).expect("serialize");
992        assert_eq!(value["subscribe"], true);
993        assert_eq!(value["listChanged"], true);
994    }
995
996    // ========================================================================
997    // ClientCapabilities Tests
998    // ========================================================================
999
1000    #[test]
1001    fn client_capabilities_default_serialization() {
1002        let caps = ClientCapabilities::default();
1003        let value = serde_json::to_value(&caps).expect("serialize");
1004        assert_eq!(value, json!({}));
1005    }
1006
1007    #[test]
1008    fn client_capabilities_full_serialization() {
1009        let caps = ClientCapabilities {
1010            sampling: Some(SamplingCapability {}),
1011            elicitation: Some(ElicitationCapability::both()),
1012            roots: Some(RootsCapability { list_changed: true }),
1013        };
1014        let value = serde_json::to_value(&caps).expect("serialize");
1015        assert!(value.get("sampling").is_some());
1016        assert!(value.get("elicitation").is_some());
1017        assert_eq!(value["roots"]["listChanged"], true);
1018    }
1019
1020    #[test]
1021    fn client_capabilities_round_trip() {
1022        let caps = ClientCapabilities {
1023            sampling: Some(SamplingCapability {}),
1024            elicitation: None,
1025            roots: Some(RootsCapability {
1026                list_changed: false,
1027            }),
1028        };
1029        let json_str = serde_json::to_string(&caps).expect("serialize");
1030        let deserialized: ClientCapabilities =
1031            serde_json::from_str(&json_str).expect("deserialize");
1032        assert!(deserialized.sampling.is_some());
1033        assert!(deserialized.elicitation.is_none());
1034        assert!(deserialized.roots.is_some());
1035    }
1036
1037    // ========================================================================
1038    // ElicitationCapability Tests
1039    // ========================================================================
1040
1041    #[test]
1042    fn elicitation_capability_form_only() {
1043        let cap = ElicitationCapability::form();
1044        assert!(cap.supports_form());
1045        assert!(!cap.supports_url());
1046        let value = serde_json::to_value(&cap).expect("serialize");
1047        assert!(value.get("form").is_some());
1048        assert!(value.get("url").is_none());
1049    }
1050
1051    #[test]
1052    fn elicitation_capability_url_only() {
1053        let cap = ElicitationCapability::url();
1054        assert!(!cap.supports_form());
1055        assert!(cap.supports_url());
1056    }
1057
1058    #[test]
1059    fn elicitation_capability_both() {
1060        let cap = ElicitationCapability::both();
1061        assert!(cap.supports_form());
1062        assert!(cap.supports_url());
1063    }
1064
1065    // ========================================================================
1066    // ServerInfo / ClientInfo Tests
1067    // ========================================================================
1068
1069    #[test]
1070    fn server_info_serialization() {
1071        let info = ServerInfo {
1072            name: "test-server".to_string(),
1073            version: "1.0.0".to_string(),
1074        };
1075        let value = serde_json::to_value(&info).expect("serialize");
1076        assert_eq!(value["name"], "test-server");
1077        assert_eq!(value["version"], "1.0.0");
1078    }
1079
1080    #[test]
1081    fn client_info_serialization() {
1082        let info = ClientInfo {
1083            name: "test-client".to_string(),
1084            version: "0.1.0".to_string(),
1085        };
1086        let value = serde_json::to_value(&info).expect("serialize");
1087        assert_eq!(value["name"], "test-client");
1088        assert_eq!(value["version"], "0.1.0");
1089    }
1090
1091    // ========================================================================
1092    // Icon Tests
1093    // ========================================================================
1094
1095    #[test]
1096    fn icon_new() {
1097        let icon = Icon::new("https://example.com/icon.png");
1098        assert!(icon.has_src());
1099        assert!(!icon.is_data_uri());
1100        assert!(icon.mime_type.is_none());
1101        assert!(icon.sizes.is_none());
1102    }
1103
1104    #[test]
1105    fn icon_with_mime_type() {
1106        let icon = Icon::with_mime_type("https://example.com/icon.png", "image/png");
1107        assert!(icon.has_src());
1108        assert_eq!(icon.mime_type, Some("image/png".to_string()));
1109    }
1110
1111    #[test]
1112    fn icon_full() {
1113        let icon = Icon::full("https://example.com/icon.png", "image/png", "32x32");
1114        assert_eq!(icon.src, Some("https://example.com/icon.png".to_string()));
1115        assert_eq!(icon.mime_type, Some("image/png".to_string()));
1116        assert_eq!(icon.sizes, Some("32x32".to_string()));
1117    }
1118
1119    #[test]
1120    fn icon_data_uri() {
1121        let icon = Icon::new("data:image/png;base64,iVBORw0KGgo=");
1122        assert!(icon.is_data_uri());
1123    }
1124
1125    #[test]
1126    fn icon_default_no_src() {
1127        let icon = Icon::default();
1128        assert!(!icon.has_src());
1129        assert!(!icon.is_data_uri());
1130    }
1131
1132    #[test]
1133    fn icon_serialization() {
1134        let icon = Icon::full(
1135            "https://example.com/icon.svg",
1136            "image/svg+xml",
1137            "16x16 32x32",
1138        );
1139        let value = serde_json::to_value(&icon).expect("serialize");
1140        assert_eq!(value["src"], "https://example.com/icon.svg");
1141        assert_eq!(value["mimeType"], "image/svg+xml");
1142        assert_eq!(value["sizes"], "16x16 32x32");
1143    }
1144
1145    #[test]
1146    fn icon_serialization_omits_none_fields() {
1147        let icon = Icon::new("https://example.com/icon.png");
1148        let value = serde_json::to_value(&icon).expect("serialize");
1149        assert!(value.get("src").is_some());
1150        assert!(value.get("mimeType").is_none());
1151        assert!(value.get("sizes").is_none());
1152    }
1153
1154    #[test]
1155    fn icon_equality() {
1156        let a = Icon::new("https://example.com/icon.png");
1157        let b = Icon::new("https://example.com/icon.png");
1158        let c = Icon::new("https://example.com/other.png");
1159        assert_eq!(a, b);
1160        assert_ne!(a, c);
1161    }
1162
1163    // ========================================================================
1164    // Content Tests
1165    // ========================================================================
1166
1167    #[test]
1168    fn content_text_serialization() {
1169        let content = Content::Text {
1170            text: "Hello, world!".to_string(),
1171        };
1172        let value = serde_json::to_value(&content).expect("serialize");
1173        assert_eq!(value["type"], "text");
1174        assert_eq!(value["text"], "Hello, world!");
1175    }
1176
1177    #[test]
1178    fn content_image_serialization() {
1179        let content = Content::Image {
1180            data: "iVBORw0KGgo=".to_string(),
1181            mime_type: "image/png".to_string(),
1182        };
1183        let value = serde_json::to_value(&content).expect("serialize");
1184        assert_eq!(value["type"], "image");
1185        assert_eq!(value["data"], "iVBORw0KGgo=");
1186        assert_eq!(value["mimeType"], "image/png");
1187    }
1188
1189    #[test]
1190    fn content_audio_serialization() {
1191        let content = Content::Audio {
1192            data: "UklGRg==".to_string(),
1193            mime_type: "audio/wav".to_string(),
1194        };
1195        let value = serde_json::to_value(&content).expect("serialize");
1196        assert_eq!(value["type"], "audio");
1197        assert_eq!(value["data"], "UklGRg==");
1198        assert_eq!(value["mimeType"], "audio/wav");
1199    }
1200
1201    #[test]
1202    fn content_resource_serialization() {
1203        let content = Content::Resource {
1204            resource: ResourceContent {
1205                uri: "file://config.json".to_string(),
1206                mime_type: Some("application/json".to_string()),
1207                text: Some("{\"key\": \"value\"}".to_string()),
1208                blob: None,
1209            },
1210        };
1211        let value = serde_json::to_value(&content).expect("serialize");
1212        assert_eq!(value["type"], "resource");
1213        assert_eq!(value["resource"]["uri"], "file://config.json");
1214        assert_eq!(value["resource"]["mimeType"], "application/json");
1215        assert_eq!(value["resource"]["text"], "{\"key\": \"value\"}");
1216        assert!(value["resource"].get("blob").is_none());
1217    }
1218
1219    #[test]
1220    fn content_text_deserialization() {
1221        let json = json!({"type": "text", "text": "Hello!"});
1222        let content: Content = serde_json::from_value(json).expect("deserialize");
1223        let text = match content {
1224            Content::Text { text } => Some(text),
1225            _ => None,
1226        };
1227        assert_eq!(text.as_deref(), Some("Hello!"));
1228    }
1229
1230    #[test]
1231    fn content_image_deserialization() {
1232        let json = json!({"type": "image", "data": "abc123", "mimeType": "image/jpeg"});
1233        let content: Content = serde_json::from_value(json).expect("deserialize");
1234        let (data, mime_type) = match content {
1235            Content::Image { data, mime_type } => (Some(data), Some(mime_type)),
1236            _ => (None, None),
1237        };
1238        assert_eq!(data.as_deref(), Some("abc123"));
1239        assert_eq!(mime_type.as_deref(), Some("image/jpeg"));
1240    }
1241
1242    #[test]
1243    fn content_audio_deserialization() {
1244        let json = json!({"type": "audio", "data": "abc123", "mimeType": "audio/mpeg"});
1245        let content: Content = serde_json::from_value(json).expect("deserialize");
1246        let (data, mime_type) = match content {
1247            Content::Audio { data, mime_type } => (Some(data), Some(mime_type)),
1248            _ => (None, None),
1249        };
1250        assert_eq!(data.as_deref(), Some("abc123"));
1251        assert_eq!(mime_type.as_deref(), Some("audio/mpeg"));
1252    }
1253
1254    // ========================================================================
1255    // ResourceContent Tests
1256    // ========================================================================
1257
1258    #[test]
1259    fn resource_content_text_serialization() {
1260        let rc = ResourceContent {
1261            uri: "file://readme.md".to_string(),
1262            mime_type: Some("text/markdown".to_string()),
1263            text: Some("# Hello".to_string()),
1264            blob: None,
1265        };
1266        let value = serde_json::to_value(&rc).expect("serialize");
1267        assert_eq!(value["uri"], "file://readme.md");
1268        assert_eq!(value["mimeType"], "text/markdown");
1269        assert_eq!(value["text"], "# Hello");
1270        assert!(value.get("blob").is_none());
1271    }
1272
1273    #[test]
1274    fn resource_content_blob_serialization() {
1275        let rc = ResourceContent {
1276            uri: "file://image.png".to_string(),
1277            mime_type: Some("image/png".to_string()),
1278            text: None,
1279            blob: Some("base64data".to_string()),
1280        };
1281        let value = serde_json::to_value(&rc).expect("serialize");
1282        assert_eq!(value["uri"], "file://image.png");
1283        assert!(value.get("text").is_none());
1284        assert_eq!(value["blob"], "base64data");
1285    }
1286
1287    #[test]
1288    fn resource_content_minimal() {
1289        let rc = ResourceContent {
1290            uri: "file://test".to_string(),
1291            mime_type: None,
1292            text: None,
1293            blob: None,
1294        };
1295        let value = serde_json::to_value(&rc).expect("serialize");
1296        assert_eq!(value["uri"], "file://test");
1297        assert!(value.get("mimeType").is_none());
1298        assert!(value.get("text").is_none());
1299        assert!(value.get("blob").is_none());
1300    }
1301
1302    // ========================================================================
1303    // Role Tests
1304    // ========================================================================
1305
1306    #[test]
1307    fn role_serialization() {
1308        assert_eq!(serde_json::to_value(Role::User).unwrap(), "user");
1309        assert_eq!(serde_json::to_value(Role::Assistant).unwrap(), "assistant");
1310    }
1311
1312    #[test]
1313    fn role_deserialization() {
1314        let user: Role = serde_json::from_value(json!("user")).expect("deserialize");
1315        assert_eq!(user, Role::User);
1316        let assistant: Role = serde_json::from_value(json!("assistant")).expect("deserialize");
1317        assert_eq!(assistant, Role::Assistant);
1318    }
1319
1320    // ========================================================================
1321    // PromptMessage Tests
1322    // ========================================================================
1323
1324    #[test]
1325    fn prompt_message_serialization() {
1326        let msg = PromptMessage {
1327            role: Role::User,
1328            content: Content::Text {
1329                text: "Tell me a joke".to_string(),
1330            },
1331        };
1332        let value = serde_json::to_value(&msg).expect("serialize");
1333        assert_eq!(value["role"], "user");
1334        assert_eq!(value["content"]["type"], "text");
1335        assert_eq!(value["content"]["text"], "Tell me a joke");
1336    }
1337
1338    #[test]
1339    fn prompt_message_assistant() {
1340        let msg = PromptMessage {
1341            role: Role::Assistant,
1342            content: Content::Text {
1343                text: "Here's a joke...".to_string(),
1344            },
1345        };
1346        let value = serde_json::to_value(&msg).expect("serialize");
1347        assert_eq!(value["role"], "assistant");
1348    }
1349
1350    // ========================================================================
1351    // PromptArgument Tests
1352    // ========================================================================
1353
1354    #[test]
1355    fn prompt_argument_required() {
1356        let arg = PromptArgument {
1357            name: "language".to_string(),
1358            description: Some("Target language".to_string()),
1359            required: true,
1360        };
1361        let value = serde_json::to_value(&arg).expect("serialize");
1362        assert_eq!(value["name"], "language");
1363        assert_eq!(value["description"], "Target language");
1364        assert_eq!(value["required"], true);
1365    }
1366
1367    #[test]
1368    fn prompt_argument_optional_omits_false() {
1369        let arg = PromptArgument {
1370            name: "style".to_string(),
1371            description: None,
1372            required: false,
1373        };
1374        let value = serde_json::to_value(&arg).expect("serialize");
1375        assert_eq!(value["name"], "style");
1376        assert!(value.get("description").is_none());
1377        // required=false should be omitted
1378        assert!(value.get("required").is_none());
1379    }
1380
1381    #[test]
1382    fn prompt_argument_deserialization_defaults() {
1383        let json = json!({"name": "arg1"});
1384        let arg: PromptArgument = serde_json::from_value(json).expect("deserialize");
1385        assert_eq!(arg.name, "arg1");
1386        assert!(arg.description.is_none());
1387        assert!(!arg.required);
1388    }
1389
1390    // ========================================================================
1391    // Tool Definition Tests
1392    // ========================================================================
1393
1394    #[test]
1395    fn tool_minimal_serialization() {
1396        let tool = Tool {
1397            name: "add".to_string(),
1398            description: None,
1399            input_schema: json!({"type": "object"}),
1400            output_schema: None,
1401            icon: None,
1402            version: None,
1403            tags: vec![],
1404            annotations: None,
1405        };
1406        let value = serde_json::to_value(&tool).expect("serialize");
1407        assert_eq!(value["name"], "add");
1408        assert_eq!(value["inputSchema"]["type"], "object");
1409        assert!(value.get("description").is_none());
1410        assert!(value.get("outputSchema").is_none());
1411        assert!(value.get("icon").is_none());
1412        assert!(value.get("version").is_none());
1413        assert!(value.get("tags").is_none());
1414        assert!(value.get("annotations").is_none());
1415    }
1416
1417    #[test]
1418    fn tool_full_serialization() {
1419        let tool = Tool {
1420            name: "compute".to_string(),
1421            description: Some("Runs a computation".to_string()),
1422            input_schema: json!({
1423                "type": "object",
1424                "properties": { "x": { "type": "number" } },
1425                "required": ["x"]
1426            }),
1427            output_schema: Some(json!({"type": "number"})),
1428            icon: Some(Icon::new("https://example.com/icon.png")),
1429            version: Some("2.1.0".to_string()),
1430            tags: vec!["math".to_string(), "compute".to_string()],
1431            annotations: Some(ToolAnnotations::new().read_only(true).idempotent(true)),
1432        };
1433        let value = serde_json::to_value(&tool).expect("serialize");
1434        assert_eq!(value["name"], "compute");
1435        assert_eq!(value["description"], "Runs a computation");
1436        assert!(value["inputSchema"]["properties"]["x"].is_object());
1437        assert_eq!(value["outputSchema"]["type"], "number");
1438        assert_eq!(value["icon"]["src"], "https://example.com/icon.png");
1439        assert_eq!(value["version"], "2.1.0");
1440        assert_eq!(value["tags"], json!(["math", "compute"]));
1441        assert_eq!(value["annotations"]["readOnly"], true);
1442        assert_eq!(value["annotations"]["idempotent"], true);
1443    }
1444
1445    #[test]
1446    fn tool_round_trip() {
1447        let json = json!({
1448            "name": "greet",
1449            "description": "Greets the user",
1450            "inputSchema": {"type": "object", "properties": {"name": {"type": "string"}}},
1451            "outputSchema": {"type": "string"},
1452            "version": "1.0.0",
1453            "tags": ["greeting"],
1454            "annotations": {"readOnly": true}
1455        });
1456        let tool: Tool = serde_json::from_value(json.clone()).expect("deserialize");
1457        assert_eq!(tool.name, "greet");
1458        assert_eq!(tool.version, Some("1.0.0".to_string()));
1459        assert_eq!(tool.tags, vec!["greeting"]);
1460        assert!(tool.annotations.as_ref().unwrap().read_only.unwrap());
1461        let re_serialized = serde_json::to_value(&tool).expect("re-serialize");
1462        assert_eq!(re_serialized["name"], json["name"]);
1463    }
1464
1465    // ========================================================================
1466    // Resource Definition Tests
1467    // ========================================================================
1468
1469    #[test]
1470    fn resource_minimal_serialization() {
1471        let resource = Resource {
1472            uri: "file://test.txt".to_string(),
1473            name: "Test File".to_string(),
1474            description: None,
1475            mime_type: None,
1476            icon: None,
1477            version: None,
1478            tags: vec![],
1479        };
1480        let value = serde_json::to_value(&resource).expect("serialize");
1481        assert_eq!(value["uri"], "file://test.txt");
1482        assert_eq!(value["name"], "Test File");
1483        assert!(value.get("description").is_none());
1484        assert!(value.get("mimeType").is_none());
1485    }
1486
1487    #[test]
1488    fn resource_full_round_trip() {
1489        let json = json!({
1490            "uri": "file://config.json",
1491            "name": "Config",
1492            "description": "Application configuration",
1493            "mimeType": "application/json",
1494            "version": "3.0.0",
1495            "tags": ["config", "json"]
1496        });
1497        let resource: Resource = serde_json::from_value(json).expect("deserialize");
1498        assert_eq!(resource.uri, "file://config.json");
1499        assert_eq!(resource.mime_type, Some("application/json".to_string()));
1500        assert_eq!(resource.tags, vec!["config", "json"]);
1501    }
1502
1503    // ========================================================================
1504    // ResourceTemplate Tests
1505    // ========================================================================
1506
1507    #[test]
1508    fn resource_template_serialization() {
1509        let template = ResourceTemplate {
1510            uri_template: "file://{path}".to_string(),
1511            name: "File Reader".to_string(),
1512            description: Some("Read any file".to_string()),
1513            mime_type: Some("text/plain".to_string()),
1514            icon: None,
1515            version: None,
1516            tags: vec![],
1517        };
1518        let value = serde_json::to_value(&template).expect("serialize");
1519        assert_eq!(value["uriTemplate"], "file://{path}");
1520        assert_eq!(value["name"], "File Reader");
1521        assert_eq!(value["description"], "Read any file");
1522        assert_eq!(value["mimeType"], "text/plain");
1523    }
1524
1525    // ========================================================================
1526    // Prompt Definition Tests
1527    // ========================================================================
1528
1529    #[test]
1530    fn prompt_with_arguments() {
1531        let prompt = Prompt {
1532            name: "translate".to_string(),
1533            description: Some("Translate text".to_string()),
1534            arguments: vec![
1535                PromptArgument {
1536                    name: "text".to_string(),
1537                    description: Some("Text to translate".to_string()),
1538                    required: true,
1539                },
1540                PromptArgument {
1541                    name: "language".to_string(),
1542                    description: Some("Target language".to_string()),
1543                    required: true,
1544                },
1545                PromptArgument {
1546                    name: "style".to_string(),
1547                    description: None,
1548                    required: false,
1549                },
1550            ],
1551            icon: None,
1552            version: None,
1553            tags: vec![],
1554        };
1555        let value = serde_json::to_value(&prompt).expect("serialize");
1556        assert_eq!(value["name"], "translate");
1557        let args = value["arguments"].as_array().expect("arguments array");
1558        assert_eq!(args.len(), 3);
1559        assert_eq!(args[0]["name"], "text");
1560        assert_eq!(args[0]["required"], true);
1561        assert_eq!(args[2]["name"], "style");
1562        // required=false should be omitted
1563        assert!(args[2].get("required").is_none());
1564    }
1565
1566    #[test]
1567    fn prompt_empty_arguments_omitted() {
1568        let prompt = Prompt {
1569            name: "simple".to_string(),
1570            description: None,
1571            arguments: vec![],
1572            icon: None,
1573            version: None,
1574            tags: vec![],
1575        };
1576        let value = serde_json::to_value(&prompt).expect("serialize");
1577        assert!(value.get("arguments").is_none());
1578    }
1579
1580    // ========================================================================
1581    // TaskId Tests
1582    // ========================================================================
1583
1584    #[test]
1585    fn task_id_new_has_prefix() {
1586        let id = TaskId::new();
1587        assert!(id.as_str().starts_with("task-"));
1588    }
1589
1590    #[test]
1591    fn task_id_from_string() {
1592        let id = TaskId::from_string("task-abc123");
1593        assert_eq!(id.as_str(), "task-abc123");
1594    }
1595
1596    #[test]
1597    fn task_id_display() {
1598        let id = TaskId::from_string("task-xyz");
1599        assert_eq!(format!("{id}"), "task-xyz");
1600    }
1601
1602    #[test]
1603    fn task_id_from_impls() {
1604        let from_string: TaskId = "my-task".to_string().into();
1605        assert_eq!(from_string.as_str(), "my-task");
1606
1607        let from_str: TaskId = "another-task".into();
1608        assert_eq!(from_str.as_str(), "another-task");
1609    }
1610
1611    #[test]
1612    fn task_id_serialization() {
1613        let id = TaskId::from_string("task-1");
1614        let value = serde_json::to_value(&id).expect("serialize");
1615        assert_eq!(value, "task-1");
1616
1617        let deserialized: TaskId = serde_json::from_value(json!("task-2")).expect("deserialize");
1618        assert_eq!(deserialized.as_str(), "task-2");
1619    }
1620
1621    #[test]
1622    fn task_id_equality() {
1623        let a = TaskId::from_string("task-1");
1624        let b = TaskId::from_string("task-1");
1625        let c = TaskId::from_string("task-2");
1626        assert_eq!(a, b);
1627        assert_ne!(a, c);
1628    }
1629
1630    // ========================================================================
1631    // TaskStatus Tests
1632    // ========================================================================
1633
1634    #[test]
1635    fn task_status_is_terminal() {
1636        assert!(TaskStatus::Completed.is_terminal());
1637        assert!(TaskStatus::Failed.is_terminal());
1638        assert!(TaskStatus::Cancelled.is_terminal());
1639        assert!(!TaskStatus::Pending.is_terminal());
1640        assert!(!TaskStatus::Running.is_terminal());
1641    }
1642
1643    #[test]
1644    fn task_status_is_active() {
1645        assert!(TaskStatus::Pending.is_active());
1646        assert!(TaskStatus::Running.is_active());
1647        assert!(!TaskStatus::Completed.is_active());
1648        assert!(!TaskStatus::Failed.is_active());
1649        assert!(!TaskStatus::Cancelled.is_active());
1650    }
1651
1652    #[test]
1653    fn task_status_serialization() {
1654        assert_eq!(
1655            serde_json::to_value(TaskStatus::Pending).unwrap(),
1656            "pending"
1657        );
1658        assert_eq!(
1659            serde_json::to_value(TaskStatus::Running).unwrap(),
1660            "running"
1661        );
1662        assert_eq!(
1663            serde_json::to_value(TaskStatus::Completed).unwrap(),
1664            "completed"
1665        );
1666        assert_eq!(serde_json::to_value(TaskStatus::Failed).unwrap(), "failed");
1667        assert_eq!(
1668            serde_json::to_value(TaskStatus::Cancelled).unwrap(),
1669            "cancelled"
1670        );
1671    }
1672
1673    #[test]
1674    fn task_status_deserialization() {
1675        assert_eq!(
1676            serde_json::from_value::<TaskStatus>(json!("pending")).unwrap(),
1677            TaskStatus::Pending
1678        );
1679        assert_eq!(
1680            serde_json::from_value::<TaskStatus>(json!("running")).unwrap(),
1681            TaskStatus::Running
1682        );
1683        assert_eq!(
1684            serde_json::from_value::<TaskStatus>(json!("completed")).unwrap(),
1685            TaskStatus::Completed
1686        );
1687        assert_eq!(
1688            serde_json::from_value::<TaskStatus>(json!("failed")).unwrap(),
1689            TaskStatus::Failed
1690        );
1691        assert_eq!(
1692            serde_json::from_value::<TaskStatus>(json!("cancelled")).unwrap(),
1693            TaskStatus::Cancelled
1694        );
1695    }
1696
1697    // ========================================================================
1698    // TaskInfo Tests
1699    // ========================================================================
1700
1701    #[test]
1702    fn task_info_serialization() {
1703        let info = TaskInfo {
1704            id: TaskId::from_string("task-1"),
1705            task_type: "compute".to_string(),
1706            status: TaskStatus::Running,
1707            progress: Some(0.5),
1708            message: Some("Processing...".to_string()),
1709            created_at: "2026-01-28T00:00:00Z".to_string(),
1710            started_at: Some("2026-01-28T00:01:00Z".to_string()),
1711            completed_at: None,
1712            error: None,
1713        };
1714        let value = serde_json::to_value(&info).expect("serialize");
1715        assert_eq!(value["id"], "task-1");
1716        assert_eq!(value["taskType"], "compute");
1717        assert_eq!(value["status"], "running");
1718        assert_eq!(value["progress"], 0.5);
1719        assert_eq!(value["message"], "Processing...");
1720        assert_eq!(value["createdAt"], "2026-01-28T00:00:00Z");
1721        assert_eq!(value["startedAt"], "2026-01-28T00:01:00Z");
1722        assert!(value.get("completedAt").is_none());
1723        assert!(value.get("error").is_none());
1724    }
1725
1726    #[test]
1727    fn task_info_minimal() {
1728        let json = json!({
1729            "id": "task-2",
1730            "taskType": "demo",
1731            "status": "pending",
1732            "createdAt": "2026-01-28T00:00:00Z"
1733        });
1734        let info: TaskInfo = serde_json::from_value(json).expect("deserialize");
1735        assert_eq!(info.id.as_str(), "task-2");
1736        assert_eq!(info.status, TaskStatus::Pending);
1737        assert!(info.progress.is_none());
1738        assert!(info.message.is_none());
1739    }
1740
1741    // ========================================================================
1742    // TaskResult Tests
1743    // ========================================================================
1744
1745    #[test]
1746    fn task_result_success() {
1747        let result = TaskResult {
1748            id: TaskId::from_string("task-1"),
1749            success: true,
1750            data: Some(json!({"value": 42})),
1751            error: None,
1752        };
1753        let value = serde_json::to_value(&result).expect("serialize");
1754        assert_eq!(value["id"], "task-1");
1755        assert_eq!(value["success"], true);
1756        assert_eq!(value["data"]["value"], 42);
1757        assert!(value.get("error").is_none());
1758    }
1759
1760    #[test]
1761    fn task_result_failure() {
1762        let result = TaskResult {
1763            id: TaskId::from_string("task-2"),
1764            success: false,
1765            data: None,
1766            error: Some("computation failed".to_string()),
1767        };
1768        let value = serde_json::to_value(&result).expect("serialize");
1769        assert_eq!(value["success"], false);
1770        assert!(value.get("data").is_none());
1771        assert_eq!(value["error"], "computation failed");
1772    }
1773
1774    // ========================================================================
1775    // SamplingContent Tests
1776    // ========================================================================
1777
1778    #[test]
1779    fn sampling_content_text_serialization() {
1780        let content = SamplingContent::Text {
1781            text: "Hello".to_string(),
1782        };
1783        let value = serde_json::to_value(&content).expect("serialize");
1784        assert_eq!(value["type"], "text");
1785        assert_eq!(value["text"], "Hello");
1786    }
1787
1788    #[test]
1789    fn sampling_content_image_serialization() {
1790        let content = SamplingContent::Image {
1791            data: "base64data".to_string(),
1792            mime_type: "image/png".to_string(),
1793        };
1794        let value = serde_json::to_value(&content).expect("serialize");
1795        assert_eq!(value["type"], "image");
1796        assert_eq!(value["data"], "base64data");
1797        assert_eq!(value["mimeType"], "image/png");
1798    }
1799
1800    // ========================================================================
1801    // SamplingMessage Tests
1802    // ========================================================================
1803
1804    #[test]
1805    fn sampling_message_user_constructor() {
1806        let msg = SamplingMessage::user("Hello!");
1807        let value = serde_json::to_value(&msg).expect("serialize");
1808        assert_eq!(value["role"], "user");
1809        assert_eq!(value["content"]["type"], "text");
1810        assert_eq!(value["content"]["text"], "Hello!");
1811    }
1812
1813    #[test]
1814    fn sampling_message_assistant_constructor() {
1815        let msg = SamplingMessage::assistant("Hi there!");
1816        let value = serde_json::to_value(&msg).expect("serialize");
1817        assert_eq!(value["role"], "assistant");
1818        assert_eq!(value["content"]["text"], "Hi there!");
1819    }
1820
1821    // ========================================================================
1822    // ModelPreferences Tests
1823    // ========================================================================
1824
1825    #[test]
1826    fn model_preferences_default() {
1827        let prefs = ModelPreferences::default();
1828        let value = serde_json::to_value(&prefs).expect("serialize");
1829        // All optional fields should be omitted
1830        assert!(value.get("hints").is_none());
1831        assert!(value.get("costPriority").is_none());
1832        assert!(value.get("speedPriority").is_none());
1833        assert!(value.get("intelligencePriority").is_none());
1834    }
1835
1836    #[test]
1837    fn model_preferences_full() {
1838        let prefs = ModelPreferences {
1839            hints: vec![ModelHint {
1840                name: Some("claude-3".to_string()),
1841            }],
1842            cost_priority: Some(0.3),
1843            speed_priority: Some(0.5),
1844            intelligence_priority: Some(0.9),
1845        };
1846        let value = serde_json::to_value(&prefs).expect("serialize");
1847        assert_eq!(value["hints"][0]["name"], "claude-3");
1848        assert_eq!(value["costPriority"], 0.3);
1849        assert_eq!(value["speedPriority"], 0.5);
1850        assert_eq!(value["intelligencePriority"], 0.9);
1851    }
1852
1853    // ========================================================================
1854    // StopReason Tests
1855    // ========================================================================
1856
1857    #[test]
1858    fn stop_reason_serialization() {
1859        assert_eq!(
1860            serde_json::to_value(StopReason::EndTurn).unwrap(),
1861            "endTurn"
1862        );
1863        assert_eq!(
1864            serde_json::to_value(StopReason::StopSequence).unwrap(),
1865            "stopSequence"
1866        );
1867        assert_eq!(
1868            serde_json::to_value(StopReason::MaxTokens).unwrap(),
1869            "maxTokens"
1870        );
1871    }
1872
1873    #[test]
1874    fn stop_reason_deserialization() {
1875        assert_eq!(
1876            serde_json::from_value::<StopReason>(json!("endTurn")).unwrap(),
1877            StopReason::EndTurn
1878        );
1879        assert_eq!(
1880            serde_json::from_value::<StopReason>(json!("stopSequence")).unwrap(),
1881            StopReason::StopSequence
1882        );
1883        assert_eq!(
1884            serde_json::from_value::<StopReason>(json!("maxTokens")).unwrap(),
1885            StopReason::MaxTokens
1886        );
1887    }
1888
1889    #[test]
1890    fn stop_reason_default() {
1891        assert_eq!(StopReason::default(), StopReason::EndTurn);
1892    }
1893
1894    // ========================================================================
1895    // PROTOCOL_VERSION Test
1896    // ========================================================================
1897
1898    #[test]
1899    fn protocol_version_value() {
1900        assert_eq!(PROTOCOL_VERSION, "2024-11-05");
1901    }
1902
1903    // ========================================================================
1904    // ToolAnnotations Tests
1905    // ========================================================================
1906
1907    #[test]
1908    fn tool_annotations_default_is_empty() {
1909        let ann = ToolAnnotations::new();
1910        assert!(ann.is_empty());
1911    }
1912
1913    #[test]
1914    fn tool_annotations_builder_chain() {
1915        let ann = ToolAnnotations::new()
1916            .read_only(true)
1917            .idempotent(true)
1918            .destructive(false)
1919            .open_world_hint("none");
1920
1921        assert_eq!(ann.read_only, Some(true));
1922        assert_eq!(ann.idempotent, Some(true));
1923        assert_eq!(ann.destructive, Some(false));
1924        assert_eq!(ann.open_world_hint.as_deref(), Some("none"));
1925        assert!(!ann.is_empty());
1926    }
1927
1928    #[test]
1929    fn tool_annotations_single_field_not_empty() {
1930        assert!(!ToolAnnotations::new().destructive(true).is_empty());
1931        assert!(!ToolAnnotations::new().idempotent(false).is_empty());
1932        assert!(!ToolAnnotations::new().read_only(true).is_empty());
1933        assert!(!ToolAnnotations::new().open_world_hint("x").is_empty());
1934    }
1935
1936    #[test]
1937    fn tool_annotations_serialization_skips_none() {
1938        let ann = ToolAnnotations::new().read_only(true);
1939        let value = serde_json::to_value(&ann).expect("serialize");
1940        assert_eq!(value["readOnly"], true);
1941        assert!(value.get("destructive").is_none());
1942        assert!(value.get("idempotent").is_none());
1943        assert!(value.get("openWorldHint").is_none());
1944    }
1945
1946    #[test]
1947    fn tool_annotations_round_trip() {
1948        let ann = ToolAnnotations::new()
1949            .destructive(true)
1950            .idempotent(false)
1951            .open_world_hint("strict");
1952        let json_str = serde_json::to_string(&ann).expect("serialize");
1953        let deserialized: ToolAnnotations = serde_json::from_str(&json_str).expect("deserialize");
1954        assert_eq!(ann, deserialized);
1955    }
1956
1957    // ========================================================================
1958    // Icon Tests
1959    // ========================================================================
1960
1961    #[test]
1962    fn icon_is_data_uri_with_data_prefix() {
1963        let icon = Icon {
1964            src: Some("data:image/png;base64,iVBOR".to_string()),
1965            mime_type: None,
1966            sizes: None,
1967        };
1968        assert!(icon.is_data_uri());
1969    }
1970
1971    #[test]
1972    fn icon_is_data_uri_without_data_prefix() {
1973        let icon = Icon {
1974            src: Some("https://example.com/icon.png".to_string()),
1975            mime_type: None,
1976            sizes: None,
1977        };
1978        assert!(!icon.is_data_uri());
1979    }
1980
1981    #[test]
1982    fn icon_is_data_uri_no_src() {
1983        let icon = Icon {
1984            src: None,
1985            mime_type: None,
1986            sizes: None,
1987        };
1988        assert!(!icon.is_data_uri());
1989    }
1990
1991    #[test]
1992    fn icon_is_data_uri_empty_string() {
1993        let icon = Icon {
1994            src: Some(String::new()),
1995            mime_type: None,
1996            sizes: None,
1997        };
1998        assert!(!icon.is_data_uri());
1999    }
2000
2001    // ========================================================================
2002    // Content Binary Factory Tests
2003    // ========================================================================
2004
2005    #[test]
2006    fn content_image_bytes_encodes_base64() {
2007        let bytes: &[u8] = &[0x89, 0x50, 0x4E, 0x47]; // PNG header
2008        let content = Content::image_bytes(bytes, "image/png");
2009        match &content {
2010            Content::Image { data, mime_type } => {
2011                assert_eq!(mime_type, "image/png");
2012                // Verify it's valid base64 that decodes back
2013                let decoded = base64::engine::general_purpose::STANDARD
2014                    .decode(data)
2015                    .expect("valid base64");
2016                assert_eq!(decoded, bytes);
2017            }
2018            _ => panic!("expected Image content"),
2019        }
2020    }
2021
2022    #[test]
2023    fn content_image_bytes_empty() {
2024        let content = Content::image_bytes(&[] as &[u8], "image/png");
2025        match &content {
2026            Content::Image { data, .. } => {
2027                let decoded = base64::engine::general_purpose::STANDARD
2028                    .decode(data)
2029                    .expect("valid base64");
2030                assert!(decoded.is_empty());
2031            }
2032            _ => panic!("expected Image content"),
2033        }
2034    }
2035
2036    #[test]
2037    fn content_audio_bytes_encodes_base64() {
2038        let bytes: &[u8] = &[0x52, 0x49, 0x46, 0x46]; // RIFF header
2039        let content = Content::audio_bytes(bytes, "audio/wav");
2040        match &content {
2041            Content::Audio { data, mime_type } => {
2042                assert_eq!(mime_type, "audio/wav");
2043                let decoded = base64::engine::general_purpose::STANDARD
2044                    .decode(data)
2045                    .expect("valid base64");
2046                assert_eq!(decoded, bytes);
2047            }
2048            _ => panic!("expected Audio content"),
2049        }
2050    }
2051
2052    #[test]
2053    fn content_resource_blob_bytes_encodes_base64() {
2054        let bytes: &[u8] = &[0xDE, 0xAD, 0xBE, 0xEF];
2055        let content = Content::resource_blob_bytes(
2056            "blob://test",
2057            Some("application/octet-stream".into()),
2058            bytes,
2059        );
2060        match &content {
2061            Content::Resource {
2062                resource: ResourceContent { uri, blob, .. },
2063            } => {
2064                assert_eq!(uri, "blob://test");
2065                let blob_data = blob.as_ref().expect("should have blob");
2066                let decoded = base64::engine::general_purpose::STANDARD
2067                    .decode(blob_data)
2068                    .expect("valid base64");
2069                assert_eq!(decoded, bytes);
2070            }
2071            _ => panic!("expected Resource content"),
2072        }
2073    }
2074
2075    #[test]
2076    fn content_resource_blob_bytes_none_mime() {
2077        let content = Content::resource_blob_bytes("blob://test", None, &[1, 2, 3]);
2078        match &content {
2079            Content::Resource {
2080                resource: ResourceContent { mime_type, .. },
2081            } => {
2082                assert!(mime_type.is_none());
2083            }
2084            _ => panic!("expected Resource content"),
2085        }
2086    }
2087
2088    // =========================================================================
2089    // Additional coverage tests (bd-1cd5)
2090    // =========================================================================
2091
2092    #[test]
2093    fn root_new_constructor() {
2094        let root = Root::new("file:///home/user/project");
2095        assert_eq!(root.uri, "file:///home/user/project");
2096        assert!(root.name.is_none());
2097    }
2098
2099    #[test]
2100    fn root_new_from_string() {
2101        let uri = String::from("file:///tmp");
2102        let root = Root::new(uri);
2103        assert_eq!(root.uri, "file:///tmp");
2104    }
2105
2106    #[test]
2107    fn root_with_name_constructor() {
2108        let root = Root::with_name("file:///workspace", "My Project");
2109        assert_eq!(root.uri, "file:///workspace");
2110        assert_eq!(root.name.as_deref(), Some("My Project"));
2111    }
2112
2113    #[test]
2114    fn root_with_name_from_strings() {
2115        let uri = String::from("file:///src");
2116        let name = String::from("Source");
2117        let root = Root::with_name(uri, name);
2118        assert_eq!(root.uri, "file:///src");
2119        assert_eq!(root.name.unwrap(), "Source");
2120    }
2121
2122    #[test]
2123    fn content_text_constructor() {
2124        let content = Content::text("hello world");
2125        match &content {
2126            Content::Text { text } => assert_eq!(text, "hello world"),
2127            _ => panic!("expected Text content"),
2128        }
2129    }
2130
2131    #[test]
2132    fn content_text_from_string() {
2133        let s = String::from("owned string");
2134        let content = Content::text(s);
2135        match &content {
2136            Content::Text { text } => assert_eq!(text, "owned string"),
2137            _ => panic!("expected Text content"),
2138        }
2139    }
2140
2141    #[test]
2142    fn content_image_base64_constructor() {
2143        let content = Content::image_base64("aGVsbG8=", "image/png");
2144        match &content {
2145            Content::Image { data, mime_type } => {
2146                assert_eq!(data, "aGVsbG8=");
2147                assert_eq!(mime_type, "image/png");
2148            }
2149            _ => panic!("expected Image content"),
2150        }
2151    }
2152
2153    #[test]
2154    fn content_audio_base64_constructor() {
2155        let content = Content::audio_base64("AAAA", "audio/mp3");
2156        match &content {
2157            Content::Audio { data, mime_type } => {
2158                assert_eq!(data, "AAAA");
2159                assert_eq!(mime_type, "audio/mp3");
2160            }
2161            _ => panic!("expected Audio content"),
2162        }
2163    }
2164
2165    #[test]
2166    fn content_resource_text_constructor() {
2167        let content = Content::resource_text(
2168            "file:///readme.md",
2169            Some("text/markdown".to_string()),
2170            "# Hello",
2171        );
2172        match &content {
2173            Content::Resource { resource } => {
2174                assert_eq!(resource.uri, "file:///readme.md");
2175                assert_eq!(resource.mime_type.as_deref(), Some("text/markdown"));
2176                assert_eq!(resource.text.as_deref(), Some("# Hello"));
2177                assert!(resource.blob.is_none());
2178            }
2179            _ => panic!("expected Resource content"),
2180        }
2181    }
2182
2183    #[test]
2184    fn content_resource_text_no_mime() {
2185        let content = Content::resource_text("file:///data.txt", None, "data");
2186        match &content {
2187            Content::Resource { resource } => {
2188                assert!(resource.mime_type.is_none());
2189                assert_eq!(resource.text.as_deref(), Some("data"));
2190            }
2191            _ => panic!("expected Resource content"),
2192        }
2193    }
2194
2195    #[test]
2196    fn content_resource_blob_base64_constructor() {
2197        let content = Content::resource_blob_base64(
2198            "file:///image.png",
2199            Some("image/png".to_string()),
2200            "iVBOR",
2201        );
2202        match &content {
2203            Content::Resource { resource } => {
2204                assert_eq!(resource.uri, "file:///image.png");
2205                assert_eq!(resource.mime_type.as_deref(), Some("image/png"));
2206                assert!(resource.text.is_none());
2207                assert_eq!(resource.blob.as_deref(), Some("iVBOR"));
2208            }
2209            _ => panic!("expected Resource content"),
2210        }
2211    }
2212
2213    #[test]
2214    fn content_resource_blob_base64_no_mime() {
2215        let content = Content::resource_blob_base64("file:///bin", None, "AQID");
2216        match &content {
2217            Content::Resource { resource } => {
2218                assert!(resource.mime_type.is_none());
2219                assert_eq!(resource.blob.as_deref(), Some("AQID"));
2220            }
2221            _ => panic!("expected Resource content"),
2222        }
2223    }
2224}