Integrate directly with the MCP Apps extension to build interactive tool UIs.
New in version 3.0.0The MCP Apps extension (io.modelcontextprotocol/ui) lets tools return interactive UIs — an HTML page rendered in a sandboxed iframe inside the host client. Instead of returning plain text or JSON, a tool can show a chart, a form, an image viewer, or anything you can build with HTML and JavaScript.This page covers the low-level extension API directly. FastMCP provides typed models for app configuration, automatic ui:// resource handling, and CSP/permission management.
A ui:// resource containing the HTML that renders that data
The tool declares which resource to use via AppConfig. When the host calls the tool, it also fetches the linked resource, renders it in a sandboxed iframe, and pushes the tool result into the app via postMessage. The app can also call tools back, enabling interactive workflows.
Copy
import jsonfrom fastmcp import FastMCPfrom fastmcp.server.apps import AppConfig, ResourceCSPmcp = FastMCP("My App Server")# The tool does the work@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))def generate_chart(data: list[float]) -> str: return json.dumps({"values": data})# The resource provides the UI@mcp.resource("ui://my-app/view.html")def chart_view() -> str: return "<html>...</html>"
The visibility field controls where a tool appears:
["model"] — visible to the LLM (the default behavior)
["app"] — only callable from within the app UI, hidden from the LLM
["model", "app"] — both
This is useful when you have tools that only make sense as part of the app’s interactive flow, not as standalone LLM actions.
Copy
@mcp.tool( app=AppConfig( resource_uri="ui://my-app/view.html", visibility=["app"], ))def refresh_data() -> str: """Only callable from the app UI, not by the LLM.""" return fetch_latest()
Where the tool appears: "model", "app", or both. Tools only.
csp
ResourceCSP
Content Security Policy for the iframe.
permissions
ResourcePermissions
Iframe sandbox permissions.
domain
str
Stable sandbox origin for the iframe.
prefers_border
bool
Whether the UI prefers a visible border.
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.
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.
Apps run in sandboxed iframes with a deny-by-default Content Security Policy. By default, only inline scripts and styles are allowed — no external network access.
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. Requires the qrcode[pil] package.
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.
Not all hosts support the Apps extension. You can check at runtime using the tool’s context:
Copy
from fastmcp import Contextfrom fastmcp.server.apps import AppConfig, UI_EXTENSION_ID@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))async def my_tool(ctx: Context) -> str: if ctx.client_supports_extension(UI_EXTENSION_ID): # Return data optimized for UI rendering return rich_response() else: # Fall back to plain text return plain_text_response()