1use super::capabilities::ToolVisibility;
22use rmcp::model::{ContentBlock, Implementation};
23use serde::{Deserialize, Serialize};
24use std::fmt;
25
26pub const EXTENSION_ID: &str = "io.modelcontextprotocol/ui";
27
28pub const RESOURCE_MIME_TYPE: &str = "text/html;profile=mcp-app";
29
30pub const LATEST_PROTOCOL_VERSION: &str = "2026-01-26";
31
32pub const UI_META_KEY: &str = "ui";
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum UiMethod {
36 #[serde(rename = "ui/initialize")]
37 Initialize,
38 #[serde(rename = "ui/notifications/initialized")]
39 Initialized,
40 #[serde(rename = "ui/notifications/tool-input")]
41 ToolInput,
42 #[serde(rename = "ui/notifications/tool-input-partial")]
43 ToolInputPartial,
44 #[serde(rename = "ui/notifications/tool-result")]
45 ToolResult,
46 #[serde(rename = "ui/notifications/tool-cancelled")]
47 ToolCancelled,
48 #[serde(rename = "ui/notifications/size-changed")]
49 SizeChanged,
50 #[serde(rename = "ui/notifications/host-context-changed")]
51 HostContextChanged,
52 #[serde(rename = "ui/request-display-mode")]
53 RequestDisplayMode,
54 #[serde(rename = "ui/message")]
55 Message,
56 #[serde(rename = "ui/update-model-context")]
57 UpdateModelContext,
58 #[serde(rename = "ui/open-link")]
59 OpenLink,
60 #[serde(rename = "ui/download-file")]
61 DownloadFile,
62 #[serde(rename = "ui/resource-teardown")]
63 ResourceTeardown,
64 #[serde(rename = "ui/notifications/request-teardown")]
65 RequestTeardown,
66}
67
68impl UiMethod {
69 #[must_use]
70 pub const fn as_str(self) -> &'static str {
71 match self {
72 Self::Initialize => "ui/initialize",
73 Self::Initialized => "ui/notifications/initialized",
74 Self::ToolInput => "ui/notifications/tool-input",
75 Self::ToolInputPartial => "ui/notifications/tool-input-partial",
76 Self::ToolResult => "ui/notifications/tool-result",
77 Self::ToolCancelled => "ui/notifications/tool-cancelled",
78 Self::SizeChanged => "ui/notifications/size-changed",
79 Self::HostContextChanged => "ui/notifications/host-context-changed",
80 Self::RequestDisplayMode => "ui/request-display-mode",
81 Self::Message => "ui/message",
82 Self::UpdateModelContext => "ui/update-model-context",
83 Self::OpenLink => "ui/open-link",
84 Self::DownloadFile => "ui/download-file",
85 Self::ResourceTeardown => "ui/resource-teardown",
86 Self::RequestTeardown => "ui/notifications/request-teardown",
87 }
88 }
89
90 #[must_use]
91 pub const fn js_const(self) -> &'static str {
92 match self {
93 Self::Initialize => "INITIALIZE",
94 Self::Initialized => "INITIALIZED",
95 Self::ToolInput => "TOOL_INPUT",
96 Self::ToolInputPartial => "TOOL_INPUT_PARTIAL",
97 Self::ToolResult => "TOOL_RESULT",
98 Self::ToolCancelled => "TOOL_CANCELLED",
99 Self::SizeChanged => "SIZE_CHANGED",
100 Self::HostContextChanged => "HOST_CONTEXT_CHANGED",
101 Self::RequestDisplayMode => "REQUEST_DISPLAY_MODE",
102 Self::Message => "MESSAGE",
103 Self::UpdateModelContext => "UPDATE_MODEL_CONTEXT",
104 Self::OpenLink => "OPEN_LINK",
105 Self::DownloadFile => "DOWNLOAD_FILE",
106 Self::ResourceTeardown => "RESOURCE_TEARDOWN",
107 Self::RequestTeardown => "REQUEST_TEARDOWN",
108 }
109 }
110
111 #[must_use]
112 pub const fn all() -> &'static [Self] {
113 &[
114 Self::Initialize,
115 Self::Initialized,
116 Self::ToolInput,
117 Self::ToolInputPartial,
118 Self::ToolResult,
119 Self::ToolCancelled,
120 Self::SizeChanged,
121 Self::HostContextChanged,
122 Self::RequestDisplayMode,
123 Self::Message,
124 Self::UpdateModelContext,
125 Self::OpenLink,
126 Self::DownloadFile,
127 Self::ResourceTeardown,
128 Self::RequestTeardown,
129 ]
130 }
131}
132
133impl fmt::Display for UiMethod {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.write_str(self.as_str())
136 }
137}
138
139#[derive(Debug, Clone, Default, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct McpUiToolMeta {
142 #[serde(skip_serializing_if = "Option::is_none")]
143 pub resource_uri: Option<String>,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 pub visibility: Option<Vec<ToolVisibility>>,
146}
147
148impl McpUiToolMeta {
149 #[must_use]
150 pub fn new(resource_uri: impl Into<String>) -> Self {
151 Self {
152 resource_uri: Some(resource_uri.into()),
153 visibility: None,
154 }
155 }
156
157 #[must_use]
158 pub fn with_visibility(mut self, visibility: Vec<ToolVisibility>) -> Self {
159 self.visibility = Some(visibility);
160 self
161 }
162}
163
164#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
165pub struct SizeChangedParams {
166 #[serde(skip_serializing_if = "Option::is_none")]
167 pub width: Option<f64>,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 pub height: Option<f64>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct UiMessageParams {
174 pub role: UiMessageRole,
175 pub content: Vec<ContentBlock>,
176}
177
178impl UiMessageParams {
179 #[must_use]
180 pub fn user_text(text: impl Into<String>) -> Self {
181 Self {
182 role: UiMessageRole::User,
183 content: vec![ContentBlock::text(text.into())],
184 }
185 }
186}
187
188#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(rename_all = "lowercase")]
190pub enum UiMessageRole {
191 #[default]
192 User,
193}
194
195#[derive(Debug, Clone, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct UiInitializeParams {
198 pub app_info: Implementation,
199 pub app_capabilities: serde_json::Value,
202 pub protocol_version: String,
203}
204
205impl UiInitializeParams {
206 #[must_use]
207 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
208 Self {
209 app_info: Implementation::new(name.into(), version.into()),
210 app_capabilities: serde_json::json!({}),
211 protocol_version: LATEST_PROTOCOL_VERSION.to_owned(),
212 }
213 }
214}
215
216#[must_use]
217pub fn ui_method_js_constants() -> String {
218 let entries = UiMethod::all()
219 .iter()
220 .map(|m| format!(" {}: '{}'", m.js_const(), m.as_str()))
221 .collect::<Vec<_>>()
222 .join(",\n");
223
224 format!(
225 "const MCP_UI = Object.freeze({{\n{entries},\n PROTOCOL_VERSION: \
226 '{LATEST_PROTOCOL_VERSION}'\n}});\n"
227 )
228}