@assistant-ui/mcp-docs-server 0.1.39 → 0.2.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 (185) hide show
  1. package/.docs/organized/code-examples/waterfall.md +17 -18
  2. package/.docs/organized/code-examples/with-a2a.md +20 -15
  3. package/.docs/organized/code-examples/with-ag-ui.md +19 -17
  4. package/.docs/organized/code-examples/with-ai-sdk-v7.md +17 -16
  5. package/.docs/organized/code-examples/with-artifacts.md +471 -145
  6. package/.docs/organized/code-examples/with-assistant-transport.md +23 -31
  7. package/.docs/organized/code-examples/with-browser-extension.md +21 -14
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +22 -20
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +13 -14
  10. package/.docs/organized/code-examples/with-cloud.md +22 -17
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +17 -16
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +17 -17
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +21 -19
  14. package/.docs/organized/code-examples/with-eve.md +70 -16
  15. package/.docs/organized/code-examples/with-expo.md +32 -51
  16. package/.docs/organized/code-examples/with-external-store.md +20 -15
  17. package/.docs/organized/code-examples/with-ffmpeg.md +24 -18
  18. package/.docs/organized/code-examples/with-generative-ui.md +20 -21
  19. package/.docs/organized/code-examples/with-google-adk.md +21 -16
  20. package/.docs/organized/code-examples/with-heat-graph.md +10 -11
  21. package/.docs/organized/code-examples/with-image-generation.md +13 -14
  22. package/.docs/organized/code-examples/with-interactables.md +17 -16
  23. package/.docs/organized/code-examples/with-langchain.md +13 -14
  24. package/.docs/organized/code-examples/with-langgraph.md +23 -17
  25. package/.docs/organized/code-examples/with-livekit.md +17 -17
  26. package/.docs/organized/code-examples/with-mcp.md +36 -32
  27. package/.docs/organized/code-examples/with-nuxt.md +2492 -0
  28. package/.docs/organized/code-examples/with-opencode.md +27 -21
  29. package/.docs/organized/code-examples/with-pi.md +69 -67
  30. package/.docs/organized/code-examples/with-react-hook-form.md +18 -17
  31. package/.docs/organized/code-examples/with-react-ink-web.md +10 -11
  32. package/.docs/organized/code-examples/with-react-ink.md +7 -7
  33. package/.docs/organized/code-examples/with-react-router.md +23 -17
  34. package/.docs/organized/code-examples/with-resumable-stream.md +15 -16
  35. package/.docs/organized/code-examples/with-store.md +31 -20
  36. package/.docs/organized/code-examples/with-tanstack.md +22 -16
  37. package/.docs/organized/code-examples/with-tap-runtime.md +19 -19
  38. package/.docs/organized/code-examples/with-virtualized-thread.md +12 -13
  39. package/.docs/organized/code-examples/with-vue.md +408 -0
  40. package/.docs/raw/docs/(docs)/cli.mdx +3 -1
  41. package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
  42. package/.docs/raw/docs/(docs)/index.mdx +9 -76
  43. package/.docs/raw/docs/(docs)/installation.mdx +4 -18
  44. package/.docs/raw/docs/(reference)/api-reference/context-providers/assistant-runtime-provider.mdx +24 -4
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +3 -0
  47. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
  48. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
  49. package/.docs/raw/docs/(reference)/api-reference/integrations/eve.mdx +8 -0
  50. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +21 -0
  51. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
  52. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +1 -18
  53. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +24 -1
  54. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +1 -1
  55. package/.docs/raw/docs/cloud/ai-sdk.mdx +2 -2
  56. package/.docs/raw/docs/cloud/langgraph.mdx +1 -1
  57. package/.docs/raw/docs/copilots/model-context.mdx +5 -4
  58. package/.docs/raw/docs/copilots/motivation.mdx +5 -5
  59. package/.docs/raw/docs/guides/attachments.mdx +3 -3
  60. package/.docs/raw/docs/guides/branching.mdx +2 -2
  61. package/.docs/raw/docs/guides/chain-of-thought.mdx +1 -1
  62. package/.docs/raw/docs/guides/context-api.mdx +89 -111
  63. package/.docs/raw/docs/guides/editing.mdx +5 -5
  64. package/.docs/raw/docs/guides/electron.mdx +369 -0
  65. package/.docs/raw/docs/guides/index.mdx +10 -0
  66. package/.docs/raw/docs/guides/quoting.mdx +3 -3
  67. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
  68. package/.docs/raw/docs/guides/resumable-streams.mdx +74 -3
  69. package/.docs/raw/docs/guides/suggestions.mdx +9 -9
  70. package/.docs/raw/docs/ink/hooks.mdx +12 -7
  71. package/.docs/raw/docs/ink/primitives.mdx +18 -10
  72. package/.docs/raw/docs/integrations/attachments/custom-adapter.mdx +16 -0
  73. package/.docs/raw/docs/integrations/auth/better-auth.mdx +3 -3
  74. package/.docs/raw/docs/integrations/auth/clerk.mdx +3 -3
  75. package/.docs/raw/docs/integrations/auth/next-auth.mdx +4 -4
  76. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +1 -1
  77. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +2 -2
  78. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  79. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  80. package/.docs/raw/docs/integrations/observability/helicone.mdx +2 -2
  81. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  82. package/.docs/raw/docs/integrations/observability/langsmith.mdx +3 -3
  83. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
  84. package/.docs/raw/docs/migrations/toolkit-tools.mdx +15 -13
  85. package/.docs/raw/docs/migrations/v0-15.mdx +236 -0
  86. package/.docs/raw/docs/primitives/attachment.mdx +2 -2
  87. package/.docs/raw/docs/primitives/composer.mdx +3 -3
  88. package/.docs/raw/docs/primitives/message.mdx +33 -1
  89. package/.docs/raw/docs/primitives/suggestion.mdx +1 -1
  90. package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
  91. package/.docs/raw/docs/react-native/hooks.mdx +17 -7
  92. package/.docs/raw/docs/react-native/index.mdx +3 -3
  93. package/.docs/raw/docs/react-native/primitives.mdx +41 -7
  94. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +3 -3
  95. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +5 -5
  96. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +5 -5
  97. package/.docs/raw/docs/runtimes/ai-sdk/v6-legacy.mdx +13 -14
  98. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +11 -12
  99. package/.docs/raw/docs/runtimes/concepts/threads.mdx +50 -11
  100. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +20 -2
  101. package/.docs/raw/docs/runtimes/custom/external-store.mdx +31 -2
  102. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +39 -12
  103. package/.docs/raw/docs/runtimes/eve/overview.mdx +51 -0
  104. package/.docs/raw/docs/runtimes/eve/quickstart.mdx +51 -2
  105. package/.docs/raw/docs/runtimes/google-adk/hooks.mdx +9 -12
  106. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  107. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
  108. package/.docs/raw/docs/runtimes/langgraph/streaming.mdx +1 -1
  109. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-2.mdx +7 -7
  110. package/.docs/raw/docs/runtimes/langgraph/tutorial/part-3.mdx +4 -4
  111. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +4 -1
  112. package/.docs/raw/docs/runtimes/opencode/overview.mdx +10 -0
  113. package/.docs/raw/docs/tools/a2ui.mdx +107 -0
  114. package/.docs/raw/docs/tools/backend.mdx +2 -2
  115. package/.docs/raw/docs/tools/defining-tools.mdx +9 -7
  116. package/.docs/raw/docs/tools/dynamic-tools.mdx +6 -4
  117. package/.docs/raw/docs/tools/interactables-legacy.mdx +28 -15
  118. package/.docs/raw/docs/tools/interactables.mdx +27 -16
  119. package/.docs/raw/docs/tools/mcp-apps.mdx +96 -8
  120. package/.docs/raw/docs/tools/mcp.mdx +9 -7
  121. package/.docs/raw/docs/tools/tool-ui.mdx +26 -22
  122. package/.docs/raw/docs/tools/user-managed-mcp.mdx +64 -14
  123. package/.docs/raw/docs/ui/file.mdx +6 -1
  124. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +2 -0
  125. package/.docs/raw/docs/ui/mcp-config.mdx +8 -3
  126. package/.docs/raw/docs/ui/model-selector.mdx +9 -9
  127. package/.docs/raw/docs/ui/part-grouping.mdx +1 -5
  128. package/.docs/raw/docs/ui/reasoning.mdx +1 -1
  129. package/.docs/raw/docs/ui/thread.mdx +24 -5
  130. package/.docs/raw/docs/utilities/react-o11y.mdx +7 -9
  131. package/dist/constants.js +2 -2
  132. package/dist/constants.js.map +1 -1
  133. package/dist/index.d.ts +1 -1
  134. package/dist/index.js +2 -2
  135. package/dist/index.js.map +1 -1
  136. package/dist/prepare-docs/code-examples.js.map +1 -1
  137. package/dist/prepare-docs/prepare.d.ts +1 -1
  138. package/dist/prepare-docs/prepare.js.map +1 -1
  139. package/dist/stdio.d.ts +1 -1
  140. package/dist/tools/docs.d.ts +6 -10
  141. package/dist/tools/docs.d.ts.map +1 -1
  142. package/dist/tools/docs.js +6 -4
  143. package/dist/tools/docs.js.map +1 -1
  144. package/dist/tools/examples.d.ts +4 -8
  145. package/dist/tools/examples.d.ts.map +1 -1
  146. package/dist/tools/examples.js +4 -3
  147. package/dist/tools/examples.js.map +1 -1
  148. package/dist/tools/resources.d.ts +1 -1
  149. package/dist/tools/resources.d.ts.map +1 -1
  150. package/dist/tools/resources.js +3 -2
  151. package/dist/tools/resources.js.map +1 -1
  152. package/dist/tools/search.d.ts +4 -10
  153. package/dist/tools/search.d.ts.map +1 -1
  154. package/dist/tools/search.js +2 -2
  155. package/dist/tools/search.js.map +1 -1
  156. package/dist/tools/tests/mcp-test-client.d.ts +15 -0
  157. package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
  158. package/dist/tools/tests/mcp-test-client.js +68 -0
  159. package/dist/tools/tests/mcp-test-client.js.map +1 -0
  160. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  161. package/dist/tools/tests/test-setup.js +2 -1
  162. package/dist/tools/tests/test-setup.js.map +1 -1
  163. package/dist/tools/xulux-templates.d.ts +9 -23
  164. package/dist/tools/xulux-templates.d.ts.map +1 -1
  165. package/dist/tools/xulux-templates.js +8 -6
  166. package/dist/tools/xulux-templates.js.map +1 -1
  167. package/dist/utils/logger.d.ts.map +1 -1
  168. package/dist/utils/mdx.js +2 -1
  169. package/dist/utils/mdx.js.map +1 -1
  170. package/dist/utils/security.js.map +1 -1
  171. package/dist/xulux/catalog-client.js +1 -1
  172. package/dist/xulux/catalog-client.js.map +1 -1
  173. package/package.json +6 -5
  174. package/src/index.ts +2 -2
  175. package/src/tools/docs.ts +2 -2
  176. package/src/tools/examples.ts +2 -2
  177. package/src/tools/resources.ts +1 -4
  178. package/src/tools/search.ts +2 -2
  179. package/src/tools/tests/completions.test.ts +40 -26
  180. package/src/tools/tests/docs.test.ts +2 -2
  181. package/src/tools/tests/integration.test.ts +3 -4
  182. package/src/tools/tests/mcp-protocol.test.ts +160 -175
  183. package/src/tools/tests/mcp-test-client.ts +111 -0
  184. package/src/tools/tests/resources.test.ts +97 -66
  185. package/src/tools/xulux-templates.ts +4 -4
@@ -41,7 +41,7 @@ Interactables allow both agents and users to read and edit tool UIs and componen
41
41
 
42
42
  ```tsx
43
43
  import {
44
- useAui,
44
+ AuiConfig,
45
45
  unstable_Interactables,
46
46
  AssistantRuntimeProvider,
47
47
  Tools,
@@ -50,13 +50,15 @@ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
50
50
 
51
51
  function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
52
52
  const runtime = useChatRuntime();
53
-
54
- const aui = useAui({
53
+ const config = AuiConfig({
55
54
  unstable_interactables: unstable_Interactables(), // [!code ++]
56
55
  });
57
56
 
58
57
  return (
59
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
58
+ <AssistantRuntimeProvider
59
+ runtime={runtime}
60
+ config={config}
61
+ >
60
62
  {children}
61
63
  </AssistantRuntimeProvider>
62
64
  );
@@ -66,7 +68,7 @@ function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
66
68
  <Callout type="idea">
67
69
  The legacy `interactables: Interactables()` scope and the new
68
70
  `unstable_interactables: unstable_Interactables()` scope are mutually
69
- exclusive. Mount only one interactables API in a single `useAui` provider.
71
+ exclusive. Mount only one interactables API in a single provider config.
70
72
  </Callout>
71
73
 
72
74
  This scope is needed for both kinds of interactable. Thread-scoped interactables also live in a toolkit, which you register with `Tools` (shown below).
@@ -166,7 +168,7 @@ const toolkit = defineToolkit({
166
168
  Register the toolkit alongside the `unstable_Interactables` scope:
167
169
 
168
170
  ```tsx
169
- const aui = useAui({
171
+ AuiConfig({
170
172
  unstable_interactables: unstable_Interactables(),
171
173
  tools: Tools({ toolkit }), // [!code ++]
172
174
  });
@@ -199,7 +201,7 @@ export async function POST(req: Request) {
199
201
  const { messages } = await req.json();
200
202
 
201
203
  const result = streamText({
202
- model: openai("gpt-5.4"),
204
+ model: openai("gpt-5.6-luna"),
203
205
  messages: await convertToModelMessages(injectInteractableContext(messages)), // [!code ++]
204
206
  });
205
207
 
@@ -592,7 +594,12 @@ An app-scoped item's history covers the current conversation, not its full cross
592
594
  By default, app-scoped interactable state is in-memory and lost on page refresh. You can add persistence by passing an adapter to `unstable_Interactables`:
593
595
 
594
596
  ```tsx
595
- import { useAui, unstable_Interactables } from "@assistant-ui/react";
597
+ import {
598
+ AuiConfig,
599
+ AuiProvider,
600
+ useAui,
601
+ unstable_Interactables,
602
+ } from "@assistant-ui/react";
596
603
 
597
604
  // Module-level (or memoized) so the adapter identity is stable across renders.
598
605
  const persistenceAdapter = {
@@ -606,19 +613,23 @@ const persistenceAdapter = {
606
613
  };
607
614
 
608
615
  function MyRuntimeProvider({ children }) {
609
- const aui = useAui({
616
+ const aui = useAui();
617
+ const config = AuiConfig({
610
618
  unstable_interactables: unstable_Interactables({
611
619
  persistence: persistenceAdapter,
612
620
  }),
613
621
  });
614
-
615
- return /* ... */;
622
+ return (
623
+ <AuiProvider extends={aui} config={config}>
624
+ {children}
625
+ </AuiProvider>
626
+ );
616
627
  }
617
628
  ```
618
629
 
619
630
  `load` is called when the adapter is attached and may be async. Loaded state seeds interactables as they register; a local edit made while a slow `load` is still in flight wins over the loaded value. Thread-scoped interactables are not touched by the adapter; they persist via thread history.
620
631
 
621
- For dynamic setups (an adapter that depends on auth), call `aui.interactables().setPersistenceAdapter(adapter)` imperatively instead.
632
+ For dynamic setups (an adapter that depends on auth), call `aui.interactables.setPersistenceAdapter(adapter)` imperatively instead.
622
633
 
623
634
  ### Sync Status
624
635
 
@@ -640,10 +651,10 @@ State changes are automatically debounced (500ms) before saving. When the owning
640
651
  For custom persistence strategies, use `exportState` and `importState` directly:
641
652
 
642
653
  ```tsx
643
- const snapshot = aui.interactables().exportState();
654
+ const snapshot = aui.interactables.exportState();
644
655
  // => { "note-1": { name: "note", state: { title: "Hello" } }, ... }
645
656
 
646
- aui.interactables().importState(snapshot);
657
+ aui.interactables.importState(snapshot);
647
658
  // Imported state is picked up when components next register
648
659
  ```
649
660
 
@@ -1040,10 +1051,10 @@ Every version of an interactable recorded in the current thread, oldest first. E
1040
1051
 
1041
1052
  ### `unstable_Interactables`
1042
1053
 
1043
- The scope resource that manages all interactables. Register it via `useAui`, optionally with a [persistence adapter](#persistence):
1054
+ The scope resource that manages all interactables. Register it via your provider's `config`, optionally with a [persistence adapter](#persistence):
1044
1055
 
1045
1056
  ```tsx
1046
- const aui = useAui({
1057
+ AuiConfig({
1047
1058
  unstable_interactables: unstable_Interactables({ persistence: myAdapter }),
1048
1059
  });
1049
1060
  ```
@@ -36,14 +36,17 @@ Compose `McpAppRenderer({...})` into your `Tools` resource. Provide `host.url` p
36
36
 
37
37
  ```tsx
38
38
  import {
39
- useAui,
39
+ AuiConfig,
40
+ AuiProvider,
40
41
  Tools,
41
42
  McpAppRenderer,
42
43
  McpAppsRemoteHost,
44
+ useAui,
43
45
  } from "@assistant-ui/react";
44
46
 
45
- function MyAssistant() {
46
- useAui({
47
+ function MyAssistant({ children }) {
48
+ const aui = useAui();
49
+ const config = AuiConfig({
47
50
  tools: Tools({
48
51
  toolkit: myToolkit,
49
52
  mcpApp: McpAppRenderer({
@@ -53,12 +56,38 @@ function MyAssistant() {
53
56
  }),
54
57
  }),
55
58
  });
56
- // ...
59
+ return (
60
+ <AuiProvider extends={aui} config={config}>
61
+ {children}
62
+ </AuiProvider>
63
+ );
57
64
  }
58
65
  ```
59
66
 
60
67
  `McpAppsRemoteHost` is the default host strategy — it POSTs `{ method, params }` to your route. A different strategy (e.g. a client-side MCP client) can be plugged in by writing a custom resource that returns the same `McpAppsHost` shape (`{ loadResource, callTool, readResource, listResources }`).
61
68
 
69
+ Changes to `headers` or a custom `fetch` function apply to subsequent host requests without reloading the widget. When the account or workspace identity changes, key the host resource explicitly so its UI resource reloads.
70
+
71
+ Install the resource helper:
72
+
73
+ ```bash
74
+ pnpm add @assistant-ui/tap
75
+ ```
76
+
77
+ ```tsx
78
+ import { withKey } from "@assistant-ui/tap";
79
+
80
+ const mcpApp = McpAppRenderer({
81
+ host: withKey(
82
+ workspaceId,
83
+ McpAppsRemoteHost({
84
+ url: "/api/mcp-apps",
85
+ headers: () => getWorkspaceHeaders(workspaceId),
86
+ }),
87
+ ),
88
+ });
89
+ ```
90
+
62
91
  `openLink` is auto-wired to `window.open(url, "_blank", "noopener,noreferrer")`. `sendMessage` is auto-wired to append a user message to the current thread (accepts `string`, `{ prompt }`, `{ text }`, or `{ message }`).
63
92
 
64
93
  ### Route handler
@@ -154,7 +183,7 @@ const tools = await client.listTools();
154
183
  const { modelVisible } = splitMcpAppTools(tools);
155
184
 
156
185
  const result = streamText({
157
- model: openai("gpt-5.4-nano"),
186
+ model: openai("gpt-5.6-luna"),
158
187
  tools: modelVisible.tools,
159
188
  // ...
160
189
  });
@@ -164,7 +193,7 @@ const result = streamText({
164
193
 
165
194
  [OpenAI Apps SDK](https://developers.openai.com/apps-sdk) servers carry the same `ui://` template under a different convention: the pointer is `_meta["openai/outputTemplate"]` on the tool definition (not `_meta.ui.resourceUri`), and the resource is served as `text/html+skybridge` rather than `text/html;profile=mcp-app`. `@ai-sdk/mcp` does not recognize `openai/outputTemplate`, so it never populates `callProviderMetadata.mcp.app` and the renderer stays idle.
166
195
 
167
- The renderer needs no change; you only have to surface the pointer. assistant-ui already reads `result._meta["ui/resourceUri"]` off tool results, so the smallest bridge is to copy the template onto the result by tool name. Build the map once from the tool listing, then stamp it inside each tool's `execute`:
196
+ The renderer needs no change; you only have to surface the pointer. assistant-ui reads the canonical `result._meta.ui.resourceUri` off tool results (and still accepts the deprecated flat `result._meta["ui/resourceUri"]`), so the smallest bridge is to copy the template onto the result by tool name. Build the map once from the tool listing, then stamp it inside each tool's `execute`:
168
197
 
169
198
  ```ts
170
199
  import type { Tool } from "ai";
@@ -184,7 +213,13 @@ const withTemplateUri = (tool: Tool, name: string): Tool => {
184
213
  ...tool,
185
214
  execute: async (args, options) => {
186
215
  const result = (await exec(args, options)) as { _meta?: Record<string, unknown> };
187
- return { ...result, _meta: { ...result._meta, "ui/resourceUri": uri } };
216
+ return {
217
+ ...result,
218
+ _meta: {
219
+ ...result._meta,
220
+ ui: { ...(result._meta?.["ui"] as Record<string, unknown>), resourceUri: uri },
221
+ },
222
+ };
188
223
  },
189
224
  } satisfies Tool;
190
225
  };
@@ -204,6 +239,59 @@ Your `mcp-apps/read-resource` handler reads the `ui://` resource as in the route
204
239
 
205
240
  The cleaner long-term fix is upstream: if `@ai-sdk/mcp`'s `getMCPAppToolMeta` also read `openai/outputTemplate`, then `callProviderMetadata.mcp.app` would populate automatically and this bridge would be unnecessary.
206
241
 
242
+ ## AG-UI integration
243
+
244
+ With `@assistant-ui/react-ag-ui`, the backend associates an MCP App with a tool call through an `ACTIVITY_SNAPSHOT`. Include the tool call ID, the app's `ui://` resource URI, and the MCP server identity when routing across multiple servers:
245
+
246
+ ```json
247
+ {
248
+ "type": "ACTIVITY_SNAPSHOT",
249
+ "activityType": "mcp-apps",
250
+ "content": {
251
+ "toolCallId": "call-1",
252
+ "resourceUri": "ui://maps/result.html",
253
+ "serverId": "maps",
254
+ "result": {
255
+ "content": [{ "type": "text", "text": "Map ready" }],
256
+ "structuredContent": { "center": [37.77, -122.42] },
257
+ "_meta": { "initialView": "street" },
258
+ "isError": false
259
+ }
260
+ }
261
+ }
262
+ ```
263
+
264
+ `serverId` is optional. If it is absent, the runtime accepts `serverHash` as a fallback. Older middleware may omit `toolCallId`, in which case the snapshot applies to the last resolved tool call; new integrations should include it so concurrent tool calls are correlated unambiguously. Emit the snapshot after the tool call's `TOOL_CALL_START`. A snapshot that names a tool call restored from prior history applies to that message directly, so re-emitting the activity after a `MESSAGES_SNAPSHOT` rehydrates its widget; a snapshot for a tool call ID the runtime has never seen is silently ignored.
265
+
266
+ Send the normal `TOOL_CALL_RESULT` with a concise, model-visible summary:
267
+
268
+ ```json
269
+ {
270
+ "type": "TOOL_CALL_RESULT",
271
+ "toolCallId": "call-1",
272
+ "content": "Map ready"
273
+ }
274
+ ```
275
+
276
+ The snapshot's `result` becomes the widget-visible `part.result`, while the `TOOL_CALL_RESULT.content` string is retained for the model as `part.modelContent`, a text content-part array (`[{ "type": "text", "text": "Map ready" }]`).
277
+
278
+ Alternatively, AG-UI servers can place MCP host fields directly on `TOOL_CALL_RESULT`:
279
+
280
+ ```json
281
+ {
282
+ "type": "TOOL_CALL_RESULT",
283
+ "toolCallId": "call-1",
284
+ "content": "Map ready",
285
+ "structuredContent": { "center": [37.77, -122.42] },
286
+ "_meta": { "initialView": "street" },
287
+ "isError": false
288
+ }
289
+ ```
290
+
291
+ In this form, the runtime assembles a `CallToolResult`-shaped `part.result` from `content`, `structuredContent`, `_meta`, and `isError`. At least one of `structuredContent` or `_meta` must be present; when both are absent the enriched path does not activate and the result falls back to the plain `content` string. The `_meta` can also carry the app pointer itself (canonical `ui.resourceUri`, or the deprecated flat `"ui/resourceUri"`), in which case no `ACTIVITY_SNAPSHOT` is needed to activate the widget. A snapshot remains the only carrier for server identity (`serverId`), and when both provide a `resourceUri` the snapshot wins.
292
+
293
+ The visibility split follows the MCP Apps contract: `content` is model-visible; `structuredContent` and `_meta` are available only to the host and widget. When assistant-ui serializes history into a later AG-UI request, it sends the saved model-visible text and never includes `structuredContent` or `_meta`.
294
+
207
295
  ## Bridge protocol
208
296
 
209
297
  The bridge implements the MCP UI JSON-RPC protocol over `window.postMessage`, filtered by both `event.source === frame.iframe.contentWindow` AND `event.origin === frame.origin` — the cross-origin domain `SafeContentFrame` issues per render. Messages from any other origin or window are dropped silently.
@@ -255,4 +343,4 @@ McpAppRenderer({
255
343
  - Widgets run cross-origin in a sandboxed iframe. The bridge filters incoming messages by both source window and origin.
256
344
  - The host route is your auth boundary — apply session checks, rate limiting, and per-tool allowlists there. The renderer trusts whatever the route returns.
257
345
  - `openLink` rejects non-`http(s)` URLs at the bridge layer, but your `openLink` handler should still treat the URL as untrusted (e.g. always use `noopener,noreferrer`).
258
- - Keep `host` and `handlers` references stable across renders (e.g. module-scope constants or `useMemo`); an unstable identity will tear down and refetch the widget on every parent re-render.
346
+ - Keep custom `host` and `handlers` references stable across renders (e.g. module-scope constants or `useMemo`); an unstable custom-host identity keeps the widget in `loadingFallback` while refetching on every parent re-render.
@@ -177,7 +177,7 @@ export async function POST(req: Request) {
177
177
  const aiToolkit = new AISDKToolkit({ toolkit });
178
178
 
179
179
  const result = streamText({
180
- model: openai("gpt-5.4-mini"),
180
+ model: openai("gpt-5.6-luna"),
181
181
  messages: await convertToModelMessages(messages),
182
182
  tools: await aiToolkit.tools({ frontend: tools }),
183
183
  onFinish: async () => {
@@ -220,7 +220,7 @@ export async function POST(req: Request) {
220
220
  const tools = await mcpClient.tools();
221
221
 
222
222
  const result = streamText({
223
- model: openai("gpt-5.4-mini"),
223
+ model: openai("gpt-5.6-luna"),
224
224
  messages: await convertToModelMessages(messages),
225
225
  tools,
226
226
  onFinish: async () => {
@@ -369,7 +369,7 @@ Register the toolkit once with `Tools({ toolkit })`. Renderer keys such as
369
369
  ```tsx title="app/components/RuntimeProvider.tsx"
370
370
  "use client";
371
371
 
372
- import { AssistantRuntimeProvider, Tools, useAui } from "@assistant-ui/react";
372
+ import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
373
373
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
374
374
  import type { ReactNode } from "react";
375
375
 
@@ -377,10 +377,12 @@ import { toolkit } from "./GitHubIssueToolUI";
377
377
 
378
378
  export function MyRuntimeProvider({ children }: { children: ReactNode }) {
379
379
  const runtime = useChatRuntime();
380
- const aui = useAui({ tools: Tools({ toolkit }) });
381
-
380
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
382
381
  return (
383
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
382
+ <AssistantRuntimeProvider
383
+ runtime={runtime}
384
+ config={config}
385
+ >
384
386
  {children}
385
387
  </AssistantRuntimeProvider>
386
388
  );
@@ -400,7 +402,7 @@ MCP tools execute on the server, so approval is a server-side tool gate, not a `
400
402
  const tools = await mcpClient.tools();
401
403
 
402
404
  const result = streamText({
403
- model: openai("gpt-5.4-mini"),
405
+ model: openai("gpt-5.6-luna"),
404
406
  messages: await convertToModelMessages(messages),
405
407
  tools,
406
408
  toolApproval: {
@@ -69,15 +69,15 @@ export default defineToolkit({
69
69
  ```
70
70
 
71
71
  ```tsx title="app/MyRuntimeProvider.tsx"
72
- import { AssistantRuntimeProvider, Tools, useAui } from "@assistant-ui/react";
72
+ import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
73
73
  import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
74
74
  import toolkit from "./toolkit";
75
75
 
76
76
  function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
77
77
  const runtime = useChatRuntime();
78
- const aui = useAui({ tools: Tools({ toolkit }) });
78
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
79
79
  return (
80
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
80
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
81
81
  {children}
82
82
  </AssistantRuntimeProvider>
83
83
  );
@@ -190,10 +190,9 @@ const toolkit = defineToolkit({
190
190
  });
191
191
 
192
192
  function App({ runtime }: { runtime: AssistantRuntime }) {
193
- const aui = useAui({ tools: Tools({ toolkit }) });
194
-
193
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
195
194
  return (
196
- <AssistantRuntimeProvider aui={aui} runtime={runtime}>
195
+ <AssistantRuntimeProvider runtime={runtime} config={config}>
197
196
  <Thread />
198
197
  </AssistantRuntimeProvider>
199
198
  );
@@ -215,7 +214,7 @@ export async function POST(req: Request) {
215
214
  const { messages } = await req.json();
216
215
 
217
216
  const result = streamText({
218
- model: openai("gpt-5.4-nano"),
217
+ model: openai("gpt-5.6-luna"),
219
218
  messages: await convertToModelMessages(messages),
220
219
  tools: {
221
220
  getWeather: tool({
@@ -331,19 +330,21 @@ export function useAnalyzeDataToolkit(theme: "light" | "dark") {
331
330
  ```
332
331
 
333
332
  ```tsx title="DynamicToolUI.tsx"
334
- import { AuiProvider, Tools, useAui } from "@assistant-ui/react";
333
+ import { AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react";
335
334
  import { useState } from "react";
336
335
  import { useAnalyzeDataToolkit } from "./analyze-data-toolkit";
337
336
 
338
337
  function DynamicToolUI({ children }: { children: React.ReactNode }) {
339
338
  const [theme] = useState<"light" | "dark">("light");
340
339
  const toolkit = useAnalyzeDataToolkit(theme);
340
+ const aui = useAui();
341
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
341
342
 
342
- const aui = useAui({
343
- tools: Tools({ toolkit }),
344
- });
345
-
346
- return <AuiProvider value={aui}>{children}</AuiProvider>;
343
+ return (
344
+ <AuiProvider extends={aui} config={config}>
345
+ {children}
346
+ </AuiProvider>
347
+ );
347
348
  }
348
349
  ```
349
350
 
@@ -390,18 +391,16 @@ export function useInventoryToolkit(productId: string, productName: string) {
390
391
  ```
391
392
 
392
393
  ```tsx title="ProductPage.tsx"
393
- import { AuiProvider, Tools, useAui } from "@assistant-ui/react";
394
+ import { AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react";
394
395
  import { useInventoryToolkit } from "./inventory-toolkit";
395
396
 
396
397
  function ProductPage({ productId, productName }) {
397
398
  const toolkit = useInventoryToolkit(productId, productName);
398
-
399
- const aui = useAui({
400
- tools: Tools({ toolkit }),
401
- });
399
+ const aui = useAui();
400
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
402
401
 
403
402
  return (
404
- <AuiProvider value={aui}>
403
+ <AuiProvider extends={aui} config={config}>
405
404
  <div>Product details...</div>
406
405
  </AuiProvider>
407
406
  );
@@ -942,15 +941,20 @@ export function useHeavyComputationToolkit() {
942
941
  ```
943
942
 
944
943
  ```tsx title="HeavyComputationToolProvider.tsx"
945
- import { AuiProvider, Tools, useAui } from "@assistant-ui/react";
944
+ import { AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react";
946
945
  import type { ReactNode } from "react";
947
946
  import { useHeavyComputationToolkit } from "./heavy-computation-toolkit";
948
947
 
949
948
  function HeavyComputationToolProvider({ children }: { children: ReactNode }) {
950
949
  const toolkit = useHeavyComputationToolkit();
951
- const aui = useAui({ tools: Tools({ toolkit }) });
950
+ const aui = useAui();
951
+ const config = AuiConfig({ tools: Tools({ toolkit }) });
952
952
 
953
- return <AuiProvider value={aui}>{children}</AuiProvider>;
953
+ return (
954
+ <AuiProvider extends={aui} config={config}>
955
+ {children}
956
+ </AuiProvider>
957
+ );
954
958
  }
955
959
  ```
956
960
 
@@ -15,14 +15,14 @@ Both flow through one connection lifecycle, one persisted state surface, and one
15
15
  ## How it works
16
16
 
17
17
  ```
18
- useAui({ mcp: McpManagerResource({ connectors }) })
18
+ AuiConfig({ mcp: McpManagerResource({ connectors }) })
19
19
  │
20
20
  ├─ Resource — connection lifecycle, server lookup, OAuth/bearer auth
21
21
  ├─ Auto-mounts the modelContext scope when no chat runtime provides one
22
22
  └─ Registers connected tools as frontend tools — your chat sees them automatically
23
23
  ```
24
24
 
25
- The manager is a single resource. Mount it with `useAui` like any other scope. OAuth (PKCE + RFC 7591 dynamic client registration), bearer, and "no auth" are first-class. Token refresh runs inside the MCP SDK on 401; this package mediates persistence and the redirect step.
25
+ The manager is a single resource. Mount it with `AuiProvider` like any other scope. OAuth (PKCE + RFC 7591 dynamic client registration), bearer, and "no auth" are first-class. Token refresh runs inside the MCP SDK on 401; this package mediates persistence and the redirect step.
26
26
 
27
27
  ## Setup
28
28
 
@@ -38,12 +38,12 @@ The manager is a single resource. Mount it with `useAui` like any other scope. O
38
38
 
39
39
  ### Mount the manager
40
40
 
41
- Declare your connectors and provide the `mcp` scope on `useAui`. No provider wrapper, no imperative hooks:
41
+ Declare your connectors and provide the `mcp` scope via `AuiProvider`. No imperative hooks:
42
42
 
43
43
  ```tsx title="app/providers.tsx"
44
44
  "use client";
45
45
 
46
- import { AuiProvider, useAui } from "@assistant-ui/react";
46
+ import { AuiConfig, AuiProvider, useAui } from "@assistant-ui/react";
47
47
  import { McpManagerResource, defineConnector } from "@assistant-ui/react-mcp";
48
48
 
49
49
  const connectors = [
@@ -64,13 +64,18 @@ const connectors = [
64
64
  ];
65
65
 
66
66
  export function Providers({ children }: { children: React.ReactNode }) {
67
- const aui = useAui({
67
+ const aui = useAui();
68
+ const config = AuiConfig({
68
69
  mcp: McpManagerResource({
69
70
  connectors,
70
71
  connectionTimeout: 15_000,
71
72
  }),
72
73
  });
73
- return <AuiProvider value={aui}>{children}</AuiProvider>;
74
+ return (
75
+ <AuiProvider extends={aui} config={config}>
76
+ {children}
77
+ </AuiProvider>
78
+ );
74
79
  }
75
80
  ```
76
81
 
@@ -114,7 +119,7 @@ Pass children to override the trigger:
114
119
  </McpConfigDialog>
115
120
  ```
116
121
 
117
- **Compose your own from primitives** — three namespaces, all unstyled and `data-*`-driven. The iteration primitives take a **render function** so the body re-runs per server with the right scope:
122
+ **Compose your own from primitives.** Four namespaces are available, all unstyled and `data-*`-driven. The iteration primitives take a **render function** so the body re-runs per server with the right scope:
118
123
 
119
124
  ```tsx title="app/mcp/page.tsx"
120
125
  "use client";
@@ -179,7 +184,7 @@ The iteration primitives wrap each item in an `McpServerByIdProvider`, so the ne
179
184
  </McpAddFormPrimitive.Root>
180
185
  ```
181
186
 
182
- The form owns its own draft state and submits via `aui.mcp().addCustomServer(...)`. Pass a render function to `AuthFields` to fully customize it.
187
+ The form owns its own draft state and submits via `aui.mcp.addCustomServer(...)`. Pass a render function to `AuthFields` to fully customize it.
183
188
 
184
189
  </Step>
185
190
  <Step>
@@ -236,12 +241,56 @@ If no chat runtime is mounted, `McpManagerResource` brings its own minimal `mode
236
241
  ```ts
237
242
  // In an event handler, never in render.
238
243
  const aui = useAui();
239
- const out = await aui.mcp().server({ id: "linear" }).callTool("search", { q });
244
+ const out = await aui.mcp.server({ id: "linear" }).callTool("search", { q });
240
245
  ```
241
246
 
242
247
  </Step>
243
248
  </Steps>
244
249
 
250
+ ## Form elicitation
251
+
252
+ Connected servers can request structured user input through form-mode elicitation. Render `McpElicitationPrimitive.Items` inside a server-scoped subtree, then compose fields and response actions from the unstyled primitives:
253
+
254
+ Answering a request requires composing `McpElicitationPrimitive`, because the server waits for the form response otherwise. Set `elicitation: false` on a connector or custom server to opt it out of advertising the capability. Numeric fields can stay text inputs (parseable strings are coerced); boolean fields must compose a checkbox, because string drafts for booleans are flagged invalid rather than coerced. Clearing a field returns it to the unanswered state, so an empty text input is omitted from the response rather than submitted as `""` or flagged invalid, unless the schema admits `""` for that property through an `enum` member or a `""` default. Render `enum` properties as a select; when `""` is not a member, its blank option is the unanswered state.
255
+
256
+ ```tsx
257
+ import { McpElicitationPrimitive } from "@assistant-ui/react-mcp";
258
+
259
+ <McpElicitationPrimitive.Items>
260
+ {() => (
261
+ <McpElicitationPrimitive.Root>
262
+ <McpElicitationPrimitive.Message />
263
+ <McpElicitationPrimitive.Error />
264
+ <McpElicitationPrimitive.Fields>
265
+ {({ name, schema, value, setValue }) =>
266
+ (schema as { type?: string } | undefined)?.type === "boolean" ? (
267
+ <label>
268
+ {name}
269
+ <input
270
+ type="checkbox"
271
+ checked={value === true}
272
+ onChange={(event) => setValue(event.target.checked)}
273
+ />
274
+ </label>
275
+ ) : (
276
+ <input
277
+ name={name}
278
+ value={typeof value === "string" ? value : ""}
279
+ onChange={(event) => setValue(event.target.value)}
280
+ />
281
+ )
282
+ }
283
+ </McpElicitationPrimitive.Fields>
284
+ <McpElicitationPrimitive.Accept>Submit</McpElicitationPrimitive.Accept>
285
+ <McpElicitationPrimitive.Decline>Decline</McpElicitationPrimitive.Decline>
286
+ <McpElicitationPrimitive.Cancel>Cancel</McpElicitationPrimitive.Cancel>
287
+ </McpElicitationPrimitive.Root>
288
+ )}
289
+ </McpElicitationPrimitive.Items>
290
+ ```
291
+
292
+ Client-side validation errors keep the elicitation pending so the user can correct the form, and `McpElicitationPrimitive.Error` renders the resulting message.
293
+
245
294
  ## Storage
246
295
 
247
296
  All persisted state — custom server records, OAuth tokens, PKCE verifiers, DCR client info — goes through a single `MCPStorage` resource. Three built-ins:
@@ -253,7 +302,7 @@ All persisted state — custom server records, OAuth tokens, PKCE verifiers, DCR
253
302
  ```tsx
254
303
  import { McpManagerResource, McpCustomStorage } from "@assistant-ui/react-mcp";
255
304
 
256
- const aui = useAui({
305
+ AuiConfig({
257
306
  mcp: McpManagerResource({
258
307
  connectors,
259
308
  storage: McpCustomStorage({
@@ -312,9 +361,9 @@ Imperative methods: `useAui` + resolve in a callback (never during render):
312
361
  const aui = useAui();
313
362
 
314
363
  // inside an event handler:
315
- await aui.mcp().addCustomServer({ name, url, auth: { type: "bearer", token } });
316
- await aui.mcp().server({ id }).connect();
317
- await aui.mcp().server({ id }).callTool("echo", { text: "hi" });
364
+ await aui.mcp.addCustomServer({ name, url, auth: { type: "bearer", token } });
365
+ await aui.mcp.server({ id }).connect();
366
+ await aui.mcp.server({ id }).callTool("echo", { text: "hi" });
318
367
 
319
368
  // Build a paginated resource browser/preview UI.
320
369
  type ResourcePage = {
@@ -322,7 +371,7 @@ type ResourcePage = {
322
371
  nextCursor?: string;
323
372
  };
324
373
 
325
- const server = aui.mcp().server({ id });
374
+ const server = aui.mcp.server({ id });
326
375
  const resources: ResourcePage["resources"] = [];
327
376
  let nextCursor: string | undefined;
328
377
 
@@ -346,6 +395,7 @@ What ships:
346
395
 
347
396
  - Tool listing and invocation, auto-registered as frontend tools
348
397
  - Resource listing and reads for app-built browsers or preview panes
398
+ - Form-mode elicitation with app-composed fields and response actions
349
399
  - OAuth (PKCE + DCR), bearer, none
350
400
  - StreamableHTTP transport
351
401
  - Manual connect/disconnect
@@ -115,7 +115,7 @@ import {
115
115
  | `File.Icon` | MIME type-aware icon, or pass custom `children` |
116
116
  | `File.Name` | Truncated filename |
117
117
  | `File.Size` | Human-readable file size |
118
- | `File.Download` | Download link button |
118
+ | `File.Download` | Download link button; renders nothing without a browser-resolvable target (`sourceType: "id"`, urls that are not http(s)/blob/data) |
119
119
 
120
120
  ### Custom Icon
121
121
 
@@ -134,6 +134,7 @@ The component also exports utility functions:
134
134
  ```tsx
135
135
  import {
136
136
  getMimeTypeIcon,
137
+ getFileDataKind,
137
138
  getBase64Size,
138
139
  formatFileSize,
139
140
  } from "@/components/assistant-ui/file";
@@ -141,6 +142,10 @@ import {
141
142
  // Get icon component for a MIME type
142
143
  const Icon = getMimeTypeIcon("application/pdf"); // FileTextIcon
143
144
 
145
+ // Classify the data field of a file part; a declared sourceType wins over sniffing, except a data: payload declared as "url"
146
+ const kind = getFileDataKind("https://example.com/report.pdf"); // "url"
147
+ const ref = getFileDataKind("file-abc123", "id"); // "id"
148
+
144
149
  // Calculate size from base64 string
145
150
  const bytes = getBase64Size("SGVsbG8gV29ybGQh"); // 12
146
151
 
@@ -74,6 +74,8 @@ const ThreadViewportFooter = () => {
74
74
 
75
75
  The component only renders when the thread is not empty, not currently running, and has at least one suggestion. Each suggestion uses `ThreadPrimitive.Suggestion`, replaces the composer text with the prompt, and sends it immediately.
76
76
 
77
+ Suggestions stay on a single line. When they overflow, the row scrolls horizontally without a scrollbar, and each edge with clipped content fades out — so no fade shows at the start edge until you scroll.
78
+
77
79
  ## Related Components
78
80
 
79
81
  - [Thread](/docs/ui/thread) - Complete chat interface with message list and composer