@frontmcp/uipack 1.8.6 → 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 +197 -24
- 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 +81 -15
- package/component/index.js +81 -15
- package/esm/adapters/index.mjs +197 -24
- package/esm/bridge-runtime/index.mjs +81 -15
- package/esm/component/index.mjs +81 -15
- package/esm/index.mjs +197 -24
- package/esm/package.json +2 -2
- package/esm/shell/index.mjs +81 -15
- package/index.js +197 -24
- package/package.json +2 -2
- package/shell/index.js +81 -15
- package/types/ui-config.d.ts +1 -1
package/esm/index.mjs
CHANGED
|
@@ -398,12 +398,38 @@ function __applyHostTheme(theme) {
|
|
|
398
398
|
} catch (e) {}
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
+
function extractToolOutput(params) {
|
|
402
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
403
|
+
if (!params) return undefined;
|
|
404
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
405
|
+
var content = params.content;
|
|
406
|
+
if (Array.isArray(content)) {
|
|
407
|
+
for (var i = 0; i < content.length; i++) {
|
|
408
|
+
var item = content[i];
|
|
409
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
410
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return content;
|
|
415
|
+
}
|
|
416
|
+
|
|
401
417
|
function readInjectedData() {
|
|
402
418
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
403
419
|
if (typeof window !== 'undefined') {
|
|
404
420
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
405
421
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
406
422
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
423
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
424
|
+
// (toolOutput is the tool's structuredContent).
|
|
425
|
+
var oa = window.openai;
|
|
426
|
+
if (oa && typeof oa === 'object') {
|
|
427
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
428
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
429
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
430
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
407
433
|
}
|
|
408
434
|
return data;
|
|
409
435
|
}
|
|
@@ -452,7 +478,24 @@ var OpenAIAdapter = {
|
|
|
452
478
|
if (sdk.displayMode) {
|
|
453
479
|
context.hostContext.displayMode = sdk.displayMode;
|
|
454
480
|
}
|
|
455
|
-
//
|
|
481
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
482
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
483
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
484
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
485
|
+
context.toolInput = globals.toolInput;
|
|
486
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
487
|
+
}
|
|
488
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
489
|
+
context.structuredContent = globals.toolOutput;
|
|
490
|
+
context.notifyToolResult(globals.toolOutput);
|
|
491
|
+
}
|
|
492
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
493
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
494
|
+
}
|
|
495
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
496
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
497
|
+
}
|
|
498
|
+
});
|
|
456
499
|
return Promise.resolve();
|
|
457
500
|
},
|
|
458
501
|
callTool: function(context, name, args) {
|
|
@@ -599,9 +642,10 @@ var ExtAppsAdapter = {
|
|
|
599
642
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
600
643
|
break;
|
|
601
644
|
case 'ui/notifications/tool-result':
|
|
602
|
-
|
|
645
|
+
var resultOutput = extractToolOutput(params);
|
|
646
|
+
context.toolOutput = resultOutput;
|
|
603
647
|
context.structuredContent = params.structuredContent;
|
|
604
|
-
context.notifyToolResult(
|
|
648
|
+
context.notifyToolResult(resultOutput);
|
|
605
649
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
606
650
|
break;
|
|
607
651
|
case 'ui/notifications/host-context-changed':
|
|
@@ -655,6 +699,15 @@ var ExtAppsAdapter = {
|
|
|
655
699
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
656
700
|
});
|
|
657
701
|
},
|
|
702
|
+
sendNotification: function(method, params) {
|
|
703
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
704
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
705
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
706
|
+
}
|
|
707
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
708
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
709
|
+
return Promise.resolve();
|
|
710
|
+
},
|
|
658
711
|
sendHandshake: function(context) {
|
|
659
712
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
660
713
|
// been established yet. The response from the host will establish
|
|
@@ -720,7 +773,7 @@ var ExtAppsAdapter = {
|
|
|
720
773
|
var pending = this.pendingSize;
|
|
721
774
|
if (!pending) return;
|
|
722
775
|
this.pendingSize = null;
|
|
723
|
-
this.
|
|
776
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
724
777
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
725
778
|
}, function(err) {
|
|
726
779
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -747,13 +800,11 @@ var ExtAppsAdapter = {
|
|
|
747
800
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
748
801
|
},
|
|
749
802
|
setSize: function(context, size) {
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
var params = {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
aspectRatio: size && size.aspectRatio
|
|
756
|
-
};
|
|
803
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
804
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
805
|
+
var params = {};
|
|
806
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
807
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
757
808
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
758
809
|
// No host origin yet: keep only the latest size and send it when the
|
|
759
810
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -764,7 +815,7 @@ var ExtAppsAdapter = {
|
|
|
764
815
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
765
816
|
});
|
|
766
817
|
}
|
|
767
|
-
return this.
|
|
818
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
768
819
|
},
|
|
769
820
|
requestClose: function(context) {
|
|
770
821
|
return this.sendRequest('ui/close', {});
|
|
@@ -1187,7 +1238,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1187
1238
|
|
|
1188
1239
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1189
1240
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1190
|
-
// ext-apps sends ui/
|
|
1241
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1191
1242
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1192
1243
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1193
1244
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1289,7 +1340,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1289
1340
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1290
1341
|
|
|
1291
1342
|
// Add spinner for buttons
|
|
1292
|
-
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>';
|
|
1343
|
+
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>';
|
|
1293
1344
|
if (target.tagName === 'BUTTON') {
|
|
1294
1345
|
target.innerHTML = spinner + 'Loading...';
|
|
1295
1346
|
}
|
|
@@ -1370,7 +1421,22 @@ function minifyJS(code) {
|
|
|
1370
1421
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1371
1422
|
return code;
|
|
1372
1423
|
}
|
|
1373
|
-
|
|
1424
|
+
const out = [];
|
|
1425
|
+
let inBlockComment = false;
|
|
1426
|
+
for (const raw of code.split("\n")) {
|
|
1427
|
+
const line = raw.trim();
|
|
1428
|
+
if (inBlockComment) {
|
|
1429
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1433
|
+
if (line.startsWith("/*")) {
|
|
1434
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
out.push(line);
|
|
1438
|
+
}
|
|
1439
|
+
return out.join("\n");
|
|
1374
1440
|
}
|
|
1375
1441
|
function generatePlatformBundle(platform, options = {}) {
|
|
1376
1442
|
const platformAdapters = {
|
|
@@ -4102,6 +4168,86 @@ function wrapDetectedContent(value) {
|
|
|
4102
4168
|
}
|
|
4103
4169
|
}
|
|
4104
4170
|
|
|
4171
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
4172
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
4173
|
+
function emphasis(text) {
|
|
4174
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
4175
|
+
}
|
|
4176
|
+
function inline(text) {
|
|
4177
|
+
const stash = [];
|
|
4178
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
4179
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
4180
|
+
/`([^`]+)`/g,
|
|
4181
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
4182
|
+
);
|
|
4183
|
+
out = out.replace(
|
|
4184
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
4185
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
4186
|
+
);
|
|
4187
|
+
out = emphasis(out);
|
|
4188
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
4189
|
+
return out;
|
|
4190
|
+
}
|
|
4191
|
+
function markdownToHtml(source) {
|
|
4192
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
4193
|
+
const blocks = [];
|
|
4194
|
+
let paragraph = [];
|
|
4195
|
+
let list;
|
|
4196
|
+
const flushParagraph = () => {
|
|
4197
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
4198
|
+
paragraph = [];
|
|
4199
|
+
};
|
|
4200
|
+
const flushList = () => {
|
|
4201
|
+
if (list)
|
|
4202
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
4203
|
+
list = void 0;
|
|
4204
|
+
};
|
|
4205
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4206
|
+
const line = lines[i];
|
|
4207
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
4208
|
+
if (fence) {
|
|
4209
|
+
flushParagraph();
|
|
4210
|
+
flushList();
|
|
4211
|
+
const fenceLength = fence[1].length;
|
|
4212
|
+
const isClosing = (l) => {
|
|
4213
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
4214
|
+
return m !== null && m[1].length >= fenceLength;
|
|
4215
|
+
};
|
|
4216
|
+
const code = [];
|
|
4217
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
4218
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
4219
|
+
</code></pre>`);
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
4223
|
+
if (heading) {
|
|
4224
|
+
flushParagraph();
|
|
4225
|
+
flushList();
|
|
4226
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
4227
|
+
continue;
|
|
4228
|
+
}
|
|
4229
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
4230
|
+
if (item) {
|
|
4231
|
+
flushParagraph();
|
|
4232
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
4233
|
+
if (list && list.tag !== tag) flushList();
|
|
4234
|
+
list ??= { tag, items: [] };
|
|
4235
|
+
list.items.push(item[2]);
|
|
4236
|
+
continue;
|
|
4237
|
+
}
|
|
4238
|
+
if (line.trim() === "") {
|
|
4239
|
+
flushParagraph();
|
|
4240
|
+
flushList();
|
|
4241
|
+
continue;
|
|
4242
|
+
}
|
|
4243
|
+
flushList();
|
|
4244
|
+
paragraph.push(line.trim());
|
|
4245
|
+
}
|
|
4246
|
+
flushParagraph();
|
|
4247
|
+
flushList();
|
|
4248
|
+
return blocks.join("\n");
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4105
4251
|
// libs/uipack/src/adapters/type-detector.ts
|
|
4106
4252
|
function detectUIType(template) {
|
|
4107
4253
|
if (template === null || template === void 0) {
|
|
@@ -4135,9 +4281,15 @@ function detectUIType(template) {
|
|
|
4135
4281
|
}
|
|
4136
4282
|
|
|
4137
4283
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
4138
|
-
function buildCspConfig(resolver) {
|
|
4284
|
+
function buildCspConfig(resolver, extra) {
|
|
4139
4285
|
const cspResourceDomains = ["https://esm.sh"];
|
|
4140
4286
|
const cspConnectDomains = ["https://esm.sh"];
|
|
4287
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
4288
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
4289
|
+
}
|
|
4290
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
4291
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
4292
|
+
}
|
|
4141
4293
|
if (resolver && "overrides" in resolver) {
|
|
4142
4294
|
const overrides = resolver.overrides;
|
|
4143
4295
|
if (overrides) {
|
|
@@ -4177,6 +4329,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4177
4329
|
}
|
|
4178
4330
|
return rawResult;
|
|
4179
4331
|
}
|
|
4332
|
+
function isReactElement(value) {
|
|
4333
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4334
|
+
}
|
|
4335
|
+
function isDefinitelyReactComponent(template) {
|
|
4336
|
+
if (typeof template !== "function") return false;
|
|
4337
|
+
const proto = template.prototype;
|
|
4338
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
4339
|
+
}
|
|
4180
4340
|
function renderToolTemplate(options) {
|
|
4181
4341
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
4182
4342
|
const uiType = detectUIType(template);
|
|
@@ -4187,30 +4347,43 @@ function renderToolTemplate(options) {
|
|
|
4187
4347
|
output,
|
|
4188
4348
|
includeBridge: true,
|
|
4189
4349
|
resolver,
|
|
4190
|
-
sizing
|
|
4350
|
+
sizing,
|
|
4351
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
4191
4352
|
};
|
|
4192
4353
|
let html2;
|
|
4193
4354
|
let hash = "";
|
|
4194
4355
|
let size = 0;
|
|
4195
4356
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
4196
|
-
const cspConfig = buildCspConfig(resolver);
|
|
4197
4357
|
const result = renderComponent(
|
|
4198
4358
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
4199
|
-
{ ...shellConfig, csp:
|
|
4359
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
4200
4360
|
);
|
|
4201
4361
|
html2 = result.html;
|
|
4202
4362
|
hash = result.hash;
|
|
4203
4363
|
size = result.size;
|
|
4204
4364
|
} else if (typeof template === "function") {
|
|
4205
|
-
|
|
4365
|
+
const helpers = createTemplateHelpers();
|
|
4366
|
+
const ctx = { input, output, helpers };
|
|
4367
|
+
let rawResult;
|
|
4368
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
4369
|
+
if (isHtmlBuilder) {
|
|
4370
|
+
try {
|
|
4371
|
+
rawResult = template(ctx);
|
|
4372
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
4373
|
+
} catch (error) {
|
|
4374
|
+
if (uiType !== "react") throw error;
|
|
4375
|
+
isHtmlBuilder = false;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
if (!isHtmlBuilder) {
|
|
4379
|
+
(options.logger ?? console).warn(
|
|
4380
|
+
`[FrontMCP] Tool "${toolName}": \`ui.template\` is a React component reference, which cannot be bundled (there is no source file to compile), so the widget would render an empty page. Use \`template: { file: './widget.tsx' }\` instead.`
|
|
4381
|
+
);
|
|
4206
4382
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4207
4383
|
html2 = shellResult.html;
|
|
4208
4384
|
hash = shellResult.hash;
|
|
4209
4385
|
size = shellResult.size;
|
|
4210
4386
|
} else {
|
|
4211
|
-
const helpers = createTemplateHelpers();
|
|
4212
|
-
const ctx = { input, output, helpers };
|
|
4213
|
-
const rawResult = template(ctx);
|
|
4214
4387
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
4215
4388
|
if (wrapped) {
|
|
4216
4389
|
html2 = wrapped;
|
|
@@ -4222,7 +4395,7 @@ function renderToolTemplate(options) {
|
|
|
4222
4395
|
}
|
|
4223
4396
|
}
|
|
4224
4397
|
} else if (typeof template === "string") {
|
|
4225
|
-
const shellResult = buildShell(template, shellConfig);
|
|
4398
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
4226
4399
|
html2 = shellResult.html;
|
|
4227
4400
|
hash = shellResult.hash;
|
|
4228
4401
|
size = shellResult.size;
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.7",
|
|
4
4
|
"description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@frontmcp/lazy-zod": "1.8.
|
|
64
|
+
"@frontmcp/lazy-zod": "1.8.7",
|
|
65
65
|
"esbuild": ">=0.27.0 <1"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
package/esm/shell/index.mjs
CHANGED
|
@@ -265,12 +265,38 @@ function __applyHostTheme(theme) {
|
|
|
265
265
|
} catch (e) {}
|
|
266
266
|
}
|
|
267
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
|
+
|
|
268
284
|
function readInjectedData() {
|
|
269
285
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
270
286
|
if (typeof window !== 'undefined') {
|
|
271
287
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
272
288
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
273
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
|
+
}
|
|
274
300
|
}
|
|
275
301
|
return data;
|
|
276
302
|
}
|
|
@@ -319,7 +345,24 @@ var OpenAIAdapter = {
|
|
|
319
345
|
if (sdk.displayMode) {
|
|
320
346
|
context.hostContext.displayMode = sdk.displayMode;
|
|
321
347
|
}
|
|
322
|
-
//
|
|
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
|
+
});
|
|
323
366
|
return Promise.resolve();
|
|
324
367
|
},
|
|
325
368
|
callTool: function(context, name, args) {
|
|
@@ -466,9 +509,10 @@ var ExtAppsAdapter = {
|
|
|
466
509
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
467
510
|
break;
|
|
468
511
|
case 'ui/notifications/tool-result':
|
|
469
|
-
|
|
512
|
+
var resultOutput = extractToolOutput(params);
|
|
513
|
+
context.toolOutput = resultOutput;
|
|
470
514
|
context.structuredContent = params.structuredContent;
|
|
471
|
-
context.notifyToolResult(
|
|
515
|
+
context.notifyToolResult(resultOutput);
|
|
472
516
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
473
517
|
break;
|
|
474
518
|
case 'ui/notifications/host-context-changed':
|
|
@@ -522,6 +566,15 @@ var ExtAppsAdapter = {
|
|
|
522
566
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
523
567
|
});
|
|
524
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
|
+
},
|
|
525
578
|
sendHandshake: function(context) {
|
|
526
579
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
527
580
|
// been established yet. The response from the host will establish
|
|
@@ -587,7 +640,7 @@ var ExtAppsAdapter = {
|
|
|
587
640
|
var pending = this.pendingSize;
|
|
588
641
|
if (!pending) return;
|
|
589
642
|
this.pendingSize = null;
|
|
590
|
-
this.
|
|
643
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
591
644
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
592
645
|
}, function(err) {
|
|
593
646
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -614,13 +667,11 @@ var ExtAppsAdapter = {
|
|
|
614
667
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
615
668
|
},
|
|
616
669
|
setSize: function(context, size) {
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
var params = {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
aspectRatio: size && size.aspectRatio
|
|
623
|
-
};
|
|
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;
|
|
624
675
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
625
676
|
// No host origin yet: keep only the latest size and send it when the
|
|
626
677
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -631,7 +682,7 @@ var ExtAppsAdapter = {
|
|
|
631
682
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
632
683
|
});
|
|
633
684
|
}
|
|
634
|
-
return this.
|
|
685
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
635
686
|
},
|
|
636
687
|
requestClose: function(context) {
|
|
637
688
|
return this.sendRequest('ui/close', {});
|
|
@@ -1054,7 +1105,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1054
1105
|
|
|
1055
1106
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1056
1107
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1057
|
-
// ext-apps sends ui/
|
|
1108
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1058
1109
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1059
1110
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1060
1111
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1156,7 +1207,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1156
1207
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1157
1208
|
|
|
1158
1209
|
// Add spinner for buttons
|
|
1159
|
-
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>';
|
|
1160
1211
|
if (target.tagName === 'BUTTON') {
|
|
1161
1212
|
target.innerHTML = spinner + 'Loading...';
|
|
1162
1213
|
}
|
|
@@ -1237,7 +1288,22 @@ function minifyJS(code) {
|
|
|
1237
1288
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1238
1289
|
return code;
|
|
1239
1290
|
}
|
|
1240
|
-
|
|
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");
|
|
1241
1307
|
}
|
|
1242
1308
|
function generatePlatformBundle(platform, options = {}) {
|
|
1243
1309
|
const platformAdapters = {
|