@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/index.js
CHANGED
|
@@ -518,12 +518,38 @@ function __applyHostTheme(theme) {
|
|
|
518
518
|
} catch (e) {}
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
+
function extractToolOutput(params) {
|
|
522
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
523
|
+
if (!params) return undefined;
|
|
524
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
525
|
+
var content = params.content;
|
|
526
|
+
if (Array.isArray(content)) {
|
|
527
|
+
for (var i = 0; i < content.length; i++) {
|
|
528
|
+
var item = content[i];
|
|
529
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
530
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return content;
|
|
535
|
+
}
|
|
536
|
+
|
|
521
537
|
function readInjectedData() {
|
|
522
538
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
523
539
|
if (typeof window !== 'undefined') {
|
|
524
540
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
525
541
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
526
542
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
543
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
544
|
+
// (toolOutput is the tool's structuredContent).
|
|
545
|
+
var oa = window.openai;
|
|
546
|
+
if (oa && typeof oa === 'object') {
|
|
547
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
548
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
549
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
550
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
527
553
|
}
|
|
528
554
|
return data;
|
|
529
555
|
}
|
|
@@ -572,7 +598,24 @@ var OpenAIAdapter = {
|
|
|
572
598
|
if (sdk.displayMode) {
|
|
573
599
|
context.hostContext.displayMode = sdk.displayMode;
|
|
574
600
|
}
|
|
575
|
-
//
|
|
601
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
602
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
603
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
604
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
605
|
+
context.toolInput = globals.toolInput;
|
|
606
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
607
|
+
}
|
|
608
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
609
|
+
context.structuredContent = globals.toolOutput;
|
|
610
|
+
context.notifyToolResult(globals.toolOutput);
|
|
611
|
+
}
|
|
612
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
613
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
614
|
+
}
|
|
615
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
616
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
617
|
+
}
|
|
618
|
+
});
|
|
576
619
|
return Promise.resolve();
|
|
577
620
|
},
|
|
578
621
|
callTool: function(context, name, args) {
|
|
@@ -719,9 +762,10 @@ var ExtAppsAdapter = {
|
|
|
719
762
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
720
763
|
break;
|
|
721
764
|
case 'ui/notifications/tool-result':
|
|
722
|
-
|
|
765
|
+
var resultOutput = extractToolOutput(params);
|
|
766
|
+
context.toolOutput = resultOutput;
|
|
723
767
|
context.structuredContent = params.structuredContent;
|
|
724
|
-
context.notifyToolResult(
|
|
768
|
+
context.notifyToolResult(resultOutput);
|
|
725
769
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
726
770
|
break;
|
|
727
771
|
case 'ui/notifications/host-context-changed':
|
|
@@ -775,6 +819,15 @@ var ExtAppsAdapter = {
|
|
|
775
819
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
776
820
|
});
|
|
777
821
|
},
|
|
822
|
+
sendNotification: function(method, params) {
|
|
823
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
824
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
825
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
826
|
+
}
|
|
827
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
828
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
829
|
+
return Promise.resolve();
|
|
830
|
+
},
|
|
778
831
|
sendHandshake: function(context) {
|
|
779
832
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
780
833
|
// been established yet. The response from the host will establish
|
|
@@ -840,7 +893,7 @@ var ExtAppsAdapter = {
|
|
|
840
893
|
var pending = this.pendingSize;
|
|
841
894
|
if (!pending) return;
|
|
842
895
|
this.pendingSize = null;
|
|
843
|
-
this.
|
|
896
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
844
897
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
845
898
|
}, function(err) {
|
|
846
899
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -867,13 +920,11 @@ var ExtAppsAdapter = {
|
|
|
867
920
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
868
921
|
},
|
|
869
922
|
setSize: function(context, size) {
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
var params = {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
aspectRatio: size && size.aspectRatio
|
|
876
|
-
};
|
|
923
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
924
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
925
|
+
var params = {};
|
|
926
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
927
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
877
928
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
878
929
|
// No host origin yet: keep only the latest size and send it when the
|
|
879
930
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -884,7 +935,7 @@ var ExtAppsAdapter = {
|
|
|
884
935
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
885
936
|
});
|
|
886
937
|
}
|
|
887
|
-
return this.
|
|
938
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
888
939
|
},
|
|
889
940
|
requestClose: function(context) {
|
|
890
941
|
return this.sendRequest('ui/close', {});
|
|
@@ -1307,7 +1358,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1307
1358
|
|
|
1308
1359
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1309
1360
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1310
|
-
// ext-apps sends ui/
|
|
1361
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1311
1362
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1312
1363
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1313
1364
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1409,7 +1460,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1409
1460
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1410
1461
|
|
|
1411
1462
|
// Add spinner for buttons
|
|
1412
|
-
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>';
|
|
1463
|
+
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>';
|
|
1413
1464
|
if (target.tagName === 'BUTTON') {
|
|
1414
1465
|
target.innerHTML = spinner + 'Loading...';
|
|
1415
1466
|
}
|
|
@@ -1490,7 +1541,22 @@ function minifyJS(code) {
|
|
|
1490
1541
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1491
1542
|
return code;
|
|
1492
1543
|
}
|
|
1493
|
-
|
|
1544
|
+
const out = [];
|
|
1545
|
+
let inBlockComment = false;
|
|
1546
|
+
for (const raw of code.split("\n")) {
|
|
1547
|
+
const line = raw.trim();
|
|
1548
|
+
if (inBlockComment) {
|
|
1549
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1553
|
+
if (line.startsWith("/*")) {
|
|
1554
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
out.push(line);
|
|
1558
|
+
}
|
|
1559
|
+
return out.join("\n");
|
|
1494
1560
|
}
|
|
1495
1561
|
function generatePlatformBundle(platform, options = {}) {
|
|
1496
1562
|
const platformAdapters = {
|
|
@@ -4222,6 +4288,86 @@ function wrapDetectedContent(value) {
|
|
|
4222
4288
|
}
|
|
4223
4289
|
}
|
|
4224
4290
|
|
|
4291
|
+
// libs/uipack/src/adapters/markdown.ts
|
|
4292
|
+
var SAFE_HREF = /^(https?:|mailto:|\/|#)/i;
|
|
4293
|
+
function emphasis(text) {
|
|
4294
|
+
return text.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
4295
|
+
}
|
|
4296
|
+
function inline(text) {
|
|
4297
|
+
const stash = [];
|
|
4298
|
+
const hold = (html2) => `\uE000${stash.push(html2) - 1}\uE000`;
|
|
4299
|
+
let out = escapeHtml(text.replace(/\uE000/g, "")).replace(
|
|
4300
|
+
/`([^`]+)`/g,
|
|
4301
|
+
(_m, code) => hold(`<code>${code}</code>`)
|
|
4302
|
+
);
|
|
4303
|
+
out = out.replace(
|
|
4304
|
+
/\[([^\]]+)\]\(([^)\s]+)\)/g,
|
|
4305
|
+
(_m, label, href) => SAFE_HREF.test(href) ? hold(`<a href="${href}" rel="noopener noreferrer">${emphasis(label)}</a>`) : label
|
|
4306
|
+
);
|
|
4307
|
+
out = emphasis(out);
|
|
4308
|
+
for (let i = 0; i < 2; i++) out = out.replace(/\uE000(\d+)\uE000/g, (_m, n) => stash[Number(n)]);
|
|
4309
|
+
return out;
|
|
4310
|
+
}
|
|
4311
|
+
function markdownToHtml(source) {
|
|
4312
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
4313
|
+
const blocks = [];
|
|
4314
|
+
let paragraph = [];
|
|
4315
|
+
let list;
|
|
4316
|
+
const flushParagraph = () => {
|
|
4317
|
+
if (paragraph.length > 0) blocks.push(`<p>${inline(paragraph.join(" "))}</p>`);
|
|
4318
|
+
paragraph = [];
|
|
4319
|
+
};
|
|
4320
|
+
const flushList = () => {
|
|
4321
|
+
if (list)
|
|
4322
|
+
blocks.push(`<${list.tag}>${list.items.map((item) => `<li>${inline(item)}</li>`).join("")}</${list.tag}>`);
|
|
4323
|
+
list = void 0;
|
|
4324
|
+
};
|
|
4325
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4326
|
+
const line = lines[i];
|
|
4327
|
+
const fence = /^\s*(`{3,})/.exec(line);
|
|
4328
|
+
if (fence) {
|
|
4329
|
+
flushParagraph();
|
|
4330
|
+
flushList();
|
|
4331
|
+
const fenceLength = fence[1].length;
|
|
4332
|
+
const isClosing = (l) => {
|
|
4333
|
+
const m = /^\s*(`{3,})\s*$/.exec(l);
|
|
4334
|
+
return m !== null && m[1].length >= fenceLength;
|
|
4335
|
+
};
|
|
4336
|
+
const code = [];
|
|
4337
|
+
for (i++; i < lines.length && !isClosing(lines[i]); i++) code.push(lines[i]);
|
|
4338
|
+
blocks.push(`<pre><code>${escapeHtml(code.join("\n"))}
|
|
4339
|
+
</code></pre>`);
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
4343
|
+
if (heading) {
|
|
4344
|
+
flushParagraph();
|
|
4345
|
+
flushList();
|
|
4346
|
+
blocks.push(`<h${heading[1].length}>${inline(heading[2].trim())}</h${heading[1].length}>`);
|
|
4347
|
+
continue;
|
|
4348
|
+
}
|
|
4349
|
+
const item = /^\s*([-*+]|\d+\.)\s+(.*)$/.exec(line);
|
|
4350
|
+
if (item) {
|
|
4351
|
+
flushParagraph();
|
|
4352
|
+
const tag = /\d/.test(item[1]) ? "ol" : "ul";
|
|
4353
|
+
if (list && list.tag !== tag) flushList();
|
|
4354
|
+
list ??= { tag, items: [] };
|
|
4355
|
+
list.items.push(item[2]);
|
|
4356
|
+
continue;
|
|
4357
|
+
}
|
|
4358
|
+
if (line.trim() === "") {
|
|
4359
|
+
flushParagraph();
|
|
4360
|
+
flushList();
|
|
4361
|
+
continue;
|
|
4362
|
+
}
|
|
4363
|
+
flushList();
|
|
4364
|
+
paragraph.push(line.trim());
|
|
4365
|
+
}
|
|
4366
|
+
flushParagraph();
|
|
4367
|
+
flushList();
|
|
4368
|
+
return blocks.join("\n");
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4225
4371
|
// libs/uipack/src/adapters/type-detector.ts
|
|
4226
4372
|
function detectUIType(template) {
|
|
4227
4373
|
if (template === null || template === void 0) {
|
|
@@ -4255,9 +4401,15 @@ function detectUIType(template) {
|
|
|
4255
4401
|
}
|
|
4256
4402
|
|
|
4257
4403
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
4258
|
-
function buildCspConfig(resolver) {
|
|
4404
|
+
function buildCspConfig(resolver, extra) {
|
|
4259
4405
|
const cspResourceDomains = ["https://esm.sh"];
|
|
4260
4406
|
const cspConnectDomains = ["https://esm.sh"];
|
|
4407
|
+
for (const domain of extra?.resourceDomains ?? []) {
|
|
4408
|
+
if (!cspResourceDomains.includes(domain)) cspResourceDomains.push(domain);
|
|
4409
|
+
}
|
|
4410
|
+
for (const domain of extra?.connectDomains ?? []) {
|
|
4411
|
+
if (!cspConnectDomains.includes(domain)) cspConnectDomains.push(domain);
|
|
4412
|
+
}
|
|
4261
4413
|
if (resolver && "overrides" in resolver) {
|
|
4262
4414
|
const overrides = resolver.overrides;
|
|
4263
4415
|
if (overrides) {
|
|
@@ -4297,6 +4449,14 @@ function renderUnwrappedResult(rawResult, options) {
|
|
|
4297
4449
|
}
|
|
4298
4450
|
return rawResult;
|
|
4299
4451
|
}
|
|
4452
|
+
function isReactElement(value) {
|
|
4453
|
+
return typeof value === "object" && value !== null && "$$typeof" in value;
|
|
4454
|
+
}
|
|
4455
|
+
function isDefinitelyReactComponent(template) {
|
|
4456
|
+
if (typeof template !== "function") return false;
|
|
4457
|
+
const proto = template.prototype;
|
|
4458
|
+
return typeof proto?.render === "function" || template["$$typeof"] !== void 0;
|
|
4459
|
+
}
|
|
4300
4460
|
function renderToolTemplate(options) {
|
|
4301
4461
|
const { toolName, input, output, template, resolver, platformType, sizing } = options;
|
|
4302
4462
|
const uiType = detectUIType(template);
|
|
@@ -4307,30 +4467,43 @@ function renderToolTemplate(options) {
|
|
|
4307
4467
|
output,
|
|
4308
4468
|
includeBridge: true,
|
|
4309
4469
|
resolver,
|
|
4310
|
-
sizing
|
|
4470
|
+
sizing,
|
|
4471
|
+
csp: options.csp ? buildCspConfig(resolver, options.csp) : void 0
|
|
4311
4472
|
};
|
|
4312
4473
|
let html2;
|
|
4313
4474
|
let hash = "";
|
|
4314
4475
|
let size = 0;
|
|
4315
4476
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
4316
|
-
const cspConfig = buildCspConfig(resolver);
|
|
4317
4477
|
const result = renderComponent(
|
|
4318
4478
|
{ source: template, inlineReact: resourceMode === "inline" },
|
|
4319
|
-
{ ...shellConfig, csp:
|
|
4479
|
+
{ ...shellConfig, csp: buildCspConfig(resolver, options.csp) }
|
|
4320
4480
|
);
|
|
4321
4481
|
html2 = result.html;
|
|
4322
4482
|
hash = result.hash;
|
|
4323
4483
|
size = result.size;
|
|
4324
4484
|
} else if (typeof template === "function") {
|
|
4325
|
-
|
|
4485
|
+
const helpers = createTemplateHelpers();
|
|
4486
|
+
const ctx = { input, output, helpers };
|
|
4487
|
+
let rawResult;
|
|
4488
|
+
let isHtmlBuilder = uiType !== "react" || !isDefinitelyReactComponent(template);
|
|
4489
|
+
if (isHtmlBuilder) {
|
|
4490
|
+
try {
|
|
4491
|
+
rawResult = template(ctx);
|
|
4492
|
+
isHtmlBuilder = !isReactElement(rawResult);
|
|
4493
|
+
} catch (error) {
|
|
4494
|
+
if (uiType !== "react") throw error;
|
|
4495
|
+
isHtmlBuilder = false;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
if (!isHtmlBuilder) {
|
|
4499
|
+
(options.logger ?? console).warn(
|
|
4500
|
+
`[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.`
|
|
4501
|
+
);
|
|
4326
4502
|
const shellResult = buildShell('<div id="root"></div>', shellConfig);
|
|
4327
4503
|
html2 = shellResult.html;
|
|
4328
4504
|
hash = shellResult.hash;
|
|
4329
4505
|
size = shellResult.size;
|
|
4330
4506
|
} else {
|
|
4331
|
-
const helpers = createTemplateHelpers();
|
|
4332
|
-
const ctx = { input, output, helpers };
|
|
4333
|
-
const rawResult = template(ctx);
|
|
4334
4507
|
const wrapped = isTrustedHtml(rawResult) ? void 0 : wrapDetectedContent(rawResult);
|
|
4335
4508
|
if (wrapped) {
|
|
4336
4509
|
html2 = wrapped;
|
|
@@ -4342,7 +4515,7 @@ function renderToolTemplate(options) {
|
|
|
4342
4515
|
}
|
|
4343
4516
|
}
|
|
4344
4517
|
} else if (typeof template === "string") {
|
|
4345
|
-
const shellResult = buildShell(template, shellConfig);
|
|
4518
|
+
const shellResult = buildShell(uiType === "markdown" ? markdownToHtml(template) : template, shellConfig);
|
|
4346
4519
|
html2 = shellResult.html;
|
|
4347
4520
|
hash = shellResult.hash;
|
|
4348
4521
|
size = shellResult.size;
|
package/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/shell/index.js
CHANGED
|
@@ -317,12 +317,38 @@ function __applyHostTheme(theme) {
|
|
|
317
317
|
} catch (e) {}
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
function extractToolOutput(params) {
|
|
321
|
+
// A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
|
|
322
|
+
if (!params) return undefined;
|
|
323
|
+
if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
|
|
324
|
+
var content = params.content;
|
|
325
|
+
if (Array.isArray(content)) {
|
|
326
|
+
for (var i = 0; i < content.length; i++) {
|
|
327
|
+
var item = content[i];
|
|
328
|
+
if (item && item.type === 'text' && typeof item.text === 'string') {
|
|
329
|
+
try { return JSON.parse(item.text); } catch (e) { return item.text; }
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return content;
|
|
334
|
+
}
|
|
335
|
+
|
|
320
336
|
function readInjectedData() {
|
|
321
337
|
var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
|
|
322
338
|
if (typeof window !== 'undefined') {
|
|
323
339
|
if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
|
|
324
340
|
if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
|
|
325
341
|
if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
|
|
342
|
+
// The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
|
|
343
|
+
// (toolOutput is the tool's structuredContent).
|
|
344
|
+
var oa = window.openai;
|
|
345
|
+
if (oa && typeof oa === 'object') {
|
|
346
|
+
if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
|
|
347
|
+
if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
|
|
348
|
+
if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
|
|
349
|
+
if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
326
352
|
}
|
|
327
353
|
return data;
|
|
328
354
|
}
|
|
@@ -371,7 +397,24 @@ var OpenAIAdapter = {
|
|
|
371
397
|
if (sdk.displayMode) {
|
|
372
398
|
context.hostContext.displayMode = sdk.displayMode;
|
|
373
399
|
}
|
|
374
|
-
//
|
|
400
|
+
// The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
|
|
401
|
+
window.addEventListener('openai:set_globals', function(event) {
|
|
402
|
+
var globals = (event && event.detail && event.detail.globals) || window.openai || {};
|
|
403
|
+
if (globals.toolInput !== undefined && globals.toolInput !== null) {
|
|
404
|
+
context.toolInput = globals.toolInput;
|
|
405
|
+
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
406
|
+
}
|
|
407
|
+
if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
|
|
408
|
+
context.structuredContent = globals.toolOutput;
|
|
409
|
+
context.notifyToolResult(globals.toolOutput);
|
|
410
|
+
}
|
|
411
|
+
if (globals.theme && globals.theme !== context.hostContext.theme) {
|
|
412
|
+
context.notifyContextChange({ theme: globals.theme });
|
|
413
|
+
}
|
|
414
|
+
if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
|
|
415
|
+
context.notifyContextChange({ displayMode: globals.displayMode });
|
|
416
|
+
}
|
|
417
|
+
});
|
|
375
418
|
return Promise.resolve();
|
|
376
419
|
},
|
|
377
420
|
callTool: function(context, name, args) {
|
|
@@ -518,9 +561,10 @@ var ExtAppsAdapter = {
|
|
|
518
561
|
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
519
562
|
break;
|
|
520
563
|
case 'ui/notifications/tool-result':
|
|
521
|
-
|
|
564
|
+
var resultOutput = extractToolOutput(params);
|
|
565
|
+
context.toolOutput = resultOutput;
|
|
522
566
|
context.structuredContent = params.structuredContent;
|
|
523
|
-
context.notifyToolResult(
|
|
567
|
+
context.notifyToolResult(resultOutput);
|
|
524
568
|
window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
|
|
525
569
|
break;
|
|
526
570
|
case 'ui/notifications/host-context-changed':
|
|
@@ -574,6 +618,15 @@ var ExtAppsAdapter = {
|
|
|
574
618
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
575
619
|
});
|
|
576
620
|
},
|
|
621
|
+
sendNotification: function(method, params) {
|
|
622
|
+
// JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
|
|
623
|
+
if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
624
|
+
return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
|
|
625
|
+
}
|
|
626
|
+
var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
|
|
627
|
+
window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
|
|
628
|
+
return Promise.resolve();
|
|
629
|
+
},
|
|
577
630
|
sendHandshake: function(context) {
|
|
578
631
|
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
579
632
|
// been established yet. The response from the host will establish
|
|
@@ -639,7 +692,7 @@ var ExtAppsAdapter = {
|
|
|
639
692
|
var pending = this.pendingSize;
|
|
640
693
|
if (!pending) return;
|
|
641
694
|
this.pendingSize = null;
|
|
642
|
-
this.
|
|
695
|
+
this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
|
|
643
696
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
|
|
644
697
|
}, function(err) {
|
|
645
698
|
for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
|
|
@@ -666,13 +719,11 @@ var ExtAppsAdapter = {
|
|
|
666
719
|
return this.sendRequest('ui/setDisplayMode', { mode: mode });
|
|
667
720
|
},
|
|
668
721
|
setSize: function(context, size) {
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
var params = {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
aspectRatio: size && size.aspectRatio
|
|
675
|
-
};
|
|
722
|
+
// Standard ext-apps sizing: the view tells the host its dimensions with the
|
|
723
|
+
// ui/notifications/size-changed notification ({ width?, height? } in px).
|
|
724
|
+
var params = {};
|
|
725
|
+
if (size && typeof size.width === 'number') params.width = size.width;
|
|
726
|
+
if (size && typeof size.height === 'number') params.height = size.height;
|
|
676
727
|
if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
|
|
677
728
|
// No host origin yet: keep only the latest size and send it when the
|
|
678
729
|
// handshake settles; every caller's promise follows that one request.
|
|
@@ -683,7 +734,7 @@ var ExtAppsAdapter = {
|
|
|
683
734
|
self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
|
|
684
735
|
});
|
|
685
736
|
}
|
|
686
|
-
return this.
|
|
737
|
+
return this.sendNotification('ui/notifications/size-changed', params);
|
|
687
738
|
},
|
|
688
739
|
requestClose: function(context) {
|
|
689
740
|
return this.sendRequest('ui/close', {});
|
|
@@ -1106,7 +1157,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
|
|
|
1106
1157
|
|
|
1107
1158
|
// Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
|
|
1108
1159
|
// Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
|
|
1109
|
-
// ext-apps sends ui/
|
|
1160
|
+
// ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
|
|
1110
1161
|
FrontMcpBridge.prototype.setSize = function(size) {
|
|
1111
1162
|
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
1112
1163
|
if (!this._adapter.setSize) return Promise.resolve();
|
|
@@ -1208,7 +1259,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
|
|
|
1208
1259
|
target.classList.add('opacity-50', 'cursor-not-allowed');
|
|
1209
1260
|
|
|
1210
1261
|
// Add spinner for buttons
|
|
1211
|
-
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>';
|
|
1262
|
+
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>';
|
|
1212
1263
|
if (target.tagName === 'BUTTON') {
|
|
1213
1264
|
target.innerHTML = spinner + 'Loading...';
|
|
1214
1265
|
}
|
|
@@ -1289,7 +1340,22 @@ function minifyJS(code) {
|
|
|
1289
1340
|
if (code.length > MAX_MINIFY_CODE_LENGTH) {
|
|
1290
1341
|
return code;
|
|
1291
1342
|
}
|
|
1292
|
-
|
|
1343
|
+
const out = [];
|
|
1344
|
+
let inBlockComment = false;
|
|
1345
|
+
for (const raw of code.split("\n")) {
|
|
1346
|
+
const line = raw.trim();
|
|
1347
|
+
if (inBlockComment) {
|
|
1348
|
+
if (line.includes("*/")) inBlockComment = false;
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
if (line === "" || line.startsWith("//")) continue;
|
|
1352
|
+
if (line.startsWith("/*")) {
|
|
1353
|
+
if (!line.includes("*/")) inBlockComment = true;
|
|
1354
|
+
continue;
|
|
1355
|
+
}
|
|
1356
|
+
out.push(line);
|
|
1357
|
+
}
|
|
1358
|
+
return out.join("\n");
|
|
1293
1359
|
}
|
|
1294
1360
|
function generatePlatformBundle(platform, options = {}) {
|
|
1295
1361
|
const platformAdapters = {
|
package/types/ui-config.d.ts
CHANGED
|
@@ -418,7 +418,7 @@ export interface UITemplateConfig<In = unknown, Out = unknown> {
|
|
|
418
418
|
* - Claude / static widgets: the host measures DOM height itself, so this is
|
|
419
419
|
* a CSS-only no-op.
|
|
420
420
|
* - OpenAI: forwarded through the Apps SDK sizing API.
|
|
421
|
-
* - ext-apps hosts: reported
|
|
421
|
+
* - ext-apps hosts: reported with the standard `ui/notifications/size-changed` notification.
|
|
422
422
|
*
|
|
423
423
|
* @default true (when the widget configures sizing)
|
|
424
424
|
*/
|