@webmcp-auto-ui/core 2.5.27 → 2.5.28

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/src/polyfill.ts CHANGED
@@ -41,6 +41,14 @@ import { validateJsonSchema } from './validate.js';
41
41
  /** Install state for save/restore of previous descriptors (Fix 7) */
42
42
  interface InstallState {
43
43
  installed: boolean;
44
+ /**
45
+ * True only when we actually overrode navigator.modelContext. When native
46
+ * WebMCP is present and forcePolyfill is false we short-circuit installation
47
+ * and MUST NOT touch navigator on cleanup — otherwise we would delete the
48
+ * native implementation.
49
+ */
50
+ didOverride: boolean;
51
+ didOverrideTesting: boolean;
44
52
  previousModelContextDescriptor?: PropertyDescriptor;
45
53
  previousModelContextTestingDescriptor?: PropertyDescriptor;
46
54
  }
@@ -62,7 +70,7 @@ let toolsChangedCallback: ToolsChangedCallback | null = null;
62
70
  let polyfillOptions: WebMCPPolyfillOptions = {};
63
71
 
64
72
  /** Install state — tracks whether we installed and the previous descriptors */
65
- const installState: InstallState = { installed: false };
73
+ const installState: InstallState = { installed: false, didOverride: false, didOverrideTesting: false };
66
74
 
67
75
  // ---------------------------------------------------------------------------
68
76
  // Internal helpers
@@ -755,6 +763,8 @@ export function initializeWebMCPPolyfill(
755
763
  // forcePolyfill is checked BEFORE the native skip so it takes precedence
756
764
  if (hasNativeWebMCP() && !polyfillOptions.forcePolyfill) {
757
765
  installState.installed = true;
766
+ installState.didOverride = false;
767
+ installState.didOverrideTesting = false;
758
768
  return;
759
769
  }
760
770
 
@@ -783,6 +793,7 @@ export function initializeWebMCPPolyfill(
783
793
  writable: false,
784
794
  enumerable: true,
785
795
  });
796
+ installState.didOverride = true;
786
797
 
787
798
  // Fix 14 — installTestingShim option
788
799
  const shimOption = polyfillOptions.installTestingShim ?? 'if-missing';
@@ -796,6 +807,7 @@ export function initializeWebMCPPolyfill(
796
807
  writable: false,
797
808
  enumerable: true,
798
809
  });
810
+ installState.didOverrideTesting = true;
799
811
  }
800
812
  }
801
813
 
@@ -820,32 +832,39 @@ export function cleanupWebMCPPolyfill(): void {
820
832
  // Clear callback slot
821
833
  toolsChangedCallback = null;
822
834
 
823
- // Fix 7 — Restore previous descriptors instead of deleting
835
+ // Fix 7 — Restore previous descriptors instead of deleting.
836
+ // Guard with didOverride/didOverrideTesting: when native WebMCP was present
837
+ // and we short-circuited install, we never defined properties on navigator
838
+ // and must NOT delete or overwrite them here (that would remove the native).
824
839
  if (typeof navigator !== 'undefined') {
825
840
  try {
826
- if (installState.previousModelContextDescriptor) {
827
- Object.defineProperty(
828
- navigator,
829
- 'modelContext',
830
- installState.previousModelContextDescriptor,
831
- );
832
- } else {
833
- delete (navigator as Navigator & Record<string, unknown>).modelContext;
841
+ if (installState.didOverride) {
842
+ if (installState.previousModelContextDescriptor) {
843
+ Object.defineProperty(
844
+ navigator,
845
+ 'modelContext',
846
+ installState.previousModelContextDescriptor,
847
+ );
848
+ } else {
849
+ delete (navigator as Navigator & Record<string, unknown>).modelContext;
850
+ }
834
851
  }
835
852
 
836
- if (installState.previousModelContextTestingDescriptor) {
837
- Object.defineProperty(
838
- navigator,
839
- 'modelContextTesting',
840
- installState.previousModelContextTestingDescriptor,
841
- );
842
- } else {
843
- const testingDesc = Object.getOwnPropertyDescriptor(
844
- navigator,
845
- 'modelContextTesting',
846
- );
847
- if (testingDesc?.configurable) {
848
- delete (navigator as Navigator & Record<string, unknown>).modelContextTesting;
853
+ if (installState.didOverrideTesting) {
854
+ if (installState.previousModelContextTestingDescriptor) {
855
+ Object.defineProperty(
856
+ navigator,
857
+ 'modelContextTesting',
858
+ installState.previousModelContextTestingDescriptor,
859
+ );
860
+ } else {
861
+ const testingDesc = Object.getOwnPropertyDescriptor(
862
+ navigator,
863
+ 'modelContextTesting',
864
+ );
865
+ if (testingDesc?.configurable) {
866
+ delete (navigator as Navigator & Record<string, unknown>).modelContextTesting;
867
+ }
849
868
  }
850
869
  }
851
870
  } catch (e) {
@@ -856,6 +875,8 @@ export function cleanupWebMCPPolyfill(): void {
856
875
  // Reset options and install state
857
876
  polyfillOptions = {};
858
877
  installState.installed = false;
878
+ installState.didOverride = false;
879
+ installState.didOverrideTesting = false;
859
880
  installState.previousModelContextDescriptor = undefined;
860
881
  installState.previousModelContextTestingDescriptor = undefined;
861
882
  }
@@ -586,12 +586,20 @@ export function createWebMcpServer(
586
586
  const allTools = [...customTools];
587
587
 
588
588
  if (builtinTools) {
589
- // Rebuild widget_display description with current widget names
589
+ // Rebuild widget_display description with current widget names.
590
+ // Clone the tool descriptor rather than mutating builtinTools in place,
591
+ // so multiple servers / repeated layer() calls don't stomp on each
592
+ // other's description (the function refs are preserved).
590
593
  const names = [...widgets.keys()];
591
- const displayTool = builtinTools.find(t => t.name === 'widget_display')!;
592
- displayTool.description = `Render a widget on the user's canvas with the specified type and parameters. Use this tool whenever you need to display data visually — charts, tables, statistics, timelines, maps, galleries, etc. The widget type must match one of the available widget names (use search_recipes or get_recipe first to learn the exact schema). Returns the widget's unique ID for later updates via the canvas tool. Do NOT use this tool for plain text responses. Available widgets: ${names.join(', ')}.`;
594
+ const dynamicDescription = `Render a widget on the user's canvas with the specified type and parameters. Use this tool whenever you need to display data visually — charts, tables, statistics, timelines, maps, galleries, etc. The widget type must match one of the available widget names (use search_recipes or get_recipe first to learn the exact schema). Returns the widget's unique ID for later updates via the canvas tool. Do NOT use this tool for plain text responses. Available widgets: ${names.join(', ')}.`;
593
595
 
594
- allTools.push(...builtinTools);
596
+ for (const tool of builtinTools) {
597
+ if (tool.name === 'widget_display') {
598
+ allTools.push({ ...tool, description: dynamicDescription });
599
+ } else {
600
+ allTools.push({ ...tool });
601
+ }
602
+ }
595
603
  }
596
604
 
597
605
  // Merge flow recipes into the layer's recipe list so the UI can browse them