@frontmcp/uipack 1.8.5 → 1.8.6

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.
@@ -105,6 +105,23 @@ function __applySizingCss(sizing) {
105
105
  }
106
106
  }
107
107
 
108
+ function __measureHeight() {
109
+ // <html> at fit-content includes body margins and collapsed child margins;
110
+ // content overflowing a fixed-height body is added; a px max-height clamps.
111
+ var de = document.documentElement;
112
+ var body = document.body;
113
+ if (!de || !body) return 0;
114
+ var prev = de.style.height;
115
+ de.style.height = 'fit-content';
116
+ var dr = de.getBoundingClientRect();
117
+ var br = body.getBoundingClientRect();
118
+ var h = Math.max(dr.height, br.top + body.scrollHeight + Math.max(0, dr.bottom - br.bottom) - dr.top);
119
+ de.style.height = prev;
120
+ var maxHeight = getComputedStyle(de).maxHeight;
121
+ var mx = maxHeight && maxHeight.slice(-2) === 'px' ? parseFloat(maxHeight) : NaN;
122
+ return Math.ceil(isNaN(mx) ? h : Math.min(h, mx));
123
+ }
124
+
108
125
  function __initAutoResize() {
109
126
  if (typeof window === 'undefined') return;
110
127
  // Idempotent: a re-injected IIFE must not stack observers.
@@ -127,23 +144,30 @@ function __initAutoResize() {
127
144
  if (typeof ResizeObserver === 'undefined') return;
128
145
 
129
146
  function startObserving() {
130
- var target = document.getElementById('root') || document.body;
131
- if (!target) return;
147
+ if (!document.body) return;
148
+ var targets = [document.documentElement, document.body];
149
+ var root = document.getElementById('root');
150
+ if (root) targets.push(root);
132
151
 
133
152
  var rafId = null;
134
153
  var lastReported = -1;
135
154
  function report() {
136
155
  rafId = null;
137
156
  try {
138
- var rect = target.getBoundingClientRect();
139
- var height = Math.ceil(rect.height);
140
- var width = Math.ceil(rect.width);
157
+ // Before the handshake an ext-apps host can't receive the report; the
158
+ // bridge:ready listener below sends it once the bridge is up.
159
+ if (window.FrontMcpBridge && window.FrontMcpBridge.initialized === false) return;
160
+ var height = __measureHeight();
161
+ var width = Math.ceil(document.documentElement.getBoundingClientRect().width);
141
162
  if (height === lastReported || height <= 0) return;
142
163
  lastReported = height;
143
164
  var payload = { height: height, width: width };
144
165
  if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
145
166
  if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
146
- window.FrontMcpBridge.setSize(payload).catch(function() {});
167
+ window.FrontMcpBridge.setSize(payload).catch(function() {
168
+ // Forget a rejected report so the same height is sent again.
169
+ if (lastReported === height) lastReported = -1;
170
+ });
147
171
  }
148
172
  window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
149
173
  } catch (e) {}
@@ -164,9 +188,17 @@ function __initAutoResize() {
164
188
  window.__mcpResizeObserver.disconnect();
165
189
  }
166
190
  var ro = new ResizeObserver(function() { schedule(); });
167
- ro.observe(target);
191
+ for (var i = 0; i < targets.length; i++) ro.observe(targets[i]);
168
192
  window.__mcpResizeObserver = ro;
169
193
  } catch (e) {}
194
+ // Report once the bridge is ready (the report scheduled below is skipped
195
+ // while the handshake is still pending).
196
+ var readySent = false;
197
+ window.addEventListener('bridge:ready', function() {
198
+ if (readySent) return;
199
+ readySent = true;
200
+ schedule();
201
+ });
170
202
  // Report once on init so the host gets an initial measurement.
171
203
  schedule();
172
204
  }
@@ -214,6 +246,25 @@ function detectViewport() {
214
246
  return undefined;
215
247
  }
216
248
 
249
+ function __applyHostTheme(theme) {
250
+ // Follow a host-supplied theme: <meta name="color-scheme"> sets the frame's
251
+ // canvas and form controls (a widget's own :root color-scheme still wins),
252
+ // <html data-theme> is a styling hook. The OS fallback is never written.
253
+ if (theme !== 'light' && theme !== 'dark') return;
254
+ if (typeof document === 'undefined' || !document.documentElement) return;
255
+ try {
256
+ var meta = document.querySelector('meta[name="color-scheme"]');
257
+ var head = document.head || document.getElementsByTagName('head')[0];
258
+ if (!meta && head) {
259
+ meta = document.createElement('meta');
260
+ meta.setAttribute('name', 'color-scheme');
261
+ head.appendChild(meta);
262
+ }
263
+ if (meta) meta.setAttribute('content', theme);
264
+ document.documentElement.setAttribute('data-theme', theme);
265
+ } catch (e) {}
266
+ }
267
+
217
268
  function readInjectedData() {
218
269
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
219
270
  if (typeof window !== 'undefined') {
@@ -320,6 +371,8 @@ var ExtAppsAdapter = {
320
371
  pendingRequests: {},
321
372
  requestId: 0,
322
373
  hostCapabilities: {},
374
+ handshakeSettled: false,
375
+ pendingSize: null,
323
376
  canHandle: function() {
324
377
  if (typeof window === 'undefined') return false;
325
378
  if (window.parent === window) return false;
@@ -485,6 +538,7 @@ var ExtAppsAdapter = {
485
538
  var timeout = setTimeout(function() {
486
539
  delete self.pendingRequests[id];
487
540
  // Handshake timeout is non-fatal \u2014 notifications may still arrive
541
+ self.settleHandshake();
488
542
  resolve();
489
543
  }, 10000);
490
544
 
@@ -498,7 +552,9 @@ var ExtAppsAdapter = {
498
552
  supportsDisplayModes: true
499
553
  });
500
554
  if (result.hostContext) {
501
- Object.assign(context.hostContext, result.hostContext);
555
+ // Notify (not a silent merge) so onContextChange listeners and the
556
+ // host theme see the handshake's context.
557
+ context.notifyContextChange(result.hostContext);
502
558
  }
503
559
  // Send ui/notifications/initialized to tell the host the view is ready.
504
560
  // Per MCP Apps spec, the host waits for this before sending tool-result.
@@ -508,9 +564,10 @@ var ExtAppsAdapter = {
508
564
  method: 'ui/notifications/initialized',
509
565
  params: {}
510
566
  }, targetOrigin);
567
+ self.settleHandshake();
511
568
  resolve();
512
569
  },
513
- reject: function(err) { resolve(); }, // Non-fatal
570
+ reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
514
571
  timeout: timeout
515
572
  };
516
573
 
@@ -524,6 +581,18 @@ var ExtAppsAdapter = {
524
581
  performHandshake: function(context) {
525
582
  return this.sendHandshake(context);
526
583
  },
584
+ settleHandshake: function() {
585
+ // Deliver the latest size requested while no host origin was trusted.
586
+ this.handshakeSettled = true;
587
+ var pending = this.pendingSize;
588
+ if (!pending) return;
589
+ this.pendingSize = null;
590
+ this.sendRequest('ui/setSize', pending.params).then(function(result) {
591
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
592
+ }, function(err) {
593
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
594
+ });
595
+ },
527
596
  callTool: function(context, name, args) {
528
597
  if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
529
598
  return Promise.reject(new Error('Server tool proxy not supported'));
@@ -547,11 +616,22 @@ var ExtAppsAdapter = {
547
616
  setSize: function(context, size) {
548
617
  // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
549
618
  // measured/desired widget dimensions to the host.
550
- return this.sendRequest('ui/setSize', {
619
+ var params = {
551
620
  height: size && size.height,
552
621
  width: size && size.width,
553
622
  aspectRatio: size && size.aspectRatio
554
- });
623
+ };
624
+ if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
625
+ // No host origin yet: keep only the latest size and send it when the
626
+ // handshake settles; every caller's promise follows that one request.
627
+ var self = this;
628
+ if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
629
+ this.pendingSize.params = params;
630
+ return new Promise(function(resolve, reject) {
631
+ self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
632
+ });
633
+ }
634
+ return this.sendRequest('ui/setSize', params);
555
635
  },
556
636
  requestClose: function(context) {
557
637
  return this.sendRequest('ui/close', {});
@@ -797,6 +877,7 @@ function FrontMcpBridge() {
797
877
  toolResultListeners: [],
798
878
  notifyContextChange: function(changes) {
799
879
  Object.assign(this.hostContext, changes);
880
+ if (changes && changes.theme) __applyHostTheme(changes.theme);
800
881
  for (var i = 0; i < this.contextListeners.length; i++) {
801
882
  try { this.contextListeners[i](changes); } catch(e) {}
802
883
  }
@@ -469,8 +469,24 @@ function isFrontmcpUiResolvable(...candidatePaths) {
469
469
  }
470
470
 
471
471
  // libs/uipack/src/component/transpiler.ts
472
+ function loadEsbuild(filename) {
473
+ try {
474
+ return __require("esbuild");
475
+ } catch (err) {
476
+ if (!isMissingEsbuild(err)) throw err;
477
+ const subject = filename ? `FileSource widget "${filename}"` : "A FileSource widget";
478
+ throw new Error(
479
+ `${subject} needs esbuild, which is not installed. @frontmcp/uipack loads esbuild on demand to bundle FileSource widgets (.tsx/.jsx) when the tool is called, so it must be installed where the server runs: \`npm install esbuild\` (in "dependencies", not "devDependencies"). Projects created with \`frontmcp create\` already get it through the \`frontmcp\` package.`,
480
+ { cause: err }
481
+ );
482
+ }
483
+ }
484
+ function isMissingEsbuild(err) {
485
+ if (!(err instanceof Error)) return false;
486
+ return err.code === "MODULE_NOT_FOUND" && /Cannot find module ['"]esbuild['"]/.test(err.message);
487
+ }
472
488
  function transpileReactSource(source, filename) {
473
- const esbuild = __require("esbuild");
489
+ const esbuild = loadEsbuild(filename);
474
490
  const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
475
491
  const result = esbuild.transformSync(source, {
476
492
  loader,
@@ -489,7 +505,7 @@ function bundleFileSource(source, filename, resolveDir, componentName, options =
489
505
  `FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`
490
506
  );
491
507
  }
492
- const esbuild = __require("esbuild");
508
+ const esbuild = loadEsbuild(filename);
493
509
  const mountCode = `
494
510
  // --- Auto-generated mount ---
495
511
  import { createElement as __h } from 'react';
@@ -586,7 +602,8 @@ if (__root) {
586
602
  } catch (err) {
587
603
  const message = err instanceof Error ? err.message : String(err);
588
604
  throw new Error(
589
- `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
605
+ `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,
606
+ { cause: err }
590
607
  );
591
608
  }
592
609
  }
@@ -648,6 +665,37 @@ function resolveImportSource(source) {
648
665
  peerDependencies: []
649
666
  };
650
667
  }
668
+ var BUILD_DIR_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "out"]);
669
+ function missingWidgetError(filePath, cause) {
670
+ const sourceWidget = findSourceWidget(filePath);
671
+ const error = new Error(
672
+ `FileSource widget not found (ENOENT): no file at "${filePath}". The widget source is read from this path when the tool is called, so it has to exist where the compiled tool runs \u2014 a path built from \`__dirname\` or \`import.meta.url\` points into the build output once the tool is compiled. tsc does not copy \`*.widget.tsx\` / \`*.widget.jsx\` files into its output; \`frontmcp build\` does, and any other build needs a step that copies them next to the compiled tool.` + (sourceWidget ? ` The source widget exists at "${sourceWidget}" \u2014 copy it to "${filePath}", or build with \`frontmcp build\`.` : ""),
673
+ { cause }
674
+ );
675
+ error.code = "ENOENT";
676
+ error.path = filePath;
677
+ return error;
678
+ }
679
+ function findSourceWidget(filePath) {
680
+ const path = __require("path");
681
+ const fs = __require("fs");
682
+ const parts = filePath.split(path.sep);
683
+ for (let i = parts.length - 2; i >= 0; i--) {
684
+ if (!BUILD_DIR_SEGMENTS.has(parts[i])) continue;
685
+ const candidates = [[...parts.slice(0, i), "src", ...parts.slice(i + 1)]];
686
+ if (parts.length - i > 2) candidates.push([...parts.slice(0, i), "src", ...parts.slice(i + 2)]);
687
+ candidates.push([...parts.slice(0, i), ...parts.slice(i + 1)]);
688
+ for (const candidate of candidates) {
689
+ const candidatePath = candidate.join(path.sep);
690
+ try {
691
+ if (fs.existsSync(candidatePath)) return candidatePath;
692
+ } catch {
693
+ }
694
+ }
695
+ return void 0;
696
+ }
697
+ return void 0;
698
+ }
651
699
  function resolveFileSource(source, options = {}) {
652
700
  const path = __require("path");
653
701
  const ext = path.extname(source.file).toLowerCase();
@@ -662,9 +710,11 @@ function resolveFileSource(source, options = {}) {
662
710
  const isNotFound = err?.code === "ENOENT";
663
711
  if (isNotFound && wasRelative) {
664
712
  throw new Error(
665
- `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`
713
+ `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`,
714
+ { cause: err }
666
715
  );
667
716
  }
717
+ if (isNotFound) throw missingWidgetError(filePath, err);
668
718
  throw err;
669
719
  }
670
720
  const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
@@ -882,6 +932,23 @@ function __applySizingCss(sizing) {
882
932
  }
883
933
  }
884
934
 
935
+ function __measureHeight() {
936
+ // <html> at fit-content includes body margins and collapsed child margins;
937
+ // content overflowing a fixed-height body is added; a px max-height clamps.
938
+ var de = document.documentElement;
939
+ var body = document.body;
940
+ if (!de || !body) return 0;
941
+ var prev = de.style.height;
942
+ de.style.height = 'fit-content';
943
+ var dr = de.getBoundingClientRect();
944
+ var br = body.getBoundingClientRect();
945
+ var h = Math.max(dr.height, br.top + body.scrollHeight + Math.max(0, dr.bottom - br.bottom) - dr.top);
946
+ de.style.height = prev;
947
+ var maxHeight = getComputedStyle(de).maxHeight;
948
+ var mx = maxHeight && maxHeight.slice(-2) === 'px' ? parseFloat(maxHeight) : NaN;
949
+ return Math.ceil(isNaN(mx) ? h : Math.min(h, mx));
950
+ }
951
+
885
952
  function __initAutoResize() {
886
953
  if (typeof window === 'undefined') return;
887
954
  // Idempotent: a re-injected IIFE must not stack observers.
@@ -904,23 +971,30 @@ function __initAutoResize() {
904
971
  if (typeof ResizeObserver === 'undefined') return;
905
972
 
906
973
  function startObserving() {
907
- var target = document.getElementById('root') || document.body;
908
- if (!target) return;
974
+ if (!document.body) return;
975
+ var targets = [document.documentElement, document.body];
976
+ var root = document.getElementById('root');
977
+ if (root) targets.push(root);
909
978
 
910
979
  var rafId = null;
911
980
  var lastReported = -1;
912
981
  function report() {
913
982
  rafId = null;
914
983
  try {
915
- var rect = target.getBoundingClientRect();
916
- var height = Math.ceil(rect.height);
917
- var width = Math.ceil(rect.width);
984
+ // Before the handshake an ext-apps host can't receive the report; the
985
+ // bridge:ready listener below sends it once the bridge is up.
986
+ if (window.FrontMcpBridge && window.FrontMcpBridge.initialized === false) return;
987
+ var height = __measureHeight();
988
+ var width = Math.ceil(document.documentElement.getBoundingClientRect().width);
918
989
  if (height === lastReported || height <= 0) return;
919
990
  lastReported = height;
920
991
  var payload = { height: height, width: width };
921
992
  if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
922
993
  if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
923
- window.FrontMcpBridge.setSize(payload).catch(function() {});
994
+ window.FrontMcpBridge.setSize(payload).catch(function() {
995
+ // Forget a rejected report so the same height is sent again.
996
+ if (lastReported === height) lastReported = -1;
997
+ });
924
998
  }
925
999
  window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
926
1000
  } catch (e) {}
@@ -941,9 +1015,17 @@ function __initAutoResize() {
941
1015
  window.__mcpResizeObserver.disconnect();
942
1016
  }
943
1017
  var ro = new ResizeObserver(function() { schedule(); });
944
- ro.observe(target);
1018
+ for (var i = 0; i < targets.length; i++) ro.observe(targets[i]);
945
1019
  window.__mcpResizeObserver = ro;
946
1020
  } catch (e) {}
1021
+ // Report once the bridge is ready (the report scheduled below is skipped
1022
+ // while the handshake is still pending).
1023
+ var readySent = false;
1024
+ window.addEventListener('bridge:ready', function() {
1025
+ if (readySent) return;
1026
+ readySent = true;
1027
+ schedule();
1028
+ });
947
1029
  // Report once on init so the host gets an initial measurement.
948
1030
  schedule();
949
1031
  }
@@ -991,6 +1073,25 @@ function detectViewport() {
991
1073
  return undefined;
992
1074
  }
993
1075
 
1076
+ function __applyHostTheme(theme) {
1077
+ // Follow a host-supplied theme: <meta name="color-scheme"> sets the frame's
1078
+ // canvas and form controls (a widget's own :root color-scheme still wins),
1079
+ // <html data-theme> is a styling hook. The OS fallback is never written.
1080
+ if (theme !== 'light' && theme !== 'dark') return;
1081
+ if (typeof document === 'undefined' || !document.documentElement) return;
1082
+ try {
1083
+ var meta = document.querySelector('meta[name="color-scheme"]');
1084
+ var head = document.head || document.getElementsByTagName('head')[0];
1085
+ if (!meta && head) {
1086
+ meta = document.createElement('meta');
1087
+ meta.setAttribute('name', 'color-scheme');
1088
+ head.appendChild(meta);
1089
+ }
1090
+ if (meta) meta.setAttribute('content', theme);
1091
+ document.documentElement.setAttribute('data-theme', theme);
1092
+ } catch (e) {}
1093
+ }
1094
+
994
1095
  function readInjectedData() {
995
1096
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
996
1097
  if (typeof window !== 'undefined') {
@@ -1097,6 +1198,8 @@ var ExtAppsAdapter = {
1097
1198
  pendingRequests: {},
1098
1199
  requestId: 0,
1099
1200
  hostCapabilities: {},
1201
+ handshakeSettled: false,
1202
+ pendingSize: null,
1100
1203
  canHandle: function() {
1101
1204
  if (typeof window === 'undefined') return false;
1102
1205
  if (window.parent === window) return false;
@@ -1262,6 +1365,7 @@ var ExtAppsAdapter = {
1262
1365
  var timeout = setTimeout(function() {
1263
1366
  delete self.pendingRequests[id];
1264
1367
  // Handshake timeout is non-fatal \u2014 notifications may still arrive
1368
+ self.settleHandshake();
1265
1369
  resolve();
1266
1370
  }, 10000);
1267
1371
 
@@ -1275,7 +1379,9 @@ var ExtAppsAdapter = {
1275
1379
  supportsDisplayModes: true
1276
1380
  });
1277
1381
  if (result.hostContext) {
1278
- Object.assign(context.hostContext, result.hostContext);
1382
+ // Notify (not a silent merge) so onContextChange listeners and the
1383
+ // host theme see the handshake's context.
1384
+ context.notifyContextChange(result.hostContext);
1279
1385
  }
1280
1386
  // Send ui/notifications/initialized to tell the host the view is ready.
1281
1387
  // Per MCP Apps spec, the host waits for this before sending tool-result.
@@ -1285,9 +1391,10 @@ var ExtAppsAdapter = {
1285
1391
  method: 'ui/notifications/initialized',
1286
1392
  params: {}
1287
1393
  }, targetOrigin);
1394
+ self.settleHandshake();
1288
1395
  resolve();
1289
1396
  },
1290
- reject: function(err) { resolve(); }, // Non-fatal
1397
+ reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
1291
1398
  timeout: timeout
1292
1399
  };
1293
1400
 
@@ -1301,6 +1408,18 @@ var ExtAppsAdapter = {
1301
1408
  performHandshake: function(context) {
1302
1409
  return this.sendHandshake(context);
1303
1410
  },
1411
+ settleHandshake: function() {
1412
+ // Deliver the latest size requested while no host origin was trusted.
1413
+ this.handshakeSettled = true;
1414
+ var pending = this.pendingSize;
1415
+ if (!pending) return;
1416
+ this.pendingSize = null;
1417
+ this.sendRequest('ui/setSize', pending.params).then(function(result) {
1418
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
1419
+ }, function(err) {
1420
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
1421
+ });
1422
+ },
1304
1423
  callTool: function(context, name, args) {
1305
1424
  if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
1306
1425
  return Promise.reject(new Error('Server tool proxy not supported'));
@@ -1324,11 +1443,22 @@ var ExtAppsAdapter = {
1324
1443
  setSize: function(context, size) {
1325
1444
  // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1326
1445
  // measured/desired widget dimensions to the host.
1327
- return this.sendRequest('ui/setSize', {
1446
+ var params = {
1328
1447
  height: size && size.height,
1329
1448
  width: size && size.width,
1330
1449
  aspectRatio: size && size.aspectRatio
1331
- });
1450
+ };
1451
+ if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
1452
+ // No host origin yet: keep only the latest size and send it when the
1453
+ // handshake settles; every caller's promise follows that one request.
1454
+ var self = this;
1455
+ if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
1456
+ this.pendingSize.params = params;
1457
+ return new Promise(function(resolve, reject) {
1458
+ self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
1459
+ });
1460
+ }
1461
+ return this.sendRequest('ui/setSize', params);
1332
1462
  },
1333
1463
  requestClose: function(context) {
1334
1464
  return this.sendRequest('ui/close', {});
@@ -1574,6 +1704,7 @@ function FrontMcpBridge() {
1574
1704
  toolResultListeners: [],
1575
1705
  notifyContextChange: function(changes) {
1576
1706
  Object.assign(this.hostContext, changes);
1707
+ if (changes && changes.theme) __applyHostTheme(changes.theme);
1577
1708
  for (var i = 0; i < this.contextListeners.length; i++) {
1578
1709
  try { this.contextListeners[i](changes); } catch(e) {}
1579
1710
  }