@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.
@@ -1108,16 +1108,43 @@ var ExtAppsAdapter = {
1108
1108
  capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
1109
1109
  trustedOrigins: ${originsArray},
1110
1110
  trustedOrigin: null,
1111
+ originTrustPending: false,
1111
1112
  pendingRequests: {},
1112
1113
  requestId: 0,
1113
1114
  hostCapabilities: {},
1114
1115
  canHandle: function() {
1115
1116
  if (typeof window === 'undefined') return false;
1116
1117
  if (window.parent === window) return false;
1117
- // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
1118
+
1119
+ // Check for OpenAI SDK - defer to OpenAIAdapter
1120
+ if (window.openai && window.openai.canvas) return false;
1118
1121
  if (window.openai && typeof window.openai.callTool === 'function') return false;
1122
+
1123
+ // Explicit ext-apps marker
1119
1124
  if (window.__mcpPlatform === 'ext-apps') return true;
1120
- return true;
1125
+ if (window.__extAppsInitialized) return true;
1126
+
1127
+ // Claude MCP Apps mode (2026+) - uses ext-apps protocol
1128
+ if (window.__mcpAppsEnabled) return true;
1129
+
1130
+ // Legacy Claude detection - defer to ClaudeAdapter
1131
+ if (window.claude) return false;
1132
+ if (window.__claudeArtifact) return false;
1133
+ if (window.__mcpPlatform === 'claude') return false;
1134
+ if (typeof location !== 'undefined') {
1135
+ try {
1136
+ var url = new URL(location.href);
1137
+ var hostname = url.hostname.toLowerCase();
1138
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
1139
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
1140
+ if (isClaudeHost || isAnthropicHost) return false;
1141
+ } catch (e) {
1142
+ // If URL parsing fails, fall through to other checks
1143
+ }
1144
+ }
1145
+
1146
+ // Do NOT default to true for any iframe
1147
+ return false;
1121
1148
  },
1122
1149
  initialize: function(context) {
1123
1150
  var self = this;
@@ -1159,6 +1186,11 @@ var ExtAppsAdapter = {
1159
1186
  context.toolInput = params.arguments || {};
1160
1187
  window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
1161
1188
  break;
1189
+ case 'ui/notifications/tool-input-partial':
1190
+ // Streaming: merge partial input with existing
1191
+ context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
1192
+ window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
1193
+ break;
1162
1194
  case 'ui/notifications/tool-result':
1163
1195
  context.toolOutput = params.content;
1164
1196
  context.structuredContent = params.structuredContent;
@@ -1169,18 +1201,26 @@ var ExtAppsAdapter = {
1169
1201
  Object.assign(context.hostContext, params);
1170
1202
  context.notifyContextChange(params);
1171
1203
  break;
1204
+ case 'ui/notifications/cancelled':
1205
+ window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
1206
+ break;
1172
1207
  }
1173
1208
  },
1174
1209
  isOriginTrusted: function(origin) {
1175
1210
  if (this.trustedOrigins.length > 0) {
1176
1211
  return this.trustedOrigins.indexOf(origin) !== -1;
1177
1212
  }
1178
- // When no trusted origins configured, trust first message origin (trust-on-first-use).
1179
- // SECURITY WARNING: This creates a race condition - whichever iframe sends the first
1180
- // message establishes permanent trust. For production, always configure trustedOrigins.
1213
+ // Trust-on-first-use: trust first message origin.
1214
+ // SECURITY WARNING: For production, always configure trustedOrigins.
1181
1215
  if (!this.trustedOrigin) {
1216
+ // Guard against race condition where multiple messages arrive simultaneously
1217
+ if (this.originTrustPending) {
1218
+ return false;
1219
+ }
1182
1220
  if (window.parent !== window && origin) {
1221
+ this.originTrustPending = true;
1183
1222
  this.trustedOrigin = origin;
1223
+ this.originTrustPending = false; // Reset after successful trust establishment
1184
1224
  return true;
1185
1225
  }
1186
1226
  return false;
@@ -1250,6 +1290,34 @@ var ExtAppsAdapter = {
1250
1290
  },
1251
1291
  requestClose: function(context) {
1252
1292
  return this.sendRequest('ui/close', {});
1293
+ },
1294
+ // Extended ext-apps methods (full specification)
1295
+ updateModelContext: function(context, data, merge) {
1296
+ if (!this.hostCapabilities.modelContextUpdate) {
1297
+ return Promise.reject(new Error('Model context update not supported'));
1298
+ }
1299
+ return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
1300
+ },
1301
+ log: function(context, level, message, data) {
1302
+ if (!this.hostCapabilities.logging) {
1303
+ // Fallback to console logging if host doesn't support it
1304
+ var logFn = console[level] || console.log;
1305
+ logFn('[Widget] ' + message, data);
1306
+ return Promise.resolve();
1307
+ }
1308
+ return this.sendRequest('ui/log', { level: level, message: message, data: data });
1309
+ },
1310
+ registerTool: function(context, name, description, inputSchema) {
1311
+ if (!this.hostCapabilities.widgetTools) {
1312
+ return Promise.reject(new Error('Widget tool registration not supported'));
1313
+ }
1314
+ return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
1315
+ },
1316
+ unregisterTool: function(context, name) {
1317
+ if (!this.hostCapabilities.widgetTools) {
1318
+ return Promise.reject(new Error('Widget tool unregistration not supported'));
1319
+ }
1320
+ return this.sendRequest('ui/unregisterTool', { name: name });
1253
1321
  }
1254
1322
  };
1255
1323
  `.trim();
@@ -1269,12 +1337,26 @@ var ClaudeAdapter = {
1269
1337
  }),
1270
1338
  canHandle: function() {
1271
1339
  if (typeof window === 'undefined') return false;
1340
+
1341
+ // If MCP Apps is enabled, let ext-apps adapter handle it
1342
+ if (window.__mcpAppsEnabled) return false;
1343
+ if (window.__mcpPlatform === 'ext-apps') return false;
1344
+ if (window.__extAppsInitialized) return false;
1345
+
1346
+ // Legacy Claude detection
1272
1347
  if (window.__mcpPlatform === 'claude') return true;
1273
1348
  if (window.claude) return true;
1274
1349
  if (window.__claudeArtifact) return true;
1275
1350
  if (typeof location !== 'undefined') {
1276
- var href = location.href;
1277
- if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
1351
+ try {
1352
+ var url = new URL(location.href);
1353
+ var hostname = url.hostname.toLowerCase();
1354
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
1355
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
1356
+ if (isClaudeHost || isAnthropicHost) return true;
1357
+ } catch (e) {
1358
+ // If URL parsing fails, fall through
1359
+ }
1278
1360
  }
1279
1361
  return false;
1280
1362
  },
@@ -1619,6 +1701,42 @@ FrontMcpBridge.prototype.requestClose = function() {
1619
1701
  return this._adapter.requestClose(this._context);
1620
1702
  };
1621
1703
 
1704
+ // Extended ext-apps methods (full specification)
1705
+ FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
1706
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1707
+ if (!this._adapter.updateModelContext) {
1708
+ return Promise.reject(new Error('updateModelContext not supported on this platform'));
1709
+ }
1710
+ return this._adapter.updateModelContext(this._context, context, merge);
1711
+ };
1712
+
1713
+ FrontMcpBridge.prototype.log = function(level, message, data) {
1714
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1715
+ if (!this._adapter.log) {
1716
+ // Fallback to console
1717
+ var logFn = console[level] || console.log;
1718
+ logFn('[Widget] ' + message, data);
1719
+ return Promise.resolve();
1720
+ }
1721
+ return this._adapter.log(this._context, level, message, data);
1722
+ };
1723
+
1724
+ FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
1725
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1726
+ if (!this._adapter.registerTool) {
1727
+ return Promise.reject(new Error('registerTool not supported on this platform'));
1728
+ }
1729
+ return this._adapter.registerTool(this._context, name, description, inputSchema);
1730
+ };
1731
+
1732
+ FrontMcpBridge.prototype.unregisterTool = function(name) {
1733
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1734
+ if (!this._adapter.unregisterTool) {
1735
+ return Promise.reject(new Error('unregisterTool not supported on this platform'));
1736
+ }
1737
+ return this._adapter.unregisterTool(this._context, name);
1738
+ };
1739
+
1622
1740
  FrontMcpBridge.prototype.setWidgetState = function(state) {
1623
1741
  Object.assign(this._context.widgetState, state);
1624
1742
  this._saveWidgetState();
@@ -808,16 +808,43 @@ var ExtAppsAdapter = {
808
808
  capabilities: Object.assign({}, DEFAULT_CAPABILITIES, { canPersistState: true, hasNetworkAccess: true }),
809
809
  trustedOrigins: ${originsArray},
810
810
  trustedOrigin: null,
811
+ originTrustPending: false,
811
812
  pendingRequests: {},
812
813
  requestId: 0,
813
814
  hostCapabilities: {},
814
815
  canHandle: function() {
815
816
  if (typeof window === 'undefined') return false;
816
817
  if (window.parent === window) return false;
817
- // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
818
+
819
+ // Check for OpenAI SDK - defer to OpenAIAdapter
820
+ if (window.openai && window.openai.canvas) return false;
818
821
  if (window.openai && typeof window.openai.callTool === 'function') return false;
822
+
823
+ // Explicit ext-apps marker
819
824
  if (window.__mcpPlatform === 'ext-apps') return true;
820
- return true;
825
+ if (window.__extAppsInitialized) return true;
826
+
827
+ // Claude MCP Apps mode (2026+) - uses ext-apps protocol
828
+ if (window.__mcpAppsEnabled) return true;
829
+
830
+ // Legacy Claude detection - defer to ClaudeAdapter
831
+ if (window.claude) return false;
832
+ if (window.__claudeArtifact) return false;
833
+ if (window.__mcpPlatform === 'claude') return false;
834
+ if (typeof location !== 'undefined') {
835
+ try {
836
+ var url = new URL(location.href);
837
+ var hostname = url.hostname.toLowerCase();
838
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
839
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
840
+ if (isClaudeHost || isAnthropicHost) return false;
841
+ } catch (e) {
842
+ // If URL parsing fails, fall through to other checks
843
+ }
844
+ }
845
+
846
+ // Do NOT default to true for any iframe
847
+ return false;
821
848
  },
822
849
  initialize: function(context) {
823
850
  var self = this;
@@ -859,6 +886,11 @@ var ExtAppsAdapter = {
859
886
  context.toolInput = params.arguments || {};
860
887
  window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
861
888
  break;
889
+ case 'ui/notifications/tool-input-partial':
890
+ // Streaming: merge partial input with existing
891
+ context.toolInput = Object.assign({}, context.toolInput, params.arguments || {});
892
+ window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
893
+ break;
862
894
  case 'ui/notifications/tool-result':
863
895
  context.toolOutput = params.content;
864
896
  context.structuredContent = params.structuredContent;
@@ -869,18 +901,26 @@ var ExtAppsAdapter = {
869
901
  Object.assign(context.hostContext, params);
870
902
  context.notifyContextChange(params);
871
903
  break;
904
+ case 'ui/notifications/cancelled':
905
+ window.dispatchEvent(new CustomEvent('tool:cancelled', { detail: { reason: params.reason } }));
906
+ break;
872
907
  }
873
908
  },
874
909
  isOriginTrusted: function(origin) {
875
910
  if (this.trustedOrigins.length > 0) {
876
911
  return this.trustedOrigins.indexOf(origin) !== -1;
877
912
  }
878
- // When no trusted origins configured, trust first message origin (trust-on-first-use).
879
- // SECURITY WARNING: This creates a race condition - whichever iframe sends the first
880
- // message establishes permanent trust. For production, always configure trustedOrigins.
913
+ // Trust-on-first-use: trust first message origin.
914
+ // SECURITY WARNING: For production, always configure trustedOrigins.
881
915
  if (!this.trustedOrigin) {
916
+ // Guard against race condition where multiple messages arrive simultaneously
917
+ if (this.originTrustPending) {
918
+ return false;
919
+ }
882
920
  if (window.parent !== window && origin) {
921
+ this.originTrustPending = true;
883
922
  this.trustedOrigin = origin;
923
+ this.originTrustPending = false; // Reset after successful trust establishment
884
924
  return true;
885
925
  }
886
926
  return false;
@@ -950,6 +990,34 @@ var ExtAppsAdapter = {
950
990
  },
951
991
  requestClose: function(context) {
952
992
  return this.sendRequest('ui/close', {});
993
+ },
994
+ // Extended ext-apps methods (full specification)
995
+ updateModelContext: function(context, data, merge) {
996
+ if (!this.hostCapabilities.modelContextUpdate) {
997
+ return Promise.reject(new Error('Model context update not supported'));
998
+ }
999
+ return this.sendRequest('ui/updateModelContext', { context: data, merge: merge !== false });
1000
+ },
1001
+ log: function(context, level, message, data) {
1002
+ if (!this.hostCapabilities.logging) {
1003
+ // Fallback to console logging if host doesn't support it
1004
+ var logFn = console[level] || console.log;
1005
+ logFn('[Widget] ' + message, data);
1006
+ return Promise.resolve();
1007
+ }
1008
+ return this.sendRequest('ui/log', { level: level, message: message, data: data });
1009
+ },
1010
+ registerTool: function(context, name, description, inputSchema) {
1011
+ if (!this.hostCapabilities.widgetTools) {
1012
+ return Promise.reject(new Error('Widget tool registration not supported'));
1013
+ }
1014
+ return this.sendRequest('ui/registerTool', { name: name, description: description, inputSchema: inputSchema });
1015
+ },
1016
+ unregisterTool: function(context, name) {
1017
+ if (!this.hostCapabilities.widgetTools) {
1018
+ return Promise.reject(new Error('Widget tool unregistration not supported'));
1019
+ }
1020
+ return this.sendRequest('ui/unregisterTool', { name: name });
953
1021
  }
954
1022
  };
955
1023
  `.trim();
@@ -969,12 +1037,26 @@ var ClaudeAdapter = {
969
1037
  }),
970
1038
  canHandle: function() {
971
1039
  if (typeof window === 'undefined') return false;
1040
+
1041
+ // If MCP Apps is enabled, let ext-apps adapter handle it
1042
+ if (window.__mcpAppsEnabled) return false;
1043
+ if (window.__mcpPlatform === 'ext-apps') return false;
1044
+ if (window.__extAppsInitialized) return false;
1045
+
1046
+ // Legacy Claude detection
972
1047
  if (window.__mcpPlatform === 'claude') return true;
973
1048
  if (window.claude) return true;
974
1049
  if (window.__claudeArtifact) return true;
975
1050
  if (typeof location !== 'undefined') {
976
- var href = location.href;
977
- if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
1051
+ try {
1052
+ var url = new URL(location.href);
1053
+ var hostname = url.hostname.toLowerCase();
1054
+ var isClaudeHost = hostname === 'claude.ai' || (hostname.length > 10 && hostname.slice(-10) === '.claude.ai');
1055
+ var isAnthropicHost = hostname === 'anthropic.com' || (hostname.length > 14 && hostname.slice(-14) === '.anthropic.com');
1056
+ if (isClaudeHost || isAnthropicHost) return true;
1057
+ } catch (e) {
1058
+ // If URL parsing fails, fall through
1059
+ }
978
1060
  }
979
1061
  return false;
980
1062
  },
@@ -1319,6 +1401,42 @@ FrontMcpBridge.prototype.requestClose = function() {
1319
1401
  return this._adapter.requestClose(this._context);
1320
1402
  };
1321
1403
 
1404
+ // Extended ext-apps methods (full specification)
1405
+ FrontMcpBridge.prototype.updateModelContext = function(context, merge) {
1406
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1407
+ if (!this._adapter.updateModelContext) {
1408
+ return Promise.reject(new Error('updateModelContext not supported on this platform'));
1409
+ }
1410
+ return this._adapter.updateModelContext(this._context, context, merge);
1411
+ };
1412
+
1413
+ FrontMcpBridge.prototype.log = function(level, message, data) {
1414
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1415
+ if (!this._adapter.log) {
1416
+ // Fallback to console
1417
+ var logFn = console[level] || console.log;
1418
+ logFn('[Widget] ' + message, data);
1419
+ return Promise.resolve();
1420
+ }
1421
+ return this._adapter.log(this._context, level, message, data);
1422
+ };
1423
+
1424
+ FrontMcpBridge.prototype.registerTool = function(name, description, inputSchema) {
1425
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1426
+ if (!this._adapter.registerTool) {
1427
+ return Promise.reject(new Error('registerTool not supported on this platform'));
1428
+ }
1429
+ return this._adapter.registerTool(this._context, name, description, inputSchema);
1430
+ };
1431
+
1432
+ FrontMcpBridge.prototype.unregisterTool = function(name) {
1433
+ if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1434
+ if (!this._adapter.unregisterTool) {
1435
+ return Promise.reject(new Error('unregisterTool not supported on this platform'));
1436
+ }
1437
+ return this._adapter.unregisterTool(this._context, name);
1438
+ };
1439
+
1322
1440
  FrontMcpBridge.prototype.setWidgetState = function(state) {
1323
1441
  Object.assign(this._context.widgetState, state);
1324
1442
  this._saveWidgetState();