@frontmcp/uipack 0.7.2 → 0.8.0
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.d.ts +1 -1
- package/adapters/index.d.ts.map +1 -1
- package/adapters/index.js +71 -24
- package/adapters/platform-meta.constants.d.ts +26 -0
- package/adapters/platform-meta.constants.d.ts.map +1 -0
- package/adapters/platform-meta.d.ts +39 -0
- package/adapters/platform-meta.d.ts.map +1 -1
- package/bridge-runtime/iife-generator.d.ts.map +1 -1
- package/bridge-runtime/index.js +125 -7
- package/build/index.js +125 -7
- package/esm/adapters/index.mjs +69 -24
- package/esm/bridge-runtime/index.mjs +125 -7
- package/esm/build/index.mjs +125 -7
- package/esm/index.mjs +180 -31
- package/esm/package.json +5 -2
- package/esm/registry/index.mjs +153 -24
- package/esm/runtime/index.mjs +125 -7
- package/esm/tool-template/index.mjs +125 -7
- package/index.js +180 -31
- package/package.json +5 -2
- package/registry/index.js +153 -24
- package/runtime/index.js +125 -7
- package/tool-template/index.js +125 -7
- package/types/ui-config.d.ts +23 -0
- package/types/ui-config.d.ts.map +1 -1
package/esm/adapters/index.mjs
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
|
+
// libs/uipack/src/adapters/platform-meta.constants.ts
|
|
2
|
+
var DISPLAY_MODE_MAP = {
|
|
3
|
+
// Standard MCP Apps modes
|
|
4
|
+
inline: "inline",
|
|
5
|
+
fullscreen: "fullscreen",
|
|
6
|
+
pip: "pip",
|
|
7
|
+
// OpenAI-style aliases
|
|
8
|
+
widget: "inline",
|
|
9
|
+
panel: "fullscreen"
|
|
10
|
+
};
|
|
11
|
+
|
|
1
12
|
// libs/uipack/src/adapters/platform-meta.ts
|
|
13
|
+
function getExtAppsMimeType(variant = "standard") {
|
|
14
|
+
switch (variant) {
|
|
15
|
+
case "profile":
|
|
16
|
+
return "text/html;profile=mcp-app";
|
|
17
|
+
case "standard":
|
|
18
|
+
default:
|
|
19
|
+
return "text/html+mcp";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isExtAppsMimeType(mimeType) {
|
|
23
|
+
return mimeType === "text/html+mcp" || mimeType === "text/html;profile=mcp-app";
|
|
24
|
+
}
|
|
2
25
|
function buildUIMeta(options) {
|
|
3
26
|
const { uiConfig, platformType, html, token, directUrl, rendererType, contentHash, manifestUri } = options;
|
|
4
27
|
const meta = {};
|
|
@@ -79,6 +102,21 @@ function buildClaudeMeta(meta, uiConfig) {
|
|
|
79
102
|
if (uiConfig.prefersBorder !== void 0) {
|
|
80
103
|
meta["claude/prefersBorder"] = uiConfig.prefersBorder;
|
|
81
104
|
}
|
|
105
|
+
if (uiConfig.resourceUri) {
|
|
106
|
+
meta["ui/resourceUri"] = uiConfig.resourceUri;
|
|
107
|
+
}
|
|
108
|
+
if (uiConfig.csp) {
|
|
109
|
+
const csp = {};
|
|
110
|
+
if (uiConfig.csp.connectDomains?.length) {
|
|
111
|
+
csp.connectDomains = uiConfig.csp.connectDomains;
|
|
112
|
+
}
|
|
113
|
+
if (uiConfig.csp.resourceDomains?.length) {
|
|
114
|
+
csp.resourceDomains = uiConfig.csp.resourceDomains;
|
|
115
|
+
}
|
|
116
|
+
if (Object.keys(csp).length > 0) {
|
|
117
|
+
meta["ui/csp"] = csp;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
82
120
|
return meta;
|
|
83
121
|
}
|
|
84
122
|
function buildGeminiMeta(meta, uiConfig) {
|
|
@@ -117,14 +155,7 @@ function buildGenericMeta(meta, uiConfig) {
|
|
|
117
155
|
}
|
|
118
156
|
}
|
|
119
157
|
if (uiConfig.displayMode) {
|
|
120
|
-
const
|
|
121
|
-
inline: "inline",
|
|
122
|
-
fullscreen: "fullscreen",
|
|
123
|
-
pip: "pip",
|
|
124
|
-
widget: "inline",
|
|
125
|
-
panel: "fullscreen"
|
|
126
|
-
};
|
|
127
|
-
const mappedMode = displayModeMap[uiConfig.displayMode];
|
|
158
|
+
const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
|
|
128
159
|
if (mappedMode) {
|
|
129
160
|
meta["ui/displayMode"] = mappedMode;
|
|
130
161
|
}
|
|
@@ -152,15 +183,7 @@ function buildExtAppsMeta(meta, uiConfig) {
|
|
|
152
183
|
}
|
|
153
184
|
}
|
|
154
185
|
if (uiConfig.displayMode) {
|
|
155
|
-
const
|
|
156
|
-
inline: "inline",
|
|
157
|
-
fullscreen: "fullscreen",
|
|
158
|
-
pip: "pip",
|
|
159
|
-
// Map OpenAI-style values
|
|
160
|
-
widget: "inline",
|
|
161
|
-
panel: "fullscreen"
|
|
162
|
-
};
|
|
163
|
-
const mappedMode = displayModeMap[uiConfig.displayMode];
|
|
186
|
+
const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
|
|
164
187
|
if (mappedMode) {
|
|
165
188
|
meta["ui/displayMode"] = mappedMode;
|
|
166
189
|
}
|
|
@@ -219,12 +242,7 @@ function buildToolDiscoveryMeta(options) {
|
|
|
219
242
|
}
|
|
220
243
|
}
|
|
221
244
|
if (uiConfig.displayMode) {
|
|
222
|
-
const
|
|
223
|
-
inline: "inline",
|
|
224
|
-
fullscreen: "fullscreen",
|
|
225
|
-
pip: "pip"
|
|
226
|
-
};
|
|
227
|
-
const mappedMode = displayModeMap[uiConfig.displayMode];
|
|
245
|
+
const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
|
|
228
246
|
if (mappedMode) {
|
|
229
247
|
meta["ui/displayMode"] = mappedMode;
|
|
230
248
|
}
|
|
@@ -236,7 +254,32 @@ function buildToolDiscoveryMeta(options) {
|
|
|
236
254
|
meta["ui/domain"] = uiConfig.sandboxDomain;
|
|
237
255
|
}
|
|
238
256
|
break;
|
|
239
|
-
|
|
257
|
+
case "claude":
|
|
258
|
+
meta["ui/resourceUri"] = staticWidgetUri;
|
|
259
|
+
meta["ui/mimeType"] = "text/html+mcp";
|
|
260
|
+
if (uiConfig.displayMode) {
|
|
261
|
+
const mappedMode = DISPLAY_MODE_MAP[uiConfig.displayMode];
|
|
262
|
+
if (mappedMode) {
|
|
263
|
+
meta["ui/displayMode"] = mappedMode;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (uiConfig.prefersBorder !== void 0) {
|
|
267
|
+
meta["ui/prefersBorder"] = uiConfig.prefersBorder;
|
|
268
|
+
}
|
|
269
|
+
if (uiConfig.csp) {
|
|
270
|
+
const csp = {};
|
|
271
|
+
if (uiConfig.csp.connectDomains?.length) {
|
|
272
|
+
csp.connectDomains = uiConfig.csp.connectDomains;
|
|
273
|
+
}
|
|
274
|
+
if (uiConfig.csp.resourceDomains?.length) {
|
|
275
|
+
csp.resourceDomains = uiConfig.csp.resourceDomains;
|
|
276
|
+
}
|
|
277
|
+
if (Object.keys(csp).length > 0) {
|
|
278
|
+
meta["ui/csp"] = csp;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
// Gemini, IDEs don't need discovery metadata
|
|
240
283
|
// They use inline HTML at call time
|
|
241
284
|
default:
|
|
242
285
|
break;
|
|
@@ -462,6 +505,8 @@ export {
|
|
|
462
505
|
buildToolResponseContent,
|
|
463
506
|
buildUIMeta,
|
|
464
507
|
getDefaultServingMode,
|
|
508
|
+
getExtAppsMimeType,
|
|
509
|
+
isExtAppsMimeType,
|
|
465
510
|
isPlatformModeSupported,
|
|
466
511
|
platformSupportsWidgets,
|
|
467
512
|
platformUsesStructuredContent,
|
|
@@ -182,16 +182,43 @@ var ExtAppsAdapter = {
|
|
|
182
182
|
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
|
|
183
183
|
trustedOrigins: ${originsArray},
|
|
184
184
|
trustedOrigin: null,
|
|
185
|
+
originTrustPending: false,
|
|
185
186
|
pendingRequests: {},
|
|
186
187
|
requestId: 0,
|
|
187
188
|
hostCapabilities: {},
|
|
188
189
|
canHandle: function() {
|
|
189
190
|
if (typeof window === 'undefined') return false;
|
|
190
191
|
if (window.parent === window) return false;
|
|
191
|
-
|
|
192
|
+
|
|
193
|
+
// Check for OpenAI SDK - defer to OpenAIAdapter
|
|
194
|
+
if (window.openai && window.openai.canvas) return false;
|
|
192
195
|
if (window.openai && typeof window.openai.callTool === 'function') return false;
|
|
196
|
+
|
|
197
|
+
// Explicit ext-apps marker
|
|
193
198
|
if (window.__mcpPlatform === 'ext-apps') return true;
|
|
194
|
-
return true;
|
|
199
|
+
if (window.__extAppsInitialized) return true;
|
|
200
|
+
|
|
201
|
+
// Claude MCP Apps mode (2026+) - uses ext-apps protocol
|
|
202
|
+
if (window.__mcpAppsEnabled) return true;
|
|
203
|
+
|
|
204
|
+
// Legacy Claude detection - defer to ClaudeAdapter
|
|
205
|
+
if (window.claude) return false;
|
|
206
|
+
if (window.__claudeArtifact) return false;
|
|
207
|
+
if (window.__mcpPlatform === 'claude') return false;
|
|
208
|
+
if (typeof location !== 'undefined') {
|
|
209
|
+
try {
|
|
210
|
+
var url = new URL(location.href);
|
|
211
|
+
var hostname = url.hostname.toLowerCase();
|
|
212
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
213
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
214
|
+
if (isClaudeHost || isAnthropicHost) return false;
|
|
215
|
+
} catch (e) {
|
|
216
|
+
// If URL parsing fails, fall through to other checks
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Do NOT default to true for any iframe
|
|
221
|
+
return false;
|
|
195
222
|
},
|
|
196
223
|
initialize: function(context) {
|
|
197
224
|
var self = this;
|
|
@@ -233,6 +260,11 @@ var ExtAppsAdapter = {
|
|
|
233
260
|
context.toolInput = params.arguments || {};
|
|
234
261
|
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
235
262
|
break;
|
|
263
|
+
case 'ui/notifications/tool-input-partial':
|
|
264
|
+
// Streaming: merge partial input with existing
|
|
265
|
+
context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
|
|
266
|
+
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
267
|
+
break;
|
|
236
268
|
case 'ui/notifications/tool-result':
|
|
237
269
|
context.toolOutput = params.content;
|
|
238
270
|
context.structuredContent = params.structuredContent;
|
|
@@ -243,18 +275,26 @@ var ExtAppsAdapter = {
|
|
|
243
275
|
Object.assign(context.hostContext, params);
|
|
244
276
|
context.notifyContextChange(params);
|
|
245
277
|
break;
|
|
278
|
+
case 'ui/notifications/cancelled':
|
|
279
|
+
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
280
|
+
break;
|
|
246
281
|
}
|
|
247
282
|
},
|
|
248
283
|
isOriginTrusted: function(origin) {
|
|
249
284
|
if (this.trustedOrigins.length > 0) {
|
|
250
285
|
return this.trustedOrigins.indexOf(origin) !== -1;
|
|
251
286
|
}
|
|
252
|
-
//
|
|
253
|
-
// SECURITY WARNING:
|
|
254
|
-
// message establishes permanent trust. For production, always configure trustedOrigins.
|
|
287
|
+
// Trust-on-first-use: trust first message origin.
|
|
288
|
+
// SECURITY WARNING: For production, always configure trustedOrigins.
|
|
255
289
|
if (!this.trustedOrigin) {
|
|
290
|
+
// Guard against race condition where multiple messages arrive simultaneously
|
|
291
|
+
if (this.originTrustPending) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
256
294
|
if (window.parent !== window && origin) {
|
|
295
|
+
this.originTrustPending = true;
|
|
257
296
|
this.trustedOrigin = origin;
|
|
297
|
+
this.originTrustPending = false; // Reset after successful trust establishment
|
|
258
298
|
return true;
|
|
259
299
|
}
|
|
260
300
|
return false;
|
|
@@ -324,6 +364,34 @@ var ExtAppsAdapter = {
|
|
|
324
364
|
},
|
|
325
365
|
requestClose: function(context) {
|
|
326
366
|
return this.sendRequest('ui/close', {});
|
|
367
|
+
},
|
|
368
|
+
// Extended ext-apps methods (full specification)
|
|
369
|
+
updateModelContext: function(context, data, merge) {
|
|
370
|
+
if (!this.hostCapabilities.modelContextUpdate) {
|
|
371
|
+
return Promise.reject(new Error('Model context update not supported'));
|
|
372
|
+
}
|
|
373
|
+
return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
|
|
374
|
+
},
|
|
375
|
+
log: function(context, level, message, data) {
|
|
376
|
+
if (!this.hostCapabilities.logging) {
|
|
377
|
+
// Fallback to console logging if host doesn't support it
|
|
378
|
+
var logFn = console[level] || console.log;
|
|
379
|
+
logFn('[Widget] ' + message, data);
|
|
380
|
+
return Promise.resolve();
|
|
381
|
+
}
|
|
382
|
+
return this.sendRequest('ui/log', { level: level, message: message, data: data });
|
|
383
|
+
},
|
|
384
|
+
registerTool: function(context, name, description, inputSchema) {
|
|
385
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
386
|
+
return Promise.reject(new Error('Widget tool registration not supported'));
|
|
387
|
+
}
|
|
388
|
+
return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
|
|
389
|
+
},
|
|
390
|
+
unregisterTool: function(context, name) {
|
|
391
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
392
|
+
return Promise.reject(new Error('Widget tool unregistration not supported'));
|
|
393
|
+
}
|
|
394
|
+
return this.sendRequest('ui/unregisterTool', { name: name });
|
|
327
395
|
}
|
|
328
396
|
};
|
|
329
397
|
`.trim();
|
|
@@ -343,12 +411,26 @@ var ClaudeAdapter = {
|
|
|
343
411
|
}),
|
|
344
412
|
canHandle: function() {
|
|
345
413
|
if (typeof window === 'undefined') return false;
|
|
414
|
+
|
|
415
|
+
// If MCP Apps is enabled, let ext-apps adapter handle it
|
|
416
|
+
if (window.__mcpAppsEnabled) return false;
|
|
417
|
+
if (window.__mcpPlatform === 'ext-apps') return false;
|
|
418
|
+
if (window.__extAppsInitialized) return false;
|
|
419
|
+
|
|
420
|
+
// Legacy Claude detection
|
|
346
421
|
if (window.__mcpPlatform === 'claude') return true;
|
|
347
422
|
if (window.claude) return true;
|
|
348
423
|
if (window.__claudeArtifact) return true;
|
|
349
424
|
if (typeof location !== 'undefined') {
|
|
350
|
-
|
|
351
|
-
|
|
425
|
+
try {
|
|
426
|
+
var url = new URL(location.href);
|
|
427
|
+
var hostname = url.hostname.toLowerCase();
|
|
428
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
429
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
430
|
+
if (isClaudeHost || isAnthropicHost) return true;
|
|
431
|
+
} catch (e) {
|
|
432
|
+
// If URL parsing fails, fall through
|
|
433
|
+
}
|
|
352
434
|
}
|
|
353
435
|
return false;
|
|
354
436
|
},
|
|
@@ -693,6 +775,42 @@ FrontMcpBridge.prototype.requestClose = function() {
|
|
|
693
775
|
return this._adapter.requestClose(this._context);
|
|
694
776
|
};
|
|
695
777
|
|
|
778
|
+
// Extended ext-apps methods (full specification)
|
|
779
|
+
FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
|
|
780
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
781
|
+
if (!this._adapter.updateModelContext) {
|
|
782
|
+
return Promise.reject(new Error('updateModelContext not supported on this platform'));
|
|
783
|
+
}
|
|
784
|
+
return this._adapter.updateModelContext(this._context, context, merge);
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
FrontMcpBridge.prototype.log = function(level, message, data) {
|
|
788
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
789
|
+
if (!this._adapter.log) {
|
|
790
|
+
// Fallback to console
|
|
791
|
+
var logFn = console[level] || console.log;
|
|
792
|
+
logFn('[Widget] ' + message, data);
|
|
793
|
+
return Promise.resolve();
|
|
794
|
+
}
|
|
795
|
+
return this._adapter.log(this._context, level, message, data);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
|
|
799
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
800
|
+
if (!this._adapter.registerTool) {
|
|
801
|
+
return Promise.reject(new Error('registerTool not supported on this platform'));
|
|
802
|
+
}
|
|
803
|
+
return this._adapter.registerTool(this._context, name, description, inputSchema);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
FrontMcpBridge.prototype.unregisterTool = function(name) {
|
|
807
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
808
|
+
if (!this._adapter.unregisterTool) {
|
|
809
|
+
return Promise.reject(new Error('unregisterTool not supported on this platform'));
|
|
810
|
+
}
|
|
811
|
+
return this._adapter.unregisterTool(this._context, name);
|
|
812
|
+
};
|
|
813
|
+
|
|
696
814
|
FrontMcpBridge.prototype.setWidgetState = function(state) {
|
|
697
815
|
Object.assign(this._context.widgetState, state);
|
|
698
816
|
this._saveWidgetState();
|
package/esm/build/index.mjs
CHANGED
|
@@ -2907,16 +2907,43 @@ var ExtAppsAdapter = {
|
|
|
2907
2907
|
capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
|
|
2908
2908
|
trustedOrigins: ${originsArray},
|
|
2909
2909
|
trustedOrigin: null,
|
|
2910
|
+
originTrustPending: false,
|
|
2910
2911
|
pendingRequests: {},
|
|
2911
2912
|
requestId: 0,
|
|
2912
2913
|
hostCapabilities: {},
|
|
2913
2914
|
canHandle: function() {
|
|
2914
2915
|
if (typeof window === 'undefined') return false;
|
|
2915
2916
|
if (window.parent === window) return false;
|
|
2916
|
-
|
|
2917
|
+
|
|
2918
|
+
// Check for OpenAI SDK - defer to OpenAIAdapter
|
|
2919
|
+
if (window.openai && window.openai.canvas) return false;
|
|
2917
2920
|
if (window.openai && typeof window.openai.callTool === 'function') return false;
|
|
2921
|
+
|
|
2922
|
+
// Explicit ext-apps marker
|
|
2918
2923
|
if (window.__mcpPlatform === 'ext-apps') return true;
|
|
2919
|
-
return true;
|
|
2924
|
+
if (window.__extAppsInitialized) return true;
|
|
2925
|
+
|
|
2926
|
+
// Claude MCP Apps mode (2026+) - uses ext-apps protocol
|
|
2927
|
+
if (window.__mcpAppsEnabled) return true;
|
|
2928
|
+
|
|
2929
|
+
// Legacy Claude detection - defer to ClaudeAdapter
|
|
2930
|
+
if (window.claude) return false;
|
|
2931
|
+
if (window.__claudeArtifact) return false;
|
|
2932
|
+
if (window.__mcpPlatform === 'claude') return false;
|
|
2933
|
+
if (typeof location !== 'undefined') {
|
|
2934
|
+
try {
|
|
2935
|
+
var url = new URL(location.href);
|
|
2936
|
+
var hostname = url.hostname.toLowerCase();
|
|
2937
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
2938
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
2939
|
+
if (isClaudeHost || isAnthropicHost) return false;
|
|
2940
|
+
} catch (e) {
|
|
2941
|
+
// If URL parsing fails, fall through to other checks
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
|
|
2945
|
+
// Do NOT default to true for any iframe
|
|
2946
|
+
return false;
|
|
2920
2947
|
},
|
|
2921
2948
|
initialize: function(context) {
|
|
2922
2949
|
var self = this;
|
|
@@ -2958,6 +2985,11 @@ var ExtAppsAdapter = {
|
|
|
2958
2985
|
context.toolInput = params.arguments || {};
|
|
2959
2986
|
window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
|
|
2960
2987
|
break;
|
|
2988
|
+
case 'ui/notifications/tool-input-partial':
|
|
2989
|
+
// Streaming: merge partial input with existing
|
|
2990
|
+
context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
|
|
2991
|
+
window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
|
|
2992
|
+
break;
|
|
2961
2993
|
case 'ui/notifications/tool-result':
|
|
2962
2994
|
context.toolOutput = params.content;
|
|
2963
2995
|
context.structuredContent = params.structuredContent;
|
|
@@ -2968,18 +3000,26 @@ var ExtAppsAdapter = {
|
|
|
2968
3000
|
Object.assign(context.hostContext, params);
|
|
2969
3001
|
context.notifyContextChange(params);
|
|
2970
3002
|
break;
|
|
3003
|
+
case 'ui/notifications/cancelled':
|
|
3004
|
+
window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
|
|
3005
|
+
break;
|
|
2971
3006
|
}
|
|
2972
3007
|
},
|
|
2973
3008
|
isOriginTrusted: function(origin) {
|
|
2974
3009
|
if (this.trustedOrigins.length > 0) {
|
|
2975
3010
|
return this.trustedOrigins.indexOf(origin) !== -1;
|
|
2976
3011
|
}
|
|
2977
|
-
//
|
|
2978
|
-
// SECURITY WARNING:
|
|
2979
|
-
// message establishes permanent trust. For production, always configure trustedOrigins.
|
|
3012
|
+
// Trust-on-first-use: trust first message origin.
|
|
3013
|
+
// SECURITY WARNING: For production, always configure trustedOrigins.
|
|
2980
3014
|
if (!this.trustedOrigin) {
|
|
3015
|
+
// Guard against race condition where multiple messages arrive simultaneously
|
|
3016
|
+
if (this.originTrustPending) {
|
|
3017
|
+
return false;
|
|
3018
|
+
}
|
|
2981
3019
|
if (window.parent !== window && origin) {
|
|
3020
|
+
this.originTrustPending = true;
|
|
2982
3021
|
this.trustedOrigin = origin;
|
|
3022
|
+
this.originTrustPending = false; // Reset after successful trust establishment
|
|
2983
3023
|
return true;
|
|
2984
3024
|
}
|
|
2985
3025
|
return false;
|
|
@@ -3049,6 +3089,34 @@ var ExtAppsAdapter = {
|
|
|
3049
3089
|
},
|
|
3050
3090
|
requestClose: function(context) {
|
|
3051
3091
|
return this.sendRequest('ui/close', {});
|
|
3092
|
+
},
|
|
3093
|
+
// Extended ext-apps methods (full specification)
|
|
3094
|
+
updateModelContext: function(context, data, merge) {
|
|
3095
|
+
if (!this.hostCapabilities.modelContextUpdate) {
|
|
3096
|
+
return Promise.reject(new Error('Model context update not supported'));
|
|
3097
|
+
}
|
|
3098
|
+
return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
|
|
3099
|
+
},
|
|
3100
|
+
log: function(context, level, message, data) {
|
|
3101
|
+
if (!this.hostCapabilities.logging) {
|
|
3102
|
+
// Fallback to console logging if host doesn't support it
|
|
3103
|
+
var logFn = console[level] || console.log;
|
|
3104
|
+
logFn('[Widget] ' + message, data);
|
|
3105
|
+
return Promise.resolve();
|
|
3106
|
+
}
|
|
3107
|
+
return this.sendRequest('ui/log', { level: level, message: message, data: data });
|
|
3108
|
+
},
|
|
3109
|
+
registerTool: function(context, name, description, inputSchema) {
|
|
3110
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
3111
|
+
return Promise.reject(new Error('Widget tool registration not supported'));
|
|
3112
|
+
}
|
|
3113
|
+
return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
|
|
3114
|
+
},
|
|
3115
|
+
unregisterTool: function(context, name) {
|
|
3116
|
+
if (!this.hostCapabilities.widgetTools) {
|
|
3117
|
+
return Promise.reject(new Error('Widget tool unregistration not supported'));
|
|
3118
|
+
}
|
|
3119
|
+
return this.sendRequest('ui/unregisterTool', { name: name });
|
|
3052
3120
|
}
|
|
3053
3121
|
};
|
|
3054
3122
|
`.trim();
|
|
@@ -3068,12 +3136,26 @@ var ClaudeAdapter = {
|
|
|
3068
3136
|
}),
|
|
3069
3137
|
canHandle: function() {
|
|
3070
3138
|
if (typeof window === 'undefined') return false;
|
|
3139
|
+
|
|
3140
|
+
// If MCP Apps is enabled, let ext-apps adapter handle it
|
|
3141
|
+
if (window.__mcpAppsEnabled) return false;
|
|
3142
|
+
if (window.__mcpPlatform === 'ext-apps') return false;
|
|
3143
|
+
if (window.__extAppsInitialized) return false;
|
|
3144
|
+
|
|
3145
|
+
// Legacy Claude detection
|
|
3071
3146
|
if (window.__mcpPlatform === 'claude') return true;
|
|
3072
3147
|
if (window.claude) return true;
|
|
3073
3148
|
if (window.__claudeArtifact) return true;
|
|
3074
3149
|
if (typeof location !== 'undefined') {
|
|
3075
|
-
|
|
3076
|
-
|
|
3150
|
+
try {
|
|
3151
|
+
var url = new URL(location.href);
|
|
3152
|
+
var hostname = url.hostname.toLowerCase();
|
|
3153
|
+
var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
|
|
3154
|
+
var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
|
|
3155
|
+
if (isClaudeHost || isAnthropicHost) return true;
|
|
3156
|
+
} catch (e) {
|
|
3157
|
+
// If URL parsing fails, fall through
|
|
3158
|
+
}
|
|
3077
3159
|
}
|
|
3078
3160
|
return false;
|
|
3079
3161
|
},
|
|
@@ -3418,6 +3500,42 @@ FrontMcpBridge.prototype.requestClose = function() {
|
|
|
3418
3500
|
return this._adapter.requestClose(this._context);
|
|
3419
3501
|
};
|
|
3420
3502
|
|
|
3503
|
+
// Extended ext-apps methods (full specification)
|
|
3504
|
+
FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
|
|
3505
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
3506
|
+
if (!this._adapter.updateModelContext) {
|
|
3507
|
+
return Promise.reject(new Error('updateModelContext not supported on this platform'));
|
|
3508
|
+
}
|
|
3509
|
+
return this._adapter.updateModelContext(this._context, context, merge);
|
|
3510
|
+
};
|
|
3511
|
+
|
|
3512
|
+
FrontMcpBridge.prototype.log = function(level, message, data) {
|
|
3513
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
3514
|
+
if (!this._adapter.log) {
|
|
3515
|
+
// Fallback to console
|
|
3516
|
+
var logFn = console[level] || console.log;
|
|
3517
|
+
logFn('[Widget] ' + message, data);
|
|
3518
|
+
return Promise.resolve();
|
|
3519
|
+
}
|
|
3520
|
+
return this._adapter.log(this._context, level, message, data);
|
|
3521
|
+
};
|
|
3522
|
+
|
|
3523
|
+
FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
|
|
3524
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
3525
|
+
if (!this._adapter.registerTool) {
|
|
3526
|
+
return Promise.reject(new Error('registerTool not supported on this platform'));
|
|
3527
|
+
}
|
|
3528
|
+
return this._adapter.registerTool(this._context, name, description, inputSchema);
|
|
3529
|
+
};
|
|
3530
|
+
|
|
3531
|
+
FrontMcpBridge.prototype.unregisterTool = function(name) {
|
|
3532
|
+
if (!this._adapter) return Promise.reject(new Error('Not initialized'));
|
|
3533
|
+
if (!this._adapter.unregisterTool) {
|
|
3534
|
+
return Promise.reject(new Error('unregisterTool not supported on this platform'));
|
|
3535
|
+
}
|
|
3536
|
+
return this._adapter.unregisterTool(this._context, name);
|
|
3537
|
+
};
|
|
3538
|
+
|
|
3421
3539
|
FrontMcpBridge.prototype.setWidgetState = function(state) {
|
|
3422
3540
|
Object.assign(this._context.widgetState, state);
|
|
3423
3541
|
this._saveWidgetState();
|