@mcp-b/global 1.3.0 → 1.5.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IframeChildTransport, TabServerTransport } from "@mcp-b/transports";
2
2
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, Server } from "@mcp-b/webmcp-ts-sdk";
3
- import { jsonSchemaToZod } from "@n8n/json-schema-to-zod";
3
+ import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
4
4
  import { z } from "zod";
5
5
  import { zodToJsonSchema as zodToJsonSchema$1 } from "zod-to-json-schema";
6
6
 
@@ -89,6 +89,24 @@ function createLogger(namespace) {
89
89
  };
90
90
  }
91
91
 
92
+ //#endregion
93
+ //#region src/tab-server-capabilities.ts
94
+ function getTabServerCapabilities(server) {
95
+ return server.server;
96
+ }
97
+ function requireCreateMessageCapability(server) {
98
+ const capabilities = getTabServerCapabilities(server);
99
+ const createMessage = capabilities?.createMessage;
100
+ if (!createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
101
+ return createMessage.bind(capabilities);
102
+ }
103
+ function requireElicitInputCapability(server) {
104
+ const capabilities = getTabServerCapabilities(server);
105
+ const elicitInput = capabilities?.elicitInput;
106
+ if (!elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
107
+ return elicitInput.bind(capabilities);
108
+ }
109
+
92
110
  //#endregion
93
111
  //#region src/validation.ts
94
112
  const logger$2 = createLogger("WebModelContext");
@@ -159,27 +177,17 @@ function validateWithZod(data, validator) {
159
177
  }
160
178
 
161
179
  //#endregion
162
- //#region src/global.ts
163
- const logger$1 = createLogger("WebModelContext");
180
+ //#region src/native-adapter.ts
164
181
  const nativeLogger = createLogger("NativeAdapter");
165
- const bridgeLogger = createLogger("MCPBridge");
166
- const testingLogger = createLogger("ModelContextTesting");
167
- /**
168
- * Marker property name used to identify polyfill implementations.
169
- * This constant ensures single source of truth for the marker used in
170
- * both detection (detectNativeAPI) and definition (WebModelContextTesting).
171
- */
172
- const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
182
+ const testingLogger$1 = createLogger("ModelContextTesting");
183
+ const POLYFILL_MARKER_PROPERTY$1 = "__isWebMCPPolyfill";
184
+ const CONSUMER_SHIM_MARKER_PROPERTY = "__webMCPConsumerShimInstalled";
185
+ const CONSUMER_CALL_TOOL_SHIM_MARKER_PROPERTY = "__webMCPCallToolShimInstalled";
186
+ const MODEL_CONTEXT_TESTING_DEPRECATION_MESSAGE = "navigator.modelContextTesting is deprecated. Use navigator.modelContext.callTool() and addEventListener('toolschanged', ...) instead.";
173
187
  /**
174
188
  * Detect if the native Chromium Web Model Context API is available.
175
189
  * Checks for both navigator.modelContext and navigator.modelContextTesting,
176
190
  * and verifies they are native implementations (not polyfills).
177
- *
178
- * Detection uses a marker property (`__isWebMCPPolyfill`) on the testing API
179
- * to reliably distinguish polyfills from native implementations. This approach
180
- * works correctly even when class names are minified in production builds.
181
- *
182
- * @returns Detection result with flags for native context and testing API availability
183
191
  */
184
192
  function detectNativeAPI() {
185
193
  /* c8 ignore next 2 */
@@ -193,7 +201,7 @@ function detectNativeAPI() {
193
201
  hasNativeContext: Boolean(modelContext),
194
202
  hasNativeTesting: Boolean(modelContextTesting)
195
203
  };
196
- if (POLYFILL_MARKER_PROPERTY in modelContextTesting && modelContextTesting[POLYFILL_MARKER_PROPERTY] === true) return {
204
+ if (POLYFILL_MARKER_PROPERTY$1 in modelContextTesting && modelContextTesting[POLYFILL_MARKER_PROPERTY$1] === true) return {
197
205
  hasNativeContext: false,
198
206
  hasNativeTesting: false
199
207
  };
@@ -203,62 +211,146 @@ function detectNativeAPI() {
203
211
  };
204
212
  }
205
213
  /**
214
+ * Installs a deprecation getter for navigator.modelContextTesting.
215
+ * Emits a warning on first access while preserving compatibility behavior.
216
+ */
217
+ function installDeprecatedTestingAccessor(modelContextTesting) {
218
+ let hasWarned = false;
219
+ try {
220
+ Object.defineProperty(window.navigator, "modelContextTesting", {
221
+ configurable: true,
222
+ enumerable: true,
223
+ get() {
224
+ if (!hasWarned) {
225
+ testingLogger$1.warn(MODEL_CONTEXT_TESTING_DEPRECATION_MESSAGE);
226
+ hasWarned = true;
227
+ }
228
+ return modelContextTesting;
229
+ }
230
+ });
231
+ } catch (error) {
232
+ testingLogger$1.warn("Failed to install modelContextTesting deprecation accessor:", error);
233
+ }
234
+ }
235
+ /**
236
+ * Dispatches a tools-changed event on a modelContext object.
237
+ */
238
+ function dispatchToolsChangedEvent(modelContext) {
239
+ try {
240
+ modelContext.dispatchEvent(new Event("toolschanged"));
241
+ } catch (error) {
242
+ nativeLogger.warn("Failed to dispatch \"toolschanged\" event:", error);
243
+ }
244
+ }
245
+ /**
246
+ * Installs consumer methods on an existing modelContext object.
247
+ * This allows native producer APIs to expose consumer semantics without replacing the object.
248
+ */
249
+ function installConsumerShim(nativeContext, callTool, options) {
250
+ const target = nativeContext;
251
+ if (target[CONSUMER_SHIM_MARKER_PROPERTY] === true) return;
252
+ let installedCallToolShim = false;
253
+ if (typeof target.callTool !== "function") try {
254
+ Object.defineProperty(target, "callTool", {
255
+ configurable: true,
256
+ writable: true,
257
+ value: callTool
258
+ });
259
+ installedCallToolShim = true;
260
+ } catch (error) {
261
+ nativeLogger.warn("Failed to install modelContext.callTool shim:", error);
262
+ }
263
+ if (!options.hasNativeTesting) {
264
+ const queueToolsChanged = () => {
265
+ if (options.onToolRegistryMutated) {
266
+ queueMicrotask(options.onToolRegistryMutated);
267
+ return;
268
+ }
269
+ queueMicrotask(() => dispatchToolsChangedEvent(nativeContext));
270
+ };
271
+ const wrapMethod = (methodName) => {
272
+ const original = target[methodName];
273
+ if (typeof original !== "function") return;
274
+ try {
275
+ Object.defineProperty(target, methodName, {
276
+ configurable: true,
277
+ writable: true,
278
+ value: (...args) => {
279
+ const result = original.apply(target, args);
280
+ if (methodName === "registerTool" && result && typeof result === "object" && "unregister" in result) {
281
+ const registration = result;
282
+ if (typeof registration.unregister === "function") {
283
+ const originalUnregister = registration.unregister.bind(registration);
284
+ registration.unregister = () => {
285
+ originalUnregister();
286
+ queueToolsChanged();
287
+ };
288
+ }
289
+ }
290
+ queueToolsChanged();
291
+ return result;
292
+ }
293
+ });
294
+ } catch (error) {
295
+ nativeLogger.warn(`Failed to wrap modelContext.${methodName} for "toolschanged":`, error);
296
+ }
297
+ };
298
+ wrapMethod("provideContext");
299
+ wrapMethod("registerTool");
300
+ wrapMethod("unregisterTool");
301
+ wrapMethod("clearContext");
302
+ }
303
+ try {
304
+ Object.defineProperty(target, CONSUMER_SHIM_MARKER_PROPERTY, {
305
+ configurable: true,
306
+ enumerable: false,
307
+ writable: false,
308
+ value: true
309
+ });
310
+ if (installedCallToolShim) Object.defineProperty(target, CONSUMER_CALL_TOOL_SHIM_MARKER_PROPERTY, {
311
+ configurable: true,
312
+ enumerable: false,
313
+ writable: false,
314
+ value: true
315
+ });
316
+ } catch {}
317
+ }
318
+ /**
206
319
  * Adapter that wraps the native Chromium Web Model Context API.
207
320
  * Synchronizes tool changes from the native API to the MCP bridge,
208
321
  * enabling MCP clients to stay in sync with the native tool registry.
209
- *
210
- * Key features:
211
- * - Listens to native tool changes via registerToolsChangedCallback()
212
- * - Syncs native tools to MCP bridge automatically
213
- * - Delegates tool execution to native API
214
- * - Converts native results to MCP ToolResponse format
215
- *
216
- * @class NativeModelContextAdapter
217
- * @implements {InternalModelContext}
218
322
  */
219
323
  var NativeModelContextAdapter = class {
220
324
  nativeContext;
221
325
  nativeTesting;
222
326
  bridge;
223
327
  syncInProgress = false;
224
- /**
225
- * Creates a new NativeModelContextAdapter.
226
- *
227
- * @param {MCPBridge} bridge - The MCP bridge instance
228
- * @param {ModelContext} nativeContext - The native navigator.modelContext
229
- * @param {ModelContextTesting} nativeTesting - The native navigator.modelContextTesting
230
- */
328
+ hasCompletedInitialToolSync = false;
231
329
  constructor(bridge, nativeContext, nativeTesting) {
232
330
  this.bridge = bridge;
233
331
  this.nativeContext = nativeContext;
234
332
  this.nativeTesting = nativeTesting;
235
- this.nativeTesting.registerToolsChangedCallback(() => {
333
+ if (this.nativeTesting) this.nativeTesting.registerToolsChangedCallback(() => {
236
334
  this.syncToolsFromNative();
237
335
  });
238
336
  this.syncToolsFromNative();
239
337
  }
240
- /**
241
- * Synchronizes tools from the native API to the MCP bridge.
242
- * Fetches all tools from navigator.modelContextTesting.listTools()
243
- * and updates the bridge's tool registry.
244
- *
245
- * @private
246
- */
247
338
  syncToolsFromNative() {
248
339
  if (this.syncInProgress) return;
249
340
  this.syncInProgress = true;
250
341
  try {
251
- const nativeTools = this.nativeTesting.listTools();
342
+ const nativeTestingTools = this.nativeTesting?.listTools();
343
+ const nativeTools = this.nativeContext.listTools();
252
344
  this.bridge.tools.clear();
253
- for (const toolInfo of nativeTools) try {
254
- const inputSchema = JSON.parse(toolInfo.inputSchema);
345
+ const sourceTools = nativeTestingTools ?? nativeTools;
346
+ for (const toolInfo of sourceTools) try {
347
+ const inputSchema = nativeTestingTools && "inputSchema" in toolInfo && typeof toolInfo.inputSchema === "string" ? JSON.parse(toolInfo.inputSchema) : toolInfo.inputSchema ?? { type: "object" };
255
348
  const validatedTool = {
256
349
  name: toolInfo.name,
257
350
  description: toolInfo.description,
258
351
  inputSchema,
259
352
  execute: async (args) => {
260
- const result = await this.nativeTesting.executeTool(toolInfo.name, JSON.stringify(args));
261
- return this.convertToToolResponse(result);
353
+ return this.executeTool(toolInfo.name, args);
262
354
  },
263
355
  inputValidator: jsonSchemaToZod$1(inputSchema)
264
356
  };
@@ -267,19 +359,39 @@ var NativeModelContextAdapter = class {
267
359
  nativeLogger.error(`Failed to sync tool "${toolInfo.name}":`, error);
268
360
  }
269
361
  this.notifyMCPServers();
362
+ if (this.hasCompletedInitialToolSync) dispatchToolsChangedEvent(this.nativeContext);
363
+ else this.hasCompletedInitialToolSync = true;
270
364
  } finally {
271
365
  this.syncInProgress = false;
272
366
  }
273
367
  }
274
368
  /**
275
- * Converts native API result to MCP ToolResponse format.
276
- * Native API returns simplified values (string, number, object, etc.)
277
- * which need to be wrapped in the MCP CallToolResult format.
278
- *
279
- * @param {unknown} result - The result from native executeTool()
280
- * @returns {ToolResponse} Formatted MCP ToolResponse
281
- * @private
369
+ * Public refresh hook for consumer shims that detect native tool mutations
370
+ * without modelContextTesting callbacks.
282
371
  */
372
+ refreshToolsFromNative() {
373
+ this.syncToolsFromNative();
374
+ }
375
+ async executeToolViaNative(toolName, args) {
376
+ const nativeWithConsumer = this.nativeContext;
377
+ if (typeof nativeWithConsumer.callTool === "function" && nativeWithConsumer[CONSUMER_CALL_TOOL_SHIM_MARKER_PROPERTY] !== true) {
378
+ const result = await nativeWithConsumer.callTool({
379
+ name: toolName,
380
+ arguments: args
381
+ });
382
+ if (result && typeof result === "object" && "content" in result && Array.isArray(result.content)) return result;
383
+ return this.convertToToolResponse(result);
384
+ }
385
+ if (this.nativeTesting) {
386
+ const result = await this.nativeTesting.executeTool(toolName, JSON.stringify(args));
387
+ return this.convertToToolResponse(result);
388
+ }
389
+ if (typeof nativeWithConsumer.executeTool === "function") {
390
+ const result = await nativeWithConsumer.executeTool(toolName, args);
391
+ return this.convertToToolResponse(result);
392
+ }
393
+ throw new Error("[Native Adapter] Tool execution is not supported by this native implementation");
394
+ }
283
395
  convertToToolResponse(result) {
284
396
  if (typeof result === "string") return { content: [{
285
397
  type: "text",
@@ -301,11 +413,6 @@ var NativeModelContextAdapter = class {
301
413
  text: String(result)
302
414
  }] };
303
415
  }
304
- /**
305
- * Notifies all connected MCP servers that the tools list has changed.
306
- *
307
- * @private
308
- */
309
416
  notifyMCPServers() {
310
417
  if (this.bridge.tabServer?.notification) this.bridge.tabServer.notification({
311
418
  method: "notifications/tools/list_changed",
@@ -316,13 +423,6 @@ var NativeModelContextAdapter = class {
316
423
  params: {}
317
424
  });
318
425
  }
319
- /**
320
- * Provides context (tools) to AI models via the native API.
321
- * Converts Zod schemas to JSON Schema before passing to native API.
322
- * Tool change callback will fire and trigger sync automatically.
323
- *
324
- * @param {ModelContextInput} context - Context containing tools to register
325
- */
326
426
  provideContext(context) {
327
427
  const { tools,...rest } = context;
328
428
  const normalizedContext = { ...rest };
@@ -332,14 +432,6 @@ var NativeModelContextAdapter = class {
332
432
  }));
333
433
  this.nativeContext.provideContext(normalizedContext);
334
434
  }
335
- /**
336
- * Registers a single tool dynamically via the native API.
337
- * Converts Zod schemas to JSON Schema before passing to native API.
338
- * Tool change callback will fire and trigger sync automatically.
339
- *
340
- * @param {ToolDescriptor} tool - The tool descriptor to register
341
- * @returns {{unregister: () => void}} Object with unregister function
342
- */
343
435
  registerTool(tool) {
344
436
  const normalizedTool = {
345
437
  ...tool,
@@ -347,37 +439,15 @@ var NativeModelContextAdapter = class {
347
439
  };
348
440
  return this.nativeContext.registerTool(normalizedTool);
349
441
  }
350
- /**
351
- * Unregisters a tool by name via the native API.
352
- * Delegates to navigator.modelContext.unregisterTool().
353
- *
354
- * @param {string} name - Name of the tool to unregister
355
- */
356
442
  unregisterTool(name) {
357
443
  this.nativeContext.unregisterTool(name);
358
444
  }
359
- /**
360
- * Clears all registered tools via the native API.
361
- * Delegates to navigator.modelContext.clearContext().
362
- */
363
445
  clearContext() {
364
446
  this.nativeContext.clearContext();
365
447
  }
366
- /**
367
- * Executes a tool via the native API.
368
- * Delegates to navigator.modelContextTesting.executeTool() with JSON string args.
369
- * Note: skipValidation option is ignored - native API handles its own validation.
370
- *
371
- * @param {string} toolName - Name of the tool to execute
372
- * @param {Record<string, unknown>} args - Arguments to pass to the tool
373
- * @param {Object} [_options] - Execution options (ignored for native adapter)
374
- * @returns {Promise<ToolResponse>} The tool's response in MCP format
375
- * @internal
376
- */
377
448
  async executeTool(toolName, args, _options) {
378
449
  try {
379
- const result = await this.nativeTesting.executeTool(toolName, JSON.stringify(args));
380
- return this.convertToToolResponse(result);
450
+ return await this.executeToolViaNative(toolName, args);
381
451
  } catch (error) {
382
452
  nativeLogger.error(`Error executing tool "${toolName}":`, error);
383
453
  return {
@@ -389,12 +459,6 @@ var NativeModelContextAdapter = class {
389
459
  };
390
460
  }
391
461
  }
392
- /**
393
- * Lists all registered tools from the MCP bridge.
394
- * Returns tools synced from the native API.
395
- *
396
- * @returns {Array<{name: string, description: string, inputSchema: InputSchema}>} Array of tool descriptors
397
- */
398
462
  listTools() {
399
463
  return Array.from(this.bridge.tools.values()).map((tool) => ({
400
464
  name: tool.name,
@@ -404,128 +468,76 @@ var NativeModelContextAdapter = class {
404
468
  ...tool.annotations && { annotations: tool.annotations }
405
469
  }));
406
470
  }
407
- /**
408
- * Registers a resource dynamically.
409
- * Note: Native Chromium API does not yet support resources.
410
- * This is a polyfill-only feature.
411
- */
471
+ async callTool(params) {
472
+ if (!params?.name || typeof params.name !== "string") throw new Error("Tool name is required");
473
+ if (!this.bridge.tools.has(params.name)) throw new Error(`Tool not found: ${params.name}`);
474
+ return this.executeTool(params.name, params.arguments ?? {});
475
+ }
412
476
  registerResource(_resource) {
413
477
  nativeLogger.warn("registerResource is not supported by native API");
414
478
  return { unregister: () => {} };
415
479
  }
416
- /**
417
- * Unregisters a resource by URI.
418
- * Note: Native Chromium API does not yet support resources.
419
- */
420
480
  unregisterResource(_uri) {
421
481
  nativeLogger.warn("unregisterResource is not supported by native API");
422
482
  }
423
- /**
424
- * Lists all registered resources.
425
- * Note: Native Chromium API does not yet support resources.
426
- */
427
483
  listResources() {
428
484
  return [];
429
485
  }
430
- /**
431
- * Lists all resource templates.
432
- * Note: Native Chromium API does not yet support resources.
433
- */
434
486
  listResourceTemplates() {
435
487
  return [];
436
488
  }
437
- /**
438
- * Reads a resource by URI.
439
- * Note: Native Chromium API does not yet support resources.
440
- * @internal
441
- */
442
489
  async readResource(_uri) {
443
490
  throw new Error("[Native Adapter] readResource is not supported by native API");
444
491
  }
445
- /**
446
- * Registers a prompt dynamically.
447
- * Note: Native Chromium API does not yet support prompts.
448
- * This is a polyfill-only feature.
449
- */
450
492
  registerPrompt(_prompt) {
451
493
  nativeLogger.warn("registerPrompt is not supported by native API");
452
494
  return { unregister: () => {} };
453
495
  }
454
- /**
455
- * Unregisters a prompt by name.
456
- * Note: Native Chromium API does not yet support prompts.
457
- */
458
496
  unregisterPrompt(_name) {
459
497
  nativeLogger.warn("unregisterPrompt is not supported by native API");
460
498
  }
461
- /**
462
- * Lists all registered prompts.
463
- * Note: Native Chromium API does not yet support prompts.
464
- */
465
499
  listPrompts() {
466
500
  return [];
467
501
  }
468
- /**
469
- * Gets a prompt with arguments.
470
- * Note: Native Chromium API does not yet support prompts.
471
- * @internal
472
- */
473
502
  async getPrompt(_name, _args) {
474
503
  throw new Error("[Native Adapter] getPrompt is not supported by native API");
475
504
  }
476
- /**
477
- * Adds an event listener for tool call events.
478
- * Delegates to the native API's addEventListener.
479
- *
480
- * @param {'toolcall'} type - Event type
481
- * @param {(event: ToolCallEvent) => void | Promise<void>} listener - Event handler
482
- * @param {boolean | AddEventListenerOptions} [options] - Event listener options
483
- */
484
505
  addEventListener(type, listener, options) {
485
- this.nativeContext.addEventListener(type, listener, options);
506
+ if (type === "toolcall") {
507
+ this.nativeContext.addEventListener("toolcall", listener, options);
508
+ return;
509
+ }
510
+ this.nativeContext.addEventListener("toolschanged", listener, options);
486
511
  }
487
- /**
488
- * Removes an event listener for tool call events.
489
- * Delegates to the native API's removeEventListener.
490
- *
491
- * @param {'toolcall'} type - Event type
492
- * @param {(event: ToolCallEvent) => void | Promise<void>} listener - Event handler
493
- * @param {boolean | EventListenerOptions} [options] - Event listener options
494
- */
495
512
  removeEventListener(type, listener, options) {
496
- this.nativeContext.removeEventListener(type, listener, options);
513
+ if (type === "toolcall") {
514
+ this.nativeContext.removeEventListener("toolcall", listener, options);
515
+ return;
516
+ }
517
+ this.nativeContext.removeEventListener("toolschanged", listener, options);
497
518
  }
498
- /**
499
- * Dispatches a tool call event.
500
- * Delegates to the native API's dispatchEvent.
501
- *
502
- * @param {Event} event - The event to dispatch
503
- * @returns {boolean} False if event was cancelled, true otherwise
504
- */
505
519
  dispatchEvent(event) {
506
520
  return this.nativeContext.dispatchEvent(event);
507
521
  }
508
- /**
509
- * Request an LLM completion from the connected client.
510
- * Note: Native Chromium API does not yet support sampling.
511
- * This is handled by the polyfill.
512
- */
513
522
  async createMessage(params) {
514
- const underlyingServer = this.bridge.tabServer.server;
515
- if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
516
- return underlyingServer.createMessage(params);
523
+ return requireCreateMessageCapability(this.bridge.tabServer)(params);
517
524
  }
518
- /**
519
- * Request user input from the connected client.
520
- * Note: Native Chromium API does not yet support elicitation.
521
- * This is handled by the polyfill.
522
- */
523
525
  async elicitInput(params) {
524
- const underlyingServer = this.bridge.tabServer.server;
525
- if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
526
- return underlyingServer.elicitInput(params);
526
+ return requireElicitInputCapability(this.bridge.tabServer)(params);
527
527
  }
528
528
  };
529
+
530
+ //#endregion
531
+ //#region src/global.ts
532
+ const logger$1 = createLogger("WebModelContext");
533
+ const bridgeLogger = createLogger("MCPBridge");
534
+ const testingLogger = createLogger("ModelContextTesting");
535
+ /**
536
+ * Marker property name used to identify polyfill implementations.
537
+ * This constant ensures single source of truth for the marker used in
538
+ * both detection (detectNativeAPI) and definition (WebModelContextTesting).
539
+ */
540
+ const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
529
541
  /**
530
542
  * ToolCallEvent implementation for the Web Model Context API.
531
543
  * Represents an event fired when a tool is called, allowing event listeners
@@ -599,7 +611,7 @@ var WebModelContextTesting = class {
599
611
  * This approach works reliably even when class names are minified in production builds.
600
612
  *
601
613
  * @see POLYFILL_MARKER_PROPERTY - The constant defining this property name
602
- * @see MayHavePolyfillMarker - The interface for type-safe detection
614
+ * @see detectNativeAPI in native-adapter.ts - native/polyfill detection logic
603
615
  */
604
616
  [POLYFILL_MARKER_PROPERTY] = true;
605
617
  toolCallHistory = [];
@@ -830,23 +842,9 @@ var WebModelContext = class {
830
842
  setTestingAPI(testingAPI) {
831
843
  this.testingAPI = testingAPI;
832
844
  }
833
- /**
834
- * Adds an event listener for tool call events.
835
- *
836
- * @param {'toolcall'} type - Event type (only 'toolcall' is supported)
837
- * @param {(event: ToolCallEvent) => void | Promise<void>} listener - Event handler function
838
- * @param {boolean | AddEventListenerOptions} [options] - Event listener options
839
- */
840
845
  addEventListener(type, listener, options) {
841
846
  this.eventTarget.addEventListener(type, listener, options);
842
847
  }
843
- /**
844
- * Removes an event listener for tool call events.
845
- *
846
- * @param {'toolcall'} type - Event type (only 'toolcall' is supported)
847
- * @param {(event: ToolCallEvent) => void | Promise<void>} listener - Event handler function
848
- * @param {boolean | EventListenerOptions} [options] - Event listener options
849
- */
850
848
  removeEventListener(type, listener, options) {
851
849
  this.eventTarget.removeEventListener(type, listener, options);
852
850
  }
@@ -916,7 +914,7 @@ var WebModelContext = class {
916
914
  const templateParams = [];
917
915
  for (const match of resource.uri.matchAll(templateParamRegex)) {
918
916
  const paramName = match[1];
919
- templateParams.push(paramName);
917
+ if (typeof paramName === "string") templateParams.push(paramName);
920
918
  }
921
919
  return {
922
920
  uri: resource.uri,
@@ -1238,6 +1236,7 @@ var WebModelContext = class {
1238
1236
  params: {}
1239
1237
  });
1240
1238
  if (this.testingAPI && "notifyToolsChanged" in this.testingAPI) this.testingAPI.notifyToolsChanged();
1239
+ this.dispatchEvent(new Event("toolschanged"));
1241
1240
  }
1242
1241
  /**
1243
1242
  * Updates the bridge resources map with merged resources from both buckets.
@@ -1387,7 +1386,9 @@ var WebModelContext = class {
1387
1386
  const params = {};
1388
1387
  for (let i = 0; i < paramNames.length; i++) {
1389
1388
  const paramName = paramNames[i];
1390
- params[paramName] = match[i + 1];
1389
+ const paramValue = match[i + 1];
1390
+ if (typeof paramName !== "string" || typeof paramValue !== "string") continue;
1391
+ params[paramName] = paramValue;
1391
1392
  }
1392
1393
  return params;
1393
1394
  }
@@ -1501,6 +1502,15 @@ var WebModelContext = class {
1501
1502
  }));
1502
1503
  }
1503
1504
  /**
1505
+ * Executes a registered tool using MCP-style params.
1506
+ * Throws for missing tools and returns MCP-shaped errors for execution/validation failures.
1507
+ */
1508
+ async callTool(params) {
1509
+ if (!params?.name || typeof params.name !== "string") throw new Error("Tool name is required");
1510
+ if (!this.bridge.tools.has(params.name)) throw new Error(`Tool not found: ${params.name}`);
1511
+ return this.executeTool(params.name, params.arguments ?? {});
1512
+ }
1513
+ /**
1504
1514
  * Request an LLM completion from the connected client.
1505
1515
  * This sends a sampling request to the connected MCP client.
1506
1516
  *
@@ -1508,9 +1518,7 @@ var WebModelContext = class {
1508
1518
  * @returns {Promise<SamplingResult>} The LLM completion result
1509
1519
  */
1510
1520
  async createMessage(params) {
1511
- const underlyingServer = this.bridge.tabServer.server;
1512
- if (!underlyingServer?.createMessage) throw new Error("Sampling is not supported: no connected client with sampling capability");
1513
- return underlyingServer.createMessage(params);
1521
+ return requireCreateMessageCapability(this.bridge.tabServer)(params);
1514
1522
  }
1515
1523
  /**
1516
1524
  * Request user input from the connected client.
@@ -1520,9 +1528,7 @@ var WebModelContext = class {
1520
1528
  * @returns {Promise<ElicitationResult>} The user's response
1521
1529
  */
1522
1530
  async elicitInput(params) {
1523
- const underlyingServer = this.bridge.tabServer.server;
1524
- if (!underlyingServer?.elicitInput) throw new Error("Elicitation is not supported: no connected client with elicitation capability");
1525
- return underlyingServer.elicitInput(params);
1531
+ return requireElicitInputCapability(this.bridge.tabServer)(params);
1526
1532
  }
1527
1533
  };
1528
1534
  /**
@@ -1678,43 +1684,44 @@ function initializeWebModelContext(options) {
1678
1684
  }
1679
1685
  const effectiveOptions = options ?? window.__webModelContextOptions;
1680
1686
  const native = detectNativeAPI();
1681
- if (native.hasNativeContext && native.hasNativeTesting) {
1687
+ if (native.hasNativeContext) {
1682
1688
  const nativeContext = window.navigator.modelContext;
1683
- const nativeTesting = window.navigator.modelContextTesting;
1684
- if (!nativeContext || !nativeTesting) {
1689
+ const nativeTesting = native.hasNativeTesting ? window.navigator.modelContextTesting : void 0;
1690
+ if (!nativeContext) {
1685
1691
  logger$1.error("Native API detection mismatch");
1686
1692
  return;
1687
1693
  }
1688
1694
  logger$1.info("✅ Native Chromium API detected");
1689
- logger$1.info(" Using native implementation with MCP bridge synchronization");
1690
- logger$1.info(" Native API will automatically collect tools from embedded iframes");
1695
+ if (nativeTesting) {
1696
+ logger$1.info(" Using native implementation with MCP bridge synchronization");
1697
+ logger$1.info(" Native API will automatically collect tools from embedded iframes");
1698
+ } else logger$1.info(" Native modelContextTesting not detected; installing consumer shim on modelContext");
1691
1699
  try {
1692
1700
  const bridge = initializeMCPBridge(effectiveOptions);
1693
- bridge.modelContext = new NativeModelContextAdapter(bridge, nativeContext, nativeTesting);
1694
- bridge.modelContextTesting = nativeTesting;
1701
+ const adapter = new NativeModelContextAdapter(bridge, nativeContext, nativeTesting);
1702
+ bridge.modelContext = adapter;
1703
+ if (nativeTesting) {
1704
+ bridge.modelContextTesting = nativeTesting;
1705
+ installDeprecatedTestingAccessor(nativeTesting);
1706
+ }
1707
+ installConsumerShim(nativeContext, (params) => adapter.callTool(params), {
1708
+ hasNativeTesting: Boolean(nativeTesting),
1709
+ ...!nativeTesting && { onToolRegistryMutated: () => adapter.refreshToolsFromNative() }
1710
+ });
1695
1711
  Object.defineProperty(window, "__mcpBridge", {
1696
1712
  value: bridge,
1697
1713
  writable: false,
1698
1714
  configurable: true
1699
1715
  });
1700
1716
  logger$1.info("✅ MCP bridge synced with native API");
1701
- logger$1.info(" MCP clients will receive automatic tool updates from native registry");
1717
+ if (nativeTesting) logger$1.info(" MCP clients will receive automatic tool updates from native registry");
1718
+ else logger$1.warn(" Native testing callbacks are unavailable; tool list change events are best-effort.");
1702
1719
  } catch (error) {
1703
1720
  logger$1.error("Failed to initialize native adapter:", error);
1704
1721
  throw error;
1705
1722
  }
1706
1723
  return;
1707
1724
  }
1708
- if (native.hasNativeContext && !native.hasNativeTesting) {
1709
- logger$1.warn("Partial native API detected");
1710
- logger$1.warn(" navigator.modelContext exists but navigator.modelContextTesting is missing");
1711
- logger$1.warn(" Cannot sync with native API. Please enable experimental features:");
1712
- logger$1.warn(" - Navigate to chrome://flags");
1713
- logger$1.warn(" - Enable \"Experimental Web Platform Features\"");
1714
- logger$1.warn(" - Or launch with: --enable-experimental-web-platform-features");
1715
- logger$1.warn(" Skipping initialization to avoid conflicts");
1716
- return;
1717
- }
1718
1725
  if (window.navigator.modelContext) {
1719
1726
  logger$1.warn("window.navigator.modelContext already exists, skipping initialization");
1720
1727
  return;
@@ -1741,11 +1748,7 @@ function initializeWebModelContext(options) {
1741
1748
  const testingAPI = new WebModelContextTesting(bridge);
1742
1749
  bridge.modelContextTesting = testingAPI;
1743
1750
  bridge.modelContext.setTestingAPI(testingAPI);
1744
- Object.defineProperty(window.navigator, "modelContextTesting", {
1745
- value: testingAPI,
1746
- writable: false,
1747
- configurable: true
1748
- });
1751
+ installDeprecatedTestingAccessor(testingAPI);
1749
1752
  testingLogger.info("✅ Polyfill installed at window.navigator.modelContextTesting");
1750
1753
  } catch (error) {
1751
1754
  logger$1.error("Failed to initialize:", error);
@@ -1773,8 +1776,19 @@ function cleanupWebModelContext() {
1773
1776
  } catch (error) {
1774
1777
  logger$1.warn("Error closing MCP servers:", error);
1775
1778
  }
1776
- delete window.navigator.modelContext;
1777
- delete window.navigator.modelContextTesting;
1779
+ const safeDeleteNavigatorProperty = (propertyName) => {
1780
+ if (Object.getOwnPropertyDescriptor(window.navigator, propertyName)?.configurable === false) {
1781
+ logger$1.debug(`Skipping cleanup for navigator.${propertyName} because property is non-configurable`);
1782
+ return;
1783
+ }
1784
+ try {
1785
+ delete window.navigator[propertyName];
1786
+ } catch (error) {
1787
+ logger$1.warn(`Failed to remove navigator.${propertyName} during cleanup:`, error);
1788
+ }
1789
+ };
1790
+ safeDeleteNavigatorProperty("modelContext");
1791
+ safeDeleteNavigatorProperty("modelContextTesting");
1778
1792
  delete window.__mcpBridge;
1779
1793
  logger$1.info("Cleaned up");
1780
1794
  }