From 8ff28c13fa591cab66147126abf8037852920cdf Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:32:22 -0400 Subject: [PATCH] Fix blocking docs issues: chart imports, Select API, Rx consistency (#3652) --- docs/apps/interactive-apps.mdx | 16 ++++++------- docs/apps/overview.mdx | 42 ++++++++++++++++++---------------- docs/apps/patterns.mdx | 21 +++++++++-------- docs/apps/prefab.mdx | 4 +++- docs/docs.json | 15 ++++++++---- 5 files changed, 56 insertions(+), 42 deletions(-) diff --git a/docs/apps/interactive-apps.mdx b/docs/apps/interactive-apps.mdx index bcf82f41a..fb2963114 100644 --- a/docs/apps/interactive-apps.mdx +++ b/docs/apps/interactive-apps.mdx @@ -283,7 +283,7 @@ Forms are the most common way to collect input and send it to the server. When a Build forms with individual input components: ```python -from prefab_ui.components import Form, Input, Select, Textarea, Button +from prefab_ui.components import Form, Input, Select, SelectOption, Textarea, Button from prefab_ui.actions.mcp import CallTool from prefab_ui.actions import ShowToast @@ -294,11 +294,11 @@ with Form( ) ): Input(name="title", label="Title", required=True) - Select( - name="priority", - label="Priority", - options=["low", "medium", "high", "critical"], - ) + with Select(name="priority", label="Priority"): + SelectOption("Low", value="low") + SelectOption("Medium", value="medium") + SelectOption("High", value="high") + SelectOption("Critical", value="critical") Textarea(name="description", label="Description") Button("Create Ticket") ``` @@ -433,7 +433,7 @@ from prefab_ui.components import ( Badge, Button, Column, ForEach, Form, Heading, Input, Muted, Row, Separator, Text, ) -from prefab_ui.rx import RESULT +from prefab_ui.rx import RESULT, Rx from pydantic import BaseModel, Field from fastmcp import FastMCP, FastMCPApp @@ -512,7 +512,7 @@ def contact_manager() -> PrefabApp: with Form( on_submit=CallTool( "search_contacts", - arguments={"query": "{{ query }}"}, + arguments={"query": Rx("query")}, on_success=SetState("contacts", RESULT), ) ): diff --git a/docs/apps/overview.mdx b/docs/apps/overview.mdx index e694156be..11c10773a 100644 --- a/docs/apps/overview.mdx +++ b/docs/apps/overview.mdx @@ -10,9 +10,11 @@ import { VersionBadge } from '/snippets/version-badge.mdx' -MCP Apps let tools return interactive UIs — charts, sortable tables, forms, dashboards — rendered in a sandboxed iframe inside the host client's conversation. +MCP tools normally return text. That works for answers, but not for data the user wants to *explore* — a revenue chart they can hover over, a sortable employee directory, a form that submits structured input. MCP Apps let your tools return interactive UIs rendered right inside the conversation. -FastMCP implements the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) and gives you two ways to build interactive UIs with [Prefab](https://prefab.prefect.io), depending on how much server-side interaction you need. +{/* TODO: screenshot of a Prefab app rendering in Claude Desktop */} + +FastMCP builds on the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) with [Prefab](https://prefab.prefect.io), a Python component library that compiles to interactive UIs. You write Python; the user sees charts, tables, forms, and dashboards. The examples throughout the Apps docs require the `apps` extra: @@ -28,7 +30,7 @@ This installs [Prefab UI](https://prefab.prefect.io), the component library used -The quickest way to give a tool a visual UI. You return a [Prefab](https://prefab.prefect.io) component or `PrefabApp` from an otherwise standard MCP tool, and when the host calls it, the app is rendered instead of plain text: +The quickest way to give a tool a visual UI. Add `app=True` to any tool and return a Prefab component — when the host calls it, the user sees an interactive UI instead of a JSON blob: ```python from prefab_ui.app import PrefabApp @@ -60,7 +62,7 @@ def revenue_chart(year: int) -> PrefabApp: return PrefabApp(view=view) ``` -Prefab apps aren't limited to static displays. Prefab's state system and client-side actions (toggles, tabs, conditionals) all work. You can even call other tools from the UI using `CallTool` with string names. There's no hard wall on what a Prefab app can do. +Prefab apps aren't limited to static displays. Prefab's state system and client-side actions (toggles, tabs, conditionals) all work. You can even call other tools from the UI using `CallTool`. There's no hard wall on what a Prefab app can do. See [Prefab Apps](/apps/prefab) for the full guide. @@ -75,7 +77,7 @@ When your app has a lot of server-side interaction — forms that save data, sea - **`@app.ui()`** — entry-point tools the model calls to open the app - **`@app.tool()`** — backend tools the UI calls via `CallTool` -Backend tools get stable global identifiers that survive namespacing, visibility is managed automatically (the model sees entry points, the UI sees backends), and `CallTool` accepts function references instead of string names: +Backend tools get stable identifiers that survive namespacing, visibility is managed automatically (the model sees entry points, the UI sees backends), and `CallTool` accepts tool names that resolve correctly regardless of how servers are composed: ```python from prefab_ui.actions import SetState, ShowToast @@ -130,20 +132,6 @@ You *can* build server-interactive UIs without `FastMCPApp` — it's all the sam See [FastMCPApp](/apps/interactive-apps) for the full guide. -## Which Approach? - -| Scenario | Approach | -| -------- | -------- | -| Visual output — charts, tables, status dashboards | [Prefab app](/apps/prefab) — `@mcp.tool(app=True)` | -| Client-side interactivity — toggles, tabs, conditionals | [Prefab app](/apps/prefab) — state + `Rx()` | -| 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 @@ -160,9 +148,23 @@ mcp.add_provider(GenerativeUI()) See [Generative UI](/apps/generative) for the full guide. +## Which Approach? + +| Scenario | Approach | +| -------- | -------- | +| Visual output — charts, tables, status dashboards | [Prefab app](/apps/prefab) — `@mcp.tool(app=True)` | +| Client-side interactivity — toggles, tabs, conditionals | [Prefab app](/apps/prefab) — state + `Rx()` | +| 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 routing | +| 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. + ## 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. +All the 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. ## Previewing Apps Locally diff --git a/docs/apps/patterns.mdx b/docs/apps/patterns.mdx index b2b0eeade..b0ff80376 100644 --- a/docs/apps/patterns.mdx +++ b/docs/apps/patterns.mdx @@ -22,7 +22,8 @@ Prefab includes [bar, line, area, pie, radar, and radial charts](https://prefab. ```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("Charts") @@ -61,7 +62,8 @@ Multiple `ChartSeries` entries plot different data keys. Add `stacked=True` to s ```python from prefab_ui.app import PrefabApp -from prefab_ui.components import Column, Heading, AreaChart, ChartSeries +from prefab_ui.components import Column, Heading +from prefab_ui.components.charts import AreaChart, ChartSeries from fastmcp import FastMCP mcp = FastMCP("Charts") @@ -97,7 +99,8 @@ def usage_trend() -> PrefabApp: ```python from prefab_ui.app import PrefabApp -from prefab_ui.components import Column, Heading, PieChart +from prefab_ui.components import Column, Heading +from prefab_ui.components.charts import PieChart from fastmcp import FastMCP mcp = FastMCP("Charts") @@ -360,7 +363,7 @@ from prefab_ui.actions.mcp import CallTool from prefab_ui.app import PrefabApp from prefab_ui.components import ( Badge, Button, Column, ForEach, Form, Heading, - Input, Muted, Row, Select, Separator, Text, Textarea, + Input, Muted, Row, Select, SelectOption, Separator, Text, Textarea, ) from prefab_ui.rx import RESULT from fastmcp import FastMCP, FastMCPApp @@ -407,11 +410,11 @@ def contact_form() -> PrefabApp: ): Input(name="name", label="Full Name", required=True) Input(name="email", label="Email", input_type="email", required=True) - Select( - name="category", - label="Category", - options=["Customer", "Vendor", "Partner", "Other"], - ) + with Select(name="category", label="Category"): + SelectOption("Customer", value="Customer") + SelectOption("Vendor", value="Vendor") + SelectOption("Partner", value="Partner") + SelectOption("Other", value="Other") Textarea(name="notes", label="Notes", placeholder="Optional notes...") Button("Save Contact") diff --git a/docs/apps/prefab.mdx b/docs/apps/prefab.mdx index 35181808b..b598fb63c 100644 --- a/docs/apps/prefab.mdx +++ b/docs/apps/prefab.mdx @@ -14,7 +14,9 @@ import { VersionBadge } from '/snippets/version-badge.mdx' [Prefab](https://prefab.prefect.io) is in early, active development — its API changes frequently and breaking changes can occur with any release. Always pin `prefab-ui` to a specific version in your dependencies (see below). -The fastest way to give a tool a visual UI: return a [Prefab](https://prefab.prefect.io) component or `PrefabApp` from an otherwise standard MCP tool. FastMCP registers the rendering engine, wires the protocol metadata, and delivers the component tree to the host. You write Python; the user sees an interactive UI. +When a tool returns text, the LLM reads it and relays the information. But some data is better *seen* — a chart communicates trend at a glance, a sortable table lets the user explore without re-prompting, a dashboard with badges and progress bars gives immediate status. + +Prefab apps transform your tools from text-returning functions into visual experiences. Add `app=True` to a tool, return a [Prefab](https://prefab.prefect.io) component, and the host renders an interactive UI instead of text. FastMCP handles the rendering engine, protocol metadata, and delivery. You write Python; the user sees charts, tables, and dashboards. This works for everything from static charts to reactive dashboards with client-side state. When your app has heavy server-side interaction — multiple backend tools, forms, search, CRUD — consider [FastMCPApp](/apps/interactive-apps), which manages tool binding, visibility, and composition safety for you. diff --git a/docs/docs.json b/docs/docs.json index 7231b40a0..6545b240b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -194,12 +194,19 @@ "apps/overview", "apps/prefab", "apps/interactive-apps", - "apps/components", - "apps/patterns", "apps/generative", "apps/development", - "apps/architecture", - "apps/low-level" + { + "collapsed": true, + "group": "Reference", + "icon": "book", + "pages": [ + "apps/components", + "apps/patterns", + "apps/architecture", + "apps/low-level" + ] + } ] }, {