@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.
Files changed (320) hide show
  1. package/README.md +36 -0
  2. package/dist/app/adapt-data-widget.d.ts +33 -0
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -0
  4. package/dist/app/adapt-data-widget.js +35 -0
  5. package/dist/app/adapt-data-widget.js.map +1 -0
  6. package/dist/app/builder/CatalogueSheet.d.ts +13 -0
  7. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -0
  8. package/dist/app/builder/CatalogueSheet.js +33 -0
  9. package/dist/app/builder/CatalogueSheet.js.map +1 -0
  10. package/dist/app/builder/PipelineStrip.d.ts +22 -0
  11. package/dist/app/builder/PipelineStrip.d.ts.map +1 -0
  12. package/dist/app/builder/PipelineStrip.js +31 -0
  13. package/dist/app/builder/PipelineStrip.js.map +1 -0
  14. package/dist/app/builder/PreviewPane.d.ts +13 -0
  15. package/dist/app/builder/PreviewPane.d.ts.map +1 -0
  16. package/dist/app/builder/PreviewPane.js +21 -0
  17. package/dist/app/builder/PreviewPane.js.map +1 -0
  18. package/dist/app/builder/SaveDialog.d.ts +14 -0
  19. package/dist/app/builder/SaveDialog.d.ts.map +1 -0
  20. package/dist/app/builder/SaveDialog.js +14 -0
  21. package/dist/app/builder/SaveDialog.js.map +1 -0
  22. package/dist/app/builder/Toolbar.d.ts +17 -0
  23. package/dist/app/builder/Toolbar.d.ts.map +1 -0
  24. package/dist/app/builder/Toolbar.js +15 -0
  25. package/dist/app/builder/Toolbar.js.map +1 -0
  26. package/dist/app/builder/Workspace.d.ts +28 -0
  27. package/dist/app/builder/Workspace.d.ts.map +1 -0
  28. package/dist/app/builder/Workspace.js +117 -0
  29. package/dist/app/builder/Workspace.js.map +1 -0
  30. package/dist/app/builder/builder-model.d.ts +86 -0
  31. package/dist/app/builder/builder-model.d.ts.map +1 -0
  32. package/dist/app/builder/builder-model.js +174 -0
  33. package/dist/app/builder/builder-model.js.map +1 -0
  34. package/dist/app/builder/builder-reducer.d.ts +71 -0
  35. package/dist/app/builder/builder-reducer.d.ts.map +1 -0
  36. package/dist/app/builder/builder-reducer.js +201 -0
  37. package/dist/app/builder/builder-reducer.js.map +1 -0
  38. package/dist/app/builder/labels.d.ts +78 -0
  39. package/dist/app/builder/labels.d.ts.map +1 -0
  40. package/dist/app/builder/labels.js +71 -0
  41. package/dist/app/builder/labels.js.map +1 -0
  42. package/dist/app/builder/status-chip.d.ts +10 -0
  43. package/dist/app/builder/status-chip.d.ts.map +1 -0
  44. package/dist/app/builder/status-chip.js +16 -0
  45. package/dist/app/builder/status-chip.js.map +1 -0
  46. package/dist/app/host-bridge.d.ts +211 -0
  47. package/dist/app/host-bridge.d.ts.map +1 -0
  48. package/dist/app/host-bridge.js +247 -0
  49. package/dist/app/host-bridge.js.map +1 -0
  50. package/dist/app/index.d.ts +16 -0
  51. package/dist/app/index.d.ts.map +1 -0
  52. package/dist/app/index.js +18 -0
  53. package/dist/app/index.js.map +1 -0
  54. package/dist/app/layout-builder.d.ts +48 -0
  55. package/dist/app/layout-builder.d.ts.map +1 -0
  56. package/dist/app/layout-builder.js +418 -0
  57. package/dist/app/layout-builder.js.map +1 -0
  58. package/dist/app/mcp-app-view.d.ts +43 -0
  59. package/dist/app/mcp-app-view.d.ts.map +1 -0
  60. package/dist/app/mcp-app-view.js +169 -0
  61. package/dist/app/mcp-app-view.js.map +1 -0
  62. package/dist/app/mcp-toolkit-app.d.ts +18 -0
  63. package/dist/app/mcp-toolkit-app.d.ts.map +1 -0
  64. package/dist/app/mcp-toolkit-app.js +22 -0
  65. package/dist/app/mcp-toolkit-app.js.map +1 -0
  66. package/dist/app/use-host-actions.d.ts +57 -0
  67. package/dist/app/use-host-actions.d.ts.map +1 -0
  68. package/dist/app/use-host-actions.js +42 -0
  69. package/dist/app/use-host-actions.js.map +1 -0
  70. package/dist/app/widget-fixture.d.ts +180 -0
  71. package/dist/app/widget-fixture.d.ts.map +1 -0
  72. package/dist/app/widget-fixture.js +273 -0
  73. package/dist/app/widget-fixture.js.map +1 -0
  74. package/dist/app/widget-props-sheet.d.ts +20 -0
  75. package/dist/app/widget-props-sheet.d.ts.map +1 -0
  76. package/dist/app/widget-props-sheet.js +87 -0
  77. package/dist/app/widget-props-sheet.js.map +1 -0
  78. package/dist/app/widget-renderer.d.ts +87 -0
  79. package/dist/app/widget-renderer.d.ts.map +1 -0
  80. package/dist/app/widget-renderer.js +105 -0
  81. package/dist/app/widget-renderer.js.map +1 -0
  82. package/dist/components/DrillButton.d.ts +22 -0
  83. package/dist/components/DrillButton.d.ts.map +1 -0
  84. package/dist/components/DrillButton.js +13 -0
  85. package/dist/components/DrillButton.js.map +1 -0
  86. package/dist/components/FilterBar.d.ts +28 -0
  87. package/dist/components/FilterBar.d.ts.map +1 -0
  88. package/dist/components/FilterBar.js +12 -0
  89. package/dist/components/FilterBar.js.map +1 -0
  90. package/dist/components/GridLayout.d.ts +26 -0
  91. package/dist/components/GridLayout.d.ts.map +1 -0
  92. package/dist/components/GridLayout.js +31 -0
  93. package/dist/components/GridLayout.js.map +1 -0
  94. package/dist/components/GroupCard.d.ts +21 -0
  95. package/dist/components/GroupCard.d.ts.map +1 -0
  96. package/dist/components/GroupCard.js +24 -0
  97. package/dist/components/GroupCard.js.map +1 -0
  98. package/dist/components/KpiGrid.d.ts +43 -0
  99. package/dist/components/KpiGrid.d.ts.map +1 -0
  100. package/dist/components/KpiGrid.js +35 -0
  101. package/dist/components/KpiGrid.js.map +1 -0
  102. package/dist/components/ListFooter.d.ts +22 -0
  103. package/dist/components/ListFooter.d.ts.map +1 -0
  104. package/dist/components/ListFooter.js +13 -0
  105. package/dist/components/ListFooter.js.map +1 -0
  106. package/dist/components/Pills.d.ts +25 -0
  107. package/dist/components/Pills.d.ts.map +1 -0
  108. package/dist/components/Pills.js +17 -0
  109. package/dist/components/Pills.js.map +1 -0
  110. package/dist/components/SectionHeading.d.ts +16 -0
  111. package/dist/components/SectionHeading.d.ts.map +1 -0
  112. package/dist/components/SectionHeading.js +10 -0
  113. package/dist/components/SectionHeading.js.map +1 -0
  114. package/dist/components/WidgetHeader.d.ts +21 -0
  115. package/dist/components/WidgetHeader.d.ts.map +1 -0
  116. package/dist/components/WidgetHeader.js +11 -0
  117. package/dist/components/WidgetHeader.js.map +1 -0
  118. package/dist/hooks/index.d.ts +11 -0
  119. package/dist/hooks/index.d.ts.map +1 -0
  120. package/dist/hooks/index.js +7 -0
  121. package/dist/hooks/index.js.map +1 -0
  122. package/dist/hooks/use-debounced-value.d.ts +7 -0
  123. package/dist/hooks/use-debounced-value.d.ts.map +1 -0
  124. package/dist/hooks/use-debounced-value.js +15 -0
  125. package/dist/hooks/use-debounced-value.js.map +1 -0
  126. package/dist/hooks/use-mobile.d.ts +6 -0
  127. package/dist/hooks/use-mobile.d.ts.map +1 -0
  128. package/dist/hooks/use-mobile.js +19 -0
  129. package/dist/hooks/use-mobile.js.map +1 -0
  130. package/dist/hooks/use-tool-query.d.ts +21 -0
  131. package/dist/hooks/use-tool-query.d.ts.map +1 -0
  132. package/dist/hooks/use-tool-query.js +47 -0
  133. package/dist/hooks/use-tool-query.js.map +1 -0
  134. package/dist/hooks/use-tool-result-recovery.d.ts +54 -0
  135. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -0
  136. package/dist/hooks/use-tool-result-recovery.js +93 -0
  137. package/dist/hooks/use-tool-result-recovery.js.map +1 -0
  138. package/dist/hooks/use-view-data.d.ts +19 -0
  139. package/dist/hooks/use-view-data.d.ts.map +1 -0
  140. package/dist/hooks/use-view-data.js +18 -0
  141. package/dist/hooks/use-view-data.js.map +1 -0
  142. package/dist/hooks/use-view-tool-query.d.ts +18 -0
  143. package/dist/hooks/use-view-tool-query.d.ts.map +1 -0
  144. package/dist/hooks/use-view-tool-query.js +29 -0
  145. package/dist/hooks/use-view-tool-query.js.map +1 -0
  146. package/dist/index.d.ts +47 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +56 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/lib/parse-tool-result.d.ts +39 -0
  151. package/dist/lib/parse-tool-result.d.ts.map +1 -0
  152. package/dist/lib/parse-tool-result.js +66 -0
  153. package/dist/lib/parse-tool-result.js.map +1 -0
  154. package/dist/lib/tone-utils.d.ts +18 -0
  155. package/dist/lib/tone-utils.d.ts.map +1 -0
  156. package/dist/lib/tone-utils.js +25 -0
  157. package/dist/lib/tone-utils.js.map +1 -0
  158. package/dist/lib/utils.d.ts +3 -0
  159. package/dist/lib/utils.d.ts.map +1 -0
  160. package/dist/lib/utils.js +6 -0
  161. package/dist/lib/utils.js.map +1 -0
  162. package/dist/locale/index.d.ts +3 -0
  163. package/dist/locale/index.d.ts.map +1 -0
  164. package/dist/locale/index.js +7 -0
  165. package/dist/locale/index.js.map +1 -0
  166. package/dist/locale/locale-provider.d.ts +50 -0
  167. package/dist/locale/locale-provider.d.ts.map +1 -0
  168. package/dist/locale/locale-provider.js +47 -0
  169. package/dist/locale/locale-provider.js.map +1 -0
  170. package/dist/primitives/alert.d.ts +10 -0
  171. package/dist/primitives/alert.d.ts.map +1 -0
  172. package/dist/primitives/alert.js +25 -0
  173. package/dist/primitives/alert.js.map +1 -0
  174. package/dist/primitives/badge.d.ts +8 -0
  175. package/dist/primitives/badge.d.ts.map +1 -0
  176. package/dist/primitives/badge.js +34 -0
  177. package/dist/primitives/badge.js.map +1 -0
  178. package/dist/primitives/button.d.ts +11 -0
  179. package/dist/primitives/button.d.ts.map +1 -0
  180. package/dist/primitives/button.js +36 -0
  181. package/dist/primitives/button.js.map +1 -0
  182. package/dist/primitives/card.d.ts +12 -0
  183. package/dist/primitives/card.d.ts.map +1 -0
  184. package/dist/primitives/card.js +25 -0
  185. package/dist/primitives/card.js.map +1 -0
  186. package/dist/primitives/dialog.d.ts +18 -0
  187. package/dist/primitives/dialog.d.ts.map +1 -0
  188. package/dist/primitives/dialog.js +38 -0
  189. package/dist/primitives/dialog.js.map +1 -0
  190. package/dist/primitives/input.d.ts +4 -0
  191. package/dist/primitives/input.d.ts.map +1 -0
  192. package/dist/primitives/input.js +7 -0
  193. package/dist/primitives/input.js.map +1 -0
  194. package/dist/primitives/scroll-area.d.ts +6 -0
  195. package/dist/primitives/scroll-area.d.ts.map +1 -0
  196. package/dist/primitives/scroll-area.js +11 -0
  197. package/dist/primitives/scroll-area.js.map +1 -0
  198. package/dist/primitives/select.d.ts +16 -0
  199. package/dist/primitives/select.d.ts.map +1 -0
  200. package/dist/primitives/select.js +38 -0
  201. package/dist/primitives/select.js.map +1 -0
  202. package/dist/primitives/separator.d.ts +4 -0
  203. package/dist/primitives/separator.d.ts.map +1 -0
  204. package/dist/primitives/separator.js +9 -0
  205. package/dist/primitives/separator.js.map +1 -0
  206. package/dist/primitives/sheet.d.ts +15 -0
  207. package/dist/primitives/sheet.d.ts.map +1 -0
  208. package/dist/primitives/sheet.js +41 -0
  209. package/dist/primitives/sheet.js.map +1 -0
  210. package/dist/primitives/skeleton.d.ts +3 -0
  211. package/dist/primitives/skeleton.d.ts.map +1 -0
  212. package/dist/primitives/skeleton.js +7 -0
  213. package/dist/primitives/skeleton.js.map +1 -0
  214. package/dist/primitives/switch.d.ts +5 -0
  215. package/dist/primitives/switch.d.ts.map +1 -0
  216. package/dist/primitives/switch.js +8 -0
  217. package/dist/primitives/switch.js.map +1 -0
  218. package/dist/primitives/table.d.ts +11 -0
  219. package/dist/primitives/table.d.ts.map +1 -0
  220. package/dist/primitives/table.js +28 -0
  221. package/dist/primitives/table.js.map +1 -0
  222. package/dist/primitives/tabs.d.ts +12 -0
  223. package/dist/primitives/tabs.d.ts.map +1 -0
  224. package/dist/primitives/tabs.js +29 -0
  225. package/dist/primitives/tabs.js.map +1 -0
  226. package/dist/providers/query-provider.d.ts +11 -0
  227. package/dist/providers/query-provider.d.ts.map +1 -0
  228. package/dist/providers/query-provider.js +20 -0
  229. package/dist/providers/query-provider.js.map +1 -0
  230. package/dist/theme/create-theme.d.ts +130 -0
  231. package/dist/theme/create-theme.d.ts.map +1 -0
  232. package/dist/theme/create-theme.js +65 -0
  233. package/dist/theme/create-theme.js.map +1 -0
  234. package/dist/theme/index.d.ts +7 -0
  235. package/dist/theme/index.d.ts.map +1 -0
  236. package/dist/theme/index.js +7 -0
  237. package/dist/theme/index.js.map +1 -0
  238. package/dist/theme/presets.d.ts +25 -0
  239. package/dist/theme/presets.d.ts.map +1 -0
  240. package/dist/theme/presets.js +68 -0
  241. package/dist/theme/presets.js.map +1 -0
  242. package/dist/theme/theme-provider.d.ts +69 -0
  243. package/dist/theme/theme-provider.d.ts.map +1 -0
  244. package/dist/theme/theme-provider.js +79 -0
  245. package/dist/theme/theme-provider.js.map +1 -0
  246. package/package.json +71 -0
  247. package/src/app/adapt-data-widget.tsx +51 -0
  248. package/src/app/builder/CatalogueSheet.tsx +230 -0
  249. package/src/app/builder/PipelineStrip.tsx +260 -0
  250. package/src/app/builder/PreviewPane.tsx +72 -0
  251. package/src/app/builder/SaveDialog.tsx +95 -0
  252. package/src/app/builder/Toolbar.tsx +95 -0
  253. package/src/app/builder/Workspace.tsx +483 -0
  254. package/src/app/builder/builder-model.test.ts +351 -0
  255. package/src/app/builder/builder-model.ts +213 -0
  256. package/src/app/builder/builder-reducer.test.ts +441 -0
  257. package/src/app/builder/builder-reducer.ts +289 -0
  258. package/src/app/builder/labels.ts +139 -0
  259. package/src/app/builder/status-chip.tsx +37 -0
  260. package/src/app/host-bridge.test.ts +206 -0
  261. package/src/app/host-bridge.tsx +371 -0
  262. package/src/app/index.ts +51 -0
  263. package/src/app/layout-builder.tsx +872 -0
  264. package/src/app/mcp-app-view.tsx +367 -0
  265. package/src/app/mcp-toolkit-app.tsx +25 -0
  266. package/src/app/use-host-actions.test.ts +16 -0
  267. package/src/app/use-host-actions.tsx +88 -0
  268. package/src/app/widget-fixture.test.ts +108 -0
  269. package/src/app/widget-fixture.tsx +441 -0
  270. package/src/app/widget-props-sheet.tsx +285 -0
  271. package/src/app/widget-renderer.test.ts +42 -0
  272. package/src/app/widget-renderer.tsx +200 -0
  273. package/src/components/DrillButton.tsx +37 -0
  274. package/src/components/FilterBar.tsx +68 -0
  275. package/src/components/GridLayout.tsx +49 -0
  276. package/src/components/GroupCard.tsx +52 -0
  277. package/src/components/KpiGrid.tsx +134 -0
  278. package/src/components/ListFooter.tsx +48 -0
  279. package/src/components/Pills.tsx +45 -0
  280. package/src/components/SectionHeading.tsx +26 -0
  281. package/src/components/WidgetHeader.tsx +51 -0
  282. package/src/globals.css +168 -0
  283. package/src/hooks/index.ts +17 -0
  284. package/src/hooks/use-debounced-value.ts +15 -0
  285. package/src/hooks/use-mobile.ts +24 -0
  286. package/src/hooks/use-tool-query.ts +63 -0
  287. package/src/hooks/use-tool-result-recovery.test.ts +65 -0
  288. package/src/hooks/use-tool-result-recovery.ts +144 -0
  289. package/src/hooks/use-view-data.ts +31 -0
  290. package/src/hooks/use-view-tool-query.ts +38 -0
  291. package/src/index.ts +134 -0
  292. package/src/lib/parse-tool-result.test.ts +139 -0
  293. package/src/lib/parse-tool-result.ts +89 -0
  294. package/src/lib/tone-utils.test.ts +35 -0
  295. package/src/lib/tone-utils.ts +38 -0
  296. package/src/lib/utils.ts +6 -0
  297. package/src/locale/index.ts +7 -0
  298. package/src/locale/locale-provider.tsx +81 -0
  299. package/src/primitives/alert.tsx +60 -0
  300. package/src/primitives/badge.tsx +49 -0
  301. package/src/primitives/button.tsx +62 -0
  302. package/src/primitives/card.tsx +92 -0
  303. package/src/primitives/dialog.tsx +144 -0
  304. package/src/primitives/input.tsx +21 -0
  305. package/src/primitives/scroll-area.tsx +54 -0
  306. package/src/primitives/select.tsx +173 -0
  307. package/src/primitives/separator.tsx +21 -0
  308. package/src/primitives/sheet.tsx +134 -0
  309. package/src/primitives/skeleton.tsx +13 -0
  310. package/src/primitives/switch.tsx +24 -0
  311. package/src/primitives/table.tsx +87 -0
  312. package/src/primitives/tabs.tsx +79 -0
  313. package/src/providers/query-provider.tsx +38 -0
  314. package/src/theme/create-theme.test.ts +108 -0
  315. package/src/theme/create-theme.ts +158 -0
  316. package/src/theme/index.ts +9 -0
  317. package/src/theme/presets.ts +82 -0
  318. package/src/theme/theme-provider.tsx +152 -0
  319. package/src/ui-catalog.test.ts +112 -0
  320. 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
+ }
@@ -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"