@@ -7,19 +7,10 @@ use crate::apps::app::{AppResource, AppResourceSource, AppTarget};
77
88use super :: App ;
99
10- pub ( crate ) fn get_mime_type ( app_target : & AppTarget ) -> String {
11- match app_target {
12- AppTarget :: AppsSDK => "text/html+skybridge" . to_string ( ) ,
13- AppTarget :: MCPApps => "text/html;profile=mcp-app" . to_string ( ) ,
14- }
15- }
10+ const MCP_MIME_TYPE : & str = "text/html;profile=mcp-app" ;
1611
17- // Attach resource mime type when requested to allow swapping between app targets (Apps SDK, MCP Apps)
18- pub ( crate ) fn attach_resource_mime_type (
19- mut resource : Resource ,
20- app_target : & AppTarget ,
21- ) -> Resource {
22- resource. raw . mime_type = Some ( get_mime_type ( app_target) ) ;
12+ pub ( crate ) fn attach_resource_mime_type ( mut resource : Resource ) -> Resource {
13+ resource. raw . mime_type = Some ( MCP_MIME_TYPE . to_string ( ) ) ;
2314 resource
2415}
2516
@@ -88,34 +79,32 @@ pub(crate) async fn get_app_resource(
8879 }
8980 } ;
9081
82+ // Most properties now are listed under _meta.ui.* but some openai specific properties are still at the root
83+ // So, we will populate both and then nest "ui" into "meta" later in this function
9184 let mut meta: Option < Meta > = None ;
85+ let mut ui: Option < Meta > = None ;
9286 if let Some ( csp) = & app. csp_settings {
93- match app_target {
94- // Note that the difference in which keys are set here and the camelCase vs snake_key is on purpose. These are differences between the two specs.
95- AppTarget :: AppsSDK => {
96- meta. get_or_insert_with ( Meta :: new) . insert (
97- "openai/widgetCSP" . into ( ) ,
98- json ! ( {
99- "connect_domains" : csp. connect_domains,
100- "resource_domains" : csp. resource_domains,
101- "frame_domains" : csp. frame_domains,
102- "redirect_domains" : csp. redirect_domains
103- } ) ,
104- ) ;
105- }
106- AppTarget :: MCPApps => {
107- meta. get_or_insert_with ( Meta :: new) . insert (
108- "csp" . into ( ) ,
109- json ! ( {
110- "connectDomains" : csp. connect_domains,
111- "resourceDomains" : csp. resource_domains,
112- "frameDomains" : csp. frame_domains,
113- "baseUriDomains" : csp. base_uri_domains
114- } ) ,
115- ) ;
116- }
87+ ui. get_or_insert_with ( Meta :: new) . insert (
88+ "csp" . into ( ) ,
89+ json ! ( {
90+ "connectDomains" : csp. connect_domains,
91+ "resourceDomains" : csp. resource_domains,
92+ "frameDomains" : csp. frame_domains,
93+ "baseUriDomains" : csp. base_uri_domains
94+ } ) ,
95+ ) ;
96+
97+ // Openai has a specific property so we'll set that separately
98+ if matches ! ( app_target, AppTarget :: AppsSDK ) {
99+ meta. get_or_insert_with ( Meta :: new) . insert (
100+ "openai/widgetCSP" . into ( ) ,
101+ json ! ( {
102+ "redirect_domains" : csp. redirect_domains
103+ } ) ,
104+ ) ;
117105 }
118106 }
107+
119108 if let Some ( widget_settings) = & app. widget_settings {
120109 if let Some ( description) = & widget_settings. description
121110 && matches ! ( app_target, AppTarget :: AppsSDK )
@@ -127,36 +116,26 @@ pub(crate) async fn get_app_resource(
127116 }
128117
129118 if let Some ( domain) = & widget_settings. domain {
130- meta. get_or_insert_with ( Meta :: new) . insert (
131- match app_target {
132- AppTarget :: AppsSDK => "openai/widgetDomain" . into ( ) ,
133- AppTarget :: MCPApps => "domain" . into ( ) ,
134- } ,
119+ ui. get_or_insert_with ( Meta :: new) . insert (
120+ "domain" . into ( ) ,
135121 serde_json:: to_value ( domain) . unwrap_or_default ( ) ,
136122 ) ;
137123 }
138124
139125 if let Some ( prefers_border) = & widget_settings. prefers_border {
140- meta. get_or_insert_with ( Meta :: new) . insert (
141- match app_target {
142- AppTarget :: AppsSDK => "openai/widgetPrefersBorder" . into ( ) ,
143- AppTarget :: MCPApps => "prefersBorder" . into ( ) ,
144- } ,
126+ ui. get_or_insert_with ( Meta :: new) . insert (
127+ "prefersBorder" . into ( ) ,
145128 serde_json:: to_value ( prefers_border) . unwrap_or_default ( ) ,
146129 ) ;
147130 }
148131 }
149132
150- // In the case of MCP Apps, the meta data is nested under `_meta.ui`
151- if matches ! ( app_target, AppTarget :: MCPApps ) {
152- let mut nested = Meta :: new ( ) ;
153- nested. insert ( "ui" . into ( ) , serde_json:: to_value ( meta) . unwrap_or_default ( ) ) ;
154- meta = Some ( nested) ;
155- }
133+ meta. get_or_insert_with ( Meta :: new)
134+ . insert ( "ui" . into ( ) , serde_json:: to_value ( ui) . unwrap_or_default ( ) ) ;
156135
157136 Ok ( ResourceContents :: TextResourceContents {
158137 uri : request. uri ,
159- mime_type : Some ( get_mime_type ( app_target ) ) ,
138+ mime_type : Some ( MCP_MIME_TYPE . to_string ( ) ) ,
160139 text,
161140 meta,
162141 } )
@@ -172,7 +151,7 @@ mod tests {
172151 use super :: * ;
173152
174153 #[ test]
175- fn attach_correct_mime_type_when_open_ai ( ) {
154+ fn attach_correct_mime_type ( ) {
176155 let resource = Resource :: new (
177156 RawResource {
178157 name : "TestResource" . to_string ( ) ,
@@ -187,89 +166,14 @@ mod tests {
187166 None ,
188167 ) ;
189168
190- let mut extensions = Extensions :: new ( ) ;
191- let request = axum:: http:: Request :: builder ( )
192- . uri ( "http://localhost?appTarget=openai" )
193- . body ( ( ) )
194- . unwrap ( ) ;
195- let ( parts, _) = request. into_parts ( ) ;
196- extensions. insert ( parts) ;
197- let app_target = AppTarget :: try_from ( extensions) . unwrap ( ) ;
198-
199- let result = attach_resource_mime_type ( resource, & app_target) ;
200-
201- assert_eq ! (
202- result. raw. mime_type,
203- Some ( "text/html+skybridge" . to_string( ) )
204- ) ;
205- }
206-
207- #[ test]
208- fn attach_correct_mime_type_when_mcp_apps ( ) {
209- let resource = Resource :: new (
210- RawResource {
211- name : "TestResource" . to_string ( ) ,
212- uri : "ui://test" . to_string ( ) ,
213- mime_type : None ,
214- title : None ,
215- description : None ,
216- icons : None ,
217- size : None ,
218- meta : None ,
219- } ,
220- None ,
221- ) ;
222-
223- let mut extensions = Extensions :: new ( ) ;
224- let request = axum:: http:: Request :: builder ( )
225- . uri ( "http://localhost?appTarget=mcp" )
226- . body ( ( ) )
227- . unwrap ( ) ;
228- let ( parts, _) = request. into_parts ( ) ;
229- extensions. insert ( parts) ;
230- let app_target = AppTarget :: try_from ( extensions) . unwrap ( ) ;
231-
232- let result = attach_resource_mime_type ( resource, & app_target) ;
169+ let result = attach_resource_mime_type ( resource) ;
233170
234171 assert_eq ! (
235172 result. raw. mime_type,
236173 Some ( "text/html;profile=mcp-app" . to_string( ) )
237174 ) ;
238175 }
239176
240- #[ test]
241- fn attach_correct_mime_type_when_not_provided ( ) {
242- let resource = Resource :: new (
243- RawResource {
244- name : "TestResource" . to_string ( ) ,
245- uri : "ui://test" . to_string ( ) ,
246- mime_type : None ,
247- title : None ,
248- description : None ,
249- icons : None ,
250- size : None ,
251- meta : None ,
252- } ,
253- None ,
254- ) ;
255-
256- let mut extensions = Extensions :: new ( ) ;
257- let request = axum:: http:: Request :: builder ( )
258- . uri ( "http://localhost" )
259- . body ( ( ) )
260- . unwrap ( ) ;
261- let ( parts, _) = request. into_parts ( ) ;
262- extensions. insert ( parts) ;
263- let app_target = AppTarget :: try_from ( extensions) . unwrap ( ) ;
264-
265- let result = attach_resource_mime_type ( resource, & app_target) ;
266-
267- assert_eq ! (
268- result. raw. mime_type,
269- Some ( "text/html+skybridge" . to_string( ) )
270- ) ;
271- }
272-
273177 #[ test]
274178 fn errors_when_invalid_target_provided ( ) {
275179 let mut extensions = Extensions :: new ( ) ;
@@ -329,21 +233,23 @@ mod tests {
329233 else {
330234 unreachable ! ( )
331235 } ;
332- assert_eq ! ( mime_type, Some ( "text/html+skybridge " . to_string( ) ) ) ;
236+ assert_eq ! ( mime_type, Some ( "text/html;profile=mcp-app " . to_string( ) ) ) ;
333237
334238 let meta = meta. unwrap ( ) ;
335- // AppsSDK CSP uses snake_case keys and includes redirect_domains (not base_uri_domains)
239+ // OpenAI-specific CSP with redirect_domains should be at root
336240 let csp = meta. get ( "openai/widgetCSP" ) . unwrap ( ) ;
337- assert ! ( csp. get( "connect_domains" ) . is_some( ) ) ;
338- assert ! ( csp. get( "resource_domains" ) . is_some( ) ) ;
339- assert ! ( csp. get( "frame_domains" ) . is_some( ) ) ;
340241 assert ! ( csp. get( "redirect_domains" ) . is_some( ) ) ;
341- assert ! ( csp . get ( "base_uri_domains" ) . is_none ( ) ) ;
242+ // OpenAI-specific description should be at root
342243 assert ! ( meta. get( "openai/widgetDescription" ) . is_some( ) ) ;
343- assert ! ( meta. get( "openai/widgetDomain" ) . is_some( ) ) ;
344- assert ! ( meta. get( "openai/widgetPrefersBorder" ) . is_some( ) ) ;
345- // AppsSDK should not have ui nesting
346- assert ! ( meta. get( "ui" ) . is_none( ) ) ;
244+ // ui nesting should contain the common properties
245+ let ui_meta = meta. get ( "ui" ) . unwrap ( ) ;
246+ let ui_csp = ui_meta. get ( "csp" ) . unwrap ( ) ;
247+ assert ! ( ui_csp. get( "connectDomains" ) . is_some( ) ) ;
248+ assert ! ( ui_csp. get( "resourceDomains" ) . is_some( ) ) ;
249+ assert ! ( ui_csp. get( "frameDomains" ) . is_some( ) ) ;
250+ assert ! ( ui_csp. get( "baseUriDomains" ) . is_some( ) ) ;
251+ assert ! ( ui_meta. get( "domain" ) . is_some( ) ) ;
252+ assert ! ( ui_meta. get( "prefersBorder" ) . is_some( ) ) ;
347253 }
348254
349255 #[ tokio:: test]
0 commit comments