@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.
- package/.docs/organized/code-examples/waterfall.md +11 -12
- package/.docs/organized/code-examples/with-a2a.md +18 -13
- package/.docs/organized/code-examples/with-ag-ui.md +19 -14
- package/.docs/organized/code-examples/{with-ai-sdk-v6.md → with-ai-sdk-v7.md} +34 -23
- package/.docs/organized/code-examples/with-artifacts.md +473 -141
- package/.docs/organized/code-examples/with-assistant-transport.md +17 -10
- package/.docs/organized/code-examples/with-browser-extension.md +17 -10
- package/.docs/organized/code-examples/with-chain-of-thought.md +21 -14
- package/.docs/organized/code-examples/with-cloud-standalone.md +12 -11
- package/.docs/organized/code-examples/with-cloud.md +20 -15
- package/.docs/organized/code-examples/with-custom-thread-list.md +19 -12
- package/.docs/organized/code-examples/with-elevenlabs-conversational.md +22 -15
- package/.docs/organized/code-examples/with-elevenlabs-scribe.md +22 -15
- package/.docs/organized/code-examples/with-eve.md +18 -11
- package/.docs/organized/code-examples/with-expo.md +35 -52
- package/.docs/organized/code-examples/with-external-store.md +18 -13
- package/.docs/organized/code-examples/with-ffmpeg.md +20 -15
- package/.docs/organized/code-examples/with-generative-ui.md +22 -17
- package/.docs/organized/code-examples/with-google-adk.md +18 -11
- package/.docs/organized/code-examples/with-heat-graph.md +10 -11
- package/.docs/organized/code-examples/with-image-generation.md +19 -12
- package/.docs/organized/code-examples/with-interactables.md +21 -17
- package/.docs/organized/code-examples/with-langchain.md +19 -12
- package/.docs/organized/code-examples/with-langgraph.md +19 -12
- package/.docs/organized/code-examples/with-livekit.md +23 -16
- package/.docs/organized/code-examples/with-mcp.md +46 -28
- package/.docs/organized/code-examples/with-opencode.md +25 -23
- package/.docs/organized/code-examples/with-pi.md +54 -19
- package/.docs/organized/code-examples/with-react-hook-form.md +21 -16
- package/.docs/organized/code-examples/with-react-ink-web.md +9 -9
- package/.docs/organized/code-examples/with-react-ink.md +4 -4
- package/.docs/organized/code-examples/with-react-router.md +22 -17
- package/.docs/organized/code-examples/with-resumable-stream.md +21 -14
- package/.docs/organized/code-examples/with-store.md +10 -11
- package/.docs/organized/code-examples/with-tanstack.md +19 -13
- package/.docs/organized/code-examples/with-tap-runtime.md +18 -13
- package/.docs/organized/code-examples/with-virtualized-thread.md +19 -14
- package/.docs/raw/docs/(docs)/base-ui.mdx +39 -0
- package/.docs/raw/docs/(docs)/cli.mdx +19 -1
- package/.docs/raw/docs/(docs)/devtools.mdx +7 -2
- package/.docs/raw/docs/(docs)/installation.mdx +15 -1
- package/.docs/raw/docs/(docs)/rtl.mdx +2 -4
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/a2ui.mdx +40 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/actions.mdx +56 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/components.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/index.mdx +22 -1
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/json-generative-ui.mdx +42 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/rendering.mdx +53 -2
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/slack.mdx +81 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/teams.mdx +86 -0
- package/.docs/raw/docs/(reference)/api-reference/generative-ui/tokens.mdx +62 -0
- package/.docs/raw/docs/(reference)/api-reference/hooks/primitives.mdx +19 -420
- package/.docs/raw/docs/(reference)/api-reference/hooks/state.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/index.mdx +4 -1
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-ai-sdk.mdx +25 -2
- package/.docs/raw/docs/(reference)/api-reference/integrations/react-data-stream.mdx +37 -0
- package/.docs/raw/docs/(reference)/api-reference/model-context/context.mdx +1 -9
- package/.docs/raw/docs/(reference)/api-reference/overview.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/assistant-if.mdx +14 -31
- package/.docs/raw/docs/(reference)/api-reference/primitives/composition.mdx +1 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/index.mdx +3 -0
- package/.docs/raw/docs/(reference)/api-reference/primitives/selection-toolbar.mdx +2 -0
- package/.docs/raw/docs/(reference)/api-reference/tools/status.mdx +0 -2
- package/.docs/raw/docs/(reference)/api-reference/tools/toolkits.mdx +12 -9
- package/.docs/raw/docs/(reference)/api-reference/utilities/miscellaneous.mdx +4 -0
- package/.docs/raw/docs/cloud/ai-sdk-assistant-ui.mdx +7 -1
- package/.docs/raw/docs/cloud/langgraph.mdx +4 -2
- package/.docs/raw/docs/copilots/model-context.mdx +1 -1
- package/.docs/raw/docs/copilots/motivation.mdx +1 -1
- package/.docs/raw/docs/guides/attachments.mdx +3 -3
- package/.docs/raw/docs/guides/branching.mdx +2 -2
- package/.docs/raw/docs/guides/chatgpt-subscription.mdx +108 -0
- package/.docs/raw/docs/guides/context-api.mdx +89 -111
- package/.docs/raw/docs/guides/dictation.mdx +185 -257
- package/.docs/raw/docs/guides/editing.mdx +5 -5
- package/.docs/raw/docs/guides/electron.mdx +369 -0
- package/.docs/raw/docs/guides/index.mdx +20 -0
- package/.docs/raw/docs/guides/mentions.mdx +31 -3
- package/.docs/raw/docs/guides/quoting.mdx +3 -3
- package/.docs/raw/docs/guides/resumable-stream-deployment.mdx +2 -2
- package/.docs/raw/docs/guides/resumable-streams.mdx +12 -1
- package/.docs/raw/docs/guides/speech.mdx +47 -29
- package/.docs/raw/docs/guides/suggestions.mdx +70 -1
- package/.docs/raw/docs/guides/voice.mdx +197 -267
- package/.docs/raw/docs/ink/hooks.mdx +3 -3
- package/.docs/raw/docs/ink/primitives.mdx +49 -8
- package/.docs/raw/docs/integrations/auth/better-auth.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/clerk.mdx +2 -2
- package/.docs/raw/docs/integrations/auth/next-auth.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/ai-sdk.mdx +10 -4
- package/.docs/raw/docs/integrations/frameworks/cloudflare-agents/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/full-stack.mdx +1 -1
- package/.docs/raw/docs/integrations/frameworks/mastra/overview.mdx +3 -3
- package/.docs/raw/docs/integrations/frameworks/mastra/separate-server.mdx +1 -1
- package/.docs/raw/docs/integrations/gateways/index.mdx +2 -2
- package/.docs/raw/docs/integrations/observability/helicone.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langfuse.mdx +1 -1
- package/.docs/raw/docs/integrations/observability/langsmith.mdx +2 -2
- package/.docs/raw/docs/integrations/persistence/custom-adapter.mdx +36 -9
- package/.docs/raw/docs/migrations/index.mdx +50 -0
- package/.docs/raw/docs/migrations/toolkit-tools.mdx +4 -2
- package/.docs/raw/docs/migrations/v0-15.mdx +156 -0
- package/.docs/raw/docs/primitives/chain-of-thought.mdx +6 -1
- package/.docs/raw/docs/primitives/composer.mdx +17 -1
- package/.docs/raw/docs/primitives/selection-toolbar.mdx +25 -0
- package/.docs/raw/docs/primitives/thread-list.mdx +2 -2
- package/.docs/raw/docs/react-native/hooks.mdx +3 -3
- package/.docs/raw/docs/react-native/index.mdx +2 -2
- package/.docs/raw/docs/react-native/primitives.mdx +62 -5
- package/.docs/raw/docs/runtimes/ag-ui/agent-state.mdx +124 -0
- package/.docs/raw/docs/runtimes/ag-ui/runtime-options.mdx +10 -1
- package/.docs/raw/docs/runtimes/ai-sdk/overview.mdx +14 -5
- package/.docs/raw/docs/runtimes/ai-sdk/v4-legacy.mdx +15 -15
- package/.docs/raw/docs/runtimes/ai-sdk/v5-legacy.mdx +8 -8
- package/.docs/raw/docs/runtimes/ai-sdk/{v6.mdx → v6-legacy.mdx} +11 -9
- package/.docs/raw/docs/runtimes/ai-sdk/v7.mdx +717 -0
- package/.docs/raw/docs/runtimes/concepts/adapters.mdx +1 -1
- package/.docs/raw/docs/runtimes/concepts/architecture.mdx +1 -1
- package/.docs/raw/docs/runtimes/concepts/threads.mdx +10 -10
- package/.docs/raw/docs/runtimes/custom/assistant-transport.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/data-stream.mdx +10 -19
- package/.docs/raw/docs/runtimes/custom/external-store.mdx +2 -2
- package/.docs/raw/docs/runtimes/custom/local-runtime.mdx +5 -3
- package/.docs/raw/docs/runtimes/eve/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/langchain.mdx +2 -2
- package/.docs/raw/docs/runtimes/langgraph/agent-state.mdx +181 -0
- package/.docs/raw/docs/runtimes/langgraph/overview.mdx +1 -1
- package/.docs/raw/docs/runtimes/opencode/hooks.mdx +3 -1
- package/.docs/raw/docs/tools/a2ui.mdx +107 -0
- package/.docs/raw/docs/tools/backend.mdx +6 -3
- package/.docs/raw/docs/tools/defining-tools.mdx +7 -1
- package/.docs/raw/docs/tools/generative-ui.mdx +60 -2
- package/.docs/raw/docs/tools/interactables-legacy.mdx +4 -4
- package/.docs/raw/docs/tools/interactables.mdx +3 -3
- package/.docs/raw/docs/tools/mcp-apps.mdx +90 -13
- package/.docs/raw/docs/tools/mcp.mdx +100 -3
- package/.docs/raw/docs/tools/tool-ui.mdx +6 -4
- package/.docs/raw/docs/tools/user-managed-mcp.mdx +77 -9
- package/.docs/raw/docs/ui/accordion.mdx +16 -10
- package/.docs/raw/docs/ui/assistant-modal.mdx +8 -4
- package/.docs/raw/docs/ui/attachment.mdx +5 -1
- package/.docs/raw/docs/ui/badge.mdx +23 -12
- package/.docs/raw/docs/ui/follow-up-suggestions.mdx +4 -2
- package/.docs/raw/docs/ui/model-selector.mdx +33 -3
- package/.docs/raw/docs/ui/part-grouping.mdx +0 -4
- package/.docs/raw/docs/ui/reasoning.mdx +1 -1
- package/.docs/raw/docs/ui/select.mdx +22 -14
- package/.docs/raw/docs/ui/sources.mdx +1 -1
- package/.docs/raw/docs/ui/tabs.mdx +25 -14
- package/.docs/raw/docs/utilities/heat-graph.mdx +2 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-docs/code-examples.d.ts.map +1 -1
- package/dist/prepare-docs/code-examples.js.map +1 -1
- package/dist/prepare-docs/copy-raw.d.ts.map +1 -1
- package/dist/prepare-docs/prepare.d.ts +1 -1
- package/dist/prompts/xulux-playground.d.ts +12 -0
- package/dist/prompts/xulux-playground.d.ts.map +1 -0
- package/dist/prompts/xulux-playground.js +33 -0
- package/dist/prompts/xulux-playground.js.map +1 -0
- package/dist/stdio.d.ts +1 -1
- package/dist/tools/docs.d.ts +8 -14
- package/dist/tools/docs.d.ts.map +1 -1
- package/dist/tools/docs.js +26 -10
- package/dist/tools/docs.js.map +1 -1
- package/dist/tools/examples.d.ts +6 -12
- package/dist/tools/examples.d.ts.map +1 -1
- package/dist/tools/examples.js +11 -8
- package/dist/tools/examples.js.map +1 -1
- package/dist/tools/resources.d.ts +1 -2
- package/dist/tools/resources.d.ts.map +1 -1
- package/dist/tools/resources.js +1 -1
- package/dist/tools/resources.js.map +1 -1
- package/dist/tools/search.d.ts +6 -15
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +2 -2
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tests/mcp-test-client.d.ts +15 -0
- package/dist/tools/tests/mcp-test-client.d.ts.map +1 -0
- package/dist/tools/tests/mcp-test-client.js +68 -0
- package/dist/tools/tests/mcp-test-client.js.map +1 -0
- package/dist/tools/tests/test-setup.d.ts.map +1 -1
- package/dist/tools/tests/test-setup.js +5 -1
- package/dist/tools/tests/test-setup.js.map +1 -1
- package/dist/tools/xulux-templates.d.ts +58 -0
- package/dist/tools/xulux-templates.d.ts.map +1 -0
- package/dist/tools/xulux-templates.js +82 -0
- package/dist/tools/xulux-templates.js.map +1 -0
- package/dist/utils/cache.d.ts +5 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +18 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/mcp-format.d.ts +1 -0
- package/dist/utils/mcp-format.d.ts.map +1 -1
- package/dist/utils/mcp-format.js +7 -4
- package/dist/utils/mcp-format.js.map +1 -1
- package/dist/utils/mdx.d.ts.map +1 -1
- package/dist/utils/paths.d.ts +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +3 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/search.d.ts.map +1 -1
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/utils/security.js.map +1 -1
- package/dist/xulux/catalog-client.d.ts +14 -0
- package/dist/xulux/catalog-client.d.ts.map +1 -0
- package/dist/xulux/catalog-client.js +67 -0
- package/dist/xulux/catalog-client.js.map +1 -0
- package/dist/xulux/fallback-catalog.d.ts +7 -0
- package/dist/xulux/fallback-catalog.d.ts.map +1 -0
- package/dist/xulux/fallback-catalog.js +47 -0
- package/dist/xulux/fallback-catalog.js.map +1 -0
- package/dist/xulux/fetch-sandbox.d.ts +5 -0
- package/dist/xulux/fetch-sandbox.d.ts.map +1 -0
- package/dist/xulux/fetch-sandbox.js +40 -0
- package/dist/xulux/fetch-sandbox.js.map +1 -0
- package/dist/xulux/template-service.d.ts +84 -0
- package/dist/xulux/template-service.d.ts.map +1 -0
- package/dist/xulux/template-service.js +223 -0
- package/dist/xulux/template-service.js.map +1 -0
- package/dist/xulux/types.d.ts +55 -0
- package/dist/xulux/types.d.ts.map +1 -0
- package/dist/xulux/types.js +6 -0
- package/dist/xulux/types.js.map +1 -0
- package/package.json +7 -6
- package/src/index.ts +55 -2
- package/src/prompts/xulux-playground.ts +36 -0
- package/src/tools/docs.ts +27 -5
- package/src/tools/examples.ts +17 -12
- package/src/tools/resources.ts +1 -4
- package/src/tools/search.ts +2 -2
- package/src/tools/tests/completions.test.ts +40 -26
- package/src/tools/tests/docs.test.ts +20 -0
- package/src/tools/tests/examples.test.ts +5 -5
- package/src/tools/tests/integration.test.ts +3 -4
- package/src/tools/tests/listings-cache.test.ts +19 -0
- package/src/tools/tests/mcp-protocol.test.ts +173 -108
- package/src/tools/tests/mcp-test-client.ts +111 -0
- package/src/tools/tests/resources.test.ts +97 -66
- package/src/tools/tests/test-setup.ts +8 -0
- package/src/tools/tests/xulux-templates.test.ts +262 -0
- package/src/tools/xulux-templates.ts +141 -0
- package/src/utils/cache.ts +20 -0
- package/src/utils/mcp-format.ts +8 -6
- package/src/utils/paths.ts +4 -1
- package/src/utils/tests/cache.test.ts +51 -0
- package/src/utils/tests/mcp-format.test.ts +22 -0
- package/src/utils/tests/security.test.ts +1 -1
- package/src/xulux/catalog-client.ts +105 -0
- package/src/xulux/fallback-catalog.ts +63 -0
- package/src/xulux/fetch-sandbox.ts +56 -0
- package/src/xulux/template-service.ts +406 -0
- package/src/xulux/types.ts +60 -0
- 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(
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
337
|
+
const snapshot = aui.interactables.exportState();
|
|
338
338
|
// => { "note-1": { name: "note", state: { title: "Hello" } }, ... }
|
|
339
339
|
|
|
340
|
-
aui.interactables
|
|
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
|
|
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
|
|
643
|
+
const snapshot = aui.interactables.exportState();
|
|
644
644
|
// => { "note-1": { name: "note", state: { title: "Hello" } }, ... }
|
|
645
645
|
|
|
646
|
-
aui.interactables
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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(
|
|
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/
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
317
|
-
await aui.mcp
|
|
318
|
-
await aui.mcp
|
|
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
|
-
-
|
|
400
|
+
- Prompts, sampling
|
|
333
401
|
- Auto-reconnect with backoff
|
|
334
402
|
- Per-tool enable/disable persistence
|
|
335
403
|
- Per-tool consent prompts
|