@assistant-ui/mcp-docs-server 0.1.38 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/.docs/organized/code-examples/waterfall.md +11 -12
  2. package/.docs/organized/code-examples/with-a2a.md +18 -13
  3. package/.docs/organized/code-examples/with-ag-ui.md +19 -14
  4. package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +34 -23
  5. package/.docs/organized/code-examples/with-artifacts.md +473 -141
  6. package/.docs/organized/code-examples/with-assistant-transport.md +17 -10
  7. package/.docs/organized/code-examples/with-browser-extension.md +17 -10
  8. package/.docs/organized/code-examples/with-chain-of-thought.md +21 -14
  9. package/.docs/organized/code-examples/with-cloud-standalone.md +12 -11
  10. package/.docs/organized/code-examples/with-cloud.md +20 -15
  11. package/.docs/organized/code-examples/with-custom-thread-list.md +19 -12
  12. package/.docs/organized/code-examples/with-elevenlabs-conversational.md +22 -15
  13. package/.docs/organized/code-examples/with-elevenlabs-scribe.md +22 -15
  14. package/.docs/organized/code-examples/with-eve.md +18 -11
  15. package/.docs/organized/code-examples/with-expo.md +35 -52
  16. package/.docs/organized/code-examples/with-external-store.md +18 -13
  17. package/.docs/organized/code-examples/with-ffmpeg.md +20 -15
  18. package/.docs/organized/code-examples/with-generative-ui.md +22 -17
  19. package/.docs/organized/code-examples/with-google-adk.md +18 -11
  20. package/.docs/organized/code-examples/with-heat-graph.md +10 -11
  21. package/.docs/organized/code-examples/with-image-generation.md +19 -12
  22. package/.docs/organized/code-examples/with-interactables.md +21 -17
  23. package/.docs/organized/code-examples/with-langchain.md +19 -12
  24. package/.docs/organized/code-examples/with-langgraph.md +19 -12
  25. package/.docs/organized/code-examples/with-livekit.md +23 -16
  26. package/.docs/organized/code-examples/with-mcp.md +46 -28
  27. package/.docs/organized/code-examples/with-opencode.md +25 -23
  28. package/.docs/organized/code-examples/with-pi.md +54 -19
  29. package/.docs/organized/code-examples/with-react-hook-form.md +21 -16
  30. package/.docs/organized/code-examples/with-react-ink-web.md +9 -9
  31. package/.docs/organized/code-examples/with-react-ink.md +4 -4
  32. package/.docs/organized/code-examples/with-react-router.md +22 -17
  33. package/.docs/organized/code-examples/with-resumable-stream.md +21 -14
  34. package/.docs/organized/code-examples/with-store.md +10 -11
  35. package/.docs/organized/code-examples/with-tanstack.md +19 -13
  36. package/.docs/organized/code-examples/with-tap-runtime.md +18 -13
  37. package/.docs/organized/code-examples/with-virtualized-thread.md +19 -14
  38. package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
  39. package/.docs/raw/docs/(docs)/cli.mdx +19 -1
  40. package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
  41. package/.docs/raw/docs/(docs)/installation.mdx +15 -1
  42. package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
  43. package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
  44. package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
  45. package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
  46. package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +22 -1
  47. package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
  48. package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
  49. package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
  50. package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
  51. package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
  52. package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
  53. package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
  54. package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
  55. package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +25 -2
  56. package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
  57. package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
  58. package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
  59. package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +14 -31
  60. package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
  61. package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
  62. package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
  63. package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
  64. package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
  65. package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
  66. package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
  67. package/.docs/raw/docs/cloud/langgraph.mdx +4 -2
  68. package/.docs/raw/docs/copilots/model-context.mdx +1 -1
  69. package/.docs/raw/docs/copilots/motivation.mdx +1 -1
  70. package/.docs/raw/docs/guides/attachments.mdx +3 -3
  71. package/.docs/raw/docs/guides/branching.mdx +2 -2
  72. package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
  73. package/.docs/raw/docs/guides/context-api.mdx +89 -111
  74. package/.docs/raw/docs/guides/dictation.mdx +185 -257
  75. package/.docs/raw/docs/guides/editing.mdx +5 -5
  76. package/.docs/raw/docs/guides/electron.mdx +369 -0
  77. package/.docs/raw/docs/guides/index.mdx +20 -0
  78. package/.docs/raw/docs/guides/mentions.mdx +31 -3
  79. package/.docs/raw/docs/guides/quoting.mdx +3 -3
  80. package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
  81. package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
  82. package/.docs/raw/docs/guides/speech.mdx +47 -29
  83. package/.docs/raw/docs/guides/suggestions.mdx +70 -1
  84. package/.docs/raw/docs/guides/voice.mdx +197 -267
  85. package/.docs/raw/docs/ink/hooks.mdx +3 -3
  86. package/.docs/raw/docs/ink/primitives.mdx +49 -8
  87. package/.docs/raw/docs/integrations/auth/better-auth.mdx +2 -2
  88. package/.docs/raw/docs/integrations/auth/clerk.mdx +2 -2
  89. package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -3
  90. package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +10 -4
  91. package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
  92. package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
  93. package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
  94. package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
  95. package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
  96. package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
  97. package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
  98. package/.docs/raw/docs/integrations/observability/langsmith.mdx +2 -2
  99. package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
  100. package/.docs/raw/docs/migrations/index.mdx +50 -0
  101. package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
  102. package/.docs/raw/docs/migrations/v0-15.mdx +156 -0
  103. package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
  104. package/.docs/raw/docs/primitives/composer.mdx +17 -1
  105. package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
  106. package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
  107. package/.docs/raw/docs/react-native/hooks.mdx +3 -3
  108. package/.docs/raw/docs/react-native/index.mdx +2 -2
  109. package/.docs/raw/docs/react-native/primitives.mdx +62 -5
  110. package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
  111. package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +10 -1
  112. package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +14 -5
  113. package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +15 -15
  114. package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +8 -8
  115. package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +11 -9
  116. package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
  117. package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
  118. package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
  119. package/.docs/raw/docs/runtimes/concepts/threads.mdx +10 -10
  120. package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +2 -2
  121. package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
  122. package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -2
  123. package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +5 -3
  124. package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
  125. package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
  126. package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
  127. package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
  128. package/.docs/raw/docs/runtimes/opencode/hooks.mdx +3 -1
  129. package/.docs/raw/docs/tools/a2ui.mdx +107 -0
  130. package/.docs/raw/docs/tools/backend.mdx +6 -3
  131. package/.docs/raw/docs/tools/defining-tools.mdx +7 -1
  132. package/.docs/raw/docs/tools/generative-ui.mdx +60 -2
  133. package/.docs/raw/docs/tools/interactables-legacy.mdx +4 -4
  134. package/.docs/raw/docs/tools/interactables.mdx +3 -3
  135. package/.docs/raw/docs/tools/mcp-apps.mdx +90 -13
  136. package/.docs/raw/docs/tools/mcp.mdx +100 -3
  137. package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
  138. package/.docs/raw/docs/tools/user-managed-mcp.mdx +77 -9
  139. package/.docs/raw/docs/ui/accordion.mdx +16 -10
  140. package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
  141. package/.docs/raw/docs/ui/attachment.mdx +5 -1
  142. package/.docs/raw/docs/ui/badge.mdx +23 -12
  143. package/.docs/raw/docs/ui/follow-up-suggestions.mdx +4 -2
  144. package/.docs/raw/docs/ui/model-selector.mdx +33 -3
  145. package/.docs/raw/docs/ui/part-grouping.mdx +0 -4
  146. package/.docs/raw/docs/ui/reasoning.mdx +1 -1
  147. package/.docs/raw/docs/ui/select.mdx +22 -14
  148. package/.docs/raw/docs/ui/sources.mdx +1 -1
  149. package/.docs/raw/docs/ui/tabs.mdx +25 -14
  150. package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
  151. package/dist/constants.d.ts.map +1 -1
  152. package/dist/index.d.ts +1 -2
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +41 -2
  155. package/dist/index.js.map +1 -1
  156. package/dist/prepare-docs/code-examples.d.ts.map +1 -1
  157. package/dist/prepare-docs/code-examples.js.map +1 -1
  158. package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
  159. package/dist/prepare-docs/prepare.d.ts +1 -1
  160. package/dist/prompts/xulux-playground.d.ts +12 -0
  161. package/dist/prompts/xulux-playground.d.ts.map +1 -0
  162. package/dist/prompts/xulux-playground.js +33 -0
  163. package/dist/prompts/xulux-playground.js.map +1 -0
  164. package/dist/stdio.d.ts +1 -1
  165. package/dist/tools/docs.d.ts +8 -14
  166. package/dist/tools/docs.d.ts.map +1 -1
  167. package/dist/tools/docs.js +26 -10
  168. package/dist/tools/docs.js.map +1 -1
  169. package/dist/tools/examples.d.ts +6 -12
  170. package/dist/tools/examples.d.ts.map +1 -1
  171. package/dist/tools/examples.js +11 -8
  172. package/dist/tools/examples.js.map +1 -1
  173. package/dist/tools/resources.d.ts +1 -2
  174. package/dist/tools/resources.d.ts.map +1 -1
  175. package/dist/tools/resources.js +1 -1
  176. package/dist/tools/resources.js.map +1 -1
  177. package/dist/tools/search.d.ts +6 -15
  178. package/dist/tools/search.d.ts.map +1 -1
  179. package/dist/tools/search.js +2 -2
  180. package/dist/tools/search.js.map +1 -1
  181. package/dist/tools/tests/mcp-test-client.d.ts +15 -0
  182. package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
  183. package/dist/tools/tests/mcp-test-client.js +68 -0
  184. package/dist/tools/tests/mcp-test-client.js.map +1 -0
  185. package/dist/tools/tests/test-setup.d.ts.map +1 -1
  186. package/dist/tools/tests/test-setup.js +5 -1
  187. package/dist/tools/tests/test-setup.js.map +1 -1
  188. package/dist/tools/xulux-templates.d.ts +58 -0
  189. package/dist/tools/xulux-templates.d.ts.map +1 -0
  190. package/dist/tools/xulux-templates.js +82 -0
  191. package/dist/tools/xulux-templates.js.map +1 -0
  192. package/dist/utils/cache.d.ts +5 -0
  193. package/dist/utils/cache.d.ts.map +1 -0
  194. package/dist/utils/cache.js +18 -0
  195. package/dist/utils/cache.js.map +1 -0
  196. package/dist/utils/logger.d.ts.map +1 -1
  197. package/dist/utils/mcp-format.d.ts +1 -0
  198. package/dist/utils/mcp-format.d.ts.map +1 -1
  199. package/dist/utils/mcp-format.js +7 -4
  200. package/dist/utils/mcp-format.js.map +1 -1
  201. package/dist/utils/mdx.d.ts.map +1 -1
  202. package/dist/utils/paths.d.ts +1 -1
  203. package/dist/utils/paths.d.ts.map +1 -1
  204. package/dist/utils/paths.js +3 -1
  205. package/dist/utils/paths.js.map +1 -1
  206. package/dist/utils/search.d.ts.map +1 -1
  207. package/dist/utils/security.d.ts.map +1 -1
  208. package/dist/utils/security.js.map +1 -1
  209. package/dist/xulux/catalog-client.d.ts +14 -0
  210. package/dist/xulux/catalog-client.d.ts.map +1 -0
  211. package/dist/xulux/catalog-client.js +67 -0
  212. package/dist/xulux/catalog-client.js.map +1 -0
  213. package/dist/xulux/fallback-catalog.d.ts +7 -0
  214. package/dist/xulux/fallback-catalog.d.ts.map +1 -0
  215. package/dist/xulux/fallback-catalog.js +47 -0
  216. package/dist/xulux/fallback-catalog.js.map +1 -0
  217. package/dist/xulux/fetch-sandbox.d.ts +5 -0
  218. package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
  219. package/dist/xulux/fetch-sandbox.js +40 -0
  220. package/dist/xulux/fetch-sandbox.js.map +1 -0
  221. package/dist/xulux/template-service.d.ts +84 -0
  222. package/dist/xulux/template-service.d.ts.map +1 -0
  223. package/dist/xulux/template-service.js +223 -0
  224. package/dist/xulux/template-service.js.map +1 -0
  225. package/dist/xulux/types.d.ts +55 -0
  226. package/dist/xulux/types.d.ts.map +1 -0
  227. package/dist/xulux/types.js +6 -0
  228. package/dist/xulux/types.js.map +1 -0
  229. package/package.json +7 -6
  230. package/src/index.ts +55 -2
  231. package/src/prompts/xulux-playground.ts +36 -0
  232. package/src/tools/docs.ts +27 -5
  233. package/src/tools/examples.ts +17 -12
  234. package/src/tools/resources.ts +1 -4
  235. package/src/tools/search.ts +2 -2
  236. package/src/tools/tests/completions.test.ts +40 -26
  237. package/src/tools/tests/docs.test.ts +20 -0
  238. package/src/tools/tests/examples.test.ts +5 -5
  239. package/src/tools/tests/integration.test.ts +3 -4
  240. package/src/tools/tests/listings-cache.test.ts +19 -0
  241. package/src/tools/tests/mcp-protocol.test.ts +173 -108
  242. package/src/tools/tests/mcp-test-client.ts +111 -0
  243. package/src/tools/tests/resources.test.ts +97 -66
  244. package/src/tools/tests/test-setup.ts +8 -0
  245. package/src/tools/tests/xulux-templates.test.ts +262 -0
  246. package/src/tools/xulux-templates.ts +141 -0
  247. package/src/utils/cache.ts +20 -0
  248. package/src/utils/mcp-format.ts +8 -6
  249. package/src/utils/paths.ts +4 -1
  250. package/src/utils/tests/cache.test.ts +51 -0
  251. package/src/utils/tests/mcp-format.test.ts +22 -0
  252. package/src/utils/tests/security.test.ts +1 -1
  253. package/src/xulux/catalog-client.ts +105 -0
  254. package/src/xulux/fallback-catalog.ts +63 -0
  255. package/src/xulux/fetch-sandbox.ts +56 -0
  256. package/src/xulux/template-service.ts +406 -0
  257. package/src/xulux/types.ts +60 -0
  258. package/.docs/raw/docs/(reference)/api-reference/hooks/utilities.mdx +0 -464
@@ -125,7 +125,7 @@ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
125
125
  import toolkit from "./toolkit";
126
126
 
127
127
  export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
128
- const runtime = useChatRuntime({ api: "/api/chat" });
128
+ const runtime = useChatRuntime();
129
129
  const aui = useAui({ tools: Tools({ toolkit }) });
130
130
 
131
131
  return (
@@ -136,6 +136,8 @@ export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
136
136
  }
137
137
  ```
138
138
 
139
+ `useChatRuntime()` targets `/api/chat` by default. To point at a different endpoint or customize requests, see [Custom transport](/docs/runtimes/ai-sdk/v7#custom-transport).
140
+
139
141
  </Step>
140
142
  <Step>
141
143
 
@@ -461,6 +463,10 @@ export default defineToolkit({
461
463
  });
462
464
  ```
463
465
 
466
+ When two MCP servers expose the same tool name, use `{ server, prefix }` on an
467
+ entry so the model sees distinct names such as `docs_search` and
468
+ `github_search`.
469
+
464
470
  See [MCP](/docs/tools/mcp) for the full server-side and user-managed MCP flows.
465
471
 
466
472
  ## Advanced
@@ -173,6 +173,8 @@ action registry to let interactive nodes call back into your app. This path uses
173
173
  the flat `{ "$type": ... }` node shape; the model puts an `$action` object on
174
174
  the node, and its `type` is matched against your registered handlers.
175
175
 
176
+ Browse the [gallery](/gallery) to see every default vocabulary component rendered live, next to its IR JSON, generated React code, and a usage snippet.
177
+
176
178
  ```tsx
177
179
  import {
178
180
  JSONGenerativeUI,
@@ -200,8 +202,7 @@ const generative = new JSONGenerativeUI({
200
202
  }
201
203
  ```
202
204
 
203
- `Select`, `Input`, and `DatePicker` add the user's value as `$input` when they
204
- fire the action. Unknown action types are ignored and warn in development.
205
+ `Select`, `Input`, `DatePicker`, `Checkbox`, and `RadioGroup` add the user's value as `$input` when they fire the action; `Form` and a `Card` with `asForm` set add an object keyed by each control's `name` instead. On a `Card` there is no Card-level `$action`: the collected object is dispatched through `confirm.$action`, while `cancel.$action` always fires without `$input`. Unknown action types are ignored and warn in development.
205
206
 
206
207
  ## Streaming
207
208
 
@@ -249,3 +250,60 @@ Tool-call UI is great when the agent already invoked a known tool. Generative
249
250
  UI flips it: the agent _composes_ UI from a vocabulary you ship. Useful for
250
251
  dashboards, status panels, and structured layouts — not for collecting user
251
252
  input (use Tool UI for that).
253
+
254
+ ## Slack Block Kit
255
+
256
+ `toSlackBlocks` from `@assistant-ui/react-generative-ui/slack` converts a
257
+ generative-UI tree into Slack's Block Kit JSON. It is pure and React-free, so
258
+ it runs equally well in a server action, a queue worker, or a webhook
259
+ handler. Components the converter doesn't recognize are skipped and reported
260
+ as warnings instead of throwing, and content that exceeds Slack's published
261
+ size and count budgets is clamped or downgraded to a simpler block rather
262
+ than producing an invalid payload.
263
+
264
+ ```tsx
265
+ import { WebClient } from "@slack/web-api";
266
+ import { toSlackBlocks } from "@assistant-ui/react-generative-ui/slack";
267
+
268
+ const slack = new WebClient(process.env.SLACK_BOT_TOKEN);
269
+
270
+ const { blocks } = toSlackBlocks({
271
+ $type: "Card",
272
+ title: "Order #48213",
273
+ children: [{ $type: "Text", value: "Shipped, arriving Thursday." }],
274
+ });
275
+
276
+ await slack.chat.postMessage({
277
+ channel: "#orders",
278
+ blocks,
279
+ });
280
+ ```
281
+
282
+ Slack posts interactive elements back as a `block_actions` payload.
283
+ `decodeBlockAction` takes one entry from that payload's `actions` array and
284
+ decodes it back into the `$action` shape your tree dispatched, with the
285
+ user's runtime selection (a picked option, a typed value) carried under
286
+ `$input`. Receiving the webhook, verifying its signature, and routing the
287
+ decoded action to your handler stay the host app's responsibility; the
288
+ converter only speaks JSON in and JSON out.
289
+
290
+ The inverse direction is `fromSlackBlocks`: it maps a Block Kit payload back into vocabulary nodes, back-mapping each element's `action_id` to `$action.type` and reparsing serialized payload values. The round trip is faithful on the plain building blocks (text, images, facts, controls, tables, simple cards) and documented-lossy elsewhere: context elements all return as `Caption`, button styles beyond `primary` and `danger` are dropped, an alert's title and description come back as one description, and card layouts flatten to the fields the `card` block carries.
291
+
292
+ A few conversion caveats worth knowing:
293
+
294
+ - `Alert` has no message-surface equivalent upstream (Slack only supports it
295
+ in modals), so messages get a context block plus section block fallback
296
+ instead.
297
+ - `Card` and `Carousel` have tight text budgets; a card whose content
298
+ overflows its budget falls back to plain blocks rather than the card
299
+ layout.
300
+ - `Chart` has no Slack Block Kit mapping and is replaced by a note block.
301
+ - The Block Kit Builder deep link format
302
+ (`https://app.slack.com/block-kit-builder/#<payload>`) is an observed
303
+ convention, not an officially documented API.
304
+
305
+ ## Microsoft Teams
306
+
307
+ The same tree converts to an Adaptive Card with `toAdaptiveCard` from `@assistant-ui/react-generative-ui/teams`: pure, React-free, pinned to Adaptive Cards 1.5 (the Teams desktop ceiling; mobile clients cap at 1.2), and total in the same way as the Slack converter, so unknown components degrade with warnings instead of failing. Interactive components encode their `$action` inside the submit payload's reserved `aui` key, and `decodeSubmitData` splits a bot's incoming `activity.value` back into the `$action` shape with the card's input values under `$input`. A root `Carousel` is an activity-level construct on Teams, so `toTeamsAttachments` returns up to ten card attachments with `attachmentLayout: "carousel"` instead of one card.
308
+
309
+ Caveats mirror the platform: Teams ignores positive and destructive action styling, TextBlock markdown is a subset (no headings, tables, or images), `Divider` and `Spacer` become `separator` and `spacing` properties on the following element, and `Chart` has no Teams mapping and is replaced by a note.
@@ -298,7 +298,7 @@ function MyRuntimeProvider({ children }) {
298
298
 
299
299
  useEffect(() => {
300
300
  // Set up persistence adapter
301
- aui.interactables().setPersistenceAdapter({
301
+ aui.interactables.setPersistenceAdapter({
302
302
  save: async (state) => {
303
303
  localStorage.setItem("interactables", JSON.stringify(state));
304
304
  },
@@ -307,7 +307,7 @@ function MyRuntimeProvider({ children }) {
307
307
  // Restore saved state on mount
308
308
  const saved = localStorage.getItem("interactables");
309
309
  if (saved) {
310
- aui.interactables().importState(JSON.parse(saved));
310
+ aui.interactables.importState(JSON.parse(saved));
311
311
  }
312
312
  }, [aui]);
313
313
 
@@ -334,10 +334,10 @@ State changes are automatically debounced (500ms) before saving. When a componen
334
334
  For custom persistence strategies, use `exportState` and `importState` directly:
335
335
 
336
336
  ```tsx
337
- const snapshot = aui.interactables().exportState();
337
+ const snapshot = aui.interactables.exportState();
338
338
  // => { "note-1": { name: "note", state: { title: "Hello" } }, ... }
339
339
 
340
- aui.interactables().importState(snapshot);
340
+ aui.interactables.importState(snapshot);
341
341
  // Imported state is picked up when components next register
342
342
  ```
343
343
 
@@ -618,7 +618,7 @@ function MyRuntimeProvider({ children }) {
618
618
 
619
619
  `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
620
 
621
- For dynamic setups (an adapter that depends on auth), call `aui.interactables().setPersistenceAdapter(adapter)` imperatively instead.
621
+ For dynamic setups (an adapter that depends on auth), call `aui.interactables.setPersistenceAdapter(adapter)` imperatively instead.
622
622
 
623
623
  ### Sync Status
624
624
 
@@ -640,10 +640,10 @@ State changes are automatically debounced (500ms) before saving. When the owning
640
640
  For custom persistence strategies, use `exportState` and `importState` directly:
641
641
 
642
642
  ```tsx
643
- const snapshot = aui.interactables().exportState();
643
+ const snapshot = aui.interactables.exportState();
644
644
  // => { "note-1": { name: "note", state: { title: "Hello" } }, ... }
645
645
 
646
- aui.interactables().importState(snapshot);
646
+ aui.interactables.importState(snapshot);
647
647
  // Imported state is picked up when components next register
648
648
  ```
649
649
 
@@ -69,20 +69,32 @@ The route accepts `POST` requests with `{ method, params }` JSON bodies. Dispatc
69
69
  // app/api/mcp-apps/route.ts
70
70
  import { createMCPClient } from "@ai-sdk/mcp";
71
71
 
72
- let clientPromise: ReturnType<typeof createMCPClient> | undefined;
73
- const getClient = () => {
74
- clientPromise ??= createMCPClient({
75
- transport: { type: "sse", url: process.env.MCP_SERVER_URL! },
76
- }).catch((error) => {
77
- clientPromise = undefined;
78
- throw error;
79
- });
72
+ const serverUrls = new Map([
73
+ ["search", process.env.SEARCH_MCP_SERVER_URL!],
74
+ ["calendar", process.env.CALENDAR_MCP_SERVER_URL!],
75
+ ]);
76
+ const fallbackServerUrl = process.env.MCP_SERVER_URL!;
77
+ const clientPromises = new Map<string, ReturnType<typeof createMCPClient>>();
78
+
79
+ const getClient = (serverId?: string) => {
80
+ const url = serverId ? serverUrls.get(serverId) : fallbackServerUrl;
81
+ if (!url) throw new Error(`Unknown MCP server: ${serverId}`);
82
+ let clientPromise = clientPromises.get(url);
83
+ if (!clientPromise) {
84
+ clientPromise = createMCPClient({
85
+ transport: { type: "sse", url },
86
+ }).catch((error) => {
87
+ clientPromises.delete(url);
88
+ throw error;
89
+ });
90
+ clientPromises.set(url, clientPromise);
91
+ }
80
92
  return clientPromise;
81
93
  };
82
94
 
83
95
  export async function POST(req: Request) {
84
96
  const { method, params } = await req.json();
85
- const client = await getClient();
97
+ const client = await getClient(params?.serverId);
86
98
 
87
99
  switch (method) {
88
100
  case "mcp-apps/read-resource": {
@@ -109,8 +121,10 @@ export async function POST(req: Request) {
109
121
  }
110
122
  case "resources/read":
111
123
  return Response.json(await client.readResource({ uri: params.uri }));
112
- case "resources/list":
113
- return Response.json(await client.listResources(params));
124
+ case "resources/list": {
125
+ const { serverId: _, ...listParams } = params ?? {};
126
+ return Response.json(await client.listResources(listParams));
127
+ }
114
128
  default:
115
129
  return Response.json({ error: "Unsupported method" }, { status: 400 });
116
130
  }
@@ -119,6 +133,10 @@ export async function POST(req: Request) {
119
133
 
120
134
  The renderer POSTs four method names: `mcp-apps/read-resource`, `tools/call`, `resources/read`, `resources/list`. Reject anything else server-side and apply your own auth / rate limiting in the route.
121
135
 
136
+ ### Multiple MCP servers
137
+
138
+ When a tool part carries `mcp.app.serverId`, the renderer forwards it to the host operations as `params.serverId` so the route can select the MCP client that owns the resource or tool. The agent stack emits this routable identity in part metadata. For `@ag-ui/mcp-apps-middleware`, assistant-ui uses its configured `serverId`, falling back to `serverHash` when `serverId` is absent or empty. Match the route's map keys to whichever identity your setup emits: configure an explicit `serverId` on each middleware server, or key the map by the emitted hashes. Omitting `serverId` preserves the single-server behavior, as shown by the fallback client in the route example.
139
+
122
140
  Per-name `setToolUI` registrations always win over the MCP fallback — you can still customize specific tools.
123
141
 
124
142
  ## AI SDK integration
@@ -146,7 +164,7 @@ const result = streamText({
146
164
 
147
165
  [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.
148
166
 
149
- 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`:
167
+ 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`:
150
168
 
151
169
  ```ts
152
170
  import type { Tool } from "ai";
@@ -166,7 +184,13 @@ const withTemplateUri = (tool: Tool, name: string): Tool => {
166
184
  ...tool,
167
185
  execute: async (args, options) => {
168
186
  const result = (await exec(args, options)) as { _meta?: Record<string, unknown> };
169
- return { ...result, _meta: { ...result._meta, "ui/resourceUri": uri } };
187
+ return {
188
+ ...result,
189
+ _meta: {
190
+ ...result._meta,
191
+ ui: { ...(result._meta?.["ui"] as Record<string, unknown>), resourceUri: uri },
192
+ },
193
+ };
170
194
  },
171
195
  } satisfies Tool;
172
196
  };
@@ -186,6 +210,59 @@ Your `mcp-apps/read-resource` handler reads the `ui://` resource as in the route
186
210
 
187
211
  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.
188
212
 
213
+ ## AG-UI integration
214
+
215
+ 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:
216
+
217
+ ```json
218
+ {
219
+ "type": "ACTIVITY_SNAPSHOT",
220
+ "activityType": "mcp-apps",
221
+ "content": {
222
+ "toolCallId": "call-1",
223
+ "resourceUri": "ui://maps/result.html",
224
+ "serverId": "maps",
225
+ "result": {
226
+ "content": [{ "type": "text", "text": "Map ready" }],
227
+ "structuredContent": { "center": [37.77, -122.42] },
228
+ "_meta": { "initialView": "street" },
229
+ "isError": false
230
+ }
231
+ }
232
+ }
233
+ ```
234
+
235
+ `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.
236
+
237
+ Send the normal `TOOL_CALL_RESULT` with a concise, model-visible summary:
238
+
239
+ ```json
240
+ {
241
+ "type": "TOOL_CALL_RESULT",
242
+ "toolCallId": "call-1",
243
+ "content": "Map ready"
244
+ }
245
+ ```
246
+
247
+ 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" }]`).
248
+
249
+ Alternatively, AG-UI servers can place MCP host fields directly on `TOOL_CALL_RESULT`:
250
+
251
+ ```json
252
+ {
253
+ "type": "TOOL_CALL_RESULT",
254
+ "toolCallId": "call-1",
255
+ "content": "Map ready",
256
+ "structuredContent": { "center": [37.77, -122.42] },
257
+ "_meta": { "initialView": "street" },
258
+ "isError": false
259
+ }
260
+ ```
261
+
262
+ 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.
263
+
264
+ 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`.
265
+
189
266
  ## Bridge protocol
190
267
 
191
268
  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.
@@ -81,7 +81,8 @@ const mcpClient = await createMCPClient({
81
81
 
82
82
  In a generative toolkit, spread `defineMcpToolkit({ ... })` with one entry per
83
83
  MCP server. The entry key names the server connection; the MCP server publishes
84
- the actual tool names.
84
+ the actual tool names. Use a readable key because it appears in connection,
85
+ tool-listing, and close errors for debugging.
85
86
 
86
87
  ```tsx title="app/toolkit.tsx"
87
88
  "use generative";
@@ -99,6 +100,62 @@ export default defineToolkit({
99
100
  });
100
101
  ```
101
102
 
103
+ Use `{ server, disabled }` when a whole MCP server should stay configured but
104
+ not expose tools for the current request, such as missing credentials, feature
105
+ flags, or plan gating:
106
+
107
+ ```tsx
108
+ defineMcpToolkit({
109
+ docs: {
110
+ server: {
111
+ type: "http",
112
+ url: process.env.DOCS_MCP_URL!,
113
+ },
114
+ disabled: !process.env.DOCS_MCP_URL,
115
+ },
116
+ });
117
+ ```
118
+
119
+ Use `tools` when the server should stay enabled but specific MCP tools should
120
+ be hidden from the model:
121
+
122
+ ```tsx
123
+ defineMcpToolkit({
124
+ docs: {
125
+ server: {
126
+ type: "http",
127
+ url: process.env.DOCS_MCP_URL!,
128
+ },
129
+ tools: {
130
+ deleteDocument: {
131
+ disabled: !userCanDelete,
132
+ },
133
+ },
134
+ },
135
+ });
136
+ ```
137
+
138
+ If multiple MCP servers expose the same tool name, wrap the entry with
139
+ `{ server, prefix }` to give each server's tools distinct model-visible names:
140
+
141
+ ```tsx
142
+ export default defineToolkit({
143
+ ...defineMcpToolkit({
144
+ docs: {
145
+ server: { type: "http", url: "https://docs.example.com/mcp" },
146
+ prefix: "docs_",
147
+ },
148
+ github: {
149
+ server: { type: "http", url: "https://github.example.com/mcp" },
150
+ prefix: "github_",
151
+ },
152
+ }),
153
+ });
154
+ ```
155
+
156
+ If both servers publish `search`, the model receives `docs_search` and
157
+ `github_search` instead of an ambiguous duplicate.
158
+
102
159
  Use `AISDKToolkit` in the route. It opens the MCP clients, merges their tools
103
160
  with the rest of your toolkit, and closes them when you call `close()`:
104
161
 
@@ -319,7 +376,7 @@ import type { ReactNode } from "react";
319
376
  import { toolkit } from "./GitHubIssueToolUI";
320
377
 
321
378
  export function MyRuntimeProvider({ children }: { children: ReactNode }) {
322
- const runtime = useChatRuntime({ api: "/api/chat" });
379
+ const runtime = useChatRuntime();
323
380
  const aui = useAui({ tools: Tools({ toolkit }) });
324
381
 
325
382
  return (
@@ -330,6 +387,46 @@ export function MyRuntimeProvider({ children }: { children: ReactNode }) {
330
387
  }
331
388
  ```
332
389
 
390
+ `useChatRuntime()` targets `/api/chat` by default. To point at a different endpoint or customize requests, see [Custom transport](/docs/runtimes/ai-sdk/v7#custom-transport).
391
+
392
+ </Step>
393
+ <Step>
394
+
395
+ ### Require approval before an MCP tool runs
396
+
397
+ MCP tools execute on the server, so approval is a server-side tool gate, not a `humanTool()` result. Gate the call with AI SDK v7's call-level `toolApproval` option, keyed by the tool's model-visible name. The tool name stays the same, so your custom renderer or the default `ToolFallback` receives `approval` and `respondToApproval` like any other backend tool:
398
+
399
+ ```ts title="app/api/chat/route.ts"
400
+ const tools = await mcpClient.tools();
401
+
402
+ const result = streamText({
403
+ model: openai("gpt-5.4-mini"),
404
+ messages: await convertToModelMessages(messages),
405
+ tools,
406
+ toolApproval: {
407
+ github_delete_repository: "user-approval",
408
+ },
409
+ onFinish: async () => {
410
+ await mcpClient.close();
411
+ },
412
+ });
413
+ ```
414
+
415
+ With `AISDKToolkit`, pass the same `toolApproval` option alongside the tools returned by `await aiToolkit.tools(...)`; key it by the prefixed name when the entry sets one.
416
+
417
+ On the client, let the AI SDK send the recorded approval decision back to the
418
+ route:
419
+
420
+ ```tsx title="app/components/RuntimeProvider.tsx"
421
+ import { lastAssistantMessageIsCompleteWithApprovalResponses } from "ai";
422
+
423
+ const runtime = useChatRuntime({
424
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
425
+ });
426
+ ```
427
+
428
+ Use this pattern for backend-owned actions such as deleting, writing, deploying, or calling privileged MCP tools. Use `humanTool()` only when the user supplies the tool result itself. For custom approval UIs, see [Server-side approval gates](/docs/tools/tool-ui#server-side-approval-gates); for the full wire setup, see [Server-side tool approval](/docs/runtimes/ai-sdk/v7#server-side-tool-approval).
429
+
333
430
  </Step>
334
431
  <Step>
335
432
 
@@ -357,7 +454,7 @@ Start the app and trigger a tool call (e.g., ask the assistant to do something t
357
454
  <Card
358
455
  title="AI SDK runtime"
359
456
  description="The runtime that ferries MCP tool calls to the chat UI."
360
- href="/docs/runtimes/ai-sdk/v6"
457
+ href="/docs/runtimes/ai-sdk/v7"
361
458
  />
362
459
  <Card
363
460
  title="Tools and tool UI"
@@ -74,7 +74,7 @@ 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
- const runtime = useChatRuntime({ api: "/api/chat" });
77
+ const runtime = useChatRuntime();
78
78
  const aui = useAui({ tools: Tools({ toolkit }) });
79
79
  return (
80
80
  <AssistantRuntimeProvider aui={aui} runtime={runtime}>
@@ -84,6 +84,8 @@ function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
84
84
  }
85
85
  ```
86
86
 
87
+ `useChatRuntime()` targets `/api/chat` by default. To point at a different endpoint or customize requests, see [Custom transport](/docs/runtimes/ai-sdk/v7#custom-transport).
88
+
87
89
  <Callout type="tip">
88
90
  Frontend toolkit entries can be passed to your backend using the
89
91
  `frontendTools` utility.
@@ -537,7 +539,7 @@ export default defineToolkit({
537
539
 
538
540
  ### Server-side approval gates
539
541
 
540
- Some runtimes (notably AI SDK v6's `needsApproval` tools) pause on the server and emit an approval request that the client must acknowledge before the tool runs. assistant-ui surfaces this on the tool part as `approval` and exposes `respondToApproval({ approved, reason? })` on the renderer:
542
+ Some runtimes (notably AI SDK v7's `toolApproval`-gated tools) pause on the server and emit an approval request that the client must acknowledge before the tool runs. assistant-ui surfaces this on the tool part as `approval` and exposes `respondToApproval({ approved, reason? })` on the renderer:
541
543
 
542
544
  ```tsx
543
545
  const toolkit = defineToolkit({
@@ -581,9 +583,9 @@ const toolkit = defineToolkit({
581
583
 
582
584
  `approval.isAutomatic` is `true` when the runtime granted the decision from a server-side policy rather than the user; render a "auto-approved" badge instead of buttons in that case.
583
585
 
584
- Approval gates require a runtime that implements them: the AI SDK v6 runtime emits them for `needsApproval` tools, and `LocalRuntime` supports gates emitted by your `ChatModelAdapter`; see [LocalRuntime approval gates](/docs/runtimes/custom/local-runtime#approval-gates). For tools where the user supplies the result itself, use `unstable_humanToolNames` with `addResult` instead; see [human-in-the-loop tools](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools).
586
+ Approval gates require a runtime that implements them: the AI SDK v7 runtime emits them for `toolApproval`-gated tools, and `LocalRuntime` supports gates emitted by your `ChatModelAdapter`; see [LocalRuntime approval gates](/docs/runtimes/custom/local-runtime#approval-gates). For tools where the user supplies the result itself, use `unstable_humanToolNames` with `addResult` instead; see [human-in-the-loop tools](/docs/runtimes/custom/local-runtime#human-in-the-loop-tools).
585
587
 
586
- For the wire-side setup (`needsApproval`, `sendAutomaticallyWhen`), see [AI SDK v6 server-side tool approval](/docs/runtimes/ai-sdk/v6#server-side-tool-approval).
588
+ For the wire-side setup (`needsApproval`, `sendAutomaticallyWhen`), see [AI SDK v7 server-side tool approval](/docs/runtimes/ai-sdk/v7#server-side-tool-approval).
587
589
 
588
590
  ### Approval options
589
591
 
@@ -114,7 +114,7 @@ Pass children to override the trigger:
114
114
  </McpConfigDialog>
115
115
  ```
116
116
 
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:
117
+ **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
118
 
119
119
  ```tsx title="app/mcp/page.tsx"
120
120
  "use client";
@@ -179,7 +179,7 @@ The iteration primitives wrap each item in an `McpServerByIdProvider`, so the ne
179
179
  </McpAddFormPrimitive.Root>
180
180
  ```
181
181
 
182
- The form owns its own draft state and submits via `aui.mcp().addCustomServer(...)`. Pass a render function to `AuthFields` to fully customize it.
182
+ The form owns its own draft state and submits via `aui.mcp.addCustomServer(...)`. Pass a render function to `AuthFields` to fully customize it.
183
183
 
184
184
  </Step>
185
185
  <Step>
@@ -221,14 +221,13 @@ import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
221
221
 
222
222
  export function Chat() {
223
223
  const runtime = useChatRuntime({
224
- api: "/api/chat",
225
224
  sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
226
225
  });
227
226
  /* … */
228
227
  }
229
228
  ```
230
229
 
231
- `sendAutomaticallyWhen` sends completed frontend tool results back to the server so the model can continue after a tool call.
230
+ `sendAutomaticallyWhen` sends completed frontend tool results back to the server so the model can continue after a tool call. `useChatRuntime()` targets `/api/chat` by default; to point at a different endpoint, see [Custom transport](/docs/runtimes/ai-sdk/v7#custom-transport).
232
231
 
233
232
  Tool names are prefixed `serverId__toolName` to avoid collisions across connected servers. The toolkit re-registers whenever a server connects / disconnects or its tool list changes.
234
233
 
@@ -237,12 +236,56 @@ If no chat runtime is mounted, `McpManagerResource` brings its own minimal `mode
237
236
  ```ts
238
237
  // In an event handler, never in render.
239
238
  const aui = useAui();
240
- const out = await aui.mcp().server({ id: "linear" }).callTool("search", { q });
239
+ const out = await aui.mcp.server({ id: "linear" }).callTool("search", { q });
241
240
  ```
242
241
 
243
242
  </Step>
244
243
  </Steps>
245
244
 
245
+ ## Form elicitation
246
+
247
+ 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:
248
+
249
+ 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.
250
+
251
+ ```tsx
252
+ import { McpElicitationPrimitive } from "@assistant-ui/react-mcp";
253
+
254
+ <McpElicitationPrimitive.Items>
255
+ {() => (
256
+ <McpElicitationPrimitive.Root>
257
+ <McpElicitationPrimitive.Message />
258
+ <McpElicitationPrimitive.Error />
259
+ <McpElicitationPrimitive.Fields>
260
+ {({ name, schema, value, setValue }) =>
261
+ (schema as { type?: string } | undefined)?.type === "boolean" ? (
262
+ <label>
263
+ {name}
264
+ <input
265
+ type="checkbox"
266
+ checked={value === true}
267
+ onChange={(event) => setValue(event.target.checked)}
268
+ />
269
+ </label>
270
+ ) : (
271
+ <input
272
+ name={name}
273
+ value={typeof value === "string" ? value : ""}
274
+ onChange={(event) => setValue(event.target.value)}
275
+ />
276
+ )
277
+ }
278
+ </McpElicitationPrimitive.Fields>
279
+ <McpElicitationPrimitive.Accept>Submit</McpElicitationPrimitive.Accept>
280
+ <McpElicitationPrimitive.Decline>Decline</McpElicitationPrimitive.Decline>
281
+ <McpElicitationPrimitive.Cancel>Cancel</McpElicitationPrimitive.Cancel>
282
+ </McpElicitationPrimitive.Root>
283
+ )}
284
+ </McpElicitationPrimitive.Items>
285
+ ```
286
+
287
+ Client-side validation errors keep the elicitation pending so the user can correct the form, and `McpElicitationPrimitive.Error` renders the resulting message.
288
+
246
289
  ## Storage
247
290
 
248
291
  All persisted state — custom server records, OAuth tokens, PKCE verifiers, DCR client info — goes through a single `MCPStorage` resource. Three built-ins:
@@ -313,9 +356,32 @@ Imperative methods: `useAui` + resolve in a callback (never during render):
313
356
  const aui = useAui();
314
357
 
315
358
  // inside an event handler:
316
- await aui.mcp().addCustomServer({ name, url, auth: { type: "bearer", token } });
317
- await aui.mcp().server({ id }).connect();
318
- await aui.mcp().server({ id }).callTool("echo", { text: "hi" });
359
+ await aui.mcp.addCustomServer({ name, url, auth: { type: "bearer", token } });
360
+ await aui.mcp.server({ id }).connect();
361
+ await aui.mcp.server({ id }).callTool("echo", { text: "hi" });
362
+
363
+ // Build a paginated resource browser/preview UI.
364
+ type ResourcePage = {
365
+ resources: Array<{ uri: string; name?: string }>;
366
+ nextCursor?: string;
367
+ };
368
+
369
+ const server = aui.mcp.server({ id });
370
+ const resources: ResourcePage["resources"] = [];
371
+ let nextCursor: string | undefined;
372
+
373
+ do {
374
+ const page = (await (nextCursor === undefined
375
+ ? server.listResources()
376
+ : server.listResources({ cursor: nextCursor }))) as ResourcePage;
377
+ resources.push(...page.resources);
378
+ nextCursor = page.nextCursor;
379
+ } while (nextCursor !== undefined);
380
+
381
+ const firstResource = resources[0];
382
+ if (firstResource) {
383
+ const preview = await server.readResource(firstResource.uri);
384
+ }
319
385
  ```
320
386
 
321
387
  ## v1 scope
@@ -323,13 +389,15 @@ await aui.mcp().server({ id }).callTool("echo", { text: "hi" });
323
389
  What ships:
324
390
 
325
391
  - Tool listing and invocation, auto-registered as frontend tools
392
+ - Resource listing and reads for app-built browsers or preview panes
393
+ - Form-mode elicitation with app-composed fields and response actions
326
394
  - OAuth (PKCE + DCR), bearer, none
327
395
  - StreamableHTTP transport
328
396
  - Manual connect/disconnect
329
397
 
330
398
  What's deferred:
331
399
 
332
- - Resources, prompts, sampling
400
+ - Prompts, sampling
333
401
  - Auto-reconnect with backoff
334
402
  - Per-tool enable/disable persistence
335
403
  - Per-tool consent prompts