@frontmcp/uipack 1.8.6 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,6 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
1
4
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
5
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
6
  }) : x)(function(x) {
@@ -535,8 +538,8 @@ if (__root) {
535
538
  configurable: true, enumerable: true
536
539
  });
537
540
  }
538
- // 2. Try injected data globals
539
- if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
541
+ // 2. Try injected data globals (null: the page carries no call's data, e.g. compiled at startup)
542
+ if (__hasData(window.__mcpToolOutput) && window.__mcpToolOutput !== null) { __render(window.__mcpToolOutput); }
540
543
  // 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
541
544
  var __bridge = window.FrontMcpBridge;
542
545
  if (__bridge && typeof __bridge.onToolResult === 'function') {
@@ -788,7 +791,7 @@ createRoot(document.getElementById('root'))
788
791
  return `${componentImport}
789
792
  import React from 'react';
790
793
  import { createRoot } from 'react-dom/client';
791
- const output = window.__mcpToolOutput;
794
+ const output = window.__mcpToolOutput || {};
792
795
  const props = ${propsCode};
793
796
  createRoot(document.getElementById('root'))
794
797
  .render(React.createElement(Component, props));`;
@@ -1092,12 +1095,38 @@ function __applyHostTheme(theme) {
1092
1095
  } catch (e) {}
1093
1096
  }
1094
1097
 
1098
+ function extractToolOutput(params) {
1099
+ // A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
1100
+ if (!params) return undefined;
1101
+ if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
1102
+ var content = params.content;
1103
+ if (Array.isArray(content)) {
1104
+ for (var i = 0; i < content.length; i++) {
1105
+ var item = content[i];
1106
+ if (item && item.type === 'text' && typeof item.text === 'string') {
1107
+ try { return JSON.parse(item.text); } catch (e) { return item.text; }
1108
+ }
1109
+ }
1110
+ }
1111
+ return content;
1112
+ }
1113
+
1095
1114
  function readInjectedData() {
1096
1115
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
1097
1116
  if (typeof window !== 'undefined') {
1098
1117
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
1099
1118
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
1100
1119
  if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
1120
+ // The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
1121
+ // (toolOutput is the tool's structuredContent).
1122
+ var oa = window.openai;
1123
+ if (oa && typeof oa === 'object') {
1124
+ if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
1125
+ if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
1126
+ if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
1127
+ if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
1128
+ }
1129
+ }
1101
1130
  }
1102
1131
  return data;
1103
1132
  }
@@ -1146,7 +1175,24 @@ var OpenAIAdapter = {
1146
1175
  if (sdk.displayMode) {
1147
1176
  context.hostContext.displayMode = sdk.displayMode;
1148
1177
  }
1149
- // Note: OpenAI SDK does not have an onContextChange equivalent
1178
+ // The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
1179
+ window.addEventListener('openai:set_globals', function(event) {
1180
+ var globals = (event && event.detail && event.detail.globals) || window.openai || {};
1181
+ if (globals.toolInput !== undefined && globals.toolInput !== null) {
1182
+ context.toolInput = globals.toolInput;
1183
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
1184
+ }
1185
+ if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
1186
+ context.structuredContent = globals.toolOutput;
1187
+ context.notifyToolResult(globals.toolOutput);
1188
+ }
1189
+ if (globals.theme && globals.theme !== context.hostContext.theme) {
1190
+ context.notifyContextChange({ theme: globals.theme });
1191
+ }
1192
+ if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
1193
+ context.notifyContextChange({ displayMode: globals.displayMode });
1194
+ }
1195
+ });
1150
1196
  return Promise.resolve();
1151
1197
  },
1152
1198
  callTool: function(context, name, args) {
@@ -1293,9 +1339,10 @@ var ExtAppsAdapter = {
1293
1339
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
1294
1340
  break;
1295
1341
  case 'ui/notifications/tool-result':
1296
- context.toolOutput = params.content;
1342
+ var resultOutput = extractToolOutput(params);
1343
+ context.toolOutput = resultOutput;
1297
1344
  context.structuredContent = params.structuredContent;
1298
- context.notifyToolResult(params.content);
1345
+ context.notifyToolResult(resultOutput);
1299
1346
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
1300
1347
  break;
1301
1348
  case 'ui/notifications/host-context-changed':
@@ -1349,6 +1396,15 @@ var ExtAppsAdapter = {
1349
1396
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
1350
1397
  });
1351
1398
  },
1399
+ sendNotification: function(method, params) {
1400
+ // JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
1401
+ if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
1402
+ return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
1403
+ }
1404
+ var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
1405
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
1406
+ return Promise.resolve();
1407
+ },
1352
1408
  sendHandshake: function(context) {
1353
1409
  // Send ui/initialize using '*' as target origin since TOFU hasn't
1354
1410
  // been established yet. The response from the host will establish
@@ -1414,7 +1470,7 @@ var ExtAppsAdapter = {
1414
1470
  var pending = this.pendingSize;
1415
1471
  if (!pending) return;
1416
1472
  this.pendingSize = null;
1417
- this.sendRequest('ui/setSize', pending.params).then(function(result) {
1473
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
1418
1474
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
1419
1475
  }, function(err) {
1420
1476
  for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
@@ -1441,13 +1497,11 @@ var ExtAppsAdapter = {
1441
1497
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
1442
1498
  },
1443
1499
  setSize: function(context, size) {
1444
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1445
- // measured/desired widget dimensions to the host.
1446
- var params = {
1447
- height: size && size.height,
1448
- width: size && size.width,
1449
- aspectRatio: size && size.aspectRatio
1450
- };
1500
+ // Standard ext-apps sizing: the view tells the host its dimensions with the
1501
+ // ui/notifications/size-changed notification ({ width?, height? } in px).
1502
+ var params = {};
1503
+ if (size && typeof size.width === 'number') params.width = size.width;
1504
+ if (size && typeof size.height === 'number') params.height = size.height;
1451
1505
  if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
1452
1506
  // No host origin yet: keep only the latest size and send it when the
1453
1507
  // handshake settles; every caller's promise follows that one request.
@@ -1458,7 +1512,7 @@ var ExtAppsAdapter = {
1458
1512
  self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
1459
1513
  });
1460
1514
  }
1461
- return this.sendRequest('ui/setSize', params);
1515
+ return this.sendNotification('ui/notifications/size-changed', params);
1462
1516
  },
1463
1517
  requestClose: function(context) {
1464
1518
  return this.sendRequest('ui/close', {});
@@ -1881,7 +1935,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1881
1935
 
1882
1936
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1883
1937
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1884
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1938
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1885
1939
  FrontMcpBridge.prototype.setSize = function(size) {
1886
1940
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1887
1941
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1983,7 +2037,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1983
2037
  target.classList.add('opacity-50', 'cursor-not-allowed');
1984
2038
 
1985
2039
  // Add spinner for buttons
1986
- var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
2040
+ var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
1987
2041
  if (target.tagName === 'BUTTON') {
1988
2042
  target.innerHTML = spinner + 'Loading...';
1989
2043
  }
@@ -2064,7 +2118,22 @@ function minifyJS(code) {
2064
2118
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
2065
2119
  return code;
2066
2120
  }
2067
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
2121
+ const out = [];
2122
+ let inBlockComment = false;
2123
+ for (const raw of code.split("\n")) {
2124
+ const line = raw.trim();
2125
+ if (inBlockComment) {
2126
+ if (line.includes("*/")) inBlockComment = false;
2127
+ continue;
2128
+ }
2129
+ if (line === "" || line.startsWith("//")) continue;
2130
+ if (line.startsWith("/*")) {
2131
+ if (!line.includes("*/")) inBlockComment = true;
2132
+ continue;
2133
+ }
2134
+ out.push(line);
2135
+ }
2136
+ return out.join("\n");
2068
2137
  }
2069
2138
  function generatePlatformBundle(platform, options = {}) {
2070
2139
  const platformAdapters = {
@@ -2119,11 +2188,8 @@ function buildCSPDirectives(csp) {
2119
2188
  directives.push(`img-src ${imgSources.join(" ")}`);
2120
2189
  const fontSources = ["'self'", "data:", ...allResourceDomains];
2121
2190
  directives.push(`font-src ${fontSources.join(" ")}`);
2122
- if (validConnectDomains.length) {
2123
- directives.push(`connect-src ${validConnectDomains.join(" ")}`);
2124
- } else {
2125
- directives.push(`connect-src ${allResourceDomains.join(" ")}`);
2126
- }
2191
+ const connectSources = [.../* @__PURE__ */ new Set([...allResourceDomains, ...validConnectDomains])];
2192
+ directives.push(`connect-src ${connectSources.join(" ")}`);
2127
2193
  directives.push("object-src 'self' data:");
2128
2194
  return directives;
2129
2195
  }
@@ -2132,26 +2198,34 @@ function buildCSPMetaTag(csp) {
2132
2198
  const content = directives.join("; ");
2133
2199
  return `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(content)}">`;
2134
2200
  }
2201
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
2135
2202
  function validateCSPDomain(domain) {
2136
- if (domain.startsWith("https://*.")) {
2137
- const rest = domain.slice(10);
2138
- return /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.[a-zA-Z]{2,}$/.test(rest);
2203
+ if (typeof domain !== "string" || /[\s;,'"]/.test(domain)) return false;
2204
+ if (/[?#]/.test(domain)) return false;
2205
+ if (domain.includes("*")) {
2206
+ const wildcard = /^(?:https|wss):\/\/\*\.(.*)$/.exec(domain);
2207
+ return wildcard !== null && /^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(wildcard[1]);
2139
2208
  }
2140
2209
  try {
2141
2210
  const url = new URL(domain);
2142
- return url.protocol === "https:";
2211
+ if (url.protocol === "https:" || url.protocol === "wss:") return true;
2212
+ return (url.protocol === "http:" || url.protocol === "ws:") && LOOPBACK_HOSTS.has(url.hostname);
2143
2213
  } catch {
2144
2214
  return false;
2145
2215
  }
2146
2216
  }
2217
+ var reportedInvalidDomains = /* @__PURE__ */ new Set();
2147
2218
  function sanitizeCSPDomains(domains) {
2148
2219
  if (!domains) return [];
2149
2220
  const valid = [];
2150
2221
  for (const domain of domains) {
2151
2222
  if (validateCSPDomain(domain)) {
2152
2223
  valid.push(domain);
2153
- } else {
2154
- console.warn(`Invalid CSP domain ignored: ${domain}`);
2224
+ } else if (!reportedInvalidDomains.has(domain)) {
2225
+ reportedInvalidDomains.add(domain);
2226
+ console.warn(
2227
+ `Invalid CSP domain ignored: ${domain} (expected an https:// or wss:// origin, or http:// / ws:// on localhost)`
2228
+ );
2155
2229
  }
2156
2230
  }
2157
2231
  return valid;