@miragon/mcp-toolkit-ui 2.7.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 +7 -4
- 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.js +1 -1
- 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 +21 -7
- 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 +11 -4
- 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/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 +2 -1
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +26 -13
- 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 +8 -2
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +12 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +18 -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 +143 -1
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +191 -1
- package/dist/app/builder/builder-model.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/use-builder-catalogue.d.ts +4 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +44 -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/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 +13 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +75 -81
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +12 -3
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +21 -5
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +11 -3
- package/dist/app/mcp-app-view-states.d.ts.map +1 -1
- package/dist/app/mcp-app-view-states.js +29 -8
- package/dist/app/mcp-app-view-states.js.map +1 -1
- package/dist/app/mcp-app-view.d.ts +33 -1
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +181 -127
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +6 -1
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +8 -1
- 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 +4 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -1
- package/dist/app/use-view-lifecycle.js +2 -0
- package/dist/app/use-view-lifecycle.js.map +1 -1
- package/dist/app/view-lifecycle.d.ts +80 -4
- package/dist/app/view-lifecycle.d.ts.map +1 -1
- package/dist/app/view-lifecycle.js +90 -3
- package/dist/app/view-lifecycle.js.map +1 -1
- 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 +3 -1
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +22 -10
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.js +1 -1
- 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 +8 -6
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts.map +1 -1
- package/dist/components/EmptyState.js +4 -2
- package/dist/components/EmptyState.js.map +1 -1
- package/dist/components/ErrorState.d.ts +5 -2
- package/dist/components/ErrorState.d.ts.map +1 -1
- package/dist/components/ErrorState.js +25 -8
- package/dist/components/ErrorState.js.map +1 -1
- package/dist/components/FilterBar.d.ts +15 -4
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +11 -6
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +66 -21
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +101 -34
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +31 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +30 -11
- 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 +27 -7
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +21 -6
- 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 +9 -3
- package/dist/components/Toast.d.ts.map +1 -1
- package/dist/components/Toast.js +31 -16
- package/dist/components/Toast.js.map +1 -1
- 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 +47 -52
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +58 -120
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +52 -10
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -16
- 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 +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -6
- 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/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 +41 -1
- package/dist/lib/toast-model.d.ts.map +1 -1
- package/dist/lib/toast-model.js +67 -1
- package/dist/lib/toast-model.js.map +1 -1
- package/dist/lib/tone-utils.d.ts +58 -6
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +66 -4
- 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 +1 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/message-types.d.ts +117 -4
- package/dist/locale/message-types.d.ts.map +1 -1
- package/dist/locale/messages-de.d.ts.map +1 -1
- package/dist/locale/messages-de.js +44 -10
- package/dist/locale/messages-de.js.map +1 -1
- package/dist/locale/messages-en.d.ts.map +1 -1
- package/dist/locale/messages-en.js +44 -10
- package/dist/locale/messages-en.js.map +1 -1
- package/dist/locale/toolkit-messages.d.ts +19 -1
- package/dist/locale/toolkit-messages.d.ts.map +1 -1
- package/dist/locale/toolkit-messages.js +54 -1
- package/dist/locale/toolkit-messages.js.map +1 -1
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +2 -0
- 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 +9 -4
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.js +1 -1
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/dialog.d.ts +1 -1
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +10 -3
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/select.js +1 -1
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +1 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +6 -2
- package/dist/primitives/sheet.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 +30 -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/theme/create-theme.d.ts +19 -1
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +15 -0
- package/dist/theme/create-theme.js.map +1 -1
- package/package.json +5 -4
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +1 -1
- package/src/app/builder/CanvasRow.tsx +63 -17
- package/src/app/builder/CatalogueSheet.tsx +50 -29
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/LayoutSurface.tsx +206 -66
- package/src/app/builder/PipelineStrip.tsx +253 -153
- package/src/app/builder/SaveDialog.tsx +14 -2
- package/src/app/builder/Toolbar.tsx +144 -59
- package/src/app/builder/Workspace.tsx +226 -58
- package/src/app/builder/builder-model.ts +341 -2
- 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/reveal.ts +10 -0
- package/src/app/builder/use-builder-catalogue.ts +56 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/index.ts +12 -2
- package/src/app/layout-builder.tsx +178 -191
- package/src/app/mcp-app-view-chrome.test.tsx +141 -1
- package/src/app/mcp-app-view-chrome.tsx +56 -6
- package/src/app/mcp-app-view-states.tsx +51 -11
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +222 -2
- package/src/app/mcp-app-view.tsx +267 -117
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +8 -1
- 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 +6 -0
- package/src/app/view-lifecycle.test.ts +216 -0
- package/src/app/view-lifecycle.ts +165 -5
- 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 +57 -16
- package/src/app/widget-renderer.tsx +1 -1
- package/src/components/DrillButton.tsx +23 -10
- package/src/components/EmptyState.tsx +6 -4
- package/src/components/ErrorState.tsx +31 -9
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +52 -26
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +297 -90
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +102 -15
- 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 +63 -10
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +37 -19
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +98 -9
- package/src/components/tone-figures.test.tsx +8 -4
- 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 +3 -4
- package/src/hooks/use-tool-result-recovery.ts +84 -142
- package/src/hooks/use-view-data.test.tsx +118 -9
- package/src/hooks/use-view-data.ts +128 -18
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +104 -2
- package/src/index.ts +48 -11
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -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 +166 -1
- package/src/lib/toast-model.ts +104 -2
- package/src/lib/tone-utils.test.ts +86 -10
- package/src/lib/tone-utils.ts +89 -8
- 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 +1 -0
- package/src/locale/message-types.ts +118 -2
- package/src/locale/messages-de.ts +49 -10
- package/src/locale/messages-en.ts +49 -10
- package/src/locale/toolkit-messages.test.ts +133 -0
- package/src/locale/toolkit-messages.ts +63 -2
- package/src/primitives/alert.tsx +3 -0
- package/src/primitives/badge.tsx +9 -4
- package/src/primitives/button.tsx +1 -1
- package/src/primitives/dialog.tsx +10 -1
- package/src/primitives/select.tsx +1 -1
- package/src/primitives/sheet.tsx +7 -0
- 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 +38 -6
- package/src/render-effects.tsx +96 -0
- package/src/theme/cd-tokens.test.ts +3 -1
- package/src/theme/create-theme.test.ts +10 -0
- package/src/theme/create-theme.ts +28 -0
- package/src/theme/theme-css.test.ts +51 -0
- package/src/theme.css +31 -1
- package/ui-catalog.json +448 -41
package/src/app/mcp-app-view.tsx
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
|
-
import { useState, useCallback, useMemo, type ReactNode } from "react"
|
|
1
|
+
import { useState, useCallback, useEffect, useMemo, type ReactNode } from "react"
|
|
2
2
|
import { LucideProvider } from "lucide-react"
|
|
3
|
-
import { ToolCancelledError,
|
|
4
|
-
import { ToastProvider
|
|
5
|
-
import { ToolkitLocaleFallback, useToolkitMessages } from "../locale/locale-provider.js"
|
|
6
|
-
import type { ViewMessages } from "../locale/toolkit-messages.js"
|
|
7
|
-
import { AppQueryProvider, queryClient } from "../providers/query-provider.js"
|
|
3
|
+
import { ToolCancelledError, useHostContext, useToolContext } from "mcp-use/react"
|
|
4
|
+
import { ToastProvider } from "../components/Toast.js"
|
|
8
5
|
import {
|
|
6
|
+
ToolkitLocaleFallback,
|
|
7
|
+
useToolkitLocale,
|
|
8
|
+
useToolkitMessages,
|
|
9
|
+
} from "../locale/locale-provider.js"
|
|
10
|
+
import { formatClockTime, resolveFormatLocale } from "../locale/toolkit-messages.js"
|
|
11
|
+
import { AppQueryProvider, useAppQueryClient } from "../providers/query-provider.js"
|
|
12
|
+
import { resolveToolSafety } from "../hooks/tool-result-recovery-model.js"
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_ASSUME_STRIPPED_AFTER_MS,
|
|
9
15
|
resolveRecoveryToolName,
|
|
10
16
|
useToolResultRecovery,
|
|
17
|
+
type RecoveryPolicy,
|
|
11
18
|
} from "../hooks/use-tool-result-recovery.js"
|
|
12
19
|
import { ICON_STROKE } from "../lib/icon-stroke.js"
|
|
13
20
|
import { useHostBridge } from "./host-bridge.js"
|
|
21
|
+
import { useViewResultSink } from "./mcp-use-host-bridge.js"
|
|
22
|
+
import { HostModelContext } from "./model-context.js"
|
|
14
23
|
import {
|
|
15
24
|
applyBuilderCommit,
|
|
16
|
-
fullscreenAvailable,
|
|
17
25
|
isCompleteViewData,
|
|
18
26
|
StepErrors,
|
|
19
27
|
ViewBody,
|
|
@@ -27,13 +35,22 @@ import {
|
|
|
27
35
|
} from "./mcp-app-view-chrome.js"
|
|
28
36
|
import { ViewPlaceholder, type DescribeViewError } from "./mcp-app-view-states.js"
|
|
29
37
|
import type { LayoutBuilderLabels } from "./layout-builder.js"
|
|
38
|
+
import { useBuildModeFocus } from "./use-build-mode-focus.js"
|
|
39
|
+
import { useFullscreenToggle, useViewRefresh } from "./use-view-actions.js"
|
|
30
40
|
import { useRetryFocus, useViewLifecycle } from "./use-view-lifecycle.js"
|
|
31
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_SLOW_HINT_MS,
|
|
43
|
+
describeViewForModel,
|
|
44
|
+
reloadedAtOf,
|
|
45
|
+
type ViewLifecycle,
|
|
46
|
+
type ViewSource,
|
|
47
|
+
} from "./view-lifecycle.js"
|
|
32
48
|
import type { WidgetComponent } from "./widget-renderer.js"
|
|
33
49
|
|
|
34
50
|
export type { McpAppViewLabels, ViewTitleLevel, ViewTitleMode } from "./mcp-app-view-chrome.js"
|
|
35
51
|
export type { DescribeStep } from "./mcp-app-view-chrome.js"
|
|
36
|
-
export type { DescribeViewError } from "./mcp-app-view-states.js"
|
|
52
|
+
export type { DescribeViewError, ViewErrorKind } from "./mcp-app-view-states.js"
|
|
53
|
+
export type { RecoveryPolicy } from "../hooks/use-tool-result-recovery.js"
|
|
37
54
|
|
|
38
55
|
export interface McpAppViewProps {
|
|
39
56
|
/**
|
|
@@ -88,6 +105,34 @@ export interface McpAppViewProps {
|
|
|
88
105
|
describeError?: DescribeViewError
|
|
89
106
|
/** Human title of a pipeline step in the failed-steps alert (default: the step id). */
|
|
90
107
|
describeStep?: DescribeStep
|
|
108
|
+
/**
|
|
109
|
+
* Which originating tools the view may re-execute on its own when the host
|
|
110
|
+
* strips the result's `structuredContent` (claude.ai, Claude Desktop).
|
|
111
|
+
* `"auto"` (default): any tool the host does not report as writing
|
|
112
|
+
* (`readOnlyHint: false` or `destructiveHint: true`); `"read-only"`: only
|
|
113
|
+
* tools reported with `readOnlyHint: true`; `"never"`: none. A recovery
|
|
114
|
+
* the policy forbids shows a note that the chat did not pass the result to
|
|
115
|
+
* the view. It offers to load the view only for a tool the host does not
|
|
116
|
+
* report as writing; for a writing tool it asks for the current state in
|
|
117
|
+
* the chat, so one click never runs the write a second time.
|
|
118
|
+
*/
|
|
119
|
+
recoverStrippedResult?: RecoveryPolicy
|
|
120
|
+
/**
|
|
121
|
+
* Grace period (ms) after which a silent call counts as a stripped result.
|
|
122
|
+
* Only used where the view could not watch the host's notifications from
|
|
123
|
+
* the start (e.g. a custom shell mounted under its own `bootstrapView`
|
|
124
|
+
* without `installToolResultObserver`); a view mounted by
|
|
125
|
+
* `mountMcpToolkitApp` sees the stripped result itself and never guesses,
|
|
126
|
+
* so a slow tool is never re-run. Default 2500 ms.
|
|
127
|
+
*/
|
|
128
|
+
strippedResultGraceMs?: number
|
|
129
|
+
/**
|
|
130
|
+
* Put the tool name into the cache keys of the data hooks inside the view
|
|
131
|
+
* (`[...queryKey, toolName, args]`, see `toolQueryKey`), so two tools never
|
|
132
|
+
* share an entry. Omitted: an enclosing `AppQueryProvider`'s setting, else
|
|
133
|
+
* the 2.x key `[...queryKey, args]`. The next major makes it the default.
|
|
134
|
+
*/
|
|
135
|
+
toolNameInQueryKey?: boolean
|
|
91
136
|
}
|
|
92
137
|
|
|
93
138
|
/**
|
|
@@ -97,6 +142,8 @@ export interface McpAppViewProps {
|
|
|
97
142
|
* the host's locale unless a `LocaleProvider` sets one, so the chrome follows
|
|
98
143
|
* the host language; `useLocale()` in consumer widgets is unaffected. Lucide
|
|
99
144
|
* icons draw at the CI stroke (2.5) throughout, the toast stack included.
|
|
145
|
+
* The widgets' data hooks fetch through an enclosing `AppQueryProvider`'s
|
|
146
|
+
* client, else the toolkit's shared `queryClient`.
|
|
100
147
|
*/
|
|
101
148
|
export function McpAppView(props: McpAppViewProps) {
|
|
102
149
|
const { locale } = useHostContext()
|
|
@@ -112,17 +159,32 @@ export function McpAppView(props: McpAppViewProps) {
|
|
|
112
159
|
)
|
|
113
160
|
}
|
|
114
161
|
|
|
162
|
+
/** The payload the view shows, where it came from, and when the view loaded it itself. */
|
|
163
|
+
interface ShownView {
|
|
164
|
+
data: ViewData | null
|
|
165
|
+
source: ViewSource | null
|
|
166
|
+
/**
|
|
167
|
+
* When the view re-loaded the payload itself (epoch ms), so its figures can
|
|
168
|
+
* differ from the result the chat answered from (`reloadedAtOf`); `null`
|
|
169
|
+
* otherwise.
|
|
170
|
+
*/
|
|
171
|
+
reloadedAt: number | null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const NO_VIEW: ShownView = { data: null, source: null, reloadedAt: null }
|
|
175
|
+
|
|
115
176
|
/**
|
|
116
|
-
* Host-delivered payload →
|
|
117
|
-
* Render-phase setState (not an effect) so the React
|
|
118
|
-
* cascading render.
|
|
177
|
+
* Host-delivered payload → the shown view, recovered payloads included, each
|
|
178
|
+
* tagged with its source. Render-phase setState (not an effect) so the React
|
|
179
|
+
* Compiler doesn't flag a cascading render.
|
|
119
180
|
*/
|
|
120
181
|
function useHostViewData(
|
|
121
182
|
initialViewData: ViewData | undefined,
|
|
122
183
|
isPending: boolean,
|
|
123
184
|
recovered: ViewData | null,
|
|
185
|
+
recoveredAt: number | null,
|
|
124
186
|
) {
|
|
125
|
-
const [
|
|
187
|
+
const [view, setView] = useState<ShownView>(NO_VIEW)
|
|
126
188
|
// `prev` MUST start as `null`, not `initialViewData`: hosts that expose the
|
|
127
189
|
// complete tool output before the app's first render (e.g. window.openai
|
|
128
190
|
// globals injected ahead of script execution) would otherwise never trip the
|
|
@@ -132,95 +194,148 @@ function useHostViewData(
|
|
|
132
194
|
const [prevInitial, setPrevInitial] = useState<ViewData | undefined | null>(null)
|
|
133
195
|
if (initialViewData !== prevInitial) {
|
|
134
196
|
setPrevInitial(initialViewData)
|
|
135
|
-
if (!isPending && isCompleteViewData(initialViewData))
|
|
197
|
+
if (!isPending && isCompleteViewData(initialViewData)) {
|
|
198
|
+
setView({ data: initialViewData, source: "host", reloadedAt: null })
|
|
199
|
+
}
|
|
136
200
|
}
|
|
137
201
|
const [prevRecovered, setPrevRecovered] = useState<ViewData | null>(null)
|
|
138
202
|
if (recovered !== prevRecovered) {
|
|
139
203
|
setPrevRecovered(recovered)
|
|
140
|
-
// The host-delivered payload always wins; recovery
|
|
141
|
-
|
|
204
|
+
// The host-delivered payload always wins; a recovery fills the gap, and a
|
|
205
|
+
// recovery for newer arguments replaces an older recovered payload (N21).
|
|
206
|
+
if (recovered) {
|
|
207
|
+
setView((prev) =>
|
|
208
|
+
prev.data === null || prev.source === "recovered"
|
|
209
|
+
? { data: recovered, source: "recovered", reloadedAt: recoveredAt }
|
|
210
|
+
: prev,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
142
213
|
}
|
|
143
|
-
return [
|
|
214
|
+
return [view, setView] as const
|
|
144
215
|
}
|
|
145
216
|
|
|
146
217
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* the
|
|
218
|
+
* Hands the payload the view shows (the host's, its own recovery's, the
|
|
219
|
+
* user's retry or refresh) to the host bridge, so `HostBridge.getWidgetData()`
|
|
220
|
+
* returns what the view shows (toolkit#176 K15), also after a latch the view
|
|
221
|
+
* could not render.
|
|
150
222
|
*/
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
} catch (e) {
|
|
179
|
-
failure = e
|
|
180
|
-
} finally {
|
|
181
|
-
setIsRefreshing(false)
|
|
182
|
-
}
|
|
183
|
-
if (failure === null) return
|
|
184
|
-
console.warn("[mcp-toolkit] refresh failed:", failure)
|
|
185
|
-
toast.show({
|
|
186
|
-
tone: "danger",
|
|
187
|
-
title: labels.refreshFailed,
|
|
188
|
-
description: labels.refreshFailedHint,
|
|
189
|
-
})
|
|
190
|
-
}, [callTool, labels, refreshToolName, setViewData, toast, viewData])
|
|
191
|
-
return { isRefreshing, refreshView }
|
|
223
|
+
function useReportShownPayload(view: ShownView) {
|
|
224
|
+
const report = useViewResultSink()
|
|
225
|
+
const payload = view.data
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (payload) report?.(payload)
|
|
228
|
+
}, [payload, report])
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The rendering call as mcp-use 2.x latches it, plus the tool the host names. */
|
|
232
|
+
function useRenderCall() {
|
|
233
|
+
const tool = useToolContext()
|
|
234
|
+
const { hostContext } = useHostContext()
|
|
235
|
+
// Since mcp-use 2.7.1 a host cancellation latches as `status: "error"`.
|
|
236
|
+
const isCancelled = tool.status === "error" && tool.error instanceof ToolCancelledError
|
|
237
|
+
const isCallError = tool.status === "error" && !isCancelled
|
|
238
|
+
return {
|
|
239
|
+
tool,
|
|
240
|
+
hostContext,
|
|
241
|
+
isPending: tool.status === "pending",
|
|
242
|
+
isCancelled,
|
|
243
|
+
isCallError,
|
|
244
|
+
callError: isCallError ? tool.error : undefined,
|
|
245
|
+
initialViewData: tool.status === "ready" ? (tool.toolOutput as ViewData) : undefined,
|
|
246
|
+
toolName: resolveRecoveryToolName(hostContext),
|
|
247
|
+
/** The host reports the tool as writing: the view never offers to re-run it after a block. */
|
|
248
|
+
toolWrites: resolveToolSafety(hostContext) === "writes",
|
|
249
|
+
}
|
|
192
250
|
}
|
|
193
251
|
|
|
194
252
|
/**
|
|
195
|
-
* The
|
|
196
|
-
*
|
|
253
|
+
* The payload the view shows: the host's, else the stripped-result recovery's.
|
|
254
|
+
* Hosts that strip `structuredContent` (claude.ai) never latch a result under
|
|
255
|
+
* mcp-use 2.x; the recovery re-executes the call once when the stripped
|
|
256
|
+
* result arrives. It never fires on a cancelled or failed call, nor while a
|
|
257
|
+
* slow tool is still running (#176): those render their state.
|
|
197
258
|
*/
|
|
198
|
-
function
|
|
199
|
-
|
|
259
|
+
function useShownView(
|
|
260
|
+
call: ReturnType<typeof useRenderCall>,
|
|
261
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>,
|
|
262
|
+
policy: RecoveryPolicy = "auto",
|
|
263
|
+
graceMs: number = DEFAULT_ASSUME_STRIPPED_AFTER_MS,
|
|
200
264
|
) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
265
|
+
const recovery = useToolResultRecovery<ViewData>({
|
|
266
|
+
resultReady: !call.isPending,
|
|
267
|
+
cancelled: call.isCancelled,
|
|
268
|
+
errored: call.isCallError,
|
|
269
|
+
props: call.initialViewData,
|
|
270
|
+
isValid: isCompleteViewData,
|
|
271
|
+
toolInput: call.tool.toolInput,
|
|
272
|
+
hostContext: call.hostContext,
|
|
273
|
+
callTool,
|
|
274
|
+
policy,
|
|
275
|
+
assumeStrippedAfterMs: graceMs,
|
|
276
|
+
})
|
|
277
|
+
const [view, setView] = useHostViewData(
|
|
278
|
+
call.initialViewData,
|
|
279
|
+
call.isPending,
|
|
280
|
+
recovery.data,
|
|
281
|
+
recovery.recoveredAt,
|
|
282
|
+
)
|
|
283
|
+
useReportShownPayload(view)
|
|
284
|
+
// A re-run's figures can differ from the answer in the chat (N23): stamp
|
|
285
|
+
// it, unless the original call failed and the chat never had figures.
|
|
286
|
+
const callFailed = call.isCallError || call.isCancelled
|
|
287
|
+
const showRetried = useCallback(
|
|
288
|
+
(data: ViewData) =>
|
|
289
|
+
setView({ data, source: "retry", reloadedAt: reloadedAtOf("retry", Date.now(), callFailed) }),
|
|
290
|
+
[setView, callFailed],
|
|
291
|
+
)
|
|
292
|
+
const showRefreshed = useCallback(
|
|
293
|
+
(data: ViewData) =>
|
|
294
|
+
setView({
|
|
295
|
+
data,
|
|
296
|
+
source: "refresh",
|
|
297
|
+
reloadedAt: reloadedAtOf("refresh", Date.now(), callFailed),
|
|
298
|
+
}),
|
|
299
|
+
[setView, callFailed],
|
|
300
|
+
)
|
|
217
301
|
return {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
302
|
+
view,
|
|
303
|
+
setView,
|
|
304
|
+
showRetried,
|
|
305
|
+
showRefreshed,
|
|
306
|
+
// Without a named tool nothing can re-run it: a plain failure. A policy
|
|
307
|
+
// block is its own state: the tool ran, and the view must not repeat it.
|
|
308
|
+
recoveryFailed: recovery.failed || recovery.blocked === "no-tool",
|
|
309
|
+
recoveryBlocked: recovery.blocked === "policy",
|
|
221
310
|
}
|
|
222
311
|
}
|
|
223
312
|
|
|
313
|
+
/**
|
|
314
|
+
* The view's own line of model context (toolkit#176 K20, N23): an error the
|
|
315
|
+
* user sees instead of the view, failed data steps, or data the view
|
|
316
|
+
* re-loaded itself (a recovery, a retry, a refresh). Renders nothing while
|
|
317
|
+
* there is nothing to report.
|
|
318
|
+
*/
|
|
319
|
+
function ViewModelContext({
|
|
320
|
+
lifecycle,
|
|
321
|
+
toolName,
|
|
322
|
+
view,
|
|
323
|
+
}: {
|
|
324
|
+
lifecycle: ViewLifecycle
|
|
325
|
+
toolName: string | undefined
|
|
326
|
+
view: ShownView
|
|
327
|
+
}) {
|
|
328
|
+
const content = describeViewForModel({
|
|
329
|
+
lifecycle,
|
|
330
|
+
toolName,
|
|
331
|
+
stepErrors: view.data?.context.errors,
|
|
332
|
+
stepsRetryable: Boolean(view.data?._refreshParams),
|
|
333
|
+
reloadedAt: view.reloadedAt,
|
|
334
|
+
reloadedBy: reloadedByOf(view.source),
|
|
335
|
+
})
|
|
336
|
+
return content ? <HostModelContext content={content} /> : null
|
|
337
|
+
}
|
|
338
|
+
|
|
224
339
|
function McpAppViewShell({
|
|
225
340
|
widgets,
|
|
226
341
|
refreshToolName = "refresh-view",
|
|
@@ -233,13 +348,21 @@ function McpAppViewShell({
|
|
|
233
348
|
slowHintAfterMs = DEFAULT_SLOW_HINT_MS,
|
|
234
349
|
describeError,
|
|
235
350
|
describeStep,
|
|
351
|
+
recoverStrippedResult,
|
|
352
|
+
strippedResultGraceMs,
|
|
353
|
+
toolNameInQueryKey,
|
|
236
354
|
}: McpAppViewProps) {
|
|
237
355
|
const messages = useToolkitMessages()
|
|
238
356
|
const L = useMemo(() => ({ ...messages.view, ...labels }), [messages.view, labels])
|
|
239
357
|
// The mcp-use 2.x view channels (all need the `bootstrapView` mount
|
|
240
358
|
// `mountMcpToolkitApp` provides); tools go through the HostBridge.
|
|
241
|
-
const
|
|
242
|
-
const { safeArea,
|
|
359
|
+
const call = useRenderCall()
|
|
360
|
+
const { safeArea, locale, timeZone } = useHostContext()
|
|
361
|
+
// A time in the language of the sentence around it, in the host's zone.
|
|
362
|
+
const formatLocale = resolveFormatLocale(useToolkitLocale(), locale)
|
|
363
|
+
// The client the view's widgets fetch through (an enclosing
|
|
364
|
+
// AppQueryProvider's, else the shared one): a refresh invalidates it.
|
|
365
|
+
const viewClient = useAppQueryClient()
|
|
243
366
|
const bridge = useHostBridge()
|
|
244
367
|
const callTool = useCallback(
|
|
245
368
|
(name: string, args: Record<string, unknown>) => bridge.callTool(name, args),
|
|
@@ -251,56 +374,46 @@ function McpAppViewShell({
|
|
|
251
374
|
(name: string, args: object) => bridge.callTool(name, args as Record<string, unknown>),
|
|
252
375
|
[bridge],
|
|
253
376
|
)
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
const isCallError = tool.status === "error" && !isCancelled
|
|
258
|
-
const initialViewData = tool.status === "ready" ? (tool.toolOutput as ViewData) : undefined
|
|
259
|
-
|
|
260
|
-
// Hosts that strip `structuredContent` (claude.ai) never latch a result under
|
|
261
|
-
// mcp-use 2.x; the recovery re-executes the call once to fill the gap. It
|
|
262
|
-
// never fires on a cancelled or failed call (#176): those render their state.
|
|
263
|
-
const recovery = useToolResultRecovery<ViewData>({
|
|
264
|
-
resultReady: !isPending,
|
|
265
|
-
cancelled: isCancelled,
|
|
266
|
-
errored: isCallError,
|
|
267
|
-
props: initialViewData,
|
|
268
|
-
isValid: isCompleteViewData,
|
|
269
|
-
toolInput: tool.toolInput,
|
|
270
|
-
hostContext,
|
|
271
|
-
callTool,
|
|
272
|
-
})
|
|
273
|
-
const [viewData, setViewData] = useHostViewData(initialViewData, isPending, recovery.data)
|
|
377
|
+
const shown = useShownView(call, callTool, recoverStrippedResult, strippedResultGraceMs)
|
|
378
|
+
const { view, setView } = shown
|
|
379
|
+
const viewData = view.data
|
|
274
380
|
const { lifecycle, retry: retryCall } = useViewLifecycle({
|
|
275
381
|
hasViewData: viewData !== null,
|
|
276
|
-
callStatus: tool.status,
|
|
277
|
-
cancelled: isCancelled,
|
|
278
|
-
callError:
|
|
279
|
-
recoveryFailed:
|
|
280
|
-
|
|
281
|
-
|
|
382
|
+
callStatus: call.tool.status,
|
|
383
|
+
cancelled: call.isCancelled,
|
|
384
|
+
callError: call.callError,
|
|
385
|
+
recoveryFailed: shown.recoveryFailed,
|
|
386
|
+
recoveryBlocked: shown.recoveryBlocked,
|
|
387
|
+
toolWrites: call.toolWrites,
|
|
388
|
+
toolName: call.toolName,
|
|
389
|
+
toolInput: call.tool.toolInput,
|
|
282
390
|
callTool,
|
|
283
391
|
isValid: isCompleteViewData,
|
|
284
|
-
onResult:
|
|
392
|
+
onResult: shown.showRetried,
|
|
285
393
|
slowHintAfterMs,
|
|
286
394
|
})
|
|
287
395
|
const { frameRef, retry } = useRetryFocus(lifecycle.phase, retryCall)
|
|
288
396
|
const { isRefreshing, refreshView } = useViewRefresh({
|
|
289
397
|
viewData,
|
|
290
|
-
setViewData,
|
|
398
|
+
setViewData: shown.showRefreshed,
|
|
291
399
|
callTool,
|
|
292
400
|
refreshToolName,
|
|
293
401
|
labels: L,
|
|
402
|
+
client: viewClient,
|
|
294
403
|
})
|
|
295
404
|
const fullscreen = useFullscreenToggle(L)
|
|
296
405
|
// In-iframe build mode is purely a UI state — the LLM never sees the catalogue.
|
|
297
406
|
const [buildMode, setBuildMode] = useState(false)
|
|
407
|
+
useBuildModeFocus(buildMode, frameRef)
|
|
298
408
|
const exitBuildMode = useCallback(
|
|
299
409
|
(commit?: BuilderCommit) => {
|
|
300
|
-
if (commit)
|
|
410
|
+
if (commit) setView((prev) => withBuilderCommit(prev, commit))
|
|
301
411
|
setBuildMode(false)
|
|
302
412
|
},
|
|
303
|
-
[
|
|
413
|
+
[setView],
|
|
414
|
+
)
|
|
415
|
+
const viewContext = (
|
|
416
|
+
<ViewModelContext lifecycle={lifecycle} toolName={call.toolName} view={view} />
|
|
304
417
|
)
|
|
305
418
|
|
|
306
419
|
// `isPending` must not gate the render: on stripping hosts the latch stays
|
|
@@ -309,6 +422,7 @@ function McpAppViewShell({
|
|
|
309
422
|
if (!viewData) {
|
|
310
423
|
return (
|
|
311
424
|
<div ref={frameRef} tabIndex={-1} data-slot="mcp-app-view" className="outline-hidden">
|
|
425
|
+
{viewContext}
|
|
312
426
|
<ViewPlaceholder
|
|
313
427
|
lifecycle={lifecycle}
|
|
314
428
|
labels={L}
|
|
@@ -321,6 +435,7 @@ function McpAppViewShell({
|
|
|
321
435
|
|
|
322
436
|
return (
|
|
323
437
|
<div ref={frameRef} tabIndex={-1} data-slot="mcp-app-view" className="outline-hidden">
|
|
438
|
+
{viewContext}
|
|
324
439
|
<main
|
|
325
440
|
style={{
|
|
326
441
|
// Fullscreen: the view owns the viewport and scrolls itself. Inline:
|
|
@@ -347,6 +462,7 @@ function McpAppViewShell({
|
|
|
347
462
|
onRefresh={refreshView}
|
|
348
463
|
onEnterBuildMode={() => setBuildMode(true)}
|
|
349
464
|
onToggleFullscreen={fullscreen.toggle}
|
|
465
|
+
dataAsOf={formatDataAsOf(view.reloadedAt, formatLocale, timeZone)}
|
|
350
466
|
/>
|
|
351
467
|
|
|
352
468
|
<StepErrors
|
|
@@ -358,7 +474,11 @@ function McpAppViewShell({
|
|
|
358
474
|
onRetry={viewData._refreshParams ? () => void refreshView() : undefined}
|
|
359
475
|
/>
|
|
360
476
|
|
|
361
|
-
<AppQueryProvider
|
|
477
|
+
<AppQueryProvider
|
|
478
|
+
callTool={callToolFn}
|
|
479
|
+
client={viewClient}
|
|
480
|
+
toolNameInQueryKey={toolNameInQueryKey}
|
|
481
|
+
>
|
|
362
482
|
<ViewBody
|
|
363
483
|
buildMode={buildMode}
|
|
364
484
|
viewData={viewData}
|
|
@@ -372,3 +492,33 @@ function McpAppViewShell({
|
|
|
372
492
|
</div>
|
|
373
493
|
)
|
|
374
494
|
}
|
|
495
|
+
|
|
496
|
+
/** How the model line names the view's own re-load of the shown payload. */
|
|
497
|
+
function reloadedByOf(source: ViewSource | null): "recovery" | "refresh" | "builder" {
|
|
498
|
+
if (source === "refresh" || source === "builder") return source
|
|
499
|
+
return "recovery"
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* The shown view with a builder commit applied (nothing to apply without
|
|
504
|
+
* data). A commit that carries the builder's live context shows figures the
|
|
505
|
+
* builder loaded itself (its catalogue call ran the steps again), so it is
|
|
506
|
+
* stamped like a refresh: the toolbar says when, and the model learns that
|
|
507
|
+
* they can differ from the result the chat answered from. A commit without a
|
|
508
|
+
* context keeps the shown figures, and with them their source and stamp.
|
|
509
|
+
*/
|
|
510
|
+
function withBuilderCommit(view: ShownView, commit: BuilderCommit): ShownView {
|
|
511
|
+
if (!view.data) return view
|
|
512
|
+
const data = applyBuilderCommit(view.data, commit)
|
|
513
|
+
if (!commit.context) return { ...view, data }
|
|
514
|
+
return { data, source: "builder", reloadedAt: reloadedAtOf("builder", Date.now(), false) }
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The toolbar's "as of" time of a re-loaded payload, `undefined` for any other. */
|
|
518
|
+
function formatDataAsOf(
|
|
519
|
+
reloadedAt: number | null,
|
|
520
|
+
locale: string,
|
|
521
|
+
timeZone: string | undefined,
|
|
522
|
+
): string | undefined {
|
|
523
|
+
return reloadedAt === null ? undefined : formatClockTime(reloadedAt, locale, timeZone)
|
|
524
|
+
}
|
|
@@ -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,5 +1,6 @@
|
|
|
1
1
|
import { useLayoutEffect, type ReactNode } from "react"
|
|
2
2
|
import { ThemeProvider, bootstrapView, useHostContext } from "mcp-use/react"
|
|
3
|
+
import { installToolResultObserver } from "../lib/tool-result-observer.js"
|
|
3
4
|
import { adoptHostFontVariables } from "../theme/host-fonts.js"
|
|
4
5
|
import { McpAppView, type McpAppViewProps } from "./mcp-app-view.js"
|
|
5
6
|
import { McpUseHostBridgeProvider } from "./mcp-use-host-bridge.js"
|
|
@@ -54,7 +55,12 @@ export function McpToolkitApp(props: McpAppViewProps) {
|
|
|
54
55
|
*
|
|
55
56
|
* `bootstrapView` owns the mount: it connects the ext-apps postMessage bridge
|
|
56
57
|
* to the host, installs an error boundary, and auto-reports size changes
|
|
57
|
-
* (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.
|
|
58
64
|
*
|
|
59
65
|
* ```tsx
|
|
60
66
|
* // main.tsx of the widget bundle
|
|
@@ -62,5 +68,6 @@ export function McpToolkitApp(props: McpAppViewProps) {
|
|
|
62
68
|
* ```
|
|
63
69
|
*/
|
|
64
70
|
export function mountMcpToolkitApp(props: McpAppViewProps): void {
|
|
71
|
+
installToolResultObserver()
|
|
65
72
|
bootstrapView({ default: () => <McpToolkitApp {...props} /> })
|
|
66
73
|
}
|