systemprompt_models/mcp/
capabilities.rs1use 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}