Skip to main content

systemprompt_models/mcp/
capabilities.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4pub const MCP_APP_MIME_TYPE: &str = "text/html;profile=mcp-app";
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub enum McpExtensionId {
8    #[serde(rename = "io.modelcontextprotocol/ui")]
9    McpAppsUi,
10    #[serde(untagged)]
11    Custom(String),
12}
13
14impl McpExtensionId {
15    pub fn as_str(&self) -> &str {
16        match self {
17            Self::McpAppsUi => "io.modelcontextprotocol/ui",
18            Self::Custom(s) => s,
19        }
20    }
21
22    pub fn custom(id: impl Into<String>) -> Self {
23        Self::Custom(id.into())
24    }
25}
26
27impl fmt::Display for McpExtensionId {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{}", self.as_str())
30    }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct McpAppsUiConfig {
36    #[serde(default = "default_mime_types")]
37    pub mime_types: Vec<String>,
38}
39
40impl Default for McpAppsUiConfig {
41    fn default() -> Self {
42        Self {
43            mime_types: default_mime_types(),
44        }
45    }
46}
47
48impl McpAppsUiConfig {
49    pub fn new() -> Self {
50        Self::default()
51    }
52
53    pub fn to_json(&self) -> serde_json::Value {
54        serde_json::json!({
55            "mimeTypes": self.mime_types
56        })
57    }
58}
59
60fn default_mime_types() -> Vec<String> {
61    vec![MCP_APP_MIME_TYPE.to_string()]
62}
63
64#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "lowercase")]
66pub enum ToolVisibility {
67    #[default]
68    Model,
69    App,
70}
71
72impl fmt::Display for ToolVisibility {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            Self::Model => write!(f, "model"),
76            Self::App => write!(f, "app"),
77        }
78    }
79}
80
81pub fn default_visibility() -> Vec<ToolVisibility> {
82    vec![ToolVisibility::Model, ToolVisibility::App]
83}
84
85pub fn model_only_visibility() -> Vec<ToolVisibility> {
86    vec![ToolVisibility::Model]
87}
88
89pub fn visibility_to_json(visibility: &[ToolVisibility]) -> serde_json::Value {
90    serde_json::json!(visibility)
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct McpCspDomains {
96    #[serde(default, skip_serializing_if = "Vec::is_empty")]
97    pub connect: Vec<String>,
98    #[serde(default, skip_serializing_if = "Vec::is_empty")]
99    pub resources: Vec<String>,
100    #[serde(default, skip_serializing_if = "Vec::is_empty")]
101    pub frames: Vec<String>,
102    #[serde(default, skip_serializing_if = "Vec::is_empty")]
103    pub base_uri: Vec<String>,
104}
105
106impl McpCspDomains {
107    pub fn empty() -> Self {
108        Self::default()
109    }
110
111    pub fn builder() -> McpCspDomainsBuilder {
112        McpCspDomainsBuilder::new()
113    }
114}
115
116#[derive(Debug, Default)]
117pub struct McpCspDomainsBuilder {
118    inner: McpCspDomains,
119}
120
121impl McpCspDomainsBuilder {
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    pub fn connect_domain(mut self, domain: impl Into<String>) -> Self {
127        self.inner.connect.push(domain.into());
128        self
129    }
130
131    pub fn connect_domains(mut self, domains: impl IntoIterator<Item = impl Into<String>>) -> Self {
132        self.inner
133            .connect
134            .extend(domains.into_iter().map(Into::into));
135        self
136    }
137
138    pub fn resource_domain(mut self, domain: impl Into<String>) -> Self {
139        self.inner.resources.push(domain.into());
140        self
141    }
142
143    pub fn resource_domains(
144        mut self,
145        domains: impl IntoIterator<Item = impl Into<String>>,
146    ) -> Self {
147        self.inner
148            .resources
149            .extend(domains.into_iter().map(Into::into));
150        self
151    }
152
153    pub fn frame_domain(mut self, domain: impl Into<String>) -> Self {
154        self.inner.frames.push(domain.into());
155        self
156    }
157
158    pub fn base_uri_domain(mut self, domain: impl Into<String>) -> Self {
159        self.inner.base_uri.push(domain.into());
160        self
161    }
162
163    pub fn build(self) -> McpCspDomains {
164        self.inner
165    }
166}
167
168#[derive(Debug, Clone, Default, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct McpResourceUiMeta {
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub csp: Option<McpCspDomains>,
173    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
174    pub prefers_border: bool,
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub domain: Option<String>,
177}
178
179impl McpResourceUiMeta {
180    pub fn new() -> Self {
181        Self::default()
182    }
183
184    pub fn with_csp(mut self, csp: McpCspDomains) -> Self {
185        self.csp = Some(csp);
186        self
187    }
188
189    pub fn with_csp_opt(mut self, csp: Option<McpCspDomains>) -> Self {
190        self.csp = csp;
191        self
192    }
193
194    pub const fn with_prefers_border(mut self, prefers: bool) -> Self {
195        self.prefers_border = prefers;
196        self
197    }
198
199    pub fn with_domain(mut self, domain: impl Into<String>) -> Self {
200        self.domain = Some(domain.into());
201        self
202    }
203
204    pub fn to_json(&self) -> serde_json::Value {
205        let mut obj = serde_json::json!({});
206        if let Some(csp) = &self.csp {
207            obj["csp"] = serde_json::json!(csp);
208        }
209        if self.prefers_border {
210            obj["prefersBorder"] = serde_json::json!(true);
211        }
212        if let Some(domain) = &self.domain {
213            obj["domain"] = serde_json::json!(domain);
214        }
215        obj
216    }
217
218    pub fn to_meta_map(&self) -> serde_json::Map<String, serde_json::Value> {
219        let mut meta = serde_json::Map::new();
220        meta.insert("ui".to_string(), self.to_json());
221        meta
222    }
223}