Use this file to discover all available pages before exploring further.
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 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.You’ll be working with two things: the @modelcontextprotocol/ext-apps JavaScript SDK for host communication, and FastMCP’s AppConfig for resources and CSP.
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.
import jsonfrom fastmcp import FastMCPfrom fastmcp.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.
@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:
from fastmcp import Contextfrom fastmcp.apps import AppConfig, UI_EXTENSION_ID@mcp.tool(app=AppConfig(resource_uri="ui://my-app/view.html"))async def my_tool(ctx: Context) -> str: 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()