mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Compare commits
30 commits
main
...
strawgate/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63744015c5 |
||
|
|
8f240fab1e |
||
|
|
8840779497 |
||
|
|
2acd49c8fe |
||
|
|
6e518d6bc8 |
||
|
|
32bd94f2aa |
||
|
|
6a4ad2d46a |
||
|
|
055e4e2d8b |
||
|
|
c82395a9a2 |
||
|
|
5ddfa7de13 |
||
|
|
2ba865555e |
||
|
|
f248845133 |
||
|
|
6f045972ab |
||
|
|
1f196083b7 |
||
|
|
0cf31e1893 |
||
|
|
f4728060bd |
||
|
|
08d4059f19 |
||
|
|
a8b273bae2 |
||
|
|
4d2060523b |
||
|
|
f8969fe729 |
||
|
|
955c996ad0 |
||
|
|
86ba8073cb |
||
|
|
48c196a343 | ||
|
|
ebba9f1aa9 |
||
|
|
333690b047 |
||
|
|
61748b9b1a | ||
|
|
f4694a99d6 | ||
|
|
27cc3f4a8f | ||
|
|
1de11b0879 | ||
|
|
2f9b74beb4 |
183 changed files with 8086 additions and 2687 deletions
2
.github/workflows/run-upgrade-checks.yml
vendored
2
.github/workflows/run-upgrade-checks.yml
vendored
|
|
@ -121,7 +121,7 @@ jobs:
|
|||
|
||||
- **ty (type checker)**: New ty releases frequently add stricter checks that flag previously-accepted code. Run `uv run ty check` locally with the latest ty to reproduce. Fix the type errors or bump the ty version floor in `pyproject.toml`.
|
||||
- **ruff**: New lint rules or stricter defaults in a ruff upgrade.
|
||||
- **mcp SDK**: Breaking changes in the `mcp` package (new method signatures, renamed types).
|
||||
- **MCP SDK**: Breaking changes in the `mcp` package (new method signatures, renamed types).
|
||||
|
||||
### What to do
|
||||
|
||||
|
|
|
|||
|
|
@ -1,119 +1,118 @@
|
|||
---
|
||||
title: App Architecture
|
||||
title: Architecture
|
||||
sidebarTitle: Architecture
|
||||
description: How FastMCP apps work under the hood — from Python to pixels.
|
||||
icon: sitemap
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
This page explains how Prefab apps work under the hood — how your Python code becomes an interactive UI inside a host client's conversation. You don't need any of this to build apps, but the mental model is useful when something isn't rendering the way you expect, when tool calls from the UI aren't reaching your server, or when you're building [custom HTML apps](/apps/low-level) and need to understand the protocol directly.
|
||||
You don't need this page to build apps. It's for when something isn't rendering the way you expect, when UI tool calls aren't reaching your server, or when you're writing [custom HTML apps](/apps/low-level) and need to understand the protocol directly.
|
||||
|
||||
## The Pipeline
|
||||
## The pipeline
|
||||
|
||||
An MCP App moves through five stages from Python to pixels:
|
||||
An MCP app moves through five stages from Python to pixels:
|
||||
|
||||
```
|
||||
Python components → JSON tree → structuredContent → Renderer iframe → Host UI
|
||||
```
|
||||
|
||||
You write Prefab components in Python. FastMCP serializes them to a JSON component tree and delivers it as `structuredContent` on the tool result. The host loads the Prefab renderer in a sandboxed iframe, pushes the JSON into it, and the renderer paints the UI. If the UI needs to call server tools, it talks back through the same `postMessage` channel.
|
||||
You write Prefab components. FastMCP serializes them to a JSON component tree and delivers it as `structuredContent` on the tool result. The host loads the Prefab renderer in a sandboxed iframe, pushes the JSON in, and the renderer paints the UI. If the UI calls server tools, it talks back through the same `postMessage` channel.
|
||||
|
||||
The following sections walk through each stage.
|
||||
The sections below walk each stage.
|
||||
|
||||
## Tool Registration
|
||||
## Tool registration
|
||||
|
||||
When you mark a tool with `app=True` or `@app.ui()`, FastMCP wires up the metadata and renderer resource that the protocol requires.
|
||||
|
||||
### The `app=True` Flag
|
||||
### The `app=True` flag
|
||||
|
||||
The `app` parameter on `@mcp.tool` accepts `True`, an `AppConfig` object, or a dict. When you pass `True`, FastMCP checks whether the tool's return type is a Prefab type (`PrefabApp`, `Component`, or unions containing them). If the tool qualifies, FastMCP expands `True` into a full `AppConfig` — setting the renderer URI, CSP headers, and visibility — and stores it in the tool's `meta["ui"]` dict.
|
||||
`app` on `@mcp.tool` accepts `True`, an `AppConfig`, or a dict. When you pass `True`, FastMCP checks whether the tool's return type is a Prefab type (`PrefabApp`, `Component`, or unions containing them). If it qualifies, FastMCP expands `True` into a full `AppConfig` — setting the renderer URI, CSP headers, and visibility — and stores it in the tool's `meta["ui"]` dict.
|
||||
|
||||
This expansion also triggers registration of the shared Prefab renderer resource (discussed below). The tool and the renderer are linked through a `resourceUri` field in the metadata: the tool says "render me with `ui://prefab/renderer.html`", and the host fetches that resource when it needs to display the result.
|
||||
This expansion also registers the shared Prefab renderer resource (below). The tool and the renderer are linked through a `resourceUri` field in the metadata: the tool says "render me with `ui://prefab/renderer.html`" and the host fetches that resource when it displays the result.
|
||||
|
||||
Type inference works the same way. If your return type annotation is a Prefab type and you haven't set `app` explicitly, FastMCP auto-wires the metadata as if you'd written `app=True`.
|
||||
Type inference works the same way. If the return type is a Prefab type and you haven't set `app` explicitly, FastMCP auto-wires the metadata as if you'd written `app=True`.
|
||||
|
||||
### FastMCPApp Registration
|
||||
### FastMCPApp registration
|
||||
|
||||
`FastMCPApp` uses the same underlying mechanism but adds two things. First, it tags every tool — both `@app.ui()` entry points and `@app.tool()` backends — with `meta["fastmcp"]["app"]` set to the app's name. This tag is how the server identifies which app a tool belongs to when routing calls from the UI.
|
||||
`FastMCPApp` uses the same mechanism but adds two things. First, it tags every tool — both `@app.ui()` entry points and `@app.tool()` backends — with `meta["fastmcp"]["app"]` set to the app's name. That tag lets the server identify which app a tool belongs to when routing UI calls.
|
||||
|
||||
Second, it sets `meta["ui"]["visibility"]` to control who can see each tool. Entry points default to `["model"]` (visible to the LLM). Backend tools default to `["app"]` (visible only to the UI). Hosts use this to filter the tool list — the model sees entry points, and the UI sees backends.
|
||||
Second, it sets `meta["ui"]["visibility"]` to control who can see each tool. Entry points default to `["model"]` (LLM-visible). Backend tools default to `["app"]` (UI-only). Hosts use this to filter the tool list.
|
||||
|
||||
## Serialization
|
||||
|
||||
When a Prefab tool runs, its return value — a `PrefabApp` or a raw `Component` — needs to become a JSON blob that the renderer can interpret.
|
||||
When a Prefab tool runs, its return value — a `PrefabApp` or a bare `Component` — becomes a JSON blob the renderer can interpret.
|
||||
|
||||
### PrefabApp.to_json()
|
||||
### `PrefabApp.to_json()`
|
||||
|
||||
The serialization entry point is `PrefabApp.to_json()`. This method walks the component tree and produces a JSON object with three top-level keys: `view` (the component tree), `state` (initial state values), and `_meta` (routing metadata).
|
||||
The entry point is `PrefabApp.to_json()`. It walks the component tree and produces a JSON object with three top-level keys: `view` (the component tree), `state` (initial state values), and `_meta` (routing metadata).
|
||||
|
||||
FastMCP passes a `tool_resolver` callback to `to_json()`. Whenever the component tree contains a `CallTool` action that references a function (not a string), the resolver converts it to a `ResolvedTool` with the function's registered name. This is how `CallTool(save_contact)` becomes `CallTool("save_contact")` in the wire format. The resolver also handles `unwrap_result` — a flag that tells the renderer to unwrap single-value results from the `{"result": value}` envelope that FastMCP uses for schema compliance.
|
||||
FastMCP passes a `tool_resolver` callback to `to_json()`. Whenever the tree contains a `CallTool` action that references a function (not a string), the resolver converts it to a `ResolvedTool` with the function's registered name. This is how `CallTool(save_contact)` becomes `CallTool("save_contact")` on the wire. The resolver also handles `unwrap_result` — a flag telling the renderer to unwrap single-value results from the `{"result": value}` envelope FastMCP uses for schema compliance.
|
||||
|
||||
### The _meta.fastmcp.app Tag
|
||||
### The `_meta.fastmcp.app` tag
|
||||
|
||||
After `to_json()` produces the JSON tree, FastMCP injects `_meta.fastmcp.app` with the app's name (if the tool belongs to a `FastMCPApp`). This tag rides along inside `structuredContent` all the way to the renderer.
|
||||
After `to_json()` produces the tree, FastMCP injects `_meta.fastmcp.app` with the app's name (if the tool belongs to a `FastMCPApp`). This tag rides along inside `structuredContent` all the way to the renderer.
|
||||
|
||||
When the renderer calls a backend tool, it includes `_meta.fastmcp.app` in the `CallTool` request. The server sees this tag and routes the call through a special path that bypasses transforms — more on this in the next section.
|
||||
When the renderer calls a backend tool, it includes `_meta.fastmcp.app` in the `CallTool` request. The server sees this tag and routes the call through a special path that bypasses transforms (below).
|
||||
|
||||
### ToolResult Assembly
|
||||
### ToolResult assembly
|
||||
|
||||
The final tool result has two parts: `content` (a list of `TextContent` blocks for the LLM) and `structuredContent` (the JSON tree for the renderer). By default, Prefab tools send `"[Rendered Prefab UI]"` as the text content — just enough for the LLM to know something was rendered. If you return a `ToolResult` explicitly, you control both halves.
|
||||
|
||||
## Tool Call Routing
|
||||
## Tool call routing
|
||||
|
||||
When a host calls a tool, the server needs to find it. Normal tool calls go through the provider chain, which applies transforms (namespace prefixes, visibility filters, etc.) before resolving the tool by name. But app UI calls need a different path.
|
||||
Normal tool calls go through the provider chain, which applies transforms (namespace prefixes, visibility filters) before resolving by name. App UI calls need a different path.
|
||||
|
||||
### The get_app_tool Bypass
|
||||
### The `get_app_tool` bypass
|
||||
|
||||
Backend tools registered with `@app.tool()` are typically hidden from the model (`visibility=["app"]`). Visibility transforms would filter them out of normal resolution. And namespace transforms might rename them — `save_contact` becomes `contacts_save_contact` — but the renderer still uses the original name.
|
||||
Backend tools are typically hidden from the model (`visibility=["app"]`). Visibility transforms would filter them out of normal resolution. And namespace transforms might rename them — `save_contact` becomes `contacts_save_contact` — while the renderer still uses the original name.
|
||||
|
||||
`get_app_tool` solves both problems. When the server sees `_meta.fastmcp.app` on an incoming `CallTool` request, it calls `get_app_tool(app_name, tool_name)` instead of the normal `get_tool(name)`. This method walks the provider tree directly, skipping the transform chain entirely. It finds the tool by its original registered name and verifies that its `meta["fastmcp"]["app"]` matches the expected app identity.
|
||||
`get_app_tool` solves both problems. When the server sees `_meta.fastmcp.app` on an incoming `CallTool` request, it calls `get_app_tool(app_name, tool_name)` instead of the normal `get_tool(name)`. This walks the provider tree directly, skipping transforms. It finds the tool by its original registered name and verifies that its `meta["fastmcp"]["app"]` matches the expected app.
|
||||
|
||||
This is why `CallTool("save_contact")` keeps working even when the server is mounted under a namespace prefix. The renderer sends the original name plus the app identity; the server uses `get_app_tool` to find the tool without transforms getting in the way.
|
||||
That's why `CallTool("save_contact")` keeps working when the server is mounted under a namespace. The renderer sends the original name plus the app identity; the server uses `get_app_tool` to find it without transforms in the way.
|
||||
|
||||
Authorization checks still apply — `get_app_tool` bypasses transforms, but it runs auth checks against the tool's `auth` configuration before executing.
|
||||
Authorization still applies. `get_app_tool` bypasses transforms but runs auth checks against the tool's `auth` config before executing.
|
||||
|
||||
### Provider Delegation
|
||||
### Provider delegation
|
||||
|
||||
The `get_app_tool` method is defined on the `Provider` base class and overridden by aggregate and wrapped providers. Aggregate providers fan out the lookup across all child providers in parallel. Wrapped providers (like `FastMCPProvider`, which wraps a nested `FastMCP` server) delegate to the inner server's `get_app_tool`. This means backend tools are reachable through any depth of server composition.
|
||||
`get_app_tool` is defined on the `Provider` base class and overridden by aggregate and wrapped providers. Aggregate providers fan out the lookup across child providers in parallel. Wrapped providers (like `FastMCPProvider`, which wraps a nested `FastMCP` server) delegate to the inner server's `get_app_tool`. Backend tools are reachable through any depth of composition.
|
||||
|
||||
## The Renderer
|
||||
## The renderer
|
||||
|
||||
The Prefab renderer is a self-contained JavaScript application that interprets the JSON component tree and renders it as a React UI.
|
||||
|
||||
### The Shared Resource
|
||||
### The shared resource
|
||||
|
||||
FastMCP registers the renderer as a `ui://prefab/renderer.html` resource with MIME type `text/html;profile=mcp-app`. The renderer HTML is bundled inside the `prefab-ui` Python package — `get_renderer_html()` returns it as a string. All Prefab tools on a server share this single resource, regardless of how many tools or apps are registered.
|
||||
FastMCP registers the renderer as a `ui://prefab/renderer.html` resource with MIME type `text/html;profile=mcp-app`. The HTML is bundled inside the `prefab-ui` Python package; `get_renderer_html()` returns it as a string. All Prefab tools on a server share this single resource.
|
||||
|
||||
The resource also carries CSP metadata (via `get_renderer_csp()`) declaring which CDN domains the renderer needs to load its JavaScript dependencies. Hosts use this to configure the iframe's Content Security Policy.
|
||||
The resource also carries CSP metadata (via `get_renderer_csp()`) declaring the CDN domains the renderer needs. Hosts use this to configure the iframe's Content Security Policy.
|
||||
|
||||
### postMessage Communication
|
||||
### `postMessage` communication
|
||||
|
||||
The renderer lives in a sandboxed iframe. It communicates with the host using `postMessage` — the standard browser API for cross-origin iframe communication. The protocol follows the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) specification:
|
||||
The renderer lives in a sandboxed iframe and communicates with the host using `postMessage`. The protocol follows the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) spec:
|
||||
|
||||
The host pushes the tool result (including `structuredContent`) into the iframe. The renderer parses the JSON component tree, initializes state, and renders the UI. When the user interacts with the UI — submitting a form, clicking a button — and that interaction triggers a `CallTool` action, the renderer sends a `callServerTool` message back to the host via `postMessage`. The host forwards this as a regular MCP `tools/call` request to the server, including `_meta.fastmcp.app` for routing.
|
||||
The host pushes the tool result (with `structuredContent`) into the iframe. The renderer parses the component tree, initializes state, and renders the UI. When the user interacts — submitting a form, clicking a button — and the interaction triggers a `CallTool` action, the renderer sends a `callServerTool` message back to the host via `postMessage`. The host forwards it as a regular MCP `tools/call` request to the server, including `_meta.fastmcp.app` for routing.
|
||||
|
||||
The response flows back the same way: server to host, host to iframe via `postMessage`, renderer updates state with the result.
|
||||
The response flows back the same way: server → host → iframe via `postMessage`, and the renderer updates state with the result.
|
||||
|
||||
### AppBridge
|
||||
|
||||
The `@modelcontextprotocol/ext-apps` JavaScript SDK provides the `App` class (sometimes called AppBridge) that manages the `postMessage` handshake. It handles connection negotiation, tool result delivery, server tool calls, and host context (like safe area insets and theme preferences). The Prefab renderer uses this SDK internally — you only interact with it directly when building [custom HTML apps](/apps/low-level).
|
||||
The `@modelcontextprotocol/ext-apps` JavaScript SDK provides the `App` class (sometimes called AppBridge) that manages the `postMessage` handshake. It handles connection negotiation, tool result delivery, server tool calls, and host context (safe area insets, theme preferences). The Prefab renderer uses it internally; you only touch it directly when building [custom HTML apps](/apps/low-level).
|
||||
|
||||
## The Dev Server
|
||||
## The dev server
|
||||
|
||||
`fastmcp dev apps` provides a local preview environment that simulates the host-side behavior without requiring a real MCP host client.
|
||||
`fastmcp dev apps` simulates the host-side behavior locally without a real MCP client.
|
||||
|
||||
### Proxy Architecture
|
||||
### Proxy architecture
|
||||
|
||||
The dev server runs two HTTP servers. Your MCP server starts on port 8000 (configurable) with the Streamable HTTP transport. The dev UI runs on port 8080 and serves a picker page that lists your app tools.
|
||||
Two HTTP servers. Your MCP server runs on port 8000 with the Streamable HTTP transport. The dev UI runs on port 8080 and serves a picker page that lists your app tools.
|
||||
|
||||
A reverse proxy at `/mcp` on the dev server forwards requests to your MCP server. This is important because the renderer iframe runs on `localhost:8080`, and your MCP server runs on `localhost:8000`. Without the proxy, the renderer's `callServerTool` requests would be cross-origin and blocked by the browser. The proxy makes everything same-origin from the iframe's perspective.
|
||||
A reverse proxy at `/mcp` on the dev server forwards requests to your MCP server. This matters because the renderer iframe runs on `localhost:8080` and your MCP server runs on `localhost:8000` — without the proxy, the renderer's `callServerTool` requests would be cross-origin and the browser would block them. The proxy keeps everything same-origin from the iframe's perspective.
|
||||
|
||||
### The Launch Flow
|
||||
### The launch flow
|
||||
|
||||
When you select a tool and click launch, the dev UI calls the tool through the proxy, receives the `structuredContent` response, and opens a new tab. That tab loads the tool's renderer resource (fetched from the proxy) in an iframe, creates an AppBridge instance, and pushes the tool result into the renderer. From this point forward, the experience matches what a real host would provide — the renderer displays the UI, and any `CallTool` actions route back through the proxy to your MCP server.
|
||||
When you select a tool and click launch, the dev UI calls the tool through the proxy, receives the `structuredContent` response, and opens a new tab. That tab loads the tool's renderer resource (via the proxy), creates an AppBridge, and pushes the tool result into the renderer. From here on it matches what a real host provides: the renderer displays the UI, and any `CallTool` actions route back through the proxy to your server.
|
||||
|
||||
Auto-reload is enabled by default, so changes to your server code restart the MCP server automatically. The dev UI stays running — just re-launch the tool to see your changes.
|
||||
Auto-reload is on by default, so changes to your server code restart the MCP server automatically. The dev UI keeps running — relaunch the tool to see changes.
|
||||
|
|
|
|||
76
docs/apps/demos/bar-chart.html
Normal file
76
docs/apps/demos/bar-chart.html
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"type": "BarChart",
|
||||
"data": [
|
||||
{
|
||||
"quarter": "Q1",
|
||||
"revenue": 42000,
|
||||
"costs": 28000
|
||||
},
|
||||
{
|
||||
"quarter": "Q2",
|
||||
"revenue": 51000,
|
||||
"costs": 31000
|
||||
},
|
||||
{
|
||||
"quarter": "Q3",
|
||||
"revenue": 47000,
|
||||
"costs": 29000
|
||||
},
|
||||
{
|
||||
"quarter": "Q4",
|
||||
"revenue": 63000,
|
||||
"costs": 35000
|
||||
}
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"dataKey": "revenue",
|
||||
"label": "Revenue"
|
||||
},
|
||||
{
|
||||
"dataKey": "costs",
|
||||
"label": "Costs"
|
||||
}
|
||||
],
|
||||
"xAxis": "quarter",
|
||||
"height": 250,
|
||||
"stacked": false,
|
||||
"horizontal": false,
|
||||
"barRadius": 4,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true,
|
||||
"showGrid": true,
|
||||
"showYAxis": true,
|
||||
"yAxisFormat": "auto"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
23
docs/apps/demos/bar-chart.py
Normal file
23
docs/apps/demos/bar-chart.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000, "costs": 28000},
|
||||
{"quarter": "Q2", "revenue": 51000, "costs": 31000},
|
||||
{"quarter": "Q3", "revenue": 47000, "costs": 29000},
|
||||
{"quarter": "Q4", "revenue": 63000, "costs": 35000},
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(css_class="p-6"):
|
||||
BarChart(
|
||||
data=data,
|
||||
series=[
|
||||
ChartSeries(data_key="revenue", label="Revenue"),
|
||||
ChartSeries(data_key="costs", label="Costs"),
|
||||
],
|
||||
x_axis="quarter",
|
||||
show_legend=True,
|
||||
height=250,
|
||||
)
|
||||
172
docs/apps/demos/contacts.html
Normal file
172
docs/apps/demos/contacts.html
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"type": "DataTable",
|
||||
"columns": [
|
||||
{
|
||||
"key": "name",
|
||||
"header": "Name",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "email",
|
||||
"header": "Email",
|
||||
"sortable": false
|
||||
},
|
||||
{
|
||||
"key": "category",
|
||||
"header": "Category",
|
||||
"sortable": false
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"name": "Arthur Dent",
|
||||
"email": "arthur@earth.com",
|
||||
"category": {
|
||||
"type": "Badge",
|
||||
"label": "Customer",
|
||||
"variant": "success"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Ford Prefect",
|
||||
"email": "ford@betelgeuse.org",
|
||||
"category": {
|
||||
"type": "Badge",
|
||||
"label": "Partner",
|
||||
"variant": "secondary"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Trillian Astra",
|
||||
"email": "trillian@heartofgold.com",
|
||||
"category": {
|
||||
"type": "Badge",
|
||||
"label": "Customer",
|
||||
"variant": "success"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Zaphod Beeblebrox",
|
||||
"email": "zaphod@galaxy.gov",
|
||||
"category": {
|
||||
"type": "Badge",
|
||||
"label": "Vendor",
|
||||
"variant": "outline"
|
||||
}
|
||||
}
|
||||
],
|
||||
"search": true,
|
||||
"paginated": false,
|
||||
"pageSize": 10
|
||||
},
|
||||
{
|
||||
"type": "Separator",
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
{
|
||||
"content": "Add Contact",
|
||||
"type": "H3"
|
||||
},
|
||||
{
|
||||
"cssClass": "gap-4",
|
||||
"type": "Form",
|
||||
"onSubmit": {
|
||||
"action": "showToast",
|
||||
"message": "Contact saved! (preview demo \u2014 no backend wired)",
|
||||
"variant": "success"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4",
|
||||
"type": "Row",
|
||||
"children": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "Input",
|
||||
"inputType": "text",
|
||||
"placeholder": "Full name",
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "Input",
|
||||
"inputType": "text",
|
||||
"placeholder": "name@example.com",
|
||||
"disabled": false,
|
||||
"readOnly": false,
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"type": "Select",
|
||||
"size": "default",
|
||||
"disabled": false,
|
||||
"required": false,
|
||||
"invalid": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "Customer",
|
||||
"label": "Customer",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "Partner",
|
||||
"label": "Partner",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "Vendor",
|
||||
"label": "Vendor",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"label": "Save Contact",
|
||||
"variant": "default",
|
||||
"size": "default",
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
78
docs/apps/demos/contacts.py
Normal file
78
docs/apps/demos/contacts.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
from prefab_ui.actions import ShowToast
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
H3,
|
||||
Badge,
|
||||
Button,
|
||||
Column,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Separator,
|
||||
)
|
||||
|
||||
contacts = [
|
||||
{"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
|
||||
{"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
|
||||
{
|
||||
"name": "Trillian Astra",
|
||||
"email": "trillian@heartofgold.com",
|
||||
"category": "Customer",
|
||||
},
|
||||
{"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Vendor"},
|
||||
]
|
||||
|
||||
rows = [
|
||||
{
|
||||
"name": c["name"],
|
||||
"email": c["email"],
|
||||
"category": Badge(
|
||||
c["category"],
|
||||
variant="success"
|
||||
if c["category"] == "Customer"
|
||||
else "secondary"
|
||||
if c["category"] == "Partner"
|
||||
else "outline",
|
||||
),
|
||||
}
|
||||
for c in contacts
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="email", header="Email"),
|
||||
DataTableColumn(key="category", header="Category"),
|
||||
],
|
||||
rows=rows,
|
||||
search=True,
|
||||
)
|
||||
|
||||
Separator()
|
||||
|
||||
H3("Add Contact")
|
||||
with Form(
|
||||
on_submit=ShowToast(
|
||||
"Contact saved! (preview demo — no backend wired)",
|
||||
variant="success",
|
||||
),
|
||||
):
|
||||
with Row(gap=4):
|
||||
Input(name="name", label="Name", placeholder="Full name", required=True)
|
||||
Input(
|
||||
name="email",
|
||||
label="Email",
|
||||
placeholder="name@example.com",
|
||||
required=True,
|
||||
)
|
||||
with Select(name="category", label="Category"):
|
||||
SelectOption(value="Customer", label="Customer")
|
||||
SelectOption(value="Partner", label="Partner")
|
||||
SelectOption(value="Vendor", label="Vendor")
|
||||
Button("Save Contact")
|
||||
157
docs/apps/demos/dashboard.html
Normal file
157
docs/apps/demos/dashboard.html
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-6",
|
||||
"type": "Row",
|
||||
"children": [
|
||||
{
|
||||
"type": "Metric",
|
||||
"label": "Revenue (Q1-Q4)",
|
||||
"value": "$220,500"
|
||||
},
|
||||
{
|
||||
"type": "Metric",
|
||||
"label": "Deals",
|
||||
"value": "4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BarChart",
|
||||
"data": [
|
||||
{
|
||||
"month": "Jan",
|
||||
"revenue": 48200,
|
||||
"costs": 31000
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"revenue": 52100,
|
||||
"costs": 32500
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"revenue": 61800,
|
||||
"costs": 34200
|
||||
},
|
||||
{
|
||||
"month": "Apr",
|
||||
"revenue": 58400,
|
||||
"costs": 33800
|
||||
}
|
||||
],
|
||||
"series": [
|
||||
{
|
||||
"dataKey": "revenue",
|
||||
"label": "Revenue"
|
||||
},
|
||||
{
|
||||
"dataKey": "costs",
|
||||
"label": "Costs"
|
||||
}
|
||||
],
|
||||
"xAxis": "month",
|
||||
"height": 200,
|
||||
"stacked": false,
|
||||
"horizontal": false,
|
||||
"barRadius": 4,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true,
|
||||
"showGrid": true,
|
||||
"showYAxis": true,
|
||||
"yAxisFormat": "auto"
|
||||
},
|
||||
{
|
||||
"type": "Separator",
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
{
|
||||
"type": "DataTable",
|
||||
"columns": [
|
||||
{
|
||||
"key": "account",
|
||||
"header": "Account",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"header": "Value",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "stage",
|
||||
"header": "Stage",
|
||||
"sortable": false
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"account": "Acme Corp",
|
||||
"value": "$84,000",
|
||||
"stage": {
|
||||
"type": "Badge",
|
||||
"label": "Won",
|
||||
"variant": "success"
|
||||
}
|
||||
},
|
||||
{
|
||||
"account": "Globex Inc",
|
||||
"value": "$52,000",
|
||||
"stage": {
|
||||
"type": "Badge",
|
||||
"label": "Negotiation",
|
||||
"variant": "secondary"
|
||||
}
|
||||
},
|
||||
{
|
||||
"account": "Initech",
|
||||
"value": "$31,500",
|
||||
"stage": {
|
||||
"type": "Badge",
|
||||
"label": "Proposal",
|
||||
"variant": "secondary"
|
||||
}
|
||||
},
|
||||
{
|
||||
"account": "Wayne Enterprises",
|
||||
"value": "$45,000",
|
||||
"stage": {
|
||||
"type": "Badge",
|
||||
"label": "Lost",
|
||||
"variant": "destructive"
|
||||
}
|
||||
}
|
||||
],
|
||||
"search": false,
|
||||
"paginated": false,
|
||||
"pageSize": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
68
docs/apps/demos/dashboard.py
Normal file
68
docs/apps/demos/dashboard.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge,
|
||||
Column,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
Row,
|
||||
Separator,
|
||||
)
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from prefab_ui.components.metric import Metric
|
||||
|
||||
monthly = [
|
||||
{"month": "Jan", "revenue": 48200, "costs": 31000},
|
||||
{"month": "Feb", "revenue": 52100, "costs": 32500},
|
||||
{"month": "Mar", "revenue": 61800, "costs": 34200},
|
||||
{"month": "Apr", "revenue": 58400, "costs": 33800},
|
||||
]
|
||||
|
||||
deals = [
|
||||
{"account": "Acme Corp", "value": "$84,000", "stage": "Won"},
|
||||
{"account": "Globex Inc", "value": "$52,000", "stage": "Negotiation"},
|
||||
{"account": "Initech", "value": "$31,500", "stage": "Proposal"},
|
||||
{"account": "Wayne Enterprises", "value": "$45,000", "stage": "Lost"},
|
||||
]
|
||||
|
||||
rows = [
|
||||
{
|
||||
"account": d["account"],
|
||||
"value": d["value"],
|
||||
"stage": Badge(
|
||||
d["stage"],
|
||||
variant="success"
|
||||
if d["stage"] == "Won"
|
||||
else "destructive"
|
||||
if d["stage"] == "Lost"
|
||||
else "secondary",
|
||||
),
|
||||
}
|
||||
for d in deals
|
||||
]
|
||||
|
||||
total = sum(m["revenue"] for m in monthly)
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
with Row(gap=6):
|
||||
Metric(label="Revenue (Q1-Q4)", value=f"${total:,}")
|
||||
Metric(label="Deals", value=f"{len(deals)}")
|
||||
BarChart(
|
||||
data=monthly,
|
||||
series=[
|
||||
ChartSeries(data_key="revenue", label="Revenue"),
|
||||
ChartSeries(data_key="costs", label="Costs"),
|
||||
],
|
||||
x_axis="month",
|
||||
show_legend=True,
|
||||
height=200,
|
||||
)
|
||||
Separator()
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="account", header="Account", sortable=True),
|
||||
DataTableColumn(key="value", header="Value", sortable=True),
|
||||
DataTableColumn(key="stage", header="Stage"),
|
||||
],
|
||||
rows=rows,
|
||||
)
|
||||
90
docs/apps/demos/data-table.html
Normal file
90
docs/apps/demos/data-table.html
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"type": "DataTable",
|
||||
"columns": [
|
||||
{
|
||||
"key": "name",
|
||||
"header": "Name",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "role",
|
||||
"header": "Role",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "dept",
|
||||
"header": "Dept",
|
||||
"sortable": true
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"name": "Alice Chen",
|
||||
"role": "Staff Engineer",
|
||||
"dept": "Platform"
|
||||
},
|
||||
{
|
||||
"name": "Bob Martinez",
|
||||
"role": "Lead Designer",
|
||||
"dept": "Design"
|
||||
},
|
||||
{
|
||||
"name": "Carol Johnson",
|
||||
"role": "Senior Engineer",
|
||||
"dept": "Platform"
|
||||
},
|
||||
{
|
||||
"name": "David Kim",
|
||||
"role": "Product Manager",
|
||||
"dept": "Product"
|
||||
},
|
||||
{
|
||||
"name": "Eva Mueller",
|
||||
"role": "Engineer",
|
||||
"dept": "Platform"
|
||||
},
|
||||
{
|
||||
"name": "Frank Lee",
|
||||
"role": "Data Scientist",
|
||||
"dept": "ML"
|
||||
},
|
||||
{
|
||||
"name": "Grace Park",
|
||||
"role": "Eng Manager",
|
||||
"dept": "Platform"
|
||||
}
|
||||
],
|
||||
"search": true,
|
||||
"paginated": false,
|
||||
"pageSize": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
24
docs/apps/demos/data-table.py
Normal file
24
docs/apps/demos/data-table.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, DataTable, DataTableColumn
|
||||
|
||||
employees = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "dept": "Platform"},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "dept": "Design"},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "dept": "Platform"},
|
||||
{"name": "David Kim", "role": "Product Manager", "dept": "Product"},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "dept": "Platform"},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "dept": "ML"},
|
||||
{"name": "Grace Park", "role": "Eng Manager", "dept": "Platform"},
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="dept", header="Dept", sortable=True),
|
||||
],
|
||||
rows=employees,
|
||||
search=True,
|
||||
)
|
||||
1105
docs/apps/demos/hitchhikers.html
Normal file
1105
docs/apps/demos/hitchhikers.html
Normal file
File diff suppressed because it is too large
Load diff
461
docs/apps/demos/hitchhikers.py
Normal file
461
docs/apps/demos/hitchhikers.py
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
"""The Hitchhiker's Guide dashboard from the Prefab welcome page.
|
||||
|
||||
Run with:
|
||||
prefab serve examples/hitchhikers-guide/dashboard.py
|
||||
prefab export examples/hitchhikers-guide/dashboard.py
|
||||
"""
|
||||
|
||||
from prefab_ui import PrefabApp
|
||||
from prefab_ui.actions import SetInterval, SetState, ShowToast
|
||||
from prefab_ui.components import (
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Carousel,
|
||||
Checkbox,
|
||||
Column,
|
||||
Combobox,
|
||||
ComboboxOption,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
DatePicker,
|
||||
Dialog,
|
||||
Grid,
|
||||
GridItem,
|
||||
HoverCard,
|
||||
Loader,
|
||||
Metric,
|
||||
Muted,
|
||||
P,
|
||||
Progress,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Ring,
|
||||
Row,
|
||||
Separator,
|
||||
Slider,
|
||||
Switch,
|
||||
Text,
|
||||
Tooltip,
|
||||
)
|
||||
from prefab_ui.components.charts import (
|
||||
BarChart,
|
||||
ChartSeries,
|
||||
RadarChart,
|
||||
Sparkline,
|
||||
)
|
||||
from prefab_ui.components.control_flow import Else, If
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
ctx_tick = Rx("ctx_tick")
|
||||
|
||||
# Context window: climbs from 24% to ~78%, then resets
|
||||
ctx_pct = (ctx_tick % 20) * 3 + 20
|
||||
ctx_variant = (ctx_pct > 70).then(
|
||||
"destructive", (ctx_pct <= 33).then("success", "default")
|
||||
)
|
||||
|
||||
with PrefabApp(
|
||||
title="Prefab Showcase",
|
||||
state={"ctx_tick": 0, "improbability": 42},
|
||||
on_mount=SetInterval(
|
||||
400,
|
||||
on_tick=SetState("ctx_tick", ctx_tick + 1),
|
||||
),
|
||||
) as app:
|
||||
with Grid(columns={"default": 1, "md": 2, "lg": 4}, gap=4):
|
||||
# ── 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):
|
||||
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")
|
||||
Text("Don't forget to bring it.")
|
||||
Button("Cancel", variant="outline")
|
||||
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("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 Carousel(auto_advance=3000, show_controls=False, direction="up"):
|
||||
with Alert(variant="success", icon="circle-check"):
|
||||
AlertTitle("Don't Panic")
|
||||
AlertDescription("Normality achieved.")
|
||||
with Alert(variant="destructive", icon="triangle-alert"):
|
||||
AlertTitle("Display Department")
|
||||
AlertDescription("Beware of the leopard.")
|
||||
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: summary row, chart, then 2-col grid below ─────────
|
||||
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(f"{ctx_pct}% used")
|
||||
Muted(f"{ctx_pct * 2}k / 200k tokens")
|
||||
with Tooltip(
|
||||
"Auto-compact buffer: 12%",
|
||||
delay=0,
|
||||
):
|
||||
Progress(
|
||||
value=ctx_pct,
|
||||
max=100,
|
||||
variant=ctx_variant,
|
||||
)
|
||||
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 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 Card():
|
||||
with CardContent():
|
||||
with Row(gap=2, align="center"):
|
||||
Loader(variant="dots", size="sm")
|
||||
Muted("Marvin is thinking...")
|
||||
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,
|
||||
)
|
||||
60
docs/apps/demos/pie-chart.html
Normal file
60
docs/apps/demos/pie-chart.html
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"type": "PieChart",
|
||||
"data": [
|
||||
{
|
||||
"category": "Bug",
|
||||
"count": 42
|
||||
},
|
||||
{
|
||||
"category": "Feature",
|
||||
"count": 28
|
||||
},
|
||||
{
|
||||
"category": "Docs",
|
||||
"count": 15
|
||||
},
|
||||
{
|
||||
"category": "Infra",
|
||||
"count": 10
|
||||
}
|
||||
],
|
||||
"dataKey": "count",
|
||||
"nameKey": "category",
|
||||
"height": 240,
|
||||
"innerRadius": 50,
|
||||
"showLabel": false,
|
||||
"paddingAngle": 0,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
21
docs/apps/demos/pie-chart.py
Normal file
21
docs/apps/demos/pie-chart.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column
|
||||
from prefab_ui.components.charts import PieChart
|
||||
|
||||
data = [
|
||||
{"category": "Bug", "count": 42},
|
||||
{"category": "Feature", "count": 28},
|
||||
{"category": "Docs", "count": 15},
|
||||
{"category": "Infra", "count": 10},
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(css_class="p-6"):
|
||||
PieChart(
|
||||
data=data,
|
||||
data_key="count",
|
||||
name_key="category",
|
||||
inner_radius=50,
|
||||
show_legend=True,
|
||||
height=240,
|
||||
)
|
||||
167
docs/apps/demos/reactive.html
Normal file
167
docs/apps/demos/reactive.html
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"let": {
|
||||
"data": "{{ region == 'south' ? south : region == 'west' ? west : north }}"
|
||||
},
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 items-center",
|
||||
"type": "Row",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "w-40",
|
||||
"name": "region",
|
||||
"type": "Select",
|
||||
"size": "default",
|
||||
"disabled": false,
|
||||
"required": false,
|
||||
"invalid": false,
|
||||
"children": [
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "north",
|
||||
"label": "North",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "south",
|
||||
"label": "South",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
},
|
||||
{
|
||||
"type": "SelectOption",
|
||||
"value": "west",
|
||||
"label": "West",
|
||||
"selected": false,
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cssClass": "ml-auto",
|
||||
"name": "show_target",
|
||||
"value": false,
|
||||
"type": "Switch",
|
||||
"size": "default",
|
||||
"disabled": false,
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"cssClass": "text-sm text-muted-foreground",
|
||||
"content": "Show target",
|
||||
"type": "Text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "BarChart",
|
||||
"data": "{{ data }}",
|
||||
"series": [
|
||||
{
|
||||
"dataKey": "sales",
|
||||
"label": "Sales"
|
||||
}
|
||||
],
|
||||
"xAxis": "month",
|
||||
"height": 200,
|
||||
"stacked": false,
|
||||
"horizontal": false,
|
||||
"barRadius": 4,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true,
|
||||
"showGrid": true,
|
||||
"showYAxis": true,
|
||||
"yAxisFormat": "auto"
|
||||
},
|
||||
{
|
||||
"type": "Condition",
|
||||
"cases": [
|
||||
{
|
||||
"when": "{{ show_target }}",
|
||||
"children": [
|
||||
{
|
||||
"type": "Metric",
|
||||
"label": "Q1 Target",
|
||||
"value": "$75,000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"region": "north",
|
||||
"north": [
|
||||
{
|
||||
"month": "Jan",
|
||||
"sales": 22000
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"sales": 25500
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"sales": 24200
|
||||
}
|
||||
],
|
||||
"south": [
|
||||
{
|
||||
"month": "Jan",
|
||||
"sales": 5800
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"sales": 6400
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"sales": 5600
|
||||
}
|
||||
],
|
||||
"west": [
|
||||
{
|
||||
"month": "Jan",
|
||||
"sales": 6000
|
||||
},
|
||||
{
|
||||
"month": "Feb",
|
||||
"sales": 6000
|
||||
},
|
||||
{
|
||||
"month": "Mar",
|
||||
"sales": 5600
|
||||
}
|
||||
],
|
||||
"show_target": true
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
66
docs/apps/demos/reactive.py
Normal file
66
docs/apps/demos/reactive.py
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column,
|
||||
Row,
|
||||
Select,
|
||||
SelectOption,
|
||||
Switch,
|
||||
Text,
|
||||
)
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from prefab_ui.components.control_flow import If
|
||||
from prefab_ui.components.metric import Metric
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
region = Rx("region")
|
||||
|
||||
north = [
|
||||
{"month": "Jan", "sales": 22000},
|
||||
{"month": "Feb", "sales": 25500},
|
||||
{"month": "Mar", "sales": 24200},
|
||||
]
|
||||
south = [
|
||||
{"month": "Jan", "sales": 5800},
|
||||
{"month": "Feb", "sales": 6400},
|
||||
{"month": "Mar", "sales": 5600},
|
||||
]
|
||||
west = [
|
||||
{"month": "Jan", "sales": 6000},
|
||||
{"month": "Feb", "sales": 6000},
|
||||
{"month": "Mar", "sales": 5600},
|
||||
]
|
||||
|
||||
with PrefabApp(
|
||||
state={
|
||||
"region": "north",
|
||||
"north": north,
|
||||
"south": south,
|
||||
"west": west,
|
||||
"show_target": True,
|
||||
},
|
||||
) as app:
|
||||
with Column(
|
||||
gap=4,
|
||||
css_class="p-6",
|
||||
let={
|
||||
"data": "{{ region == 'south' ? south : region == 'west' ? west : north }}",
|
||||
},
|
||||
):
|
||||
with Row(gap=4, align="center"):
|
||||
with Select(name="region", css_class="w-40"):
|
||||
SelectOption(value="north", label="North")
|
||||
SelectOption(value="south", label="South")
|
||||
SelectOption(value="west", label="West")
|
||||
Switch(name="show_target", css_class="ml-auto")
|
||||
Text("Show target", css_class="text-sm text-muted-foreground")
|
||||
BarChart(
|
||||
data=Rx("data"),
|
||||
series=[ChartSeries(data_key="sales", label="Sales")],
|
||||
x_axis="month",
|
||||
height=200,
|
||||
)
|
||||
with If(Rx("show_target")):
|
||||
Metric(
|
||||
label="Q1 Target",
|
||||
value="$75,000",
|
||||
)
|
||||
237
docs/apps/demos/team-directory-reactive.html
Normal file
237
docs/apps/demos/team-directory-reactive.html
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4",
|
||||
"type": "Grid",
|
||||
"columnTemplate": "1fr 2fr",
|
||||
"children": [
|
||||
{
|
||||
"type": "PieChart",
|
||||
"data": [
|
||||
{
|
||||
"office": "San Francisco",
|
||||
"count": 3
|
||||
},
|
||||
{
|
||||
"office": "New York",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"office": "London",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"office": "Berlin",
|
||||
"count": 1
|
||||
}
|
||||
],
|
||||
"dataKey": "count",
|
||||
"nameKey": "office",
|
||||
"height": 300,
|
||||
"innerRadius": 0,
|
||||
"showLabel": false,
|
||||
"paddingAngle": 0,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true
|
||||
},
|
||||
{
|
||||
"type": "DataTable",
|
||||
"columns": [
|
||||
{
|
||||
"key": "name",
|
||||
"header": "Name",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "role",
|
||||
"header": "Role",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "office",
|
||||
"header": "Office",
|
||||
"sortable": true
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"name": "Alice Chen",
|
||||
"role": "Staff Engineer",
|
||||
"office": "San Francisco",
|
||||
"email": "alice@company.com",
|
||||
"projects": 3
|
||||
},
|
||||
{
|
||||
"name": "Bob Martinez",
|
||||
"role": "Lead Designer",
|
||||
"office": "New York",
|
||||
"email": "bob@company.com",
|
||||
"projects": 5
|
||||
},
|
||||
{
|
||||
"name": "Carol Johnson",
|
||||
"role": "Senior Engineer",
|
||||
"office": "London",
|
||||
"email": "carol@company.com",
|
||||
"projects": 2
|
||||
},
|
||||
{
|
||||
"name": "David Kim",
|
||||
"role": "Product Manager",
|
||||
"office": "San Francisco",
|
||||
"email": "david@company.com",
|
||||
"projects": 7
|
||||
},
|
||||
{
|
||||
"name": "Eva Mueller",
|
||||
"role": "Engineer",
|
||||
"office": "Berlin",
|
||||
"email": "eva@company.com",
|
||||
"projects": 1
|
||||
},
|
||||
{
|
||||
"name": "Frank Lee",
|
||||
"role": "Data Scientist",
|
||||
"office": "San Francisco",
|
||||
"email": "frank@company.com",
|
||||
"projects": 4
|
||||
},
|
||||
{
|
||||
"name": "Grace Park",
|
||||
"role": "Engineering Manager",
|
||||
"office": "New York",
|
||||
"email": "grace@company.com",
|
||||
"projects": 6
|
||||
}
|
||||
],
|
||||
"search": true,
|
||||
"paginated": false,
|
||||
"pageSize": 10,
|
||||
"onRowClick": {
|
||||
"action": "setState",
|
||||
"key": "selected",
|
||||
"value": "{{ $event }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Condition",
|
||||
"cases": [
|
||||
{
|
||||
"when": "{{ selected }}",
|
||||
"children": [
|
||||
{
|
||||
"type": "Card",
|
||||
"children": [
|
||||
{
|
||||
"type": "CardHeader",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-2 items-center",
|
||||
"type": "Row",
|
||||
"children": [
|
||||
{
|
||||
"content": "{{ selected.name }}",
|
||||
"type": "H3"
|
||||
},
|
||||
{
|
||||
"type": "Badge",
|
||||
"label": "{{ selected.office }}",
|
||||
"variant": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "CardContent",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 grid-cols-3",
|
||||
"type": "Grid",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-0",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"content": "Role",
|
||||
"type": "Small"
|
||||
},
|
||||
{
|
||||
"content": "{{ selected.role }}",
|
||||
"type": "Text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cssClass": "gap-0",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"content": "Email",
|
||||
"type": "Small"
|
||||
},
|
||||
{
|
||||
"content": "{{ selected.email }}",
|
||||
"type": "Text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"cssClass": "gap-0",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"content": "Active Projects",
|
||||
"type": "Small"
|
||||
},
|
||||
{
|
||||
"content": "{{ selected.projects }}",
|
||||
"type": "Text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"selected": null
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
116
docs/apps/demos/team-directory-reactive.py
Normal file
116
docs/apps/demos/team-directory-reactive.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
from collections import Counter
|
||||
|
||||
from prefab_ui.actions import SetState
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
H3,
|
||||
Badge,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
Column,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
Grid,
|
||||
Row,
|
||||
Small,
|
||||
Text,
|
||||
)
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from prefab_ui.components.control_flow import If
|
||||
from prefab_ui.rx import STATE, Rx
|
||||
|
||||
MEMBERS = [
|
||||
{
|
||||
"name": "Alice Chen",
|
||||
"role": "Staff Engineer",
|
||||
"office": "San Francisco",
|
||||
"email": "alice@company.com",
|
||||
"projects": 3,
|
||||
},
|
||||
{
|
||||
"name": "Bob Martinez",
|
||||
"role": "Lead Designer",
|
||||
"office": "New York",
|
||||
"email": "bob@company.com",
|
||||
"projects": 5,
|
||||
},
|
||||
{
|
||||
"name": "Carol Johnson",
|
||||
"role": "Senior Engineer",
|
||||
"office": "London",
|
||||
"email": "carol@company.com",
|
||||
"projects": 2,
|
||||
},
|
||||
{
|
||||
"name": "David Kim",
|
||||
"role": "Product Manager",
|
||||
"office": "San Francisco",
|
||||
"email": "david@company.com",
|
||||
"projects": 7,
|
||||
},
|
||||
{
|
||||
"name": "Eva Mueller",
|
||||
"role": "Engineer",
|
||||
"office": "Berlin",
|
||||
"email": "eva@company.com",
|
||||
"projects": 1,
|
||||
},
|
||||
{
|
||||
"name": "Frank Lee",
|
||||
"role": "Data Scientist",
|
||||
"office": "San Francisco",
|
||||
"email": "frank@company.com",
|
||||
"projects": 4,
|
||||
},
|
||||
{
|
||||
"name": "Grace Park",
|
||||
"role": "Engineering Manager",
|
||||
"office": "New York",
|
||||
"email": "grace@company.com",
|
||||
"projects": 6,
|
||||
},
|
||||
]
|
||||
|
||||
OFFICE_COUNTS = [
|
||||
{"office": office, "count": count}
|
||||
for office, count in Counter(m["office"] for m in MEMBERS).items()
|
||||
]
|
||||
|
||||
with PrefabApp(state={"selected": None}) as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=OFFICE_COUNTS,
|
||||
data_key="count",
|
||||
name_key="office",
|
||||
show_legend=True,
|
||||
)
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="office", header="Office", sortable=True),
|
||||
],
|
||||
rows=MEMBERS,
|
||||
search=True,
|
||||
on_row_click=SetState("selected", Rx("$event")),
|
||||
)
|
||||
|
||||
with If(STATE.selected):
|
||||
with Card():
|
||||
with CardHeader():
|
||||
with Row(gap=2, align="center"):
|
||||
H3(Rx("selected.name"))
|
||||
Badge(Rx("selected.office"))
|
||||
with CardContent():
|
||||
with Grid(columns=3, gap=4):
|
||||
with Column(gap=0):
|
||||
Small("Role")
|
||||
Text(Rx("selected.role"))
|
||||
with Column(gap=0):
|
||||
Small("Email")
|
||||
Text(Rx("selected.email"))
|
||||
with Column(gap=0):
|
||||
Small("Active Projects")
|
||||
Text(Rx("selected.projects"))
|
||||
127
docs/apps/demos/team-directory.html
Normal file
127
docs/apps/demos/team-directory.html
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Prefab</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" crossorigin href="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.css">
|
||||
<script type="module" crossorigin src="https://cdn.jsdelivr.net/npm/@prefecthq/prefab-ui@0.19.0/dist/app/renderer.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" style="max-width:64rem;margin:0 auto;padding:2rem"></div>
|
||||
<script id="prefab:initial-data" type="application/json">{
|
||||
"$prefab": {
|
||||
"version": "0.2"
|
||||
},
|
||||
"view": {
|
||||
"cssClass": "pf-app-root",
|
||||
"type": "Div",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4 p-6",
|
||||
"type": "Column",
|
||||
"children": [
|
||||
{
|
||||
"cssClass": "gap-4",
|
||||
"type": "Grid",
|
||||
"columnTemplate": "1fr 2fr",
|
||||
"children": [
|
||||
{
|
||||
"type": "PieChart",
|
||||
"data": [
|
||||
{
|
||||
"office": "San Francisco",
|
||||
"count": 3
|
||||
},
|
||||
{
|
||||
"office": "New York",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"office": "London",
|
||||
"count": 1
|
||||
},
|
||||
{
|
||||
"office": "Berlin",
|
||||
"count": 1
|
||||
}
|
||||
],
|
||||
"dataKey": "count",
|
||||
"nameKey": "office",
|
||||
"height": 300,
|
||||
"innerRadius": 0,
|
||||
"showLabel": false,
|
||||
"paddingAngle": 0,
|
||||
"showLegend": true,
|
||||
"showTooltip": true,
|
||||
"animate": true
|
||||
},
|
||||
{
|
||||
"type": "DataTable",
|
||||
"columns": [
|
||||
{
|
||||
"key": "name",
|
||||
"header": "Name",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "role",
|
||||
"header": "Role",
|
||||
"sortable": true
|
||||
},
|
||||
{
|
||||
"key": "office",
|
||||
"header": "Office",
|
||||
"sortable": true
|
||||
}
|
||||
],
|
||||
"rows": [
|
||||
{
|
||||
"name": "Alice Chen",
|
||||
"role": "Staff Engineer",
|
||||
"office": "San Francisco"
|
||||
},
|
||||
{
|
||||
"name": "Bob Martinez",
|
||||
"role": "Lead Designer",
|
||||
"office": "New York"
|
||||
},
|
||||
{
|
||||
"name": "Carol Johnson",
|
||||
"role": "Senior Engineer",
|
||||
"office": "London"
|
||||
},
|
||||
{
|
||||
"name": "David Kim",
|
||||
"role": "Product Manager",
|
||||
"office": "San Francisco"
|
||||
},
|
||||
{
|
||||
"name": "Eva Mueller",
|
||||
"role": "Engineer",
|
||||
"office": "Berlin"
|
||||
},
|
||||
{
|
||||
"name": "Frank Lee",
|
||||
"role": "Data Scientist",
|
||||
"office": "San Francisco"
|
||||
},
|
||||
{
|
||||
"name": "Grace Park",
|
||||
"role": "Engineering Manager",
|
||||
"office": "New York"
|
||||
}
|
||||
],
|
||||
"search": true,
|
||||
"paginated": false,
|
||||
"pageSize": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}</script>
|
||||
</body>
|
||||
</html>
|
||||
39
docs/apps/demos/team-directory.py
Normal file
39
docs/apps/demos/team-directory.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from collections import Counter
|
||||
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, DataTable, DataTableColumn, Grid
|
||||
from prefab_ui.components.charts import PieChart
|
||||
|
||||
members = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco"},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York"},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London"},
|
||||
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco"},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin"},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco"},
|
||||
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York"},
|
||||
]
|
||||
|
||||
office_counts = [
|
||||
{"office": office, "count": count}
|
||||
for office, count in Counter(m["office"] for m in members).items()
|
||||
]
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=office_counts,
|
||||
data_key="count",
|
||||
name_key="office",
|
||||
show_legend=True,
|
||||
)
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="office", header="Office", sortable=True),
|
||||
],
|
||||
rows=members,
|
||||
search=True,
|
||||
)
|
||||
|
|
@ -3,7 +3,6 @@ title: Development
|
|||
sidebarTitle: Development
|
||||
description: Preview and test your app tools locally without a full MCP host.
|
||||
icon: flask
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
|
@ -14,11 +13,11 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
<img src="/apps/images/dev-app.png" alt="The dev UI showing a rendered Prefab app with the MCP inspector panel" />
|
||||
</Frame>
|
||||
|
||||
`fastmcp dev apps` launches a browser-based preview for your app tools. It starts your MCP server and a local dev UI side by side — you pick a tool, fill in its arguments, and see the rendered result in a new tab. No MCP host client needed.
|
||||
`fastmcp dev apps` gives you a browser preview for your app tools without needing an MCP host client. It starts your server and a local dev UI side by side: you pick a tool, fill in its arguments, and the rendered result opens in a new tab.
|
||||
|
||||
This works with both [Prefab apps](/apps/prefab) and [custom HTML apps](/apps/low-level).
|
||||
Works with both [Interactive Tools](/apps/prefab) and [custom HTML apps](/apps/low-level).
|
||||
|
||||
## Quick Start
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
|
|
@ -26,7 +25,7 @@ fastmcp dev apps server.py
|
|||
|
||||
The dev UI opens at `http://localhost:8080`. Your MCP server runs on port 8000 with auto-reload enabled by default — save a file and the server restarts automatically.
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
The dev server does three things:
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ When you submit a form, the dev server **calls your tool** via the MCP protocol
|
|||
|
||||
A **reverse proxy** on `/mcp` forwards requests from the browser to your MCP server, avoiding CORS issues that would otherwise block the iframe-based renderer from talking to a different port.
|
||||
|
||||
## MCP Inspector
|
||||
## MCP inspector
|
||||
|
||||
The dev UI includes an inspector panel on the left side that captures MCP traffic in real time. It shows JSON-RPC messages flowing between the browser and your server — requests, responses, and AppBridge `postMessage` traffic.
|
||||
|
||||
|
|
@ -56,7 +55,7 @@ fastmcp dev apps server.py:mcp --mcp-port 9000 --dev-port 9090 --no-reload
|
|||
| Dev Port | `--dev-port` | `8080` | Port for the dev UI |
|
||||
| Auto-Reload | `--reload` / `--no-reload` | On | Watch files and restart the server on changes |
|
||||
|
||||
## Multiple Tools
|
||||
## Multiple tools
|
||||
|
||||
If your server has multiple app tools, the picker shows a dropdown. Each tool gets its own form and launch button. The tool's `title` is displayed when available, falling back to the tool name.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ title: Examples
|
|||
sidebarTitle: Examples
|
||||
description: Example apps you can run right now.
|
||||
icon: images
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Every example below is a working FastMCP server you can run with `fastmcp dev apps` or connect to from any MCP host. The source is in `examples/apps/` in the repository.
|
||||
Each tile below is a working FastMCP server you can run with `fastmcp dev apps` or connect to from any MCP host. Source lives in `examples/apps/` in the repository.
|
||||
|
||||
<Columns cols={2}>
|
||||
<Tile href="#sales-dashboard" title="Sales Dashboard" description="Metrics, charts, and deal pipeline">
|
||||
|
|
@ -44,7 +43,7 @@ Every example below is a working FastMCP server you can run with `fastmcp dev ap
|
|||
</Tile>
|
||||
</Columns>
|
||||
|
||||
## Running Examples
|
||||
## Running the examples
|
||||
|
||||
Preview any example in your browser with the dev server:
|
||||
|
||||
|
|
@ -53,11 +52,11 @@ pip install "fastmcp[apps]"
|
|||
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
|
||||
```
|
||||
|
||||
The dev server opens an interactive browser UI where you can select a tool and provide arguments. In a real deployment, the LLM provides these arguments on the fly based on the conversation. For example, the quiz example works best when connected to an MCP host like Goose or Claude Desktop, where the LLM generates the questions itself.
|
||||
The dev UI lets you pick a tool and fill in arguments. In a real deployment the LLM provides those arguments from conversation context — the quiz example especially shines when connected to a host like Goose or Claude Desktop, where the LLM generates the questions itself.
|
||||
|
||||
## Standalone Examples
|
||||
## Standalone apps
|
||||
|
||||
### Sales Dashboard
|
||||
### Sales dashboard
|
||||
|
||||
A full dashboard with KPI metrics, revenue trends, segment breakdown, and a deal pipeline table. Shows what you can build with a single `app=True` tool and Prefab's chart and data components.
|
||||
|
||||
|
|
@ -65,9 +64,9 @@ A full dashboard with KPI metrics, revenue trends, segment breakdown, and a deal
|
|||
fastmcp dev apps examples/apps/sales_dashboard/sales_dashboard_server.py
|
||||
```
|
||||
|
||||
### System Monitor
|
||||
### System monitor
|
||||
|
||||
Reads live CPU, memory, and disk stats from your machine using `psutil`. Auto-refreshes via `SetInterval` calling a backend tool, with a dropdown to control the refresh rate. The chart accumulates 100 data points over time.
|
||||
Reads live CPU, memory, and disk stats from your machine using `psutil`. Auto-refreshes via `SetInterval` calling a backend tool, with a dropdown to control the refresh rate. The chart accumulates up to 100 data points over time.
|
||||
|
||||
```bash
|
||||
pip install psutil
|
||||
|
|
@ -82,59 +81,12 @@ The LLM generates trivia questions and passes them to the tool. The user answers
|
|||
fastmcp dev apps examples/apps/quiz/quiz_server.py
|
||||
```
|
||||
|
||||
### Interactive Map
|
||||
### Interactive map
|
||||
|
||||
Accepts addresses or place names, geocodes them via OpenStreetMap Nominatim (free, no API key), and renders an interactive Leaflet map using Prefab's `Embed` component with inline HTML. Proves that Prefab apps aren't limited to built-in components.
|
||||
Accepts addresses or place names, geocodes them via OpenStreetMap Nominatim (free, no API key), and renders an interactive Leaflet map using Prefab's `Embed` component with inline HTML. A reminder that Prefab apps can break out of built-in components when they need to.
|
||||
|
||||
```bash
|
||||
fastmcp dev apps examples/apps/map/map_server.py
|
||||
```
|
||||
|
||||
## Built-in Providers
|
||||
|
||||
These are ready-made capabilities you add with a single `add_provider()` call.
|
||||
|
||||
### [File Upload](/apps/providers/file-upload)
|
||||
|
||||
Drag-and-drop file upload. The user drops files, clicks Upload, and the server stores them. The LLM can list and read uploaded files through model-visible tools.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.file_upload import FileUpload
|
||||
mcp.add_provider(FileUpload())
|
||||
```
|
||||
|
||||
### [Approval](/apps/providers/approval)
|
||||
|
||||
Human-in-the-loop confirmation. The LLM presents what it's about to do, the user clicks Approve or Reject, and the decision flows back as a message.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.approval import Approval
|
||||
mcp.add_provider(Approval())
|
||||
```
|
||||
|
||||
### [Choice](/apps/providers/choice)
|
||||
|
||||
Present clickable options instead of asking users to type. Clean structured input without parsing free text.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.choice import Choice
|
||||
mcp.add_provider(Choice())
|
||||
```
|
||||
|
||||
### [Form Input](/apps/providers/form)
|
||||
|
||||
Generate a validated form from a Pydantic model. Submission is validated against the model before being returned.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.form import FormInput
|
||||
mcp.add_provider(FormInput(model=MyModel))
|
||||
```
|
||||
|
||||
### [Generative UI](/apps/providers/generative)
|
||||
|
||||
The LLM writes Prefab Python code at runtime and the result renders as a streaming interactive UI. Tailored visualizations for any data. See the [full guide](/apps/generative) for details.
|
||||
|
||||
```python
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
For ready-made building blocks like approvals, choice pickers, file uploads, and Pydantic forms, see the [Providers](/apps/providers/approval) group.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Generative UI means the LLM writes the UI code at runtime. Instead of calling a pre-built tool with a fixed interface, the model writes Prefab Python code tailored to the current data and request. The user watches the UI build up in real time as the model generates code.
|
||||
<video src="/apps/images/generative-ui.mp4" autoPlay loop muted playsInline style={{width:"100%", borderRadius:"8px", marginBottom:"1rem"}} />
|
||||
|
||||
With Generative UI, the LLM writes the UI code at runtime. Instead of calling a pre-built tool with a fixed shape, the model writes Prefab Python tailored to the current data and request. The user watches the UI stream in as the model generates it.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
|
@ -20,15 +22,15 @@ mcp = FastMCP("Prefab Studio")
|
|||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
That's it. The `GenerativeUI` provider registers everything:
|
||||
One provider registers three things:
|
||||
|
||||
- **`generate_prefab_ui`** — a tool that accepts Python code, executes it in a Pyodide sandbox, and renders the result as a Prefab app
|
||||
- **`search_prefab_components`** — a tool that lets the LLM search the Prefab component library to discover what's available
|
||||
- **The generative renderer** — a `ui://` resource with browser-side Pyodide for streaming progressive rendering
|
||||
- **`search_prefab_components`** — a tool the LLM uses to discover what components are available
|
||||
- **The streaming renderer** — a `ui://` resource with browser-side Pyodide that progressively renders partial code as the LLM generates it
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
When the LLM decides to call `generate_prefab_ui`, it writes Prefab Python code into the `code` argument. The MCP Apps protocol creates the renderer iframe in parallel with the tool call, so the app is already running when partial arguments start flowing.
|
||||
When the LLM calls `generate_prefab_ui`, it writes Prefab Python code into the `code` argument. The MCP Apps protocol creates the renderer iframe in parallel with the tool call, so the app is already running by the time partial arguments start flowing.
|
||||
|
||||
As the LLM generates each token:
|
||||
|
||||
|
|
@ -37,11 +39,11 @@ As the LLM generates each token:
|
|||
3. Browser-side Pyodide executes whatever compiles successfully
|
||||
4. The user sees components appear as they're written
|
||||
|
||||
When the LLM finishes, the server runs the complete code in a server-side Pyodide sandbox for validation, and the renderer replaces the streaming preview with the final server-validated result.
|
||||
When the LLM finishes, the server runs the complete code in a server-side Pyodide sandbox for validation, and the renderer swaps the streaming preview for the final server-validated result.
|
||||
|
||||
## What the LLM Writes
|
||||
## What the LLM writes
|
||||
|
||||
The tool description includes code examples that teach the LLM the Prefab patterns. A typical generation looks like:
|
||||
The tool description includes examples that teach the model the Prefab patterns. A typical generation looks like:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Column, Row, Heading, Text, Badge, Card, CardContent
|
||||
|
|
@ -73,9 +75,9 @@ with PrefabApp() as app:
|
|||
Badge("+18%", variant="success")
|
||||
```
|
||||
|
||||
The model writes real Python — loops, f-strings, computation, helper functions. Prefab's component library gives it charts, tables, forms, cards, badges, and layout primitives to work with.
|
||||
The model writes real Python — loops, f-strings, computation, helper functions. Prefab gives it charts, tables, forms, cards, badges, and layout primitives to compose.
|
||||
|
||||
## The Component Search Tool
|
||||
## The component search tool
|
||||
|
||||
Before writing code, the LLM can call `search_prefab_components` to discover what's available:
|
||||
|
||||
|
|
@ -87,11 +89,11 @@ search_prefab_components("Chart")
|
|||
...
|
||||
```
|
||||
|
||||
Passing `detail=True` returns full field descriptions and docstrings. The search tool introspects the actual Prefab classes at runtime, so it's always up to date with the installed version.
|
||||
Passing `detail=True` returns full field descriptions and docstrings. The search tool introspects Prefab classes at runtime, so it's always up to date with the installed version.
|
||||
|
||||
## Passing Data
|
||||
## Passing data
|
||||
|
||||
The `generate_prefab_ui` tool accepts a `data` parameter. Values passed here become global variables in the sandbox:
|
||||
The `generate_prefab_ui` tool accepts a `data` parameter. Values become global variables in the sandbox:
|
||||
|
||||
```python
|
||||
# The LLM can reference 'sales_data' directly in its code
|
||||
|
|
@ -101,11 +103,11 @@ result = await generate_prefab_ui(
|
|||
)
|
||||
```
|
||||
|
||||
This lets the model use real data from earlier in the conversation to build visualizations.
|
||||
This lets the model use data from earlier in the conversation to build visualizations.
|
||||
|
||||
## Configuration
|
||||
|
||||
`GenerativeUI` accepts options for customizing tool names:
|
||||
`GenerativeUI` takes options for customizing tool names:
|
||||
|
||||
```python
|
||||
GenerativeUI(
|
||||
|
|
@ -117,17 +119,16 @@ GenerativeUI(
|
|||
|
||||
## Requirements
|
||||
|
||||
Generative UI requires `fastmcp[apps]` which installs `prefab-ui`. The Pyodide sandbox (for server-side validation) requires Deno — it installs automatically on first use.
|
||||
Generative UI needs `fastmcp[apps]`, which pulls in `prefab-ui`. The server-side Pyodide sandbox (for final validation) requires Deno — it installs automatically on first use.
|
||||
|
||||
The streaming renderer loads Pyodide from CDN in the browser. The CSP is configured automatically by the provider — no manual setup needed.
|
||||
The streaming renderer loads Pyodide from CDN in the browser. The CSP is configured automatically by the provider — no manual setup.
|
||||
|
||||
## Sandbox Limitations
|
||||
## Sandbox limitations
|
||||
|
||||
The Pyodide sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, requests, etc.) are **not available** — the LLM's code must work with only built-in Python and Prefab components. If the LLM tries to import an unavailable package, the sandbox will raise an `ImportError`.
|
||||
The Pyodide sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, requests, etc.) are **not available** — the LLM's code must work with only built-in Python and Prefab. If the LLM imports something unavailable, the sandbox raises `ImportError`.
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
- **[GenerativeUI Provider Reference](/apps/providers/generative)** — Configuration options and quick setup
|
||||
- **[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`
|
||||
- **[Interactive Tools](/apps/prefab)** — the component building blocks the LLM will use
|
||||
- **[Prefab component reference](https://prefab.prefect.io/docs/components)** — full component library
|
||||
- **[Development](/apps/development)** — preview generative tools locally with `fastmcp dev apps`
|
||||
|
|
|
|||
BIN
docs/apps/images/generative-ui.mp4
Normal file
BIN
docs/apps/images/generative-ui.mp4
Normal file
Binary file not shown.
|
|
@ -1,45 +1,40 @@
|
|||
---
|
||||
title: FastMCPApp
|
||||
sidebarTitle: FastMCPApp
|
||||
description: Managed tool binding, visibility, and composition for apps with heavy server interaction.
|
||||
description: Wire an interactive UI to backend tools with managed visibility and composition safety.
|
||||
icon: puzzle-piece
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
<Tip>
|
||||
[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.
|
||||
</Tip>
|
||||
<PrefabPinWarning />
|
||||
|
||||
Any [Prefab app](/apps/prefab) can call server tools — there's nothing stopping you from using `CallTool("tool_name")` in a regular `@mcp.tool(app=True)`. But once you have multiple backend tools, the management overhead adds up: Which tools should the model see vs. only the UI? What happens to string-based tool references when servers are composed under namespaces? How do you keep things wired correctly as the app grows?
|
||||
<iframe src="/apps/demos/contacts.html" style={{width:"100%", height:"650px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:
|
||||
Search a list, fill out a form, click save, the list updates. That pattern — UI that reads and writes data on the server — needs two things: backend tools that actually do the work, and a way to call them from the UI. `FastMCPApp` handles the wiring.
|
||||
|
||||
- **`@app.ui()`** — entry-point tools the model calls to open the app. These return a Prefab UI.
|
||||
- **`@app.tool()`** — backend tools the UI calls via `CallTool`. These do the work.
|
||||
You'll build up to the contacts app above by the end of this page. Let's start with something smaller.
|
||||
|
||||
Backend tools get globally stable identifiers that survive namespacing. Visibility is managed automatically — the model sees entry points, the UI sees backends. And `CallTool` accepts function references instead of strings, so references are refactorable and composition-safe.
|
||||
## A minimal interactive app
|
||||
|
||||
## Your First Interactive App
|
||||
|
||||
Here's a minimal app with a form that saves data:
|
||||
The smallest interactive app: a form that saves a note, and a list that updates when the user submits.
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge, Button, Column, ForEach, Form,
|
||||
Heading, Input, Row, Separator, Text,
|
||||
Badge, Button, Column, ForEach, Form, Heading,
|
||||
Input, Row, Separator, Text,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Notes")
|
||||
|
||||
notes_db: list[dict] = []
|
||||
|
||||
|
||||
|
|
@ -83,13 +78,23 @@ def notes_app() -> PrefabApp:
|
|||
mcp = FastMCP("Notes Server", providers=[app])
|
||||
```
|
||||
|
||||
When the model calls `notes_app`, the user sees a form. Submitting it calls `add_note` on the server, updates the state with the result, and shows a toast — all without leaving the UI.
|
||||
The model sees one tool: `notes_app`. Calling it opens the UI. When the user submits the form, `CallTool("add_note")` fires, the server saves the note, returns the updated list, and `SetState("notes", RESULT)` writes that list back into state. `ForEach("notes")` re-renders. The model never sees `add_note` — it's UI-only.
|
||||
|
||||
Let's break down the key concepts.
|
||||
## Why not just `@mcp.tool(app=True)`?
|
||||
|
||||
## Entry Points: @app.ui()
|
||||
A fair question. Any [Interactive Tool](/apps/prefab) can call a server tool — there's nothing stopping you from putting `CallTool("add_note")` inside a regular `@mcp.tool(app=True)`. It works for one or two tools. Things get harder once the app grows:
|
||||
|
||||
Entry points are what the model sees and calls to open your app. They return a Prefab UI, just like display tools:
|
||||
- Which tools should the model see, and which are UI-only?
|
||||
- What happens to `CallTool("add_note")` when you mount this server under a namespace and the tool becomes `notes_add_note`?
|
||||
- How do you keep it all wired correctly as you compose servers?
|
||||
|
||||
`FastMCPApp` owns these concerns. Entry points register as model-visible. Backend tools register as UI-only by default. Backend tools get globally stable identifiers that survive namespacing, and `CallTool` accepts function references, so references stay valid when you compose servers.
|
||||
|
||||
The rest of this page covers each piece in turn.
|
||||
|
||||
## `@app.ui()` — entry points
|
||||
|
||||
Entry points are what the model sees. They return a `PrefabApp` and default to `visibility=["model"]`, showing up in the LLM tool list but not callable from within the UI.
|
||||
|
||||
```python
|
||||
@app.ui()
|
||||
|
|
@ -97,21 +102,15 @@ def dashboard() -> PrefabApp:
|
|||
"""The model calls this to open the dashboard."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Dashboard")
|
||||
# ... build UI ...
|
||||
...
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
Entry points default to `visibility=["model"]` — they show up in the tool list for the LLM but aren't callable from within the app UI. They support the same options as `@mcp.tool`: `name`, `description`, `title`, `tags`, `icons`, `auth`, and `timeout`.
|
||||
`@app.ui()` supports the same options as `@mcp.tool`: `name`, `description`, `title`, `tags`, `icons`, `auth`, and `timeout`.
|
||||
|
||||
```python
|
||||
@app.ui(title="Contact Manager", description="Open the contact management interface")
|
||||
def contact_manager() -> PrefabApp:
|
||||
...
|
||||
```
|
||||
## `@app.tool()` — backend tools
|
||||
|
||||
## Backend Tools: @app.tool()
|
||||
|
||||
Backend tools do the work. The UI calls them via `CallTool`; they run on the server and return data:
|
||||
Backend tools do the work. By default they're visible only to the UI (`visibility=["app"]`), not the model.
|
||||
|
||||
```python
|
||||
@app.tool()
|
||||
|
|
@ -121,7 +120,7 @@ def save_contact(name: str, email: str) -> list[dict]:
|
|||
return list(db)
|
||||
```
|
||||
|
||||
By default, backend tools are only visible to the app UI (`visibility=["app"]`). The model doesn't see them in the tool list. If you want a tool callable by both the model and the UI, pass `model=True`:
|
||||
If you want a tool callable by both the model and the UI, pass `model=True`:
|
||||
|
||||
```python
|
||||
@app.tool(model=True)
|
||||
|
|
@ -130,37 +129,32 @@ def list_contacts() -> list[dict]:
|
|||
return list(db)
|
||||
```
|
||||
|
||||
Backend tools support `name`, `description`, `auth`, and `timeout`:
|
||||
Backend tools support `name`, `description`, `auth`, and `timeout`.
|
||||
|
||||
```python
|
||||
@app.tool(description="Search contacts by name or email", timeout=10.0)
|
||||
def search(query: str) -> list[dict]:
|
||||
...
|
||||
```
|
||||
## `CallTool` — UI → backend
|
||||
|
||||
## Connecting UI to Backend: CallTool
|
||||
|
||||
`CallTool` is the bridge between the UI and the server. Pass the name of a backend tool registered with `@app.tool()`:
|
||||
`CallTool` is how the UI invokes a backend tool. Pass the tool's name (or a direct function reference):
|
||||
|
||||
```python
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
|
||||
# Reference a backend tool by name
|
||||
CallTool("save_contact", arguments={"name": "Alice", "email": "alice@example.com"})
|
||||
|
||||
# Arguments can reference state with Rx
|
||||
# Or a function reference — resolves to a stable global key
|
||||
CallTool(save_contact, arguments={...})
|
||||
```
|
||||
|
||||
Arguments can reference state with `Rx`:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import STATE
|
||||
|
||||
CallTool("search", arguments={"query": STATE.search_term})
|
||||
```
|
||||
|
||||
FastMCPApp resolves the name to the tool's stable global key automatically, so `CallTool("save_contact")` keeps working even when the server is mounted under a namespace.
|
||||
### Handling results
|
||||
|
||||
You can also pass the function directly — `CallTool(save_contact)` — which can be convenient when the tool is defined in the same file. Both forms resolve identically.
|
||||
|
||||
### Handling Results
|
||||
|
||||
Server calls are asynchronous. Use `on_success` and `on_error` callbacks to handle outcomes:
|
||||
Server calls are async. Use `on_success` and `on_error` callbacks:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
|
|
@ -176,78 +170,54 @@ CallTool(
|
|||
)
|
||||
```
|
||||
|
||||
`RESULT` is a reactive reference to the value the tool returned — available inside `on_success` callbacks. Similarly, `ERROR` (from `prefab_ui.rx`) is available inside `on_error`.
|
||||
`RESULT` is a reactive reference to the tool's return value, available inside `on_success`. `ERROR` (from `prefab_ui.rx`) is the counterpart inside `on_error`. Callbacks can be a single action or a list; they execute in order and short-circuit on error.
|
||||
|
||||
Callbacks can be a single action or a list of actions. They execute in order, and an error in any action short-circuits the rest.
|
||||
### `result_key` shorthand
|
||||
|
||||
### result_key Shorthand
|
||||
|
||||
When a tool returns data that should replace a state key, `result_key` is a convenient shorthand for `on_success=SetState(key, RESULT)`:
|
||||
When a tool's return value should replace a state key, use `result_key`:
|
||||
|
||||
```python
|
||||
CallTool("list_contacts", result_key="contacts")
|
||||
|
||||
# equivalent to:
|
||||
CallTool(
|
||||
"list_contacts",
|
||||
on_success=SetState("contacts", RESULT),
|
||||
)
|
||||
# same as:
|
||||
CallTool("list_contacts", on_success=SetState("contacts", RESULT))
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
`CallTool` is one of several actions available in Prefab. Actions are events attached to component handlers like `on_click`, `on_submit`, and `on_change`.
|
||||
`CallTool` is one of several actions. Actions attach to handlers like `on_click`, `on_submit`, and `on_change`.
|
||||
|
||||
### Client Actions
|
||||
|
||||
These run instantly in the browser — no server round-trip:
|
||||
Client-side actions run instantly in the browser, no server round-trip:
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ToggleState, AppendState, PopState, ShowToast
|
||||
|
||||
# Set a value
|
||||
SetState("count", 42)
|
||||
|
||||
# Toggle a boolean
|
||||
ToggleState("expanded")
|
||||
|
||||
# Append to a list
|
||||
AppendState("items", {"name": "New Item"})
|
||||
|
||||
# Remove by index
|
||||
PopState("items", 0)
|
||||
|
||||
# Show a notification
|
||||
ShowToast("Done!", variant="success")
|
||||
```
|
||||
|
||||
### Chaining Actions
|
||||
|
||||
Pass a list to execute multiple actions in sequence:
|
||||
Pass a list to chain actions:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Button
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
|
||||
Button(
|
||||
"Reset",
|
||||
on_click=[
|
||||
SetState("query", ""),
|
||||
SetState("results", []),
|
||||
ShowToast("Cleared", variant="default"),
|
||||
ShowToast("Cleared"),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Loading States
|
||||
### Loading states
|
||||
|
||||
A common pattern: show a loading indicator while a server call is in flight.
|
||||
A common pattern: disable a button and show a spinner while a call is in flight.
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.components import Button
|
||||
from prefab_ui.rx import RESULT, Rx
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
saving = Rx("saving")
|
||||
|
||||
|
|
@ -271,21 +241,17 @@ Button(
|
|||
],
|
||||
)
|
||||
|
||||
# Pass state={"saving": False} to PrefabApp when returning
|
||||
# PrefabApp(view=view, state={"saving": False, ...})
|
||||
```
|
||||
|
||||
## Forms
|
||||
|
||||
Forms are the most common way to collect input and send it to the server. When a form submits, all named input values are gathered and passed as arguments to the `CallTool` action.
|
||||
Forms collect input and submit it to a tool. When submitted, named input values become the tool's arguments.
|
||||
|
||||
### Manual Forms
|
||||
|
||||
Build forms with individual input components:
|
||||
### Manual forms
|
||||
|
||||
```python
|
||||
from prefab_ui.components import Form, Input, Select, SelectOption, Textarea, Button
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.actions import ShowToast
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
|
|
@ -298,26 +264,19 @@ with Form(
|
|||
SelectOption("Low", value="low")
|
||||
SelectOption("Medium", value="medium")
|
||||
SelectOption("High", value="high")
|
||||
SelectOption("Critical", value="critical")
|
||||
Textarea(name="description", label="Description")
|
||||
Button("Create Ticket")
|
||||
```
|
||||
|
||||
When submitted, the CallTool receives `{"title": "...", "priority": "...", "description": "..."}` as arguments to `create_ticket`.
|
||||
On submit, `CallTool` receives `{"title": ..., "priority": ..., "description": ...}`.
|
||||
|
||||
### Pydantic Model Forms
|
||||
### Forms from Pydantic models
|
||||
|
||||
For structured data, `Form.from_model()` generates the entire form from a Pydantic model — inputs, labels, and submit wiring:
|
||||
For structured input, `Form.from_model()` generates the whole form — inputs, labels, validation:
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from prefab_ui.components import Column, Heading, Form
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.rx import RESULT
|
||||
|
||||
class BugReport(BaseModel):
|
||||
title: str = Field(title="Bug Title")
|
||||
|
|
@ -329,7 +288,6 @@ class BugReport(BaseModel):
|
|||
|
||||
@app.ui()
|
||||
def report_bug() -> PrefabApp:
|
||||
"""File a bug report."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Report a Bug")
|
||||
Form.from_model(
|
||||
|
|
@ -337,7 +295,6 @@ def report_bug() -> PrefabApp:
|
|||
on_submit=CallTool(
|
||||
"create_bug",
|
||||
on_success=ShowToast("Bug filed!", variant="success"),
|
||||
on_error=ShowToast("Failed to submit", variant="error"),
|
||||
),
|
||||
)
|
||||
return PrefabApp(view=view)
|
||||
|
|
@ -345,69 +302,47 @@ def report_bug() -> PrefabApp:
|
|||
|
||||
@app.tool()
|
||||
def create_bug(data: BugReport) -> str:
|
||||
"""Create a bug report."""
|
||||
# save to database...
|
||||
return f"Created: {data.title}"
|
||||
```
|
||||
|
||||
`str` fields become text inputs, `Literal` becomes a select dropdown, `bool` becomes a checkbox. Field titles and defaults are respected.
|
||||
`str` becomes a text input, `Literal` becomes a select, `bool` becomes a checkbox. Field titles and defaults are respected.
|
||||
|
||||
## Composition and Namespacing
|
||||
## Composition and namespacing
|
||||
|
||||
The reason `FastMCPApp` exists — and why you'd use it instead of plain `@mcp.tool(app=True)` with `CallTool("tool_name")` — is composition safety.
|
||||
The reason `FastMCPApp` exists — and why you'd pick it over plain `@mcp.tool(app=True)` with string-based `CallTool` — is composition safety.
|
||||
|
||||
When you mount a server under a namespace, tool names get prefixed:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
|
||||
platform = FastMCP("Platform")
|
||||
platform.mount("contacts", contacts_server)
|
||||
|
||||
# "save_contact" becomes "contacts_save_contact"
|
||||
```
|
||||
|
||||
If your UI used `CallTool("save_contact")`, it would break — the tool is now named `contacts_save_contact`. But `CallTool(save_contact)` with a function reference resolves to a globally stable key (like `save_contact-a1b2c3d4`) that bypasses the namespace entirely.
|
||||
`CallTool("save_contact")` would now be broken. But `CallTool(save_contact)` with a function reference resolves to a globally stable identifier that bypasses the namespace. Your app works the same whether standalone or mounted.
|
||||
|
||||
This is why `FastMCPApp` assigns global keys to backend tools, and why `CallTool` accepts function references. Your app works the same whether it's running standalone or mounted inside a larger platform.
|
||||
|
||||
### Mounting an App
|
||||
### Mounting
|
||||
|
||||
`FastMCPApp` is a Provider. Add it to a server with `providers=` or `add_provider`:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
@app.ui()
|
||||
def contact_manager() -> PrefabApp:
|
||||
...
|
||||
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> dict:
|
||||
...
|
||||
|
||||
|
||||
# Option 1: providers list
|
||||
mcp = FastMCP("Platform", providers=[app])
|
||||
|
||||
# Option 2: add_provider
|
||||
# or
|
||||
mcp = FastMCP("Platform")
|
||||
mcp.add_provider(app)
|
||||
```
|
||||
|
||||
Multiple apps can coexist on the same server:
|
||||
Multiple apps can coexist; each gets its own global keys, so there's no collision even if two apps have a tool named `save`.
|
||||
|
||||
```python
|
||||
mcp = FastMCP("Platform", providers=[contacts_app, inventory_app, billing_app])
|
||||
```
|
||||
|
||||
Each app's backend tools have their own global keys, so there's no collision even if two apps have a tool named `save`.
|
||||
### Running standalone
|
||||
|
||||
### Running Standalone
|
||||
|
||||
For development, `FastMCPApp` has a convenience `run()` method that wraps itself in a temporary `FastMCP` server:
|
||||
For development, `FastMCPApp` has a `run()` shortcut that wraps itself in a temporary `FastMCP` server:
|
||||
|
||||
```python
|
||||
app = FastMCPApp("Contacts")
|
||||
|
|
@ -417,9 +352,9 @@ if __name__ == "__main__":
|
|||
app.run()
|
||||
```
|
||||
|
||||
## Complete Example: Contact Manager
|
||||
## A full example: contact manager
|
||||
|
||||
This pulls together everything — entry points, backend tools, callable references, forms (both manual and Pydantic), state management, and actions:
|
||||
This brings everything together — entry point, backend tools, Pydantic form, manual form, state, actions, and multi-visibility.
|
||||
|
||||
```python expandable
|
||||
from __future__ import annotations
|
||||
|
|
@ -437,8 +372,6 @@ from prefab_ui.rx import RESULT, Rx
|
|||
from pydantic import BaseModel, Field
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
# Data
|
||||
|
||||
contacts_db: list[dict] = [
|
||||
{"name": "Arthur Dent", "email": "arthur@earth.com", "category": "Customer"},
|
||||
{"name": "Ford Prefect", "email": "ford@betelgeuse.org", "category": "Partner"},
|
||||
|
|
@ -451,8 +384,6 @@ class ContactModel(BaseModel):
|
|||
category: Literal["Customer", "Vendor", "Partner", "Other"] = "Other"
|
||||
|
||||
|
||||
# App
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
|
||||
|
|
@ -528,11 +459,11 @@ if __name__ == "__main__":
|
|||
mcp.run()
|
||||
```
|
||||
|
||||
This example is also available as a runnable server at `examples/apps/contacts/contacts_server.py`.
|
||||
Also available as a runnable server at `examples/apps/contacts/contacts_server.py`.
|
||||
|
||||
## Next Steps
|
||||
## Next steps
|
||||
|
||||
- **[Prefab Apps](/apps/prefab)** — Components, state, and reactive displays (the building blocks)
|
||||
- **[Patterns](/apps/patterns)** — Copy-paste examples for common UIs
|
||||
- **[Development](/apps/development)** — Preview and test app tools locally
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference and advanced patterns
|
||||
- **[Interactive Tools](/apps/prefab)** — the building blocks: charts, tables, dashboards, reactive state
|
||||
- **[Examples](/apps/examples)** — complete working servers
|
||||
- **[Development](/apps/development)** — preview and test app tools locally
|
||||
- **[Prefab UI docs](https://prefab.prefect.io)** — full component reference
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@ title: Custom HTML Apps
|
|||
sidebarTitle: Custom HTML
|
||||
description: Build apps with your own HTML, CSS, and JavaScript using the MCP Apps extension directly.
|
||||
icon: code
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
The [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) is an open protocol that lets tools return interactive UIs — an HTML page rendered in a sandboxed iframe inside the host client. [Prefab UI](/apps/prefab) builds on this protocol so you never have to think about it, but when you need full control — custom rendering, a specific JavaScript framework, maps, 3D, video — you can use the MCP Apps extension directly.
|
||||
Everything on this page is for when you want full control: your own HTML, your own JavaScript framework, a map library, a 3D viewer, custom video playback. [Interactive Tools](/apps/prefab) wrap the MCP Apps extension so you never have to think about it — this page is what you reach for when you need to think about it.
|
||||
|
||||
This page covers how to write custom HTML apps and wire them up in FastMCP. You'll be working with the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) JavaScript SDK for host communication, and FastMCP's `AppConfig` for resource and CSP management.
|
||||
You'll be working with two things: the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) JavaScript SDK for host communication, and FastMCP's `AppConfig` for resources and CSP.
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
An MCP App has two parts:
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ def my_tool() -> str:
|
|||
return "result"
|
||||
```
|
||||
|
||||
### Tool Visibility
|
||||
### Tool visibility
|
||||
|
||||
The `visibility` field controls where a tool appears:
|
||||
|
||||
|
|
@ -88,7 +87,7 @@ def refresh_data() -> str:
|
|||
return fetch_latest()
|
||||
```
|
||||
|
||||
### AppConfig Fields
|
||||
### AppConfig fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
|
|
@ -103,9 +102,9 @@ def refresh_data() -> str:
|
|||
On **resources**, `resource_uri` and `visibility` must not be set — the resource *is* the UI. Use `AppConfig` on resources only for `csp`, `permissions`, and other display settings.
|
||||
</Note>
|
||||
|
||||
## UI Resources
|
||||
## UI resources
|
||||
|
||||
Resources using the `ui://` scheme are automatically served with the MIME type `text/html;profile=mcp-app`. You don't need to set this manually.
|
||||
Resources using the `ui://` scheme are automatically served with the MIME type `text/html;profile=mcp-app`. No need to set it manually.
|
||||
|
||||
```python
|
||||
@mcp.resource("ui://my-app/view.html")
|
||||
|
|
@ -115,7 +114,7 @@ def my_view() -> str:
|
|||
|
||||
The HTML can be anything — a full single-page app, a simple display, or a complex interactive tool. The host renders it in a sandboxed iframe and establishes a `postMessage` channel for communication.
|
||||
|
||||
### Writing the App HTML
|
||||
### Writing the app HTML
|
||||
|
||||
Your HTML app communicates with the host using the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) JavaScript SDK. The simplest approach is to load it from a CDN:
|
||||
|
||||
|
|
@ -204,7 +203,7 @@ def my_view() -> str:
|
|||
|
||||
Hosts may or may not grant these permissions. Your app should use JavaScript feature detection as a fallback.
|
||||
|
||||
## Example: QR Code Server
|
||||
## Example: a QR code server
|
||||
|
||||
This example creates a tool that generates QR codes and an app that renders them as images. It's based on the [official MCP Apps example](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server). Requires the `qrcode[pil]` package.
|
||||
|
||||
|
|
@ -286,7 +285,7 @@ def view() -> str:
|
|||
|
||||
The tool generates a QR code as a base64 PNG. The resource loads the MCP Apps JS SDK from unpkg (declared in the CSP), listens for tool results, and renders the image. The host wires them together — when the LLM calls `generate_qr`, the QR code appears in an interactive frame inside the conversation.
|
||||
|
||||
## Checking Client Support
|
||||
## Checking client support
|
||||
|
||||
Not all hosts support the Apps extension. You can check at runtime using the tool's [context](/servers/context):
|
||||
|
||||
|
|
|
|||
|
|
@ -3,179 +3,70 @@ title: Apps
|
|||
sidebarTitle: Overview
|
||||
description: Give your tools interactive UIs rendered directly in the conversation.
|
||||
icon: grid-2
|
||||
tag: NEW
|
||||
mode: center
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
||||
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.
|
||||
A FastMCP app is a tool that returns an interactive UI instead of text. When the host calls it, the user sees a chart, a table, a form, or a whole dashboard rendered right inside the conversation, with working sort, search, tooltips, and state.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-showcase.png" alt="A Prefab app showing forms, charts, metrics, progress bars, data tables, and interactive controls — all built in Python" />
|
||||
</Frame>
|
||||
<div style={{
|
||||
margin: '0 clamp(-180px, calc(-18vw + 90px), 0px) 2rem',
|
||||
maxHeight: '700px',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
maskImage: 'linear-gradient(to bottom, black 75%, transparent)',
|
||||
WebkitMaskImage: 'linear-gradient(to bottom, black 75%, transparent)',
|
||||
}}>
|
||||
<iframe src="/apps/demos/hitchhikers.html" style={{width:"100%", height:"2000px", border:"none", borderRadius:"8px", background:"transparent"}} frameBorder="0" scrolling="no" allowtransparency="true"></iframe>
|
||||
</div>
|
||||
|
||||
FastMCP builds on the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) with [Prefab](https://prefab.prefect.io), a Python component library that compiles to interactive UIs. You write Python; the user sees charts, tables, forms, and dashboards.
|
||||
The dashboard above is a [Prefab](https://prefab.prefect.io) showcase — a taste of what you can deliver from a FastMCP tool. Every card, chart, slider, dialog, and carousel is a Python component. Build a composition like this, add `@mcp.tool(app=True)`, and the host renders it inside the conversation.
|
||||
|
||||
<Note>
|
||||
The examples throughout the Apps docs require the `apps` extra:
|
||||
Under the hood, FastMCP builds on the [MCP Apps extension](https://modelcontextprotocol.io/docs/extensions/apps) and uses Prefab to describe UIs in Python.
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
```
|
||||
|
||||
This installs [Prefab UI](https://prefab.prefect.io), the component library used to build app UIs.
|
||||
</Note>
|
||||
<PrefabPinWarning />
|
||||
|
||||
<Warning>
|
||||
FastMCP pins a **minimum** version of `prefab-ui` for compatibility but intentionally does **not** pin an upper bound. Prefab is a rapidly evolving library with frequent breaking changes. If you are deploying to production, you **must** pin `prefab-ui` to a specific version in your own dependencies. Without a pin, a fresh deploy could pull a newer Prefab version that changes component APIs, breaking your app.
|
||||
</Warning>
|
||||
## Pick your path
|
||||
|
||||
## Which Approach?
|
||||
Four patterns cover almost everything you'd want to build. Most apps start with Interactive Tools; you only reach for the others when you've hit a specific limit.
|
||||
|
||||
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.
|
||||
### [Interactive Tools](/apps/prefab) — start here
|
||||
|
||||
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)**.
|
||||
|
||||
FastMCP also includes ready-made **[app providers](/apps/providers/approval)** that add common capabilities with a single `add_provider()` call.
|
||||
|
||||
## Building Apps
|
||||
|
||||
### Prefab Apps
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
The quickest way to give a tool a visual UI. Add `app=True` to any tool and return a Prefab component — when the host calls it, the user sees an interactive UI instead of a JSON blob:
|
||||
Add `app=True` to a tool and return a Prefab component. Charts, tables, dashboards, and client-side interactivity (toggles, tabs, filtering) all work without any server round-trips.
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Dashboard")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def revenue_chart(year: int) -> PrefabApp:
|
||||
"""Show annual revenue as an interactive bar chart."""
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000},
|
||||
{"quarter": "Q2", "revenue": 51000},
|
||||
{"quarter": "Q3", "revenue": 47000},
|
||||
{"quarter": "Q4", "revenue": 63000},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"{year} Revenue")
|
||||
BarChart(
|
||||
data=data,
|
||||
series=[ChartSeries(data_key="revenue", label="Revenue")],
|
||||
x_axis="quarter",
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
def team_directory() -> DataTable:
|
||||
return DataTable(columns=[...], rows=employees, search=True)
|
||||
```
|
||||
|
||||
Prefab apps aren't limited to static displays. Prefab's state system and client-side actions (toggles, tabs, conditionals) all work. You can even call other tools from the UI using `CallTool`. There's no hard wall on what a Prefab app can do.
|
||||
### [FastMCPApp](/apps/interactive-apps) — when the UI calls back to the server
|
||||
|
||||
See [Prefab Apps](/apps/prefab) for the full guide.
|
||||
Forms that save data, buttons that trigger backend work, search that hits a database. `FastMCPApp` manages the wiring between UI actions and backend tools, with stable tool identifiers that survive server composition.
|
||||
|
||||
### FastMCPApp
|
||||
### [Generative UI](/apps/generative) — when the LLM writes the UI
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
When your app has a lot of server-side interaction — forms that save data, search that queries a database, multi-step workflows — managing the connection between UI and backend tools gets complicated fast. Which tools should the model see vs. only the UI? What happens to tool references when servers are composed under namespaces? How do you keep `CallTool("save_contact")` working when the tool name changes?
|
||||
|
||||
`FastMCPApp` is a class that solves these problems. It gives you two decorators that work together:
|
||||
|
||||
- **`@app.ui()`** — entry-point tools the model calls to open the app
|
||||
- **`@app.tool()`** — backend tools the UI calls via `CallTool`
|
||||
|
||||
Backend tools get stable identifiers that survive namespacing, visibility is managed automatically (the model sees entry points, the UI sees backends), and `CallTool` accepts tool names that resolve correctly regardless of how servers are composed:
|
||||
Register one provider and the model can write Prefab code tailored to the current data and request. The user watches the UI build up as the model generates it.
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Form, Input, Button, ForEach, Row, Text, Badge, Separator,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
|
||||
@app.tool()
|
||||
def save_contact(name: str, email: str) -> list[dict]:
|
||||
"""Save a contact and return the updated list."""
|
||||
db.append({"name": name, "email": email})
|
||||
return list(db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def contact_manager() -> PrefabApp:
|
||||
"""Open the contact manager."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
with ForEach("contacts") as contact:
|
||||
with Row(gap=2):
|
||||
Text(contact.name)
|
||||
Badge(contact.email)
|
||||
Separator()
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Saved!", variant="success"),
|
||||
],
|
||||
)
|
||||
):
|
||||
Input(name="name", label="Name", required=True)
|
||||
Input(name="email", label="Email", required=True)
|
||||
Button("Save")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Server", providers=[app])
|
||||
```
|
||||
|
||||
You *can* build server-interactive UIs without `FastMCPApp` — it's all the same protocol underneath. But once you have multiple tools, composition concerns, or visibility requirements, `FastMCPApp` handles the complexity so you don't have to.
|
||||
|
||||
See [FastMCPApp](/apps/interactive-apps) for the full guide.
|
||||
|
||||
### Generative UI
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
Instead of pre-building a UI, the LLM can write one from scratch. The `GenerativeUI` provider registers tools that let the model write Prefab Python code, execute it in a sandbox, and render the result — with streaming so the user watches the UI build up in real time.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("Prefab Studio")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
See [Generative UI](/apps/generative) for the full guide, or the [provider reference](/apps/providers/generative) for configuration options.
|
||||
### [Custom HTML](/apps/low-level) — when you need full control
|
||||
|
||||
### Custom HTML
|
||||
Write your own HTML, CSS, and JavaScript. Use a specific framework, drop in a map or 3D viewer, embed video. You're talking to the MCP Apps protocol directly.
|
||||
|
||||
All the approaches above use [Prefab UI](https://prefab.prefect.io) to build UIs in pure Python. If you need full control — your own HTML, CSS, JavaScript, a specific framework — you can use the [MCP Apps extension directly](/apps/low-level). You write the HTML yourself and communicate with the host via the [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) SDK.
|
||||
## What's next
|
||||
|
||||
## Previewing Apps Locally
|
||||
|
||||
The `fastmcp dev apps` command launches a browser-based preview for your app tools — no MCP host client needed. See [Development](/apps/development).
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
```
|
||||
- **[Quickstart](/apps/quickstart)** — build a working app in a minute
|
||||
- **[Examples](/apps/examples)** — complete working servers you can run today
|
||||
- **[Providers](/apps/providers/approval)** — ready-made capabilities (approvals, choice pickers, file upload, forms) you add with one line
|
||||
- **[Development](/apps/development)** — preview app tools locally with `fastmcp dev apps`
|
||||
|
|
|
|||
|
|
@ -1,431 +0,0 @@
|
|||
---
|
||||
title: Patterns
|
||||
sidebarTitle: Patterns
|
||||
description: Copy-paste examples for common tool UIs.
|
||||
icon: grid-2-plus
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
Each pattern below is a complete, copy-pasteable tool. They're organized by what you're building — pick the one closest to your use case, paste it, and adapt.
|
||||
|
||||
For the full set of available components — layout containers, form controls, overlays, and more — see the [Prefab component reference](https://prefab.prefect.io/docs/components).
|
||||
|
||||
## Charts
|
||||
|
||||
Prefab includes [bar, line, area, pie, radar, and radial charts](https://prefab.prefect.io/docs/components/charts). They render client-side with tooltips, legends, and responsive sizing.
|
||||
|
||||
### Bar Chart
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def quarterly_revenue(year: int) -> PrefabApp:
|
||||
"""Show quarterly revenue as a bar chart."""
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000, "costs": 28000},
|
||||
{"quarter": "Q2", "revenue": 51000, "costs": 31000},
|
||||
{"quarter": "Q3", "revenue": 47000, "costs": 29000},
|
||||
{"quarter": "Q4", "revenue": 63000, "costs": 35000},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"{year} Revenue vs Costs")
|
||||
BarChart(
|
||||
data=data,
|
||||
series=[
|
||||
ChartSeries(data_key="revenue", label="Revenue"),
|
||||
ChartSeries(data_key="costs", label="Costs"),
|
||||
],
|
||||
x_axis="quarter",
|
||||
show_legend=True,
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
Multiple `ChartSeries` entries plot different data keys. Add `stacked=True` to stack bars, or `horizontal=True` to flip the axes.
|
||||
|
||||
### Area Chart
|
||||
|
||||
`LineChart` and `AreaChart` share the same API as `BarChart`, with `curve` for interpolation and `show_dots` for data points:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import AreaChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def usage_trend() -> PrefabApp:
|
||||
"""Show API usage over time."""
|
||||
data = [
|
||||
{"date": "Feb 1", "requests": 1200},
|
||||
{"date": "Feb 2", "requests": 1350},
|
||||
{"date": "Feb 3", "requests": 980},
|
||||
{"date": "Feb 4", "requests": 1500},
|
||||
{"date": "Feb 5", "requests": 1420},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("API Usage")
|
||||
AreaChart(
|
||||
data=data,
|
||||
series=[ChartSeries(data_key="requests", label="Requests")],
|
||||
x_axis="date",
|
||||
curve="smooth",
|
||||
height=250,
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
### Pie and Donut Charts
|
||||
|
||||
`PieChart` uses `data_key` (the numeric value) and `name_key` (the label). Set `inner_radius` for a donut:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Charts")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def ticket_breakdown() -> PrefabApp:
|
||||
"""Show open tickets by category."""
|
||||
data = [
|
||||
{"category": "Bug", "count": 23},
|
||||
{"category": "Feature", "count": 15},
|
||||
{"category": "Docs", "count": 8},
|
||||
{"category": "Infra", "count": 12},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Open Tickets")
|
||||
PieChart(
|
||||
data=data,
|
||||
data_key="count",
|
||||
name_key="category",
|
||||
show_legend=True,
|
||||
inner_radius=60,
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Data Tables
|
||||
|
||||
[DataTable](https://prefab.prefect.io/docs/components/data-display/data-table) provides sortable columns, full-text search, and pagination — all client-side:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, DataTable, DataTableColumn
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Directory")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def employee_directory() -> PrefabApp:
|
||||
"""Show a searchable, sortable employee directory."""
|
||||
employees = [
|
||||
{"name": "Alice Chen", "department": "Engineering", "role": "Staff Engineer", "location": "SF"},
|
||||
{"name": "Bob Martinez", "department": "Design", "role": "Lead Designer", "location": "NYC"},
|
||||
{"name": "Carol Johnson", "department": "Engineering", "role": "Senior Engineer", "location": "London"},
|
||||
{"name": "David Kim", "department": "Product", "role": "Product Manager", "location": "SF"},
|
||||
{"name": "Eva Müller", "department": "Engineering", "role": "Engineer", "location": "Berlin"},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Employee Directory")
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="department", header="Department", sortable=True),
|
||||
DataTableColumn(key="role", header="Role"),
|
||||
DataTableColumn(key="location", header="Office", sortable=True),
|
||||
],
|
||||
rows=employees,
|
||||
search=True,
|
||||
paginated=True,
|
||||
page_size=15,
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Status Displays
|
||||
|
||||
Cards, badges, progress bars, and grids combine naturally for dashboards:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Row, Grid, Heading, Text, Muted, Badge,
|
||||
Card, CardContent, Progress, Separator,
|
||||
)
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Monitoring")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def system_status() -> PrefabApp:
|
||||
"""Show current system health."""
|
||||
services = [
|
||||
{"name": "API Gateway", "status": "healthy", "ok": True, "latency_ms": 12, "uptime_pct": 99.9},
|
||||
{"name": "Database", "status": "healthy", "ok": True, "latency_ms": 3, "uptime_pct": 99.99},
|
||||
{"name": "Cache", "status": "degraded", "ok": False, "latency_ms": 45, "uptime_pct": 98.2},
|
||||
{"name": "Queue", "status": "healthy", "ok": True, "latency_ms": 8, "uptime_pct": 99.8},
|
||||
]
|
||||
all_ok = all(s["ok"] for s in services)
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
with Row(gap=2, align="center"):
|
||||
Heading("System Status")
|
||||
Badge(
|
||||
"All Healthy" if all_ok else "Degraded",
|
||||
variant="success" if all_ok else "destructive",
|
||||
)
|
||||
Separator()
|
||||
with Grid(columns=2, gap=4):
|
||||
for svc in services:
|
||||
with Card():
|
||||
with CardContent():
|
||||
with Row(gap=2, align="center"):
|
||||
Text(svc["name"], css_class="font-medium")
|
||||
Badge(
|
||||
svc["status"],
|
||||
variant="success" if svc["ok"] else "destructive",
|
||||
)
|
||||
Muted(f"Response: {svc['latency_ms']}ms")
|
||||
Progress(value=svc["uptime_pct"])
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Reactive Displays
|
||||
|
||||
These patterns use state and `Rx()` for client-side interactivity — no server calls needed.
|
||||
|
||||
### Feature Toggles
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Switch, Alert, If, Separator
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Flags")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def feature_flags() -> PrefabApp:
|
||||
"""Toggle feature flags with live preview."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Feature Flags")
|
||||
Switch(name="dark_mode", label="Dark Mode")
|
||||
Switch(name="beta", label="Beta Features")
|
||||
Separator()
|
||||
with If(Rx("dark_mode")):
|
||||
Alert(title="Dark mode enabled", description="UI will use dark theme.")
|
||||
with If(Rx("beta")):
|
||||
Alert(
|
||||
title="Beta features active",
|
||||
description="Experimental features are now visible.",
|
||||
variant="warning",
|
||||
)
|
||||
|
||||
return PrefabApp(view=view, state={"dark_mode": False, "beta": False})
|
||||
```
|
||||
|
||||
### Tabs
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Text, Muted, Badge, Row,
|
||||
DataTable, DataTableColumn, Tabs, Tab, ForEach,
|
||||
)
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Projects")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def project_overview() -> PrefabApp:
|
||||
"""Show project details organized in tabs."""
|
||||
project = {
|
||||
"name": "FastMCP v3",
|
||||
"description": "Next generation MCP framework with Apps support.",
|
||||
"status": "Active",
|
||||
"members": [
|
||||
{"name": "Alice Chen", "role": "Lead"},
|
||||
{"name": "Bob Martinez", "role": "Design"},
|
||||
],
|
||||
"activity": [
|
||||
{"timestamp": "2 hours ago", "message": "Merged PR #342"},
|
||||
{"timestamp": "1 day ago", "message": "Released v3.0.1"},
|
||||
],
|
||||
}
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(project["name"])
|
||||
with Tabs():
|
||||
with Tab("Overview"):
|
||||
Text(project["description"])
|
||||
with Row(gap=4):
|
||||
Badge(project["status"])
|
||||
|
||||
with Tab("Members"):
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role"),
|
||||
],
|
||||
rows=project["members"],
|
||||
)
|
||||
|
||||
with Tab("Activity"):
|
||||
with ForEach("activity") as item:
|
||||
with Row(gap=2):
|
||||
Muted(item.timestamp)
|
||||
Text(item.message)
|
||||
|
||||
return PrefabApp(view=view, state={"activity": project["activity"]})
|
||||
```
|
||||
|
||||
### Accordion
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Column, Heading, Row, Text, Badge, Progress,
|
||||
Accordion, AccordionItem,
|
||||
)
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("API Monitor")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def api_health() -> PrefabApp:
|
||||
"""Show health details for each API endpoint."""
|
||||
endpoints = [
|
||||
{"path": "/api/users", "status": 200, "healthy": True, "avg_ms": 45, "p99_ms": 120, "uptime_pct": 99.9},
|
||||
{"path": "/api/orders", "status": 200, "healthy": True, "avg_ms": 82, "p99_ms": 250, "uptime_pct": 99.7},
|
||||
{"path": "/api/search", "status": 200, "healthy": True, "avg_ms": 150, "p99_ms": 500, "uptime_pct": 99.5},
|
||||
{"path": "/api/webhooks", "status": 503, "healthy": False, "avg_ms": 2000, "p99_ms": 5000, "uptime_pct": 95.1},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("API Health")
|
||||
with Accordion(multiple=True):
|
||||
for ep in endpoints:
|
||||
with AccordionItem(ep["path"]):
|
||||
with Row(gap=4):
|
||||
Badge(
|
||||
f"{ep['status']}",
|
||||
variant="success" if ep["healthy"] else "destructive",
|
||||
)
|
||||
Text(f"Avg: {ep['avg_ms']}ms")
|
||||
Text(f"P99: {ep['p99_ms']}ms")
|
||||
Progress(value=ep["uptime_pct"])
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
## Interactive Patterns
|
||||
|
||||
These patterns call server tools. For context on `FastMCPApp`, `@app.tool()`, and `CallTool`, see [FastMCPApp](/apps/interactive-apps).
|
||||
|
||||
### Contact Form
|
||||
|
||||
```python
|
||||
from prefab_ui.actions import SetState, ShowToast
|
||||
from prefab_ui.actions.mcp import CallTool
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Badge, Button, Column, ForEach, Form, Heading,
|
||||
Input, Muted, Row, Select, SelectOption, Separator, Text, Textarea,
|
||||
)
|
||||
from prefab_ui.rx import RESULT
|
||||
from fastmcp import FastMCP, FastMCPApp
|
||||
|
||||
app = FastMCPApp("Contacts")
|
||||
|
||||
contacts_db: list[dict] = [
|
||||
{"name": "Zaphod Beeblebrox", "email": "zaphod@galaxy.gov", "category": "Partner"},
|
||||
]
|
||||
|
||||
|
||||
@app.tool()
|
||||
def save_contact(
|
||||
name: str, email: str, category: str = "Other", notes: str = "",
|
||||
) -> list[dict]:
|
||||
"""Save a new contact and return the updated list."""
|
||||
contacts_db.append({"name": name, "email": email, "category": category})
|
||||
return list(contacts_db)
|
||||
|
||||
|
||||
@app.ui()
|
||||
def contact_form() -> PrefabApp:
|
||||
"""Contact list with an add form."""
|
||||
with Column(gap=6, css_class="p-6") as view:
|
||||
Heading("Contacts")
|
||||
|
||||
with ForEach("contacts") as contact:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(contact.name, css_class="font-medium")
|
||||
Muted(contact.email)
|
||||
Badge(contact.category)
|
||||
|
||||
Separator()
|
||||
|
||||
with Form(
|
||||
on_submit=CallTool(
|
||||
"save_contact",
|
||||
on_success=[
|
||||
SetState("contacts", RESULT),
|
||||
ShowToast("Contact saved!", variant="success"),
|
||||
],
|
||||
on_error=ShowToast("Failed to save", variant="error"),
|
||||
)
|
||||
):
|
||||
Input(name="name", label="Full Name", required=True)
|
||||
Input(name="email", label="Email", input_type="email", required=True)
|
||||
with Select(name="category", label="Category"):
|
||||
SelectOption("Customer", value="Customer")
|
||||
SelectOption("Vendor", value="Vendor")
|
||||
SelectOption("Partner", value="Partner")
|
||||
SelectOption("Other", value="Other")
|
||||
Textarea(name="notes", label="Notes", placeholder="Optional notes...")
|
||||
Button("Save Contact")
|
||||
|
||||
return PrefabApp(view=view, state={"contacts": list(contacts_db)})
|
||||
|
||||
|
||||
mcp = FastMCP("Server", providers=[app])
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — Managed tool binding for server-connected UIs
|
||||
- **[Development](/apps/development)** — Preview app tools locally with `fastmcp dev apps`
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference, layout guides, and more
|
||||
|
|
@ -1,321 +1,254 @@
|
|||
---
|
||||
title: Prefab UI
|
||||
sidebarTitle: Prefab UI
|
||||
description: The component library behind FastMCP apps — charts, tables, dashboards, forms, and reactive displays.
|
||||
title: Interactive Tools
|
||||
sidebarTitle: Interactive Tools
|
||||
description: Turn your tools into interactive UIs with charts, tables, and dashboards.
|
||||
icon: palette
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
import PrefabPinWarning from '/snippets/prefab-pin-warning.mdx'
|
||||
|
||||
<VersionBadge version="3.1.0" />
|
||||
|
||||
<Warning>
|
||||
[Prefab](https://prefab.prefect.io) is in early, active development — breaking changes can occur with any release. FastMCP pins a minimum version of `prefab-ui` for compatibility but does not pin an upper bound. If you are deploying to production, **pin `prefab-ui` to a specific version** in your own dependencies.
|
||||
</Warning>
|
||||
<PrefabPinWarning />
|
||||
|
||||
[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.
|
||||
<iframe src="/apps/demos/dashboard.html" style={{width:"100%", height:"680px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
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.
|
||||
Believe it or not, that dashboard is a FastMCP tool. The chart has tooltips. The table is sortable. The badges are styled by deal stage. The whole thing is about 40 lines of Python, and the user sees it right inside their conversation instead of a wall of JSON.
|
||||
|
||||
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).
|
||||
The pattern behind every example on this page is the same: add `app=True` to your tool, build a UI with [Prefab](https://prefab.prefect.io) components, and return it as a `PrefabApp`. Prefab has [100+ components](https://prefab.prefect.io/docs/components), from data tables and charts to forms and progress bars. You compose them in Python; the host renders them as a live, interactive application.
|
||||
|
||||
## Getting Started
|
||||
## Start with a table
|
||||
|
||||
Here's a tool that returns a bar chart:
|
||||
Most tools return data the user wants to explore. A `DataTable` is often the smallest useful upgrade — your data goes from a JSON blob to a searchable, sortable table:
|
||||
|
||||
<iframe src="/apps/demos/data-table.html" style={{width:"100%", height:"530px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Dashboard")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def revenue_chart(year: int) -> PrefabApp:
|
||||
"""Show annual revenue as an interactive bar chart."""
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000},
|
||||
{"quarter": "Q2", "revenue": 51000},
|
||||
{"quarter": "Q3", "revenue": 47000},
|
||||
{"quarter": "Q4", "revenue": 63000},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading(f"{year} Revenue")
|
||||
BarChart(
|
||||
data=data,
|
||||
series=[ChartSeries(data_key="revenue", label="Revenue")],
|
||||
x_axis="quarter",
|
||||
)
|
||||
|
||||
return PrefabApp(view=view)
|
||||
```
|
||||
|
||||
The `app=True` flag tells FastMCP this tool returns a UI. When a host calls the tool, the user sees an interactive chart instead of a JSON blob. The [Patterns](/apps/patterns) page has more examples.
|
||||
|
||||
## Layout and Components
|
||||
|
||||
Prefab uses Python's `with` statement to express nesting. Containers like `Column`, `Row`, and `Grid` collect their children automatically:
|
||||
|
||||
```python
|
||||
from prefab_ui.components import (
|
||||
Column, Row, Grid, Heading, Text, Muted, Badge,
|
||||
Card, CardContent, Separator,
|
||||
)
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Team Status")
|
||||
Separator()
|
||||
with Grid(columns=2, gap=4):
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("API Gateway", css_class="font-medium")
|
||||
Badge("healthy", variant="success")
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text("Cache", css_class="font-medium")
|
||||
Badge("degraded", variant="destructive")
|
||||
```
|
||||
|
||||
You can also use Python loops to generate components at build time:
|
||||
|
||||
```python
|
||||
services = [
|
||||
{"name": "API", "status": "healthy", "ok": True},
|
||||
{"name": "Cache", "status": "degraded", "ok": False},
|
||||
]
|
||||
|
||||
with Grid(columns=2, gap=4):
|
||||
for svc in services:
|
||||
with Card():
|
||||
with CardContent():
|
||||
Text(svc["name"])
|
||||
Badge(
|
||||
svc["status"],
|
||||
variant="success" if svc["ok"] else "destructive",
|
||||
)
|
||||
```
|
||||
|
||||
Build-time loops produce static content — the data is baked into the component tree at construction time. For dynamic iteration over state that changes at render time, use `ForEach` (covered below).
|
||||
|
||||
The full component library — layout containers, data display, charts, forms, overlays — is documented in the [Prefab component reference](https://prefab.prefect.io/docs/components).
|
||||
|
||||
## State and Reactivity
|
||||
|
||||
Display tools can be interactive without calling the server. The key is **state** — a client-side key-value store that lives in the browser. Components read from state, actions mutate it, and the UI re-renders automatically.
|
||||
|
||||
### Declaring State
|
||||
|
||||
Pass a `state` dict to `PrefabApp` to declare initial state, then use `Rx("key")` to create reactive references:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, Switch, Alert, If
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Flags")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def feature_flags() -> PrefabApp:
|
||||
"""Toggle feature flags with live preview."""
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Feature Flags")
|
||||
Switch(name="dark_mode", label="Dark Mode")
|
||||
Switch(name="beta", label="Beta Features")
|
||||
|
||||
with If(Rx("dark_mode")):
|
||||
Alert(title="Dark mode enabled")
|
||||
with If(Rx("beta")):
|
||||
Alert(title="Beta features active", variant="warning")
|
||||
|
||||
return PrefabApp(view=view, state={"dark_mode": False, "beta": False})
|
||||
```
|
||||
|
||||
Three things to notice here:
|
||||
|
||||
The `state` dict on `PrefabApp` declares the keys and their starting values. `Rx("dark_mode")` creates a reactive reference that compiles to `{{ dark_mode }}` in the wire protocol.
|
||||
|
||||
Interactive components with a `name` prop automatically bind to state. The `Switch(name="dark_mode")` syncs its on/off value to the `dark_mode` state key on every toggle — no event wiring needed.
|
||||
|
||||
`If(Rx("dark_mode"))` shows its children only when the state key is truthy. When the switch flips, the condition re-evaluates instantly in the browser.
|
||||
|
||||
### Reactive References with Rx
|
||||
|
||||
The `Rx` class is how you reference state in component props:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
count = Rx("count")
|
||||
```
|
||||
|
||||
Rx objects support arithmetic, comparisons, and formatting — they compile to expressions the renderer evaluates at render time:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Text, Slider
|
||||
from prefab_ui.rx import Rx
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Calculator")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def tip_calculator() -> PrefabApp:
|
||||
"""Calculate tip with a slider."""
|
||||
tip_pct = Rx("tip_pct")
|
||||
bill = Rx("bill")
|
||||
|
||||
tip_amount = tip_pct / 100 * bill
|
||||
total = bill + tip_amount
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Slider(name="bill", label="Bill Amount", min=0, max=500, step=0.5)
|
||||
Slider(name="tip_pct", label="Tip %", min=0, max=50)
|
||||
Text(f"Tip: {tip_amount.currency()}")
|
||||
Text(f"Total: {total.currency()}")
|
||||
|
||||
return PrefabApp(view=view, state={"bill": 50.00, "tip_pct": 18})
|
||||
```
|
||||
|
||||
`Rx("tip_pct") / 100 * Rx("bill")` builds a compound expression — it doesn't do the math in Python. The renderer evaluates it live as the sliders move. The `.currency()` pipe formats the result as currency.
|
||||
|
||||
#### Pipes
|
||||
|
||||
Rx objects support formatting pipes that transform values at render time:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
price = Rx("price")
|
||||
ratio = Rx("ratio")
|
||||
name = Rx("name")
|
||||
|
||||
price.currency() # $42.50
|
||||
price.currency("EUR") # EUR format
|
||||
ratio.percent() # 85%
|
||||
name.upper() # ALICE
|
||||
name.truncate(10) # alice (or truncated if longer)
|
||||
```
|
||||
|
||||
Number pipes include `currency`, `percent`, `number`, `compact`, `round`, and `abs`. String pipes include `upper`, `lower`, and `truncate`. See the [Prefab expression docs](https://prefab.prefect.io/docs/concepts/expressions) for the full list.
|
||||
|
||||
#### Conditionals
|
||||
|
||||
The `.then()` method creates ternary expressions:
|
||||
|
||||
```python
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
connected = Rx("connected")
|
||||
|
||||
Badge(
|
||||
connected.then("Online", "Offline"),
|
||||
variant=connected.then("success", "destructive"),
|
||||
)
|
||||
```
|
||||
|
||||
### Dynamic Iteration with ForEach
|
||||
|
||||
Python `for` loops generate static content at build time. When you need to iterate over state that can change — a list that grows, items that get filtered — use `ForEach`:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Heading, ForEach, Row, Text, Badge
|
||||
from prefab_ui.components import DataTable, DataTableColumn
|
||||
from fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Directory")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_list() -> PrefabApp:
|
||||
"""Show the current team."""
|
||||
members = [
|
||||
{"name": "Alice", "role": "Engineering"},
|
||||
{"name": "Bob", "role": "Design"},
|
||||
def team_directory() -> DataTable:
|
||||
"""Browse the team directory."""
|
||||
employees = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "dept": "Platform"},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "dept": "Design"},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "dept": "Platform"},
|
||||
{"name": "David Kim", "role": "Product Manager", "dept": "Product"},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "dept": "Platform"},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "dept": "ML"},
|
||||
{"name": "Grace Park", "role": "Eng Manager", "dept": "Platform"},
|
||||
]
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Team")
|
||||
with ForEach("members") as member:
|
||||
with Row(gap=2, align="center"):
|
||||
Text(member.name, css_class="font-medium")
|
||||
Badge(member.role)
|
||||
|
||||
return PrefabApp(view=view, state={"members": members})
|
||||
```
|
||||
|
||||
`ForEach("members")` iterates over the `members` state key. The `as member` gives you an Rx proxy scoped to each item, so `member.name` resolves to `{{ $item.name }}` in the wire protocol. If the `members` state changes (e.g., through an action), the list re-renders automatically.
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
`If`, `Elif`, and `Else` control what's visible based on state:
|
||||
|
||||
```python
|
||||
from prefab_ui.app import PrefabApp
|
||||
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:
|
||||
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"):
|
||||
Text("Advanced features unlocked")
|
||||
with Else():
|
||||
Text("Basic features only")
|
||||
|
||||
# Pass state={"tier": "free"} to PrefabApp when returning
|
||||
```
|
||||
|
||||
Changes are instant — switching the dropdown re-evaluates the conditions in the browser.
|
||||
|
||||
## Giving the LLM Context
|
||||
|
||||
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
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.tools import ToolResult
|
||||
|
||||
mcp = FastMCP("Sales")
|
||||
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def sales_overview(year: int) -> ToolResult:
|
||||
"""Show sales data visually and summarize for the model."""
|
||||
data = get_sales_data(year)
|
||||
total = sum(row["revenue"] for row in data)
|
||||
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
Heading("Sales Overview")
|
||||
BarChart(data=data, series=[ChartSeries(data_key="revenue")])
|
||||
|
||||
return ToolResult(
|
||||
content=f"Total revenue for {year}: ${total:,} across {len(data)} quarters",
|
||||
structured_content=view,
|
||||
return DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="name", header="Name", sortable=True),
|
||||
DataTableColumn(key="role", header="Role", sortable=True),
|
||||
DataTableColumn(key="dept", header="Dept", sortable=True),
|
||||
],
|
||||
rows=employees,
|
||||
search=True,
|
||||
)
|
||||
```
|
||||
|
||||
The user sees the chart. The LLM sees the summary string.
|
||||
That's it. Add `app=True`, return a Prefab component instead of raw dicts. FastMCP handles the rendering, sandboxing, and security. No wrapper class needed for simple cases like this.
|
||||
|
||||
## Advanced
|
||||
## Add charts
|
||||
|
||||
<Accordion title="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:
|
||||
When numbers tell a better story as a visual, swap in a chart. The API is the same: pass your data as a list of dicts, tell the chart which keys to plot.
|
||||
|
||||
<iframe src="/apps/demos/bar-chart.html" style={{width:"100%", height:"430px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
```python
|
||||
@mcp.tool(app=True)
|
||||
def quarterly_revenue(year: int) -> BarChart:
|
||||
"""Show quarterly revenue as a bar chart."""
|
||||
data = [
|
||||
{"quarter": "Q1", "revenue": 42000, "costs": 28000},
|
||||
{"quarter": "Q2", "revenue": 51000, "costs": 31000},
|
||||
{"quarter": "Q3", "revenue": 47000, "costs": 29000},
|
||||
{"quarter": "Q4", "revenue": 63000, "costs": 35000},
|
||||
]
|
||||
|
||||
return BarChart(
|
||||
data=data,
|
||||
series=[
|
||||
ChartSeries(data_key="revenue", label="Revenue"),
|
||||
ChartSeries(data_key="costs", label="Costs"),
|
||||
],
|
||||
x_axis="quarter",
|
||||
show_legend=True,
|
||||
)
|
||||
```
|
||||
|
||||
Each `ChartSeries` plots a different key from the data. `BarChart`, `LineChart`, `AreaChart`, `PieChart`, `RadarChart`, and `RadialChart` all follow the same pattern. Hover over the bars to see tooltips.
|
||||
|
||||
<iframe src="/apps/demos/pie-chart.html" style={{width:"100%", height:"410px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
```python
|
||||
@mcp.tool(app=True)
|
||||
def ticket_breakdown() -> PieChart:
|
||||
"""Show open tickets by category."""
|
||||
data = [
|
||||
{"category": "Bug", "count": 42},
|
||||
{"category": "Feature", "count": 28},
|
||||
{"category": "Docs", "count": 15},
|
||||
{"category": "Infra", "count": 10},
|
||||
]
|
||||
|
||||
return PieChart(
|
||||
data=data,
|
||||
data_key="count",
|
||||
name_key="category",
|
||||
inner_radius=50,
|
||||
show_legend=True,
|
||||
)
|
||||
```
|
||||
|
||||
See the [Prefab chart docs](https://prefab.prefect.io/docs/components) for stacking, curves, custom colors, and more.
|
||||
|
||||
## Compose a dashboard
|
||||
|
||||
Tables and charts are useful on their own, but the real power comes from composing them. `Column` stacks children vertically, `Row` lays them out side by side, and `with` blocks establish nesting — the indentation is the layout.
|
||||
|
||||
<iframe src="/apps/demos/dashboard.html" style={{width:"100%", height:"680px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
```python expandable
|
||||
@mcp.tool(app=True)
|
||||
def sales_dashboard() -> PrefabApp:
|
||||
"""Show sales KPIs, trends, and deals."""
|
||||
monthly = [
|
||||
{"month": "Jan", "revenue": 48200, "costs": 31000},
|
||||
{"month": "Feb", "revenue": 52100, "costs": 32500},
|
||||
{"month": "Mar", "revenue": 61800, "costs": 34200},
|
||||
{"month": "Apr", "revenue": 58400, "costs": 33800},
|
||||
]
|
||||
deals = [
|
||||
{"account": "Acme Corp", "value": "$84,000", "stage": "Won"},
|
||||
{"account": "Globex Inc", "value": "$52,000", "stage": "Negotiation"},
|
||||
{"account": "Initech", "value": "$31,500", "stage": "Proposal"},
|
||||
{"account": "Wayne Enterprises", "value": "$45,000", "stage": "Lost"},
|
||||
]
|
||||
|
||||
rows = [
|
||||
{
|
||||
"account": d["account"],
|
||||
"value": d["value"],
|
||||
"stage": Badge(
|
||||
d["stage"],
|
||||
variant="success" if d["stage"] == "Won"
|
||||
else "destructive" if d["stage"] == "Lost"
|
||||
else "secondary",
|
||||
),
|
||||
}
|
||||
for d in deals
|
||||
]
|
||||
|
||||
total = sum(m["revenue"] for m in monthly)
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
with Row(gap=6):
|
||||
Metric(label="Revenue (Q1-Q4)", value=f"${total:,}")
|
||||
Metric(label="Deals", value=f"{len(deals)}")
|
||||
BarChart(
|
||||
data=monthly,
|
||||
series=[
|
||||
ChartSeries(data_key="revenue", label="Revenue"),
|
||||
ChartSeries(data_key="costs", label="Costs"),
|
||||
],
|
||||
x_axis="month",
|
||||
show_legend=True,
|
||||
)
|
||||
Separator()
|
||||
DataTable(
|
||||
columns=[
|
||||
DataTableColumn(key="account", header="Account", sortable=True),
|
||||
DataTableColumn(key="value", header="Value", sortable=True),
|
||||
DataTableColumn(key="stage", header="Stage"),
|
||||
],
|
||||
rows=rows,
|
||||
)
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
Notice how `Badge` components can be placed inside table cells — any Prefab component works as a cell value, so you can put progress bars, icons, or buttons in your tables too.
|
||||
|
||||
## Make it reactive
|
||||
|
||||
Everything above renders once from the data your Python provides. But interactive tools can also respond to user input in real time, without any server round-trips. Prefab's state system lets components read and write client-side values, so the UI updates instantly as the user interacts with it.
|
||||
|
||||
<iframe src="/apps/demos/reactive.html" style={{width:"100%", height:"500px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
Try switching regions in the dropdown, and toggling the switch on and off.
|
||||
|
||||
```python expandable
|
||||
from prefab_ui.rx import Rx
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def regional_sales() -> PrefabApp:
|
||||
"""Sales by region with a live filter."""
|
||||
north = [
|
||||
{"month": "Jan", "sales": 22000},
|
||||
{"month": "Feb", "sales": 25500},
|
||||
{"month": "Mar", "sales": 24200},
|
||||
]
|
||||
south = [
|
||||
{"month": "Jan", "sales": 5800},
|
||||
{"month": "Feb", "sales": 6400},
|
||||
{"month": "Mar", "sales": 5600},
|
||||
]
|
||||
west = [
|
||||
{"month": "Jan", "sales": 6000},
|
||||
{"month": "Feb", "sales": 6000},
|
||||
{"month": "Mar", "sales": 5600},
|
||||
]
|
||||
|
||||
with PrefabApp(
|
||||
state={
|
||||
"region": "north",
|
||||
"north": north, "south": south, "west": west,
|
||||
"show_target": True,
|
||||
},
|
||||
) as app:
|
||||
with Column(
|
||||
gap=4,
|
||||
css_class="p-6",
|
||||
let={"data": "{{ region == 'south' ? south"
|
||||
" : region == 'west' ? west"
|
||||
" : north }}"},
|
||||
):
|
||||
with Row(gap=4, align="center"):
|
||||
with Select(name="region", css_class="w-40"):
|
||||
SelectOption(value="north", label="North")
|
||||
SelectOption(value="south", label="South")
|
||||
SelectOption(value="west", label="West")
|
||||
Switch(name="show_target", css_class="ml-auto")
|
||||
Text("Show target", css_class="text-sm text-muted-foreground")
|
||||
BarChart(
|
||||
data=Rx("data"),
|
||||
series=[ChartSeries(data_key="sales", label="Sales")],
|
||||
x_axis="month",
|
||||
)
|
||||
with If(Rx("show_target")):
|
||||
Metric(label="Q1 Target", value="$75,000")
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
The `state` dict on `PrefabApp` declares initial values. The `Select` writes to the `region` key on every change. A `let` binding picks the matching dataset, and the chart re-renders. The `Switch` toggles a `Metric` on and off through `If(Rx("show_target"))`. All of this happens in the browser — no calls back to your server.
|
||||
|
||||
`Rx` is a reactive reference: `Rx("region")` compiles to an expression the renderer evaluates live. It supports arithmetic, comparisons, formatting pipes (`.currency()`, `.percent()`), and ternary conditionals (`.then()`). For the full state system, see the [Prefab state docs](https://prefab.prefect.io/docs/concepts/state) and [expression docs](https://prefab.prefect.io/docs/concepts/expressions).
|
||||
|
||||
## Content Security Policy
|
||||
|
||||
Interactive tools render in a sandboxed iframe with a strict CSP. If your tool loads external resources — embedding iframes, fetching from APIs, loading scripts — add the required domains:
|
||||
|
||||
```python
|
||||
from fastmcp.apps import PrefabAppConfig, ResourceCSP
|
||||
|
|
@ -327,40 +260,37 @@ 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.
|
||||
</Accordion>
|
||||
`PrefabAppConfig()` with no arguments is equivalent to `app=True`.
|
||||
|
||||
<Accordion title="Type inference">
|
||||
If your return type annotation is a Prefab type — `PrefabApp`, `Component`, or unions containing them — FastMCP enables app rendering automatically, even without `app=True`:
|
||||
## Giving the LLM context
|
||||
|
||||
By default, the LLM sees `"[Rendered Prefab UI]"` as the tool result. If the model needs to reason about the data, return a `ToolResult` with a text summary alongside the UI:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
def greet(name: str) -> PrefabApp:
|
||||
return PrefabApp(view=Heading(f"Hello, {name}!"))
|
||||
```
|
||||
|
||||
Explicit `app=True` is recommended for clarity.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Mixing with custom HTML">
|
||||
Prefab tools and [custom HTML tools](/apps/low-level) coexist on the same server:
|
||||
|
||||
```python
|
||||
from fastmcp.apps import AppConfig
|
||||
from fastmcp.tools import ToolResult
|
||||
|
||||
@mcp.tool(app=True)
|
||||
def team_directory() -> PrefabApp:
|
||||
...
|
||||
def sales_overview(year: int) -> ToolResult:
|
||||
"""Show sales visually, summarize for the model."""
|
||||
data = get_sales_data(year)
|
||||
total = sum(row["revenue"] for row in data)
|
||||
|
||||
@mcp.tool(app=AppConfig(resource_uri="ui://my-app/map.html"))
|
||||
def map_view() -> str:
|
||||
...
|
||||
with Column(gap=4, css_class="p-6") as view:
|
||||
BarChart(data=data, series=[ChartSeries(data_key="revenue")])
|
||||
|
||||
return ToolResult(
|
||||
content=f"Total revenue for {year}: ${total:,} across {len(data)} quarters",
|
||||
structured_content=view,
|
||||
)
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
## Next Steps
|
||||
The user sees the chart. The model sees the summary.
|
||||
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — Managed tool binding for apps with heavy server interaction
|
||||
- **[Patterns](/apps/patterns)** — Charts, tables, dashboards, and other common examples
|
||||
- **[Development](/apps/development)** — Preview app tools locally with `fastmcp dev apps`
|
||||
- **[Prefab UI Docs](https://prefab.prefect.io)** — Full component reference, advanced state patterns, and more
|
||||
## Next steps
|
||||
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — when your UI needs to call backend tools (forms, search, CRUD)
|
||||
- **[Generative UI](/apps/generative)** — let the LLM design the UI at runtime
|
||||
- **[Custom HTML](/apps/low-level)** — when Prefab isn't enough (maps, 3D, your own framework)
|
||||
- **[Examples](/apps/examples)** — complete working servers you can run today
|
||||
- **[Development](/apps/development)** — preview your tools locally with `fastmcp dev apps`
|
||||
- **[Prefab UI](https://prefab.prefect.io)** — full component reference with 100+ components, theming, and advanced patterns
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ request_approval(
|
|||
)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
When the user clicks a button, two things happen:
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ choose(
|
|||
)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
## How it works
|
||||
|
||||
Each option renders as a full-width button in a vertical stack. When the user clicks one:
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ FileUpload(
|
|||
|
||||
The `max_file_size` limit is enforced both in the UI (the DropZone rejects oversized files) and on the server (the `store_files` tool validates before calling `on_store`).
|
||||
|
||||
## Storage Scoping
|
||||
## Storage scoping
|
||||
|
||||
By default, files are stored in memory and scoped by MCP session ID. Each session gets its own isolated file store — files uploaded in one conversation aren't visible in another.
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ class SharedUpload(FileUpload):
|
|||
return "__shared__"
|
||||
```
|
||||
|
||||
## Custom Storage
|
||||
## Custom storage
|
||||
|
||||
The default implementation stores files in memory for the lifetime of the server process. For persistent storage, subclass `FileUpload` and override three methods. Each receives the current `Context`, giving you access to session IDs, auth tokens, and request metadata for partitioning and authorization.
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ This registers two tools:
|
|||
|
||||
The tool name is derived from the model class name, lowercased: `collect_{modelname}`. So `BugReport` becomes `collect_bugreport`, `ShippingAddress` becomes `collect_shippingaddress`. Use `tool_name` to override if needed. The LLM calls it with a prompt explaining what it needs, and the user gets a form with fields matching the model.
|
||||
|
||||
## Field Mapping
|
||||
## Field mapping
|
||||
|
||||
`FormInput` uses Prefab's `Form.from_model()`, which maps Pydantic types to form components:
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ FormInput(
|
|||
|
||||
Set `send_message=True` to push the result back into the conversation via `SendMessage`, triggering the LLM's next turn. Without it, the result is just the tool return value.
|
||||
|
||||
## Multiple Forms
|
||||
## Multiple forms
|
||||
|
||||
Add multiple providers for different models — each gets its own tool:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
---
|
||||
title: Generative UI
|
||||
sidebarTitle: Generative UI
|
||||
description: Let the LLM generate custom UIs at runtime
|
||||
icon: wand-magic-sparkles
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from '/snippets/version-badge.mdx'
|
||||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
`GenerativeUI` lets the LLM write Prefab Python code at runtime and render it as a streaming interactive UI. Instead of calling pre-built tools with fixed interfaces, the model creates tailored visualizations for whatever data it's working with.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.apps.generative import GenerativeUI
|
||||
|
||||
mcp = FastMCP("My Server")
|
||||
mcp.add_provider(GenerativeUI())
|
||||
```
|
||||
|
||||
This registers:
|
||||
|
||||
| Component | Type | Purpose |
|
||||
|-----------|------|---------|
|
||||
| `generate_prefab_ui` | Tool | Accepts Python code, executes in Pyodide sandbox, renders result |
|
||||
| `search_prefab_components` | Tool | Lets the LLM discover available Prefab components |
|
||||
| Generative renderer | Resource | `ui://` resource with browser-side Pyodide for streaming |
|
||||
|
||||
The LLM writes real Python — loops, f-strings, computation — using Prefab's component library (charts, tables, forms, cards, layout primitives). As the model generates tokens, the host streams partial code to the renderer via `ontoolinputpartial`, so the user watches the UI build up in real time.
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
GenerativeUI(
|
||||
tool_name="generate_prefab_ui", # Rename the generation tool
|
||||
components_tool_name="search_prefab_components", # Rename the search tool
|
||||
include_components_tool=True, # Set False to omit the search tool
|
||||
)
|
||||
```
|
||||
|
||||
## What the LLM Sees
|
||||
|
||||
The tool description includes code examples that teach the LLM the Prefab patterns. The LLM calls `generate_prefab_ui` with a `code` argument containing Prefab Python, and optionally a `data` argument to pass in real data from the conversation:
|
||||
|
||||
```python
|
||||
# The LLM generates something like:
|
||||
generate_prefab_ui(
|
||||
code="""
|
||||
from prefab_ui.components import Column, Heading
|
||||
from prefab_ui.components.charts import BarChart, ChartSeries
|
||||
from prefab_ui.app import PrefabApp
|
||||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4):
|
||||
Heading("Revenue")
|
||||
BarChart(data=data, series=[ChartSeries(data_key="revenue")], x_axis="quarter")
|
||||
""",
|
||||
data={"data": [{"quarter": "Q1", "revenue": 42000}, ...]}
|
||||
)
|
||||
```
|
||||
|
||||
The component search tool lets the LLM discover what's available before writing code — `search_prefab_components("Chart")` returns matching components with import paths.
|
||||
|
||||
## Requirements
|
||||
|
||||
Requires `fastmcp[apps]` (installs `prefab-ui`). The Pyodide sandbox for server-side validation requires Deno, which installs automatically on first use. The streaming renderer loads Pyodide from CDN in the browser — CSP is configured automatically.
|
||||
|
||||
The sandbox includes the Python standard library and Prefab. External packages (NumPy, pandas, etc.) are not available.
|
||||
|
||||
## Learn More
|
||||
|
||||
The full **[Generative UI guide](/apps/generative)** covers the streaming mechanics in detail, how to pass data, the component search tool, and sandbox limitations.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
title: Quickstart
|
||||
sidebarTitle: Quickstart
|
||||
description: Build your first MCP app in under a minute.
|
||||
description: Build your first FastMCP app in under a minute.
|
||||
icon: rocket
|
||||
tag: NEW
|
||||
---
|
||||
|
|
@ -10,33 +10,29 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
|
|||
|
||||
<VersionBadge version="3.2.0" />
|
||||
|
||||
MCP tools normally return text. FastMCP apps return interactive UIs rendered directly in the conversation: charts, tables, forms, dashboards. The easiest way to build one is with [Prefab UI](https://prefab.prefect.io), a Python component library designed for exactly this. You describe the UI in Python; Prefab compiles it to something the host can render.
|
||||
By the end of this page, you'll have a working tool that returns this:
|
||||
|
||||
This tutorial builds a working app from scratch. Here's what you'll have in about a minute:
|
||||
<iframe src="/apps/demos/team-directory.html" style={{width:"100%", height:"545px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-quickstart.png" alt="A team directory app with a pie chart and sortable data table, rendered inside a conversation in Goose" />
|
||||
</Frame>
|
||||
A pie chart the user can hover, a table they can sort and search — and a single Python tool.
|
||||
|
||||
## Setup
|
||||
|
||||
Install FastMCP with the `apps` extra, which pulls in Prefab UI:
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install "fastmcp[apps]"
|
||||
```
|
||||
|
||||
## A Tool That Returns a UI
|
||||
The `apps` extra pulls in [Prefab](https://prefab.prefect.io), the Python component library used to build app UIs.
|
||||
|
||||
When your tool has something to *show* (a table of results, a chart, a status dashboard) you can return an interactive UI instead of text. Build the visualization with Prefab components, return it from your tool, and set `app=True` so FastMCP knows to render it. The user sees a live, interactive widget right in the conversation instead of a wall of JSON.
|
||||
## Write the tool
|
||||
|
||||
Create `server.py`:
|
||||
Create `server.py`. The interesting parts: `app=True` tells FastMCP this tool renders a UI, and `with PrefabApp() as app:` is the canonical pattern for composing one.
|
||||
|
||||
```python server.py expandable
|
||||
from collections import Counter
|
||||
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import Column, Grid, Heading, DataTable, DataTableColumn
|
||||
from prefab_ui.components import Column, DataTable, DataTableColumn, Grid
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from fastmcp import FastMCP
|
||||
|
||||
|
|
@ -63,7 +59,6 @@ def team_directory() -> PrefabApp:
|
|||
|
||||
with PrefabApp() as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
Heading("Team Directory")
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=office_counts,
|
||||
|
|
@ -84,40 +79,42 @@ def team_directory() -> PrefabApp:
|
|||
return app
|
||||
```
|
||||
|
||||
That `app=True` is doing a lot behind the scenes. It tells FastMCP to set up everything the MCP Apps protocol requires: the renderer resource, the content security policy, the metadata that tells the host "this tool returns a UI." Without it, you'd wire all of that up by hand. With it, you just return Prefab components and FastMCP handles the rest. The host (Claude Desktop, Goose, etc.) loads the result in a sandboxed iframe where the user can sort columns, search, and interact, all client-side with no round-trips to your server.
|
||||
The Prefab code reads top-to-bottom. `PrefabApp()` is the root; everything inside its `with` block becomes the UI. `Column` stacks children vertically, `Grid` lays them out in columns. `DataTable` takes rows and column definitions and gives you sort and search for free.
|
||||
|
||||
The Prefab code itself reads top-to-bottom like a document. `PrefabApp()` is the root container and everything inside its `with` block becomes the app's UI. `Column` arranges children vertically. `Heading` renders a title. `DataTable` takes rows of data and column definitions, and gives you sorting and search for free. The `with` blocks establish parent-child relationships: nesting components inside each other builds the layout tree.
|
||||
`app=True` does the rest: it sets up the renderer resource, the content security policy, and the metadata that tells the host "this tool returns a UI." The host loads the result in a sandboxed iframe where the user can interact with it — all client-side, no round-trips.
|
||||
|
||||
## Running It
|
||||
## Preview it
|
||||
|
||||
FastMCP includes a dev server that renders your app tools in a browser, no MCP host needed:
|
||||
FastMCP ships a dev server that renders your app tools in a browser, no MCP host needed:
|
||||
|
||||
```bash
|
||||
fastmcp dev apps server.py
|
||||
```
|
||||
|
||||
This opens `http://localhost:8080` where you can pick a tool and see the rendered UI. Try sorting the table columns and typing in the search box.
|
||||
|
||||
## Making It Interactive
|
||||
|
||||
The table above is a static snapshot that renders once from the data your Python code provides. But Prefab apps can also respond to user interaction in real time, without any server round-trips.
|
||||
|
||||
The key concept is **state**: a client-side key-value store that components read from and write to. When the user interacts with a component, it updates state. Other components that reference that state re-render instantly. See the [Prefab state docs](https://prefab.prefect.io/docs/concepts/state) for the full guide.
|
||||
|
||||
Here's the same directory, but now clicking a row shows that person's details in a card:
|
||||
Open `http://localhost:8080`, pick `team_directory`, and try sorting columns and searching.
|
||||
|
||||
<Frame>
|
||||
<img src="/apps/images/app-quickstart-dev-2.png" alt="The team directory with a detail card showing after clicking Bob Martinez" />
|
||||
<img src="/apps/images/app-quickstart-dev-2.png" alt="The team directory rendered in the fastmcp dev apps preview, showing a pie chart, searchable table, and a detail card after clicking a row" />
|
||||
</Frame>
|
||||
|
||||
## Make it reactive
|
||||
|
||||
The UI above renders once from your Python. Prefab apps can also respond to user input live, without any server round-trips. The key concept is **state**: a client-side key-value store that components read from and write to.
|
||||
|
||||
Click a row in the demo below to see a detail card appear:
|
||||
|
||||
<iframe src="/apps/demos/team-directory-reactive.html" style={{width:"100%", height:"675px", border:"none", overflow:"hidden", borderRadius:"8px"}} frameBorder="0" scrolling="no"></iframe>
|
||||
|
||||
Add a few imports, give each member a couple more fields, wire up a click handler, and render a detail card when something's selected:
|
||||
|
||||
```python expandable server.py
|
||||
from collections import Counter
|
||||
|
||||
from prefab_ui.actions import SetState
|
||||
from prefab_ui.app import PrefabApp
|
||||
from prefab_ui.components import (
|
||||
Card, CardContent, CardHeader, Column, Grid, H3, Heading, Muted,
|
||||
Row, DataTable, DataTableColumn, Badge, Small, Text,
|
||||
Badge, Card, CardContent, CardHeader, Column, DataTable, DataTableColumn,
|
||||
Grid, H3, Row, Small, Text,
|
||||
)
|
||||
from prefab_ui.components.charts import PieChart
|
||||
from prefab_ui.components.control_flow import If
|
||||
|
|
@ -129,16 +126,12 @@ mcp = FastMCP("My First App")
|
|||
MEMBERS = [
|
||||
{"name": "Alice Chen", "role": "Staff Engineer", "office": "San Francisco", "email": "alice@company.com", "projects": 3},
|
||||
{"name": "Bob Martinez", "role": "Lead Designer", "office": "New York", "email": "bob@company.com", "projects": 5},
|
||||
{"name": "Carol Johnson", "role": "Senior Engineer", "office": "London", "email": "carol@company.com", "projects": 2},
|
||||
{"name": "David Kim", "role": "Product Manager", "office": "San Francisco", "email": "david@company.com", "projects": 7},
|
||||
{"name": "Eva Mueller", "role": "Engineer", "office": "Berlin", "email": "eva@company.com", "projects": 1},
|
||||
{"name": "Frank Lee", "role": "Data Scientist", "office": "San Francisco", "email": "frank@company.com", "projects": 4},
|
||||
{"name": "Grace Park", "role": "Engineering Manager", "office": "New York", "email": "grace@company.com", "projects": 6},
|
||||
# ... more members ...
|
||||
]
|
||||
|
||||
OFFICE_COUNTS = [
|
||||
{"office": office, "count": count}
|
||||
for office, count in Counter(m["office"] for m in MEMBERS).items()
|
||||
{"office": o, "count": c}
|
||||
for o, c in Counter(m["office"] for m in MEMBERS).items()
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -147,7 +140,6 @@ def team_directory() -> PrefabApp:
|
|||
"""Browse the team directory."""
|
||||
with PrefabApp(state={"selected": None}) as app:
|
||||
with Column(gap=4, css_class="p-6"):
|
||||
Heading("Team Directory")
|
||||
with Grid(columns=[1, 2], gap=4):
|
||||
PieChart(
|
||||
data=OFFICE_COUNTS,
|
||||
|
|
@ -187,22 +179,18 @@ def team_directory() -> PrefabApp:
|
|||
return app
|
||||
```
|
||||
|
||||
Three new ideas here:
|
||||
Three new ideas do all the work:
|
||||
|
||||
**`SetState` + `on_row_click`** is the interaction. When the user clicks a table row, `SetState("selected", Rx("$event"))` writes the clicked row's data into the `selected` state key. `$event` is a special variable that contains the event payload (in this case, the row dict).
|
||||
- **`on_row_click=SetState("selected", Rx("$event"))`** — clicking a row writes its data into the `selected` state key. `$event` is the clicked row dict.
|
||||
- **`Rx("selected.name")`** — a reactive reference. It doesn't hold a Python value; it compiles to a browser-side expression that re-evaluates whenever `selected` changes, so `Text(Rx("selected.name"))` always shows the latest clicked name.
|
||||
- **`If(STATE.selected)`** — conditionally renders its body. Before any click, `selected` is `None` and the card stays hidden.
|
||||
|
||||
**`Rx("selected.name")`** reads from state reactively. It doesn't hold a Python value. It compiles to a browser-side expression that re-evaluates live whenever `selected` changes. So `Text(Rx("selected.name"))` always shows the name of whoever was last clicked.
|
||||
The `state={"selected": None}` dict on `PrefabApp` sets the initial value. Everything else happens in the browser — no round-trips to your server when the user clicks.
|
||||
|
||||
**`If(STATE.selected)`** conditionally renders the detail card only when something has been selected. Before any click, `selected` is `None` and the card is hidden.
|
||||
## Where to go next
|
||||
|
||||
The `state` dict on `PrefabApp` sets initial values when the app loads. Run `fastmcp dev apps server.py` again and try clicking a row.
|
||||
You've built a tool that returns an interactive, reactive UI. This pattern covers a huge range of use cases: build a visualization, return it, and the user gets it rendered right in the conversation.
|
||||
|
||||
## Next Steps
|
||||
|
||||
You've built a tool that returns an interactive, reactive UI. This pattern covers a huge range of use cases: build a visualization in Prefab, return it from a tool, and the user gets dashboards, charts, data tables, and status displays right in the conversation.
|
||||
|
||||
When you need the UI to talk back to your server (forms that save data, buttons that trigger actions, search that queries a database) you promote the tool to a **[FastMCPApp](/apps/interactive-apps)**. That gives you managed backend tools, automatic visibility control, and stable routing so your UI's button clicks reach the right server-side code.
|
||||
|
||||
- **[Prefab UI](/apps/prefab)** covers the full component library: charts, forms, badges, progress bars, and the [reactive state system](https://prefab.prefect.io/docs/concepts/state) in depth.
|
||||
- **[FastMCPApp](/apps/interactive-apps)** is the next step when your UI needs to interact with backend logic.
|
||||
- **[App Providers](/apps/providers/approval)** are ready-made capabilities you can add with a single `add_provider()` call.
|
||||
- **[Interactive Tools](/apps/prefab)** — charts, tables, dashboards, reactive state, with live demos
|
||||
- **[FastMCPApp](/apps/interactive-apps)** — when the UI needs to call back to your server (forms, search, CRUD)
|
||||
- **[Examples](/apps/examples)** — complete working servers you can run today
|
||||
|
|
|
|||
|
|
@ -197,19 +197,10 @@
|
|||
"pages": [
|
||||
"apps/overview",
|
||||
"apps/quickstart",
|
||||
"apps/examples",
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Building Apps",
|
||||
"icon": "hammer",
|
||||
"pages": [
|
||||
"apps/prefab",
|
||||
"apps/interactive-apps",
|
||||
"apps/generative",
|
||||
"apps/patterns"
|
||||
],
|
||||
"tag": "NEW"
|
||||
},
|
||||
"apps/prefab",
|
||||
"apps/interactive-apps",
|
||||
"apps/generative",
|
||||
"apps/low-level",
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Providers",
|
||||
|
|
@ -218,21 +209,19 @@
|
|||
"apps/providers/approval",
|
||||
"apps/providers/choice",
|
||||
"apps/providers/file-upload",
|
||||
"apps/providers/form",
|
||||
"apps/providers/generative"
|
||||
"apps/providers/form"
|
||||
],
|
||||
"tag": "NEW"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"group": "Advanced",
|
||||
"icon": "gear",
|
||||
"group": "Reference",
|
||||
"icon": "book",
|
||||
"pages": [
|
||||
"apps/development",
|
||||
"apps/architecture",
|
||||
"apps/low-level"
|
||||
],
|
||||
"tag": "NEW"
|
||||
"apps/examples",
|
||||
"apps/architecture"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -296,6 +285,7 @@
|
|||
"integrations/eunomia-authorization",
|
||||
"integrations/github",
|
||||
"integrations/google",
|
||||
"integrations/keycloak",
|
||||
"integrations/oci",
|
||||
"integrations/permit",
|
||||
"integrations/propelauth",
|
||||
|
|
@ -412,6 +402,14 @@
|
|||
]
|
||||
},
|
||||
"redirects": [
|
||||
{
|
||||
"destination": "/apps/generative",
|
||||
"source": "/apps/providers/generative"
|
||||
},
|
||||
{
|
||||
"destination": "/apps/prefab",
|
||||
"source": "/apps/patterns"
|
||||
},
|
||||
{
|
||||
"destination": "/cli/overview",
|
||||
"source": "/patterns/cli"
|
||||
|
|
|
|||
|
|
@ -9,29 +9,32 @@ import { VersionBadge } from "/snippets/version-badge.mdx"
|
|||
|
||||
<VersionBadge version="2.11.0" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using WorkOS's **AuthKit**, a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern, where AuthKit handles user login and your FastMCP server validates the tokens.
|
||||
|
||||
<Warning>
|
||||
AuthKit does not currently support [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) resource indicators, so FastMCP cannot validate that tokens were issued for the specific resource server. If you need resource-specific audience validation, consider using [WorkOSProvider](/integrations/workos) (OAuth proxy pattern) instead.
|
||||
</Warning>
|
||||
This guide shows you how to secure your FastMCP server using WorkOS's **AuthKit**, a complete authentication and user management solution. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern with [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) resource indicators: AuthKit issues tokens whose `aud` claim is bound to your server's resource URL, and FastMCP validates that claim automatically.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. A **[WorkOS Account](https://workos.com/)** and a new **Project**.
|
||||
2. An **[AuthKit](https://www.authkit.com/)** instance configured within your WorkOS project.
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://localhost:8000`).
|
||||
3. Your FastMCP server's URL (can be localhost for development, e.g., `http://127.0.0.1:8000`).
|
||||
|
||||
### Step 1: AuthKit Configuration
|
||||
### Step 1: WorkOS Dashboard
|
||||
|
||||
In your WorkOS Dashboard, enable AuthKit and configure the following settings:
|
||||
In the WorkOS Dashboard, go to **Connect → Configuration** and configure:
|
||||
|
||||
<Steps>
|
||||
<Step title="Enable Dynamic Client Registration">
|
||||
Go to **Applications → Configuration** and enable **Dynamic Client Registration**. This allows MCP clients register with your application automatically.
|
||||
<Step title="MCP Auth">
|
||||
Enable **Dynamic Client Registration** (DCR) so MCP clients can register themselves. Alternatively, enable **Client ID Metadata Document** (CIMD) if your clients support it.
|
||||
</Step>
|
||||
|
||||

|
||||
<Step title="MCP resource indicators">
|
||||
Add your FastMCP server's resource URL (e.g., `http://127.0.0.1:8000/mcp`) as a valid resource indicator.
|
||||
|
||||
This must exactly match what FastMCP advertises in its protected resource metadata. Start your server first and it will log the correct URL on startup — copy that value.
|
||||
|
||||
Without this step, AuthKit falls back to a default environment-scoped audience and audience validation will fail with a 401.
|
||||
</Step>
|
||||
|
||||
<Step title="Note Your AuthKit Domain">
|
||||
|
|
@ -47,16 +50,18 @@ Create your FastMCP server file and use the `AuthKitProvider` to handle all the
|
|||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
||||
|
||||
# The AuthKitProvider automatically discovers WorkOS endpoints
|
||||
# and configures JWT token validation
|
||||
# AuthKitProvider automatically discovers WorkOS endpoints, configures JWT
|
||||
# validation, and binds the token audience to this server's resource URL.
|
||||
auth_provider = AuthKitProvider(
|
||||
authkit_domain="https://your-project-12345.authkit.app",
|
||||
base_url="http://localhost:8000" # Use your actual server URL
|
||||
base_url="http://127.0.0.1:8000", # Use your actual server URL
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="AuthKit Secured App", auth=auth_provider)
|
||||
```
|
||||
|
||||
When the server starts, it logs the resource URL it is validating against. Paste that URL into your Dashboard's **MCP resource indicators** list.
|
||||
|
||||
## Testing
|
||||
|
||||
To test your server, you can use the `fastmcp` CLI to run it locally. Assuming you've saved the above code to `server.py` (after replacing the `authkit_domain` and `base_url` with your actual values!), you can run the following command:
|
||||
|
|
@ -75,7 +80,7 @@ import asyncio
|
|||
auth = OAuth(additional_client_metadata={"token_endpoint_auth_method": "none"})
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp", auth=auth) as client:
|
||||
async with Client("http://127.0.0.1:8000/mcp", auth=auth) as client:
|
||||
assert await client.ping()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -94,7 +99,7 @@ from fastmcp.server.auth.providers.workos import AuthKitProvider
|
|||
# Load configuration from environment variables
|
||||
auth = AuthKitProvider(
|
||||
authkit_domain=os.environ.get("AUTHKIT_DOMAIN"),
|
||||
base_url=os.environ.get("BASE_URL", "https://your-server.com")
|
||||
base_url=os.environ.get("BASE_URL", "https://your-server.com"),
|
||||
)
|
||||
|
||||
mcp = FastMCP(name="AuthKit Secured App", auth=auth)
|
||||
|
|
|
|||
141
docs/integrations/keycloak.mdx
Normal file
141
docs/integrations/keycloak.mdx
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
title: Keycloak OAuth 🤝 FastMCP
|
||||
sidebarTitle: Keycloak
|
||||
description: Secure your FastMCP server with Keycloak OAuth
|
||||
icon: shield-check
|
||||
tag: NEW
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx"
|
||||
|
||||
<VersionBadge version="3.2.4" />
|
||||
|
||||
This guide shows you how to secure your FastMCP server using **Keycloak OAuth**. This integration uses the [**Remote OAuth**](/servers/auth/remote-oauth) pattern with Dynamic Client Registration (DCR), where Keycloak handles user login and your FastMCP server validates the tokens.
|
||||
|
||||
<Note>
|
||||
**Keycloak 26.6.0 or later is required.** Earlier versions had a DCR incompatibility with MCP clients ([PR #45309](https://github.com/keycloak/keycloak/pull/45309)) that is fixed in 26.6.0.
|
||||
</Note>
|
||||
|
||||
## Configuration
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before you begin, you will need:
|
||||
1. A running **[Keycloak](https://keycloak.org/)** instance (e.g., `http://localhost:8080`)
|
||||
2. A Keycloak realm with **Dynamic Client Registration** enabled and a trusted host policy that allows your server URL (e.g., `http://localhost:8000/*`)
|
||||
3. Your FastMCP server's public URL (e.g., `http://localhost:8000`)
|
||||
|
||||
### FastMCP Configuration
|
||||
|
||||
Create your FastMCP server and use `KeycloakAuthProvider` to handle OAuth:
|
||||
|
||||
```python server.py
|
||||
import os
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
auth = KeycloakAuthProvider(
|
||||
realm_url=os.getenv("KEYCLOAK_REALM_URL") or "http://localhost:8080/realms/myrealm",
|
||||
base_url="http://localhost:8000",
|
||||
# audience="http://localhost:8000", # Recommended for production
|
||||
)
|
||||
|
||||
mcp = FastMCP("Keycloak Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"scope": token.claims.get("scope"),
|
||||
"azp": token.claims.get("azp"),
|
||||
}
|
||||
```
|
||||
|
||||
<Warning>
|
||||
**Production security**: Always configure the `audience` parameter in production. Without it, your server accepts tokens issued for any audience. Configure Keycloak audience mappers and set `audience` to your server's base URL to ensure tokens are specifically intended for your server.
|
||||
</Warning>
|
||||
|
||||
## Local Development
|
||||
|
||||
Local infrastructure tooling is deliberately kept out of the FastMCP core library to keep auth integrations slim and the associated maintenance burden as low as possible. That said, Keycloak is a popular identity provider for local development and testing, so a dedicated FastMCP-compatible setup blueprint lives in the companion project [**fastmcp-keycloak-local**](https://github.com/stephaneberle9/fastmcp-keycloak-local).
|
||||
|
||||
It provides everything needed to develop and test FastMCP servers with Keycloak OAuth locally: a Docker-based Keycloak setup with a pre-configured `fastmcp` realm (Dynamic Client Registration enabled, test user included), cross-platform start scripts, and integration guides for the MCP Inspector, Claude Desktop, and Claude Code CLI.
|
||||
|
||||
## Testing
|
||||
|
||||
### Running the Server
|
||||
|
||||
```bash
|
||||
fastmcp run server.py --transport http --port 8000
|
||||
```
|
||||
|
||||
### Testing with a Client
|
||||
|
||||
```python client.py
|
||||
import asyncio
|
||||
from fastmcp import Client
|
||||
|
||||
async def main():
|
||||
async with Client("http://localhost:8000/mcp", auth="oauth") as client:
|
||||
print("✓ Authenticated with Keycloak!")
|
||||
result = await client.call_tool("get_access_token_claims")
|
||||
print(f"sub: {result.data.get('sub', 'N/A')}")
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
On first run, your browser will open to Keycloak's authorization page. After login, the client receives a token and caches it for subsequent runs.
|
||||
|
||||
## Features
|
||||
|
||||
### JWT Token Validation
|
||||
|
||||
- **Signature Verification**: Validates tokens against Keycloak's JWKS endpoint
|
||||
- **Expiration Checking**: Automatically rejects expired tokens
|
||||
- **Issuer Validation**: Ensures tokens come from your specific Keycloak realm
|
||||
- **Scope Enforcement**: Verifies required OAuth scopes are present
|
||||
- **Audience Validation**: Optional validation that tokens target your server (configure `audience`)
|
||||
|
||||
### User Claims
|
||||
|
||||
Access user information from Keycloak JWT tokens:
|
||||
|
||||
```python
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
@mcp.tool
|
||||
async def admin_only_tool() -> str:
|
||||
"""A tool only available to admin users."""
|
||||
token = get_access_token()
|
||||
roles = token.claims.get("realm_access", {}).get("roles", [])
|
||||
if "admin" not in roles:
|
||||
raise ValueError("This tool requires admin access")
|
||||
return "Admin access granted!"
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Token Verifier
|
||||
|
||||
```python
|
||||
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
||||
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider
|
||||
|
||||
custom_verifier = JWTVerifier(
|
||||
jwks_uri="http://localhost:8080/realms/myrealm/protocol/openid-connect/certs",
|
||||
issuer="http://localhost:8080/realms/myrealm",
|
||||
audience="my-resource-server",
|
||||
required_scopes=["api:read", "api:write"],
|
||||
)
|
||||
|
||||
auth = KeycloakAuthProvider(
|
||||
realm_url="http://localhost:8080/realms/myrealm",
|
||||
base_url="http://localhost:8000",
|
||||
token_verifier=custom_verifier,
|
||||
)
|
||||
```
|
||||
|
|
@ -196,6 +196,7 @@
|
|||
"python-sdk/fastmcp-server-auth-providers-in_memory",
|
||||
"python-sdk/fastmcp-server-auth-providers-introspection",
|
||||
"python-sdk/fastmcp-server-auth-providers-jwt",
|
||||
"python-sdk/fastmcp-server-auth-providers-keycloak",
|
||||
"python-sdk/fastmcp-server-auth-providers-oci",
|
||||
"python-sdk/fastmcp-server-auth-providers-propelauth",
|
||||
"python-sdk/fastmcp-server-auth-providers-scalekit",
|
||||
|
|
@ -315,6 +316,7 @@
|
|||
"python-sdk/fastmcp-server-tasks-__init__",
|
||||
"python-sdk/fastmcp-server-tasks-capabilities",
|
||||
"python-sdk/fastmcp-server-tasks-config",
|
||||
"python-sdk/fastmcp-server-tasks-context",
|
||||
"python-sdk/fastmcp-server-tasks-elicitation",
|
||||
"python-sdk/fastmcp-server-tasks-handlers",
|
||||
"python-sdk/fastmcp-server-tasks-keys",
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ generate_cli_script(server_name: str, server_spec: str, transport_code: str, ext
|
|||
Generate the full CLI script source code.
|
||||
|
||||
|
||||
### `generate_skill_content` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/generate.py#L621" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `generate_skill_content` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/generate.py#L619" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_skill_content(server_name: str, cli_filename: str, tools: list[mcp.types.Tool]) -> str
|
||||
|
|
@ -43,7 +43,7 @@ generate_skill_content(server_name: str, cli_filename: str, tools: list[mcp.type
|
|||
Generate a SKILL.md file for a generated CLI script.
|
||||
|
||||
|
||||
### `generate_cli_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/generate.py#L672" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `generate_cli_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/generate.py#L670" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_cli_command(server_spec: Annotated[str, cyclopts.Parameter(help='Server URL, Python file, MCPConfig JSON, discovered name, or .js file')], output: Annotated[str, cyclopts.Parameter(help='Output file path (default: cli.py)')] = 'cli.py') -> None
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ Generate a Goose deeplink for installing an MCP extension.
|
|||
- A goose://extension?... deeplink URL.
|
||||
|
||||
|
||||
### `install_goose` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/goose.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `install_goose` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/goose.py#L85" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
install_goose(file: Path, server_object: str | None, name: str) -> bool
|
||||
|
|
@ -49,7 +49,7 @@ Install FastMCP server in Goose via deeplink.
|
|||
- True if installation was successful, False otherwise.
|
||||
|
||||
|
||||
### `goose_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/goose.py#L136" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `goose_command` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/cli/install/goose.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
goose_command(server_spec: str) -> None
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ sidebarTitle: client
|
|||
|
||||
## Classes
|
||||
|
||||
### `ClientSessionState` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L97" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ClientSessionState` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L96" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Holds all session-related state for a Client instance.
|
||||
|
|
@ -16,13 +16,13 @@ This allows clean separation of configuration (which is copied) from
|
|||
session state (which should be fresh for each new client instance).
|
||||
|
||||
|
||||
### `CallToolResult` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CallToolResult` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L113" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Parsed result from a tool call.
|
||||
|
||||
|
||||
### `Client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L124" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
MCP client that delegates connection management to a Transport instance.
|
||||
|
|
@ -85,7 +85,7 @@ async with client:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L371" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
session(self) -> ClientSession
|
||||
|
|
@ -94,7 +94,7 @@ session(self) -> ClientSession
|
|||
Get the current active session. Raises RuntimeError if not connected.
|
||||
|
||||
|
||||
#### `initialize_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L380" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize_result(self) -> mcp.types.InitializeResult | None
|
||||
|
|
@ -103,7 +103,7 @@ initialize_result(self) -> mcp.types.InitializeResult | None
|
|||
Get the result of the initialization request.
|
||||
|
||||
|
||||
#### `set_roots` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L385" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_roots` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L384" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_roots(self, roots: RootsList | RootsHandler) -> None
|
||||
|
|
@ -112,7 +112,7 @@ set_roots(self, roots: RootsList | RootsHandler) -> None
|
|||
Set the roots for the client. This does not automatically call `send_roots_list_changed`.
|
||||
|
||||
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L389" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_sampling_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L388" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabilities: mcp.types.SamplingCapability | None = None) -> None
|
||||
|
|
@ -121,7 +121,7 @@ set_sampling_callback(self, sampling_callback: SamplingHandler, sampling_capabil
|
|||
Set the sampling callback for the client.
|
||||
|
||||
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L404" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_elicitation_callback` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L403" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
||||
|
|
@ -130,7 +130,7 @@ set_elicitation_callback(self, elicitation_callback: ElicitationHandler) -> None
|
|||
Set the elicitation callback for the client.
|
||||
|
||||
|
||||
#### `is_connected` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L412" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `is_connected` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L411" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_connected(self) -> bool
|
||||
|
|
@ -139,7 +139,7 @@ is_connected(self) -> bool
|
|||
Check if the client is currently connected.
|
||||
|
||||
|
||||
#### `new` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L416" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `new` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L415" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
new(self) -> Client[ClientTransportT]
|
||||
|
|
@ -155,7 +155,7 @@ share state with the original client.
|
|||
- A new Client instance with the same configuration but disconnected state.
|
||||
|
||||
|
||||
#### `initialize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L461" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `initialize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L476" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
initialize(self, timeout: datetime.timedelta | float | int | None = None) -> mcp.types.InitializeResult
|
||||
|
|
@ -183,13 +183,13 @@ capabilities, protocol version, and optional instructions.
|
|||
- `RuntimeError`: If the client is not connected or initialization times out.
|
||||
|
||||
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L762" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L786" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close(self)
|
||||
```
|
||||
|
||||
#### `ping` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L768" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `ping` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L792" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ping(self) -> bool
|
||||
|
|
@ -198,7 +198,7 @@ ping(self) -> bool
|
|||
Send a ping request.
|
||||
|
||||
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L773" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L797" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cancel(self, request_id: str | int, reason: str | None = None) -> None
|
||||
|
|
@ -207,7 +207,7 @@ cancel(self, request_id: str | int, reason: str | None = None) -> None
|
|||
Send a cancellation notification for an in-progress request.
|
||||
|
||||
|
||||
#### `progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L790" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L814" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
progress(self, progress_token: str | int, progress: float, total: float | None = None, message: str | None = None) -> None
|
||||
|
|
@ -216,7 +216,7 @@ progress(self, progress_token: str | int, progress: float, total: float | None =
|
|||
Send a progress notification.
|
||||
|
||||
|
||||
#### `set_logging_level` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L802" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_logging_level` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L826" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
||||
|
|
@ -225,7 +225,7 @@ set_logging_level(self, level: mcp.types.LoggingLevel) -> None
|
|||
Send a logging/setLevel request.
|
||||
|
||||
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L806" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `send_roots_list_changed` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L830" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
send_roots_list_changed(self) -> None
|
||||
|
|
@ -234,7 +234,7 @@ send_roots_list_changed(self) -> None
|
|||
Send a roots/list_changed notification.
|
||||
|
||||
|
||||
#### `complete_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L812" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete_mcp` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L836" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete_mcp(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.CompleteResult
|
||||
|
|
@ -257,7 +257,7 @@ containing the completion and any additional metadata.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `complete` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L843" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `complete` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L867" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
complete(self, ref: mcp.types.ResourceTemplateReference | mcp.types.PromptReference, argument: dict[str, str], context_arguments: dict[str, Any] | None = None) -> mcp.types.Completion
|
||||
|
|
@ -279,7 +279,7 @@ include with the completion request. Defaults to None.
|
|||
- `McpError`: If the request results in a TimeoutError | JSONRPCError
|
||||
|
||||
|
||||
#### `generate_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L870" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `generate_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/client.py#L894" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
generate_name(cls, name: str | None = None) -> str
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ with fallback to polling (reliable). Optimally wakes up immediately
|
|||
on status changes when server sends notifications/tasks/status.
|
||||
|
||||
**Args:**
|
||||
- `state`: Desired state ('submitted', 'working', 'completed', 'failed').
|
||||
If None, waits for any terminal state (completed/failed)
|
||||
- `state`: Desired state ('working', 'input_required', 'completed', 'failed', 'cancelled').
|
||||
If None, waits until the task exits the 'working' state (completed, failed, cancelled, input_required, etc.)
|
||||
- `timeout`: Maximum time to wait in seconds
|
||||
|
||||
**Returns:**
|
||||
|
|
@ -125,7 +125,7 @@ on status changes when server sends notifications/tasks/status.
|
|||
- `TimeoutError`: If desired state not reached within timeout
|
||||
|
||||
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L272" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `cancel` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
cancel(self) -> None
|
||||
|
|
@ -140,7 +140,7 @@ Note: If server executed immediately (graceful degradation), this is a no-op
|
|||
as there's no server-side task to cancel.
|
||||
|
||||
|
||||
### `ToolTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L294" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ToolTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Represents a tool call that may execute in background or immediately.
|
||||
|
|
@ -151,7 +151,7 @@ or executes synchronously (graceful degradation per SEP-1686).
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L336" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
result(self) -> CallToolResult
|
||||
|
|
@ -166,7 +166,7 @@ Otherwise waits for background task to complete and retrieves result.
|
|||
- The parsed tool result (same as call_tool returns)
|
||||
|
||||
|
||||
### `PromptTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L396" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `PromptTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L411" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Represents a prompt call that may execute in background or immediately.
|
||||
|
|
@ -177,7 +177,7 @@ or executes synchronously (graceful degradation per SEP-1686).
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L427" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L442" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
result(self) -> mcp.types.GetPromptResult
|
||||
|
|
@ -192,7 +192,7 @@ Otherwise waits for background task to complete and retrieves result.
|
|||
- The prompt result with messages and description
|
||||
|
||||
|
||||
### `ResourceTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L461" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResourceTask` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L476" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Represents a resource read that may execute in background or immediately.
|
||||
|
|
@ -203,7 +203,7 @@ or executes synchronously (graceful degradation per SEP-1686).
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L497" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/client/tasks.py#L512" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
result(self) -> list[mcp.types.TextResourceContents | mcp.types.BlobResourceContents]
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Base classes and interfaces for FastMCP resources.
|
|||
|
||||
## Classes
|
||||
|
||||
### `ResourceContent` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResourceContent` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L93" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_resource_contents` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L120" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResourceResult` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L195" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L202" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L218" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base class for all resources.
|
||||
|
|
@ -70,13 +70,13 @@ Base class for all resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L236" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L243" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], uri: str | AnyUrl) -> FunctionResource
|
||||
```
|
||||
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L275" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L282" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L282" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_default_name` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_default_name(self) -> Self
|
||||
|
|
@ -94,7 +94,7 @@ set_default_name(self) -> Self
|
|||
Set default name from URI if not provided.
|
||||
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L292" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L299" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L304" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `convert_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L375" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L405" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L398" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L428" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
key(self) -> str
|
||||
|
|
@ -149,7 +149,7 @@ key(self) -> str
|
|||
The globally unique lookup key for this resource.
|
||||
|
||||
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L403" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L433" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L409" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L439" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L430" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/base.py#L460" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
|
|
|
|||
|
|
@ -52,9 +52,23 @@ Supports RFC 6570 URI templates:
|
|||
- Query params: `{?var1,var2}`
|
||||
|
||||
|
||||
### `expand_uri_template` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L112" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
expand_uri_template(uri_template: str, params: dict[str, Any]) -> str
|
||||
```
|
||||
|
||||
|
||||
Expand a URI template with parameters — inverse of `match_uri_template`.
|
||||
|
||||
Supports the same RFC 6570 subset:
|
||||
- Path params: `{var}`, `{var*}`
|
||||
- Query params: `{?var1,var2}`
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `ResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L112" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L144" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A template for dynamically creating resources.
|
||||
|
|
@ -62,13 +76,13 @@ A template for dynamically creating resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L171" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(fn: Callable[..., Any], uri_template: str, name: str | None = None, version: str | int | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, mime_type: str | None = None, tags: set[str] | None = None, annotations: Annotations | None = None, meta: dict[str, Any] | None = None, task: bool | TaskConfig | None = None, auth: AuthCheck | list[AuthCheck] | None = None) -> FunctionResourceTemplate
|
||||
```
|
||||
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L172" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_default_mime_type` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L204" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_default_mime_type(cls, mime_type: str | None) -> str
|
||||
|
|
@ -77,7 +91,7 @@ set_default_mime_type(cls, mime_type: str | None) -> str
|
|||
Set default MIME type if not provided.
|
||||
|
||||
|
||||
#### `matches` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L178" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `matches` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L210" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
matches(self, uri: str) -> dict[str, Any] | None
|
||||
|
|
@ -86,7 +100,7 @@ matches(self, uri: str) -> dict[str, Any] | None
|
|||
Check if URI matches template and extract parameters.
|
||||
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L214" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
||||
|
|
@ -95,7 +109,7 @@ read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
|||
Read the resource content.
|
||||
|
||||
|
||||
#### `convert_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L188" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `convert_result` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L220" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
convert_result(self, raw_value: Any) -> ResourceResult
|
||||
|
|
@ -111,7 +125,7 @@ Handles ResourceResult passthrough and converts raw values using
|
|||
ResourceResult's normalization.
|
||||
|
||||
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L252" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L284" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_resource(self, uri: str, params: dict[str, Any]) -> Resource
|
||||
|
|
@ -123,7 +137,7 @@ The base implementation does not support background tasks.
|
|||
Use FunctionResourceTemplate for task support.
|
||||
|
||||
|
||||
#### `to_mcp_template` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L263" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `to_mcp_template` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_mcp_template(self, **overrides: Any) -> SDKResourceTemplate
|
||||
|
|
@ -132,7 +146,7 @@ to_mcp_template(self, **overrides: Any) -> SDKResourceTemplate
|
|||
Convert the resource template to an SDKResourceTemplate.
|
||||
|
||||
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L283" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_mcp_template` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L315" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_mcp_template(cls, mcp_template: SDKResourceTemplate) -> ResourceTemplate
|
||||
|
|
@ -141,7 +155,7 @@ from_mcp_template(cls, mcp_template: SDKResourceTemplate) -> ResourceTemplate
|
|||
Creates a FastMCP ResourceTemplate from a raw MCP ResourceTemplate object.
|
||||
|
||||
|
||||
#### `key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L296" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L328" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
key(self) -> str
|
||||
|
|
@ -150,7 +164,7 @@ key(self) -> str
|
|||
The globally unique lookup key for this template.
|
||||
|
||||
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L333" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_with_docket(self, docket: Docket) -> None
|
||||
|
|
@ -159,7 +173,7 @@ register_with_docket(self, docket: Docket) -> None
|
|||
Register this template with docket for background execution.
|
||||
|
||||
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L307" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L339" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_to_docket(self, docket: Docket, params: dict[str, Any], **kwargs: Any) -> Execution
|
||||
|
|
@ -175,13 +189,13 @@ Schedule this template for background execution via docket.
|
|||
- `**kwargs`: Additional kwargs passed to docket.add()
|
||||
|
||||
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L330" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
### `FunctionResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L337" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FunctionResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L369" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
A template for dynamically creating resources.
|
||||
|
|
@ -189,7 +203,7 @@ A template for dynamically creating resources.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L415" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
create_resource(self, uri: str, params: dict[str, Any]) -> Resource
|
||||
|
|
@ -198,7 +212,7 @@ create_resource(self, uri: str, params: dict[str, Any]) -> Resource
|
|||
Create a resource from the template with the given parameters.
|
||||
|
||||
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L402" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L434" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
||||
|
|
@ -207,7 +221,7 @@ read(self, arguments: dict[str, Any]) -> str | bytes | ResourceResult
|
|||
Read the resource content.
|
||||
|
||||
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L441" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L473" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_with_docket(self, docket: Docket) -> None
|
||||
|
|
@ -216,7 +230,7 @@ register_with_docket(self, docket: Docket) -> None
|
|||
Register this template with docket for background execution.
|
||||
|
||||
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L447" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L479" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
add_to_docket(self, docket: Docket, params: dict[str, Any], **kwargs: Any) -> Execution
|
||||
|
|
@ -234,7 +248,7 @@ FunctionResourceTemplate splats the params dict since .fn expects **kwargs.
|
|||
- `**kwargs`: Additional kwargs passed to docket.add()
|
||||
|
||||
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L473" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `from_function` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/resources/template.py#L505" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_function(cls, fn: Callable[..., Any], uri_template: str, name: str | None = None, version: str | int | None = None, title: str | None = None, description: str | None = None, icons: list[Icon] | None = None, mime_type: str | None = None, tags: set[str] | None = None, annotations: Annotations | None = None, meta: dict[str, Any] | None = None, task: bool | TaskConfig | None = None, auth: AuthCheck | list[AuthCheck] | None = None) -> FunctionResourceTemplate
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ custom authentication routes.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L236" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L247" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -102,7 +102,7 @@ All auth providers must implement token verification.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L249" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L260" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_mcp_path(self, mcp_path: str | None) -> None
|
||||
|
|
@ -119,7 +119,7 @@ MCP endpoint path.
|
|||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L263" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L274" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -143,7 +143,7 @@ provider does not create the actual MCP endpoint route.
|
|||
- List of all routes for this provider (excluding the MCP endpoint itself)
|
||||
|
||||
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L286" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L297" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -171,7 +171,7 @@ This is used to construct path-scoped well-known URLs.
|
|||
- List of well-known discovery routes (typically mounted at root level)
|
||||
|
||||
|
||||
#### `get_middleware` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L318" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_middleware` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L329" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_middleware(self) -> list
|
||||
|
|
@ -183,7 +183,7 @@ Get HTTP application-level middleware for this auth provider.
|
|||
- List of Starlette Middleware instances to apply to the HTTP app
|
||||
|
||||
|
||||
### `TokenVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TokenVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L366" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Base class for token verifiers (Resource Servers).
|
||||
|
|
@ -194,7 +194,7 @@ Token verifiers typically don't provide authentication routes by default.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `scopes_supported` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L373" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `scopes_supported` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L398" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
scopes_supported(self) -> list[str]
|
||||
|
|
@ -208,7 +208,7 @@ where tokens contain short-form scopes but clients request full URI
|
|||
scopes).
|
||||
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L408" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -217,7 +217,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify a bearer token and return access info if valid.
|
||||
|
||||
|
||||
### `RemoteAuthProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L388" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `RemoteAuthProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L413" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Authentication provider for resource servers that verify tokens from known authorization servers.
|
||||
|
|
@ -234,7 +234,7 @@ the authorization servers that issue valid tokens.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L435" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L468" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -243,7 +243,7 @@ verify_token(self, token: str) -> AccessToken | None
|
|||
Verify token using the configured token verifier.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L439" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L472" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -254,7 +254,7 @@ Get routes for this provider.
|
|||
Creates protected resource metadata routes (RFC 9728).
|
||||
|
||||
|
||||
### `MultiAuth` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L471" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `MultiAuth` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L510" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Composes an optional auth server with additional token verifiers.
|
||||
|
|
@ -270,7 +270,7 @@ come from the server; verifiers contribute only token verification.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L536" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L591" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -283,7 +283,7 @@ it is logged and treated as a non-match so that remaining sources
|
|||
still get a chance to verify the token.
|
||||
|
||||
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L557" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L612" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_mcp_path(self, mcp_path: str | None) -> None
|
||||
|
|
@ -292,7 +292,7 @@ set_mcp_path(self, mcp_path: str | None) -> None
|
|||
Propagate MCP path to the server and all verifiers.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L565" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L620" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -301,7 +301,7 @@ get_routes(self, mcp_path: str | None = None) -> list[Route]
|
|||
Delegate route creation to the server.
|
||||
|
||||
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L571" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L626" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -313,7 +313,7 @@ This ensures that server-specific well-known route logic (e.g.,
|
|||
OAuthProvider's RFC 8414 path-aware discovery) is preserved.
|
||||
|
||||
|
||||
### `OAuthProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L582" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OAuthProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L637" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
OAuth Authorization Server provider.
|
||||
|
|
@ -324,7 +324,7 @@ authorization flows, token issuance, and token verification.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L645" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L708" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -342,7 +342,7 @@ to our existing load_access_token method.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L660" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L723" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
@ -358,7 +358,7 @@ This method creates the full set of OAuth routes including:
|
|||
- List of OAuth routes
|
||||
|
||||
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L739" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_well_known_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/auth.py#L802" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_well_known_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ Handles provider-specific requirements:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L572" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L576" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_mcp_path(self, mcp_path: str | None) -> None
|
||||
|
|
@ -157,7 +157,7 @@ this specific MCP endpoint.
|
|||
- `mcp_path`: The path where the MCP endpoint is mounted (e.g., "/mcp")
|
||||
|
||||
|
||||
#### `jwt_issuer` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L596" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `jwt_issuer` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L600" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
jwt_issuer(self) -> JWTIssuer
|
||||
|
|
@ -169,7 +169,7 @@ The JWT issuer is created when set_mcp_path() is called (via get_routes()).
|
|||
This property ensures a clear error if used before initialization.
|
||||
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L656" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L660" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
|
|
@ -182,7 +182,7 @@ For unregistered clients, returns None (which will raise an error in the SDK).
|
|||
CIMD clients (URL-based client IDs) are looked up and cached automatically.
|
||||
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L700" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L704" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
|
|
@ -196,7 +196,7 @@ redirect URI will likely be localhost or unknown to the proxied IDP. The
|
|||
proxied IDP only knows about this server's fixed redirect URI.
|
||||
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L753" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L757" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -214,7 +214,7 @@ If consent is disabled (require_authorization_consent=False), skip the consent s
|
|||
and redirect directly to the upstream IdP.
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L872" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L876" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
|
|
@ -226,7 +226,7 @@ Look up our client code and return authorization code object
|
|||
with PKCE challenge for validation.
|
||||
|
||||
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L920" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L924" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
|
|
@ -244,7 +244,7 @@ Implements the token factory pattern:
|
|||
PKCE validation is handled by the MCP framework before this method is called.
|
||||
|
||||
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1178" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
|
|
@ -256,7 +256,7 @@ Looks up by token hash and reconstructs the RefreshToken object.
|
|||
Validates that the token belongs to the requesting client.
|
||||
|
||||
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1207" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
||||
|
|
@ -273,7 +273,7 @@ Implements two-tier refresh:
|
|||
6. Keep same FastMCP refresh token (unless upstream rotates)
|
||||
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1558" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1562" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -293,7 +293,7 @@ The FastMCP JWT is a reference token - all authorization data comes
|
|||
from validating the upstream token via the TokenVerifier.
|
||||
|
||||
|
||||
#### `revoke_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1723" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `revoke_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1727" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
@ -306,7 +306,7 @@ For all tokens, attempts upstream revocation if endpoint is configured.
|
|||
Access token JTI mappings expire via TTL.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1769" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy/proxy.py#L1773" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ that is OIDC compliant.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L452" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_oidc_configuration` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L456" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_oidc_configuration(self, config_url: AnyHttpUrl, strict: bool | None, timeout_seconds: int | None) -> OIDCConfiguration
|
||||
|
|
@ -66,7 +66,7 @@ Gets the OIDC configuration for the specified configuration URL.
|
|||
- `timeout_seconds`: HTTP request timeout in seconds
|
||||
|
||||
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L469" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/oidc_proxy.py#L473" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_verifier(self) -> TokenVerifier
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ Features:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L203" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_token_verifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/aws.py#L207" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_token_verifier(self) -> AWSCognitoTokenVerifier
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ using the OAuth Proxy pattern for non-DCR OAuth flows.
|
|||
|
||||
## Functions
|
||||
|
||||
### `EntraOBOToken` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L721" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `EntraOBOToken` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L726" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
EntraOBOToken(scopes: list[str]) -> str
|
||||
|
|
@ -43,7 +43,7 @@ or OBO exchange fails
|
|||
|
||||
## Classes
|
||||
|
||||
### `AzureProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L38" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Azure (Microsoft Entra) OAuth provider for FastMCP.
|
||||
|
|
@ -78,7 +78,7 @@ Setup:
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L273" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L494" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_obo_credential` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L499" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L545" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `close_obo_credentials` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L550" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
close_obo_credentials(self) -> None
|
||||
|
|
@ -129,7 +129,7 @@ close_obo_credentials(self) -> None
|
|||
Close all cached OBO credentials.
|
||||
|
||||
|
||||
### `AzureJWTVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L556" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AzureJWTVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L561" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
JWT verifier pre-configured for Azure AD / Microsoft Entra ID.
|
||||
|
|
@ -166,7 +166,7 @@ Example::
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `scopes_supported` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L636" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `scopes_supported` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/azure.py#L641" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
scopes_supported(self) -> list[str]
|
||||
|
|
|
|||
|
|
@ -16,19 +16,19 @@ It simulates the OAuth 2.1 flow locally without external calls.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L65" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L67" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client(self, client_id: str) -> OAuthClientInformationFull | None
|
||||
```
|
||||
|
||||
#### `register_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L68" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_client` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_client(self, client_info: OAuthClientInformationFull) -> None
|
||||
```
|
||||
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L92" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `authorize` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
authorize(self, client: OAuthClientInformationFull, params: AuthorizationParams) -> str
|
||||
|
|
@ -38,37 +38,37 @@ Simulates user authorization and generates an authorization code.
|
|||
Returns a redirect URI with the code and state.
|
||||
|
||||
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L149" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L151" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_authorization_code(self, client: OAuthClientInformationFull, authorization_code: str) -> AuthorizationCode | None
|
||||
```
|
||||
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_authorization_code` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L164" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_authorization_code(self, client: OAuthClientInformationFull, authorization_code: AuthorizationCode) -> OAuthToken
|
||||
```
|
||||
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L217" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_refresh_token(self, client: OAuthClientInformationFull, refresh_token: str) -> RefreshToken | None
|
||||
```
|
||||
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L230" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `exchange_refresh_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L232" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
exchange_refresh_token(self, client: OAuthClientInformationFull, refresh_token: RefreshToken, scopes: list[str]) -> OAuthToken
|
||||
```
|
||||
|
||||
#### `load_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `load_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
load_access_token(self, token: str) -> AccessToken | None
|
||||
```
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -86,7 +86,7 @@ to our existing load_access_token method.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
#### `revoke_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L355" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `revoke_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/in_memory.py#L357" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
revoke_token(self, token: AccessToken | RefreshToken) -> None
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ Validate a JWT bearer token and return an AccessToken when the token is valid.
|
|||
- AccessToken | None: An AccessToken populated from token claims if the token is valid; `None` if the token is expired, has an invalid signature or format, fails issuer/audience/scope validation, or any other validation error occurs.
|
||||
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L515" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L523" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
@ -115,7 +115,7 @@ to our existing load_access_token method.
|
|||
- AccessToken object if valid, None if invalid or expired
|
||||
|
||||
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L531" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `StaticTokenVerifier` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L539" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Simple static token verifier for testing and development.
|
||||
|
|
@ -136,7 +136,7 @@ WARNING: Never use this in production - tokens are stored in plain text!
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L565" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `verify_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/jwt.py#L573" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
verify_token(self, token: str) -> AccessToken | None
|
||||
|
|
|
|||
20
docs/python-sdk/fastmcp-server-auth-providers-keycloak.mdx
Normal file
20
docs/python-sdk/fastmcp-server-auth-providers-keycloak.mdx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: keycloak
|
||||
sidebarTitle: keycloak
|
||||
---
|
||||
|
||||
# `fastmcp.server.auth.providers.keycloak`
|
||||
|
||||
|
||||
Keycloak authentication provider for FastMCP.
|
||||
|
||||
## Classes
|
||||
|
||||
### `KeycloakAuthProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/keycloak.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Keycloak authentication provider using Dynamic Client Registration (DCR).
|
||||
|
||||
Requires Keycloak 26.6.0 or later, which includes the fix for DCR compatibility
|
||||
with MCP clients (https://github.com/keycloak/keycloak/pull/45309).
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ Setup Requirements:
|
|||
4. Note your Client ID and Client Secret
|
||||
|
||||
|
||||
### `AuthKitProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L255" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `AuthKitProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L259" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
AuthKit metadata provider for DCR (Dynamic Client Registration).
|
||||
|
|
@ -82,10 +82,31 @@ IMPORTANT SETUP REQUIREMENTS:
|
|||
For detailed setup instructions, see:
|
||||
https://workos.com/docs/authkit/mcp/integrating/token-verification
|
||||
|
||||
Token audience is bound to this server automatically: when the MCP
|
||||
mount path becomes known (typically at ``http_app()`` construction),
|
||||
``JWTVerifier.audience`` is set to the resource URL advertised in
|
||||
``.well-known/oauth-protected-resource``. Enable Resource Indicators
|
||||
(RFC 8707) in your WorkOS Dashboard and list that same URL — AuthKit
|
||||
will then mint tokens with the matching ``aud`` claim.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L353" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_mcp_path` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L362" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_mcp_path(self, mcp_path: str | None) -> None
|
||||
```
|
||||
|
||||
Bind the default verifier's audience to this server's resource URL.
|
||||
|
||||
AuthKit with Resource Indicators (RFC 8707) mints tokens whose ``aud``
|
||||
claim equals the resource URL the client requested — which is the URL
|
||||
we advertise in ``.well-known/oauth-protected-resource``. Binding the
|
||||
audience here keeps validation in lock-step with what clients are sent.
|
||||
|
||||
|
||||
#### `get_routes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/auth/providers/workos.py#L384" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_routes(self, mcp_path: str | None = None) -> list[Route]
|
||||
|
|
|
|||
|
|
@ -577,37 +577,37 @@ regardless of this setting.
|
|||
elicit(self, message: str, response_type: None) -> AcceptedElicitation[dict[str, Any]] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1028" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1031" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: type[T]) -> AcceptedElicitation[T] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1038" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1044" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: list[str]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1048" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1057" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: dict[str, dict[str, str]]) -> AcceptedElicitation[str] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1058" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1070" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: list[list[str]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1070" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1085" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: list[dict[str, dict[str, str]]]) -> AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
|
||||
```
|
||||
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1082" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `elicit` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1100" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit(self, message: str, response_type: type[T] | list[str] | dict[str, dict[str, str]] | list[list[str]] | list[dict[str, dict[str, str]]] | None = None) -> AcceptedElicitation[T] | AcceptedElicitation[dict[str, Any]] | AcceptedElicitation[str] | AcceptedElicitation[list[str]] | DeclinedElicitation | CancelledElicitation
|
||||
|
|
@ -634,9 +634,17 @@ Clients must send an empty object ("{}")in response.
|
|||
- `response_type`: The type of the response, which should be a primitive
|
||||
type or dataclass or BaseModel. If it is a primitive type, an
|
||||
object schema with a single "value" field will be generated.
|
||||
- `response_title`: Optional label to display for the wrapped ``value``
|
||||
field when ``response_type`` is a scalar, Literal, Enum, or one
|
||||
of the dict/list shorthand forms. Overrides the auto-generated
|
||||
"Value" label. Raises ``TypeError`` if passed with a BaseModel,
|
||||
dataclass, or ``None`` response type (use ``Field(title=...)``
|
||||
on the model instead).
|
||||
- `response_description`: Optional description to attach to the wrapped
|
||||
``value`` field. Same scope rules as ``response_title``.
|
||||
|
||||
|
||||
#### `set_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1196" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_state(self, key: str, value: Any) -> None
|
||||
|
|
@ -657,7 +665,7 @@ requests.
|
|||
The key is automatically prefixed with the session identifier.
|
||||
|
||||
|
||||
#### `get_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1271" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_state(self, key: str) -> Any
|
||||
|
|
@ -671,7 +679,7 @@ then falls back to the session-scoped state store.
|
|||
Returns None if the key is not found.
|
||||
|
||||
|
||||
#### `delete_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1252" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `delete_state` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1285" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
delete_state(self, key: str) -> None
|
||||
|
|
@ -682,7 +690,7 @@ Delete a value from the state store.
|
|||
Removes from both request-scoped and session-scoped stores.
|
||||
|
||||
|
||||
#### `enable_components` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1273" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `enable_components` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1306" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
enable_components(self) -> None
|
||||
|
|
@ -706,7 +714,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `match_all`: If True, matches all components regardless of other criteria.
|
||||
|
||||
|
||||
#### `disable_components` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `disable_components` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
disable_components(self) -> None
|
||||
|
|
@ -730,7 +738,7 @@ ResourceListChangedNotification, and PromptListChangedNotification.
|
|||
- `match_all`: If True, matches all components regardless of other criteria.
|
||||
|
||||
|
||||
#### `reset_visibility` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1349" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `reset_visibility` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/context.py#L1382" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
reset_visibility(self) -> None
|
||||
|
|
|
|||
|
|
@ -15,74 +15,7 @@ CurrentWorker) and background task execution require fastmcp[tasks].
|
|||
|
||||
## Functions
|
||||
|
||||
### `get_task_context` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L107" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_context() -> TaskContextInfo | None
|
||||
```
|
||||
|
||||
|
||||
Get the current task context if running inside a background task worker.
|
||||
|
||||
This function extracts task information from the Docket execution context.
|
||||
Returns None if not running in a task context (e.g., foreground execution).
|
||||
|
||||
**Returns:**
|
||||
- TaskContextInfo with task_id and session_id, or None if not in a task.
|
||||
|
||||
|
||||
### `register_task_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L145" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_task_session(session_id: str, session: ServerSession) -> None
|
||||
```
|
||||
|
||||
|
||||
Register a session for Context access in background tasks.
|
||||
|
||||
Called automatically when a task is submitted to Docket. The session is
|
||||
stored as a weakref so it doesn't prevent garbage collection when the
|
||||
client disconnects.
|
||||
|
||||
**Args:**
|
||||
- `session_id`: The session identifier
|
||||
- `session`: The ServerSession instance
|
||||
|
||||
|
||||
### `get_task_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L159" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_session(session_id: str) -> ServerSession | None
|
||||
```
|
||||
|
||||
|
||||
Get a registered session by ID if still alive.
|
||||
|
||||
**Args:**
|
||||
- `session_id`: The session identifier
|
||||
|
||||
**Returns:**
|
||||
- The ServerSession if found and alive, None otherwise
|
||||
|
||||
|
||||
### `register_task_server` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L194" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L428" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `is_docket_available` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L114" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
is_docket_available() -> bool
|
||||
|
|
@ -103,7 +36,7 @@ Any of those failing means we treat docket as unavailable and fall back
|
|||
to the no-tasks code paths instead of crashing deep inside a request.
|
||||
|
||||
|
||||
### `require_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L457" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `require_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L143" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
require_docket(feature: str) -> None
|
||||
|
|
@ -117,7 +50,7 @@ Raise ImportError with install instructions if docket not available.
|
|||
"CurrentDocket()"). Will be included in the error message.
|
||||
|
||||
|
||||
### `transform_context_annotations` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L497" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `transform_context_annotations` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
transform_context_annotations(fn: Callable[..., Any]) -> Callable[..., Any]
|
||||
|
|
@ -143,7 +76,7 @@ allows them to have defaults in any order.
|
|||
- Function with modified signature (same function object, updated __signature__)
|
||||
|
||||
|
||||
### `get_context` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L638" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_context` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L324" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_context() -> Context
|
||||
|
|
@ -153,7 +86,7 @@ get_context() -> Context
|
|||
Get the current FastMCP Context instance directly.
|
||||
|
||||
|
||||
### `get_server` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L648" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_server` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L334" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_server() -> FastMCP
|
||||
|
|
@ -173,7 +106,7 @@ started the worker).
|
|||
- `RuntimeError`: If no server in context
|
||||
|
||||
|
||||
### `get_http_request` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L682" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_http_request` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L364" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_request() -> Request
|
||||
|
|
@ -187,7 +120,7 @@ In background tasks, returns a synthetic request populated with the
|
|||
snapshotted headers from the originating HTTP request.
|
||||
|
||||
|
||||
### `get_http_headers` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L729" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_http_headers` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L411" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_http_headers(include_all: bool = False, include: set[str] | None = None) -> dict[str, str]
|
||||
|
|
@ -208,7 +141,7 @@ normally be excluded. This is useful for proxy transports that need to forward
|
|||
authorization headers to upstream MCP servers.
|
||||
|
||||
|
||||
### `get_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L786" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_access_token` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L468" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_access_token() -> AccessToken | None
|
||||
|
|
@ -227,7 +160,7 @@ token snapshot stored in Redis at task submission time.
|
|||
- The access token if an authenticated user is available, None otherwise.
|
||||
|
||||
|
||||
### `without_injected_parameters` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L857" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `without_injected_parameters` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L539" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
without_injected_parameters(fn: Callable[..., Any]) -> Callable[..., Any]
|
||||
|
|
@ -252,7 +185,7 @@ Handles:
|
|||
- Async wrapper function without injected parameters
|
||||
|
||||
|
||||
### `resolve_dependencies` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1006" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `resolve_dependencies` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L688" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
resolve_dependencies(fn: Callable[..., Any], arguments: dict[str, Any]) -> AsyncGenerator[dict[str, Any], None]
|
||||
|
|
@ -278,7 +211,7 @@ time, so all injection goes through the unified DI system.
|
|||
which will be filtered out)
|
||||
|
||||
|
||||
### `CurrentContext` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1144" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentContext` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L831" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentContext() -> Context
|
||||
|
|
@ -297,7 +230,7 @@ current MCP operation (tool/resource/prompt call).
|
|||
- `RuntimeError`: If no active context found (during resolution)
|
||||
|
||||
|
||||
### `OptionalCurrentContext` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1169" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `OptionalCurrentContext` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L856" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
OptionalCurrentContext() -> Context | None
|
||||
|
|
@ -307,7 +240,7 @@ OptionalCurrentContext() -> Context | None
|
|||
Get the current FastMCP Context, or None when no context is active.
|
||||
|
||||
|
||||
### `CurrentDocket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1204" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentDocket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L891" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentDocket() -> Docket
|
||||
|
|
@ -327,7 +260,7 @@ automatically creates for background task scheduling.
|
|||
- `ImportError`: If fastmcp[tasks] not installed
|
||||
|
||||
|
||||
### `CurrentWorker` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1260" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentWorker` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L947" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentWorker() -> Worker
|
||||
|
|
@ -347,7 +280,7 @@ automatically creates for background task processing.
|
|||
- `ImportError`: If fastmcp[tasks] not installed
|
||||
|
||||
|
||||
### `CurrentFastMCP` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1301" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentFastMCP` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L988" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentFastMCP() -> FastMCP
|
||||
|
|
@ -365,7 +298,7 @@ This dependency provides access to the active FastMCP server.
|
|||
- `RuntimeError`: If no server in context (during resolution)
|
||||
|
||||
|
||||
### `CurrentRequest` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentRequest` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1028" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentRequest() -> Request
|
||||
|
|
@ -385,7 +318,7 @@ current HTTP request. Only available when running over HTTP transports
|
|||
- `RuntimeError`: If no HTTP request in context (e.g., STDIO transport)
|
||||
|
||||
|
||||
### `CurrentHeaders` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1382" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentHeaders` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1069" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentHeaders() -> dict[str, str]
|
||||
|
|
@ -403,7 +336,7 @@ transport.
|
|||
- A dependency that resolves to a dictionary of header name -> value
|
||||
|
||||
|
||||
### `CurrentAccessToken` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1600" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `CurrentAccessToken` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
CurrentAccessToken() -> AccessToken
|
||||
|
|
@ -422,7 +355,7 @@ authenticated request. Raises an error if no authentication is present.
|
|||
- `RuntimeError`: If no authenticated user (use get_access_token() for optional)
|
||||
|
||||
|
||||
### `TokenClaim` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1657" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `TokenClaim` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1344" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
TokenClaim(name: str) -> str
|
||||
|
|
@ -447,62 +380,7 @@ without needing the full token object.
|
|||
|
||||
## Classes
|
||||
|
||||
### `TaskContextInfo` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L93" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Information about the current background task context.
|
||||
|
||||
Returned by ``get_task_context()`` when running inside a Docket worker.
|
||||
Contains identifiers needed to communicate with the MCP session.
|
||||
|
||||
|
||||
### `TaskContextSnapshot` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L217" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
All context data snapshotted at task-submission time.
|
||||
|
||||
Stored as a single Redis key per task, restored once in the worker.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `capture` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L228" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
capture(cls) -> TaskContextSnapshot
|
||||
```
|
||||
|
||||
Capture current context for background task execution.
|
||||
|
||||
|
||||
#### `from_json` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L244" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_json(cls, raw: str | bytes) -> TaskContextSnapshot
|
||||
```
|
||||
|
||||
Deserialize from JSON stored in Redis.
|
||||
|
||||
|
||||
#### `to_json` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L258" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_json(self) -> str
|
||||
```
|
||||
|
||||
Serialize to JSON for Redis storage.
|
||||
|
||||
|
||||
#### `save` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
save(self, docket: Docket, session_id: str, task_id: str, ttl_seconds: int) -> None
|
||||
```
|
||||
|
||||
Store this snapshot as a single Redis key.
|
||||
|
||||
|
||||
### `ProgressLike` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1410" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ProgressLike` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1097" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Protocol for progress tracking interface.
|
||||
|
|
@ -513,7 +391,7 @@ and Docket's Progress (worker context).
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1418" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1105" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
current(self) -> int | None
|
||||
|
|
@ -522,7 +400,7 @@ current(self) -> int | None
|
|||
Current progress value.
|
||||
|
||||
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1423" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1110" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
total(self) -> int
|
||||
|
|
@ -531,7 +409,7 @@ total(self) -> int
|
|||
Total/target progress value.
|
||||
|
||||
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1428" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1115" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
message(self) -> str | None
|
||||
|
|
@ -540,7 +418,7 @@ message(self) -> str | None
|
|||
Current progress message.
|
||||
|
||||
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1432" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1119" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_total(self, total: int) -> None
|
||||
|
|
@ -549,7 +427,7 @@ set_total(self, total: int) -> None
|
|||
Set the total/target value for progress tracking.
|
||||
|
||||
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1436" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
increment(self, amount: int = 1) -> None
|
||||
|
|
@ -558,7 +436,7 @@ increment(self, amount: int = 1) -> None
|
|||
Atomically increment the current progress value.
|
||||
|
||||
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1440" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_message(self, message: str | None) -> None
|
||||
|
|
@ -567,7 +445,7 @@ set_message(self, message: str | None) -> None
|
|||
Update the progress status message.
|
||||
|
||||
|
||||
### `InMemoryProgress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1445" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `InMemoryProgress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1132" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
In-memory progress tracker for immediate tool execution.
|
||||
|
|
@ -579,25 +457,25 @@ progress doesn't need to be observable across processes.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1470" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1157" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
current(self) -> int | None
|
||||
```
|
||||
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1474" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1161" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
total(self) -> int
|
||||
```
|
||||
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1478" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1165" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
message(self) -> str | None
|
||||
```
|
||||
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1481" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1168" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_total(self, total: int) -> None
|
||||
|
|
@ -606,7 +484,7 @@ set_total(self, total: int) -> None
|
|||
Set the total/target value for progress tracking.
|
||||
|
||||
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1487" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1174" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
increment(self, amount: int = 1) -> None
|
||||
|
|
@ -615,7 +493,7 @@ increment(self, amount: int = 1) -> None
|
|||
Atomically increment the current progress value.
|
||||
|
||||
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1496" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_message(self, message: str | None) -> None
|
||||
|
|
@ -624,7 +502,7 @@ set_message(self, message: str | None) -> None
|
|||
Update the progress status message.
|
||||
|
||||
|
||||
### `Progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1501" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `Progress` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1188" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Progress dependency that works in both server and worker contexts.
|
||||
|
|
@ -639,7 +517,7 @@ share mutable state.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1542" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `current` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1229" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
current(self) -> int | None
|
||||
|
|
@ -648,7 +526,7 @@ current(self) -> int | None
|
|||
Current progress value.
|
||||
|
||||
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1548" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1235" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
total(self) -> int
|
||||
|
|
@ -657,7 +535,7 @@ total(self) -> int
|
|||
Total/target progress value.
|
||||
|
||||
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1554" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1241" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
message(self) -> str | None
|
||||
|
|
@ -666,7 +544,7 @@ message(self) -> str | None
|
|||
Current progress message.
|
||||
|
||||
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1559" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_total` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1246" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_total(self, total: int) -> None
|
||||
|
|
@ -675,7 +553,7 @@ set_total(self, total: int) -> None
|
|||
Set the total/target value for progress tracking.
|
||||
|
||||
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1564" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `increment` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1251" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
increment(self, amount: int = 1) -> None
|
||||
|
|
@ -684,7 +562,7 @@ increment(self, amount: int = 1) -> None
|
|||
Atomically increment the current progress value.
|
||||
|
||||
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1569" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `set_message` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/dependencies.py#L1256" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
set_message(self, message: str | None) -> None
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ sidebarTitle: elicitation
|
|||
### `parse_elicit_response_type` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L132" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_elicit_response_type(response_type: Any) -> ElicitConfig
|
||||
parse_elicit_response_type(response_type: Any, response_title: str | None = None, response_description: str | None = None) -> ElicitConfig
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -27,8 +27,15 @@ Supports multiple syntaxes:
|
|||
- Scalar types (bool, int, float, str, Literal, Enum): single value
|
||||
- Other types (dataclass, BaseModel): use directly
|
||||
|
||||
The ``response_title`` and ``response_description`` arguments customize the
|
||||
label and description of the wrapped ``value`` property for the scalar/dict/list
|
||||
shorthand forms. They are only valid when FastMCP is wrapping the response
|
||||
type; passing them with a full BaseModel/dataclass (or ``None``) raises
|
||||
``TypeError``, because in those cases the user already controls field
|
||||
metadata via ``Field(title=..., description=...)``.
|
||||
|
||||
### `handle_elicit_accept` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L265" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
### `handle_elicit_accept` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
handle_elicit_accept(config: ElicitConfig, content: Any) -> AcceptedElicitation[Any]
|
||||
|
|
@ -45,7 +52,7 @@ Handle an accepted elicitation response.
|
|||
- AcceptedElicitation with the extracted/validated data
|
||||
|
||||
|
||||
### `get_elicitation_schema` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L324" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_elicitation_schema` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_elicitation_schema(response_type: type[T]) -> dict[str, Any]
|
||||
|
|
@ -58,7 +65,7 @@ Get the schema for an elicitation response.
|
|||
- `response_type`: The type of the response
|
||||
|
||||
|
||||
### `validate_elicitation_json_schema` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L343" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `validate_elicitation_json_schema` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/elicitation.py#L387" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
validate_elicitation_json_schema(schema: dict[str, Any]) -> None
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ backoff to avoid overwhelming the server or external dependencies.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `on_request` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L192" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `on_request` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/middleware/error_handling.py#L202" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
on_request(self, context: MiddlewareContext, call_next: CallNext) -> Any
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ executed.
|
|||
|
||||
## Classes
|
||||
|
||||
### `FastMCPProviderTool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L72" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProviderTool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Tool that delegates execution to a wrapped server's middleware.
|
||||
|
|
@ -30,7 +30,7 @@ chain is executed.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L94" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L64" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
wrap(cls, server: Any, tool: Tool) -> FastMCPProviderTool
|
||||
|
|
@ -39,7 +39,7 @@ wrap(cls, server: Any, tool: Tool) -> FastMCPProviderTool
|
|||
Wrap a Tool to delegate execution to the server's middleware.
|
||||
|
||||
|
||||
#### `run` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L151" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `run` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L121" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L170" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L140" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
### `FastMCPProviderResource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L177" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProviderResource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L147" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L168" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L241" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L211" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
### `FastMCPProviderPrompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L248" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProviderPrompt` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L218" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L269" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L239" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L320" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `render` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L290" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L339" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
### `FastMCPProviderResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L346" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProviderResourceTemplate` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L316" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Resource template that creates FastMCPProviderResources.
|
||||
|
|
@ -133,7 +133,7 @@ when read.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L368" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `wrap` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L338" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L389" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `create_resource` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L359" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L439" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `read` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L409" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L460" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `register_with_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L428" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L463" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `add_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L431" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L482" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_span_attributes` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L450" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_span_attributes(self) -> dict[str, Any]
|
||||
```
|
||||
|
||||
### `FastMCPProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L494" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `FastMCPProvider` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L462" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Provider that wraps a FastMCP server.
|
||||
|
|
@ -210,7 +210,7 @@ This ensures middleware runs when components are executed.
|
|||
|
||||
**Methods:**
|
||||
|
||||
#### `get_app_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L569" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_app_tool` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L537" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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_tool_by_hash` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L580" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tool_by_hash` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L548" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tool_by_hash(self, tool_hash: str, tool_name: str) -> Tool | None
|
||||
|
|
@ -228,7 +228,7 @@ get_tool_by_hash(self, tool_hash: str, tool_name: str) -> Tool | None
|
|||
Delegate to nested server's get_tool_by_hash, wrapping for middleware.
|
||||
|
||||
|
||||
#### `get_tasks` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L679" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `get_tasks` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L647" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_tasks(self) -> Sequence[FastMCPComponent]
|
||||
|
|
@ -242,7 +242,7 @@ server's transforms applied, then applies this provider's transforms
|
|||
for correct registration keys.
|
||||
|
||||
|
||||
#### `lifespan` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L720" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
#### `lifespan` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/fastmcp_provider.py#L688" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
lifespan(self) -> AsyncIterator[None]
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ the app name + tool name). CSP on the resource is the tool's
|
|||
|
||||
## Functions
|
||||
|
||||
### `synthesize_prefab_resources` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L198" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `synthesize_prefab_resources` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L200" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
synthesize_prefab_resources(server: FastMCP) -> list[Resource]
|
||||
|
|
@ -33,7 +33,7 @@ synthesize_prefab_resources(server: FastMCP) -> list[Resource]
|
|||
Return fresh synthetic Prefab resources for all prefab tools. Pure.
|
||||
|
||||
|
||||
### `synthesize_prefab_resource_by_uri` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L213" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `synthesize_prefab_resource_by_uri` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L215" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
synthesize_prefab_resource_by_uri(server: FastMCP, uri: str) -> Resource | None
|
||||
|
|
@ -43,7 +43,7 @@ synthesize_prefab_resource_by_uri(server: FastMCP, uri: str) -> Resource | None
|
|||
Intercept a Prefab renderer URI and synthesize on demand.
|
||||
|
||||
|
||||
### `rewrite_tool_meta_for_wire` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L226" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `rewrite_tool_meta_for_wire` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/providers/prefab_synthesis.py#L228" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
rewrite_tool_meta_for_wire(tool: Tool) -> Tool
|
||||
|
|
|
|||
175
docs/python-sdk/fastmcp-server-tasks-context.mdx
Normal file
175
docs/python-sdk/fastmcp-server-tasks-context.mdx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
---
|
||||
title: context
|
||||
sidebarTitle: context
|
||||
---
|
||||
|
||||
# `fastmcp.server.tasks.context`
|
||||
|
||||
|
||||
Task context and scoping for background task execution.
|
||||
|
||||
Determines authorization scope (``get_task_scope``), manages the context
|
||||
snapshot that is captured at task submission and restored in workers
|
||||
(``TaskContextSnapshot``), and maintains in-process registries for live
|
||||
sessions and servers.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `get_task_scope` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L30" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_scope() -> str | None
|
||||
```
|
||||
|
||||
|
||||
Get the authorization scope for task isolation.
|
||||
|
||||
Returns the raw scope identifier for the current access token, or
|
||||
``None`` when no auth context is present (anonymous tasks).
|
||||
|
||||
The scope is composed as ``client_id|sub`` when the token carries a
|
||||
``sub`` claim — necessary for fixed-OAuth servers where ``client_id`` is
|
||||
shared across all users — and falls back to ``client_id`` alone for
|
||||
DCR/CIMD flows where the client identity is already per-user.
|
||||
|
||||
Encoding for Redis/Docket keys happens at the boundary in ``keys.py``;
|
||||
this function returns the raw value.
|
||||
|
||||
|
||||
### `get_task_context` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L70" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_context() -> TaskContextInfo | None
|
||||
```
|
||||
|
||||
|
||||
Get the current task context if running inside a background task worker.
|
||||
|
||||
This function extracts task information from the Docket execution context.
|
||||
Returns None if not running in a task context (e.g., foreground execution).
|
||||
|
||||
**Returns:**
|
||||
- TaskContextInfo with task_id and task_scope, or None if not in a task.
|
||||
|
||||
|
||||
### `get_task_session_id` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L245" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_session_id() -> str | None
|
||||
```
|
||||
|
||||
|
||||
Get the session_id for the current background task, if available.
|
||||
|
||||
Loads the task snapshot (from cache or Redis) and returns the session_id
|
||||
that was captured at task submission time. Returns None if not in a task
|
||||
context or if the snapshot isn't available.
|
||||
|
||||
|
||||
### `register_task_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_task_session(session_id: str, session: ServerSession) -> None
|
||||
```
|
||||
|
||||
|
||||
Register a session for in-process background task access.
|
||||
|
||||
Called automatically when a task is submitted to Docket. The session is
|
||||
stored as a weakref so it doesn't prevent garbage collection when the
|
||||
client disconnects.
|
||||
|
||||
|
||||
### `get_task_session` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L351" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_session(session_id: str) -> ServerSession | None
|
||||
```
|
||||
|
||||
|
||||
Get a registered session by ID if still alive.
|
||||
|
||||
Returns None in distributed workers where the session lives in another
|
||||
process — callers must handle this gracefully.
|
||||
|
||||
|
||||
### `register_task_server` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L370" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
register_task_server(task_id: str, server: FastMCP) -> None
|
||||
```
|
||||
|
||||
|
||||
Register the server for a background task.
|
||||
|
||||
Called at task-submission time so that background workers can resolve
|
||||
the correct (child) server for mounted tasks.
|
||||
|
||||
|
||||
### `get_task_server` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L381" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_task_server(task_id: str) -> FastMCP | None
|
||||
```
|
||||
|
||||
|
||||
Get the registered server for a background task, if still alive.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `TaskContextInfo` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L56" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Information about the current background task context.
|
||||
|
||||
Returned by ``get_task_context()`` when running inside a Docket worker.
|
||||
Contains identifiers needed to communicate with the MCP session.
|
||||
|
||||
|
||||
### `TaskContextSnapshot` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L100" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
All context data snapshotted at task-submission time.
|
||||
|
||||
Stored as a single Redis key per task, restored once in the worker.
|
||||
|
||||
|
||||
**Methods:**
|
||||
|
||||
#### `capture` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L112" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
capture(cls) -> TaskContextSnapshot
|
||||
```
|
||||
|
||||
Capture current context for background task execution.
|
||||
|
||||
|
||||
#### `from_json` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L139" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
from_json(cls, raw: str | bytes) -> TaskContextSnapshot
|
||||
```
|
||||
|
||||
Deserialize from JSON stored in Redis.
|
||||
|
||||
|
||||
#### `to_json` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L154" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
to_json(self) -> str
|
||||
```
|
||||
|
||||
Serialize to JSON for Redis storage.
|
||||
|
||||
|
||||
#### `save` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/context.py#L165" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
save(self, docket: Docket, task_scope: str | None, task_id: str, ttl_seconds: int) -> None
|
||||
```
|
||||
|
||||
Store this snapshot as a single Redis key.
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ internal APIs for background task coordination.
|
|||
|
||||
## Functions
|
||||
|
||||
### `elicit_for_task` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L42" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `elicit_for_task` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
elicit_for_task(task_id: str, session: ServerSession | None, message: str, schema: dict[str, Any], fastmcp: FastMCP) -> mcp.types.ElicitResult
|
||||
|
|
@ -50,10 +50,10 @@ in a Docket worker context where there's no active MCP request.
|
|||
- `McpError`: If the elicitation request fails
|
||||
|
||||
|
||||
### `relay_elicitation` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L234" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `relay_elicitation` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L239" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
relay_elicitation(session: ServerSession, session_id: str, task_id: str, elicitation: dict[str, Any], fastmcp: FastMCP) -> None
|
||||
relay_elicitation(session: ServerSession, task_scope: str | None, task_id: str, elicitation: dict[str, Any], fastmcp: FastMCP) -> None
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -66,16 +66,16 @@ response to Redis so the blocked worker can resume.
|
|||
|
||||
**Args:**
|
||||
- `session`: MCP ServerSession
|
||||
- `session_id`: Session identifier
|
||||
- `task_scope`: Authorization scope for Redis key construction
|
||||
- `task_id`: Background task ID
|
||||
- `elicitation`: Elicitation metadata (message, requestedSchema)
|
||||
- `fastmcp`: FastMCP server instance
|
||||
|
||||
|
||||
### `handle_task_input` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L290" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `handle_task_input` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/elicitation.py#L295" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
handle_task_input(task_id: str, session_id: str, action: str, content: dict[str, Any] | None, fastmcp: FastMCP) -> bool
|
||||
handle_task_input(task_id: str, task_scope: str | None, action: str, content: dict[str, Any] | None, fastmcp: FastMCP) -> bool
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ request from a background task.
|
|||
|
||||
**Args:**
|
||||
- `task_id`: The background task ID
|
||||
- `session_id`: The MCP session ID
|
||||
- `task_scope`: Authorization scope for Redis key construction
|
||||
- `action`: The elicitation action ("accept", "decline", "cancel")
|
||||
- `content`: The response content (for "accept" action)
|
||||
- `fastmcp`: The FastMCP server instance
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Handles queuing tool/prompt/resource executions to Docket as background tasks.
|
|||
|
||||
## Functions
|
||||
|
||||
### `submit_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/handlers.py#L39" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `submit_to_docket` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/handlers.py#L43" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```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
|
||||
|
|
|
|||
|
|
@ -6,34 +6,40 @@ sidebarTitle: keys
|
|||
# `fastmcp.server.tasks.keys`
|
||||
|
||||
|
||||
Task key management for SEP-1686 background tasks.
|
||||
Docket and Redis key encoding for background tasks.
|
||||
|
||||
Task keys encode security scoping and metadata in the Docket key format:
|
||||
`{session_id}:{client_task_id}:{task_type}:{component_identifier}`
|
||||
The compound Docket task key embeds the auth boundary so that the parser can
|
||||
reject cross-scope access without consulting Redis. Authenticated and
|
||||
anonymous tasks live in disjoint keyspaces:
|
||||
|
||||
This format provides:
|
||||
- Session-based security scoping (prevents cross-session access)
|
||||
- Task type identification (tool/prompt/resource)
|
||||
- Component identification (name or URI for result conversion)
|
||||
auth:{enc_scope}:{client_task_id}:{task_type}:{enc_identifier}
|
||||
anon:{client_task_id}:{task_type}:{enc_identifier}
|
||||
|
||||
The same `auth/anon` partition is used for the per-task Redis prefix
|
||||
(``fastmcp:task:auth:{enc_scope}`` vs ``fastmcp:task:anon``) — see
|
||||
``task_redis_prefix``.
|
||||
|
||||
``task_scope`` is the raw scope identifier (typically derived from
|
||||
``client_id`` or ``client_id|sub``); encoding happens once, at the boundary,
|
||||
in this module.
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### `build_task_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L15" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `build_task_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L41" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
build_task_key(session_id: str, client_task_id: str, task_type: str, component_identifier: str) -> str
|
||||
build_task_key(task_scope: str | None, client_task_id: str, task_type: str, component_identifier: str) -> str
|
||||
```
|
||||
|
||||
|
||||
Build Docket task key with embedded metadata.
|
||||
|
||||
Format: `{session_id}:{client_task_id}:{task_type}:{component_identifier}`
|
||||
|
||||
The component_identifier is URI-encoded to handle special characters (colons, slashes, etc.).
|
||||
When ``task_scope`` is ``None`` the task is anonymous and lives in the
|
||||
``anon`` keyspace. Otherwise it lives under ``auth:{enc_scope}``.
|
||||
|
||||
**Args:**
|
||||
- `session_id`: Session ID for security scoping
|
||||
- `task_scope`: Raw authorization scope, or ``None`` for anonymous tasks
|
||||
- `client_task_id`: Client-provided task ID
|
||||
- `task_type`: Type of task ("tool", "prompt", "resource")
|
||||
- `component_identifier`: Tool name, prompt name, or resource URI
|
||||
|
|
@ -43,16 +49,18 @@ The component_identifier is URI-encoded to handle special characters (colons, sl
|
|||
|
||||
**Examples:**
|
||||
|
||||
>>> build_task_key("session123", "task456", "tool", "my_tool")
|
||||
'session123:task456:tool:my_tool'
|
||||
>>> build_task_key("session123", "task456", "resource", "file://data.txt")
|
||||
'session123:task456:resource:file%3A%2F%2Fdata.txt'
|
||||
>>> build_task_key("client-a", "task456", "tool", "my_tool")
|
||||
'auth:client-a:task456:tool:my_tool'
|
||||
>>> build_task_key(None, "task456", "tool", "my_tool")
|
||||
'anon:task456:tool:my_tool'
|
||||
>>> build_task_key("client-a", "task456", "resource", "file://data.txt")
|
||||
'auth:client-a:task456:resource:file%3A%2F%2Fdata.txt'
|
||||
|
||||
|
||||
### `parse_task_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L47" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `parse_task_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L80" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
parse_task_key(task_key: str) -> dict[str, str]
|
||||
parse_task_key(task_key: str) -> TaskKeyParts
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -62,17 +70,21 @@ Parse Docket task key to extract metadata.
|
|||
- `task_key`: Encoded task key from Docket
|
||||
|
||||
**Returns:**
|
||||
- Dict with keys: session_id, client_task_id, task_type, component_identifier
|
||||
- Dict with keys: ``task_scope`` (``str | None``), ``client_task_id``,
|
||||
- ``task_type``, ``component_identifier``.
|
||||
|
||||
**Raises:**
|
||||
- `ValueError`: If the key has an unrecognized tag or wrong segment count.
|
||||
|
||||
**Examples:**
|
||||
|
||||
>>> parse_task_key("session123:task456:tool:my_tool")
|
||||
`{'session_id': 'session123', 'client_task_id': 'task456', 'task_type': 'tool', 'component_identifier': 'my_tool'}`
|
||||
>>> parse_task_key("session123:task456:resource:file%3A%2F%2Fdata.txt")
|
||||
`{'session_id': 'session123', 'client_task_id': 'task456', 'task_type': 'resource', 'component_identifier': 'file://data.txt'}`
|
||||
>>> parse_task_key("auth:client-a:task456:tool:my_tool")
|
||||
`{'task_scope': 'client-a', 'client_task_id': 'task456', 'task_type': 'tool', 'component_identifier': 'my_tool'}`
|
||||
>>> parse_task_key("anon:task456:tool:my_tool")
|
||||
`{'task_scope': None, 'client_task_id': 'task456', 'task_type': 'tool', 'component_identifier': 'my_tool'}`
|
||||
|
||||
|
||||
### `get_client_task_id_from_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L78" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_client_task_id_from_key` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L137" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_client_task_id_from_key(task_key: str) -> str
|
||||
|
|
@ -85,5 +97,37 @@ Extract just the client task ID from a task key.
|
|||
- `task_key`: Full encoded task key
|
||||
|
||||
**Returns:**
|
||||
- Client-provided task ID (second segment)
|
||||
- Client-provided task ID
|
||||
|
||||
**Examples:**
|
||||
|
||||
>>> get_client_task_id_from_key("auth:client-a:task456:tool:my_tool")
|
||||
'task456'
|
||||
>>> get_client_task_id_from_key("anon:task456:tool:my_tool")
|
||||
'task456'
|
||||
|
||||
|
||||
### `task_redis_prefix` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L156" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
task_redis_prefix(task_scope: str | None) -> str
|
||||
```
|
||||
|
||||
|
||||
Return the Redis key prefix that owns a given scope.
|
||||
|
||||
Authenticated tasks live under ``fastmcp:task:auth:{enc_scope}``;
|
||||
anonymous tasks live under ``fastmcp:task:anon``. Callers append
|
||||
``f":{task_id}:..."`` to compose the final key.
|
||||
|
||||
|
||||
## Classes
|
||||
|
||||
### `TaskKeyParts` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/keys.py#L23" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
|
||||
Decoded segments of a Docket task key.
|
||||
|
||||
``task_scope`` is ``None`` for anonymous tasks, the raw scope string
|
||||
otherwise.
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ This loop:
|
|||
- `fastmcp`: FastMCP server instance (for elicitation relay)
|
||||
|
||||
|
||||
### `ensure_subscriber_running` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L238" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `ensure_subscriber_running` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L246" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
ensure_subscriber_running(session_id: str, session: ServerSession, docket: Docket, fastmcp: FastMCP) -> None
|
||||
|
|
@ -86,7 +86,7 @@ Safe to call multiple times for the same session.
|
|||
- `fastmcp`: FastMCP server instance (for elicitation relay)
|
||||
|
||||
|
||||
### `stop_subscriber` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L278" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `stop_subscriber` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L286" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
stop_subscriber(session_id: str) -> None
|
||||
|
|
@ -102,7 +102,7 @@ for delivery if client reconnects (with TTL expiration).
|
|||
- `session_id`: Session identifier
|
||||
|
||||
|
||||
### `get_subscriber_count` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L298" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `get_subscriber_count` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/notifications.py#L306" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
get_subscriber_count() -> int
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ This module requires fastmcp[tasks] (pydocket). It is only imported when docket
|
|||
|
||||
## Functions
|
||||
|
||||
### `tasks_get_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L137" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `tasks_get_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L135" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskResult
|
||||
|
|
@ -33,7 +33,7 @@ Handle MCP 'tasks/get' request (SEP-1686).
|
|||
- Task status response with spec-compliant fields
|
||||
|
||||
|
||||
### `tasks_result_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L222" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `tasks_result_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L220" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any
|
||||
|
|
@ -52,7 +52,7 @@ Converts raw task return values to MCP types based on task type.
|
|||
- MCP result (CallToolResult, GetPromptResult, or ReadResourceResult)
|
||||
|
||||
|
||||
### `tasks_list_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L403" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `tasks_list_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L401" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tasks_list_handler(server: FastMCP, params: dict[str, Any]) -> ListTasksResult
|
||||
|
|
@ -71,7 +71,7 @@ Note: With client-side tracking, this returns minimal info.
|
|||
- Response with tasks list and pagination
|
||||
|
||||
|
||||
### `tasks_cancel_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L421" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
### `tasks_cancel_handler` <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/src/fastmcp/server/tasks/requests.py#L419" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
|
||||
|
||||
```python
|
||||
tasks_cancel_handler(server: FastMCP, params: dict[str, Any]) -> CancelTaskResult
|
||||
|
|
|
|||
|
|
@ -117,6 +117,12 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
This URL is used to construct OAuth callback URLs and operational endpoints. When mounting under a path prefix, include that prefix in `base_url`. Use `issuer_url` separately to specify where auth server metadata is located (typically at root level).
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="resource_base_url" type="AnyHttpUrl | str | None">
|
||||
Optional public base URL for the protected resource metadata and token audience.
|
||||
|
||||
Use this when your OAuth callbacks and operational endpoints need to live under one public URL, but the protected MCP resource should be advertised under another. FastMCP will still append the MCP mount path (for example, `/mcp`) to this base URL.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="redirect_path" type="str" default="/auth/callback">
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth
|
||||
application
|
||||
|
|
|
|||
|
|
@ -79,6 +79,12 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
Public URL of your FastMCP server (e.g., `https://your-server.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="resource_base_url" type="AnyHttpUrl | str | None">
|
||||
Optional public base URL for the protected resource metadata and token audience.
|
||||
|
||||
Use this when your OAuth callbacks and operational endpoints need to live under one public URL, but the protected MCP resource should be advertised under another. FastMCP will still append the MCP mount path (for example, `/mcp`) to this base URL.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="strict" type="bool | None">
|
||||
Strict flag for configuration validation. When True, requires all OIDC
|
||||
mandatory fields.
|
||||
|
|
|
|||
|
|
@ -156,6 +156,28 @@ async def pick_a_boolean(ctx: Context) -> str:
|
|||
```
|
||||
</CodeGroup>
|
||||
|
||||
#### Customizing the Field Label
|
||||
|
||||
<VersionBadge version="3.3.0" />
|
||||
|
||||
When FastMCP wraps a scalar, `Literal`, `Enum`, or one of the constrained-option shorthands, the wrapper's `value` property is labelled `"Value"` by default — and some clients (including VS Code) render that label directly in the UI. Pass `response_title` and `response_description` to override it:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def confirm_purchase(ctx: Context) -> str:
|
||||
result = await ctx.elicit(
|
||||
"Buy 1x Baguette?",
|
||||
response_type=bool,
|
||||
response_title="Confirm purchase",
|
||||
response_description="Approve this transaction?",
|
||||
)
|
||||
if result.action == "accept":
|
||||
return "Purchased" if result.data else "Declined"
|
||||
return "No response"
|
||||
```
|
||||
|
||||
These arguments only apply when FastMCP is adding the wrapper. For structured responses (`BaseModel`, dataclass, `TypedDict`), set the metadata on the individual fields via `Field(title=..., description=...)` — passing `response_title` or `response_description` alongside a model type raises `TypeError`.
|
||||
|
||||
### No Response
|
||||
|
||||
Sometimes, the goal of an elicitation is to simply get a user to approve or reject an action. Pass `None` as the response type to indicate that no data is expected. The `data` field will be `None` when the user accepts.
|
||||
|
|
|
|||
|
|
@ -61,14 +61,14 @@ The server creates spans for each operation using [MCP semantic conventions](htt
|
|||
| Span Name | Description |
|
||||
|-----------|-------------|
|
||||
| `tools/call {name}` | Tool execution (e.g., `tools/call get_weather`) |
|
||||
| `resources/read {uri}` | Resource read (e.g., `resources/read config://database`) |
|
||||
| `resources/read` | Resource read (URI in `mcp.resource.uri` attribute, not span name) |
|
||||
| `prompts/get {name}` | Prompt render (e.g., `prompts/get greeting`) |
|
||||
|
||||
For mounted servers, an additional `delegate {name}` span shows the delegation to the child server.
|
||||
|
||||
### Client Spans
|
||||
|
||||
The FastMCP client creates spans for outgoing requests with the same naming pattern (`tools/call {name}`, `resources/read {uri}`, `prompts/get {name}`).
|
||||
The FastMCP client creates spans for outgoing requests with the same naming pattern (`tools/call {name}`, `resources/read`, `prompts/get {name}`).
|
||||
|
||||
### Span Hierarchy
|
||||
|
||||
|
|
@ -186,21 +186,16 @@ def risky_operation() -> str:
|
|||
raise ValueError("Something went wrong")
|
||||
|
||||
# The span will have:
|
||||
# - status = ERROR
|
||||
# - status = ERROR with exception message as description
|
||||
# - error.type = "tool_error" (or exception class name for non-tool errors)
|
||||
# - exception event with stack trace
|
||||
```
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
### RPC Semantic Conventions
|
||||
|
||||
Standard [RPC semantic conventions](https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/):
|
||||
|
||||
| Attribute | Value |
|
||||
|-----------|-------|
|
||||
| `rpc.system` | `"mcp"` |
|
||||
| `rpc.service` | Server name |
|
||||
| `rpc.method` | MCP protocol method |
|
||||
<Warning>
|
||||
**Migrating from v3.1 or earlier:** The `rpc.system`, `rpc.service`, and `rpc.method` span attributes were removed in favor of the [MCP semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/) listed below. If you have dashboards or alerts keyed on those `rpc.*` attributes, update them to use `mcp.method.name` and the `fastmcp.*` attributes instead.
|
||||
</Warning>
|
||||
|
||||
### MCP Semantic Conventions
|
||||
|
||||
|
|
@ -211,6 +206,9 @@ FastMCP implements the [OpenTelemetry MCP semantic conventions](https://opentele
|
|||
| `mcp.method.name` | The MCP method being called (`tools/call`, `resources/read`, `prompts/get`) |
|
||||
| `mcp.session.id` | Session identifier for the MCP connection |
|
||||
| `mcp.resource.uri` | The resource URI (for resource operations) |
|
||||
| `gen_ai.tool.name` | Tool name (on `tools/call` spans) |
|
||||
| `gen_ai.prompt.name` | Prompt name (on `prompts/get` spans) |
|
||||
| `error.type` | Error classification (`tool_error` for ToolError, otherwise exception class name) |
|
||||
|
||||
### Auth Attributes
|
||||
|
||||
|
|
|
|||
3
docs/snippets/prefab-pin-warning.mdx
Normal file
3
docs/snippets/prefab-pin-warning.mdx
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Tip>
|
||||
[Prefab](https://prefab.prefect.io) is under active development with frequent breaking changes. FastMCP sets a minimum `prefab-ui` version but does not pin an upper bound — **pin `prefab-ui` to a specific version in your own dependencies** before deploying.
|
||||
</Tip>
|
||||
|
|
@ -115,6 +115,12 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
This URL is used to construct OAuth callback URLs and operational endpoints. When mounting under a path prefix, include that prefix in `base_url`. Use `issuer_url` separately to specify where auth server metadata is located (typically at root level).
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="resource_base_url" type="AnyHttpUrl | str | None">
|
||||
Optional public base URL for the protected resource metadata and token audience.
|
||||
|
||||
Use this when your OAuth callbacks and operational endpoints need to live under one public URL, but the protected MCP resource should be advertised under another. FastMCP will still append the MCP mount path (for example, `/mcp`) to this base URL.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="redirect_path" type="str" default="/auth/callback">
|
||||
Path for OAuth callbacks. Must match the redirect URI configured in your OAuth
|
||||
application
|
||||
|
|
|
|||
|
|
@ -79,6 +79,12 @@ mcp = FastMCP(name="My Server", auth=auth)
|
|||
Public URL of your FastMCP server (e.g., `https://your-server.com`)
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="resource_base_url" type="AnyHttpUrl | str | None">
|
||||
Optional public base URL for the protected resource metadata and token audience.
|
||||
|
||||
Use this when your OAuth callbacks and operational endpoints need to live under one public URL, but the protected MCP resource should be advertised under another. FastMCP will still append the MCP mount path (for example, `/mcp`) to this base URL.
|
||||
</ParamField>
|
||||
|
||||
<ParamField body="strict" type="bool | None">
|
||||
Strict flag for configuration validation. When True, requires all OIDC
|
||||
mandatory fields.
|
||||
|
|
|
|||
36
examples/auth/authkit/README.md
Normal file
36
examples/auth/authkit/README.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# AuthKit Example
|
||||
|
||||
Protects a FastMCP server with WorkOS AuthKit. The server binds the JWT
|
||||
`aud` claim to its own resource URL automatically — you just paste that same
|
||||
URL into the WorkOS Dashboard as a resource indicator.
|
||||
|
||||
## WorkOS Dashboard setup
|
||||
|
||||
In the WorkOS Dashboard for your project, go to **Connect → Configuration** and:
|
||||
|
||||
1. Under **MCP Auth**, enable **Dynamic Client Registration** (or **Client ID
|
||||
Metadata Document** if your MCP client supports it).
|
||||
2. Under **MCP resource indicators**, add `http://127.0.0.1:8000/mcp` as a
|
||||
valid resource indicator.
|
||||
|
||||
## Running
|
||||
|
||||
1. Set your AuthKit domain:
|
||||
|
||||
```bash
|
||||
export AUTHKIT_DOMAIN="https://your-app.authkit.app"
|
||||
```
|
||||
|
||||
2. Start the server. It logs the resource URL it's validating against —
|
||||
that's the URL that must match your dashboard resource indicator:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
3. In another terminal, run the client. Your browser will open for AuthKit
|
||||
authentication:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
"""AuthKit DCR server example for FastMCP.
|
||||
"""AuthKit server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with AuthKit DCR.
|
||||
Demonstrates an MCP server secured by WorkOS AuthKit. FastMCP binds the JWT
|
||||
audience to this server's resource URL automatically; you configure the same
|
||||
URL as an MCP resource indicator in the WorkOS Dashboard.
|
||||
|
||||
Required environment variables:
|
||||
- FASTMCP_SERVER_AUTH_AUTHKITPROVIDER_AUTHKIT_DOMAIN: Your AuthKit domain (e.g., "https://your-app.authkit.app")
|
||||
- AUTHKIT_DOMAIN: Your AuthKit domain (e.g., "https://your-app.authkit.app")
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
|
|
@ -16,10 +18,10 @@ from fastmcp.server.auth.providers.workos import AuthKitProvider
|
|||
|
||||
auth = AuthKitProvider(
|
||||
authkit_domain=os.getenv("AUTHKIT_DOMAIN") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
)
|
||||
|
||||
mcp = FastMCP("AuthKit DCR Example Server", auth=auth)
|
||||
mcp = FastMCP("AuthKit Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
# AuthKit DCR Example
|
||||
|
||||
Demonstrates FastMCP server protection with AuthKit Dynamic Client Registration.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Set your AuthKit domain:
|
||||
|
||||
```bash
|
||||
export AUTHKIT_DOMAIN="https://your-app.authkit.app"
|
||||
```
|
||||
|
||||
2. Run the server:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
3. In another terminal, run the client:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
||||
The client will open your browser for AuthKit authentication.
|
||||
|
|
@ -10,7 +10,7 @@ Demonstrates FastMCP server protection with AWS Cognito OAuth.
|
|||
- Create an App Client in your User Pool
|
||||
- Configure the App Client settings:
|
||||
- Enable "Authorization code grant" flow
|
||||
- Add Callback URL: `http://localhost:8000/auth/callback`
|
||||
- Add Callback URL: `http://127.0.0.1:8000/auth/callback`
|
||||
- Configure OAuth scopes (at minimum: `openid`)
|
||||
- Note your User Pool ID, App Client ID, Client Secret, and Cognito Domain Prefix
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import asyncio
|
|||
|
||||
from fastmcp.client import Client
|
||||
|
||||
SERVER_URL = "http://localhost:8000/mcp"
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
fastmcp
|
||||
python-dotenv
|
||||
python-dotenv
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ auth = AWSCognitoProvider(
|
|||
or "eu-central-1",
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_AWS_COGNITO_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# redirect_path="/custom/callback"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ This example demonstrates how to use the Azure OAuth provider with FastMCP serve
|
|||
2. Click "New registration" and configure:
|
||||
- Name: Your app name
|
||||
- Supported account types: Choose based on your needs
|
||||
- Redirect URI: `http://localhost:8000/auth/callback` (Web platform)
|
||||
- Redirect URI: `http://127.0.0.1:8000/auth/callback` (Web platform)
|
||||
3. After creation, go to "Certificates & secrets" → "New client secret"
|
||||
4. Note these values from the Overview page:
|
||||
- Application (client) ID
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ auth = AzureProvider(
|
|||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_AZURE_CLIENT_SECRET") or "",
|
||||
tenant_id=os.getenv("FASTMCP_SERVER_AUTH_AZURE_TENANT_ID")
|
||||
or "", # Required for single-tenant apps - get from Azure Portal
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
required_scopes=["read"],
|
||||
# required_scopes is automatically loaded from FASTMCP_SERVER_AUTH_AZURE_REQUIRED_SCOPES
|
||||
# At least one scope is required - use unprefixed scope names from your Azure App (e.g., ["read", "write"])
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Demonstrates FastMCP server protection with Clerk OAuth.
|
|||
- Create or select an application
|
||||
- Go to Developers > OAuth Applications
|
||||
- Create an OAuth application
|
||||
- Add Authorized redirect URI: `http://localhost:8000/auth/callback`
|
||||
- Add Authorized redirect URI: `http://127.0.0.1:8000/auth/callback`
|
||||
- Copy the Client ID and Client Secret
|
||||
- Note your instance domain (e.g., `saving-primate-16.clerk.accounts.dev`)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ auth = ClerkProvider(
|
|||
domain=os.getenv("FASTMCP_SERVER_AUTH_CLERK_DOMAIN") or "",
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_CLERK_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_CLERK_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
|
||||
# Optional: specify required scopes (defaults to ["openid", "email", "profile"])
|
||||
# required_scopes=["openid", "email", "profile", "public_metadata"],
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Demonstrates FastMCP server protection with Discord OAuth.
|
|||
- Go to https://discord.com/developers/applications
|
||||
- Click "New Application" and give it a name
|
||||
- Go to OAuth2 in the left sidebar
|
||||
- Add a Redirect URL: `http://localhost:8000/auth/callback`
|
||||
- Add a Redirect URL: `http://127.0.0.1:8000/auth/callback`
|
||||
- Copy the Client ID and Client Secret
|
||||
|
||||
2. Set environment variables:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from fastmcp.server.auth.providers.discord import DiscordProvider
|
|||
auth = DiscordProvider(
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_DISCORD_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_DISCORD_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Demonstrates FastMCP server protection with GitHub OAuth.
|
|||
|
||||
1. Create a GitHub OAuth App:
|
||||
- Go to GitHub Settings > Developer settings > OAuth Apps
|
||||
- Set Authorization callback URL to: `http://localhost:8000/auth/callback`
|
||||
- Set Authorization callback URL to: `http://127.0.0.1:8000/auth/callback`
|
||||
- Copy the Client ID and Client Secret
|
||||
|
||||
2. Set environment variables:
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import asyncio
|
|||
|
||||
from fastmcp.client import Client, OAuth
|
||||
|
||||
SERVER_URL = "http://localhost:8000/mcp"
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from fastmcp.server.auth.providers.github import GitHubProvider
|
|||
auth = GitHubProvider(
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GITHUB_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Demonstrates FastMCP server protection with Google OAuth.
|
|||
- Create or select a project
|
||||
- Go to APIs & Services > Credentials
|
||||
- Create OAuth 2.0 Client ID (Web application)
|
||||
- Add Authorized redirect URI: `http://localhost:8000/auth/callback`
|
||||
- Add Authorized redirect URI: `http://127.0.0.1:8000/auth/callback`
|
||||
- Copy the Client ID and Client Secret
|
||||
|
||||
2. Set environment variables:
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from fastmcp.server.auth.providers.google import GoogleProvider
|
|||
auth = GoogleProvider(
|
||||
client_id=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_ID") or "",
|
||||
client_secret=os.getenv("FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET") or "",
|
||||
base_url="http://localhost:8000",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# redirect_path="/auth/callback", # Default path - change if using a different callback URL
|
||||
# Optional: specify required scopes
|
||||
# required_scopes=["openid", "https://www.googleapis.com/auth/userinfo.email"],
|
||||
|
|
|
|||
29
examples/auth/keycloak_oauth/README.md
Normal file
29
examples/auth/keycloak_oauth/README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Keycloak OAuth Example
|
||||
|
||||
Demonstrates FastMCP server protection with Keycloak OAuth.
|
||||
|
||||
**Requires Keycloak 26.6.0 or later** with Dynamic Client Registration enabled.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Configure a Keycloak realm with Dynamic Client Registration enabled and a trusted host policy for your server URL (e.g. `http://127.0.0.1:8000/*`).
|
||||
|
||||
2. Set environment variables:
|
||||
|
||||
```bash
|
||||
export KEYCLOAK_REALM_URL="http://localhost:8080/realms/your-realm"
|
||||
```
|
||||
|
||||
3. Run the server:
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
4. In another terminal, run the client:
|
||||
|
||||
```bash
|
||||
python client.py
|
||||
```
|
||||
|
||||
The client will open your browser for Keycloak authentication.
|
||||
33
examples/auth/keycloak_oauth/client.py
Normal file
33
examples/auth/keycloak_oauth/client.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
"""OAuth client example for connecting to a Keycloak-protected FastMCP server.
|
||||
|
||||
To run:
|
||||
python client.py
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastmcp import Client
|
||||
|
||||
SERVER_URL = "http://127.0.0.1:8000/mcp"
|
||||
|
||||
|
||||
async def main():
|
||||
async with Client(SERVER_URL, auth="oauth") as client:
|
||||
assert await client.ping()
|
||||
print("Successfully authenticated!")
|
||||
|
||||
tools = await client.list_tools()
|
||||
print(f"Available tools ({len(tools)}):")
|
||||
for tool in tools:
|
||||
print(f" - {tool.name}: {tool.description}")
|
||||
|
||||
print("Calling protected tool: get_access_token_claims")
|
||||
result = await client.call_tool("get_access_token_claims")
|
||||
claims = result.data
|
||||
print(f" sub: {claims.get('sub', 'N/A')}")
|
||||
print(f" scope: {claims.get('scope', 'N/A')}")
|
||||
print(f" azp: {claims.get('azp', 'N/A')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
44
examples/auth/keycloak_oauth/server.py
Normal file
44
examples/auth/keycloak_oauth/server.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"""Keycloak OAuth server example for FastMCP.
|
||||
|
||||
This example demonstrates how to protect a FastMCP server with Keycloak OAuth.
|
||||
|
||||
Required: Keycloak 26.6.0 or later with Dynamic Client Registration enabled.
|
||||
|
||||
To run:
|
||||
KEYCLOAK_REALM_URL=https://your-keycloak.com/realms/myrealm python server.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.auth.providers.keycloak import KeycloakAuthProvider
|
||||
from fastmcp.server.dependencies import get_access_token
|
||||
|
||||
auth = KeycloakAuthProvider(
|
||||
realm_url=os.getenv("KEYCLOAK_REALM_URL") or "http://localhost:8080/realms/fastmcp",
|
||||
base_url="http://127.0.0.1:8000",
|
||||
# audience="http://127.0.0.1:8000", # Recommended for production
|
||||
)
|
||||
|
||||
mcp = FastMCP("Keycloak Example Server", auth=auth)
|
||||
|
||||
|
||||
@mcp.tool
|
||||
def echo(message: str) -> str:
|
||||
"""Echo the provided message."""
|
||||
return message
|
||||
|
||||
|
||||
@mcp.tool
|
||||
async def get_access_token_claims() -> dict:
|
||||
"""Get the authenticated user's access token claims."""
|
||||
token = get_access_token()
|
||||
return {
|
||||
"sub": token.claims.get("sub"),
|
||||
"scope": token.claims.get("scope"),
|
||||
"azp": token.claims.get("azp"),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="http", port=8000)
|
||||
|
|
@ -4,12 +4,12 @@ This example demonstrates mounting multiple OAuth-protected MCP servers in a sin
|
|||
|
||||
## URL Structure
|
||||
|
||||
- **GitHub MCP**: `http://localhost:8000/api/mcp/github/mcp`
|
||||
- **Google MCP**: `http://localhost:8000/api/mcp/google/mcp`
|
||||
- **GitHub MCP**: `http://127.0.0.1:8000/api/mcp/github/mcp`
|
||||
- **Google MCP**: `http://127.0.0.1:8000/api/mcp/google/mcp`
|
||||
|
||||
Discovery endpoints (RFC 8414 path-aware):
|
||||
- **GitHub**: `http://localhost:8000/.well-known/oauth-authorization-server/api/mcp/github`
|
||||
- **Google**: `http://localhost:8000/.well-known/oauth-authorization-server/api/mcp/google`
|
||||
- **GitHub**: `http://127.0.0.1:8000/.well-known/oauth-authorization-server/api/mcp/github`
|
||||
- **Google**: `http://127.0.0.1:8000/.well-known/oauth-authorization-server/api/mcp/google`
|
||||
|
||||
## Setup
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ export FASTMCP_SERVER_AUTH_GOOGLE_CLIENT_SECRET="your-google-client-secret"
|
|||
```
|
||||
|
||||
Configure redirect URIs in each provider's developer console (note the `/api/mcp/{provider}` prefix since the servers are mounted):
|
||||
- GitHub: `http://localhost:8000/api/mcp/github/auth/callback/github`
|
||||
- Google: `http://localhost:8000/api/mcp/google/auth/callback/google`
|
||||
- GitHub: `http://127.0.0.1:8000/api/mcp/github/auth/callback/github`
|
||||
- Google: `http://127.0.0.1:8000/api/mcp/google/auth/callback/google`
|
||||
|
||||
## Running
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ application, each with its own provider. It showcases RFC 8414 path-aware discov
|
|||
where each server has its own authorization server metadata endpoint.
|
||||
|
||||
URL structure:
|
||||
- GitHub MCP: http://localhost:8000/api/mcp/github/mcp
|
||||
- Google MCP: http://localhost:8000/api/mcp/google/mcp
|
||||
- GitHub discovery: http://localhost:8000/.well-known/oauth-authorization-server/api/mcp/github
|
||||
- Google discovery: http://localhost:8000/.well-known/oauth-authorization-server/api/mcp/google
|
||||
- GitHub MCP: http://127.0.0.1:8000/api/mcp/github/mcp
|
||||
- Google MCP: http://127.0.0.1:8000/api/mcp/google/mcp
|
||||
- GitHub discovery: http://127.0.0.1:8000/.well-known/oauth-authorization-server/api/mcp/github
|
||||
- Google discovery: http://127.0.0.1:8000/.well-known/oauth-authorization-server/api/mcp/google
|
||||
|
||||
Required environment variables:
|
||||
- FASTMCP_SERVER_AUTH_GITHUB_CLIENT_ID: Your GitHub OAuth app client ID
|
||||
|
|
@ -31,7 +31,7 @@ from fastmcp.server.auth.providers.github import GitHubProvider
|
|||
from fastmcp.server.auth.providers.google import GoogleProvider
|
||||
|
||||
# Configuration
|
||||
ROOT_URL = "http://localhost:8000"
|
||||
ROOT_URL = "http://127.0.0.1:8000"
|
||||
API_PREFIX = "/api/mcp"
|
||||
|
||||
# --- GitHub OAuth Server ---
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Create a `.env` file:
|
|||
PROPELAUTH_AUTH_URL=https://auth.yourdomain.com
|
||||
PROPELAUTH_INTROSPECTION_CLIENT_ID=your-client-id
|
||||
PROPELAUTH_INTROSPECTION_CLIENT_SECRET=your-client-secret
|
||||
BASE_URL=http://localhost:8000/
|
||||
BASE_URL=http://127.0.0.1:8000/
|
||||
# Optional: additional scopes tokens must include (comma-separated)
|
||||
# PROPELAUTH_REQUIRED_SCOPES=read:user_data
|
||||
```
|
||||
|
|
@ -50,7 +50,7 @@ Start the server:
|
|||
uv run python server.py
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:8000/mcp` with PropelAuth OAuth authentication enabled.
|
||||
The server will start on `http://127.0.0.1:8000/mcp` with PropelAuth OAuth authentication enabled.
|
||||
|
||||
Test with client:
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Required environment variables:
|
|||
|
||||
Optional:
|
||||
- PROPELAUTH_REQUIRED_SCOPES: Comma-separated scopes tokens must include
|
||||
- BASE_URL: Public URL where the FastMCP server is exposed (defaults to `http://localhost:8000/`)
|
||||
- BASE_URL: Public URL where the FastMCP server is exposed (defaults to `http://127.0.0.1:8000/`)
|
||||
|
||||
To run:
|
||||
python server.py
|
||||
|
|
@ -29,7 +29,7 @@ auth = PropelAuthProvider(
|
|||
auth_url=os.environ["PROPELAUTH_AUTH_URL"],
|
||||
introspection_client_id=os.environ["PROPELAUTH_INTROSPECTION_CLIENT_ID"],
|
||||
introspection_client_secret=os.environ["PROPELAUTH_INTROSPECTION_CLIENT_SECRET"],
|
||||
base_url=os.getenv("BASE_URL", "http://localhost:8000/"),
|
||||
base_url=os.getenv("BASE_URL", "http://127.0.0.1:8000/"),
|
||||
)
|
||||
|
||||
mcp = FastMCP("PropelAuth OAuth Example Server", auth=auth)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Create a `.env` file:
|
|||
# Required Scalekit credentials
|
||||
SCALEKIT_ENVIRONMENT_URL=<YOUR_APP_ENVIRONMENT_URL>
|
||||
SCALEKIT_RESOURCE_ID=<YOUR_APP_RESOURCE_ID> # res_926EXAMPLE5878
|
||||
BASE_URL=http://localhost:8000/
|
||||
BASE_URL=http://127.0.0.1:8000/
|
||||
# Optional: additional scopes tokens must include (comma-separated)
|
||||
# SCALEKIT_REQUIRED_SCOPES=read,write
|
||||
```
|
||||
|
|
@ -38,7 +38,7 @@ Start the server:
|
|||
uv run python server.py
|
||||
```
|
||||
|
||||
The server will start on `http://localhost:8000/mcp` with Scalekit OAuth authentication enabled.
|
||||
The server will start on `http://127.0.0.1:8000/mcp` with Scalekit OAuth authentication enabled.
|
||||
|
||||
Test with client:
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue