pulseengine_mcp_protocol/
model.rs

1//! MCP model types for protocol messages and data structures
2
3use crate::Error;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::Arc;
7
8/// MIME type constants for common resource types
9pub mod mime_types {
10    /// HTML content with MCP JavaScript SDK for interactive UIs (MCP Apps Extension)
11    pub const HTML_MCP: &str = "text/html+mcp";
12
13    /// Plain HTML content
14    pub const HTML: &str = "text/html";
15
16    /// JSON data
17    pub const JSON: &str = "application/json";
18
19    /// Plain text
20    pub const TEXT: &str = "text/plain";
21
22    /// Binary blob
23    pub const OCTET_STREAM: &str = "application/octet-stream";
24}
25
26/// URI scheme constants for resource URIs
27pub mod uri_schemes {
28    /// UI resources for interactive interfaces (MCP Apps Extension)
29    pub const UI: &str = "ui://";
30
31    /// File system resources
32    pub const FILE: &str = "file://";
33
34    /// HTTP resources
35    pub const HTTP: &str = "http://";
36
37    /// HTTPS resources
38    pub const HTTPS: &str = "https://";
39}
40
41/// Metadata for MCP protocol messages (MCP 2025-06-18)
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Meta {
44    /// Progress token for tracking long-running operations
45    #[serde(rename = "progressToken", skip_serializing_if = "Option::is_none")]
46    pub progress_token: Option<String>,
47}
48
49/// A flexible identifier type for JSON-RPC request IDs
50#[derive(Debug, Clone, Eq, PartialEq, Hash)]
51pub enum NumberOrString {
52    Number(i64),
53    String(Arc<str>),
54}
55
56impl NumberOrString {
57    pub fn into_json_value(self) -> serde_json::Value {
58        match self {
59            NumberOrString::Number(n) => serde_json::Value::Number(serde_json::Number::from(n)),
60            NumberOrString::String(s) => serde_json::Value::String(s.to_string()),
61        }
62    }
63
64    pub fn from_json_value(value: serde_json::Value) -> Option<Self> {
65        match value {
66            serde_json::Value::Number(n) => n.as_i64().map(NumberOrString::Number),
67            serde_json::Value::String(s) => Some(NumberOrString::String(Arc::from(s.as_str()))),
68            _ => None,
69        }
70    }
71}
72
73impl std::fmt::Display for NumberOrString {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        match self {
76            NumberOrString::Number(n) => write!(f, "{n}"),
77            NumberOrString::String(s) => write!(f, "{s}"),
78        }
79    }
80}
81
82impl Serialize for NumberOrString {
83    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84    where
85        S: serde::Serializer,
86    {
87        match self {
88            NumberOrString::Number(n) => serializer.serialize_i64(*n),
89            NumberOrString::String(s) => serializer.serialize_str(s),
90        }
91    }
92}
93
94impl<'de> Deserialize<'de> for NumberOrString {
95    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96    where
97        D: serde::Deserializer<'de>,
98    {
99        struct NumberOrStringVisitor;
100
101        impl<'de> serde::de::Visitor<'de> for NumberOrStringVisitor {
102            type Value = NumberOrString;
103
104            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
105                formatter.write_str("a number or string")
106            }
107
108            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
109            where
110                E: serde::de::Error,
111            {
112                Ok(NumberOrString::Number(value))
113            }
114
115            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
116            where
117                E: serde::de::Error,
118            {
119                Ok(NumberOrString::Number(value as i64))
120            }
121
122            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
123            where
124                E: serde::de::Error,
125            {
126                Ok(NumberOrString::String(Arc::from(value)))
127            }
128
129            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
130            where
131                E: serde::de::Error,
132            {
133                Ok(NumberOrString::String(Arc::from(value.as_str())))
134            }
135        }
136
137        deserializer.deserialize_any(NumberOrStringVisitor)
138    }
139}
140
141/// JSON-RPC 2.0 Request
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct Request {
144    /// JSON-RPC version (always "2.0")
145    pub jsonrpc: String,
146    /// Request method name
147    pub method: String,
148    /// Request parameters
149    #[serde(default = "serde_json::Value::default")]
150    pub params: serde_json::Value,
151    /// Request ID (None for notifications)
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub id: Option<NumberOrString>,
154}
155
156/// JSON-RPC 2.0 Response
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct Response {
159    /// JSON-RPC version (always "2.0")
160    pub jsonrpc: String,
161    /// Response result (if successful)
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub result: Option<serde_json::Value>,
164    /// Response error (if failed)
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub error: Option<Error>,
167    /// Request ID (can be null for error responses)
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub id: Option<NumberOrString>,
170}
171
172/// MCP Protocol version in date format (YYYY-MM-DD)
173#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Serialize, Deserialize)]
174pub struct ProtocolVersion(std::borrow::Cow<'static, str>);
175
176impl Default for ProtocolVersion {
177    fn default() -> Self {
178        Self::LATEST
179    }
180}
181
182impl std::fmt::Display for ProtocolVersion {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        self.0.fmt(f)
185    }
186}
187
188impl ProtocolVersion {
189    pub const V_2025_06_18: Self = Self(std::borrow::Cow::Borrowed("2025-06-18"));
190    pub const V_2025_03_26: Self = Self(std::borrow::Cow::Borrowed("2025-03-26"));
191    pub const V_2024_11_05: Self = Self(std::borrow::Cow::Borrowed("2024-11-05"));
192    pub const LATEST: Self = Self::V_2025_06_18;
193
194    pub fn new(version: impl Into<std::borrow::Cow<'static, str>>) -> Self {
195        Self(version.into())
196    }
197}
198
199/// Server implementation information
200#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct Implementation {
202    pub name: String,
203    pub version: String,
204}
205
206/// Server capabilities configuration
207#[derive(Debug, Clone, Serialize, Deserialize, Default)]
208pub struct ServerCapabilities {
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub tools: Option<ToolsCapability>,
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub resources: Option<ResourcesCapability>,
213    #[serde(skip_serializing_if = "Option::is_none")]
214    pub prompts: Option<PromptsCapability>,
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub logging: Option<LoggingCapability>,
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub sampling: Option<SamplingCapability>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub elicitation: Option<ElicitationCapability>,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, Default)]
224pub struct ToolsCapability {
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub list_changed: Option<bool>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize, Default)]
230pub struct ResourcesCapability {
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub subscribe: Option<bool>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub list_changed: Option<bool>,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, Default)]
238pub struct PromptsCapability {
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub list_changed: Option<bool>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, Default)]
244pub struct LoggingCapability {
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub level: Option<String>,
247}
248
249/// Log level based on RFC 5424 syslog severity levels
250#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
251#[serde(rename_all = "lowercase")]
252pub enum LogLevel {
253    Emergency,
254    Alert,
255    Critical,
256    Error,
257    Warning,
258    Notice,
259    Info,
260    Debug,
261}
262
263impl LogLevel {
264    pub fn as_str(&self) -> &'static str {
265        match self {
266            LogLevel::Emergency => "emergency",
267            LogLevel::Alert => "alert",
268            LogLevel::Critical => "critical",
269            LogLevel::Error => "error",
270            LogLevel::Warning => "warning",
271            LogLevel::Notice => "notice",
272            LogLevel::Info => "info",
273            LogLevel::Debug => "debug",
274        }
275    }
276}
277
278impl std::str::FromStr for LogLevel {
279    type Err = String;
280
281    fn from_str(s: &str) -> Result<Self, Self::Err> {
282        match s.to_lowercase().as_str() {
283            "emergency" => Ok(LogLevel::Emergency),
284            "alert" => Ok(LogLevel::Alert),
285            "critical" => Ok(LogLevel::Critical),
286            "error" => Ok(LogLevel::Error),
287            "warning" => Ok(LogLevel::Warning),
288            "notice" => Ok(LogLevel::Notice),
289            "info" => Ok(LogLevel::Info),
290            "debug" => Ok(LogLevel::Debug),
291            _ => Err(format!("Invalid log level: {s}")),
292        }
293    }
294}
295
296impl std::fmt::Display for LogLevel {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        write!(f, "{}", self.as_str())
299    }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, Default)]
303pub struct SamplingCapability {}
304
305#[derive(Debug, Clone, Serialize, Deserialize, Default)]
306pub struct ElicitationCapability {}
307
308impl ServerCapabilities {
309    pub fn builder() -> ServerCapabilitiesBuilder {
310        ServerCapabilitiesBuilder::default()
311    }
312}
313
314#[derive(Default)]
315pub struct ServerCapabilitiesBuilder {
316    capabilities: ServerCapabilities,
317}
318
319impl ServerCapabilitiesBuilder {
320    #[must_use]
321    pub fn enable_tools(mut self) -> Self {
322        self.capabilities.tools = Some(ToolsCapability {
323            list_changed: Some(true),
324        });
325        self
326    }
327
328    #[must_use]
329    pub fn enable_resources(mut self) -> Self {
330        self.capabilities.resources = Some(ResourcesCapability {
331            subscribe: Some(true),
332            list_changed: Some(true),
333        });
334        self
335    }
336
337    #[must_use]
338    pub fn enable_prompts(mut self) -> Self {
339        self.capabilities.prompts = Some(PromptsCapability {
340            list_changed: Some(true),
341        });
342        self
343    }
344
345    #[must_use]
346    pub fn enable_logging(mut self) -> Self {
347        self.capabilities.logging = Some(LoggingCapability {
348            level: Some("info".to_string()),
349        });
350        self
351    }
352
353    #[must_use]
354    pub fn enable_sampling(mut self) -> Self {
355        self.capabilities.sampling = Some(SamplingCapability {});
356        self
357    }
358
359    #[must_use]
360    pub fn enable_elicitation(mut self) -> Self {
361        self.capabilities.elicitation = Some(ElicitationCapability {});
362        self
363    }
364
365    pub fn build(self) -> ServerCapabilities {
366        self.capabilities
367    }
368}
369
370/// Server information response
371#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct ServerInfo {
373    pub protocol_version: ProtocolVersion,
374    pub capabilities: ServerCapabilities,
375    pub server_info: Implementation,
376    pub instructions: Option<String>,
377}
378
379/// Tool definition
380#[derive(Debug, Clone, Serialize, Deserialize)]
381#[serde(rename_all = "camelCase")]
382pub struct Tool {
383    pub name: String,
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub title: Option<String>,
386    pub description: String,
387    pub input_schema: serde_json::Value,
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub output_schema: Option<serde_json::Value>,
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub annotations: Option<ToolAnnotations>,
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub icons: Option<Vec<Icon>>,
394    /// Tool metadata for extensions like MCP Apps (SEP-1865)
395    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
396    pub _meta: Option<ToolMeta>,
397}
398
399/// Tool annotations for behavioral hints
400#[derive(Debug, Clone, Serialize, Deserialize, Default)]
401pub struct ToolAnnotations {
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub read_only_hint: Option<bool>,
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub destructive_hint: Option<bool>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub idempotent_hint: Option<bool>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub open_world_hint: Option<bool>,
410}
411
412/// Tool metadata for protocol extensions
413///
414/// This supports the MCP Apps Extension (SEP-1865) and future extensions
415/// that need to attach metadata to tools.
416#[derive(Debug, Clone, Serialize, Deserialize, Default)]
417pub struct ToolMeta {
418    /// Reference to a UI resource (MCP Apps Extension)
419    ///
420    /// Links this tool to an interactive HTML interface that can be displayed
421    /// when the tool is called. The URI should use the `ui://` scheme and
422    /// reference a resource returned by `list_resources`.
423    ///
424    /// Example: `"ui://charts/bar-chart"`
425    #[serde(rename = "ui/resourceUri", skip_serializing_if = "Option::is_none")]
426    pub ui_resource_uri: Option<String>,
427}
428
429impl ToolMeta {
430    /// Create tool metadata with a UI resource reference
431    pub fn with_ui_resource(uri: impl Into<String>) -> Self {
432        Self {
433            ui_resource_uri: Some(uri.into()),
434        }
435    }
436}
437
438/// Icon definition for tools and other resources
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct Icon {
441    pub uri: String,
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub mime_type: Option<String>,
444}
445
446/// List tools result
447#[derive(Debug, Clone, Serialize, Deserialize)]
448#[serde(rename_all = "camelCase")]
449pub struct ListToolsResult {
450    pub tools: Vec<Tool>,
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub next_cursor: Option<String>,
453}
454
455/// Pagination parameters
456#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct PaginatedRequestParam {
458    pub cursor: Option<String>,
459}
460
461/// Tool call parameters
462#[derive(Debug, Clone, Serialize, Deserialize)]
463pub struct CallToolRequestParam {
464    pub name: String,
465    pub arguments: Option<serde_json::Value>,
466}
467
468/// Content types for tool responses
469#[derive(Debug, Clone, Serialize, Deserialize)]
470#[serde(tag = "type")]
471pub enum Content {
472    #[serde(rename = "text")]
473    Text {
474        text: String,
475        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
476        _meta: Option<Meta>,
477    },
478    #[serde(rename = "image")]
479    Image {
480        data: String,
481        mime_type: String,
482        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
483        _meta: Option<Meta>,
484    },
485    #[serde(rename = "resource")]
486    Resource {
487        #[serde(with = "serde_json_string_or_object")]
488        resource: String,
489        text: Option<String>,
490        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
491        _meta: Option<Meta>,
492    },
493}
494
495impl Content {
496    pub fn text(text: impl Into<String>) -> Self {
497        Self::Text {
498            text: text.into(),
499            _meta: None,
500        }
501    }
502
503    pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
504        Self::Image {
505            data: data.into(),
506            mime_type: mime_type.into(),
507            _meta: None,
508        }
509    }
510
511    pub fn resource(resource: impl Into<String>, text: Option<String>) -> Self {
512        Self::Resource {
513            resource: resource.into(),
514            text,
515            _meta: None,
516        }
517    }
518
519    /// Create a UI HTML resource content (for MCP Apps Extension / MCP-UI)
520    ///
521    /// This helper simplifies creating HTML UI resources by automatically formatting
522    /// the resource JSON according to the MCP-UI specification.
523    ///
524    /// # Example
525    ///
526    /// ```rust
527    /// use pulseengine_mcp_protocol::Content;
528    ///
529    /// let html = r#"<html><body><h1>Hello!</h1></body></html>"#;
530    /// let content = Content::ui_html("ui://greetings/interactive", html);
531    /// ```
532    ///
533    /// This is equivalent to but much more concise than:
534    /// ```rust,ignore
535    /// let resource_json = serde_json::json!({
536    ///     "uri": "ui://greetings/interactive",
537    ///     "mimeType": "text/html",
538    ///     "text": html
539    /// });
540    /// Content::Resource {
541    ///     resource: resource_json.to_string(),
542    ///     text: None,
543    ///     _meta: None,
544    /// }
545    /// ```
546    pub fn ui_html(uri: impl Into<String>, html: impl Into<String>) -> Self {
547        let resource_json = serde_json::json!({
548            "uri": uri.into(),
549            "mimeType": "text/html",
550            "text": html.into()
551        });
552        Self::Resource {
553            resource: resource_json.to_string(),
554            text: None,
555            _meta: None,
556        }
557    }
558
559    /// Create a UI resource content with custom MIME type (for MCP Apps Extension / MCP-UI)
560    ///
561    /// This helper allows you to create UI resources with any MIME type and content.
562    ///
563    /// # Example
564    ///
565    /// ```rust
566    /// use pulseengine_mcp_protocol::Content;
567    ///
568    /// let json_data = r#"{"message": "Hello, World!"}"#;
569    /// let content = Content::ui_resource(
570    ///     "ui://data/greeting",
571    ///     "application/json",
572    ///     json_data
573    /// );
574    /// ```
575    pub fn ui_resource(
576        uri: impl Into<String>,
577        mime_type: impl Into<String>,
578        content: impl Into<String>,
579    ) -> Self {
580        let resource_json = serde_json::json!({
581            "uri": uri.into(),
582            "mimeType": mime_type.into(),
583            "text": content.into()
584        });
585        Self::Resource {
586            resource: resource_json.to_string(),
587            text: None,
588            _meta: None,
589        }
590    }
591
592    /// Get text content if this is a text content type
593    pub fn as_text(&self) -> Option<&Self> {
594        match self {
595            Self::Text { .. } => Some(self),
596            _ => None,
597        }
598    }
599}
600
601/// Text content struct for compatibility
602pub struct TextContent {
603    pub text: String,
604}
605
606impl Content {
607    /// Get text content as `TextContent` struct for compatibility
608    pub fn as_text_content(&self) -> Option<TextContent> {
609        match self {
610            Self::Text { text, .. } => Some(TextContent { text: text.clone() }),
611            _ => None,
612        }
613    }
614}
615
616/// Tool call result
617#[derive(Debug, Clone, Serialize, Deserialize)]
618#[serde(rename_all = "camelCase")]
619pub struct CallToolResult {
620    pub content: Vec<Content>,
621    pub is_error: Option<bool>,
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub structured_content: Option<serde_json::Value>,
624    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
625    pub _meta: Option<Meta>,
626}
627
628impl CallToolResult {
629    pub fn success(content: Vec<Content>) -> Self {
630        Self {
631            content,
632            is_error: Some(false),
633            structured_content: None,
634            _meta: None,
635        }
636    }
637
638    pub fn error(content: Vec<Content>) -> Self {
639        Self {
640            content,
641            is_error: Some(true),
642            structured_content: None,
643            _meta: None,
644        }
645    }
646
647    pub fn text(text: impl Into<String>) -> Self {
648        Self::success(vec![Content::text(text)])
649    }
650
651    pub fn error_text(text: impl Into<String>) -> Self {
652        Self::error(vec![Content::text(text)])
653    }
654
655    /// Create a success result with structured content
656    pub fn structured(content: Vec<Content>, structured_content: serde_json::Value) -> Self {
657        Self {
658            content,
659            is_error: Some(false),
660            structured_content: Some(structured_content),
661            _meta: None,
662        }
663    }
664
665    /// Create an error result with structured content
666    pub fn structured_error(content: Vec<Content>, structured_content: serde_json::Value) -> Self {
667        Self {
668            content,
669            is_error: Some(true),
670            structured_content: Some(structured_content),
671            _meta: None,
672        }
673    }
674
675    /// Create a result with both text and structured content
676    pub fn text_with_structured(
677        text: impl Into<String>,
678        structured_content: serde_json::Value,
679    ) -> Self {
680        Self::structured(vec![Content::text(text)], structured_content)
681    }
682
683    /// Validate structured content against a schema
684    ///
685    /// # Errors
686    ///
687    /// Returns an error if the structured content doesn't match the provided schema
688    pub fn validate_structured_content(
689        &self,
690        output_schema: &serde_json::Value,
691    ) -> crate::Result<()> {
692        use crate::validation::Validator;
693
694        if let Some(structured_content) = &self.structured_content {
695            Validator::validate_structured_content(structured_content, output_schema)?;
696        }
697        Ok(())
698    }
699}
700
701/// Resource definition
702#[derive(Debug, Clone, Serialize, Deserialize)]
703pub struct Resource {
704    pub uri: String,
705    pub name: String,
706    #[serde(skip_serializing_if = "Option::is_none")]
707    pub title: Option<String>,
708    pub description: Option<String>,
709    pub mime_type: Option<String>,
710    pub annotations: Option<Annotations>,
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub icons: Option<Vec<Icon>>,
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub raw: Option<RawResource>,
715    /// UI-specific metadata (MCP Apps Extension)
716    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
717    pub _meta: Option<ResourceMeta>,
718}
719
720/// Resource metadata for extensions
721#[derive(Debug, Clone, Serialize, Deserialize, Default)]
722pub struct ResourceMeta {
723    /// UI configuration (MCP Apps Extension)
724    #[serde(rename = "ui", skip_serializing_if = "Option::is_none")]
725    pub ui: Option<UiResourceMeta>,
726}
727
728/// UI resource metadata (MCP Apps Extension - SEP-1865)
729#[derive(Debug, Clone, Serialize, Deserialize, Default)]
730pub struct UiResourceMeta {
731    /// Content Security Policy configuration
732    #[serde(skip_serializing_if = "Option::is_none")]
733    pub csp: Option<CspConfig>,
734
735    /// Optional dedicated sandbox origin/domain
736    #[serde(skip_serializing_if = "Option::is_none")]
737    pub domain: Option<String>,
738
739    /// Whether the UI prefers a visual boundary/border
740    #[serde(rename = "prefersBorder", skip_serializing_if = "Option::is_none")]
741    pub prefers_border: Option<bool>,
742}
743
744/// Content Security Policy configuration for UI resources
745#[derive(Debug, Clone, Serialize, Deserialize, Default)]
746pub struct CspConfig {
747    /// Allowed origins for network requests (fetch, XHR, WebSocket)
748    #[serde(rename = "connectDomains", skip_serializing_if = "Option::is_none")]
749    pub connect_domains: Option<Vec<String>>,
750
751    /// Allowed origins for static resources (images, scripts, fonts)
752    #[serde(rename = "resourceDomains", skip_serializing_if = "Option::is_none")]
753    pub resource_domains: Option<Vec<String>>,
754}
755
756/// Resource annotations
757#[derive(Debug, Clone, Serialize, Deserialize, Default)]
758pub struct Annotations {
759    pub audience: Option<Vec<String>>,
760    pub priority: Option<f32>,
761}
762
763impl Resource {
764    /// Create a UI resource for interactive interfaces (MCP Apps Extension)
765    ///
766    /// This creates a resource with the `text/html+mcp` MIME type and `ui://` URI scheme,
767    /// suitable for embedding interactive HTML interfaces.
768    ///
769    /// # Example
770    ///
771    /// ```
772    /// use pulseengine_mcp_protocol::Resource;
773    ///
774    /// let resource = Resource::ui_resource(
775    ///     "ui://charts/bar-chart",
776    ///     "Bar Chart Viewer",
777    ///     "Interactive bar chart visualization",
778    /// );
779    /// ```
780    pub fn ui_resource(
781        uri: impl Into<String>,
782        name: impl Into<String>,
783        description: impl Into<String>,
784    ) -> Self {
785        Self {
786            uri: uri.into(),
787            name: name.into(),
788            title: None,
789            description: Some(description.into()),
790            mime_type: Some(mime_types::HTML_MCP.to_string()),
791            annotations: None,
792            icons: None,
793            raw: None,
794            _meta: None,
795        }
796    }
797
798    /// Create a UI resource with CSP configuration
799    pub fn ui_resource_with_csp(
800        uri: impl Into<String>,
801        name: impl Into<String>,
802        description: impl Into<String>,
803        csp: CspConfig,
804    ) -> Self {
805        Self {
806            uri: uri.into(),
807            name: name.into(),
808            title: None,
809            description: Some(description.into()),
810            mime_type: Some(mime_types::HTML_MCP.to_string()),
811            annotations: None,
812            icons: None,
813            raw: None,
814            _meta: Some(ResourceMeta {
815                ui: Some(UiResourceMeta {
816                    csp: Some(csp),
817                    domain: None,
818                    prefers_border: None,
819                }),
820            }),
821        }
822    }
823
824    /// Check if this resource is a UI resource (has `ui://` scheme)
825    pub fn is_ui_resource(&self) -> bool {
826        self.uri.starts_with(uri_schemes::UI)
827    }
828
829    /// Get the URI scheme of this resource (e.g., "ui://", "file://", etc.)
830    pub fn uri_scheme(&self) -> Option<&str> {
831        self.uri.split_once("://").map(|(scheme, _)| scheme)
832    }
833}
834
835impl ResourceContents {
836    /// Create resource contents for HTML UI (MCP Apps Extension)
837    pub fn html_ui(uri: impl Into<String>, html: impl Into<String>) -> Self {
838        Self {
839            uri: uri.into(),
840            mime_type: Some(mime_types::HTML_MCP.to_string()),
841            text: Some(html.into()),
842            blob: None,
843            _meta: None,
844        }
845    }
846
847    /// Create resource contents with JSON data
848    pub fn json(uri: impl Into<String>, json: impl Into<String>) -> Self {
849        Self {
850            uri: uri.into(),
851            mime_type: Some(mime_types::JSON.to_string()),
852            text: Some(json.into()),
853            blob: None,
854            _meta: None,
855        }
856    }
857
858    /// Create resource contents with plain text
859    pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
860        Self {
861            uri: uri.into(),
862            mime_type: Some(mime_types::TEXT.to_string()),
863            text: Some(text.into()),
864            blob: None,
865            _meta: None,
866        }
867    }
868}
869
870/// List resources result
871#[derive(Debug, Clone, Serialize, Deserialize)]
872pub struct ListResourcesResult {
873    pub resources: Vec<Resource>,
874    #[serde(skip_serializing_if = "Option::is_none")]
875    pub next_cursor: Option<String>,
876}
877
878/// Read resource parameters
879#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct ReadResourceRequestParam {
881    pub uri: String,
882}
883
884/// Resource contents wrapper
885#[derive(Debug, Clone, Serialize, Deserialize)]
886pub struct ResourceContents {
887    pub uri: String,
888    pub mime_type: Option<String>,
889    pub text: Option<String>,
890    pub blob: Option<String>,
891    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
892    pub _meta: Option<Meta>,
893}
894
895/// Read resource result
896#[derive(Debug, Clone, Serialize, Deserialize)]
897pub struct ReadResourceResult {
898    pub contents: Vec<ResourceContents>,
899}
900
901/// Raw resource (for internal use)
902#[derive(Debug, Clone, Serialize, Deserialize)]
903pub struct RawResource {
904    pub uri: String,
905    pub data: Vec<u8>,
906    pub mime_type: Option<String>,
907    pub name: Option<String>,
908    pub description: Option<String>,
909    pub size: Option<usize>,
910}
911
912impl PromptMessage {
913    /// Create a new text message
914    pub fn new_text(role: PromptMessageRole, text: impl Into<String>) -> Self {
915        Self {
916            role,
917            content: PromptMessageContent::Text { text: text.into() },
918        }
919    }
920
921    /// Create a new image message
922    pub fn new_image(
923        role: PromptMessageRole,
924        data: impl Into<String>,
925        mime_type: impl Into<String>,
926    ) -> Self {
927        Self {
928            role,
929            content: PromptMessageContent::Image {
930                data: data.into(),
931                mime_type: mime_type.into(),
932            },
933        }
934    }
935}
936
937impl CompleteResult {
938    /// Create a simple completion result
939    pub fn simple(completion: impl Into<String>) -> Self {
940        Self {
941            completion: vec![CompletionInfo {
942                completion: completion.into(),
943                has_more: Some(false),
944            }],
945        }
946    }
947}
948
949/// Prompt definition
950#[derive(Debug, Clone, Serialize, Deserialize)]
951pub struct Prompt {
952    pub name: String,
953    #[serde(skip_serializing_if = "Option::is_none")]
954    pub title: Option<String>,
955    pub description: Option<String>,
956    pub arguments: Option<Vec<PromptArgument>>,
957    #[serde(skip_serializing_if = "Option::is_none")]
958    pub icons: Option<Vec<Icon>>,
959}
960
961/// Prompt argument definition
962#[derive(Debug, Clone, Serialize, Deserialize)]
963pub struct PromptArgument {
964    pub name: String,
965    pub description: Option<String>,
966    pub required: Option<bool>,
967}
968
969/// List prompts result
970#[derive(Debug, Clone, Serialize, Deserialize)]
971pub struct ListPromptsResult {
972    pub prompts: Vec<Prompt>,
973    #[serde(skip_serializing_if = "Option::is_none")]
974    pub next_cursor: Option<String>,
975}
976
977/// Get prompt parameters
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct GetPromptRequestParam {
980    pub name: String,
981    pub arguments: Option<HashMap<String, String>>,
982}
983
984/// Prompt message role
985#[derive(Debug, Clone, Serialize, Deserialize)]
986#[serde(rename_all = "lowercase")]
987pub enum PromptMessageRole {
988    User,
989    Assistant,
990    System,
991}
992
993/// Prompt message content
994#[derive(Debug, Clone, Serialize, Deserialize)]
995#[serde(tag = "type")]
996pub enum PromptMessageContent {
997    #[serde(rename = "text")]
998    Text { text: String },
999    #[serde(rename = "image")]
1000    Image { data: String, mime_type: String },
1001}
1002
1003/// Prompt message
1004#[derive(Debug, Clone, Serialize, Deserialize)]
1005pub struct PromptMessage {
1006    pub role: PromptMessageRole,
1007    pub content: PromptMessageContent,
1008}
1009
1010/// Get prompt result
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1012pub struct GetPromptResult {
1013    pub description: Option<String>,
1014    pub messages: Vec<PromptMessage>,
1015}
1016
1017/// Initialize request parameters
1018#[derive(Debug, Clone, Serialize, Deserialize)]
1019pub struct InitializeRequestParam {
1020    #[serde(rename = "protocolVersion")]
1021    pub protocol_version: String,
1022    pub capabilities: serde_json::Value,
1023    #[serde(rename = "clientInfo")]
1024    pub client_info: Implementation,
1025}
1026
1027/// Initialize result
1028#[derive(Debug, Clone, Serialize, Deserialize)]
1029pub struct InitializeResult {
1030    #[serde(rename = "protocolVersion")]
1031    pub protocol_version: String,
1032    pub capabilities: ServerCapabilities,
1033    #[serde(rename = "serverInfo")]
1034    pub server_info: Implementation,
1035    #[serde(skip_serializing_if = "Option::is_none")]
1036    pub instructions: Option<String>,
1037}
1038
1039/// Completion context for context-aware completion (MCP 2025-06-18)
1040#[derive(Debug, Clone, Serialize, Deserialize)]
1041#[serde(rename_all = "camelCase")]
1042pub struct CompletionContext {
1043    /// Names of arguments that have already been provided
1044    pub argument_names: Vec<String>,
1045    /// Values of arguments that have already been provided
1046    pub values: HashMap<String, serde_json::Value>,
1047}
1048
1049impl CompletionContext {
1050    /// Create a new completion context
1051    pub fn new(argument_names: Vec<String>, values: HashMap<String, serde_json::Value>) -> Self {
1052        Self {
1053            argument_names,
1054            values,
1055        }
1056    }
1057
1058    /// Get an iterator over argument names
1059    pub fn argument_names_iter(&self) -> impl Iterator<Item = &String> {
1060        self.argument_names.iter()
1061    }
1062}
1063
1064/// Completion request parameters
1065#[derive(Debug, Clone, Serialize, Deserialize)]
1066pub struct CompleteRequestParam {
1067    pub ref_: String,
1068    pub argument: serde_json::Value,
1069    /// Optional context for context-aware completion (MCP 2025-06-18)
1070    #[serde(skip_serializing_if = "Option::is_none")]
1071    pub context: Option<CompletionContext>,
1072}
1073
1074/// Completion information
1075#[derive(Debug, Clone, Serialize, Deserialize)]
1076pub struct CompletionInfo {
1077    pub completion: String,
1078    pub has_more: Option<bool>,
1079}
1080
1081/// Complete result
1082#[derive(Debug, Clone, Serialize, Deserialize)]
1083pub struct CompleteResult {
1084    pub completion: Vec<CompletionInfo>,
1085}
1086
1087/// Set logging level parameters
1088#[derive(Debug, Clone, Serialize, Deserialize)]
1089pub struct SetLevelRequestParam {
1090    pub level: LogLevel,
1091}
1092
1093/// Resource template definition
1094#[derive(Debug, Clone, Serialize, Deserialize)]
1095pub struct ResourceTemplate {
1096    #[serde(rename = "uriTemplate")]
1097    pub uri_template: String,
1098    pub name: String,
1099    pub description: Option<String>,
1100    #[serde(rename = "mimeType")]
1101    pub mime_type: Option<String>,
1102}
1103
1104/// List resource templates result
1105#[derive(Debug, Clone, Serialize, Deserialize)]
1106pub struct ListResourceTemplatesResult {
1107    #[serde(rename = "resourceTemplates")]
1108    pub resource_templates: Vec<ResourceTemplate>,
1109    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
1110    pub next_cursor: Option<String>,
1111}
1112
1113/// Subscribe request parameters
1114#[derive(Debug, Clone, Serialize, Deserialize)]
1115pub struct SubscribeRequestParam {
1116    pub uri: String,
1117}
1118
1119/// Unsubscribe request parameters
1120#[derive(Debug, Clone, Serialize, Deserialize)]
1121pub struct UnsubscribeRequestParam {
1122    pub uri: String,
1123}
1124
1125/// Resource updated notification parameters
1126/// Sent when a subscribed resource changes
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct ResourceUpdatedNotification {
1129    /// URI of the resource that was updated
1130    pub uri: String,
1131}
1132
1133/// Elicitation request parameters
1134#[derive(Debug, Clone, Serialize, Deserialize)]
1135pub struct ElicitationRequestParam {
1136    pub message: String,
1137    #[serde(rename = "requestedSchema")]
1138    pub requested_schema: serde_json::Value,
1139}
1140
1141/// Elicitation response actions
1142#[derive(Debug, Clone, Serialize, Deserialize)]
1143#[serde(rename_all = "lowercase")]
1144pub enum ElicitationAction {
1145    Accept,
1146    Decline,
1147    Cancel,
1148}
1149
1150/// Elicitation response
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1152pub struct ElicitationResponse {
1153    pub action: ElicitationAction,
1154    #[serde(skip_serializing_if = "Option::is_none")]
1155    pub data: Option<serde_json::Value>,
1156}
1157
1158/// Elicitation result
1159#[derive(Debug, Clone, Serialize, Deserialize)]
1160pub struct ElicitationResult {
1161    pub response: ElicitationResponse,
1162}
1163
1164impl ElicitationResult {
1165    /// Create an accept result with data
1166    pub fn accept(data: serde_json::Value) -> Self {
1167        Self {
1168            response: ElicitationResponse {
1169                action: ElicitationAction::Accept,
1170                data: Some(data),
1171            },
1172        }
1173    }
1174
1175    /// Create a decline result
1176    pub fn decline() -> Self {
1177        Self {
1178            response: ElicitationResponse {
1179                action: ElicitationAction::Decline,
1180                data: None,
1181            },
1182        }
1183    }
1184
1185    /// Create a cancel result
1186    pub fn cancel() -> Self {
1187        Self {
1188            response: ElicitationResponse {
1189                action: ElicitationAction::Cancel,
1190                data: None,
1191            },
1192        }
1193    }
1194}
1195
1196/// Serde module for serializing/deserializing JSON strings as objects
1197mod serde_json_string_or_object {
1198    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1199    use serde_json::Value;
1200
1201    pub fn serialize<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
1202    where
1203        S: Serializer,
1204    {
1205        // Parse the string as JSON and serialize it as an object
1206        match serde_json::from_str::<Value>(value) {
1207            Ok(json_value) => json_value.serialize(serializer),
1208            Err(_) => serializer.serialize_str(value), // Fall back to string if not valid JSON
1209        }
1210    }
1211
1212    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
1213    where
1214        D: Deserializer<'de>,
1215    {
1216        // Deserialize as JSON Value and convert to string
1217        let value = Value::deserialize(deserializer)?;
1218        Ok(value.to_string())
1219    }
1220}