diff --git a/docs/apps/architecture.mdx b/docs/apps/architecture.mdx index 8fe5c3868..ee666568f 100644 --- a/docs/apps/architecture.mdx +++ b/docs/apps/architecture.mdx @@ -1,13 +1,14 @@ --- -title: Architecture +title: App Architecture sidebarTitle: Architecture -description: How Prefab apps work under the hood — from Python to pixels. +description: How FastMCP apps work under the hood — from Python to pixels. icon: sitemap +tag: NEW --- import { VersionBadge } from '/snippets/version-badge.mdx' - + This page explains the internal architecture of Prefab apps — how your Python code becomes an interactive UI inside a host client's conversation. If you're building [custom HTML apps](/apps/low-level), the pipeline is simpler and covered on that page. You don't need to understand any of this to build Prefab apps, but the mental model is useful when you're debugging, extending, or contributing. diff --git a/docs/apps/components.mdx b/docs/apps/components.mdx deleted file mode 100644 index 5283a7903..000000000 --- a/docs/apps/components.mdx +++ /dev/null @@ -1,601 +0,0 @@ ---- -title: Component Reference -sidebarTitle: Components -description: Quick reference for the most-used Prefab components. -icon: shapes -tag: NEW ---- - -import { VersionBadge } from '/snippets/version-badge.mdx' - - - -This page is a scannable reference for the Prefab components you'll use most often in MCP Apps. Each entry shows the component, what it does, a minimal code example, and the props that matter. - -For the full component library — every prop, variant, and advanced pattern — see the [Prefab component reference](https://prefab.prefect.io/docs/components). - - -All components below are imported from `prefab_ui.components` unless otherwise noted. Charts must be imported from `prefab_ui.components.charts`. - - -## Layout - -Layout components control how children are arranged. They all use Python's `with` statement to collect their children. - -### Column - -Stacks children vertically. The most common top-level container for an app view. - -```python -from prefab_ui.components import Column, Text - -with Column(gap=4, css_class="p-6") as view: - Text("First") - Text("Second") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `gap` | `int` | Space between children (Tailwind units) | -| `align` | `str` | Cross-axis alignment: `"start"`, `"center"`, `"end"`, `"stretch"` | -| `justify` | `str` | Main-axis alignment: `"start"`, `"center"`, `"end"`, `"between"` | -| `css_class` | `str` | Tailwind CSS classes | - -### Row - -Arranges children horizontally. - -```python -from prefab_ui.components import Row, Badge, Text - -with Row(gap=2, align="center"): - Text("Status") - Badge("Online", variant="success") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `gap` | `int` | Space between children | -| `align` | `str` | Cross-axis alignment | -| `justify` | `str` | Main-axis alignment | -| `wrap` | `bool` | Wrap children to next line | - -### Grid - -Lays out children in a CSS grid with a fixed number of columns. - -```python -from prefab_ui.components import Grid, Card, CardContent, Text - -with Grid(columns=3, gap=4): - for label in ["API", "Cache", "DB"]: - with Card(): - with CardContent(): - Text(label) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `columns` | `int` | Number of grid columns | -| `gap` | `int` | Space between cells | - -### Card / CardContent - -A bordered container with padding. `Card` provides the outer border and shadow; `CardContent` adds standard inner padding. Cards are commonly used inside grids for dashboard-style layouts. - -```python -from prefab_ui.components import Card, CardContent, Text, Badge - -with Card(): - with CardContent(): - Text("API Gateway") - Badge("healthy", variant="success") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `css_class` | `str` | Additional Tailwind classes | - -### Separator - -Renders a horizontal rule between sections. Takes no required props. - -```python -from prefab_ui.components import Column, Heading, Separator, Text - -with Column(gap=4): - Heading("Section A") - Separator() - Text("Content below the line") -``` - -## Typography - -### Heading - -Renders a heading element. Defaults to `level=2` (an `

`). - -```python -from prefab_ui.components import Heading - -Heading("Dashboard") -Heading("Subsection", level=3) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `level` | `int` | Heading level: `1`-`4` | - -### Text - -General-purpose text element. Accepts reactive expressions (`Rx`) as content so the text can update with state changes. - -```python -from prefab_ui.components import Text - -Text("Hello, world") -Text("Styled text", css_class="font-medium text-blue-500") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `css_class` | `str` | Tailwind CSS classes | - -### Muted - -Renders text in a subdued color. Useful for secondary information like timestamps, metadata, or helper text. - -```python -from prefab_ui.components import Muted - -Muted("Last updated 5 minutes ago") -``` - -### Badge - -A small label for status indicators, tags, or categories. Supports color variants to convey meaning at a glance. - -```python -from prefab_ui.components import Badge - -Badge("Active", variant="success") -Badge("Pending", variant="warning") -Badge("Failed", variant="destructive") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `variant` | `str` | `"default"`, `"success"`, `"warning"`, `"destructive"`, `"outline"` | - -## Data - -### DataTable - -A fully interactive table with client-side sorting, searching, and pagination. You define columns and pass row data as a list of dicts. - -```python -from prefab_ui.components import DataTable, DataTableColumn - -DataTable( - columns=[ - DataTableColumn(key="name", header="Name", sortable=True), - DataTableColumn(key="role", header="Role"), - ], - rows=[ - {"name": "Alice", "role": "Engineer"}, - {"name": "Bob", "role": "Designer"}, - ], - search=True, - paginated=True, - page_size=15, -) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `columns` | `list[DataTableColumn]` | Column definitions | -| `rows` | `list[dict]` | Row data | -| `search` | `bool` | Enable full-text search | -| `paginated` | `bool` | Enable pagination | -| `page_size` | `int` | Rows per page (default `10`) | - -`DataTableColumn` takes `key` (the dict key), `header` (display name), and `sortable` (enable sorting on that column). - -### BarChart - -Renders vertical or horizontal bar charts. Each `ChartSeries` maps a key from your data to a colored bar group. Multiple series produce grouped (or stacked) bars. - -```python -from prefab_ui.components.charts import BarChart, ChartSeries - -BarChart( - data=[ - {"month": "Jan", "revenue": 4200}, - {"month": "Feb", "revenue": 5100}, - ], - series=[ChartSeries(data_key="revenue", label="Revenue")], - x_axis="month", - show_legend=True, -) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `data` | `list[dict]` | Chart data | -| `series` | `list[ChartSeries]` | Data series to plot | -| `x_axis` | `str` | Key for the x-axis labels | -| `stacked` | `bool` | Stack bars instead of grouping | -| `horizontal` | `bool` | Flip axes for horizontal bars | -| `show_legend` | `bool` | Display the legend | -| `height` | `int` | Chart height in pixels | - -### PieChart - -Displays proportional data as slices. Set `inner_radius` for a donut chart. Unlike bar/line charts, `PieChart` uses `data_key` for the numeric value and `name_key` for the label — it doesn't use `ChartSeries`. - -```python -from prefab_ui.components.charts import PieChart - -PieChart( - data=[ - {"category": "Bug", "count": 23}, - {"category": "Feature", "count": 15}, - ], - data_key="count", - name_key="category", - inner_radius=60, - show_legend=True, -) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `data` | `list[dict]` | Chart data | -| `data_key` | `str` | Key for the numeric value | -| `name_key` | `str` | Key for the label | -| `inner_radius` | `int` | Inner radius for donut charts (0 = full pie) | -| `show_legend` | `bool` | Display the legend | - -### LineChart - -Plots data points connected by lines. Shares the same API as `BarChart` — use `series`, `x_axis`, and optionally `curve` to control interpolation. - -```python -from prefab_ui.components.charts import LineChart, ChartSeries - -LineChart( - data=[ - {"day": "Mon", "requests": 120}, - {"day": "Tue", "requests": 185}, - ], - series=[ChartSeries(data_key="requests", label="Requests")], - x_axis="day", - curve="smooth", - show_dots=True, -) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `data` | `list[dict]` | Chart data | -| `series` | `list[ChartSeries]` | Data series to plot | -| `x_axis` | `str` | Key for x-axis labels | -| `curve` | `str` | `"linear"` or `"smooth"` | -| `show_dots` | `bool` | Show data point markers | -| `height` | `int` | Chart height in pixels | - -## Forms - -Form components collect user input. Each has a `name` prop that determines the key in the submitted data. When used inside a `Form`, their values are gathered automatically on submit. - -### Input - -A single-line text field. Set `input_type` to `"email"`, `"password"`, `"number"`, etc. for browser-native validation. - -```python -from prefab_ui.components import Input - -Input(name="email", label="Email", input_type="email", required=True) -Input(name="search", placeholder="Search...") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | -| `placeholder` | `str` | Placeholder text | -| `input_type` | `str` | HTML input type | -| `required` | `bool` | Mark as required | -| `disabled` | `bool` | Disable input | - -### Select - -A dropdown for choosing from a list of options. Pass a flat list of strings, or structured `SelectOption` objects for custom labels. - -```python -from prefab_ui.components import Select, SelectOption - -with Select(name="priority", label="Priority"): - SelectOption("Low", value="low") - SelectOption("Medium", value="medium") - SelectOption("High", value="high") -``` - -Options are defined as `SelectOption` children, not as a prop. - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | -| `placeholder` | `str` | Placeholder text | - -### Textarea - -A multi-line text area. Works the same as `Input` but renders as a resizable text box. - -```python -from prefab_ui.components import Textarea - -Textarea(name="notes", label="Notes", placeholder="Add details...") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | -| `placeholder` | `str` | Placeholder text | -| `rows` | `int` | Visible height in rows | - -### Checkbox - -A boolean toggle rendered as a checkbox. Binds to state as `True`/`False`. - -```python -from prefab_ui.components import Checkbox - -Checkbox(name="agree", label="I agree to the terms") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | - -### Switch - -A toggle switch. Functionally identical to `Checkbox` but rendered as a sliding toggle, better suited for settings and feature flags. - -```python -from prefab_ui.components import Switch - -Switch(name="dark_mode", label="Dark Mode") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | - -### Slider - -A range input for numeric values. The user drags a handle between `min` and `max`. - -```python -from prefab_ui.components import Slider - -Slider(name="volume", label="Volume", min=0, max=100, step=1) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `name` | `str` | State/form key | -| `label` | `str` | Label text | -| `min` | `float` | Minimum value | -| `max` | `float` | Maximum value | -| `step` | `float` | Step increment | - -### Form - -Wraps input components and gathers their values on submit. Attach a `CallTool` action to `on_submit` to send the data to the server. Every named input inside the form becomes a key in the arguments dict. - -```python -from prefab_ui.components import Form, Input, Button -from prefab_ui.actions.mcp import CallTool - -with Form(on_submit=CallTool("save_contact")): - Input(name="name", label="Name", required=True) - Input(name="email", label="Email", required=True) - Button("Save") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `on_submit` | `Action` | Action to run when the form is submitted | - -### Button - -A clickable button. Inside a `Form`, a button triggers form submission by default. Outside a form, attach actions to `on_click`. - -```python -from prefab_ui.components import Button -from prefab_ui.actions import SetState - -Button("Reset", on_click=SetState("count", 0)) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `variant` | `str` | `"default"`, `"outline"`, `"ghost"`, `"destructive"` | -| `on_click` | `Action` | Action to run on click | -| `disabled` | `bool` | Disable the button | - -## Containers - -### Tabs / Tab - -Organizes content into switchable panels. Each `Tab` becomes a panel with a label in the tab bar. Switching tabs is instant — all panels are rendered, only one is visible. - -```python -from prefab_ui.components import Tabs, Tab, Text - -with Tabs(): - with Tab("Overview"): - Text("Overview content here") - with Tab("Details"): - Text("Detail content here") -``` - -| Prop (Tabs) | Type | Description | -|-------------|------|-------------| -| `value` | `str` | Label of the initially active tab | - -### Accordion / AccordionItem - -Collapsible sections. Each `AccordionItem` has a title that toggles its content open and closed. By default, only one item is open at a time. - -```python -from prefab_ui.components import Accordion, AccordionItem, Text - -with Accordion(multiple=True): - with AccordionItem("Section A"): - Text("Content for section A") - with AccordionItem("Section B"): - Text("Content for section B") -``` - -| Prop (Accordion) | Type | Description | -|------------------|------|-------------| -| `multiple` | `bool` | Allow multiple items open simultaneously | - -### Dialog - -A modal overlay that appears above the page content. Pair it with a trigger (like a `Button`) to open and close it. - -```python -from prefab_ui.components import Dialog, Column, Heading, Text - -with Dialog(title="Confirm Delete"): - with Column(gap=2): - Text("Are you sure you want to delete this item?") -``` - -| Prop | Type | Description | -|------|------|-------------| -| `title` | `str` | Dialog title in the header | -| `description` | `str` | Subtitle below the title | - -### Pages / Page - -Multi-page navigation within a single app. `Pages` renders one `Page` at a time, controlled by state. Useful for multi-step workflows and wizards. - -```python -from prefab_ui.components import Pages, Page, Text, Button -from prefab_ui.actions import SetState -from prefab_ui.rx import Rx - -with Pages(active_page=Rx("page")): - with Page("welcome"): - Text("Welcome!") - Button("Next", on_click=SetState("page", "setup")) - with Page("setup"): - Text("Configure your settings") - -# Pass state={"page": "welcome"} to PrefabApp when returning -``` - -| Prop (Pages) | Type | Description | -|-------------|------|-------------| -| `active_page` | `str \| Rx` | The label of the currently visible page | - -## Control Flow - -Control flow components conditionally show or iterate over children based on reactive state. They evaluate in the browser, so changes are instant. - -### If / Elif / Else - -Conditionally render content based on state values. `If` evaluates an `Rx` expression; `Elif` and `Else` follow the same pattern as Python's branching. - -```python -from prefab_ui.components import If, Elif, Else, Text, Select -from prefab_ui.rx import Rx - -role = Rx("role") - -Select(name="role", options=["viewer", "editor", "admin"]) -with If(role == "admin"): - Text("Full access") -with Elif(role == "editor"): - Text("Edit access") -with Else(): - Text("Read-only access") - -# Pass state={"role": "viewer"} to PrefabApp when returning -``` - -### ForEach - -Iterates over a state array and renders children for each item. The loop variable is an `Rx` proxy scoped to the current item, so `item.name` resolves at render time. - -```python -from prefab_ui.components import ForEach, Row, Text, Badge - -with ForEach("users") as user: - with Row(gap=2): - Text(user.name) - Badge(user.role) -``` - -| Prop | Type | Description | -|------|------|-------------| -| first arg | `str` | The state key containing the array | - -## Feedback - -### Alert - -A callout box for important messages. Supports variants to signal severity. - -```python -from prefab_ui.components import Alert - -Alert(title="Deployment complete", variant="success") -Alert( - title="Rate limit approaching", - description="Current usage is at 85% of your plan limit.", - variant="warning", -) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `title` | `str` | Alert heading | -| `description` | `str` | Body text | -| `variant` | `str` | `"default"`, `"success"`, `"warning"`, `"destructive"` | - -### Progress - -A horizontal progress bar. Pass a `value` between 0 and 100. Supports reactive values so the bar updates as state changes. - -```python -from prefab_ui.components import Progress - -Progress(value=75) -``` - -| Prop | Type | Description | -|------|------|-------------| -| `value` | `int \| Rx` | Progress percentage (0-100) | - -### Loader - -A spinning indicator for loading states. Takes no required props — just drop it in and it spins. - -```python -from prefab_ui.components import Loader - -Loader() -``` - ---- - -For the complete API — including additional components like `Metric`, `Calendar`, `Markdown`, `Embed`, and advanced chart types — see the [Prefab component reference](https://prefab.prefect.io/docs/components). diff --git a/docs/apps/generative.mdx b/docs/apps/generative.mdx index 71d7e111d..4046acd0f 100644 --- a/docs/apps/generative.mdx +++ b/docs/apps/generative.mdx @@ -123,6 +123,6 @@ The streaming renderer loads Pyodide from CDN in the browser. The CSP is configu ## 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 +- **[Prefab UI](/apps/prefab)** — The component library and state system the LLM writes code against +- **[Prefab Component Reference](https://prefab.prefect.io/docs/components)** — Full component library documentation - **[Development](/apps/development)** — Preview generative UI tools locally with `fastmcp dev apps` diff --git a/docs/apps/images/app-chart.png b/docs/apps/images/app-chart.png new file mode 100644 index 000000000..cfc816d0e Binary files /dev/null and b/docs/apps/images/app-chart.png differ diff --git a/docs/apps/images/app-contacts.png b/docs/apps/images/app-contacts.png new file mode 100644 index 000000000..5d74f7cb9 Binary files /dev/null and b/docs/apps/images/app-contacts.png differ diff --git a/docs/apps/images/app-datatable.png b/docs/apps/images/app-datatable.png new file mode 100644 index 000000000..e69de29bb diff --git a/docs/apps/images/app-greet.png b/docs/apps/images/app-greet.png new file mode 100644 index 000000000..70a0e4412 Binary files /dev/null and b/docs/apps/images/app-greet.png differ diff --git a/docs/apps/images/app-overview.png b/docs/apps/images/app-overview.png new file mode 100644 index 000000000..35f68fd58 Binary files /dev/null and b/docs/apps/images/app-overview.png differ diff --git a/docs/apps/images/app-showcase.png b/docs/apps/images/app-showcase.png new file mode 100644 index 000000000..c03294bdb Binary files /dev/null and b/docs/apps/images/app-showcase.png differ diff --git a/docs/apps/overview.mdx b/docs/apps/overview.mdx index 11c10773a..c74482bd7 100644 --- a/docs/apps/overview.mdx +++ b/docs/apps/overview.mdx @@ -12,7 +12,9 @@ import { VersionBadge } from '/snippets/version-badge.mdx' 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. -{/* TODO: screenshot of a Prefab app rendering in Claude Desktop */} + + A Prefab app showing forms, charts, metrics, progress bars, data tables, and interactive controls — all built in Python + 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. @@ -150,17 +152,13 @@ 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 | +Most apps start with **[Prefab Apps](/apps/prefab)** — add `app=True` to a tool and return components. That covers charts, tables, dashboards, and client-side interactivity. -The boundary isn't sharp. Start with a Prefab app; graduate to `FastMCPApp` when the tool-management complexity justifies it. +When your UI needs multiple backend tools with managed visibility and composition safety, use **[FastMCPApp](/apps/interactive-apps)**. + +When you want the LLM to design the UI at runtime, use **[Generative UI](/apps/generative)**. + +When you need your own HTML/JS (maps, 3D, video), use **[Custom HTML](/apps/low-level)**. ## Custom HTML Apps diff --git a/docs/apps/prefab.mdx b/docs/apps/prefab.mdx index b598fb63c..0ee703f5e 100644 --- a/docs/apps/prefab.mdx +++ b/docs/apps/prefab.mdx @@ -1,7 +1,7 @@ --- -title: Prefab Apps -sidebarTitle: Prefab Apps -description: Build interactive tool UIs in pure Python — charts, tables, dashboards, forms, and reactive displays. +title: Prefab UI +sidebarTitle: Prefab UI +description: The component library behind FastMCP apps — charts, tables, dashboards, forms, and reactive displays. icon: palette tag: NEW --- @@ -14,11 +14,11 @@ 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). -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 UI](https://prefab.prefect.io) is the component library behind all FastMCP app features. You describe layouts, charts, tables, and forms in Python, and Prefab compiles them to interactive UIs that render in the host's conversation. -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. +The simplest way to use it: add `app=True` to a tool and return Prefab components. The host renders an interactive UI instead of text. This works for everything from static charts to reactive dashboards with client-side state — no server round-trips needed. -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. +For apps that need server interaction (forms, search, CRUD), see [FastMCPApp](/apps/interactive-apps) which adds managed tool binding on top of Prefab UI. For LLM-generated UIs, see [Generative UI](/apps/generative). ## Getting Started @@ -258,17 +258,16 @@ def team_list() -> PrefabApp: ```python from prefab_ui.app import PrefabApp -from prefab_ui.components import Column, Select, If, Elif, Else, Text +from prefab_ui.components import Column, Select, SelectOption, If, Elif, Else, Text from prefab_ui.rx import Rx tier = Rx("tier") with Column(gap=4) as view: - Select( - name="tier", - label="Plan", - options=["free", "pro", "enterprise"], - ) + with Select(name="tier", label="Plan"): + SelectOption("Free", value="free") + SelectOption("Pro", value="pro") + SelectOption("Enterprise", value="enterprise") with If(tier == "enterprise"): Text("Full access to all features") with Elif(tier == "pro"): @@ -281,37 +280,9 @@ with Column(gap=4) as view: Changes are instant — switching the dropdown re-evaluates the conditions in the browser. -## What You Return +## Giving the LLM Context -### Components - -The simplest approach. Return a component directly and FastMCP wraps it in a `PrefabApp` automatically: - -```python -@mcp.tool(app=True) -def status() -> Column: - with Column(gap=2) as view: - Heading("All Systems Operational") - Badge("Healthy", variant="success") - return view -``` - -### PrefabApp - -When you need initial state or other configuration, return a `PrefabApp` explicitly with a `state` dict: - -```python -@mcp.tool(app=True) -def dashboard() -> PrefabApp: - # ... build view ... - return PrefabApp(view=view, state={"tab": "overview"}) -``` - -### ToolResult - -Every tool result has two audiences: the renderer (which displays the UI) and the LLM (which reads text content to understand what happened). By default, Prefab sends `"[Rendered Prefab UI]"` as the text — which tells the LLM nothing useful. - -If the model needs to reason about the data — reference it in conversation, summarize it, decide what to do next — wrap your return in a `ToolResult`: +By default, Prefab sends `"[Rendered Prefab UI]"` as the text content for the LLM. If the model needs to reason about the data, wrap your return in a `ToolResult` with a meaningful summary: ```python from prefab_ui.app import PrefabApp @@ -339,35 +310,12 @@ def sales_overview(year: int) -> ToolResult: ) ``` -The user sees the chart. The LLM sees the summary string and can reason about it. +The user sees the chart. The LLM sees the summary string. -## Type Inference +## Advanced -If your tool's return type annotation is a Prefab type — `PrefabApp`, `Component`, or unions/optionals containing them — FastMCP detects this and enables app rendering automatically: - -```python -@mcp.tool -def greet(name: str) -> PrefabApp: - return PrefabApp(view=Heading(f"Hello, {name}!")) -``` - -This is equivalent to `@mcp.tool(app=True)`. Explicit `app=True` is recommended for clarity, and required when the return type doesn't reveal a Prefab type (e.g., `-> ToolResult`). - -## How It Works - -When a tool returns a Prefab component or `PrefabApp`, three things happen behind the scenes: - -A **shared renderer** is registered as a `ui://prefab/renderer.html` resource. This is a JavaScript rendering engine that interprets Prefab's JSON component protocol. The host fetches it once and reuses it for all Prefab tools on the server. - -The **tool metadata** is wired so the host knows to load the renderer iframe when displaying the result. This includes Content Security Policy headers that the renderer needs for its dependencies. - -The **component tree** is serialized as `structuredContent` on the tool result. The renderer receives this JSON and renders the UI. - -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: + +`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: ```python from fastmcp.apps import PrefabAppConfig, ResourceCSP @@ -380,10 +328,22 @@ 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 + +If your return type annotation is a Prefab type — `PrefabApp`, `Component`, or unions containing them — FastMCP enables app rendering automatically, even without `app=True`: -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 +@mcp.tool +def greet(name: str) -> PrefabApp: + return PrefabApp(view=Heading(f"Hello, {name}!")) +``` + +Explicit `app=True` is recommended for clarity. + + + +Prefab tools and [custom HTML tools](/apps/low-level) coexist on the same server: ```python from fastmcp.apps import AppConfig @@ -396,6 +356,7 @@ def team_directory() -> PrefabApp: def map_view() -> str: ... ``` + ## Next Steps diff --git a/docs/docs.json b/docs/docs.json index 6545b240b..5e08f3069 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -140,8 +140,7 @@ "servers/providers/proxy", "servers/providers/skills", "servers/providers/custom" - ], - "tag": "NEW" + ] }, { "collapsed": true, @@ -156,8 +155,7 @@ "servers/transforms/tool-search", "servers/transforms/resources-as-tools", "servers/transforms/prompts-as-tools" - ], - "tag": "NEW" + ] }, { "collapsed": true, @@ -171,8 +169,7 @@ "servers/auth/oidc-proxy", "servers/auth/full-oauth-server", "servers/auth/multi-auth" - ], - "tag": "UPDATED" + ] }, "servers/authorization", { @@ -201,11 +198,11 @@ "group": "Reference", "icon": "book", "pages": [ - "apps/components", "apps/patterns", "apps/architecture", "apps/low-level" - ] + ], + "tag": "NEW" } ] }, @@ -381,7 +378,8 @@ "pages": [ "python-sdk/fastmcp-apps-__init__", "python-sdk/fastmcp-apps-app", - "python-sdk/fastmcp-apps-config" + "python-sdk/fastmcp-apps-config", + "python-sdk/fastmcp-apps-generative" ] }, { diff --git a/docs/python-sdk/fastmcp-apps-app.mdx b/docs/python-sdk/fastmcp-apps-app.mdx index 93f93f3e3..759e487ae 100644 --- a/docs/python-sdk/fastmcp-apps-app.mdx +++ b/docs/python-sdk/fastmcp-apps-app.mdx @@ -26,8 +26,8 @@ Usage:: return Column(...) @app.tool() - def save_contact(name: str, email: str) -> dict: - return {"name": name, "email": email} + def save_contact(name: str, email: str) -> str: + return name server = FastMCP("Platform") server.add_provider(app) diff --git a/docs/python-sdk/fastmcp-apps-config.mdx b/docs/python-sdk/fastmcp-apps-config.mdx index aba84b288..d7c5edf2f 100644 --- a/docs/python-sdk/fastmcp-apps-config.mdx +++ b/docs/python-sdk/fastmcp-apps-config.mdx @@ -15,7 +15,7 @@ UI metadata for clients that support interactive app rendering. ## Functions -### `app_config_to_meta_dict` +### `app_config_to_meta_dict` ```python app_config_to_meta_dict(app: AppConfig | dict[str, Any]) -> dict[str, Any] @@ -62,3 +62,29 @@ All fields use ``exclude_none`` serialization so only explicitly-set values appear on the wire. Aliases match the MCP Apps wire format (camelCase). + +### `PrefabAppConfig` + + +App configuration for Prefab tools with sensible defaults. + +Like ``app=True`` but customizable. Auto-wires the Prefab renderer +URI and merges the renderer's CSP with any additional domains you +specify. The renderer resource is registered automatically. + +Example:: + + @mcp.tool(app=PrefabAppConfig()) # same as app=True + + @mcp.tool(app=PrefabAppConfig( + csp=ResourceCSP(frame_domains=["https://example.com"]), + )) + + +**Methods:** + +#### `model_post_init` + +```python +model_post_init(self, __context: Any) -> None +``` diff --git a/docs/python-sdk/fastmcp-apps-generative.mdx b/docs/python-sdk/fastmcp-apps-generative.mdx new file mode 100644 index 000000000..336d4f353 --- /dev/null +++ b/docs/python-sdk/fastmcp-apps-generative.mdx @@ -0,0 +1,56 @@ +--- +title: generative +sidebarTitle: generative +--- + +# `fastmcp.apps.generative` + + +GenerativeUI — a Provider that adds LLM-generated UI capabilities. + +Registers tools and resources from ``prefab_ui.generative`` so that an +LLM can write Prefab Python code, execute it in a sandbox, and render +the result as a streaming interactive UI. + +Requires ``fastmcp[apps]`` (prefab-ui). + +Usage:: + + from fastmcp import FastMCP + from fastmcp.apps.generative import GenerativeUI + + mcp = FastMCP("My Server") + mcp.add_provider(GenerativeUI()) + + +## Classes + +### `GenerativeUI` + + +A Provider that adds generative UI capabilities to a server. + +Registers: + +- A ``generate_ui`` tool that accepts Prefab Python code, executes + it in a Pyodide sandbox, and returns the rendered PrefabApp. + Supports streaming via ``ontoolinputpartial``. +- A ``components`` tool that searches the Prefab component library. +- The generative renderer resource with CSP for Pyodide CDN access. + +Example:: + + from fastmcp import FastMCP + from fastmcp.apps.generative import GenerativeUI + + mcp = FastMCP("My Server") + mcp.add_provider(GenerativeUI()) + + +**Methods:** + +#### `lifespan` + +```python +lifespan(self) -> AsyncIterator[None] +``` diff --git a/docs/python-sdk/fastmcp-cli-apps_dev.mdx b/docs/python-sdk/fastmcp-cli-apps_dev.mdx index 059a40494..5ca1aa6ec 100644 --- a/docs/python-sdk/fastmcp-cli-apps_dev.mdx +++ b/docs/python-sdk/fastmcp-cli-apps_dev.mdx @@ -32,7 +32,7 @@ Startup sequence ## Functions -### `run_dev_apps` +### `run_dev_apps` ```python run_dev_apps(server_spec: str) -> None diff --git a/docs/python-sdk/fastmcp-client-transports-http.mdx b/docs/python-sdk/fastmcp-client-transports-http.mdx index 6f819138c..a0db1401d 100644 --- a/docs/python-sdk/fastmcp-client-transports-http.mdx +++ b/docs/python-sdk/fastmcp-client-transports-http.mdx @@ -10,7 +10,7 @@ Streamable HTTP transport for FastMCP Client. ## Classes -### `StreamableHttpTransport` +### `StreamableHttpTransport` Transport implementation that connects to an MCP server via Streamable HTTP Requests. @@ -18,19 +18,19 @@ Transport implementation that connects to an MCP server via Streamable HTTP Requ **Methods:** -#### `connect_session` +#### `connect_session` ```python connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ClientSession] ``` -#### `get_session_id` +#### `get_session_id` ```python get_session_id(self) -> str | None ``` -#### `close` +#### `close` ```python close(self) diff --git a/docs/python-sdk/fastmcp-client-transports-stdio.mdx b/docs/python-sdk/fastmcp-client-transports-stdio.mdx index eb7d98eb2..ac317bfc3 100644 --- a/docs/python-sdk/fastmcp-client-transports-stdio.mdx +++ b/docs/python-sdk/fastmcp-client-transports-stdio.mdx @@ -30,49 +30,49 @@ connect_session(self, **session_kwargs: Unpack[SessionKwargs]) -> AsyncIterator[ connect(self, **session_kwargs: Unpack[SessionKwargs]) -> ClientSession | None ``` -#### `disconnect` +#### `disconnect` ```python disconnect(self) ``` -#### `close` +#### `close` ```python close(self) ``` -### `PythonStdioTransport` +### `PythonStdioTransport` Transport for running Python scripts. -### `FastMCPStdioTransport` +### `FastMCPStdioTransport` Transport for running FastMCP servers using the FastMCP CLI. -### `NodeStdioTransport` +### `NodeStdioTransport` Transport for running Node.js scripts. -### `UvStdioTransport` +### `UvStdioTransport` Transport for running commands via the uv tool. -### `UvxStdioTransport` +### `UvxStdioTransport` Transport for running commands via the uvx tool. -### `NpxStdioTransport` +### `NpxStdioTransport` Transport for running commands via the npx tool. diff --git a/docs/python-sdk/fastmcp-exceptions.mdx b/docs/python-sdk/fastmcp-exceptions.mdx index d8f5a6871..3494751f7 100644 --- a/docs/python-sdk/fastmcp-exceptions.mdx +++ b/docs/python-sdk/fastmcp-exceptions.mdx @@ -10,61 +10,71 @@ Custom exceptions for FastMCP. ## Classes -### `FastMCPError` +### `FastMCPDeprecationWarning` + + +Deprecation warning for FastMCP APIs. + +Subclass of DeprecationWarning so that standard warning filters +still apply, but FastMCP can selectively enable its own warnings +without affecting other libraries in the process. + + +### `FastMCPError` Base error for FastMCP. -### `ValidationError` +### `ValidationError` Error in validating parameters or return values. -### `ResourceError` +### `ResourceError` Error in resource operations. -### `ToolError` +### `ToolError` Error in tool operations. -### `PromptError` +### `PromptError` Error in prompt operations. -### `InvalidSignature` +### `InvalidSignature` Invalid signature for use with FastMCP. -### `ClientError` +### `ClientError` Error in client operations. -### `NotFoundError` +### `NotFoundError` Object not found. -### `DisabledError` +### `DisabledError` Object is disabled. -### `AuthorizationError` +### `AuthorizationError` Error when authorization check fails. diff --git a/docs/python-sdk/fastmcp-prompts-base.mdx b/docs/python-sdk/fastmcp-prompts-base.mdx index d016286b4..1c57fbea2 100644 --- a/docs/python-sdk/fastmcp-prompts-base.mdx +++ b/docs/python-sdk/fastmcp-prompts-base.mdx @@ -10,7 +10,7 @@ Base classes for FastMCP prompts. ## Classes -### `Message` +### `Message` Wrapper for prompt message with auto-serialization. @@ -21,7 +21,7 @@ Accepts any content - strings pass through, other types **Methods:** -#### `to_mcp_prompt_message` +#### `to_mcp_prompt_message` ```python to_mcp_prompt_message(self) -> PromptMessage @@ -30,13 +30,13 @@ to_mcp_prompt_message(self) -> PromptMessage Convert to MCP PromptMessage. -### `PromptArgument` +### `PromptArgument` An argument that can be passed to a prompt. -### `PromptResult` +### `PromptResult` Canonical result type for prompt rendering. @@ -47,7 +47,7 @@ roles, and metadata at both the message and result level. **Methods:** -#### `to_mcp_prompt_result` +#### `to_mcp_prompt_result` ```python to_mcp_prompt_result(self) -> GetPromptResult @@ -56,7 +56,7 @@ to_mcp_prompt_result(self) -> GetPromptResult Convert to MCP GetPromptResult. -### `Prompt` +### `Prompt` A prompt template that can be rendered with parameters. @@ -64,7 +64,7 @@ A prompt template that can be rendered with parameters. **Methods:** -#### `to_mcp_prompt` +#### `to_mcp_prompt` ```python to_mcp_prompt(self, **overrides: Any) -> SDKPrompt @@ -73,7 +73,7 @@ to_mcp_prompt(self, **overrides: Any) -> SDKPrompt Convert the prompt to an MCP prompt. -#### `from_function` +#### `from_function` ```python from_function(cls, fn: Callable[..., Any]) -> FunctionPrompt @@ -87,7 +87,7 @@ The function can return: - PromptResult: used directly -#### `render` +#### `render` ```python render(self, arguments: dict[str, Any] | None = None) -> str | list[Message | str] | PromptResult @@ -101,7 +101,7 @@ Subclasses must implement this method. Return one of: - PromptResult: Used directly -#### `convert_result` +#### `convert_result` ```python convert_result(self, raw_value: Any) -> PromptResult @@ -113,7 +113,7 @@ Convert a raw return value to PromptResult. - `TypeError`: for unsupported types -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None @@ -122,7 +122,7 @@ register_with_docket(self, docket: Docket) -> None Register this prompt with docket for background execution. -#### `add_to_docket` +#### `add_to_docket` ```python add_to_docket(self, docket: Docket, arguments: dict[str, Any] | None, **kwargs: Any) -> Execution @@ -138,7 +138,7 @@ Schedule this prompt for background execution via docket. - `**kwargs`: Additional kwargs passed to docket.add() -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] diff --git a/docs/python-sdk/fastmcp-resources-base.mdx b/docs/python-sdk/fastmcp-resources-base.mdx index 23735cff5..aab4a1dd7 100644 --- a/docs/python-sdk/fastmcp-resources-base.mdx +++ b/docs/python-sdk/fastmcp-resources-base.mdx @@ -10,7 +10,7 @@ Base classes and interfaces for FastMCP resources. ## Classes -### `ResourceContent` +### `ResourceContent` Wrapper for resource content with optional MIME type and metadata. @@ -21,7 +21,7 @@ other types (dict, list, BaseModel, etc.) are automatically JSON-serialized. **Methods:** -#### `to_mcp_resource_contents` +#### `to_mcp_resource_contents` ```python to_mcp_resource_contents(self, uri: AnyUrl | str) -> mcp.types.TextResourceContents | mcp.types.BlobResourceContents @@ -36,7 +36,7 @@ Convert to MCP resource contents type. - TextResourceContents for str content, BlobResourceContents for bytes -### `ResourceResult` +### `ResourceResult` Canonical result type for resource reads. @@ -47,7 +47,7 @@ per-item MIME types, and metadata at both the item and result level. **Methods:** -#### `to_mcp_result` +#### `to_mcp_result` ```python to_mcp_result(self, uri: AnyUrl | str) -> mcp.types.ReadResourceResult @@ -62,7 +62,7 @@ Convert to MCP ReadResourceResult. - MCP ReadResourceResult with converted contents -### `Resource` +### `Resource` Base class for all resources. @@ -70,13 +70,13 @@ Base class for all resources. **Methods:** -#### `from_function` +#### `from_function` ```python from_function(cls, fn: Callable[..., Any], uri: str | AnyUrl) -> FunctionResource ``` -#### `set_default_mime_type` +#### `set_default_mime_type` ```python set_default_mime_type(cls, mime_type: str | None) -> str @@ -85,7 +85,7 @@ set_default_mime_type(cls, mime_type: str | None) -> str Set default MIME type if not provided. -#### `set_default_name` +#### `set_default_name` ```python set_default_name(self) -> Self @@ -94,7 +94,7 @@ set_default_name(self) -> Self Set default name from URI if not provided. -#### `read` +#### `read` ```python read(self) -> str | bytes | ResourceResult @@ -108,7 +108,7 @@ Subclasses implement this to return resource data. Supported return types: - ResourceResult: Full control over contents and result-level meta -#### `convert_result` +#### `convert_result` ```python convert_result(self, raw_value: Any) -> ResourceResult @@ -131,7 +131,7 @@ MCP Apps CSP/permissions) is propagated to each content item so that hosts can read it from the ``resources/read`` response. -#### `to_mcp_resource` +#### `to_mcp_resource` ```python to_mcp_resource(self, **overrides: Any) -> SDKResource @@ -140,7 +140,7 @@ to_mcp_resource(self, **overrides: Any) -> SDKResource Convert the resource to an SDKResource. -#### `key` +#### `key` ```python key(self) -> str @@ -149,7 +149,7 @@ key(self) -> str The globally unique lookup key for this resource. -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None @@ -158,7 +158,7 @@ register_with_docket(self, docket: Docket) -> None Register this resource with docket for background execution. -#### `add_to_docket` +#### `add_to_docket` ```python add_to_docket(self, docket: Docket, **kwargs: Any) -> Execution @@ -173,7 +173,7 @@ Schedule this resource for background execution via docket. - `**kwargs`: Additional kwargs passed to docket.add() -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] diff --git a/docs/python-sdk/fastmcp-resources-function_resource.mdx b/docs/python-sdk/fastmcp-resources-function_resource.mdx index ebdad0fd9..4977f0d58 100644 --- a/docs/python-sdk/fastmcp-resources-function_resource.mdx +++ b/docs/python-sdk/fastmcp-resources-function_resource.mdx @@ -10,7 +10,7 @@ Standalone @resource decorator for FastMCP. ## Functions -### `resource` +### `resource` ```python resource(uri: str) -> Callable[[F], F] @@ -25,19 +25,19 @@ using mcp.add_resource(). ## Classes -### `DecoratedResource` +### `DecoratedResource` Protocol for functions decorated with @resource. -### `ResourceMeta` +### `ResourceMeta` Metadata attached to functions by the @resource decorator. -### `FunctionResource` +### `FunctionResource` A resource that defers data loading by wrapping a function. @@ -54,7 +54,7 @@ The function can return: **Methods:** -#### `from_function` +#### `from_function` ```python from_function(cls, fn: Callable[..., Any], uri: str | AnyUrl | None = None) -> FunctionResource @@ -71,7 +71,7 @@ individual parameters must not be passed. Cannot be used together with metadata parameter. -#### `read` +#### `read` ```python read(self) -> str | bytes | ResourceResult @@ -80,7 +80,7 @@ read(self) -> str | bytes | ResourceResult Read the resource by calling the wrapped function. -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None diff --git a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx index 19b6a9a01..6301e4c28 100644 --- a/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx +++ b/docs/python-sdk/fastmcp-server-auth-providers-azure.mdx @@ -14,7 +14,7 @@ using the OAuth Proxy pattern for non-DCR OAuth flows. ## Functions -### `EntraOBOToken` +### `EntraOBOToken` ```python EntraOBOToken(scopes: list[str]) -> str @@ -43,7 +43,7 @@ or OBO exchange fails ## Classes -### `AzureProvider` +### `AzureProvider` Azure (Microsoft Entra) OAuth provider for FastMCP. @@ -78,7 +78,7 @@ Setup: **Methods:** -#### `authorize` +#### `authorize` ```python authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str @@ -98,7 +98,7 @@ scopes to determine the resource/audience instead of a separate parameter. - Authorization URL to redirect the user to Azure AD -#### `get_obo_credential` +#### `get_obo_credential` ```python get_obo_credential(self, user_assertion: str) -> OnBehalfOfCredential @@ -120,7 +120,7 @@ calls multiple tools with the same scopes. - `ImportError`: If azure-identity is not installed (requires fastmcp[azure]). -#### `close_obo_credentials` +#### `close_obo_credentials` ```python close_obo_credentials(self) -> None @@ -129,7 +129,7 @@ close_obo_credentials(self) -> None Close all cached OBO credentials. -### `AzureJWTVerifier` +### `AzureJWTVerifier` JWT verifier pre-configured for Azure AD / Microsoft Entra ID. @@ -166,7 +166,7 @@ Example:: **Methods:** -#### `scopes_supported` +#### `scopes_supported` ```python scopes_supported(self) -> list[str] diff --git a/docs/python-sdk/fastmcp-server-dependencies.mdx b/docs/python-sdk/fastmcp-server-dependencies.mdx index 6f35aba31..f23d5ec59 100644 --- a/docs/python-sdk/fastmcp-server-dependencies.mdx +++ b/docs/python-sdk/fastmcp-server-dependencies.mdx @@ -15,7 +15,7 @@ CurrentWorker) and background task execution require fastmcp[tasks]. ## Functions -### `get_task_context` +### `get_task_context` ```python get_task_context() -> TaskContextInfo | None @@ -31,7 +31,7 @@ Returns None if not running in a task context (e.g., foreground execution). - TaskContextInfo with task_id and session_id, or None if not in a task. -### `register_task_session` +### `register_task_session` ```python register_task_session(session_id: str, session: ServerSession) -> None @@ -49,7 +49,7 @@ client disconnects. - `session`: The ServerSession instance -### `get_task_session` +### `get_task_session` ```python get_task_session(session_id: str) -> ServerSession | None @@ -65,7 +65,24 @@ Get a registered session by ID if still alive. - The ServerSession if found and alive, None otherwise -### `is_docket_available` +### `register_task_server` + +```python +register_task_server(task_id: str, server: FastMCP) -> None +``` + + +Register the server for a background task. + +Called at task-submission time (inside the child server's call_tool +context) so that background workers can resolve CurrentFastMCP() and +ctx.fastmcp to the child server for mounted tasks. + +The map is bounded to avoid unbounded growth in long-lived servers. +Evicted entries fall back to the ContextVar (parent server). + + +### `is_docket_available` ```python is_docket_available() -> bool @@ -75,7 +92,7 @@ is_docket_available() -> bool Check if pydocket is installed. -### `require_docket` +### `require_docket` ```python require_docket(feature: str) -> None @@ -89,7 +106,7 @@ Raise ImportError with install instructions if docket not available. "CurrentDocket()"). Will be included in the error message. -### `transform_context_annotations` +### `transform_context_annotations` ```python transform_context_annotations(fn: Callable[..., Any]) -> Callable[..., Any] @@ -115,7 +132,7 @@ allows them to have defaults in any order. - Function with modified signature (same function object, updated __signature__) -### `get_context` +### `get_context` ```python get_context() -> Context @@ -125,7 +142,7 @@ get_context() -> Context Get the current FastMCP Context instance directly. -### `get_server` +### `get_server` ```python get_server() -> FastMCP @@ -134,6 +151,10 @@ get_server() -> FastMCP Get the current FastMCP server instance directly. +In a background-task worker, checks the task-server map first so that +mounted-child tasks resolve to the child server (not the parent that +started the worker). + **Returns:** - The active FastMCP server @@ -141,7 +162,7 @@ Get the current FastMCP server instance directly. - `RuntimeError`: If no server in context -### `get_http_request` +### `get_http_request` ```python get_http_request() -> Request @@ -153,7 +174,7 @@ Get the current HTTP request. Tries MCP SDK's request_ctx first, then falls back to FastMCP's HTTP context. -### `get_http_headers` +### `get_http_headers` ```python get_http_headers(include_all: bool = False, include: set[str] | None = None) -> dict[str, str] @@ -174,7 +195,7 @@ normally be excluded. This is useful for proxy transports that need to forward authorization headers to upstream MCP servers. -### `get_access_token` +### `get_access_token` ```python get_access_token() -> AccessToken | None @@ -193,7 +214,7 @@ token snapshot stored in Redis at task submission time. - The access token if an authenticated user is available, None otherwise. -### `without_injected_parameters` +### `without_injected_parameters` ```python without_injected_parameters(fn: Callable[..., Any]) -> Callable[..., Any] @@ -218,7 +239,7 @@ Handles: - Async wrapper function without injected parameters -### `resolve_dependencies` +### `resolve_dependencies` ```python resolve_dependencies(fn: Callable[..., Any], arguments: dict[str, Any]) -> AsyncGenerator[dict[str, Any], None] @@ -244,7 +265,7 @@ time, so all injection goes through the unified DI system. which will be filtered out) -### `CurrentContext` +### `CurrentContext` ```python CurrentContext() -> Context @@ -263,7 +284,7 @@ current MCP operation (tool/resource/prompt call). - `RuntimeError`: If no active context found (during resolution) -### `OptionalCurrentContext` +### `OptionalCurrentContext` ```python OptionalCurrentContext() -> Context | None @@ -273,7 +294,7 @@ OptionalCurrentContext() -> Context | None Get the current FastMCP Context, or None when no context is active. -### `CurrentDocket` +### `CurrentDocket` ```python CurrentDocket() -> Docket @@ -293,7 +314,7 @@ automatically creates for background task scheduling. - `ImportError`: If fastmcp[tasks] not installed -### `CurrentWorker` +### `CurrentWorker` ```python CurrentWorker() -> Worker @@ -313,7 +334,7 @@ automatically creates for background task processing. - `ImportError`: If fastmcp[tasks] not installed -### `CurrentFastMCP` +### `CurrentFastMCP` ```python CurrentFastMCP() -> FastMCP @@ -331,7 +352,7 @@ This dependency provides access to the active FastMCP server. - `RuntimeError`: If no server in context (during resolution) -### `CurrentRequest` +### `CurrentRequest` ```python CurrentRequest() -> Request @@ -351,7 +372,7 @@ current HTTP request. Only available when running over HTTP transports - `RuntimeError`: If no HTTP request in context (e.g., STDIO transport) -### `CurrentHeaders` +### `CurrentHeaders` ```python CurrentHeaders() -> dict[str, str] @@ -369,7 +390,7 @@ transport. - A dependency that resolves to a dictionary of header name -> value -### `CurrentAccessToken` +### `CurrentAccessToken` ```python CurrentAccessToken() -> AccessToken @@ -388,7 +409,7 @@ authenticated request. Raises an error if no authentication is present. - `RuntimeError`: If no authenticated user (use get_access_token() for optional) -### `TokenClaim` +### `TokenClaim` ```python TokenClaim(name: str) -> str @@ -413,7 +434,7 @@ without needing the full token object. ## Classes -### `TaskContextInfo` +### `TaskContextInfo` Information about the current background task context. @@ -422,7 +443,7 @@ Returned by ``get_task_context()`` when running inside a Docket worker. Contains identifiers needed to communicate with the MCP session. -### `ProgressLike` +### `ProgressLike` Protocol for progress tracking interface. @@ -433,7 +454,7 @@ and Docket's Progress (worker context). **Methods:** -#### `current` +#### `current` ```python current(self) -> int | None @@ -442,7 +463,7 @@ current(self) -> int | None Current progress value. -#### `total` +#### `total` ```python total(self) -> int @@ -451,7 +472,7 @@ total(self) -> int Total/target progress value. -#### `message` +#### `message` ```python message(self) -> str | None @@ -460,7 +481,7 @@ message(self) -> str | None Current progress message. -#### `set_total` +#### `set_total` ```python set_total(self, total: int) -> None @@ -469,7 +490,7 @@ set_total(self, total: int) -> None Set the total/target value for progress tracking. -#### `increment` +#### `increment` ```python increment(self, amount: int = 1) -> None @@ -478,7 +499,7 @@ increment(self, amount: int = 1) -> None Atomically increment the current progress value. -#### `set_message` +#### `set_message` ```python set_message(self, message: str | None) -> None @@ -487,7 +508,7 @@ set_message(self, message: str | None) -> None Update the progress status message. -### `InMemoryProgress` +### `InMemoryProgress` In-memory progress tracker for immediate tool execution. @@ -499,25 +520,25 @@ progress doesn't need to be observable across processes. **Methods:** -#### `current` +#### `current` ```python current(self) -> int | None ``` -#### `total` +#### `total` ```python total(self) -> int ``` -#### `message` +#### `message` ```python message(self) -> str | None ``` -#### `set_total` +#### `set_total` ```python set_total(self, total: int) -> None @@ -526,7 +547,7 @@ set_total(self, total: int) -> None Set the total/target value for progress tracking. -#### `increment` +#### `increment` ```python increment(self, amount: int = 1) -> None @@ -535,7 +556,7 @@ increment(self, amount: int = 1) -> None Atomically increment the current progress value. -#### `set_message` +#### `set_message` ```python set_message(self, message: str | None) -> None @@ -544,7 +565,7 @@ set_message(self, message: str | None) -> None Update the progress status message. -### `Progress` +### `Progress` FastMCP Progress dependency that works in both server and worker contexts. @@ -561,7 +582,7 @@ is installed. **Methods:** -#### `current` +#### `current` ```python current(self) -> int | None @@ -570,7 +591,7 @@ current(self) -> int | None Current progress value. -#### `total` +#### `total` ```python total(self) -> int @@ -579,7 +600,7 @@ total(self) -> int Total/target progress value. -#### `message` +#### `message` ```python message(self) -> str | None @@ -588,7 +609,7 @@ message(self) -> str | None Current progress message. -#### `set_total` +#### `set_total` ```python set_total(self, total: int) -> None @@ -597,7 +618,7 @@ set_total(self, total: int) -> None Set the total/target value for progress tracking. -#### `increment` +#### `increment` ```python increment(self, amount: int = 1) -> None @@ -606,7 +627,7 @@ increment(self, amount: int = 1) -> None Atomically increment the current progress value. -#### `set_message` +#### `set_message` ```python set_message(self, message: str | None) -> None diff --git a/docs/python-sdk/fastmcp-server-middleware-tool_injection.mdx b/docs/python-sdk/fastmcp-server-middleware-tool_injection.mdx index 1f7b25ebd..549150689 100644 --- a/docs/python-sdk/fastmcp-server-middleware-tool_injection.mdx +++ b/docs/python-sdk/fastmcp-server-middleware-tool_injection.mdx @@ -10,7 +10,7 @@ A middleware for injecting tools into the MCP server context. ## Functions -### `list_prompts` +### `list_prompts` ```python list_prompts(context: Context) -> list[Prompt] @@ -20,7 +20,7 @@ list_prompts(context: Context) -> list[Prompt] List prompts available on the server. -### `get_prompt` +### `get_prompt` ```python get_prompt(context: Context, name: Annotated[str, 'The name of the prompt to render.'], arguments: Annotated[dict[str, Any] | None, 'The arguments to pass to the prompt.'] = None) -> mcp.types.GetPromptResult @@ -30,7 +30,7 @@ get_prompt(context: Context, name: Annotated[str, 'The name of the prompt to ren Render a prompt available on the server. -### `list_resources` +### `list_resources` ```python list_resources(context: Context) -> list[mcp.types.Resource] @@ -40,7 +40,7 @@ list_resources(context: Context) -> list[mcp.types.Resource] List resources available on the server. -### `read_resource` +### `read_resource` ```python read_resource(context: Context, uri: Annotated[AnyUrl | str, 'The URI of the resource to read.']) -> ResourceResult @@ -52,7 +52,7 @@ Read a resource available on the server. ## Classes -### `ToolInjectionMiddleware` +### `ToolInjectionMiddleware` A middleware for injecting tools into the context. @@ -60,7 +60,7 @@ A middleware for injecting tools into the context. **Methods:** -#### `on_list_tools` +#### `on_list_tools` ```python on_list_tools(self, context: MiddlewareContext[mcp.types.ListToolsRequest], call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]]) -> Sequence[Tool] @@ -69,7 +69,7 @@ on_list_tools(self, context: MiddlewareContext[mcp.types.ListToolsRequest], call Inject tools into the response. -#### `on_call_tool` +#### `on_call_tool` ```python on_call_tool(self, context: MiddlewareContext[mcp.types.CallToolRequestParams], call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult]) -> ToolResult @@ -78,7 +78,7 @@ on_call_tool(self, context: MiddlewareContext[mcp.types.CallToolRequestParams], Intercept tool calls to injected tools. -### `PromptToolMiddleware` +### `PromptToolMiddleware` A middleware for injecting prompts as tools into the context. @@ -87,7 +87,7 @@ A middleware for injecting prompts as tools into the context. Use ``fastmcp.server.transforms.PromptsAsTools`` instead. -### `ResourceToolMiddleware` +### `ResourceToolMiddleware` A middleware for injecting resources as tools into the context. diff --git a/docs/python-sdk/fastmcp-server-openapi-server.mdx b/docs/python-sdk/fastmcp-server-openapi-server.mdx index 4f751e090..374eac2ba 100644 --- a/docs/python-sdk/fastmcp-server-openapi-server.mdx +++ b/docs/python-sdk/fastmcp-server-openapi-server.mdx @@ -21,7 +21,7 @@ This class is deprecated. Use FastMCP with OpenAPIProvider instead: ## Classes -### `FastMCPOpenAPI` +### `FastMCPOpenAPI` FastMCP server implementation that creates components from an OpenAPI schema. diff --git a/docs/python-sdk/fastmcp-server-providers-fastmcp_provider.mdx b/docs/python-sdk/fastmcp-server-providers-fastmcp_provider.mdx index c30b79175..e3e70093e 100644 --- a/docs/python-sdk/fastmcp-server-providers-fastmcp_provider.mdx +++ b/docs/python-sdk/fastmcp-server-providers-fastmcp_provider.mdx @@ -39,7 +39,7 @@ wrap(cls, server: Any, tool: Tool) -> FastMCPProviderTool Wrap a Tool to delegate execution to the server's middleware. -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -51,13 +51,13 @@ This is called when the tool is used within a TransformedTool forwarding function or other contexts where task_meta is not available. -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] ``` -### `FastMCPProviderResource` +### `FastMCPProviderResource` Resource that delegates reading to a wrapped server's read_resource(). @@ -68,7 +68,7 @@ When `read()` is called, this resource invokes the wrapped server's **Methods:** -#### `wrap` +#### `wrap` ```python wrap(cls, server: Any, resource: Resource) -> FastMCPProviderResource @@ -77,13 +77,13 @@ wrap(cls, server: Any, resource: Resource) -> FastMCPProviderResource Wrap a Resource to delegate reading to the server's middleware. -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] ``` -### `FastMCPProviderPrompt` +### `FastMCPProviderPrompt` Prompt that delegates rendering to a wrapped server's render_prompt(). @@ -94,7 +94,7 @@ When `render()` is called, this prompt invokes the wrapped server's **Methods:** -#### `wrap` +#### `wrap` ```python wrap(cls, server: Any, prompt: Prompt) -> FastMCPProviderPrompt @@ -103,7 +103,7 @@ wrap(cls, server: Any, prompt: Prompt) -> FastMCPProviderPrompt Wrap a Prompt to delegate rendering to the server's middleware. -#### `render` +#### `render` ```python render(self, arguments: dict[str, Any] | None = None) -> PromptResult @@ -115,13 +115,13 @@ This is called when the prompt is used within a transformed context or other contexts where task_meta is not available. -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] ``` -### `FastMCPProviderResourceTemplate` +### `FastMCPProviderResourceTemplate` Resource template that creates FastMCPProviderResources. @@ -133,7 +133,7 @@ when read. **Methods:** -#### `wrap` +#### `wrap` ```python wrap(cls, server: Any, template: ResourceTemplate) -> FastMCPProviderResourceTemplate @@ -142,7 +142,7 @@ wrap(cls, server: Any, template: ResourceTemplate) -> FastMCPProviderResourceTem Wrap a ResourceTemplate to create FastMCPProviderResources. -#### `create_resource` +#### `create_resource` ```python create_resource(self, uri: str, params: dict[str, Any]) -> Resource @@ -155,7 +155,7 @@ We use `_original_uri_template` with `params` to construct the internal URI that the nested server understands. -#### `read` +#### `read` ```python read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult @@ -167,7 +167,7 @@ Reads the resource via the wrapped server and returns the ResourceResult. This method is called by Docket during background task execution. -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None @@ -176,7 +176,7 @@ register_with_docket(self, docket: Docket) -> None No-op: the child's actual template is registered via get_tasks(). -#### `add_to_docket` +#### `add_to_docket` ```python add_to_docket(self, docket: Docket, params: dict[str, Any], **kwargs: Any) -> Execution @@ -188,13 +188,13 @@ The child's FunctionResourceTemplate.fn is registered (via get_tasks), and it expects splatted **kwargs, so we splat params here. -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] ``` -### `FastMCPProvider` +### `FastMCPProvider` Provider that wraps a FastMCP server. @@ -210,7 +210,7 @@ This ensures middleware runs when components are executed. **Methods:** -#### `get_app_tool` +#### `get_app_tool` ```python get_app_tool(self, app_name: str, tool_name: str) -> Tool | None @@ -219,7 +219,7 @@ get_app_tool(self, app_name: str, tool_name: str) -> Tool | None Delegate to nested server's get_app_tool, wrapping for middleware. -#### `get_tasks` +#### `get_tasks` ```python get_tasks(self) -> Sequence[FastMCPComponent] @@ -233,7 +233,7 @@ server's transforms applied, then applies this provider's transforms for correct registration keys. -#### `lifespan` +#### `lifespan` ```python lifespan(self) -> AsyncIterator[None] diff --git a/docs/python-sdk/fastmcp-server-providers-filesystem_discovery.mdx b/docs/python-sdk/fastmcp-server-providers-filesystem_discovery.mdx index 8f37fb1eb..2a4595b89 100644 --- a/docs/python-sdk/fastmcp-server-providers-filesystem_discovery.mdx +++ b/docs/python-sdk/fastmcp-server-providers-filesystem_discovery.mdx @@ -16,7 +16,7 @@ This module provides functions to: ## Functions -### `discover_files` +### `discover_files` ```python discover_files(root: Path) -> list[Path] @@ -34,10 +34,10 @@ Excludes __init__.py files (they're for package structure, not components). - List of .py file paths, sorted for deterministic order. -### `import_module_from_file` +### `import_module_from_file` ```python -import_module_from_file(file_path: Path) -> ModuleType +import_module_from_file(file_path: Path, provider_root: Path | None = None) -> ModuleType ``` @@ -47,8 +47,13 @@ If the file is part of a package (directory has __init__.py), imports it as a proper package member (relative imports work). Otherwise, imports directly using spec_from_file_location. +sys.path is modified only for the duration of the import and restored +immediately after, so no permanent pollution occurs. + **Args:** - `file_path`: Path to the Python file. +- `provider_root`: The provider's root directory. Prevents package root +discovery from walking above this boundary into ancestor packages. **Returns:** - The imported module. @@ -57,7 +62,7 @@ imports directly using spec_from_file_location. - `ImportError`: If the module cannot be imported. -### `extract_components` +### `extract_components` ```python extract_components(module: ModuleType) -> list[FastMCPComponent] @@ -77,7 +82,7 @@ or functions decorated with @tool/@resource/@prompt that have __fastmcp__ metada - List of component objects (Tool, Resource, ResourceTemplate, Prompt). -### `discover_and_import` +### `discover_and_import` ```python discover_and_import(root: Path) -> DiscoveryResult @@ -97,7 +102,7 @@ This is the main entry point for filesystem-based discovery. ## Classes -### `DiscoveryResult` +### `DiscoveryResult` Result of filesystem discovery. diff --git a/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-tools.mdx b/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-tools.mdx index efeae3661..802adb6e0 100644 --- a/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-tools.mdx +++ b/docs/python-sdk/fastmcp-server-providers-local_provider-decorators-tools.mdx @@ -14,7 +14,7 @@ registration functionality to LocalProvider. ## Classes -### `ToolDecoratorMixin` +### `ToolDecoratorMixin` Mixin class providing tool decorator functionality for LocalProvider. @@ -26,7 +26,7 @@ This mixin contains all methods related to: **Methods:** -#### `add_tool` +#### `add_tool` ```python add_tool(self: LocalProvider, tool: Tool | Callable[..., Any]) -> Tool @@ -37,19 +37,19 @@ Add a tool to this provider's storage. Accepts either a Tool object or a decorated function with __fastmcp__ metadata. -#### `tool` +#### `tool` ```python tool(self: LocalProvider, name_or_fn: F) -> F ``` -#### `tool` +#### `tool` ```python tool(self: LocalProvider, name_or_fn: str | None = None) -> Callable[[F], F] ``` -#### `tool` +#### `tool` ```python tool(self: LocalProvider, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool | partial[Callable[[AnyFunction], FunctionTool] | FunctionTool] diff --git a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx index 951ff9a7a..f16ecaaeb 100644 --- a/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx +++ b/docs/python-sdk/fastmcp-server-providers-openapi-components.mdx @@ -10,7 +10,7 @@ OpenAPI component classes: Tool, Resource, and ResourceTemplate. ## Classes -### `OpenAPITool` +### `OpenAPITool` Tool implementation for OpenAPI endpoints. @@ -18,7 +18,7 @@ Tool implementation for OpenAPI endpoints. **Methods:** -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -27,7 +27,7 @@ run(self, arguments: dict[str, Any]) -> ToolResult Execute the HTTP request using RequestDirector. -### `OpenAPIResource` +### `OpenAPIResource` Resource implementation for OpenAPI endpoints. @@ -35,7 +35,7 @@ Resource implementation for OpenAPI endpoints. **Methods:** -#### `read` +#### `read` ```python read(self) -> ResourceResult @@ -44,7 +44,7 @@ read(self) -> ResourceResult Fetch the resource data by making an HTTP request. -### `OpenAPIResourceTemplate` +### `OpenAPIResourceTemplate` Resource template implementation for OpenAPI endpoints. @@ -52,7 +52,7 @@ Resource template implementation for OpenAPI endpoints. **Methods:** -#### `create_resource` +#### `create_resource` ```python create_resource(self, uri: str, params: dict[str, Any], context: Context | None = None) -> Resource diff --git a/docs/python-sdk/fastmcp-server-server.mdx b/docs/python-sdk/fastmcp-server-server.mdx index 287480c9c..2e7832961 100644 --- a/docs/python-sdk/fastmcp-server-server.mdx +++ b/docs/python-sdk/fastmcp-server-server.mdx @@ -10,7 +10,7 @@ FastMCP - A more ergonomic interface for MCP servers. ## Functions -### `default_lifespan` +### `default_lifespan` ```python default_lifespan(server: FastMCP[LifespanResultT]) -> AsyncIterator[Any] @@ -26,7 +26,7 @@ Default lifespan context manager that does nothing. - An empty dictionary as the lifespan result. -### `create_proxy` +### `create_proxy` ```python create_proxy(target: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy @@ -54,53 +54,53 @@ use `FastMCPProxy` or `ProxyProvider` directly from `fastmcp.server.providers.pr ## Classes -### `StateValue` +### `StateValue` Wrapper for stored context state values. -### `FastMCP` +### `FastMCP` **Methods:** -#### `name` +#### `name` ```python name(self) -> str ``` -#### `instructions` +#### `instructions` ```python instructions(self) -> str | None ``` -#### `instructions` +#### `instructions` ```python instructions(self, value: str | None) -> None ``` -#### `version` +#### `version` ```python version(self) -> str | None ``` -#### `website_url` +#### `website_url` ```python website_url(self) -> str | None ``` -#### `icons` +#### `icons` ```python icons(self) -> list[mcp.types.Icon] ``` -#### `local_provider` +#### `local_provider` ```python local_provider(self) -> LocalProvider @@ -115,13 +115,13 @@ Use this to remove components: mcp.local_provider.remove_prompt("my_prompt") -#### `add_middleware` +#### `add_middleware` ```python add_middleware(self, middleware: Middleware) -> None ``` -#### `add_provider` +#### `add_provider` ```python add_provider(self, provider: Provider) -> None @@ -141,7 +141,7 @@ always take precedence over providers. - Prompts become "namespace_promptname" -#### `get_tasks` +#### `get_tasks` ```python get_tasks(self) -> Sequence[FastMCPComponent] @@ -153,7 +153,7 @@ Overrides AggregateProvider.get_tasks() to apply server-level transforms after aggregation. AggregateProvider handles provider-level namespacing. -#### `add_transform` +#### `add_transform` ```python add_transform(self, transform: Transform) -> None @@ -168,7 +168,7 @@ They transform tools, resources, and prompts from ALL providers. - `transform`: The transform to add. -#### `add_tool_transformation` +#### `add_tool_transformation` ```python add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None @@ -180,7 +180,7 @@ Add a tool transformation. Use ``add_transform(ToolTransform({...}))`` instead. -#### `remove_tool_transformation` +#### `remove_tool_transformation` ```python remove_tool_transformation(self, _tool_name: str) -> None @@ -192,7 +192,7 @@ Remove a tool transformation. Tool transformations are now immutable. Use enable/disable controls instead. -#### `list_tools` +#### `list_tools` ```python list_tools(self) -> Sequence[Tool] @@ -205,7 +205,7 @@ and middleware execution. Returns all versions (no deduplication). Protocol handlers deduplicate for MCP wire format. -#### `get_tool` +#### `get_tool` ```python get_tool(self, name: str, version: VersionSpec | None = None) -> Tool | None @@ -228,7 +228,7 @@ requested, falls back to the next-highest enabled version. - The tool if found and enabled, None otherwise. -#### `list_resources` +#### `list_resources` ```python list_resources(self) -> Sequence[Resource] @@ -241,7 +241,7 @@ and middleware execution. Returns all versions (no deduplication). Protocol handlers deduplicate for MCP wire format. -#### `get_resource` +#### `get_resource` ```python get_resource(self, uri: str, version: VersionSpec | None = None) -> Resource | None @@ -263,7 +263,7 @@ requested, falls back to the next-highest enabled version. - The resource if found and enabled, None otherwise. -#### `list_resource_templates` +#### `list_resource_templates` ```python list_resource_templates(self) -> Sequence[ResourceTemplate] @@ -276,7 +276,7 @@ auth filtering, and middleware execution. Returns all versions (no deduplication Protocol handlers deduplicate for MCP wire format. -#### `get_resource_template` +#### `get_resource_template` ```python get_resource_template(self, uri: str, version: VersionSpec | None = None) -> ResourceTemplate | None @@ -298,7 +298,7 @@ requested, falls back to the next-highest enabled version. - The template if found and enabled, None otherwise. -#### `list_prompts` +#### `list_prompts` ```python list_prompts(self) -> Sequence[Prompt] @@ -311,7 +311,7 @@ and middleware execution. Returns all versions (no deduplication). Protocol handlers deduplicate for MCP wire format. -#### `get_prompt` +#### `get_prompt` ```python get_prompt(self, name: str, version: VersionSpec | None = None) -> Prompt | None @@ -333,19 +333,19 @@ requested, falls back to the next-highest enabled version. - The prompt if found and enabled, None otherwise. -#### `call_tool` +#### `call_tool` ```python call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult ``` -#### `call_tool` +#### `call_tool` ```python call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult ``` -#### `call_tool` +#### `call_tool` ```python call_tool(self, name: str, arguments: dict[str, Any] | None = None) -> ToolResult | mcp.types.CreateTaskResult @@ -378,19 +378,19 @@ tool registry, bypassing transforms. - `ValidationError`: If arguments fail validation -#### `read_resource` +#### `read_resource` ```python read_resource(self, uri: str) -> ResourceResult ``` -#### `read_resource` +#### `read_resource` ```python read_resource(self, uri: str) -> mcp.types.CreateTaskResult ``` -#### `read_resource` +#### `read_resource` ```python read_resource(self, uri: str) -> ResourceResult | mcp.types.CreateTaskResult @@ -419,19 +419,19 @@ return ResourceResult. - `ResourceError`: If resource read fails -#### `render_prompt` +#### `render_prompt` ```python render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult ``` -#### `render_prompt` +#### `render_prompt` ```python render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> mcp.types.CreateTaskResult ``` -#### `render_prompt` +#### `render_prompt` ```python render_prompt(self, name: str, arguments: dict[str, Any] | None = None) -> PromptResult | mcp.types.CreateTaskResult @@ -461,7 +461,7 @@ return PromptResult. - `PromptError`: If prompt rendering fails -#### `add_tool` +#### `add_tool` ```python add_tool(self, tool: Tool | Callable[..., Any]) -> Tool @@ -479,7 +479,7 @@ with the Context type annotation. See the @tool decorator for examples. - The tool instance that was added to the server. -#### `remove_tool` +#### `remove_tool` ```python remove_tool(self, name: str, version: str | None = None) -> None @@ -498,19 +498,19 @@ Remove tool(s) from the server. - `NotFoundError`: If no matching tool is found. -#### `tool` +#### `tool` ```python tool(self, name_or_fn: F) -> F ``` -#### `tool` +#### `tool` ```python tool(self, name_or_fn: str | None = None) -> Callable[[F], F] ``` -#### `tool` +#### `tool` ```python tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool | partial[Callable[[AnyFunction], FunctionTool] | FunctionTool] @@ -566,7 +566,7 @@ server.tool(my_function, name="custom_name") ``` -#### `add_resource` +#### `add_resource` ```python add_resource(self, resource: Resource | Callable[..., Any]) -> Resource | ResourceTemplate @@ -581,7 +581,7 @@ Add a resource to the server. - The resource instance that was added to the server. -#### `add_template` +#### `add_template` ```python add_template(self, template: ResourceTemplate) -> ResourceTemplate @@ -596,7 +596,7 @@ Add a resource template to the server. - The template instance that was added to the server. -#### `resource` +#### `resource` ```python resource(self, uri: str) -> Callable[[F], F] @@ -655,7 +655,7 @@ async def get_weather(city: str) -> str: ``` -#### `add_prompt` +#### `add_prompt` ```python add_prompt(self, prompt: Prompt | Callable[..., Any]) -> Prompt @@ -670,19 +670,19 @@ Add a prompt to the server. - The prompt instance that was added to the server. -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: F) -> F ``` -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: str | None = None) -> Callable[[F], F] ``` -#### `prompt` +#### `prompt` ```python prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt | partial[Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt] @@ -759,7 +759,7 @@ Decorator to register a prompt. ``` -#### `mount` +#### `mount` ```python mount(self, server: FastMCP[LifespanResultT], namespace: str | None = None, as_proxy: bool | None = None, tool_names: dict[str, str] | None = None, prefix: str | None = None) -> None @@ -806,7 +806,7 @@ mounted server. - `prefix`: Deprecated. Use namespace instead. -#### `import_server` +#### `import_server` ```python import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None) -> None @@ -847,7 +847,7 @@ templates, and prompts are imported with their original names. objects are imported with their original names. -#### `from_openapi` +#### `from_openapi` ```python from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient | None = None, name: str = 'OpenAPI Server', route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, validate_output: bool = True, **settings: Any) -> Self @@ -876,7 +876,7 @@ response structure while still returning structured JSON. - A FastMCP server with an OpenAPIProvider attached. -#### `from_fastapi` +#### `from_fastapi` ```python from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | None = None, route_map_fn: OpenAPIRouteMapFn | None = None, mcp_component_fn: OpenAPIComponentFn | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> Self @@ -900,7 +900,7 @@ Use this to configure timeout and other client settings. - A FastMCP server with an OpenAPIProvider attached. -#### `as_proxy` +#### `as_proxy` ```python as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | FastMCP1Server | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy @@ -918,7 +918,7 @@ instance or any value accepted as the `transport` argument of `fastmcp.client.Client` constructor. -#### `generate_name` +#### `generate_name` ```python generate_name(cls, name: str | None = None) -> str diff --git a/docs/python-sdk/fastmcp-server-tasks-handlers.mdx b/docs/python-sdk/fastmcp-server-tasks-handlers.mdx index 31f228f14..3493b752e 100644 --- a/docs/python-sdk/fastmcp-server-tasks-handlers.mdx +++ b/docs/python-sdk/fastmcp-server-tasks-handlers.mdx @@ -13,7 +13,7 @@ Handles queuing tool/prompt/resource executions to Docket as background tasks. ## Functions -### `submit_to_docket` +### `submit_to_docket` ```python submit_to_docket(task_type: Literal['tool', 'resource', 'template', 'prompt'], key: str, component: Tool | Resource | ResourceTemplate | Prompt, arguments: dict[str, Any] | None = None, task_meta: TaskMeta | None = None) -> mcp.types.CreateTaskResult diff --git a/docs/python-sdk/fastmcp-tools-base.mdx b/docs/python-sdk/fastmcp-tools-base.mdx index 264b74677..4f8d362ea 100644 --- a/docs/python-sdk/fastmcp-tools-base.mdx +++ b/docs/python-sdk/fastmcp-tools-base.mdx @@ -7,7 +7,7 @@ sidebarTitle: base ## Functions -### `default_serializer` +### `default_serializer` ```python default_serializer(data: Any) -> str @@ -15,17 +15,17 @@ default_serializer(data: Any) -> str ## Classes -### `ToolResult` +### `ToolResult` **Methods:** -#### `to_mcp_result` +#### `to_mcp_result` ```python to_mcp_result(self) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]] | CallToolResult ``` -### `Tool` +### `Tool` Internal tool registration info. @@ -33,7 +33,7 @@ Internal tool registration info. **Methods:** -#### `to_mcp_tool` +#### `to_mcp_tool` ```python to_mcp_tool(self, **overrides: Any) -> MCPTool @@ -42,7 +42,7 @@ to_mcp_tool(self, **overrides: Any) -> MCPTool Convert the FastMCP tool to an MCP tool. -#### `from_function` +#### `from_function` ```python from_function(cls, fn: Callable[..., Any]) -> FunctionTool @@ -51,7 +51,7 @@ from_function(cls, fn: Callable[..., Any]) -> FunctionTool Create a Tool from a function. -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -66,7 +66,7 @@ implemented by subclasses. (list of ContentBlocks, dict of structured output). -#### `convert_result` +#### `convert_result` ```python convert_result(self, raw_value: Any) -> ToolResult @@ -78,7 +78,7 @@ Handles ToolResult passthrough and converts raw values using the tool's attributes (serializer, output_schema) for proper conversion. -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None @@ -87,7 +87,7 @@ register_with_docket(self, docket: Docket) -> None Register this tool with docket for background execution. -#### `add_to_docket` +#### `add_to_docket` ```python add_to_docket(self, docket: Docket, arguments: dict[str, Any], **kwargs: Any) -> Execution @@ -103,13 +103,13 @@ Schedule this tool for background execution via docket. - `**kwargs`: Additional kwargs passed to docket.add() -#### `from_tool` +#### `from_tool` ```python from_tool(cls, tool: Tool | Callable[..., Any]) -> TransformedTool ``` -#### `get_span_attributes` +#### `get_span_attributes` ```python get_span_attributes(self) -> dict[str, Any] diff --git a/docs/python-sdk/fastmcp-tools-function_tool.mdx b/docs/python-sdk/fastmcp-tools-function_tool.mdx index 88d2646d6..9bb157d22 100644 --- a/docs/python-sdk/fastmcp-tools-function_tool.mdx +++ b/docs/python-sdk/fastmcp-tools-function_tool.mdx @@ -10,7 +10,7 @@ Standalone @tool decorator for FastMCP. ## Functions -### `tool` +### `tool` ```python tool(name_or_fn: str | Callable[..., Any] | None = None) -> Any @@ -41,18 +41,7 @@ Metadata attached to functions by the @tool decorator. **Methods:** -#### `to_mcp_tool` - -```python -to_mcp_tool(self, **overrides: Any) -> mcp.types.Tool -``` - -Convert the FastMCP tool to an MCP tool. - -Extends the base implementation to add task execution mode if enabled. - - -#### `from_function` +#### `from_function` ```python from_function(cls, fn: Callable[..., Any]) -> FunctionTool @@ -68,7 +57,7 @@ individual parameters must not be passed. Cannot be used together with metadata parameter. -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -77,7 +66,7 @@ run(self, arguments: dict[str, Any]) -> ToolResult Run the tool with arguments. -#### `register_with_docket` +#### `register_with_docket` ```python register_with_docket(self, docket: Docket) -> None @@ -89,7 +78,7 @@ FunctionTool registers the underlying function, which has the user's Depends parameters for docket to resolve. -#### `add_to_docket` +#### `add_to_docket` ```python add_to_docket(self, docket: Docket, arguments: dict[str, Any], **kwargs: Any) -> Execution diff --git a/docs/python-sdk/fastmcp-tools-tool_transform.mdx b/docs/python-sdk/fastmcp-tools-tool_transform.mdx index 321818b27..24f6270aa 100644 --- a/docs/python-sdk/fastmcp-tools-tool_transform.mdx +++ b/docs/python-sdk/fastmcp-tools-tool_transform.mdx @@ -7,7 +7,7 @@ sidebarTitle: tool_transform ## Functions -### `forward` +### `forward` ```python forward(**kwargs: Any) -> ToolResult @@ -36,7 +36,7 @@ tool has args `a` and `b`, and an `transform_args` was provided that maps `x` to - `TypeError`: If provided arguments don't match the transformed schema. -### `forward_raw` +### `forward_raw` ```python forward_raw(**kwargs: Any) -> ToolResult @@ -62,7 +62,7 @@ y=2)` will call the parent tool with `x=1` and `y=2`. - `RuntimeError`: If called outside a transformed tool context. -### `apply_transformations_to_tools` +### `apply_transformations_to_tools` ```python apply_transformations_to_tools(tools: dict[str, Tool], transformations: dict[str, ToolTransformConfig]) -> dict[str, Tool] @@ -78,7 +78,7 @@ but transformations are keyed by tool name (e.g., "my_tool"). ## Classes -### `ArgTransform` +### `ArgTransform` Configuration for transforming a parent tool's argument. @@ -150,7 +150,7 @@ ArgTransform(name="new_name", description="New desc", default=None, type=int) ``` -### `ArgTransformConfig` +### `ArgTransformConfig` A model for requesting a single argument transform. @@ -158,7 +158,7 @@ A model for requesting a single argument transform. **Methods:** -#### `to_arg_transform` +#### `to_arg_transform` ```python to_arg_transform(self) -> ArgTransform @@ -167,7 +167,7 @@ to_arg_transform(self) -> ArgTransform Convert the argument transform to a FastMCP argument transform. -### `TransformedTool` +### `TransformedTool` A tool that is transformed from another tool. @@ -191,7 +191,7 @@ validation when forward() is called from custom functions. **Methods:** -#### `run` +#### `run` ```python run(self, arguments: dict[str, Any]) -> ToolResult @@ -210,7 +210,7 @@ functions. - ToolResult object containing content and optional structured output. -#### `from_tool` +#### `from_tool` ```python from_tool(cls, tool: Tool | Callable[..., Any], name: str | None = None, version: str | NotSetT | None = NotSet, title: str | NotSetT | None = NotSet, description: str | NotSetT | None = NotSet, tags: set[str] | None = None, transform_fn: Callable[..., Any] | None = None, transform_args: dict[str, ArgTransform] | None = None, annotations: ToolAnnotations | NotSetT | None = NotSet, output_schema: dict[str, Any] | NotSetT | None = NotSet, serializer: Callable[[Any], str] | NotSetT | None = NotSet, meta: dict[str, Any] | NotSetT | None = NotSet) -> TransformedTool @@ -293,7 +293,7 @@ async def custom_output(**kwargs) -> ToolResult: ``` -### `ToolTransformConfig` +### `ToolTransformConfig` Provides a way to transform a tool. @@ -301,7 +301,7 @@ Provides a way to transform a tool. **Methods:** -#### `apply` +#### `apply` ```python apply(self, tool: Tool) -> TransformedTool diff --git a/docs/servers/auth/oauth-proxy.mdx b/docs/servers/auth/oauth-proxy.mdx index d554490b8..19f5c2cee 100644 --- a/docs/servers/auth/oauth-proxy.mdx +++ b/docs/servers/auth/oauth-proxy.mdx @@ -3,7 +3,6 @@ title: OAuth Proxy sidebarTitle: OAuth Proxy description: Bridge traditional OAuth providers to work seamlessly with MCP's authentication flow. icon: share -tag: NEW --- import { VersionBadge } from "/snippets/version-badge.mdx"; diff --git a/examples/apps/datatable_server.py b/examples/apps/datatable_server.py index 35776eeee..dc78b4984 100644 --- a/examples/apps/datatable_server.py +++ b/examples/apps/datatable_server.py @@ -1,4 +1,18 @@ -from prefab_ui.components import Column, Heading, Muted +from collections import Counter + +from prefab_ui.app import PrefabApp +from prefab_ui.components import ( + Badge, + Card, + CardContent, + Column, + Grid, + Heading, + Row, + Separator, + Text, +) +from prefab_ui.components.charts import BarChart, ChartSeries, PieChart from prefab_ui.components.data_table import DataTable, DataTableColumn from fastmcp import FastMCP @@ -43,12 +57,53 @@ TEAM = [ @mcp.tool(app=True) -def team_directory(department: str | None = None) -> Column: - """Browse the team directory — sortable, searchable, paginated.""" +def team_directory(department: str | None = None) -> PrefabApp: + """Browse the team directory — sortable, searchable, with department breakdown.""" rows = [p for p in TEAM if not department or p["role"] == department] - with Column(gap=4, css_class="p-6") as view: - Heading("Team Directory") - Muted(f"{len(rows)} people") + + dept_counts = Counter(p["role"] for p in rows) + chart_data = [{"department": k, "count": v} for k, v in dept_counts.items()] + + level_counts = Counter(p["level"] for p in rows) + level_data = [{"level": k, "count": v} for k, v in level_counts.items()] + + with Column(gap=6, css_class="p-6") as view: + with Row(gap=2, align="center"): + Heading("Team Directory") + Badge(f"{len(rows)} people", variant="secondary") + + with Grid(columns=2, gap=6): + with Card(): + with CardContent(): + Text( + "By Department", + css_class="text-sm font-medium text-muted-foreground mb-2", + ) + PieChart( + data=chart_data, + data_key="count", + name_key="department", + show_legend=True, + inner_radius=40, + height=200, + ) + + with Card(): + with CardContent(): + Text( + "By Level", + css_class="text-sm font-medium text-muted-foreground mb-2", + ) + BarChart( + data=level_data, + series=[ChartSeries(data_key="count", label="People")], + x_axis="level", + height=200, + horizontal=True, + ) + + Separator() + DataTable( columns=[ DataTableColumn(key="name", header="Name", sortable=True), @@ -60,7 +115,8 @@ def team_directory(department: str | None = None) -> Column: search=True, paginated=True, ) - return view + + return PrefabApp(view=view) if __name__ == "__main__": diff --git a/examples/apps/showcase_server.py b/examples/apps/showcase_server.py new file mode 100644 index 000000000..4368a7f9c --- /dev/null +++ b/examples/apps/showcase_server.py @@ -0,0 +1,345 @@ +# ruff: noqa: F405 +"""Component showcase — demonstrates the breadth of Prefab UI components. + +Usage: + uv run python showcase_server.py +""" + +from prefab_ui.actions import SetState, ShowToast +from prefab_ui.app import PrefabApp +from prefab_ui.components import * # noqa: F403, F405 +from prefab_ui.components.charts import * # noqa: F403, F405 +from prefab_ui.components.control_flow import Else, If + +from fastmcp import FastMCP + +mcp = FastMCP("Showcase") + + +@mcp.tool(app=True) +def showcase() -> PrefabApp: + """Prefab UI component showcase.""" + with Grid(columns={"default": 1, "md": 2, "lg": 4}, gap=4, css_class="p-4") as view: + # ── Col 1 ───────────────────────────────────────────────────── + with Column(gap=4): + with Card(): + with CardHeader(): + CardTitle("Register Towel") + CardDescription("The most important item in the galaxy") + with CardContent(): + with Column(gap=3): + owner_input = Input(placeholder="Owner name...", name="owner") + with Combobox( + placeholder="Type...", search_placeholder="Search types..." + ): + ComboboxOption("Bath", value="bath") + ComboboxOption("Beach", value="beach") + ComboboxOption("Interstellar", value="interstellar") + ComboboxOption("Microfiber", value="micro") + DatePicker(placeholder="Registration date") + with CardFooter(): + with Row(gap=2): + with Dialog( + title="Towel Registered!", + description="Your towel has been added to the galactic registry.", + ): + Button("Register") + with If("{{ owner }}"): + Text( + f"Thanks, {owner_input.rx}. Don't forget to bring it." + ) + with Else(): + Text("Anonymous, I see? Don't forget to bring it.") + Button("Cancel", variant="outline") + with Card(): + with CardContent(): + with Row(gap=2, align="center"): + Loader(variant="dots", size="sm") + Muted("Marvin is thinking...") + + with Card(): + with CardHeader(): + CardTitle("Ship Status") + with CardContent(): + with Column(gap=3): + with Row(align="center", css_class="justify-between"): + Text("heart-of-gold") + with HoverCard(open_delay=0, close_delay=200): + Badge("In Orbit", variant="default") + with Column(gap=2): + Text("heart-of-gold") + Muted("Deployed 2h ago") + Progress(value=100, max=100, variant="success") + Progress(value=100, max=100, indicator_class="bg-yellow-400") + with Row(align="center", css_class="justify-between"): + Text("vogon-poetry") + with Tooltip("64% — ETA 12 min", delay=0): + with Badge(variant="secondary"): + Loader(size="sm") + Text("Deploying") + Progress(value=64, max=100) + with Row(align="center", css_class="justify-between"): + Text("deep-thought") + with Tooltip( + "Computing... 7.5 million years remaining", delay=0 + ): + with Badge(variant="outline"): + Loader(size="sm", variant="ios") + Text("Soon...") + Progress(value=12, max=100) + with Card(): + with CardHeader(): + CardTitle("Planet Ratings") + with CardContent(): + RadarChart( + data=[ + {"axis": "Views", "earth": 30, "mag": 95}, + {"axis": "Fjords", "earth": 65, "mag": 100}, + {"axis": "Pubs", "earth": 90, "mag": 10}, + {"axis": "Mice", "earth": 40, "mag": 85}, + {"axis": "Tea", "earth": 95, "mag": 15}, + {"axis": "Safety", "earth": 45, "mag": 70}, + ], + series=[ + ChartSeries(dataKey="earth", label="Earth"), + ChartSeries(dataKey="mag", label="Magrathea"), + ], + axis_key="axis", + height=200, + show_legend=True, + show_tooltip=True, + ) + + # ── Col 2 ───────────────────────────────────────────────────── + with Column(gap=4): + with Card(): + with CardHeader(): + CardTitle("Survival Odds") + with CardContent(css_class="w-fit mx-auto"): + Ring( + value=42, + label="42%", + variant="info", + size="lg", + thickness=12, + indicator_class="group-hover:drop-shadow-[0_0_24px_rgba(59,130,246,0.9)]", + ) + with Card(): + with CardHeader(): + with Row(gap=2, align="center"): + CardTitle("Improbability Drive") + Loader(variant="pulse", size="sm", css_class="text-blue-500") + with CardContent(): + with Column(gap=2): + Slider(min=0, max=100, value=42, name="improbability") + with Row(align="center", css_class="justify-between"): + Muted("Probable") + Muted("Infinite") + with Alert(variant="success", icon="circle-check"): + AlertTitle("Don't Panic") + AlertDescription("Normality achieved.") + with Card(): + with CardHeader(): + CardTitle("Prefect Horizon Config") + with CardContent(): + with Column(gap=3): + Switch(label="Auto-scale agents", value=True, name="autoscale") + Separator() + Switch(label="Code Mode", value=True, name="code_mode") + Separator() + Switch(label="Tool call caching", value=False, name="cache") + with CardFooter(): + Button("Save Preferences", on_click=ShowToast("Preferences saved!")) + with Card(): + with CardHeader(): + CardTitle("Travel Class") + with CardContent(): + with RadioGroup(name="travel_class"): + Radio(option="economy", label="Economy") + Radio(option="business", label="Business Class") + Radio( + option="improbability", + label="Infinite Improbability", + value=True, + ) + + # ── Cols 3–4 ────────────────────────────────────────────────── + with GridItem(css_class="md:col-span-2"): + with Column(gap=4): + with Grid(columns=2, gap=4, css_class="h-32"): + with Card(): + with CardHeader(): + CardTitle("Context Window") + with CardContent(): + with Column(gap=6, justify="center", css_class="h-full"): + with Row(align="center", css_class="justify-between"): + Text("45% used") + Muted("90k / 200k tokens") + with Tooltip("Auto-compact buffer: 12%", delay=0): + Progress(value=45, max=100) + with Card(css_class="pb-0 gap-0"): + with CardContent(): + Metric( + label="Fjords designed", + value="1,847", + delta="+3 coastlines", + ) + Sparkline( + data=[ + 820, + 950, + 1100, + 980, + 1250, + 1400, + 1350, + 1500, + 1680, + 1847, + ], + variant="success", + fill=True, + css_class="h-16", + ) + with Card(): + with CardHeader(): + CardTitle("Towel Incidents") + with CardContent(): + BarChart( + data=[ + {"month": "Jan", "lost": 8, "found": 5}, + {"month": "Feb", "lost": 24, "found": 15}, + {"month": "Mar", "lost": 12, "found": 28}, + {"month": "Apr", "lost": 35, "found": 19}, + {"month": "May", "lost": 18, "found": 38}, + {"month": "Jun", "lost": 42, "found": 30}, + ], + series=[ + ChartSeries(dataKey="lost", label="Lost"), + ChartSeries(dataKey="found", label="Found"), + ], + x_axis="month", + height=200, + bar_radius=4, + show_legend=True, + show_tooltip=True, + show_grid=True, + ) + + with Grid(columns=2, gap=4): + with Column(gap=4): + with Card(): + with CardContent(): + with Column(gap=2): + Checkbox(label="Towel packed", value=True) + Checkbox(label="Guide charged", value=True) + Checkbox(label="Babel fish inserted", value=False) + with If("{{ !pressed }}"): + Button( + "This is probably the best button to press.", + variant="success", + on_click=SetState("pressed", True), + ) + with Else(): + Button( + "Please do not press this button again.", + variant="destructive", + on_click=SetState("pressed", False), + ) + with Card(): + with CardHeader(): + CardTitle("Marvin's Mood") + with CardContent(): + with Column(gap=3): + P("How's life?") + with Column(gap=2): + Button( + "Meh", + on_click=ShowToast( + "Noted. Enthusiasm levels nominal." + ), + ) + Button( + "Depressed", + variant="info", + on_click=ShowToast( + "I think you ought to know I'm feeling very depressed." + ), + ) + Button( + "Don't talk to me about life", + variant="warning", + on_click=ShowToast( + "Brain the size of a planet and they ask me to pick up a piece of paper." + ), + ) + + with Column(gap=4): + with Alert(variant="destructive", icon="triangle-alert"): + AlertTitle("Beware of the Leopard") + with Card(): + with CardContent(): + DataTable( + columns=[ + DataTableColumn( + key="crew", header="Crew", sortable=True + ), + DataTableColumn( + key="species", + header="Species", + sortable=True, + ), + DataTableColumn( + key="towel", header="Towel?", sortable=True + ), + DataTableColumn( + key="status", header="Status", sortable=True + ), + ], + rows=[ + { + "crew": "Arthur Dent", + "species": "Human", + "towel": "Yes", + "status": "Confused", + }, + { + "crew": "Ford Prefect", + "species": "Betelgeusian", + "towel": "Always", + "status": "Drinking", + }, + { + "crew": "Zaphod", + "species": "Betelgeusian", + "towel": "Lost it", + "status": "Presidential", + }, + { + "crew": "Trillian", + "species": "Human", + "towel": "Yes", + "status": "Navigating", + }, + { + "crew": "Marvin", + "species": "Android", + "towel": "No point", + "status": "Depressed", + }, + { + "crew": "Slartibartfast", + "species": "Magrathean", + "towel": "Somewhere", + "status": "Designing", + }, + ], + search=True, + paginated=False, + ) + + return PrefabApp(view=view, state={"pressed": False, "improbability": 42}) + + +if __name__ == "__main__": + mcp.run() diff --git a/src/fastmcp/apps/app.py b/src/fastmcp/apps/app.py index b53e7028f..eaba588df 100644 --- a/src/fastmcp/apps/app.py +++ b/src/fastmcp/apps/app.py @@ -18,8 +18,8 @@ Usage:: return Column(...) @app.tool() - def save_contact(name: str, email: str) -> dict: - return {"name": name, "email": email} + def save_contact(name: str, email: str) -> str: + return name server = FastMCP("Platform") server.add_provider(app) diff --git a/src/fastmcp/cli/apps_dev.py b/src/fastmcp/cli/apps_dev.py index d1e2be811..b074e60b8 100644 --- a/src/fastmcp/cli/apps_dev.py +++ b/src/fastmcp/cli/apps_dev.py @@ -372,6 +372,8 @@ _HOST_HTML_TEMPLATE = """\ await bridge.sendToolResult(result); status.style.display = "none"; iframe.style.display = "block"; + // Prevent horizontal scrollbar when vertical scrollbar appears + try {{ iframe.contentDocument.documentElement.style.overflowX = "hidden"; }} catch(e) {{}} }}; // Start listening before the iframe loads @@ -415,8 +417,8 @@ _LOG_PANEL_HTML = """\ } #mcp-log-panel.hidden { display: none; } #app-frame { - width: calc(100% - 360px) !important; height: 100% !important; - margin-left: 360px !important; + width: 100% !important; height: 100% !important; + margin-left: 0 !important; } #mcp-log-resize { position: absolute; right: -3px; top: 0; bottom: 0; width: 6px; @@ -507,7 +509,7 @@ _LOG_PANEL_HTML = """\ background: #181825; color: #cdd6f4; border: 1px solid #45475a; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; - font-size: 11px; display: none; + font-size: 11px; display: block; } #mcp-log-open:hover { background: #313244; } #mcp-log-filters { @@ -553,7 +555,7 @@ _LOG_PANEL_HTML = """\ .log-level-alert { background: rgba(243, 139, 168, 0.25); color: #f38ba8; } .log-level-emergency { background: rgba(243, 139, 168, 0.3); color: #f38ba8; } -
+