@frontmcp/uipack 1.0.0-beta.12 → 1.0.0-beta.14
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 +156 -46
- package/adapters/template-renderer.d.ts +8 -5
- package/adapters/template-renderer.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +76 -15
- package/component/index.js +136 -26
- package/component/transpiler.d.ts.map +1 -1
- package/esm/adapters/index.mjs +156 -46
- package/esm/bridge-runtime/index.mjs +76 -15
- package/esm/component/index.mjs +136 -26
- package/esm/index.mjs +156 -46
- package/esm/package.json +1 -1
- package/esm/shell/index.mjs +77 -15
- package/index.js +156 -46
- package/package.json +1 -1
- package/shell/data-injector.d.ts.map +1 -1
- package/shell/index.js +77 -15
package/index.js
CHANGED
|
@@ -297,6 +297,19 @@ function generateBridgeIIFE(options = {}) {
|
|
|
297
297
|
parts.push("});");
|
|
298
298
|
parts.push("");
|
|
299
299
|
parts.push("window.FrontMcpBridge = bridge;");
|
|
300
|
+
parts.push("function __showLoading() {");
|
|
301
|
+
parts.push(' var root = document.getElementById("root");');
|
|
302
|
+
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
303
|
+
parts.push(
|
|
304
|
+
` root.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;padding:2rem;color:#6b7280;font-family:system-ui,sans-serif"><div style="text-align:center"><div style="width:24px;height:24px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:__spin 0.6s linear infinite;margin:0 auto 12px"></div><div style="font-size:0.875rem">Loading widget...</div></div></div><style>@keyframes __spin{to{transform:rotate(360deg)}}</style>';`
|
|
305
|
+
);
|
|
306
|
+
parts.push(" }");
|
|
307
|
+
parts.push("}");
|
|
308
|
+
parts.push('if (document.readyState === "loading") {');
|
|
309
|
+
parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');
|
|
310
|
+
parts.push("} else {");
|
|
311
|
+
parts.push(" __showLoading();");
|
|
312
|
+
parts.push("}");
|
|
300
313
|
parts.push("})();");
|
|
301
314
|
const code = parts.join("\n");
|
|
302
315
|
if (minify) {
|
|
@@ -473,7 +486,20 @@ var ExtAppsAdapter = {
|
|
|
473
486
|
self.handleMessage(context, event);
|
|
474
487
|
});
|
|
475
488
|
|
|
476
|
-
|
|
489
|
+
// Defer handshake until after the document is fully loaded.
|
|
490
|
+
// During document.write() (used by the sandbox proxy), postMessage
|
|
491
|
+
// from the inner iframe may not reach the parent. Waiting for DOMContentLoaded
|
|
492
|
+
// or using setTimeout ensures the iframe is fully attached.
|
|
493
|
+
return new Promise(function(resolve) {
|
|
494
|
+
function doHandshake() {
|
|
495
|
+
self.sendHandshake(context).then(resolve, resolve);
|
|
496
|
+
}
|
|
497
|
+
if (document.readyState === 'loading') {
|
|
498
|
+
document.addEventListener('DOMContentLoaded', doHandshake);
|
|
499
|
+
} else {
|
|
500
|
+
setTimeout(doHandshake, 0);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
477
503
|
},
|
|
478
504
|
handleMessage: function(context, event) {
|
|
479
505
|
if (!this.isOriginTrusted(event.origin)) return;
|
|
@@ -567,32 +593,67 @@ var ExtAppsAdapter = {
|
|
|
567
593
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
568
594
|
});
|
|
569
595
|
},
|
|
570
|
-
|
|
596
|
+
sendHandshake: function(context) {
|
|
597
|
+
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
598
|
+
// been established yet. The response from the host will establish
|
|
599
|
+
// TOFU trust via handleMessage \u2192 isOriginTrusted.
|
|
571
600
|
var self = this;
|
|
601
|
+
var id = ++this.requestId;
|
|
572
602
|
var params = {
|
|
573
603
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
574
604
|
appCapabilities: { tools: { listChanged: false } },
|
|
575
605
|
protocolVersion: '2024-11-05'
|
|
576
606
|
};
|
|
577
607
|
|
|
578
|
-
return
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
608
|
+
return new Promise(function(resolve, reject) {
|
|
609
|
+
var timeout = setTimeout(function() {
|
|
610
|
+
delete self.pendingRequests[id];
|
|
611
|
+
// Handshake timeout is non-fatal \u2014 notifications may still arrive
|
|
612
|
+
resolve();
|
|
613
|
+
}, 10000);
|
|
614
|
+
|
|
615
|
+
self.pendingRequests[id] = {
|
|
616
|
+
resolve: function(result) {
|
|
617
|
+
self.hostCapabilities = result.hostCapabilities || {};
|
|
618
|
+
self.capabilities = Object.assign({}, self.capabilities, {
|
|
619
|
+
canCallTools: Boolean(self.hostCapabilities.serverTools || self.hostCapabilities.serverToolProxy),
|
|
620
|
+
canSendMessages: true,
|
|
621
|
+
canOpenLinks: Boolean(self.hostCapabilities.openLinks || self.hostCapabilities.openLink),
|
|
622
|
+
supportsDisplayModes: true
|
|
623
|
+
});
|
|
624
|
+
if (result.hostContext) {
|
|
625
|
+
Object.assign(context.hostContext, result.hostContext);
|
|
626
|
+
}
|
|
627
|
+
// Send ui/notifications/initialized to tell the host the view is ready.
|
|
628
|
+
// Per MCP Apps spec, the host waits for this before sending tool-result.
|
|
629
|
+
var targetOrigin = self.trustedOrigin || '*';
|
|
630
|
+
window.parent.postMessage({
|
|
631
|
+
jsonrpc: '2.0',
|
|
632
|
+
method: 'ui/notifications/initialized',
|
|
633
|
+
params: {}
|
|
634
|
+
}, targetOrigin);
|
|
635
|
+
resolve();
|
|
636
|
+
},
|
|
637
|
+
reject: function(err) { resolve(); }, // Non-fatal
|
|
638
|
+
timeout: timeout
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Use '*' for the initial handshake \u2014 we don't know the host origin yet.
|
|
642
|
+
// The sandbox proxy will relay this to the host.
|
|
643
|
+
window.parent.postMessage({
|
|
644
|
+
jsonrpc: '2.0', id: id, method: 'ui/initialize', params: params
|
|
645
|
+
}, '*');
|
|
589
646
|
});
|
|
590
647
|
},
|
|
648
|
+
performHandshake: function(context) {
|
|
649
|
+
return this.sendHandshake(context);
|
|
650
|
+
},
|
|
591
651
|
callTool: function(context, name, args) {
|
|
592
|
-
if (!this.hostCapabilities.serverToolProxy) {
|
|
652
|
+
if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
|
|
593
653
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
594
654
|
}
|
|
595
|
-
|
|
655
|
+
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
656
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
596
657
|
},
|
|
597
658
|
sendMessage: function(context, content) {
|
|
598
659
|
return this.sendRequest('ui/message', { content: content });
|
|
@@ -2773,6 +2834,7 @@ function escapeAttribute(str) {
|
|
|
2773
2834
|
function buildDataInjectionScript(options) {
|
|
2774
2835
|
const { toolName, input, output, structuredContent } = options;
|
|
2775
2836
|
const lines = [
|
|
2837
|
+
`window.__mcpAppsEnabled = true;`,
|
|
2776
2838
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
2777
2839
|
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
2778
2840
|
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
@@ -3103,22 +3165,71 @@ function bundleFileSource(source, filename, resolveDir, componentName) {
|
|
|
3103
3165
|
const esbuild = require("esbuild");
|
|
3104
3166
|
const mountCode = `
|
|
3105
3167
|
// --- Auto-generated mount ---
|
|
3168
|
+
import { createElement as __h } from 'react';
|
|
3106
3169
|
import { createRoot } from 'react-dom/client';
|
|
3107
3170
|
import { McpBridgeProvider } from '@frontmcp/ui/react';
|
|
3108
|
-
|
|
3109
|
-
const __root = document.getElementById('root');
|
|
3171
|
+
var __root = document.getElementById('root');
|
|
3110
3172
|
if (__root) {
|
|
3111
|
-
createRoot(__root)
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3173
|
+
var __reactRoot = createRoot(__root);
|
|
3174
|
+
function __hasData(v) { return v !== undefined; }
|
|
3175
|
+
function __render(output) {
|
|
3176
|
+
__reactRoot.render(
|
|
3177
|
+
__h(McpBridgeProvider, null,
|
|
3178
|
+
__h(${componentName}, { output: output !== undefined ? output : null, input: window.__mcpToolInput, loading: !__hasData(output) })
|
|
3179
|
+
)
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
// Render immediately (component shows loading state until data arrives)
|
|
3183
|
+
__render(undefined);
|
|
3184
|
+
// 1. Try OpenAI SDK (toolOutput set synchronously or after load)
|
|
3185
|
+
if (typeof window !== 'undefined') {
|
|
3186
|
+
if (!window.openai) window.openai = {};
|
|
3187
|
+
var __cur = window.openai.toolOutput;
|
|
3188
|
+
if (__hasData(__cur)) { __render(__cur); }
|
|
3189
|
+
Object.defineProperty(window.openai, 'toolOutput', {
|
|
3190
|
+
get: function() { return __cur; },
|
|
3191
|
+
set: function(v) { __cur = v; __render(v); },
|
|
3192
|
+
configurable: true, enumerable: true
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
// 2. Try injected data globals
|
|
3196
|
+
if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
|
|
3197
|
+
// 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
|
|
3198
|
+
var __bridge = window.FrontMcpBridge;
|
|
3199
|
+
if (__bridge && typeof __bridge.onToolResult === 'function') {
|
|
3200
|
+
__bridge.onToolResult(function(data) { __render(data); });
|
|
3201
|
+
} else {
|
|
3202
|
+
// 4. Fallback: listen for tool:result CustomEvent (standalone / MCP Inspector)
|
|
3203
|
+
window.addEventListener('tool:result', function(e) {
|
|
3204
|
+
var d = e.detail;
|
|
3205
|
+
if (d) __render(d.structuredContent !== undefined ? d.structuredContent : d.content !== undefined ? d.content : d);
|
|
3206
|
+
});
|
|
3207
|
+
}
|
|
3116
3208
|
}`;
|
|
3117
3209
|
const loader = {
|
|
3118
3210
|
".tsx": "tsx",
|
|
3119
3211
|
".jsx": "jsx"
|
|
3120
3212
|
};
|
|
3121
3213
|
const stdinLoader = filename.endsWith(".tsx") ? "tsx" : "jsx";
|
|
3214
|
+
const alias = {};
|
|
3215
|
+
try {
|
|
3216
|
+
const nodePath = require("path");
|
|
3217
|
+
const nodeFs = require("fs");
|
|
3218
|
+
const candidates = [
|
|
3219
|
+
nodePath.join(process.cwd(), "node_modules", "@frontmcp", "ui", "dist", "esm"),
|
|
3220
|
+
nodePath.join(resolveDir, "node_modules", "@frontmcp", "ui", "dist", "esm")
|
|
3221
|
+
];
|
|
3222
|
+
for (const uiEsmBase of candidates) {
|
|
3223
|
+
if (!nodeFs.existsSync(uiEsmBase)) continue;
|
|
3224
|
+
const subpaths = ["components", "react", "theme", "bridge", "runtime"];
|
|
3225
|
+
for (const sub of subpaths) {
|
|
3226
|
+
const mjs = nodePath.join(uiEsmBase, sub, "index.mjs");
|
|
3227
|
+
if (nodeFs.existsSync(mjs)) alias[`@frontmcp/ui/${sub}`] = mjs;
|
|
3228
|
+
}
|
|
3229
|
+
if (Object.keys(alias).length > 0) break;
|
|
3230
|
+
}
|
|
3231
|
+
} catch {
|
|
3232
|
+
}
|
|
3122
3233
|
try {
|
|
3123
3234
|
const result = esbuild.buildSync({
|
|
3124
3235
|
stdin: {
|
|
@@ -3131,10 +3242,9 @@ if (__root) {
|
|
|
3131
3242
|
write: false,
|
|
3132
3243
|
format: "esm",
|
|
3133
3244
|
target: "es2020",
|
|
3134
|
-
jsx: "
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
external: ["react", "react-dom"],
|
|
3245
|
+
jsx: "automatic",
|
|
3246
|
+
external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
|
|
3247
|
+
alias,
|
|
3138
3248
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
3139
3249
|
platform: "browser",
|
|
3140
3250
|
treeShaking: true,
|
|
@@ -3657,8 +3767,26 @@ function wrapDetectedContent(value) {
|
|
|
3657
3767
|
}
|
|
3658
3768
|
|
|
3659
3769
|
// libs/uipack/src/adapters/template-renderer.ts
|
|
3770
|
+
function buildCspConfig(resolver) {
|
|
3771
|
+
const cspResourceDomains = ["https://esm.sh"];
|
|
3772
|
+
const cspConnectDomains = ["https://esm.sh"];
|
|
3773
|
+
if (resolver && "overrides" in resolver) {
|
|
3774
|
+
const overrides = resolver.overrides;
|
|
3775
|
+
if (overrides) {
|
|
3776
|
+
for (const url of Object.values(overrides)) {
|
|
3777
|
+
try {
|
|
3778
|
+
const origin = new URL(url).origin;
|
|
3779
|
+
if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
|
|
3780
|
+
if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
|
|
3781
|
+
} catch {
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
|
|
3787
|
+
}
|
|
3660
3788
|
function renderToolTemplate(options) {
|
|
3661
|
-
const { toolName, input, output, template,
|
|
3789
|
+
const { toolName, input, output, template, resolver } = options;
|
|
3662
3790
|
const uiType = detectUIType(template);
|
|
3663
3791
|
const shellConfig = {
|
|
3664
3792
|
toolName,
|
|
@@ -3671,25 +3799,7 @@ function renderToolTemplate(options) {
|
|
|
3671
3799
|
let hash = "";
|
|
3672
3800
|
let size = 0;
|
|
3673
3801
|
if (typeof template === "object" && template !== null && "file" in template) {
|
|
3674
|
-
const
|
|
3675
|
-
const cspConnectDomains = ["https://esm.sh"];
|
|
3676
|
-
if (resolver && "overrides" in resolver) {
|
|
3677
|
-
const overrides = resolver.overrides;
|
|
3678
|
-
if (overrides) {
|
|
3679
|
-
for (const url of Object.values(overrides)) {
|
|
3680
|
-
try {
|
|
3681
|
-
const origin = new URL(url).origin;
|
|
3682
|
-
if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
|
|
3683
|
-
if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
|
|
3684
|
-
} catch {
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
const cspConfig = {
|
|
3690
|
-
resourceDomains: cspResourceDomains,
|
|
3691
|
-
connectDomains: cspConnectDomains
|
|
3692
|
-
};
|
|
3802
|
+
const cspConfig = buildCspConfig(resolver);
|
|
3693
3803
|
const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
|
|
3694
3804
|
html = result.html;
|
|
3695
3805
|
hash = result.hash;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/uipack",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.14",
|
|
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",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,MAAM,CAYT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA6BvD"}
|
package/shell/index.js
CHANGED
|
@@ -162,6 +162,7 @@ function safeJsonForScript(value) {
|
|
|
162
162
|
function buildDataInjectionScript(options) {
|
|
163
163
|
const { toolName, input, output, structuredContent } = options;
|
|
164
164
|
const lines = [
|
|
165
|
+
`window.__mcpAppsEnabled = true;`,
|
|
165
166
|
`window.__mcpToolName = ${safeJsonForScript(toolName)};`,
|
|
166
167
|
`window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
|
|
167
168
|
`window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
|
|
@@ -252,6 +253,19 @@ function generateBridgeIIFE(options = {}) {
|
|
|
252
253
|
parts.push("});");
|
|
253
254
|
parts.push("");
|
|
254
255
|
parts.push("window.FrontMcpBridge = bridge;");
|
|
256
|
+
parts.push("function __showLoading() {");
|
|
257
|
+
parts.push(' var root = document.getElementById("root");');
|
|
258
|
+
parts.push(" if (root && !root.hasChildNodes()) {");
|
|
259
|
+
parts.push(
|
|
260
|
+
` root.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;padding:2rem;color:#6b7280;font-family:system-ui,sans-serif"><div style="text-align:center"><div style="width:24px;height:24px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:__spin 0.6s linear infinite;margin:0 auto 12px"></div><div style="font-size:0.875rem">Loading widget...</div></div></div><style>@keyframes __spin{to{transform:rotate(360deg)}}</style>';`
|
|
261
|
+
);
|
|
262
|
+
parts.push(" }");
|
|
263
|
+
parts.push("}");
|
|
264
|
+
parts.push('if (document.readyState === "loading") {');
|
|
265
|
+
parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');
|
|
266
|
+
parts.push("} else {");
|
|
267
|
+
parts.push(" __showLoading();");
|
|
268
|
+
parts.push("}");
|
|
255
269
|
parts.push("})();");
|
|
256
270
|
const code = parts.join("\n");
|
|
257
271
|
if (minify) {
|
|
@@ -428,7 +442,20 @@ var ExtAppsAdapter = {
|
|
|
428
442
|
self.handleMessage(context, event);
|
|
429
443
|
});
|
|
430
444
|
|
|
431
|
-
|
|
445
|
+
// Defer handshake until after the document is fully loaded.
|
|
446
|
+
// During document.write() (used by the sandbox proxy), postMessage
|
|
447
|
+
// from the inner iframe may not reach the parent. Waiting for DOMContentLoaded
|
|
448
|
+
// or using setTimeout ensures the iframe is fully attached.
|
|
449
|
+
return new Promise(function(resolve) {
|
|
450
|
+
function doHandshake() {
|
|
451
|
+
self.sendHandshake(context).then(resolve, resolve);
|
|
452
|
+
}
|
|
453
|
+
if (document.readyState === 'loading') {
|
|
454
|
+
document.addEventListener('DOMContentLoaded', doHandshake);
|
|
455
|
+
} else {
|
|
456
|
+
setTimeout(doHandshake, 0);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
432
459
|
},
|
|
433
460
|
handleMessage: function(context, event) {
|
|
434
461
|
if (!this.isOriginTrusted(event.origin)) return;
|
|
@@ -522,32 +549,67 @@ var ExtAppsAdapter = {
|
|
|
522
549
|
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
|
|
523
550
|
});
|
|
524
551
|
},
|
|
525
|
-
|
|
552
|
+
sendHandshake: function(context) {
|
|
553
|
+
// Send ui/initialize using '*' as target origin since TOFU hasn't
|
|
554
|
+
// been established yet. The response from the host will establish
|
|
555
|
+
// TOFU trust via handleMessage \u2192 isOriginTrusted.
|
|
526
556
|
var self = this;
|
|
557
|
+
var id = ++this.requestId;
|
|
527
558
|
var params = {
|
|
528
559
|
appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
|
|
529
560
|
appCapabilities: { tools: { listChanged: false } },
|
|
530
561
|
protocolVersion: '2024-11-05'
|
|
531
562
|
};
|
|
532
563
|
|
|
533
|
-
return
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
564
|
+
return new Promise(function(resolve, reject) {
|
|
565
|
+
var timeout = setTimeout(function() {
|
|
566
|
+
delete self.pendingRequests[id];
|
|
567
|
+
// Handshake timeout is non-fatal \u2014 notifications may still arrive
|
|
568
|
+
resolve();
|
|
569
|
+
}, 10000);
|
|
570
|
+
|
|
571
|
+
self.pendingRequests[id] = {
|
|
572
|
+
resolve: function(result) {
|
|
573
|
+
self.hostCapabilities = result.hostCapabilities || {};
|
|
574
|
+
self.capabilities = Object.assign({}, self.capabilities, {
|
|
575
|
+
canCallTools: Boolean(self.hostCapabilities.serverTools || self.hostCapabilities.serverToolProxy),
|
|
576
|
+
canSendMessages: true,
|
|
577
|
+
canOpenLinks: Boolean(self.hostCapabilities.openLinks || self.hostCapabilities.openLink),
|
|
578
|
+
supportsDisplayModes: true
|
|
579
|
+
});
|
|
580
|
+
if (result.hostContext) {
|
|
581
|
+
Object.assign(context.hostContext, result.hostContext);
|
|
582
|
+
}
|
|
583
|
+
// Send ui/notifications/initialized to tell the host the view is ready.
|
|
584
|
+
// Per MCP Apps spec, the host waits for this before sending tool-result.
|
|
585
|
+
var targetOrigin = self.trustedOrigin || '*';
|
|
586
|
+
window.parent.postMessage({
|
|
587
|
+
jsonrpc: '2.0',
|
|
588
|
+
method: 'ui/notifications/initialized',
|
|
589
|
+
params: {}
|
|
590
|
+
}, targetOrigin);
|
|
591
|
+
resolve();
|
|
592
|
+
},
|
|
593
|
+
reject: function(err) { resolve(); }, // Non-fatal
|
|
594
|
+
timeout: timeout
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
// Use '*' for the initial handshake \u2014 we don't know the host origin yet.
|
|
598
|
+
// The sandbox proxy will relay this to the host.
|
|
599
|
+
window.parent.postMessage({
|
|
600
|
+
jsonrpc: '2.0', id: id, method: 'ui/initialize', params: params
|
|
601
|
+
}, '*');
|
|
544
602
|
});
|
|
545
603
|
},
|
|
604
|
+
performHandshake: function(context) {
|
|
605
|
+
return this.sendHandshake(context);
|
|
606
|
+
},
|
|
546
607
|
callTool: function(context, name, args) {
|
|
547
|
-
if (!this.hostCapabilities.serverToolProxy) {
|
|
608
|
+
if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
|
|
548
609
|
return Promise.reject(new Error('Server tool proxy not supported'));
|
|
549
610
|
}
|
|
550
|
-
|
|
611
|
+
// Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
|
|
612
|
+
return this.sendRequest('tools/call', { name: name, arguments: args || {} });
|
|
551
613
|
},
|
|
552
614
|
sendMessage: function(context, content) {
|
|
553
615
|
return this.sendRequest('ui/message', { content: content });
|