@miragon/mcp-toolkit-ui 2.6.0 → 2.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/README.md +14 -5
- package/dist/app/adapt-data-widget.d.ts +1 -22
- package/dist/app/adapt-data-widget.d.ts.map +1 -1
- package/dist/app/adapt-data-widget.js +1 -35
- package/dist/app/adapt-data-widget.js.map +1 -1
- package/dist/app/builder/BuilderBanners.d.ts +7 -2
- package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
- package/dist/app/builder/BuilderBanners.js +8 -3
- package/dist/app/builder/BuilderBanners.js.map +1 -1
- package/dist/app/builder/CanvasRow.d.ts +4 -2
- package/dist/app/builder/CanvasRow.d.ts.map +1 -1
- package/dist/app/builder/CanvasRow.js +29 -17
- package/dist/app/builder/CanvasRow.js.map +1 -1
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
- package/dist/app/builder/CatalogueSheet.js +14 -6
- package/dist/app/builder/CatalogueSheet.js.map +1 -1
- package/dist/app/builder/CellPropsSheet.d.ts +7 -3
- package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
- package/dist/app/builder/CellPropsSheet.js +10 -3
- package/dist/app/builder/CellPropsSheet.js.map +1 -1
- package/dist/app/builder/DiscardDialog.d.ts +18 -0
- package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
- package/dist/app/builder/DiscardDialog.js +18 -0
- package/dist/app/builder/DiscardDialog.js.map +1 -0
- package/dist/app/builder/EmptyHint.d.ts +4 -0
- package/dist/app/builder/EmptyHint.d.ts.map +1 -1
- package/dist/app/builder/EmptyHint.js +6 -1
- package/dist/app/builder/EmptyHint.js.map +1 -1
- package/dist/app/builder/LayoutSurface.d.ts +33 -9
- package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
- package/dist/app/builder/LayoutSurface.js +74 -23
- package/dist/app/builder/LayoutSurface.js.map +1 -1
- package/dist/app/builder/PipelineStrip.d.ts +5 -2
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +33 -15
- package/dist/app/builder/PipelineStrip.js.map +1 -1
- package/dist/app/builder/SaveDialog.d.ts +9 -1
- package/dist/app/builder/SaveDialog.d.ts.map +1 -1
- package/dist/app/builder/SaveDialog.js +9 -3
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +14 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +24 -7
- package/dist/app/builder/Toolbar.js.map +1 -1
- package/dist/app/builder/Workspace.d.ts +23 -3
- package/dist/app/builder/Workspace.d.ts.map +1 -1
- package/dist/app/builder/Workspace.js +58 -8
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-model.d.ts +146 -3
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +196 -5
- package/dist/app/builder/builder-model.js.map +1 -1
- package/dist/app/builder/labels.d.ts +13 -62
- package/dist/app/builder/labels.d.ts.map +1 -1
- package/dist/app/builder/labels.js +9 -58
- package/dist/app/builder/labels.js.map +1 -1
- package/dist/app/builder/reveal.d.ts +10 -0
- package/dist/app/builder/reveal.d.ts.map +1 -0
- package/dist/app/builder/reveal.js +10 -0
- package/dist/app/builder/reveal.js.map +1 -0
- package/dist/app/builder/status-chip.d.ts.map +1 -1
- package/dist/app/builder/status-chip.js +3 -2
- package/dist/app/builder/status-chip.js.map +1 -1
- package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +50 -17
- package/dist/app/builder/use-builder-catalogue.js.map +1 -1
- package/dist/app/builder/use-builder-session.d.ts +30 -0
- package/dist/app/builder/use-builder-session.d.ts.map +1 -0
- package/dist/app/builder/use-builder-session.js +58 -0
- package/dist/app/builder/use-builder-session.js.map +1 -0
- package/dist/app/builder/use-display-resolver.d.ts +17 -0
- package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
- package/dist/app/builder/use-display-resolver.js +16 -0
- package/dist/app/builder/use-display-resolver.js.map +1 -0
- package/dist/app/builder/use-save-dashboard.d.ts +3 -1
- package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
- package/dist/app/builder/use-save-dashboard.js +3 -1
- package/dist/app/builder/use-save-dashboard.js.map +1 -1
- package/dist/app/index.d.ts +8 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +8 -2
- package/dist/app/index.js.map +1 -1
- package/dist/app/layout-builder.d.ts +18 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +84 -78
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +103 -20
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +125 -40
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +23 -0
- package/dist/app/mcp-app-view-states.d.ts.map +1 -0
- package/dist/app/mcp-app-view-states.js +55 -0
- package/dist/app/mcp-app-view-states.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +72 -5
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +241 -141
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +8 -2
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +33 -4
- package/dist/app/mcp-toolkit-app.js.map +1 -1
- package/dist/app/mcp-use-host-bridge.d.ts +15 -6
- package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
- package/dist/app/mcp-use-host-bridge.js +51 -22
- package/dist/app/mcp-use-host-bridge.js.map +1 -1
- package/dist/app/model-context.d.ts +71 -0
- package/dist/app/model-context.d.ts.map +1 -0
- package/dist/app/model-context.js +101 -0
- package/dist/app/model-context.js.map +1 -0
- package/dist/app/use-build-mode-focus.d.ts +33 -0
- package/dist/app/use-build-mode-focus.d.ts.map +1 -0
- package/dist/app/use-build-mode-focus.js +59 -0
- package/dist/app/use-build-mode-focus.js.map +1 -0
- package/dist/app/use-view-actions.d.ts +31 -0
- package/dist/app/use-view-actions.d.ts.map +1 -0
- package/dist/app/use-view-actions.js +81 -0
- package/dist/app/use-view-actions.js.map +1 -0
- package/dist/app/use-view-lifecycle.d.ts +42 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -0
- package/dist/app/use-view-lifecycle.js +81 -0
- package/dist/app/use-view-lifecycle.js.map +1 -0
- package/dist/app/view-lifecycle.d.ts +185 -0
- package/dist/app/view-lifecycle.d.ts.map +1 -0
- package/dist/app/view-lifecycle.js +207 -0
- package/dist/app/view-lifecycle.js.map +1 -0
- package/dist/app/widget-fixture.d.ts +10 -3
- package/dist/app/widget-fixture.d.ts.map +1 -1
- package/dist/app/widget-fixture.js +21 -8
- package/dist/app/widget-fixture.js.map +1 -1
- package/dist/app/widget-props-sheet.d.ts +6 -10
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +25 -20
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.d.ts +41 -1
- package/dist/app/widget-renderer.d.ts.map +1 -1
- package/dist/app/widget-renderer.js +82 -31
- package/dist/app/widget-renderer.js.map +1 -1
- package/dist/components/DrillButton.d.ts +9 -6
- package/dist/components/DrillButton.d.ts.map +1 -1
- package/dist/components/DrillButton.js +9 -6
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts +33 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +19 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorState.d.ts +53 -0
- package/dist/components/ErrorState.d.ts.map +1 -0
- package/dist/components/ErrorState.js +77 -0
- package/dist/components/ErrorState.js.map +1 -0
- package/dist/components/FilterBar.d.ts +16 -5
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +13 -5
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/GroupCard.d.ts.map +1 -1
- package/dist/components/GroupCard.js +2 -1
- package/dist/components/GroupCard.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +70 -18
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +112 -23
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +32 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +35 -6
- package/dist/components/ListFooter.js.map +1 -1
- package/dist/components/PagedListFooter.d.ts +57 -0
- package/dist/components/PagedListFooter.d.ts.map +1 -0
- package/dist/components/PagedListFooter.js +27 -0
- package/dist/components/PagedListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +36 -12
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +29 -8
- package/dist/components/Pills.js.map +1 -1
- package/dist/components/SectionHeading.d.ts +3 -1
- package/dist/components/SectionHeading.d.ts.map +1 -1
- package/dist/components/SectionHeading.js +6 -2
- package/dist/components/SectionHeading.js.map +1 -1
- package/dist/components/Toast.d.ts +43 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +106 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/WidgetHeader.d.ts +34 -10
- package/dist/components/WidgetHeader.d.ts.map +1 -1
- package/dist/components/WidgetHeader.js +11 -6
- package/dist/components/WidgetHeader.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
- package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
- package/dist/hooks/tool-result-recovery-model.js +273 -0
- package/dist/hooks/tool-result-recovery-model.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +56 -14
- package/dist/hooks/use-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-tool-query.js +102 -25
- package/dist/hooks/use-tool-query.js.map +1 -1
- package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +62 -110
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +55 -7
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -11
- package/dist/hooks/use-view-data.js.map +1 -1
- package/dist/hooks/use-view-tool-query.d.ts +12 -7
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-view-tool-query.js +15 -14
- package/dist/hooks/use-view-tool-query.js.map +1 -1
- package/dist/index.d.ts +27 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -7
- package/dist/index.js.map +1 -1
- package/dist/lib/focus-return.d.ts +61 -0
- package/dist/lib/focus-return.d.ts.map +1 -0
- package/dist/lib/focus-return.js +46 -0
- package/dist/lib/focus-return.js.map +1 -0
- package/dist/lib/focus-ring.d.ts +18 -0
- package/dist/lib/focus-ring.d.ts.map +1 -0
- package/dist/lib/focus-ring.js +18 -0
- package/dist/lib/focus-ring.js.map +1 -0
- package/dist/lib/icon-stroke.d.ts +12 -0
- package/dist/lib/icon-stroke.d.ts.map +1 -0
- package/dist/lib/icon-stroke.js +12 -0
- package/dist/lib/icon-stroke.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +23 -1
- package/dist/lib/parse-tool-result.d.ts.map +1 -1
- package/dist/lib/parse-tool-result.js +38 -2
- package/dist/lib/parse-tool-result.js.map +1 -1
- package/dist/lib/toast-model.d.ts +86 -0
- package/dist/lib/toast-model.d.ts.map +1 -0
- package/dist/lib/toast-model.js +102 -0
- package/dist/lib/toast-model.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +95 -9
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +100 -9
- package/dist/lib/tone-utils.js.map +1 -1
- package/dist/lib/tool-query.d.ts +107 -0
- package/dist/lib/tool-query.d.ts.map +1 -0
- package/dist/lib/tool-query.js +182 -0
- package/dist/lib/tool-query.js.map +1 -0
- package/dist/lib/tool-result-observer.d.ts +108 -0
- package/dist/lib/tool-result-observer.d.ts.map +1 -0
- package/dist/lib/tool-result-observer.js +158 -0
- package/dist/lib/tool-result-observer.js.map +1 -0
- package/dist/lib/typography.d.ts +37 -0
- package/dist/lib/typography.d.ts.map +1 -0
- package/dist/lib/typography.js +41 -0
- package/dist/lib/typography.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +11 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/locale/index.d.ts +3 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/index.js +2 -1
- package/dist/locale/index.js.map +1 -1
- package/dist/locale/locale-provider.d.ts +42 -1
- package/dist/locale/locale-provider.d.ts.map +1 -1
- package/dist/locale/locale-provider.js +44 -3
- package/dist/locale/locale-provider.js.map +1 -1
- package/dist/locale/message-types.d.ts +336 -0
- package/dist/locale/message-types.d.ts.map +1 -0
- package/dist/locale/message-types.js +7 -0
- package/dist/locale/message-types.js.map +1 -0
- package/dist/locale/messages-de.d.ts +9 -0
- package/dist/locale/messages-de.d.ts.map +1 -0
- package/dist/locale/messages-de.js +185 -0
- package/dist/locale/messages-de.js.map +1 -0
- package/dist/locale/messages-en.d.ts +8 -0
- package/dist/locale/messages-en.d.ts.map +1 -0
- package/dist/locale/messages-en.js +184 -0
- package/dist/locale/messages-en.js.map +1 -0
- package/dist/locale/toolkit-messages.d.ts +57 -0
- package/dist/locale/toolkit-messages.d.ts.map +1 -0
- package/dist/locale/toolkit-messages.js +103 -0
- package/dist/locale/toolkit-messages.js.map +1 -0
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +6 -2
- package/dist/primitives/alert.js.map +1 -1
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/badge.d.ts.map +1 -1
- package/dist/primitives/badge.js +19 -6
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.d.ts.map +1 -1
- package/dist/primitives/button.js +12 -8
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/card.js +4 -4
- package/dist/primitives/card.js.map +1 -1
- package/dist/primitives/dialog.d.ts +7 -3
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +22 -8
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/input.d.ts.map +1 -1
- package/dist/primitives/input.js +4 -1
- package/dist/primitives/input.js.map +1 -1
- package/dist/primitives/scroll-area.js +1 -1
- package/dist/primitives/scroll-area.js.map +1 -1
- package/dist/primitives/select.d.ts.map +1 -1
- package/dist/primitives/select.js +6 -3
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +3 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +17 -7
- package/dist/primitives/sheet.js.map +1 -1
- package/dist/primitives/skeleton.js +1 -1
- package/dist/primitives/skeleton.js.map +1 -1
- package/dist/primitives/switch.d.ts.map +1 -1
- package/dist/primitives/switch.js +2 -1
- package/dist/primitives/switch.js.map +1 -1
- package/dist/primitives/tabs.d.ts.map +1 -1
- package/dist/primitives/tabs.js +5 -3
- package/dist/primitives/tabs.js.map +1 -1
- package/dist/providers/query-provider.d.ts +32 -1
- package/dist/providers/query-provider.d.ts.map +1 -1
- package/dist/providers/query-provider.js +53 -4
- package/dist/providers/query-provider.js.map +1 -1
- package/dist/render-cases-primitives.js +1 -1
- package/dist/render-cases-primitives.js.map +1 -1
- package/dist/render-cases.d.ts.map +1 -1
- package/dist/render-cases.js +48 -6
- package/dist/render-cases.js.map +1 -1
- package/dist/render-effects.d.ts +42 -0
- package/dist/render-effects.d.ts.map +1 -0
- package/dist/render-effects.js +81 -0
- package/dist/render-effects.js.map +1 -0
- package/dist/render-scan.d.ts +25 -0
- package/dist/render-scan.d.ts.map +1 -0
- package/dist/render-scan.js +66 -0
- package/dist/render-scan.js.map +1 -0
- package/dist/theme/cd-tokens.d.ts +66 -0
- package/dist/theme/cd-tokens.d.ts.map +1 -0
- package/dist/theme/cd-tokens.js +64 -0
- package/dist/theme/cd-tokens.js.map +1 -0
- package/dist/theme/create-theme.d.ts +214 -39
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +204 -13
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/fonts.d.ts +38 -0
- package/dist/theme/fonts.d.ts.map +1 -0
- package/dist/theme/fonts.js +56 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/theme/host-fonts.d.ts +39 -0
- package/dist/theme/host-fonts.d.ts.map +1 -0
- package/dist/theme/host-fonts.js +44 -0
- package/dist/theme/host-fonts.js.map +1 -0
- package/dist/theme/index.d.ts +8 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +5 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/portal-container.d.ts +16 -0
- package/dist/theme/portal-container.d.ts.map +1 -0
- package/dist/theme/portal-container.js +19 -0
- package/dist/theme/portal-container.js.map +1 -0
- package/dist/theme/presets.d.ts +24 -9
- package/dist/theme/presets.d.ts.map +1 -1
- package/dist/theme/presets.js +44 -11
- package/dist/theme/presets.js.map +1 -1
- package/dist/theme/theme-provider.d.ts +27 -9
- package/dist/theme/theme-provider.d.ts.map +1 -1
- package/dist/theme/theme-provider.js +42 -20
- package/dist/theme/theme-provider.js.map +1 -1
- package/package.json +11 -5
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +26 -13
- package/src/app/builder/CanvasRow.tsx +79 -32
- package/src/app/builder/CatalogueSheet.tsx +58 -35
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/EmptyHint.tsx +16 -11
- package/src/app/builder/LayoutSurface.tsx +207 -67
- package/src/app/builder/PipelineStrip.tsx +265 -156
- package/src/app/builder/SaveDialog.tsx +25 -14
- package/src/app/builder/Toolbar.tsx +154 -57
- package/src/app/builder/Workspace.tsx +226 -56
- package/src/app/builder/builder-feedback.test.tsx +75 -0
- package/src/app/builder/builder-model.test.ts +5 -0
- package/src/app/builder/builder-model.ts +346 -6
- package/src/app/builder/builder-model.ux.test.ts +456 -0
- package/src/app/builder/builder-ux.test.tsx +844 -0
- package/src/app/builder/labels.ts +15 -121
- package/src/app/builder/reveal.ts +10 -0
- package/src/app/builder/status-chip.tsx +8 -4
- package/src/app/builder/use-builder-catalogue.ts +62 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/builder/use-save-dashboard.ts +5 -0
- package/src/app/index.ts +20 -3
- package/src/app/layout-builder.tsx +193 -186
- package/src/app/mcp-app-view-chrome.test.tsx +487 -14
- package/src/app/mcp-app-view-chrome.tsx +328 -100
- package/src/app/mcp-app-view-states.tsx +140 -0
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +478 -0
- package/src/app/mcp-app-view.tsx +447 -190
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +39 -8
- package/src/app/mcp-use-host-bridge.test.tsx +180 -0
- package/src/app/mcp-use-host-bridge.tsx +67 -24
- package/src/app/model-context.test.tsx +116 -0
- package/src/app/model-context.tsx +125 -0
- package/src/app/use-build-mode-focus.ts +62 -0
- package/src/app/use-view-actions.ts +89 -0
- package/src/app/use-view-lifecycle.ts +122 -0
- package/src/app/view-lifecycle.test.ts +443 -0
- package/src/app/view-lifecycle.ts +334 -0
- package/src/app/widget-fixture.effects.test.tsx +51 -0
- package/src/app/widget-fixture.render.test.tsx +81 -0
- package/src/app/widget-fixture.tsx +34 -11
- package/src/app/widget-props-sheet.tsx +67 -39
- package/src/app/widget-renderer.render.test.tsx +131 -0
- package/src/app/widget-renderer.tsx +136 -38
- package/src/components/DrillButton.tsx +24 -10
- package/src/components/EmptyState.tsx +83 -0
- package/src/components/ErrorState.tsx +204 -0
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +56 -27
- package/src/components/GroupCard.tsx +3 -2
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +329 -91
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +110 -12
- package/src/components/PagedListFooter.test.tsx +140 -0
- package/src/components/PagedListFooter.tsx +107 -0
- package/src/components/Pills.test.tsx +77 -0
- package/src/components/Pills.tsx +78 -16
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +206 -0
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +314 -0
- package/src/components/tone-figures.test.tsx +58 -0
- package/src/fonts.css +19 -0
- package/src/globals.css +9 -165
- package/src/hooks/index.ts +6 -3
- package/src/hooks/tool-result-recovery-model.test.ts +744 -0
- package/src/hooks/tool-result-recovery-model.ts +418 -0
- package/src/hooks/use-tool-query.effects.test.tsx +205 -0
- package/src/hooks/use-tool-query.ts +156 -33
- package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
- package/src/hooks/use-tool-result-recovery.test.ts +28 -5
- package/src/hooks/use-tool-result-recovery.ts +101 -131
- package/src/hooks/use-view-data.test.tsx +169 -0
- package/src/hooks/use-view-data.ts +131 -10
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +425 -0
- package/src/index.ts +102 -13
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -0
- package/src/lib/focus-ring.ts +20 -0
- package/src/lib/icon-stroke.test.ts +59 -0
- package/src/lib/icon-stroke.ts +13 -0
- package/src/lib/parse-tool-result.test.ts +46 -1
- package/src/lib/parse-tool-result.ts +40 -2
- package/src/lib/toast-model.test.ts +233 -0
- package/src/lib/toast-model.ts +165 -0
- package/src/lib/tone-utils.test.ts +125 -16
- package/src/lib/tone-utils.ts +144 -15
- package/src/lib/tool-query.test.ts +569 -0
- package/src/lib/tool-query.ts +253 -0
- package/src/lib/tool-result-observer.test.ts +296 -0
- package/src/lib/tool-result-observer.ts +211 -0
- package/src/lib/typography.test.ts +45 -0
- package/src/lib/typography.ts +47 -0
- package/src/lib/utils.ts +12 -1
- package/src/locale/index.ts +14 -1
- package/src/locale/locale-provider.test.tsx +87 -0
- package/src/locale/locale-provider.tsx +90 -3
- package/src/locale/message-types.ts +337 -0
- package/src/locale/messages-de.ts +198 -0
- package/src/locale/messages-en.ts +196 -0
- package/src/locale/toolkit-messages.test.ts +349 -0
- package/src/locale/toolkit-messages.ts +143 -0
- package/src/no-hardcoded-copy.test.ts +432 -0
- package/src/primitives/alert.tsx +7 -2
- package/src/primitives/badge.tsx +19 -7
- package/src/primitives/button.tsx +13 -8
- package/src/primitives/card.tsx +4 -4
- package/src/primitives/dialog.tsx +42 -8
- package/src/primitives/input.tsx +5 -3
- package/src/primitives/scroll-area.tsx +1 -1
- package/src/primitives/select.tsx +7 -4
- package/src/primitives/sheet.tsx +33 -6
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/switch.tsx +4 -2
- package/src/primitives/tabs.tsx +9 -6
- package/src/providers/query-provider.test.tsx +154 -0
- package/src/providers/query-provider.tsx +66 -5
- package/src/render-cases-primitives.tsx +1 -1
- package/src/render-cases.tsx +82 -6
- package/src/render-effects.tsx +96 -0
- package/src/render-scan.ts +75 -0
- package/src/theme/cd-tokens.generated.css +48 -0
- package/src/theme/cd-tokens.test.ts +109 -0
- package/src/theme/cd-tokens.ts +67 -0
- package/src/theme/create-theme.test.ts +253 -5
- package/src/theme/create-theme.ts +341 -41
- package/src/theme/fonts.test.ts +85 -0
- package/src/theme/fonts.ts +72 -0
- package/src/theme/host-fonts.test.ts +135 -0
- package/src/theme/host-fonts.ts +52 -0
- package/src/theme/index.ts +15 -1
- package/src/theme/portal-container.ts +20 -0
- package/src/theme/presets.ts +44 -11
- package/src/theme/theme-css.test.ts +619 -0
- package/src/theme/theme-provider.test.tsx +82 -0
- package/src/theme/theme-provider.tsx +60 -24
- package/src/theme.css +344 -0
- package/ui-catalog.json +783 -49
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest"
|
|
2
|
+
import { mountMcpToolkitApp } from "./mcp-toolkit-app.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The mount order behind K13 (toolkit#176): `mountMcpToolkitApp` installs the
|
|
6
|
+
* tool-result observer BEFORE `bootstrapView` connects the view to its host,
|
|
7
|
+
* so the observer sees the handshake and the view never mistakes a slow tool
|
|
8
|
+
* for a stripped result.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const calls = vi.hoisted(() => ({ order: [] as string[] }))
|
|
12
|
+
|
|
13
|
+
vi.mock(import("../lib/tool-result-observer.js"), async (importOriginal) => {
|
|
14
|
+
const actual = await importOriginal()
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
installToolResultObserver: () => {
|
|
18
|
+
calls.order.push("installToolResultObserver")
|
|
19
|
+
return null
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
vi.mock(import("mcp-use/react"), async (importOriginal) => {
|
|
25
|
+
const actual = await importOriginal()
|
|
26
|
+
return {
|
|
27
|
+
...actual,
|
|
28
|
+
bootstrapView: (() => {
|
|
29
|
+
calls.order.push("bootstrapView")
|
|
30
|
+
}) as unknown as typeof actual.bootstrapView,
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe("mountMcpToolkitApp", () => {
|
|
35
|
+
it("installs the tool-result observer before bootstrapView connects to the host", () => {
|
|
36
|
+
calls.order = []
|
|
37
|
+
mountMcpToolkitApp({ widgets: {} })
|
|
38
|
+
expect(calls.order).toEqual(["installToolResultObserver", "bootstrapView"])
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLayoutEffect, type ReactNode } from "react"
|
|
2
|
+
import { ThemeProvider, bootstrapView, useHostContext } from "mcp-use/react"
|
|
3
|
+
import { installToolResultObserver } from "../lib/tool-result-observer.js"
|
|
4
|
+
import { adoptHostFontVariables } from "../theme/host-fonts.js"
|
|
2
5
|
import { McpAppView, type McpAppViewProps } from "./mcp-app-view.js"
|
|
3
6
|
import { McpUseHostBridgeProvider } from "./mcp-use-host-bridge.js"
|
|
4
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Keeps Geist first when the host sends its font. mcp-use's `ThemeProvider`
|
|
10
|
+
* writes the host's SEP-1865 style variables inline on `<html>`, including
|
|
11
|
+
* `--font-sans`/`--font-mono`, which share their names with the toolkit's font
|
|
12
|
+
* roles and would replace the Geist-first stacks whole. This component sits
|
|
13
|
+
* ABOVE that provider on purpose: a parent's layout effect runs after its
|
|
14
|
+
* children's, so the host variables are already applied when it moves the
|
|
15
|
+
* font pair into the fallback slot (`adoptHostFontVariables`). Both effects
|
|
16
|
+
* key on the same host-context snapshot, so they run in the same commit.
|
|
17
|
+
*/
|
|
18
|
+
function HostFontRoles({ children }: { children: ReactNode }) {
|
|
19
|
+
const { hostContext } = useHostContext()
|
|
20
|
+
useLayoutEffect(() => {
|
|
21
|
+
if (typeof document === "undefined") return
|
|
22
|
+
adoptHostFontVariables(document.documentElement.style, hostContext?.styles?.variables)
|
|
23
|
+
}, [hostContext])
|
|
24
|
+
return children
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
/**
|
|
6
28
|
* Consumer-facing root for a toolkit-rendered MCP App. Composes the mcp-use
|
|
7
29
|
* 2.x view plumbing around `McpAppView`: `ThemeProvider` applies the host's
|
|
8
|
-
* theme/style variables to the document
|
|
30
|
+
* theme/style variables to the document (with the host's font kept behind
|
|
31
|
+
* Geist, see `adoptHostFontVariables`), and `McpUseHostBridgeProvider`
|
|
9
32
|
* installs the `HostBridge` (and view-scope flag) every toolkit widget below
|
|
10
33
|
* resolves through `useHostBridge()`.
|
|
11
34
|
*
|
|
@@ -16,11 +39,13 @@ import { McpUseHostBridgeProvider } from "./mcp-use-host-bridge.js"
|
|
|
16
39
|
*/
|
|
17
40
|
export function McpToolkitApp(props: McpAppViewProps) {
|
|
18
41
|
return (
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
42
|
+
<HostFontRoles>
|
|
43
|
+
<ThemeProvider>
|
|
44
|
+
<McpUseHostBridgeProvider>
|
|
45
|
+
<McpAppView {...props} />
|
|
46
|
+
</McpUseHostBridgeProvider>
|
|
47
|
+
</ThemeProvider>
|
|
48
|
+
</HostFontRoles>
|
|
24
49
|
)
|
|
25
50
|
}
|
|
26
51
|
|
|
@@ -30,7 +55,12 @@ export function McpToolkitApp(props: McpAppViewProps) {
|
|
|
30
55
|
*
|
|
31
56
|
* `bootstrapView` owns the mount: it connects the ext-apps postMessage bridge
|
|
32
57
|
* to the host, installs an error boundary, and auto-reports size changes
|
|
33
|
-
* (replacing 1.x's `createRoot` + `McpUseProvider` pair).
|
|
58
|
+
* (replacing 1.x's `createRoot` + `McpUseProvider` pair). The tool-result
|
|
59
|
+
* observer goes in first, so it sees the whole host lifecycle from the
|
|
60
|
+
* handshake on: the view then tells a stripped result from a slow tool and
|
|
61
|
+
* never re-runs a call the host is still answering (toolkit#176). A custom
|
|
62
|
+
* shell under its own `bootstrapView` calls `installToolResultObserver()`
|
|
63
|
+
* before it for the same effect.
|
|
34
64
|
*
|
|
35
65
|
* ```tsx
|
|
36
66
|
* // main.tsx of the widget bundle
|
|
@@ -38,5 +68,6 @@ export function McpToolkitApp(props: McpAppViewProps) {
|
|
|
38
68
|
* ```
|
|
39
69
|
*/
|
|
40
70
|
export function mountMcpToolkitApp(props: McpAppViewProps): void {
|
|
71
|
+
installToolResultObserver()
|
|
41
72
|
bootstrapView({ default: () => <McpToolkitApp {...props} /> })
|
|
42
73
|
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { useEffect, type ReactNode } from "react"
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
4
|
+
import { flushEffects, hookProbe, renderWithEffects, type EffectRender } from "../render-effects.js"
|
|
5
|
+
import { useHostBridge, type HostBridge } from "./host-bridge.js"
|
|
6
|
+
import { McpUseHostBridgeProvider, useViewResultSink } from "./mcp-use-host-bridge.js"
|
|
7
|
+
import { useHostModelContext } from "./model-context.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `McpUseHostBridgeProvider`'s wiring with its effects running (toolkit#176
|
|
11
|
+
* K15, K20, N29): `getWidgetData()` returns the payload the view reports it
|
|
12
|
+
* shows, the bridge's `setModelContext` is one line of the view's native
|
|
13
|
+
* model-context tree, and `useHostModelContext` lines come and go with their
|
|
14
|
+
* component. The mcp-use view hooks are the only mock.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const host = vi.hoisted(
|
|
18
|
+
(): {
|
|
19
|
+
tool: { status: string; toolOutput?: unknown }
|
|
20
|
+
useViewState: ReturnType<typeof vi.fn> | null
|
|
21
|
+
} => ({ tool: { status: "pending" }, useViewState: null }),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
vi.mock(import("mcp-use/react"), async (importOriginal) => {
|
|
25
|
+
const actual = await importOriginal()
|
|
26
|
+
const { createElement } = await import("react")
|
|
27
|
+
host.useViewState = vi.fn()
|
|
28
|
+
const ModelContext = ({ content }: { content: string }) =>
|
|
29
|
+
// Not a <meta>: React 19 hoists those into <head>.
|
|
30
|
+
createElement("output", { "data-model-context": content })
|
|
31
|
+
const stable = {
|
|
32
|
+
dynamic: { callTool: () => Promise.resolve({}) },
|
|
33
|
+
followUp: () => Promise.resolve(),
|
|
34
|
+
openExternal: () => Promise.resolve(),
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
...actual,
|
|
38
|
+
ModelContext: ModelContext as unknown as typeof actual.ModelContext,
|
|
39
|
+
useDynamicTool: (() => stable.dynamic) as unknown as typeof actual.useDynamicTool,
|
|
40
|
+
useSendFollowUp: (() => stable.followUp) as unknown as typeof actual.useSendFollowUp,
|
|
41
|
+
useOpenExternal: (() => stable.openExternal) as unknown as typeof actual.useOpenExternal,
|
|
42
|
+
useToolContext: (() => host.tool) as unknown as typeof actual.useToolContext,
|
|
43
|
+
useViewTheme: (() => "light") as unknown as typeof actual.useViewTheme,
|
|
44
|
+
useHostContext: (() => ({ isAvailable: true })) as unknown as typeof actual.useHostContext,
|
|
45
|
+
useViewState: host.useViewState as unknown as typeof actual.useViewState,
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const PAYLOAD = { title: "Incidents", context: { keys: {} }, layout: { rows: [] } }
|
|
50
|
+
|
|
51
|
+
/** Reports `payload` the way McpAppView reports what it shows. */
|
|
52
|
+
function Reporter({ payload }: { payload: unknown }) {
|
|
53
|
+
const report = useViewResultSink()
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
report?.(payload)
|
|
56
|
+
}, [payload, report])
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Calls the bridge's setModelContext with `text` on every change. */
|
|
61
|
+
function BridgeLine({ text }: { text: string }) {
|
|
62
|
+
const bridge = useHostBridge()
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
bridge.setModelContext?.(text)
|
|
65
|
+
}, [bridge, text])
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Line({ content }: { content: string | null }) {
|
|
70
|
+
useHostModelContext(content)
|
|
71
|
+
return null
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const lines = (view: EffectRender) =>
|
|
75
|
+
[...view.container.querySelectorAll("[data-model-context]")].map((node) =>
|
|
76
|
+
node.getAttribute("data-model-context"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
let mounted: EffectRender | null = null
|
|
80
|
+
|
|
81
|
+
async function render(children: ReactNode) {
|
|
82
|
+
mounted = await renderWithEffects(<McpUseHostBridgeProvider>{children}</McpUseHostBridgeProvider>)
|
|
83
|
+
return mounted
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rerender = (view: EffectRender, children: ReactNode) =>
|
|
87
|
+
view.rerender(<McpUseHostBridgeProvider>{children}</McpUseHostBridgeProvider>)
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
host.tool = { status: "pending", toolOutput: undefined }
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
afterEach(async () => {
|
|
94
|
+
await mounted?.unmount()
|
|
95
|
+
mounted = null
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe("McpUseHostBridgeProvider getWidgetData (K15)", () => {
|
|
99
|
+
const bridgeProbe = () => hookProbe((): HostBridge => useHostBridge())
|
|
100
|
+
|
|
101
|
+
it("returns the payload the view reports while the host's latch is still pending", async () => {
|
|
102
|
+
const probe = bridgeProbe()
|
|
103
|
+
const view = await render(<probe.Probe />)
|
|
104
|
+
expect(probe.current().getWidgetData()).toBeNull()
|
|
105
|
+
await rerender(
|
|
106
|
+
view,
|
|
107
|
+
<>
|
|
108
|
+
<probe.Probe />
|
|
109
|
+
<Reporter payload={PAYLOAD} />
|
|
110
|
+
</>,
|
|
111
|
+
)
|
|
112
|
+
await flushEffects()
|
|
113
|
+
expect(probe.current().getWidgetData()).toEqual(PAYLOAD)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("prefers the reported payload over an unusable latched output (structuredContent: null)", async () => {
|
|
117
|
+
host.tool = { status: "ready", toolOutput: null }
|
|
118
|
+
const probe = bridgeProbe()
|
|
119
|
+
const view = await render(<probe.Probe />)
|
|
120
|
+
expect(probe.current().getWidgetData()).toBeNull()
|
|
121
|
+
await rerender(
|
|
122
|
+
view,
|
|
123
|
+
<>
|
|
124
|
+
<probe.Probe />
|
|
125
|
+
<Reporter payload={PAYLOAD} />
|
|
126
|
+
</>,
|
|
127
|
+
)
|
|
128
|
+
await flushEffects()
|
|
129
|
+
expect(probe.current().getWidgetData()).toEqual(PAYLOAD)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("without a report, the latched output once ready", async () => {
|
|
133
|
+
host.tool = { status: "ready", toolOutput: PAYLOAD }
|
|
134
|
+
const probe = bridgeProbe()
|
|
135
|
+
await render(<probe.Probe />)
|
|
136
|
+
expect(probe.current().getWidgetData()).toEqual(PAYLOAD)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("McpUseHostBridgeProvider model context (K20, N29)", () => {
|
|
141
|
+
it("the bridge's setModelContext is one native line: set, replaced, removed when blank", async () => {
|
|
142
|
+
const view = await render(<BridgeLine text="Viewing 3 incidents" />)
|
|
143
|
+
await flushEffects()
|
|
144
|
+
expect(lines(view)).toEqual(["Viewing 3 incidents"])
|
|
145
|
+
await rerender(view, <BridgeLine text="Viewing 2 incidents" />)
|
|
146
|
+
await flushEffects()
|
|
147
|
+
expect(lines(view), "last writer wins: still one line").toEqual(["Viewing 2 incidents"])
|
|
148
|
+
await rerender(view, <BridgeLine text="" />)
|
|
149
|
+
await flushEffects()
|
|
150
|
+
expect(lines(view)).toEqual([])
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it("never seeds the widgets' view state (N29)", async () => {
|
|
154
|
+
await render(<BridgeLine text="Viewing 3 incidents" />)
|
|
155
|
+
await flushEffects()
|
|
156
|
+
expect(host.useViewState).not.toHaveBeenCalled()
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it("useHostModelContext joins the tree on mount and leaves it on unmount or when blank", async () => {
|
|
160
|
+
const view = await render(
|
|
161
|
+
<>
|
|
162
|
+
<Line content="Filter: open" />
|
|
163
|
+
<Line content="Selected: #42" />
|
|
164
|
+
</>,
|
|
165
|
+
)
|
|
166
|
+
expect(lines(view)).toEqual(["Filter: open", "Selected: #42"])
|
|
167
|
+
await rerender(
|
|
168
|
+
view,
|
|
169
|
+
<>
|
|
170
|
+
<Line content="Filter: open" />
|
|
171
|
+
<Line content=" " />
|
|
172
|
+
</>,
|
|
173
|
+
)
|
|
174
|
+
expect(lines(view), "a blank line is removed").toEqual(["Filter: open"])
|
|
175
|
+
await rerender(view, <Line content="Filter: open" />)
|
|
176
|
+
expect(lines(view)).toEqual(["Filter: open"])
|
|
177
|
+
await rerender(view, null)
|
|
178
|
+
expect(lines(view), "unmounted, its line is gone").toEqual([])
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -14,24 +14,17 @@ import {
|
|
|
14
14
|
useOpenExternal,
|
|
15
15
|
useSendFollowUp,
|
|
16
16
|
useToolContext,
|
|
17
|
-
useViewState,
|
|
18
17
|
useViewTheme,
|
|
19
18
|
} from "mcp-use/react"
|
|
20
19
|
import { HostBridgeProvider, toHostBridge, type HostBridge } from "./host-bridge.js"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const McpUseViewContext = createContext(false)
|
|
30
|
-
|
|
31
|
-
/** Whether the subtree runs inside an mcp-use view (see {@link McpUseViewContext}). */
|
|
32
|
-
export function useIsInsideMcpUseView(): boolean {
|
|
33
|
-
return useContext(McpUseViewContext)
|
|
34
|
-
}
|
|
20
|
+
import {
|
|
21
|
+
McpUseViewContext,
|
|
22
|
+
ModelContextChannelContext,
|
|
23
|
+
ModelContextLines,
|
|
24
|
+
modelContextLine,
|
|
25
|
+
withModelContextLine,
|
|
26
|
+
type ModelContextChannel,
|
|
27
|
+
} from "./model-context.js"
|
|
35
28
|
|
|
36
29
|
type DynamicCallTool = (name: string, args: Record<string, unknown>) => Promise<unknown>
|
|
37
30
|
type BoundCall = (args: Record<string, unknown>) => Promise<unknown>
|
|
@@ -99,6 +92,34 @@ function useDynamicToolPool(): { callTool: DynamicCallTool; binders: ReactNode }
|
|
|
99
92
|
return { callTool, binders }
|
|
100
93
|
}
|
|
101
94
|
|
|
95
|
+
/** The line of the bridge's own `setModelContext` (last writer wins). */
|
|
96
|
+
const BRIDGE_LINE = "host-bridge"
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Where the view reports the payload it shows (the host's, a stripped-result
|
|
100
|
+
* recovery's, the user's retry or refresh), so `HostBridge.getWidgetData()`
|
|
101
|
+
* returns what the view shows (toolkit#176 K15): the bridge, not one
|
|
102
|
+
* component's local state, holds the view's data.
|
|
103
|
+
*/
|
|
104
|
+
const ViewResultSinkContext = createContext<((payload: unknown) => void) | null>(null)
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The sink for the payload a view shows, or `null` outside a
|
|
108
|
+
* {@link McpUseHostBridgeProvider}. `McpAppView` reports through it.
|
|
109
|
+
*/
|
|
110
|
+
export function useViewResultSink(): ((payload: unknown) => void) | null {
|
|
111
|
+
return useContext(ViewResultSinkContext)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* What `getWidgetData()` returns: the payload the view reported it shows,
|
|
116
|
+
* else the rendering tool's output once the call latched `"ready"`.
|
|
117
|
+
*/
|
|
118
|
+
function bridgeOutput(shown: unknown, tool: { status: string; toolOutput?: unknown }): unknown {
|
|
119
|
+
if (shown !== undefined) return shown
|
|
120
|
+
return tool.status === "ready" ? tool.toolOutput : undefined
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
/**
|
|
103
124
|
* Install the `mcp-use` {@link HostBridge} (plus the view-scope flag) for a
|
|
104
125
|
* subtree inside an mcp-use 2.x view.
|
|
@@ -112,10 +133,16 @@ function useDynamicToolPool(): { callTool: DynamicCallTool; binders: ReactNode }
|
|
|
112
133
|
* - `sendFollowup` / `openExternal` — `useSendFollowUp` / `useOpenExternal`;
|
|
113
134
|
* while the bridge is not connected (local `file://` preview), `openExternal`
|
|
114
135
|
* throws into `toHostBridge`'s documented `window.open` fallback.
|
|
115
|
-
* - `setModelContext` —
|
|
116
|
-
*
|
|
117
|
-
* `
|
|
118
|
-
*
|
|
136
|
+
* - `setModelContext` — one line of the view's model-context tree (an mcp-use
|
|
137
|
+
* `ModelContext` node beside the ones `HostModelContext` and
|
|
138
|
+
* `useHostModelContext` register), which hosts receive via
|
|
139
|
+
* `ui/update-model-context` (ext-apps) or `widgetState` (ChatGPT). The
|
|
140
|
+
* provider does not seed mcp-use's view state, so a widget's own
|
|
141
|
+
* `useViewState(default)` keeps its default (N29).
|
|
142
|
+
* - `getWidgetData` — the payload the view reports it shows
|
|
143
|
+
* ({@link useViewResultSink}): the host's, or the one it got by re-running
|
|
144
|
+
* the tool when the host stripped it or kept back an unusable one. Without
|
|
145
|
+
* a report, the rendering tool's `structuredContent` once ready.
|
|
119
146
|
*
|
|
120
147
|
* Must render inside a `bootstrapView`-mounted tree (the mcp-use hooks throw
|
|
121
148
|
* otherwise) — `McpToolkitApp` does this for the standard bundle; use it
|
|
@@ -128,9 +155,17 @@ export function McpUseHostBridgeProvider({ children }: { children: ReactNode }):
|
|
|
128
155
|
const tool = useToolContext()
|
|
129
156
|
const theme = useViewTheme()
|
|
130
157
|
const { isAvailable } = useHostContext()
|
|
131
|
-
const [,
|
|
158
|
+
const [lines, setLines] = useState<ReadonlyMap<string, string>>(() => new Map())
|
|
159
|
+
const channel = useMemo<ModelContextChannel>(
|
|
160
|
+
() => ({ set: (id, content) => setLines((prev) => withModelContextLine(prev, id, content)) }),
|
|
161
|
+
[],
|
|
162
|
+
)
|
|
163
|
+
// The payload the view reports it shows wins: after a latch the view could
|
|
164
|
+
// not render (`structuredContent: null` latches "ready"), the host's own
|
|
165
|
+
// output is not what the user sees.
|
|
166
|
+
const [shown, setShown] = useState<unknown>(undefined)
|
|
132
167
|
|
|
133
|
-
const output =
|
|
168
|
+
const output = bridgeOutput(shown, tool)
|
|
134
169
|
|
|
135
170
|
const bridge = useMemo<HostBridge>(
|
|
136
171
|
() =>
|
|
@@ -147,18 +182,26 @@ export function McpUseHostBridgeProvider({ children }: { children: ReactNode }):
|
|
|
147
182
|
},
|
|
148
183
|
output,
|
|
149
184
|
theme,
|
|
185
|
+
// toHostBridge writes model context as `{ __model_context }`: it
|
|
186
|
+
// becomes the bridge's line of the one channel, never view state.
|
|
150
187
|
setState: (state) => {
|
|
151
|
-
|
|
188
|
+
const text = state.__model_context
|
|
189
|
+
if (typeof text === "string") channel.set(BRIDGE_LINE, modelContextLine(text))
|
|
152
190
|
return Promise.resolve()
|
|
153
191
|
},
|
|
154
192
|
}),
|
|
155
|
-
[callTool, sendFollowUp, openExternal, output, theme, isAvailable,
|
|
193
|
+
[callTool, sendFollowUp, openExternal, output, theme, isAvailable, channel],
|
|
156
194
|
)
|
|
157
195
|
|
|
158
196
|
return (
|
|
159
197
|
<McpUseViewContext.Provider value={true}>
|
|
160
198
|
{binders}
|
|
161
|
-
<
|
|
199
|
+
<ModelContextLines lines={lines} />
|
|
200
|
+
<ModelContextChannelContext.Provider value={channel}>
|
|
201
|
+
<ViewResultSinkContext.Provider value={setShown}>
|
|
202
|
+
<HostBridgeProvider bridge={bridge}>{children}</HostBridgeProvider>
|
|
203
|
+
</ViewResultSinkContext.Provider>
|
|
204
|
+
</ModelContextChannelContext.Provider>
|
|
162
205
|
</McpUseViewContext.Provider>
|
|
163
206
|
)
|
|
164
207
|
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
3
|
+
import { describe, expect, it, vi } from "vitest"
|
|
4
|
+
import {
|
|
5
|
+
HostModelContext,
|
|
6
|
+
McpUseViewContext,
|
|
7
|
+
ModelContextLines,
|
|
8
|
+
modelContextLine,
|
|
9
|
+
useIsInsideMcpUseView,
|
|
10
|
+
withModelContextLine,
|
|
11
|
+
} from "./model-context.js"
|
|
12
|
+
|
|
13
|
+
// The native node, made visible (it throws outside a bootstrapView mount).
|
|
14
|
+
vi.mock(import("mcp-use/react"), async (importOriginal) => {
|
|
15
|
+
const actual = await importOriginal()
|
|
16
|
+
const { createElement, Fragment } = await import("react")
|
|
17
|
+
const ModelContext = ({ content, children }: { content: string; children?: ReactNode }) =>
|
|
18
|
+
createElement(
|
|
19
|
+
Fragment,
|
|
20
|
+
null,
|
|
21
|
+
createElement("meta", { "data-model-context": content }),
|
|
22
|
+
children,
|
|
23
|
+
)
|
|
24
|
+
return { ...actual, ModelContext: ModelContext as unknown as typeof actual.ModelContext }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The view's ONE model-context channel (toolkit#176 K20, N29): every line
|
|
29
|
+
* the view reports joins the native mcp-use tree inside a view; outside one
|
|
30
|
+
* it falls back to the bridge.
|
|
31
|
+
*/
|
|
32
|
+
describe("withModelContextLine", () => {
|
|
33
|
+
it("sets, replaces and removes a line, keeping the order of first registration", () => {
|
|
34
|
+
const one = withModelContextLine(new Map(), "a", "Viewing 3 incidents")
|
|
35
|
+
const two = withModelContextLine(one, "b", "Filter: open")
|
|
36
|
+
const replaced = withModelContextLine(two, "a", "Viewing 2 incidents")
|
|
37
|
+
expect([...replaced]).toEqual([
|
|
38
|
+
["a", "Viewing 2 incidents"],
|
|
39
|
+
["b", "Filter: open"],
|
|
40
|
+
])
|
|
41
|
+
expect([...withModelContextLine(replaced, "a", null)]).toEqual([["b", "Filter: open"]])
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("returns the same map when nothing changes (no re-render)", () => {
|
|
45
|
+
const lines = withModelContextLine(new Map(), "a", "x")
|
|
46
|
+
expect(withModelContextLine(lines, "a", "x")).toBe(lines)
|
|
47
|
+
expect(withModelContextLine(lines, "missing", null)).toBe(lines)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe("modelContextLine", () => {
|
|
52
|
+
it("a blank line is nothing to report", () => {
|
|
53
|
+
expect(modelContextLine("Viewing 3 incidents")).toBe("Viewing 3 incidents")
|
|
54
|
+
expect(modelContextLine(" ")).toBeNull()
|
|
55
|
+
expect(modelContextLine("")).toBeNull()
|
|
56
|
+
expect(modelContextLine(null)).toBeNull()
|
|
57
|
+
expect(modelContextLine(undefined)).toBeNull()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe("HostModelContext", () => {
|
|
62
|
+
it("inside a view: the native node, its children nested below it", () => {
|
|
63
|
+
const html = renderToStaticMarkup(
|
|
64
|
+
<McpUseViewContext.Provider value={true}>
|
|
65
|
+
<HostModelContext content="Viewing 3 incidents">
|
|
66
|
+
<span>board</span>
|
|
67
|
+
</HostModelContext>
|
|
68
|
+
</McpUseViewContext.Provider>,
|
|
69
|
+
)
|
|
70
|
+
expect(html).toBe('<meta data-model-context="Viewing 3 incidents"/><span>board</span>')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("reports one line without children", () => {
|
|
74
|
+
const html = renderToStaticMarkup(
|
|
75
|
+
<McpUseViewContext.Provider value={true}>
|
|
76
|
+
<HostModelContext content="The view could not be shown." />
|
|
77
|
+
</McpUseViewContext.Provider>,
|
|
78
|
+
)
|
|
79
|
+
expect(html).toBe('<meta data-model-context="The view could not be shown."/>')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it("outside a view: no native node, the children render (the bridge gets the line)", () => {
|
|
83
|
+
const html = renderToStaticMarkup(
|
|
84
|
+
<HostModelContext content="Viewing 3 incidents">
|
|
85
|
+
<span>board</span>
|
|
86
|
+
</HostModelContext>,
|
|
87
|
+
)
|
|
88
|
+
expect(html).toBe("<span>board</span>")
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe("ModelContextLines / useIsInsideMcpUseView", () => {
|
|
93
|
+
it("renders one native node per channel line", () => {
|
|
94
|
+
const lines = new Map([
|
|
95
|
+
["a", "Viewing 3 incidents"],
|
|
96
|
+
["host-bridge", "Selected: INC-7"],
|
|
97
|
+
])
|
|
98
|
+
expect(renderToStaticMarkup(<ModelContextLines lines={lines} />)).toBe(
|
|
99
|
+
'<meta data-model-context="Viewing 3 incidents"/><meta data-model-context="Selected: INC-7"/>',
|
|
100
|
+
)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("reports the view scope", () => {
|
|
104
|
+
function Probe() {
|
|
105
|
+
return <i>{String(useIsInsideMcpUseView())}</i>
|
|
106
|
+
}
|
|
107
|
+
expect(renderToStaticMarkup(<Probe />)).toBe("<i>false</i>")
|
|
108
|
+
expect(
|
|
109
|
+
renderToStaticMarkup(
|
|
110
|
+
<McpUseViewContext.Provider value={true}>
|
|
111
|
+
<Probe />
|
|
112
|
+
</McpUseViewContext.Provider>,
|
|
113
|
+
),
|
|
114
|
+
).toBe("<i>true</i>")
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useId, type ReactNode } from "react"
|
|
2
|
+
import { ModelContext } from "mcp-use/react"
|
|
3
|
+
import { useHostBridgeOrNull } from "./host-bridge.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The view's model context: what the model reads about the view on its next
|
|
7
|
+
* turn (`ui/update-model-context`). The one module that builds on mcp-use's
|
|
8
|
+
* native `ModelContext`, which throws outside a `bootstrapView` mount; every
|
|
9
|
+
* other component reports through {@link HostModelContext} or
|
|
10
|
+
* {@link useHostModelContext} (toolkit#176 K20).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `true` inside an mcp-use 2.x view — i.e. under a `McpUseHostBridgeProvider`,
|
|
15
|
+
* which itself only renders under a `bootstrapView`-mounted tree. Components
|
|
16
|
+
* that want to use `mcp-use/react` primitives (which throw outside a view)
|
|
17
|
+
* branch on this instead of probing mcp-use internals. Exported for the
|
|
18
|
+
* provider and for tests that render a view-scoped subtree.
|
|
19
|
+
*/
|
|
20
|
+
export const McpUseViewContext = createContext(false)
|
|
21
|
+
|
|
22
|
+
/** Whether the subtree runs inside an mcp-use view (see {@link McpUseViewContext}). */
|
|
23
|
+
export function useIsInsideMcpUseView(): boolean {
|
|
24
|
+
return useContext(McpUseViewContext)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Model-context reporting that works on every host — the host-portable
|
|
29
|
+
* replacement for rendering `mcp-use/react`'s `ModelContext` directly.
|
|
30
|
+
*
|
|
31
|
+
* Inside an mcp-use view it renders the native `ModelContext`, which nests:
|
|
32
|
+
* the lines of its children hang below its own in the one description the
|
|
33
|
+
* host receives. Everywhere else — ChatGPT via `createChatGptHostBridge`, the
|
|
34
|
+
* `WidgetFixtureHost` harness, standalone apps — the description flows
|
|
35
|
+
* through the bridge's `setModelContext` instead (last-writer-wins; fine for
|
|
36
|
+
* the single-widget mounts those hosts render).
|
|
37
|
+
*
|
|
38
|
+
* Widgets that self-fetch and report their own context (cockpit views like
|
|
39
|
+
* `TasksBoard`) must use this instead of importing `ModelContext` from
|
|
40
|
+
* `mcp-use/react` — the direct import crashes the widget in every non-view
|
|
41
|
+
* mount, starting with the widget playground. Without `children` it reports
|
|
42
|
+
* one line where it is rendered.
|
|
43
|
+
*/
|
|
44
|
+
export function HostModelContext({
|
|
45
|
+
content,
|
|
46
|
+
children,
|
|
47
|
+
}: {
|
|
48
|
+
content: string
|
|
49
|
+
children?: ReactNode
|
|
50
|
+
}): ReactNode {
|
|
51
|
+
const insideView = useIsInsideMcpUseView()
|
|
52
|
+
if (insideView) return <ModelContext content={content}>{children}</ModelContext>
|
|
53
|
+
return <BridgeModelContext content={content}>{children}</BridgeModelContext>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function BridgeModelContext({ content, children }: { content: string; children?: ReactNode }) {
|
|
57
|
+
const bridge = useHostBridgeOrNull()
|
|
58
|
+
const setModelContext = bridge?.setModelContext
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setModelContext?.(content)
|
|
61
|
+
}, [setModelContext, content])
|
|
62
|
+
return children
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The view's ONE model-context channel: lines registered here render as
|
|
67
|
+
* root-level native `ModelContext` nodes ({@link ModelContextLines}), the
|
|
68
|
+
* tree `HostModelContext` nodes join too, so the host receives everything
|
|
69
|
+
* the view reports in one description. `McpUseHostBridgeProvider` owns it.
|
|
70
|
+
*/
|
|
71
|
+
export interface ModelContextChannel {
|
|
72
|
+
/** Set (`content`) or remove (`null`) the line registered under `id`. */
|
|
73
|
+
set: (id: string, content: string | null) => void
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const ModelContextChannelContext = createContext<ModelContextChannel | null>(null)
|
|
77
|
+
|
|
78
|
+
/** `lines` with `id` set to `content` (or removed for `null`); the same map when nothing changes. */
|
|
79
|
+
export function withModelContextLine(
|
|
80
|
+
lines: ReadonlyMap<string, string>,
|
|
81
|
+
id: string,
|
|
82
|
+
content: string | null,
|
|
83
|
+
): ReadonlyMap<string, string> {
|
|
84
|
+
if (content === null ? !lines.has(id) : lines.get(id) === content) return lines
|
|
85
|
+
const next = new Map(lines)
|
|
86
|
+
if (content === null) next.delete(id)
|
|
87
|
+
else next.set(id, content)
|
|
88
|
+
return next
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A line of model context, or `null` for a blank one (nothing to report). */
|
|
92
|
+
export function modelContextLine(content: string | null | undefined): string | null {
|
|
93
|
+
return content?.trim() ? content : null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Renders the channel's lines as native `ModelContext` nodes (inside a view only). */
|
|
97
|
+
export function ModelContextLines({ lines }: { lines: ReadonlyMap<string, string> }): ReactNode {
|
|
98
|
+
return [...lines].map(([id, content]) => <ModelContext key={id} content={content} />)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Report one line of model context for as long as the calling component is
|
|
103
|
+
* mounted: what the user is looking at, a filter, an error the user sees.
|
|
104
|
+
* The channel helper for components with nothing to wrap
|
|
105
|
+
* ({@link HostModelContext} is the wrapping form).
|
|
106
|
+
*
|
|
107
|
+
* Inside an mcp-use view the line joins the view's one model-context tree and
|
|
108
|
+
* leaves it on unmount or when `content` turns blank. Elsewhere (the ChatGPT
|
|
109
|
+
* bridge, the fixture harness, standalone apps) it goes through the bridge's
|
|
110
|
+
* `setModelContext`, last writer wins.
|
|
111
|
+
*/
|
|
112
|
+
export function useHostModelContext(content: string | null | undefined): void {
|
|
113
|
+
const channel = useContext(ModelContextChannelContext)
|
|
114
|
+
const setModelContext = useHostBridgeOrNull()?.setModelContext
|
|
115
|
+
const id = useId()
|
|
116
|
+
const line = modelContextLine(content)
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
if (channel) {
|
|
119
|
+
channel.set(id, line)
|
|
120
|
+
return () => channel.set(id, null)
|
|
121
|
+
}
|
|
122
|
+
if (line !== null) setModelContext?.(line)
|
|
123
|
+
return undefined
|
|
124
|
+
}, [channel, id, line, setModelContext])
|
|
125
|
+
}
|