@frontmcp/uipack 1.8.5 → 1.8.7
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.
- package/adapters/index.js +339 -35
- package/adapters/markdown.d.ts +9 -0
- package/adapters/markdown.d.ts.map +1 -0
- package/adapters/template-renderer.d.ts +8 -0
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +169 -22
- package/component/index.js +223 -26
- package/component/loader.d.ts.map +1 -1
- package/component/transpiler.d.ts.map +1 -1
- package/esm/adapters/index.mjs +339 -35
- package/esm/bridge-runtime/index.mjs +169 -22
- package/esm/component/index.mjs +223 -26
- package/esm/index.mjs +339 -35
- package/esm/package.json +8 -2
- package/esm/shell/index.mjs +169 -22
- package/index.js +339 -35
- package/package.json +8 -2
- package/shell/index.js +169 -22
- package/types/ui-config.d.ts +5 -3
- package/types/ui-config.d.ts.map +1 -1
package/bridge-runtime/index.js
CHANGED
|
@@ -134,6 +134,23 @@ function __applySizingCss(sizing) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
function __measureHeight() {
|
|
138
|
+
// <html> at fit-content includes body margins and collapsed child margins;
|
|
139
|
+
// content overflowing a fixed-height body is added; a px max-height clamps.
|
|
140
|
+
var de = document.documentElement;
|
|
141
|
+
var body = document.body;
|
|
142
|
+
if (!de || !body) return 0;
|
|
143
|
+
var prev = de.style.height;
|
|
144
|
+
de.style.height = 'fit-content';
|
|
145
|
+
var dr = de.getBoundingClientRect();
|
|
146
|
+
var br = body.getBoundingClientRect();
|
|
147
|
+
var h = Math.max(dr.height, br.top + body.scrollHeight + Math.max(0, dr.bottom - br.bottom) - dr.top);
|
|
148
|
+
de.style.height = prev;
|
|
149
|
+
var maxHeight = getComputedStyle(de).maxHeight;
|
|
150
|
+
var mx = maxHeight && maxHeight.slice(-2) === 'px' ? parseFloat(maxHeight) : NaN;
|
|
151
|
+
return Math.ceil(isNaN(mx) ? h : Math.min(h, mx));
|
|
152
|
+
}
|
|
153
|
+
|
|
137
154
|
function __initAutoResize() {
|
|
138
155
|
if (typeof window === 'undefined') return;
|
|
139
156
|
// Idempotent: a re-injected IIFE must not stack observers.
|
|
@@ -156,23 +173,30 @@ function __initAutoResize() {
|
|
|
156
173
|
if (typeof ResizeObserver === 'undefined') return;
|
|
157
174
|
|
|
158
175
|
function startObserving() {
|
|
159
|
-
|
|
160
|
-
|
|
176
|
+
if (!document.body) return;
|
|
177
|
+
var targets = [document.documentElement, document.body];
|
|
178
|
+
var root = document.getElementById('root');
|
|
179
|
+
if (root) targets.push(root);
|
|
161
180
|
|
|
162
181
|
var rafId = null;
|
|
163
182
|
var lastReported = -1;
|
|
164
183
|
function report() {
|
|
165
184
|
rafId = null;
|
|
166
185
|
try {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
186
|
+
// Before the handshake an ext-apps host can't receive the report; the
|
|
187
|
+
// bridge:ready listener below sends it once the bridge is up.
|
|
188
|
+
if (window.FrontMcpBridge && window.FrontMcpBridge.initialized === false) return;
|
|
189
|
+
var height = __measureHeight();
|
|
190
|
+
var width = Math.ceil(document.documentElement.getBoundingClientRect().width);
|
|
170
191
|
if (height === lastReported || height <= 0) return;
|
|
171
192
|
lastReported = height;
|
|
172
193
|
var payload = { height: height, width: width };
|
|
173
194
|
if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
|
|
174
195
|
if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
|
|
175
|
-
window.FrontMcpBridge.setSize(payload).catch(function() {
|
|
196
|
+
window.FrontMcpBridge.setSize(payload).catch(function() {
|
|
197
|
+
// Forget a rejected report so the same height is sent again.
|
|
198
|
+
if (lastReported === height) lastReported = -1;
|
|
199
|
+
});
|
|
176
200
|
}
|
|
177
201
|
window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
|
|
178
202
|
} catch (e) {}
|
|
@@ -193,9 +217,17 @@ function __initAutoResize() {
|
|
|
193
217
|
window.__mcpResizeObserver.disconnect();
|
|
194
218
|
}
|
|
195
219
|
var ro = new ResizeObserver(function() { schedule(); });
|
|
196
|
-
ro.observe(
|
|
220
|
+
for (var i = 0; i < targets.length; i++) ro.observe(targets[i]);
|
|
197
221
|
window.__mcpResizeObserver = ro;
|
|
198
222
|
} catch (e) {}
|
|
223
|
+
// Report once the bridge is ready (the report scheduled below is skipped
|
|
224
|
+
// while the handshake is still pending).
|
|
225
|
+
var readySent = false;
|
|
226
|
+
window.addEventListener('bridge:ready', function() {
|
|
227
|
+
if (readySent) return;
|
|
228
|
+
readySent = true;
|
|
229
|
+
schedule();
|
|
230
|
+
});
|
|
199
231
|
// Report once on init so the host gets an initial measurement.
|
|
200
232
|
schedule();
|
|
201
233
|
}
|
|
@@ -243,12 +275,57 @@ function detectViewport() {
|
|
|
243
275
|
return undefined;
|
|
244
276
|
}
|
|
245
277
|
|
|
278
|
+
function __applyHostTheme(theme) {
|
|
279
|
+
// Follow a host-supplied theme: <meta name="color-scheme"> sets the frame's
|
|
280
|
+
// canvas and form controls (a widget's own :root color-scheme still wins),
|
|
281
|
+
// <html data-theme> is a styling hook. The OS fallback is never written.
|
|
282
|
+
if (theme !== 'light' && theme !== 'dark') return;
|
|
283
|
+
if (typeof document === 'undefined' || !document.documentElement) return;
|
|
284
|
+
try {
|
|
285
|
+
var meta = document.querySelector('meta[name="color-scheme"]');
|
|
286
|
+
var head = document.head || document.getElementsByTagName('head')[0];
|
|
287
|
+
if (!meta && head) {
|
|
288
|
+
meta = document.createElement('meta');
|
|
289
|
+
meta.setAttribute('name', 'color-scheme');
|
|
290
|
+
head.appendChild(meta);
|
|
291
|
+
}
|
|
292
|
+
if (meta) meta.setAttribute('content', theme);
|
|
293
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
294
|
+
} catch (e) {}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function extractToolOutput(params) {
|
|
298
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
299
|
+
if (!params) return undefined;
|
|
300
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
301
|
+
var content = params.content;
|
|
302
|
+
if (Array.isArray(content)) {
|
|
303
|
+
for (var i = 0; i < content.length; i++) {
|
|
304
|
+
var item = content[i];
|
|
305
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
306
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return content;
|
|
311
|
+
}
|
|
312
|
+
|
|
246
313
|
function readInjectedData() {
|
|
247
314
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
248
315
|
if (typeof window !== 'undefined') {
|
|
249
316
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
250
317
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
251
318
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
319
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
320
|
+
// (toolOutput is the tool's structuredContent).
|
|
321
|
+
var oa = window.openai;
|
|
322
|
+
if (oa && typeof oa === 'object') {
|
|
323
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
324
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
325
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
326
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
252
329
|
}
|
|
253
330
|
return data;
|
|
254
331
|
}
|
|
@@ -297,7 +374,24 @@ var OpenAIAdapter = {
|
|
|
297
374
|
if (sdk.displayMode) {
|
|
298
375
|
context.hostContext.displayMode = sdk.displayMode;
|
|
299
376
|
}
|
|
300
|
-
//
|
|
377
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
378
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
379
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
380
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
381
|
+
context.toolInput = globals.toolInput;
|
|
382
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
383
|
+
}
|
|
384
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
385
|
+
context.structuredContent = globals.toolOutput;
|
|
386
|
+
context.notifyToolResult(globals.toolOutput);
|
|
387
|
+
}
|
|
388
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
389
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
390
|
+
}
|
|
391
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
392
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
301
395
|
return Promise.resolve();
|
|
302
396
|
},
|
|
303
397
|
callTool: function(context, name, args) {
|
|
@@ -349,6 +443,8 @@ var ExtAppsAdapter = {
|
|
|
349
443
|
pendingRequests: {},
|
|
350
444
|
requestId: 0,
|
|
351
445
|
hostCapabilities: {},
|
|
446
|
+
handshakeSettled: false,
|
|
447
|
+
pendingSize: null,
|
|
352
448
|
canHandle: function() {
|
|
353
449
|
if (typeof window === 'undefined') return false;
|
|
354
450
|
if (window.parent === window) return false;
|
|
@@ -442,9 +538,10 @@ var ExtAppsAdapter = {
|
|
|
442
538
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
443
539
|
break;
|
|
444
540
|
case 'ui/notifications/tool-result':
|
|
445
|
-
|
|
541
|
+
var resultOutput = extractToolOutput(params);
|
|
542
|
+
context.toolOutput = resultOutput;
|
|
446
543
|
context.structuredContent = params.structuredContent;
|
|
447
|
-
context.notifyToolResult(
|
|
544
|
+
context.notifyToolResult(resultOutput);
|
|
448
545
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
449
546
|
break;
|
|
450
547
|
case 'ui/notifications/host-context-changed':
|
|
@@ -498,6 +595,15 @@ var ExtAppsAdapter = {
|
|
|
498
595
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
499
596
|
});
|
|
500
597
|
},
|
|
598
|
+
sendNotification: function(method, params) {
|
|
599
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
600
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
601
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
602
|
+
}
|
|
603
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
604
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
605
|
+
return Promise.resolve();
|
|
606
|
+
},
|
|
501
607
|
sendHandshake: function(context) {
|
|
502
608
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
503
609
|
// been established yet. The response from the host will establish
|
|
@@ -514,6 +620,7 @@ var ExtAppsAdapter = {
|
|
|
514
620
|
var timeout = setTimeout(function() {
|
|
515
621
|
delete self.pendingRequests[id];
|
|
516
622
|
// Handshake timeout is non-fatal \u2014 notifications may still arrive
|
|
623
|
+
self.settleHandshake();
|
|
517
624
|
resolve();
|
|
518
625
|
}, 10000);
|
|
519
626
|
|
|
@@ -527,7 +634,9 @@ var ExtAppsAdapter = {
|
|
|
527
634
|
supportsDisplayModes: true
|
|
528
635
|
});
|
|
529
636
|
if (result.hostContext) {
|
|
530
|
-
|
|
637
|
+
// Notify (not a silent merge) so onContextChange listeners and the
|
|
638
|
+
// host theme see the handshake's context.
|
|
639
|
+
context.notifyContextChange(result.hostContext);
|
|
531
640
|
}
|
|
532
641
|
// Send ui/notifications/initialized to tell the host the view is ready.
|
|
533
642
|
// Per MCP Apps spec, the host waits for this before sending tool-result.
|
|
@@ -537,9 +646,10 @@ var ExtAppsAdapter = {
|
|
|
537
646
|
method: 'ui/notifications/initialized',
|
|
538
647
|
params: {}
|
|
539
648
|
}, targetOrigin);
|
|
649
|
+
self.settleHandshake();
|
|
540
650
|
resolve();
|
|
541
651
|
},
|
|
542
|
-
reject: function(err) { resolve(); }, // Non-fatal
|
|
652
|
+
reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
|
|
543
653
|
timeout: timeout
|
|
544
654
|
};
|
|
545
655
|
|
|
@@ -553,6 +663,18 @@ var ExtAppsAdapter = {
|
|
|
553
663
|
performHandshake: function(context) {
|
|
554
664
|
return this.sendHandshake(context);
|
|
555
665
|
},
|
|
666
|
+
settleHandshake: function() {
|
|
667
|
+
// Deliver the latest size requested while no host origin was trusted.
|
|
668
|
+
this.handshakeSettled = true;
|
|
669
|
+
var pending = this.pendingSize;
|
|
670
|
+
if (!pending) return;
|
|
671
|
+
this.pendingSize = null;
|
|
672
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
673
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
674
|
+
}, function(err) {
|
|
675
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
676
|
+
});
|
|
677
|
+
},
|
|
556
678
|
callTool: function(context, name, args) {
|
|
557
679
|
if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
|
|
558
680
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
@@ -574,13 +696,22 @@ var ExtAppsAdapter = {
|
|
|
574
696
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
575
697
|
},
|
|
576
698
|
setSize: function(context, size) {
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
699
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
700
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
701
|
+
var params = {};
|
|
702
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
703
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
704
|
+
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
705
|
+
// No host origin yet: keep only the latest size and send it when the
|
|
706
|
+
// handshake settles; every caller's promise follows that one request.
|
|
707
|
+
var self = this;
|
|
708
|
+
if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
|
|
709
|
+
this.pendingSize.params = params;
|
|
710
|
+
return new Promise(function(resolve, reject) {
|
|
711
|
+
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
584
715
|
},
|
|
585
716
|
requestClose: function(context) {
|
|
586
717
|
return this.sendRequest('ui/close', {});
|
|
@@ -826,6 +957,7 @@ function FrontMcpBridge() {
|
|
|
826
957
|
toolResultListeners: [],
|
|
827
958
|
notifyContextChange: function(changes) {
|
|
828
959
|
Object.assign(this.hostContext, changes);
|
|
960
|
+
if (changes && changes.theme) __applyHostTheme(changes.theme);
|
|
829
961
|
for (var i = 0; i < this.contextListeners.length; i++) {
|
|
830
962
|
try { this.contextListeners[i](changes); } catch(e) {}
|
|
831
963
|
}
|
|
@@ -1002,7 +1134,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1002
1134
|
|
|
1003
1135
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1004
1136
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1005
|
-
// ext-apps sends ui/
|
|
1137
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1006
1138
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1007
1139
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1008
1140
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1104,7 +1236,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1104
1236
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1105
1237
|
|
|
1106
1238
|
// Add spinner for buttons
|
|
1107
|
-
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>';
|
|
1239
|
+
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>';
|
|
1108
1240
|
if (target.tagName === 'BUTTON') {
|
|
1109
1241
|
target.innerHTML = spinner + 'Loading...';
|
|
1110
1242
|
}
|
|
@@ -1185,7 +1317,22 @@ function minifyJS(code) {
|
|
|
1185
1317
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1186
1318
|
return code;
|
|
1187
1319
|
}
|
|
1188
|
-
|
|
1320
|
+
const out = [];
|
|
1321
|
+
let inBlockComment = false;
|
|
1322
|
+
for (const raw of code.split("\n")) {
|
|
1323
|
+
const line = raw.trim();
|
|
1324
|
+
if (inBlockComment) {
|
|
1325
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1329
|
+
if (line.startsWith("/*")) {
|
|
1330
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
out.push(line);
|
|
1334
|
+
}
|
|
1335
|
+
return out.join("\n");
|
|
1189
1336
|
}
|
|
1190
1337
|
function generatePlatformBundle(platform, options = {}) {
|
|
1191
1338
|
const platformAdapters = {
|