@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
|
@@ -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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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(
|
|
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,12 +246,57 @@ 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
|
+
|
|
268
|
+
function extractToolOutput(params) {
|
|
269
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
270
|
+
if (!params) return undefined;
|
|
271
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
272
|
+
var content = params.content;
|
|
273
|
+
if (Array.isArray(content)) {
|
|
274
|
+
for (var i = 0; i < content.length; i++) {
|
|
275
|
+
var item = content[i];
|
|
276
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
277
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return content;
|
|
282
|
+
}
|
|
283
|
+
|
|
217
284
|
function readInjectedData() {
|
|
218
285
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
219
286
|
if (typeof window !== 'undefined') {
|
|
220
287
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
221
288
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
222
289
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
290
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
291
|
+
// (toolOutput is the tool's structuredContent).
|
|
292
|
+
var oa = window.openai;
|
|
293
|
+
if (oa && typeof oa === 'object') {
|
|
294
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
295
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
296
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
297
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
223
300
|
}
|
|
224
301
|
return data;
|
|
225
302
|
}
|
|
@@ -268,7 +345,24 @@ var OpenAIAdapter = {
|
|
|
268
345
|
if (sdk.displayMode) {
|
|
269
346
|
context.hostContext.displayMode = sdk.displayMode;
|
|
270
347
|
}
|
|
271
|
-
//
|
|
348
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
349
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
350
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
351
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
352
|
+
context.toolInput = globals.toolInput;
|
|
353
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
354
|
+
}
|
|
355
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
356
|
+
context.structuredContent = globals.toolOutput;
|
|
357
|
+
context.notifyToolResult(globals.toolOutput);
|
|
358
|
+
}
|
|
359
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
360
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
361
|
+
}
|
|
362
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
363
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
272
366
|
return Promise.resolve();
|
|
273
367
|
},
|
|
274
368
|
callTool: function(context, name, args) {
|
|
@@ -320,6 +414,8 @@ var ExtAppsAdapter = {
|
|
|
320
414
|
pendingRequests: {},
|
|
321
415
|
requestId: 0,
|
|
322
416
|
hostCapabilities: {},
|
|
417
|
+
handshakeSettled: false,
|
|
418
|
+
pendingSize: null,
|
|
323
419
|
canHandle: function() {
|
|
324
420
|
if (typeof window === 'undefined') return false;
|
|
325
421
|
if (window.parent === window) return false;
|
|
@@ -413,9 +509,10 @@ var ExtAppsAdapter = {
|
|
|
413
509
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
414
510
|
break;
|
|
415
511
|
case 'ui/notifications/tool-result':
|
|
416
|
-
|
|
512
|
+
var resultOutput = extractToolOutput(params);
|
|
513
|
+
context.toolOutput = resultOutput;
|
|
417
514
|
context.structuredContent = params.structuredContent;
|
|
418
|
-
context.notifyToolResult(
|
|
515
|
+
context.notifyToolResult(resultOutput);
|
|
419
516
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
420
517
|
break;
|
|
421
518
|
case 'ui/notifications/host-context-changed':
|
|
@@ -469,6 +566,15 @@ var ExtAppsAdapter = {
|
|
|
469
566
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
470
567
|
});
|
|
471
568
|
},
|
|
569
|
+
sendNotification: function(method, params) {
|
|
570
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
571
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
572
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
573
|
+
}
|
|
574
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
575
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
576
|
+
return Promise.resolve();
|
|
577
|
+
},
|
|
472
578
|
sendHandshake: function(context) {
|
|
473
579
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
474
580
|
// been established yet. The response from the host will establish
|
|
@@ -485,6 +591,7 @@ var ExtAppsAdapter = {
|
|
|
485
591
|
var timeout = setTimeout(function() {
|
|
486
592
|
delete self.pendingRequests[id];
|
|
487
593
|
// Handshake timeout is non-fatal \u2014 notifications may still arrive
|
|
594
|
+
self.settleHandshake();
|
|
488
595
|
resolve();
|
|
489
596
|
}, 10000);
|
|
490
597
|
|
|
@@ -498,7 +605,9 @@ var ExtAppsAdapter = {
|
|
|
498
605
|
supportsDisplayModes: true
|
|
499
606
|
});
|
|
500
607
|
if (result.hostContext) {
|
|
501
|
-
|
|
608
|
+
// Notify (not a silent merge) so onContextChange listeners and the
|
|
609
|
+
// host theme see the handshake's context.
|
|
610
|
+
context.notifyContextChange(result.hostContext);
|
|
502
611
|
}
|
|
503
612
|
// Send ui/notifications/initialized to tell the host the view is ready.
|
|
504
613
|
// Per MCP Apps spec, the host waits for this before sending tool-result.
|
|
@@ -508,9 +617,10 @@ var ExtAppsAdapter = {
|
|
|
508
617
|
method: 'ui/notifications/initialized',
|
|
509
618
|
params: {}
|
|
510
619
|
}, targetOrigin);
|
|
620
|
+
self.settleHandshake();
|
|
511
621
|
resolve();
|
|
512
622
|
},
|
|
513
|
-
reject: function(err) { resolve(); }, // Non-fatal
|
|
623
|
+
reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
|
|
514
624
|
timeout: timeout
|
|
515
625
|
};
|
|
516
626
|
|
|
@@ -524,6 +634,18 @@ var ExtAppsAdapter = {
|
|
|
524
634
|
performHandshake: function(context) {
|
|
525
635
|
return this.sendHandshake(context);
|
|
526
636
|
},
|
|
637
|
+
settleHandshake: function() {
|
|
638
|
+
// Deliver the latest size requested while no host origin was trusted.
|
|
639
|
+
this.handshakeSettled = true;
|
|
640
|
+
var pending = this.pendingSize;
|
|
641
|
+
if (!pending) return;
|
|
642
|
+
this.pendingSize = null;
|
|
643
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
644
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
645
|
+
}, function(err) {
|
|
646
|
+
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
647
|
+
});
|
|
648
|
+
},
|
|
527
649
|
callTool: function(context, name, args) {
|
|
528
650
|
if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
|
|
529
651
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
@@ -545,13 +667,22 @@ var ExtAppsAdapter = {
|
|
|
545
667
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
546
668
|
},
|
|
547
669
|
setSize: function(context, size) {
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
670
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
671
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
672
|
+
var params = {};
|
|
673
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
674
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
675
|
+
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
676
|
+
// No host origin yet: keep only the latest size and send it when the
|
|
677
|
+
// handshake settles; every caller's promise follows that one request.
|
|
678
|
+
var self = this;
|
|
679
|
+
if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
|
|
680
|
+
this.pendingSize.params = params;
|
|
681
|
+
return new Promise(function(resolve, reject) {
|
|
682
|
+
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
555
686
|
},
|
|
556
687
|
requestClose: function(context) {
|
|
557
688
|
return this.sendRequest('ui/close', {});
|
|
@@ -797,6 +928,7 @@ function FrontMcpBridge() {
|
|
|
797
928
|
toolResultListeners: [],
|
|
798
929
|
notifyContextChange: function(changes) {
|
|
799
930
|
Object.assign(this.hostContext, changes);
|
|
931
|
+
if (changes && changes.theme) __applyHostTheme(changes.theme);
|
|
800
932
|
for (var i = 0; i < this.contextListeners.length; i++) {
|
|
801
933
|
try { this.contextListeners[i](changes); } catch(e) {}
|
|
802
934
|
}
|
|
@@ -973,7 +1105,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
973
1105
|
|
|
974
1106
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
975
1107
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
976
|
-
// ext-apps sends ui/
|
|
1108
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
977
1109
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
978
1110
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
979
1111
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1075,7 +1207,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1075
1207
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1076
1208
|
|
|
1077
1209
|
// Add spinner for buttons
|
|
1078
|
-
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>';
|
|
1210
|
+
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>';
|
|
1079
1211
|
if (target.tagName === 'BUTTON') {
|
|
1080
1212
|
target.innerHTML = spinner + 'Loading...';
|
|
1081
1213
|
}
|
|
@@ -1156,7 +1288,22 @@ function minifyJS(code) {
|
|
|
1156
1288
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1157
1289
|
return code;
|
|
1158
1290
|
}
|
|
1159
|
-
|
|
1291
|
+
const out = [];
|
|
1292
|
+
let inBlockComment = false;
|
|
1293
|
+
for (const raw of code.split("\n")) {
|
|
1294
|
+
const line = raw.trim();
|
|
1295
|
+
if (inBlockComment) {
|
|
1296
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1300
|
+
if (line.startsWith("/*")) {
|
|
1301
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
out.push(line);
|
|
1305
|
+
}
|
|
1306
|
+
return out.join("\n");
|
|
1160
1307
|
}
|
|
1161
1308
|
function generatePlatformBundle(platform, options = {}) {
|
|
1162
1309
|
const platformAdapters = {
|