|
39 | 39 | */ |
40 | 40 | function getProxyParams() { |
41 | 41 | var params = new URLSearchParams(window.location.search); |
| 42 | + var colorScheme = params.get('color_scheme'); |
42 | 43 | return { |
43 | 44 | secret: params.get('secret') || '', |
44 | | - baseUrl: window.location.origin |
| 45 | + baseUrl: window.location.origin, |
| 46 | + colorScheme: colorScheme === 'light' || colorScheme === 'dark' ? colorScheme : null |
45 | 47 | }; |
46 | 48 | } |
47 | 49 |
|
| 50 | + function applyProxyColorScheme(nextColorScheme) { |
| 51 | + var proxyParams = getProxyParams(); |
| 52 | + var colorScheme = nextColorScheme || proxyParams.colorScheme || 'light dark'; |
| 53 | + document.documentElement.style.colorScheme = colorScheme; |
| 54 | + document.body.style.colorScheme = colorScheme; |
| 55 | + if (guestIframe) { |
| 56 | + guestIframe.style.setProperty('color-scheme', colorScheme); |
| 57 | + } |
| 58 | + } |
| 59 | + |
| 60 | + function createColorSchemePrelude(colorScheme) { |
| 61 | + var hostColorScheme = colorScheme === 'dark' ? 'dark' : 'light'; |
| 62 | + var matchMediaScript = [ |
| 63 | + '<scr' + 'ipt>', |
| 64 | + '(function(){', |
| 65 | + 'var nativeMatchMedia=window.matchMedia&&window.matchMedia.bind(window);', |
| 66 | + 'function normalizeColorScheme(value){return value==="dark"?"dark":"light";}', |
| 67 | + 'function setHostColorScheme(value){window.__mcpHostColorScheme=normalizeColorScheme(value);document.documentElement.style.colorScheme=window.__mcpHostColorScheme;if(document.body){document.body.style.colorScheme=window.__mcpHostColorScheme;}var meta=document.querySelector("meta[name=\\"color-scheme\\"]");if(meta){meta.setAttribute("content",window.__mcpHostColorScheme);}}', |
| 68 | + 'setHostColorScheme(' + JSON.stringify(hostColorScheme) + ');', |
| 69 | + 'document.addEventListener("DOMContentLoaded",function(){setHostColorScheme(window.__mcpHostColorScheme);});', |
| 70 | + 'if(nativeMatchMedia){window.matchMedia=function(query){var normalized=String(query).replace(/\\s+/g," ").trim().toLowerCase();var isDark=normalized==="(prefers-color-scheme: dark)";var isLight=normalized==="(prefers-color-scheme: light)";if(!isDark&&!isLight){return nativeMatchMedia(query);}return {matches:isDark?window.__mcpHostColorScheme==="dark":window.__mcpHostColorScheme==="light",media:String(query),onchange:null,addListener:function(){},removeListener:function(){},addEventListener:function(){},removeEventListener:function(){},dispatchEvent:function(){return false;}};};}', |
| 71 | + 'window.addEventListener("message",function(event){var data=event.data;if(!data||data.method!=="ui/notifications/host-context-changed"){return;}var theme=data.params&&data.params.theme;if(theme==="light"||theme==="dark"){setHostColorScheme(theme);}});', |
| 72 | + '})();', |
| 73 | + '</scr' + 'ipt>' |
| 74 | + ].join(''); |
| 75 | + |
| 76 | + return '<meta name="color-scheme" content="' + hostColorScheme + '"><style id="mcp-app-host-color-scheme">:root{color-scheme:' + hostColorScheme + ';}html,body{background-color:transparent;}</style>' + matchMediaScript; |
| 77 | + } |
| 78 | + |
| 79 | + function injectGuestColorScheme(html, colorScheme) { |
| 80 | + if (!colorScheme) { |
| 81 | + return html; |
| 82 | + } |
| 83 | + |
| 84 | + var prelude = createColorSchemePrelude(colorScheme); |
| 85 | + var cleanedHtml = html.replace(/<meta\s+[^>]*name\s*=\s*["']color-scheme["'][^>]*>/gi, ''); |
| 86 | + |
| 87 | + if (/<head\b[^>]*>/i.test(cleanedHtml)) { |
| 88 | + return cleanedHtml.replace(/<head\b[^>]*>/i, function (match) { |
| 89 | + return match + prelude; |
| 90 | + }); |
| 91 | + } |
| 92 | + |
| 93 | + if (/<html\b[^>]*>/i.test(cleanedHtml)) { |
| 94 | + return cleanedHtml.replace(/<html\b[^>]*>/i, function (match) { |
| 95 | + return match + '<head>' + prelude + '</head>'; |
| 96 | + }); |
| 97 | + } |
| 98 | + |
| 99 | + return prelude + cleanedHtml; |
| 100 | + } |
| 101 | + |
48 | 102 | /** |
49 | 103 | * Store guest HTML on the server and get a nonce for retrieval. |
50 | 104 | * This allows the guest iframe to load from a real HTTPS URL |
|
93 | 147 | guestIframe.setAttribute('allow', allowList.join('; ')); |
94 | 148 | } |
95 | 149 |
|
96 | | - guestIframe.style.cssText = 'width:100%; height:100%; border:none;'; |
| 150 | + var proxyParams = getProxyParams(); |
| 151 | + var colorScheme = proxyParams.colorScheme || 'light dark'; |
| 152 | + guestIframe.style.cssText = 'width:100%; height:100%; border:none; background-color:transparent; color-scheme:' + colorScheme + ';'; |
| 153 | + var guestHtml = injectGuestColorScheme(html, proxyParams.colorScheme); |
97 | 154 |
|
98 | 155 | // Store the HTML server-side and load via real URL. |
99 | 156 | // This gives the guest iframe a real https:// URL instead of about:srcdoc, |
100 | 157 | // which is required by SDKs (like Square Web Payments) that check |
101 | 158 | // window.location.protocol for secure context verification. |
102 | 159 | try { |
103 | | - var nonce = await storeGuestHtml(html); |
104 | | - var proxyParams = getProxyParams(); |
| 160 | + var nonce = await storeGuestHtml(guestHtml); |
105 | 161 | guestIframe.src = proxyParams.baseUrl + '/mcp-app-guest?secret=' + encodeURIComponent(proxyParams.secret) + '&nonce=' + encodeURIComponent(nonce); |
106 | 162 | } catch (e) { |
107 | 163 | // Fallback to srcdoc if the store endpoint is not available |
108 | 164 | console.warn('Failed to use /mcp-app-guest endpoint, falling back to srcdoc:', e); |
109 | | - guestIframe.srcdoc = html; |
| 165 | + guestIframe.srcdoc = guestHtml; |
110 | 166 | } |
111 | 167 |
|
112 | 168 | document |
|
129 | 185 |
|
130 | 186 | var method = data.method; |
131 | 187 |
|
| 188 | + if (method === 'ui/notifications/host-context-changed') { |
| 189 | + var theme = data.params && data.params.theme; |
| 190 | + if (theme === 'light' || theme === 'dark') { |
| 191 | + applyProxyColorScheme(theme); |
| 192 | + } |
| 193 | + } |
| 194 | + |
132 | 195 | // Handle sandbox-specific notifications from Host |
133 | 196 | if (method === 'ui/notifications/sandbox-resource-ready') { |
134 | 197 | var params = data.params || {}; |
|
181 | 244 |
|
182 | 245 | // Set up message listener |
183 | 246 | window.addEventListener('message', handleMessage); |
| 247 | + applyProxyColorScheme(); |
184 | 248 |
|
185 | 249 | // Notify Host that Sandbox is ready |
186 | 250 | window |
|
0 commit comments