@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.
@@ -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 displayModeMap = {
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 displayModeMap = {
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 displayModeMap = {
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
- // Claude, Gemini, IDEs don't need discovery metadata
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
- // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
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
- // When no trusted origins configured, trust first message origin (trust-on-first-use).
253
- // SECURITY WARNING: This creates a race condition - whichever iframe sends the first
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
- var href = location.href;
351
- if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
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();
@@ -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
- // Check for OpenAI SDK (window.openai.callTool) - defer to OpenAIAdapter
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
- // When no trusted origins configured, trust first message origin (trust-on-first-use).
2978
- // SECURITY WARNING: This creates a race condition - whichever iframe sends the first
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
- var href = location.href;
3076
- if (href.indexOf('claude.ai') !== -1 || href.indexOf('anthropic.com') !== -1) return true;
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();