Docs: generative UI page, fix imports, add PrefabAppConfig (#3650)

This commit is contained in:
Jeremiah Lowin 2026-03-27 10:18:58 -04:00 committed by GitHub
commit 426572670e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 175 additions and 10 deletions

128
docs/apps/generative.mdx Normal file
View file

@ -0,0 +1,128 @@
---
title: Generative UI
sidebarTitle: Generative UI
description: Let the LLM build custom Prefab UIs on the fly.
icon: wand-magic-sparkles
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
Generative UI means the LLM writes the UI code at runtime. Instead of calling a pre-built tool with a fixed interface, the model writes Prefab Python code tailored to the current data and request. The user watches the UI build up in real time as the model generates code.
```python
from fastmcp import FastMCP
from fastmcp.apps.generative import GenerativeUI
mcp = FastMCP("Prefab Studio")
mcp.add_provider(GenerativeUI())
```
That's it. The `GenerativeUI` provider registers everything:
- **`generate_prefab_ui`** — a tool that accepts Python code, executes it in a Pyodide sandbox, and renders the result as a Prefab app
- **`search_prefab_components`** — a tool that lets the LLM search the Prefab component library to discover what's available
- **The generative renderer** — a `ui://` resource with browser-side Pyodide for streaming progressive rendering
## How It Works
When the LLM decides to call `generate_prefab_ui`, it writes Prefab Python code into the `code` argument. The MCP Apps protocol creates the renderer iframe in parallel with the tool call, so the app is already running when partial arguments start flowing.
As the LLM generates each token:
1. The host forwards partial arguments to the app via `ontoolinputpartial`
2. The renderer extracts the growing `code` string
3. Browser-side Pyodide executes whatever compiles successfully
4. The user sees components appear as they're written
When the LLM finishes, the server runs the complete code in a server-side Pyodide sandbox for validation, and the renderer replaces the streaming preview with the final server-validated result.
## What the LLM Writes
The tool description includes code examples that teach the LLM the Prefab patterns. A typical generation looks like:
```python
from prefab_ui.components import Column, Row, Heading, Text, Badge, Card, CardContent
from prefab_ui.components.charts import BarChart, ChartSeries
from prefab_ui.app import PrefabApp
with PrefabApp() as app:
with Column(gap=6, css_class="p-6"):
Heading("Q3 Revenue Report")
BarChart(
data=[
{"month": "Jul", "revenue": 42000},
{"month": "Aug", "revenue": 51000},
{"month": "Sep", "revenue": 63000},
],
series=[ChartSeries(data_key="revenue", label="Revenue")],
x_axis="month",
)
with Row(gap=4):
with Card():
with CardContent():
Text("Total", css_class="text-sm text-muted-foreground")
Heading("$156,000")
with Card():
with CardContent():
Text("Growth", css_class="text-sm text-muted-foreground")
Badge("+18%", variant="success")
```
The model writes real Python — loops, f-strings, computation, helper functions. Prefab's component library gives it charts, tables, forms, cards, badges, and layout primitives to work with.
## The Component Search Tool
Before writing code, the LLM can call `search_prefab_components` to discover what's available:
```
search_prefab_components("Chart")
→ 7 components matching 'Chart':
AreaChart — from prefab_ui.components.charts import AreaChart
BarChart — from prefab_ui.components.charts import BarChart
...
```
Passing `detail=True` returns full field descriptions and docstrings. The search tool introspects the actual Prefab classes at runtime, so it's always up to date with the installed version.
## Passing Data
The `generate_prefab_ui` tool accepts a `data` parameter. Values passed here become global variables in the sandbox:
```python
# The LLM can reference 'sales_data' directly in its code
result = await generate_prefab_ui(
code="...",
data={"sales_data": [{"month": "Jan", "revenue": 42000}, ...]}
)
```
This lets the model use real data from earlier in the conversation to build visualizations.
## Configuration
`GenerativeUI` accepts options for customizing tool names:
```python
GenerativeUI(
tool_name="generate_prefab_ui", # default
components_tool_name="search_prefab_components", # default
include_components_tool=True, # default
)
```
## Requirements
Generative UI requires `fastmcp[apps]` which installs `prefab-ui`. The Pyodide sandbox (for server-side validation) requires Deno — it installs automatically on first use.
The streaming renderer loads Pyodide from CDN in the browser. The CSP is configured automatically by the provider — no manual setup needed.
## Next Steps
- **[Prefab Apps](/apps/prefab)** — The component library and state system the LLM writes code against
- **[Components](/apps/components)** — Quick reference for the most-used components
- **[Development](/apps/development)** — Preview generative UI tools locally with `fastmcp dev apps`

View file

@ -27,7 +27,7 @@ The tool declares which resource to use via `AppConfig`. When the host calls the
import json
from fastmcp import FastMCP
from fastmcp.server.apps import AppConfig, ResourceCSP
from fastmcp.apps import AppConfig, ResourceCSP
mcp = FastMCP("My App Server")
@ -47,7 +47,7 @@ def chart_view() -> str:
`AppConfig` controls how a tool or resource participates in the Apps extension. Import it from `fastmcp.server.apps`:
```python
from fastmcp.server.apps import AppConfig
from fastmcp.apps import AppConfig
```
On **tools**, you'll typically set `resource_uri` to point to the UI resource:
@ -158,7 +158,7 @@ Apps run in sandboxed iframes with a deny-by-default Content Security Policy. By
If your app needs to load external resources (CDN scripts, API calls, embedded iframes), declare the allowed domains with `ResourceCSP`:
```python
from fastmcp.server.apps import AppConfig, ResourceCSP
from fastmcp.apps import AppConfig, ResourceCSP
@mcp.resource(
"ui://my-app/view.html",
@ -185,7 +185,7 @@ def my_view() -> str:
If your app needs browser capabilities like camera or clipboard access, request them via `ResourcePermissions`:
```python
from fastmcp.server.apps import AppConfig, ResourcePermissions
from fastmcp.apps import AppConfig, ResourcePermissions
@mcp.resource(
"ui://my-app/view.html",
@ -214,7 +214,7 @@ import qrcode
from mcp import types
from fastmcp import FastMCP
from fastmcp.server.apps import AppConfig, ResourceCSP
from fastmcp.apps import AppConfig, ResourceCSP
from fastmcp.tools import ToolResult
mcp = FastMCP("QR Code Server")
@ -290,7 +290,7 @@ Not all hosts support the Apps extension. You can check at runtime using the too
```python
from fastmcp import Context
from fastmcp.server.apps import AppConfig, UI_EXTENSION_ID
from fastmcp.apps import AppConfig, UI_EXTENSION_ID
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))
async def my_tool(ctx: Context) -> str:

View file

@ -32,7 +32,8 @@ The quickest way to give a tool a visual UI. You return a [Prefab](https://prefa
```python
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
from prefab_ui.components import Column, Heading
from prefab_ui.components.charts import BarChart, ChartSeries
from fastmcp import FastMCP
mcp = FastMCP("Dashboard")
@ -138,10 +139,27 @@ See [FastMCPApp](/apps/interactive-apps) for the full guide.
| Light server interaction — one or two tool calls | [Prefab app](/apps/prefab) — `CallTool("tool_name")` |
| Heavy server interaction — forms, CRUD, search, multi-step | [FastMCPApp](/apps/interactive-apps) — managed tool binding |
| Composed servers — apps mounted under namespaces | [FastMCPApp](/apps/interactive-apps) — stable global keys |
| LLM-generated UIs — bespoke visualizations per request | [Generative UI](/apps/generative) — `GenerativeUI` provider |
| Custom rendering — maps, 3D, specific JS frameworks | [Custom HTML](/apps/low-level) — raw MCP Apps extension |
The boundary isn't sharp. Start with a Prefab app; graduate to `FastMCPApp` when the tool-management complexity justifies it.
## Generative UI
<VersionBadge version="3.2.0" />
Instead of pre-building a UI, the LLM can write one from scratch. The `GenerativeUI` provider registers tools that let the model write Prefab Python code, execute it in a sandbox, and render the result — with streaming so the user watches the UI build up in real time.
```python
from fastmcp import FastMCP
from fastmcp.apps.generative import GenerativeUI
mcp = FastMCP("Prefab Studio")
mcp.add_provider(GenerativeUI())
```
See [Generative UI](/apps/generative) for the full guide.
## Custom HTML Apps
Both approaches above use [Prefab UI](https://prefab.prefect.io) to build UIs in pure Python. If you need full control — your own HTML, CSS, JavaScript, a specific framework — you can use the [MCP Apps extension directly](/apps/low-level). You write the HTML yourself and communicate with the host via the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) SDK.

View file

@ -24,7 +24,8 @@ Here's a tool that returns a bar chart:
```python
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
from prefab_ui.components import Column, Heading
from prefab_ui.components.charts import BarChart, ChartSeries
from fastmcp import FastMCP
mcp = FastMCP("Dashboard")
@ -312,7 +313,8 @@ If the model needs to reason about the data — reference it in conversation, su
```python
from prefab_ui.app import PrefabApp
from prefab_ui.components import Column, Heading, BarChart, ChartSeries
from prefab_ui.components import Column, Heading
from prefab_ui.components.charts import BarChart, ChartSeries
from fastmcp import FastMCP
from fastmcp.tools import ToolResult
@ -361,12 +363,28 @@ The **component tree** is serialized as `structuredContent` on the tool result.
None of this requires configuration. The `app=True` flag (or type inference) is the only thing you need.
## Customizing CSP
`app=True` auto-wires the Prefab renderer with default CSP settings. If your app loads external resources — embedding iframes, fetching from APIs, loading scripts — use `PrefabAppConfig` to add the required domains while keeping all the auto-wiring:
```python
from fastmcp.apps import PrefabAppConfig, ResourceCSP
@mcp.tool(app=PrefabAppConfig(
csp=ResourceCSP(frame_domains=["https://example.com"]),
))
def dashboard_with_embed() -> PrefabApp:
...
```
`PrefabAppConfig()` with no arguments is equivalent to `app=True`. It auto-sets the renderer URI and merges the renderer's CSP with any additional domains you provide.
## Mixing with Custom HTML
Prefab tools and [custom HTML tools](/apps/low-level) coexist in the same server. Prefab tools share a single renderer resource; custom tools point to their own:
```python
from fastmcp.server.apps import AppConfig
from fastmcp.apps import AppConfig
@mcp.tool(app=True)
def team_directory() -> PrefabApp:

View file

@ -196,6 +196,7 @@
"apps/interactive-apps",
"apps/components",
"apps/patterns",
"apps/generative",
"apps/development",
"apps/architecture",
"apps/low-level"