@miragon/mcp-toolkit-ui 0.10.1
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 +36 -0
- package/dist/app/adapt-data-widget.d.ts +33 -0
- package/dist/app/adapt-data-widget.d.ts.map +1 -0
- package/dist/app/adapt-data-widget.js +35 -0
- package/dist/app/adapt-data-widget.js.map +1 -0
- package/dist/app/builder/CatalogueSheet.d.ts +13 -0
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -0
- package/dist/app/builder/CatalogueSheet.js +33 -0
- package/dist/app/builder/CatalogueSheet.js.map +1 -0
- package/dist/app/builder/PipelineStrip.d.ts +22 -0
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -0
- package/dist/app/builder/PipelineStrip.js +31 -0
- package/dist/app/builder/PipelineStrip.js.map +1 -0
- package/dist/app/builder/PreviewPane.d.ts +13 -0
- package/dist/app/builder/PreviewPane.d.ts.map +1 -0
- package/dist/app/builder/PreviewPane.js +21 -0
- package/dist/app/builder/PreviewPane.js.map +1 -0
- package/dist/app/builder/SaveDialog.d.ts +14 -0
- package/dist/app/builder/SaveDialog.d.ts.map +1 -0
- package/dist/app/builder/SaveDialog.js +14 -0
- package/dist/app/builder/SaveDialog.js.map +1 -0
- package/dist/app/builder/Toolbar.d.ts +17 -0
- package/dist/app/builder/Toolbar.d.ts.map +1 -0
- package/dist/app/builder/Toolbar.js +15 -0
- package/dist/app/builder/Toolbar.js.map +1 -0
- package/dist/app/builder/Workspace.d.ts +28 -0
- package/dist/app/builder/Workspace.d.ts.map +1 -0
- package/dist/app/builder/Workspace.js +117 -0
- package/dist/app/builder/Workspace.js.map +1 -0
- package/dist/app/builder/builder-model.d.ts +86 -0
- package/dist/app/builder/builder-model.d.ts.map +1 -0
- package/dist/app/builder/builder-model.js +174 -0
- package/dist/app/builder/builder-model.js.map +1 -0
- package/dist/app/builder/builder-reducer.d.ts +71 -0
- package/dist/app/builder/builder-reducer.d.ts.map +1 -0
- package/dist/app/builder/builder-reducer.js +201 -0
- package/dist/app/builder/builder-reducer.js.map +1 -0
- package/dist/app/builder/labels.d.ts +78 -0
- package/dist/app/builder/labels.d.ts.map +1 -0
- package/dist/app/builder/labels.js +71 -0
- package/dist/app/builder/labels.js.map +1 -0
- package/dist/app/builder/status-chip.d.ts +10 -0
- package/dist/app/builder/status-chip.d.ts.map +1 -0
- package/dist/app/builder/status-chip.js +16 -0
- package/dist/app/builder/status-chip.js.map +1 -0
- package/dist/app/host-bridge.d.ts +211 -0
- package/dist/app/host-bridge.d.ts.map +1 -0
- package/dist/app/host-bridge.js +247 -0
- package/dist/app/host-bridge.js.map +1 -0
- package/dist/app/index.d.ts +16 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +18 -0
- package/dist/app/index.js.map +1 -0
- package/dist/app/layout-builder.d.ts +48 -0
- package/dist/app/layout-builder.d.ts.map +1 -0
- package/dist/app/layout-builder.js +418 -0
- package/dist/app/layout-builder.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +43 -0
- package/dist/app/mcp-app-view.d.ts.map +1 -0
- package/dist/app/mcp-app-view.js +169 -0
- package/dist/app/mcp-app-view.js.map +1 -0
- package/dist/app/mcp-toolkit-app.d.ts +18 -0
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -0
- package/dist/app/mcp-toolkit-app.js +22 -0
- package/dist/app/mcp-toolkit-app.js.map +1 -0
- package/dist/app/use-host-actions.d.ts +57 -0
- package/dist/app/use-host-actions.d.ts.map +1 -0
- package/dist/app/use-host-actions.js +42 -0
- package/dist/app/use-host-actions.js.map +1 -0
- package/dist/app/widget-fixture.d.ts +180 -0
- package/dist/app/widget-fixture.d.ts.map +1 -0
- package/dist/app/widget-fixture.js +273 -0
- package/dist/app/widget-fixture.js.map +1 -0
- package/dist/app/widget-props-sheet.d.ts +20 -0
- package/dist/app/widget-props-sheet.d.ts.map +1 -0
- package/dist/app/widget-props-sheet.js +87 -0
- package/dist/app/widget-props-sheet.js.map +1 -0
- package/dist/app/widget-renderer.d.ts +87 -0
- package/dist/app/widget-renderer.d.ts.map +1 -0
- package/dist/app/widget-renderer.js +105 -0
- package/dist/app/widget-renderer.js.map +1 -0
- package/dist/components/DrillButton.d.ts +22 -0
- package/dist/components/DrillButton.d.ts.map +1 -0
- package/dist/components/DrillButton.js +13 -0
- package/dist/components/DrillButton.js.map +1 -0
- package/dist/components/FilterBar.d.ts +28 -0
- package/dist/components/FilterBar.d.ts.map +1 -0
- package/dist/components/FilterBar.js +12 -0
- package/dist/components/FilterBar.js.map +1 -0
- package/dist/components/GridLayout.d.ts +26 -0
- package/dist/components/GridLayout.d.ts.map +1 -0
- package/dist/components/GridLayout.js +31 -0
- package/dist/components/GridLayout.js.map +1 -0
- package/dist/components/GroupCard.d.ts +21 -0
- package/dist/components/GroupCard.d.ts.map +1 -0
- package/dist/components/GroupCard.js +24 -0
- package/dist/components/GroupCard.js.map +1 -0
- package/dist/components/KpiGrid.d.ts +43 -0
- package/dist/components/KpiGrid.d.ts.map +1 -0
- package/dist/components/KpiGrid.js +35 -0
- package/dist/components/KpiGrid.js.map +1 -0
- package/dist/components/ListFooter.d.ts +22 -0
- package/dist/components/ListFooter.d.ts.map +1 -0
- package/dist/components/ListFooter.js +13 -0
- package/dist/components/ListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +25 -0
- package/dist/components/Pills.d.ts.map +1 -0
- package/dist/components/Pills.js +17 -0
- package/dist/components/Pills.js.map +1 -0
- package/dist/components/SectionHeading.d.ts +16 -0
- package/dist/components/SectionHeading.d.ts.map +1 -0
- package/dist/components/SectionHeading.js +10 -0
- package/dist/components/SectionHeading.js.map +1 -0
- package/dist/components/WidgetHeader.d.ts +21 -0
- package/dist/components/WidgetHeader.d.ts.map +1 -0
- package/dist/components/WidgetHeader.js +11 -0
- package/dist/components/WidgetHeader.js.map +1 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-debounced-value.d.ts +7 -0
- package/dist/hooks/use-debounced-value.d.ts.map +1 -0
- package/dist/hooks/use-debounced-value.js +15 -0
- package/dist/hooks/use-debounced-value.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +6 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +19 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +21 -0
- package/dist/hooks/use-tool-query.d.ts.map +1 -0
- package/dist/hooks/use-tool-query.js +47 -0
- package/dist/hooks/use-tool-query.js.map +1 -0
- package/dist/hooks/use-tool-result-recovery.d.ts +54 -0
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -0
- package/dist/hooks/use-tool-result-recovery.js +93 -0
- package/dist/hooks/use-tool-result-recovery.js.map +1 -0
- package/dist/hooks/use-view-data.d.ts +19 -0
- package/dist/hooks/use-view-data.d.ts.map +1 -0
- package/dist/hooks/use-view-data.js +18 -0
- package/dist/hooks/use-view-data.js.map +1 -0
- package/dist/hooks/use-view-tool-query.d.ts +18 -0
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -0
- package/dist/hooks/use-view-tool-query.js +29 -0
- package/dist/hooks/use-view-tool-query.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +39 -0
- package/dist/lib/parse-tool-result.d.ts.map +1 -0
- package/dist/lib/parse-tool-result.js +66 -0
- package/dist/lib/parse-tool-result.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +18 -0
- package/dist/lib/tone-utils.d.ts.map +1 -0
- package/dist/lib/tone-utils.js +25 -0
- package/dist/lib/tone-utils.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.d.ts.map +1 -0
- package/dist/locale/index.js +7 -0
- package/dist/locale/index.js.map +1 -0
- package/dist/locale/locale-provider.d.ts +50 -0
- package/dist/locale/locale-provider.d.ts.map +1 -0
- package/dist/locale/locale-provider.js +47 -0
- package/dist/locale/locale-provider.js.map +1 -0
- package/dist/primitives/alert.d.ts +10 -0
- package/dist/primitives/alert.d.ts.map +1 -0
- package/dist/primitives/alert.js +25 -0
- package/dist/primitives/alert.js.map +1 -0
- package/dist/primitives/badge.d.ts +8 -0
- package/dist/primitives/badge.d.ts.map +1 -0
- package/dist/primitives/badge.js +34 -0
- package/dist/primitives/badge.js.map +1 -0
- package/dist/primitives/button.d.ts +11 -0
- package/dist/primitives/button.d.ts.map +1 -0
- package/dist/primitives/button.js +36 -0
- package/dist/primitives/button.js.map +1 -0
- package/dist/primitives/card.d.ts +12 -0
- package/dist/primitives/card.d.ts.map +1 -0
- package/dist/primitives/card.js +25 -0
- package/dist/primitives/card.js.map +1 -0
- package/dist/primitives/dialog.d.ts +18 -0
- package/dist/primitives/dialog.d.ts.map +1 -0
- package/dist/primitives/dialog.js +38 -0
- package/dist/primitives/dialog.js.map +1 -0
- package/dist/primitives/input.d.ts +4 -0
- package/dist/primitives/input.d.ts.map +1 -0
- package/dist/primitives/input.js +7 -0
- package/dist/primitives/input.js.map +1 -0
- package/dist/primitives/scroll-area.d.ts +6 -0
- package/dist/primitives/scroll-area.d.ts.map +1 -0
- package/dist/primitives/scroll-area.js +11 -0
- package/dist/primitives/scroll-area.js.map +1 -0
- package/dist/primitives/select.d.ts +16 -0
- package/dist/primitives/select.d.ts.map +1 -0
- package/dist/primitives/select.js +38 -0
- package/dist/primitives/select.js.map +1 -0
- package/dist/primitives/separator.d.ts +4 -0
- package/dist/primitives/separator.d.ts.map +1 -0
- package/dist/primitives/separator.js +9 -0
- package/dist/primitives/separator.js.map +1 -0
- package/dist/primitives/sheet.d.ts +15 -0
- package/dist/primitives/sheet.d.ts.map +1 -0
- package/dist/primitives/sheet.js +41 -0
- package/dist/primitives/sheet.js.map +1 -0
- package/dist/primitives/skeleton.d.ts +3 -0
- package/dist/primitives/skeleton.d.ts.map +1 -0
- package/dist/primitives/skeleton.js +7 -0
- package/dist/primitives/skeleton.js.map +1 -0
- package/dist/primitives/switch.d.ts +5 -0
- package/dist/primitives/switch.d.ts.map +1 -0
- package/dist/primitives/switch.js +8 -0
- package/dist/primitives/switch.js.map +1 -0
- package/dist/primitives/table.d.ts +11 -0
- package/dist/primitives/table.d.ts.map +1 -0
- package/dist/primitives/table.js +28 -0
- package/dist/primitives/table.js.map +1 -0
- package/dist/primitives/tabs.d.ts +12 -0
- package/dist/primitives/tabs.d.ts.map +1 -0
- package/dist/primitives/tabs.js +29 -0
- package/dist/primitives/tabs.js.map +1 -0
- package/dist/providers/query-provider.d.ts +11 -0
- package/dist/providers/query-provider.d.ts.map +1 -0
- package/dist/providers/query-provider.js +20 -0
- package/dist/providers/query-provider.js.map +1 -0
- package/dist/theme/create-theme.d.ts +130 -0
- package/dist/theme/create-theme.d.ts.map +1 -0
- package/dist/theme/create-theme.js +65 -0
- package/dist/theme/create-theme.js.map +1 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +7 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/presets.d.ts +25 -0
- package/dist/theme/presets.d.ts.map +1 -0
- package/dist/theme/presets.js +68 -0
- package/dist/theme/presets.js.map +1 -0
- package/dist/theme/theme-provider.d.ts +69 -0
- package/dist/theme/theme-provider.d.ts.map +1 -0
- package/dist/theme/theme-provider.js +79 -0
- package/dist/theme/theme-provider.js.map +1 -0
- package/package.json +71 -0
- package/src/app/adapt-data-widget.tsx +51 -0
- package/src/app/builder/CatalogueSheet.tsx +230 -0
- package/src/app/builder/PipelineStrip.tsx +260 -0
- package/src/app/builder/PreviewPane.tsx +72 -0
- package/src/app/builder/SaveDialog.tsx +95 -0
- package/src/app/builder/Toolbar.tsx +95 -0
- package/src/app/builder/Workspace.tsx +483 -0
- package/src/app/builder/builder-model.test.ts +351 -0
- package/src/app/builder/builder-model.ts +213 -0
- package/src/app/builder/builder-reducer.test.ts +441 -0
- package/src/app/builder/builder-reducer.ts +289 -0
- package/src/app/builder/labels.ts +139 -0
- package/src/app/builder/status-chip.tsx +37 -0
- package/src/app/host-bridge.test.ts +206 -0
- package/src/app/host-bridge.tsx +371 -0
- package/src/app/index.ts +51 -0
- package/src/app/layout-builder.tsx +872 -0
- package/src/app/mcp-app-view.tsx +367 -0
- package/src/app/mcp-toolkit-app.tsx +25 -0
- package/src/app/use-host-actions.test.ts +16 -0
- package/src/app/use-host-actions.tsx +88 -0
- package/src/app/widget-fixture.test.ts +108 -0
- package/src/app/widget-fixture.tsx +441 -0
- package/src/app/widget-props-sheet.tsx +285 -0
- package/src/app/widget-renderer.test.ts +42 -0
- package/src/app/widget-renderer.tsx +200 -0
- package/src/components/DrillButton.tsx +37 -0
- package/src/components/FilterBar.tsx +68 -0
- package/src/components/GridLayout.tsx +49 -0
- package/src/components/GroupCard.tsx +52 -0
- package/src/components/KpiGrid.tsx +134 -0
- package/src/components/ListFooter.tsx +48 -0
- package/src/components/Pills.tsx +45 -0
- package/src/components/SectionHeading.tsx +26 -0
- package/src/components/WidgetHeader.tsx +51 -0
- package/src/globals.css +168 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/use-debounced-value.ts +15 -0
- package/src/hooks/use-mobile.ts +24 -0
- package/src/hooks/use-tool-query.ts +63 -0
- package/src/hooks/use-tool-result-recovery.test.ts +65 -0
- package/src/hooks/use-tool-result-recovery.ts +144 -0
- package/src/hooks/use-view-data.ts +31 -0
- package/src/hooks/use-view-tool-query.ts +38 -0
- package/src/index.ts +134 -0
- package/src/lib/parse-tool-result.test.ts +139 -0
- package/src/lib/parse-tool-result.ts +89 -0
- package/src/lib/tone-utils.test.ts +35 -0
- package/src/lib/tone-utils.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/locale/index.ts +7 -0
- package/src/locale/locale-provider.tsx +81 -0
- package/src/primitives/alert.tsx +60 -0
- package/src/primitives/badge.tsx +49 -0
- package/src/primitives/button.tsx +62 -0
- package/src/primitives/card.tsx +92 -0
- package/src/primitives/dialog.tsx +144 -0
- package/src/primitives/input.tsx +21 -0
- package/src/primitives/scroll-area.tsx +54 -0
- package/src/primitives/select.tsx +173 -0
- package/src/primitives/separator.tsx +21 -0
- package/src/primitives/sheet.tsx +134 -0
- package/src/primitives/skeleton.tsx +13 -0
- package/src/primitives/switch.tsx +24 -0
- package/src/primitives/table.tsx +87 -0
- package/src/primitives/tabs.tsx +79 -0
- package/src/providers/query-provider.tsx +38 -0
- package/src/theme/create-theme.test.ts +108 -0
- package/src/theme/create-theme.ts +158 -0
- package/src/theme/index.ts +9 -0
- package/src/theme/presets.ts +82 -0
- package/src/theme/theme-provider.tsx +152 -0
- package/src/ui-catalog.test.ts +112 -0
- package/ui-catalog.json +1280 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from "react"
|
|
2
|
+
import { useWidget } from "mcp-use/react"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The host capabilities a hand-built widget needs, abstracted away from any one
|
|
6
|
+
* host runtime. A `HostBridge` is the single seam a portable widget talks to:
|
|
7
|
+
* write the widget against {@link useHostBridge} and it runs unchanged in our
|
|
8
|
+
* own `mcp-use` host, inside ChatGPT (OpenAI Apps SDK), or as a standalone web
|
|
9
|
+
* app driven directly by a `@modelcontextprotocol/sdk` client — only the
|
|
10
|
+
* surrounding adapter changes.
|
|
11
|
+
*
|
|
12
|
+
* This is deliberately the *minimum* a deterministic UI needs, not a mirror of
|
|
13
|
+
* any host's full surface. Each adapter maps these verbs onto its host and
|
|
14
|
+
* fails soft where the host cannot honour them (see the per-factory docs).
|
|
15
|
+
*/
|
|
16
|
+
export interface HostBridge {
|
|
17
|
+
/**
|
|
18
|
+
* Invoke an MCP tool on the connected server and resolve with its raw
|
|
19
|
+
* response (typically `{ structuredContent, content, … }`). Callers decode it
|
|
20
|
+
* with `parseToolResult`. Rejects if the call fails so a widget's own error
|
|
21
|
+
* state (TanStack Query, try/catch) can surface it.
|
|
22
|
+
*/
|
|
23
|
+
callTool(name: string, args: Record<string, unknown>): Promise<unknown>
|
|
24
|
+
/**
|
|
25
|
+
* Hand a natural-language prompt back to the conversation to drive a new model
|
|
26
|
+
* turn (navigate to another widget, ask the agent to analyse something). In
|
|
27
|
+
* hosts without a conversation (standalone web app) this is a no-op unless an
|
|
28
|
+
* `onFollowup` handler is supplied.
|
|
29
|
+
*/
|
|
30
|
+
sendFollowup(prompt: string): void
|
|
31
|
+
/**
|
|
32
|
+
* Ask the host to open a URL outside the (often sandboxed) widget frame.
|
|
33
|
+
* Falls back to `window.open` only where no host bridge mediates links.
|
|
34
|
+
*/
|
|
35
|
+
openExternal(url: string): void
|
|
36
|
+
/**
|
|
37
|
+
* The structured data this widget was rendered with, if the host pushed any
|
|
38
|
+
* (the tool result's `structuredContent` / `toolOutput`). Returns `null` when
|
|
39
|
+
* the host delivers data another way (e.g. props) or has none.
|
|
40
|
+
*/
|
|
41
|
+
getWidgetData<T>(): T | null
|
|
42
|
+
/**
|
|
43
|
+
* Persist a short text the model should see on the next turn (selection,
|
|
44
|
+
* filter, current view). Optional: not every host exposes model context, and
|
|
45
|
+
* a standalone web app has no model at all — callers must guard its absence.
|
|
46
|
+
*/
|
|
47
|
+
setModelContext?(text: string): void
|
|
48
|
+
/** The host's colour-scheme preference, when it reports one. */
|
|
49
|
+
theme?: "light" | "dark"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const HostBridgeContext = createContext<HostBridge | null>(null)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Provide an explicit {@link HostBridge} to a subtree. Wrap a portable widget in
|
|
56
|
+
* the adapter for the host it is running in — {@link createChatGptHostBridge},
|
|
57
|
+
* {@link createStandaloneHostBridge}, or {@link createMcpUseHostBridge} — and
|
|
58
|
+
* every `useHostBridge()` below resolves to it.
|
|
59
|
+
*
|
|
60
|
+
* Omitting the provider is supported: {@link useHostBridge} (and, for backward
|
|
61
|
+
* compatibility, `useHostActions`) then fall back to the `mcp-use`/react default
|
|
62
|
+
* bridge so existing widgets keep working without any wrapping.
|
|
63
|
+
*/
|
|
64
|
+
export function HostBridgeProvider({
|
|
65
|
+
bridge,
|
|
66
|
+
children,
|
|
67
|
+
}: {
|
|
68
|
+
bridge: HostBridge
|
|
69
|
+
children: ReactNode
|
|
70
|
+
}): ReactNode {
|
|
71
|
+
return <HostBridgeContext.Provider value={bridge}>{children}</HostBridgeContext.Provider>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Read the explicit {@link HostBridge} from context, or `null` if no
|
|
76
|
+
* {@link HostBridgeProvider} is present. Adapters that want to *fall back* to the
|
|
77
|
+
* default `mcp-use` bridge (rather than throw) use this instead of
|
|
78
|
+
* {@link useHostBridge}.
|
|
79
|
+
*/
|
|
80
|
+
export function useHostBridgeOrNull(): HostBridge | null {
|
|
81
|
+
return useContext(HostBridgeContext)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The {@link HostBridge} for the current subtree. Resolution order:
|
|
86
|
+
*
|
|
87
|
+
* 1. An explicit bridge from the nearest {@link HostBridgeProvider}.
|
|
88
|
+
* 2. The default `mcp-use`/react bridge ({@link createMcpUseHostBridge}), so a
|
|
89
|
+
* widget mounted in our own host needs no provider.
|
|
90
|
+
*
|
|
91
|
+
* The `mcp-use` hooks the default bridge wraps must run under an `mcp-use`
|
|
92
|
+
* provider/host context; outside one (e.g. a bare unit test render) wrap the
|
|
93
|
+
* widget in a `HostBridgeProvider` with a {@link createStandaloneHostBridge}
|
|
94
|
+
* instead. This hook never returns `null`: a portable widget can always assume a
|
|
95
|
+
* bridge exists.
|
|
96
|
+
*/
|
|
97
|
+
export function useHostBridge(): HostBridge {
|
|
98
|
+
const explicit = useContext(HostBridgeContext)
|
|
99
|
+
// Always call the default-bridge hook (Rules of Hooks): it's cheap and only
|
|
100
|
+
// *used* when no explicit bridge is provided. The mcp-use hooks underneath are
|
|
101
|
+
// host-context readers, safe to call even when their result goes unused.
|
|
102
|
+
const fallback = useMcpUseHostBridge()
|
|
103
|
+
return explicit ?? fallback
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build a {@link HostBridge} backed by `mcp-use`/react — the default for widgets
|
|
108
|
+
* running in the toolkit's own host. Wraps `useWidget()` (host actions,
|
|
109
|
+
* `toolOutput`, `theme`) and `useCallTool` so it preserves exactly the behaviour
|
|
110
|
+
* widgets had before the bridge abstraction:
|
|
111
|
+
*
|
|
112
|
+
* - `openExternal` routes through the host's `openExternal` and falls back to
|
|
113
|
+
* `window.open` when no host bridge is wired (local dev preview) — mirroring
|
|
114
|
+
* the original `useHostActions.openLink`.
|
|
115
|
+
* - `sendFollowup` routes through `sendFollowUpMessage` and swallows rejections
|
|
116
|
+
* with a `console.warn`, as `useHostActions` did.
|
|
117
|
+
*
|
|
118
|
+
* Must be called inside an `mcp-use` host context (it calls `mcp-use` hooks).
|
|
119
|
+
* Use {@link createMcpUseHostBridge} for the standalone factory variant.
|
|
120
|
+
*/
|
|
121
|
+
export function useMcpUseHostBridge(): HostBridge {
|
|
122
|
+
const widget = useWidget()
|
|
123
|
+
const { callTool, sendFollowUpMessage, openExternal, output, theme, setState } = widget
|
|
124
|
+
|
|
125
|
+
return useMemo<HostBridge>(
|
|
126
|
+
() => ({
|
|
127
|
+
callTool: (name, args) => callTool(name, args),
|
|
128
|
+
sendFollowup: (prompt) => {
|
|
129
|
+
void sendFollowUpMessage(prompt).catch((err: unknown) => {
|
|
130
|
+
console.warn("[host-bridge] sendFollowUpMessage failed:", err)
|
|
131
|
+
})
|
|
132
|
+
},
|
|
133
|
+
openExternal: (url) => {
|
|
134
|
+
try {
|
|
135
|
+
openExternal(url)
|
|
136
|
+
} catch {
|
|
137
|
+
// No host bridge (local dev preview) — fall back to window.open.
|
|
138
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener")
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
getWidgetData: <T,>() => (output as T | null) ?? null,
|
|
142
|
+
setModelContext: (text) => {
|
|
143
|
+
void setState({ __model_context: text }).catch((err: unknown) => {
|
|
144
|
+
console.warn("[host-bridge] setState (model context) failed:", err)
|
|
145
|
+
})
|
|
146
|
+
},
|
|
147
|
+
theme: theme === "dark" ? "dark" : "light",
|
|
148
|
+
}),
|
|
149
|
+
[callTool, sendFollowUpMessage, openExternal, output, theme, setState],
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Factory form of {@link useMcpUseHostBridge} for symmetry with the other
|
|
155
|
+
* adapters. Because the `mcp-use` bridge is hook-driven (it reads live host
|
|
156
|
+
* context), the "factory" is really the hook itself — call it from a component.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* function MyWidget() {
|
|
160
|
+
* const bridge = createMcpUseHostBridge() // === useMcpUseHostBridge()
|
|
161
|
+
* // …or rely on useHostBridge()'s default and skip the provider entirely.
|
|
162
|
+
* }
|
|
163
|
+
*/
|
|
164
|
+
export const createMcpUseHostBridge = useMcpUseHostBridge
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Minimal structural view of the OpenAI Apps SDK `window.openai` object this
|
|
168
|
+
* adapter reads. Every field is optional: the real SDK shape varies across
|
|
169
|
+
* versions and host builds, so the adapter probes each method/field defensively
|
|
170
|
+
* and degrades to a no-op (with a `console.warn`) rather than assuming presence.
|
|
171
|
+
*
|
|
172
|
+
* Assumptions documented here (verify against the Apps SDK you target):
|
|
173
|
+
* - `callTool(name, args)` resolves with the tool response. Required for tool
|
|
174
|
+
* calls; without it `callTool` rejects with a clear error.
|
|
175
|
+
* - `sendFollowUpMessage` is called with `{ prompt }` (older builds) — we also
|
|
176
|
+
* accept a bare-string signature and try both shapes.
|
|
177
|
+
* - `openExternal` is called with `{ href }`.
|
|
178
|
+
* - `toolOutput` (preferred) or `toolResponseMetadata` carries the structured
|
|
179
|
+
* widget data; `setWidgetState(state)` persists model-visible state.
|
|
180
|
+
* - `theme` is `"light"` | `"dark"`.
|
|
181
|
+
*/
|
|
182
|
+
export interface OpenAiAppsSdk {
|
|
183
|
+
callTool?: (name: string, args: Record<string, unknown>) => Promise<unknown>
|
|
184
|
+
sendFollowUpMessage?: (arg: { prompt: string } | string) => unknown
|
|
185
|
+
sendFollowupMessage?: (arg: { prompt: string } | string) => unknown
|
|
186
|
+
openExternal?: (payload: { href: string } | string) => unknown
|
|
187
|
+
toolOutput?: unknown
|
|
188
|
+
toolResponseMetadata?: unknown
|
|
189
|
+
setWidgetState?: (state: Record<string, unknown>) => unknown
|
|
190
|
+
theme?: unknown
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resolve the `window.openai` object, or `null` outside a browser / when the SDK
|
|
195
|
+
* is absent. Pulled out so the no-SDK fallback path is unit-testable by passing
|
|
196
|
+
* an explicit object (or `null`).
|
|
197
|
+
*/
|
|
198
|
+
function resolveOpenAi(explicit?: OpenAiAppsSdk | null): OpenAiAppsSdk | null {
|
|
199
|
+
if (explicit !== undefined) return explicit
|
|
200
|
+
if (typeof window === "undefined") return null
|
|
201
|
+
const candidate: unknown = (window as { openai?: unknown }).openai
|
|
202
|
+
return candidate && typeof candidate === "object" ? candidate : null
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Build a {@link HostBridge} for the OpenAI Apps SDK (ChatGPT). Reads the host's
|
|
207
|
+
* `window.openai` object and maps the bridge verbs onto it **defensively** —
|
|
208
|
+
* every method is probed before use, and a missing one degrades to a logged
|
|
209
|
+
* no-op (or, for `callTool`, a rejected promise) instead of crashing the widget.
|
|
210
|
+
* This is what lets the *same* hand-built widget run in ChatGPT with no code
|
|
211
|
+
* change, even across Apps SDK versions whose surface drifts.
|
|
212
|
+
*
|
|
213
|
+
* Pass an explicit `sdk` to test the guards without a real `window.openai`
|
|
214
|
+
* (passing `null` exercises the fully-absent path). See {@link OpenAiAppsSdk}
|
|
215
|
+
* for the documented assumptions about the SDK shape.
|
|
216
|
+
*/
|
|
217
|
+
export function createChatGptHostBridge(sdk?: OpenAiAppsSdk | null): HostBridge {
|
|
218
|
+
const openai = resolveOpenAi(sdk)
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
callTool: (name, args) => {
|
|
222
|
+
if (typeof openai?.callTool !== "function") {
|
|
223
|
+
console.warn("[host-bridge] window.openai.callTool unavailable — cannot call", name)
|
|
224
|
+
return Promise.reject(
|
|
225
|
+
new Error(
|
|
226
|
+
`[host-bridge] OpenAI Apps SDK callTool is unavailable; cannot invoke "${name}".`,
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
return Promise.resolve(openai.callTool(name, args))
|
|
231
|
+
},
|
|
232
|
+
sendFollowup: (prompt) => {
|
|
233
|
+
const fn = openai?.sendFollowUpMessage ?? openai?.sendFollowupMessage
|
|
234
|
+
if (typeof fn !== "function") {
|
|
235
|
+
console.warn("[host-bridge] window.openai.sendFollowUpMessage unavailable — no-op")
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
// Newer builds take `{ prompt }`; try that first, then a bare string.
|
|
240
|
+
const result: unknown = fn({ prompt })
|
|
241
|
+
if (result instanceof Promise) {
|
|
242
|
+
result.catch((err: unknown) => {
|
|
243
|
+
console.warn("[host-bridge] sendFollowUpMessage rejected:", err)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
try {
|
|
248
|
+
fn(prompt)
|
|
249
|
+
} catch (err) {
|
|
250
|
+
console.warn("[host-bridge] sendFollowUpMessage failed:", err)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
openExternal: (url) => {
|
|
255
|
+
if (typeof openai?.openExternal === "function") {
|
|
256
|
+
try {
|
|
257
|
+
openai.openExternal({ href: url })
|
|
258
|
+
return
|
|
259
|
+
} catch (err) {
|
|
260
|
+
console.warn("[host-bridge] window.openai.openExternal failed:", err)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener")
|
|
264
|
+
},
|
|
265
|
+
getWidgetData: <T,>() => {
|
|
266
|
+
const data = openai?.toolOutput ?? openai?.toolResponseMetadata
|
|
267
|
+
return (data as T | null) ?? null
|
|
268
|
+
},
|
|
269
|
+
setModelContext: (text) => {
|
|
270
|
+
if (typeof openai?.setWidgetState !== "function") {
|
|
271
|
+
console.warn("[host-bridge] window.openai.setWidgetState unavailable — model context no-op")
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
openai.setWidgetState({ __model_context: text })
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.warn("[host-bridge] setWidgetState failed:", err)
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
theme: openai?.theme === "dark" ? "dark" : openai?.theme === "light" ? "light" : undefined,
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Options for {@link createStandaloneHostBridge}. */
|
|
285
|
+
export interface StandaloneHostBridgeOptions {
|
|
286
|
+
/**
|
|
287
|
+
* The only required capability: how to call a tool on the server. In a
|
|
288
|
+
* standalone web app this typically wraps a `@modelcontextprotocol/sdk`
|
|
289
|
+
* `Client.callTool({ name, arguments })`, but any function with this shape
|
|
290
|
+
* works — that injection is the whole point: hand-built widgets render
|
|
291
|
+
* against *any* existing MCP server without the toolkit's host runtime.
|
|
292
|
+
*/
|
|
293
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>
|
|
294
|
+
/**
|
|
295
|
+
* Supply the structured data a widget reads via `getWidgetData()`. A static
|
|
296
|
+
* value, or a getter `() => value` that is re-read on each call (so the data
|
|
297
|
+
* can change without rebuilding the bridge). Any function is treated as a
|
|
298
|
+
* getter — widget data is never itself a function. Optional: many standalone
|
|
299
|
+
* widgets fetch everything through `callTool` instead.
|
|
300
|
+
*/
|
|
301
|
+
getData?: unknown
|
|
302
|
+
/**
|
|
303
|
+
* Handle a `sendFollowup` prompt. A standalone app has no conversation, so
|
|
304
|
+
* this is where you wire "ask the model" to your own chat UI, a toast, or a
|
|
305
|
+
* logger. Omit to make `sendFollowup` a logged no-op.
|
|
306
|
+
*/
|
|
307
|
+
onFollowup?: (prompt: string) => void
|
|
308
|
+
/**
|
|
309
|
+
* Handle `openExternal`. Defaults to `window.open(url, "_blank")` when omitted
|
|
310
|
+
* (a standalone app is not sandboxed, so that is safe).
|
|
311
|
+
*/
|
|
312
|
+
onOpenExternal?: (url: string) => void
|
|
313
|
+
/** Handle `setModelContext`. Omit to make it a logged no-op. */
|
|
314
|
+
onModelContext?: (text: string) => void
|
|
315
|
+
/** Static theme to report to widgets. */
|
|
316
|
+
theme?: "light" | "dark"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Build a {@link HostBridge} for a standalone web app driven against an existing
|
|
321
|
+
* MCP server — no toolkit host, no `createFrameworkApp`. You inject `callTool`
|
|
322
|
+
* (usually a thin wrapper over a `@modelcontextprotocol/sdk` client); the
|
|
323
|
+
* conversation-shaped verbs (`sendFollowup`, `setModelContext`) are no-ops
|
|
324
|
+
* unless you wire handlers, and `openExternal` defaults to `window.open`.
|
|
325
|
+
*
|
|
326
|
+
* This is the decoupling layer that turns the toolkit's hand-built widgets into
|
|
327
|
+
* a reusable UI kit for *any* MCP server: render `<HostBridgeProvider bridge=…>`
|
|
328
|
+
* once at the app root and every portable widget below works. The widget fixture
|
|
329
|
+
* harness also builds on this factory so host simulation has a single source.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* const client = new Client(…) // @modelcontextprotocol/sdk
|
|
333
|
+
* const bridge = createStandaloneHostBridge({
|
|
334
|
+
* callTool: (name, args) => client.callTool({ name, arguments: args }),
|
|
335
|
+
* getData: () => currentRecord,
|
|
336
|
+
* })
|
|
337
|
+
*/
|
|
338
|
+
export function createStandaloneHostBridge(options: StandaloneHostBridgeOptions): HostBridge {
|
|
339
|
+
const { callTool, getData, onFollowup, onOpenExternal, onModelContext, theme } = options
|
|
340
|
+
return {
|
|
341
|
+
callTool: (name, args) => callTool(name, args),
|
|
342
|
+
sendFollowup: (prompt) => {
|
|
343
|
+
if (onFollowup) {
|
|
344
|
+
onFollowup(prompt)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
console.warn("[host-bridge] standalone bridge has no onFollowup handler — prompt dropped")
|
|
348
|
+
},
|
|
349
|
+
openExternal: (url) => {
|
|
350
|
+
if (onOpenExternal) {
|
|
351
|
+
onOpenExternal(url)
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
if (typeof window !== "undefined") window.open(url, "_blank", "noopener")
|
|
355
|
+
},
|
|
356
|
+
getWidgetData: <T,>() => {
|
|
357
|
+
const data = typeof getData === "function" ? (getData as () => unknown)() : getData
|
|
358
|
+
return (data as T | null) ?? null
|
|
359
|
+
},
|
|
360
|
+
setModelContext: (text) => {
|
|
361
|
+
if (onModelContext) {
|
|
362
|
+
onModelContext(text)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
console.warn(
|
|
366
|
+
"[host-bridge] standalone bridge has no onModelContext handler — context dropped",
|
|
367
|
+
)
|
|
368
|
+
},
|
|
369
|
+
theme,
|
|
370
|
+
}
|
|
371
|
+
}
|
package/src/app/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export { McpAppView } from "./mcp-app-view.js"
|
|
2
|
+
export type { McpAppViewProps, McpAppViewLabels } from "./mcp-app-view.js"
|
|
3
|
+
export { McpToolkitApp } from "./mcp-toolkit-app.js"
|
|
4
|
+
export {
|
|
5
|
+
WidgetRenderer,
|
|
6
|
+
WidgetErrorBoundary,
|
|
7
|
+
RenderedWidget,
|
|
8
|
+
contextHasNoData,
|
|
9
|
+
} from "./widget-renderer.js"
|
|
10
|
+
export type { WidgetRendererProps, WidgetComponent } from "./widget-renderer.js"
|
|
11
|
+
export { LayoutBuilder } from "./layout-builder.js"
|
|
12
|
+
export type { LayoutBuilderProps, LayoutBuilderLabels } from "./layout-builder.js"
|
|
13
|
+
|
|
14
|
+
// App-level value modules that import `mcp-use/react` (ModelContext, useWidget)
|
|
15
|
+
// — exported from this subpath only, never the root barrel.
|
|
16
|
+
export { adaptDataWidget } from "./adapt-data-widget.js"
|
|
17
|
+
export type { DescribeForModel } from "./adapt-data-widget.js"
|
|
18
|
+
export { useHostActions, buildShowWidgetIntent } from "./use-host-actions.js"
|
|
19
|
+
export type { HostActions } from "./use-host-actions.js"
|
|
20
|
+
|
|
21
|
+
// Host-portability bridge: the seam that lets the same hand-built widget run in
|
|
22
|
+
// our mcp-use host, ChatGPT (Apps SDK), or a standalone web app against an
|
|
23
|
+
// existing MCP server. Lives under ./app because the default adapter imports
|
|
24
|
+
// `mcp-use/react` — never the root barrel.
|
|
25
|
+
export {
|
|
26
|
+
HostBridgeProvider,
|
|
27
|
+
useHostBridge,
|
|
28
|
+
useHostBridgeOrNull,
|
|
29
|
+
useMcpUseHostBridge,
|
|
30
|
+
createMcpUseHostBridge,
|
|
31
|
+
createChatGptHostBridge,
|
|
32
|
+
createStandaloneHostBridge,
|
|
33
|
+
} from "./host-bridge.js"
|
|
34
|
+
export type { HostBridge, OpenAiAppsSdk, StandaloneHostBridgeOptions } from "./host-bridge.js"
|
|
35
|
+
|
|
36
|
+
// Storybook-style harness for developing widgets in isolation with fixture
|
|
37
|
+
// data and a mocked host. Lives under ./app because it mocks the
|
|
38
|
+
// `mcp-use/react` host bridge (window.openai) — never the root barrel.
|
|
39
|
+
export {
|
|
40
|
+
WidgetFixtureHost,
|
|
41
|
+
FixtureCallToolRegistry,
|
|
42
|
+
buildFixtureWidgetProps,
|
|
43
|
+
useFixtureHost,
|
|
44
|
+
} from "./widget-fixture.js"
|
|
45
|
+
export type {
|
|
46
|
+
WidgetFixtureHostProps,
|
|
47
|
+
FixtureWidget,
|
|
48
|
+
FixtureToolEntry,
|
|
49
|
+
FixtureToolResult,
|
|
50
|
+
HostActionLog,
|
|
51
|
+
} from "./widget-fixture.js"
|