FastMCP 3.2 is the Apps release. The 3.0 architecture gave you providers and transforms; 3.1 shipped Code Mode for tool discovery. 3.2 puts a face on it: your tools can now return interactive UIs — charts, dashboards, forms, maps — rendered right inside the conversation.
FastMCPApp
FastMCPApp is a new provider class for building interactive applications inside MCP. It separates the tools the LLM sees (@app.ui()) from the backend tools the UI calls (@app.tool()), manages visibility automatically, and gives tool references stable identifiers that survive namespace transforms and server composition — without requiring host cooperation.
from fastmcp import FastMCP, FastMCPApp
from prefab_ui.actions.mcp import CallTool
from prefab_ui.components import Column, Form, Input, Button, ForEach, Text
app = FastMCPApp("Contacts")
@app.tool()
def save_contact(name: str, email: str) -> list[dict]:
db.append({"name": name, "email": email})
return list(db)
@app.ui()
def contact_manager() -> PrefabApp:
with PrefabApp(state={"contacts": list(db)}) as view:
with Column(gap=4):
ForEach("contacts", lambda c: Text(c.name))
with Form(on_submit=CallTool("save_contact")):
Input(name="name", required=True)
Input(name="email", required=True)
Button("Save")
return view
mcp = FastMCP("Server", providers=[app])The UI is built with Prefab, a Python component library that compiles to interactive UIs. You write Python; the user sees charts, tables, forms, and dashboards. FastMCP handles the MCP Apps protocol machinery — renderer resources, CSP configuration, structured content serialization — so you don't have to.
For simpler cases where you just want to visualize data without server interaction, set app=True on any tool and return Prefab components directly:
@mcp.tool(app=True)
def revenue_chart(year: int) -> PrefabApp:
with PrefabApp() as app:
BarChart(data=revenue_data, series=[ChartSeries(data_key="revenue")])
return appBuilt-in Providers
Five ready-made providers you add with a single add_provider() call:
- FileUpload — drag-and-drop file upload with session-scoped storage
- Approval — human-in-the-loop confirmation gates
- Choice — clickable option selection
- FormInput — generate validated forms from Pydantic models
- GenerativeUI — the LLM writes Prefab code at runtime and the result streams to the user as it's generated
Dev Server
fastmcp dev apps launches a browser-based preview for your app tools — pick a tool, provide arguments, and see the rendered UI without connecting to an MCP host. Includes an MCP message inspector for debugging the protocol traffic.
Security
This release includes a significant security hardening pass: SSRF/path traversal prevention, JWT algorithm restrictions, OAuth scope enforcement, CSRF fixes, and more. See the changelog for the full list.
Everything Else
forward_resource flag on all OAuth providers for IdPs that don't support RFC 8707. Clerk auth provider. FileResource encoding parameter. SSL verify parameter. client_log_level setting. MCP conformance tests in CI. And contributions from 13 new contributors.
What's Changed
New Features 🎉
- Add FastMCPApp — a Provider for composable MCP applications by @jlowin in #3385
- Add fastmcp dev apps command with browser UI preview by @jlowin in #3489
- Add GenerativeUI provider, bump prefab-ui 0.14.0 by @jlowin in #3647
- Add FileUpload provider by @jlowin in #3669
- Add Approval and Choice providers by @jlowin in #3686
- Add FormInput provider, bump prefab-ui to 0.15.0 by @jlowin in #3687
Breaking Changes ⚠️
Enhancements ✨
- feat: add
--config-pathflag to claude-desktop install command by @Sumanshu-Nankana in #3380 - Support ImageContent and AudioContent in Message class by @ericrobinson-indeed in #3396
- Deprecate PromptToolMiddleware and ResourceToolMiddleware by @jlowin in #3389
- Block HS* algorithms when JWTVerifier is configured with JWKS by @jlowin in #3419
- Remove prek from Marvin workflows by @jlowin in #3444
- Add dependency version compatibility guidance to code-review skill by @jlowin in #3475
- Remove "good first issue" label by @jlowin in #3482
- Cache component lists in ProxyProvider by @jlowin in #3479
- Support logging/setLevel and add client_log_level by @jlowin in #3491
- Propagate x-fastmcp-wrap-result in tool result _meta by @jlowin in #3490
- feat(auth): add external_consent param to suppress misleading warning by @mtthidoteu in #3473
- Add
verifyparameter for SSL certificate configuration by @jlowin in #3487 - Expose minimum_check_interval, reduce task pickup latency by @jlowin in #3500
- Fix test timeouts, suppress deprecation warnings, speed up auth tests by @jlowin in #3504
- Auto-close upgrade check issue when build passes by @jlowin in #3505
- feat: make upstream_client_secret optional in OAuthProxy by @jlowin in #3486
- Add security label to triage workflow and release notes by @jlowin in #3516
- Claude/review contributor guidelines by @jlowin in #3517
- pin pydantic-monty to 0.0.8 by @jlowin in #3539
- Support ImageContent and AudioContent in sampling handlers by @jlowin in #3550
- Graceful degradation for multi-server proxy setup by @jlowin in #3546
- Extract TokenCache utility, add caching to GitHubTokenVerifier by @jlowin in #3547
- Add review-pr skill for Codex bot workflow by @jlowin in #3552
- Add MCP message inspector to dev apps UI by @jlowin in #3570
- Comprehensive MCP Apps docs, string CallTool resolution by @jlowin in #3575
- Replace UUID global keys with (app_name, tool_name) registry by @jlowin in #3585
- Route app tool calls through provider chain by @jlowin in #3587
- Dev apps: show more/less for long tool descriptions by @jlowin in #3600
- Apps Phase 1: docs, examples, app-only tool filtering by @jlowin in #3593
- Forward enable_cimd to OAuthProxy in all provider subclasses by @jlowin in #3608
- Tune too-long triage heuristic by @jlowin in #3610
- Update ty ignore comments for 0.0.25 compatibility by @jlowin in #3614
- Move app modules to fastmcp.apps package by @jlowin in #3616
- Tighten too-long heuristic for design-document issues by @jlowin in #3620
- Run MCP conformance tests by @strawgate in #3628
- Add PrefabAppConfig for customizable Prefab tool setup by @jlowin in #3648
- Clean error when dev apps ports are in use by @jlowin in #3658
- Add Clerk OAuth provider by @mostafa6765 in #3677
- Add interactive map example with geocoding by @jlowin in #3702
- Bump pydantic-monty to 0.0.9 by @jlowin in #3707
- Add forward_resource flag to OAuthProxy by @jlowin in #3711
Security 🔒
- fix: enforce per-tool auth checks in sampling tool wrapper by @jlowin in #3494
- fix: handle re.error from malformed URI templates by @jlowin in #3501
- fix: reject empty/OIDC-only required_scopes in AzureProvider by @jlowin in #3503
- fix: restrict $ref resolution to local refs only (SSRF/LFI) by @jlowin in #3502
- fix: URL-encode path params to prevent SSRF/path traversal (GHSA-vv7q-7jx5-f767) by @jlowin in #3507
- fix: prevent path traversal in skill download by @jlowin in #3493
- fix: prefer IdP-granted scopes over client-requested scopes in OAuthProxy by @jlowin in #3492
- fix: remove forced follow_redirects from httpx_client_factory calls by @jlowin in #3496
- Bump PyJWT >= 2.12.0 (CVE-2026-32597) by @jlowin in #3515
- Drop diskcache from examples/testing_demo lockfile (CVE-2025-69872) by @jlowin in #3518
- fix: CSRF double-submit cookie check in consent flow by @jlowin in #3519
- fix: validate server names in install commands by @jlowin in #3522
- fix: reject refresh tokens used as Bearer access tokens by @jlowin in #3524
- fix: route ResourcesAsTools/PromptsAsTools through server middleware by @jlowin in #3495
Fixes 🐞
- Update docs banner and fix mobile layout by @jlowin in #3370
- Remove form-action from consent CSP, forward consent_csp_policy in providers by @jlowin in #3372
- Fix resource templates with query params on mounted servers by @jlowin in #3373
- Increase uv transport test timeout for CI cold starts by @jlowin in #3376
- Fix stale catalog in CodeMode execute by @jlowin in #3375
- Deduplicate versioned tools in CatalogTransform catalog by @jlowin in #3374
- Fix ty 0.0.20 compatibility by @jlowin in #3377
- Forward scopes_supported through RemoteAuthProvider subclasses by @jlowin in #3388
- Enforce token scopes in WorkOS verifier to prevent scope bypass by @jlowin in #3407
- Bind Discord token verification to configured client_id by @jlowin in #3405
- Return after
McpErrorin initialization middleware to prevent fallthrough by @jlowin in #3413 - Escape client_id in OAuth consent advanced details by @jlowin in #3418
- Bound client auto-pagination loops to prevent unbounded list fetches by @jlowin in #3411
- Raise ValueError for invalid boolean query params in resource templates by @jlowin in #3434
- Validate workspace path is a directory in cursor install by @jlowin in #3435
- Validate version metadata to reject non-scalar types by @jlowin in #3437
- Bind AWS Cognito token verification to configured app client by @jlowin in #3406
- Avoid stale context leakage when proxying with an already‑connected ProxyClient by @jlowin in #3408
- Prevent skills manifests from hashing files outside the skill directory by @jlowin in #3410
- Harden fastmcp metadata parsing in proxy paths by @jlowin in #3412
- Re-hash response caching keys to avoid persisting raw request input by @jlowin in #3414
- Handle Windows npx detection when npx.cmd is missing by @jlowin in #3416
- Guard OAuth callback result from post-completion overwrites by @jlowin in #3417
- Fix tool argument rename collisions with passthrough params by @jlowin in #3431
- Guard default progress handler against total=0 notifications by @jlowin in #3432
- Fix get_* returning None when latest version is disabled by @jlowin in #3439
- Fix server lifespan overlap teardown by @jlowin in #3415
- Fix $ref output schema object detection regression by @jlowin in #3420
- Preserve kw-only defaults when rebuilding functions for resolved annotations by @jlowin in #3429
- Redact sensitive headers in OpenAPI provider debug logging by @jlowin in #3436
- Fix async partial callables rejected by iscoroutinefunction by @jlowin in #3438
- Block insecure HS* JWT verification with JWKS/public keys by @jlowin in #3430
- Sanitize untrusted output in
fastmcp listandfastmcp callby @jlowin in #3409 - fix: propagate
versionto components in FileSystemProvider by @martimfasantos in #3458 - fix: use intent-based flag for OIDC scope patch in load_access_token by @voidborne-d in #3465
- Set readOnlyHint=True on ResourcesAsTools generated tools by @jlowin in #3476
- fix: normalize Google scope shorthands and surface valid_scopes by @jlowin in #3477
- fix: resolve ty 0.0.23 type-checking errors by @jlowin in #3481
- fix: shield lifespan teardown from cancellation by @jlowin in #3480
- fix: forward custom_route endpoints from mounted servers by @voidborne-d in #3462
- fix: use dynamic version in CLI help text instead of hardcoded 2.0 by @saschabuehrle in #3456
- Fix Monty 0.0.8 compatibility by @hkc5 in #3468
- Fix task test teardown hanging 5s per test by @jlowin in #3499
- fix: validate workspace path is a directory before cursor install by @nightcityblade in #3440
- Treat
refresh_expires_in=0as missing, fall back to 30-day default by @jlowin in #3514 - fix: use raw strings for regex in pytest.raises match by @jlowin in #3523
- fix: resolve Pyright "Module is not callable" on @tool, @resource, @prompt decorators by @jlowin in #3540
- fix: flaky KEY_PREFIX warning test in lowest-direct deps by @jlowin in #3549
- fix: suppress output schema for ToolResult subclass annotations by @jlowin in #3548
- Bump anthropic minimum to 0.48.0 by @jlowin in #3553
- Update startup banner deploy URL to Prefect Horizon by @zzstoatzz in #3557
- fix: increase sleep duration in proxy cache tests by @strawgate in #3567
- fix: store absolute token expiry to prevent stale expires_in on reload by @jlowin in #3572
- fix: preserve tool properties named 'title' during schema compression by @jlowin in #3582
- Add
encodingparameter toFileResourceby @shulkx in #3580 - Transparently refresh upstream token in OAuthProxy.load_access_token() by @jlowin in #3584
- Fix loopback redirect URI port matching per RFC 8252 §7.3 by @radoshi in #3589
- Fix app tool routing: visibility check and middleware propagation by @jlowin in #3591
- Fix query parameter serialization to respect OpenAPI explode setting by @jlowin in #3595
- Fix dev apps form: union types, textarea support, JSON parsing by @jlowin in #3597
- Respect OpenAPI content type in request body serialization by @jlowin in #3611
- fix(google): replace deprecated /oauth2/v1/tokeninfo with /oauth2/v3/userinfo by @shigechika in #3603
- fix: resolve EntraOBOToken dependency injection through MultiAuth by @jer805 in #3609
- fix: filesystem provider import machinery by @strawgate in #3626
- fix: recover StdioTransport after subprocess exits by @strawgate in #3630
- fix(server): preserve mounted tool task metadata by @pandego in #3632
- fix: scope deprecation warning filter to FastMCPDeprecationWarning by @jlowin in #3649
- fix: resolve CurrentFastMCP/ctx.fastmcp to child server in mounted background tasks by @jlowin in #3651
- Fix blocking docs issues: chart imports, Select API, Rx consistency by @jlowin in #3652
- Fix prompt caching round-trip on cache miss by @strawgate in #3666
- fix: serialize object query params per OpenAPI style/explode rules by @4444J99 in #3662
- fix: HTTP request headers not accessible in background task workers by @pandego in #3631
- fix: restore HTTP headers in worker execution path for background tasks by @jlowin in #3681
- fix: strip discriminator after dereferencing schemas by @jlowin in #3682
- fix: remove stale ty:ignore directives for ty 0.0.26 by @jlowin in #3684
- fix: dev apps log panel UX improvements by @jlowin in #3698
- Add quiz example app, fix dev server empty string args by @jlowin in #3700
Docs 📚
- Add early-development warning to Prefab docs by @jlowin in #3362
- Add tag to docs by @jlowin in #3382
- Add settings and environment variables reference by @jlowin in #3384
- Add contributing guidelines and update issue/PR templates by @jlowin in #3485
- [Documentation] Move stateless_http transport kwarg to http_app as FastMCP constructo… by @mhallo in #3510
- Update security policy by @jlowin in #3521
- Add release instructions to CLAUDE.md by @jlowin in #3583
- fix(docs): correct misleading stateless_http header by @jlowin in #3622
- Add tag to deployment pages by @jlowin in #3624
- Docs: generative UI page, fix imports, add PrefabAppConfig by @jlowin in #3650
- docs: improve contributor guidelines for framework contributions by @jlowin in #3653
- Add release notes for v3.1.0, v3.1.1, and v2.14.6 by @jlowin in #3659
- Docs: showcase hero, narrative improvements, panel closed by default by @jlowin in #3657
- Docs: add FileTreeStore sanitization warnings and update examples by @strawgate in #3661
- Add prefab-ui version pinning warning to docs by @jlowin in #3688
- Reorganize apps overview TOC by @jlowin in #3689
- Fix docs gaps in app provider pages by @jlowin in #3690
- Polish apps docs for 3.2 release by @jlowin in #3693
- Add apps quickstart tutorial by @jlowin in #3695
- Improve quickstart: pie chart, interactive row selection, screenshots by @jlowin in #3699
- Add sales dashboard and live system monitor examples, bump prefab-ui to 0.17 by @jlowin in #3696
- Add examples gallery page by @jlowin in #3705
- docs: note that custom routes are unauthenticated by @jlowin in #3706
- Remove hardcoded prefab-ui version from pinning warnings by @jlowin in #3708
Examples & Contrib 💡
Dependencies 📦
- Bump authlib from 1.6.6 to 1.6.7 in /examples/testing_demo in the uv group across 1 directory by @dependabot[bot] in #3390
- Bump actions/create-github-app-token from 2 to 3 by @dependabot[bot] in #3511
- chore(deps): bump pyasn1 from 0.6.2 to 0.6.3 in the uv group across 1 directory by @dependabot[bot] in #3538
- chore(deps): bump j178/prek-action from 1 to 2 by @dependabot[bot] in #3578
- chore(deps): bump requests from 2.32.5 to 2.33.0 in the uv group across 1 directory by @dependabot[bot] in #3638
- chore(deps): bump cryptography from 46.0.5 to 46.0.6 in /examples/testing_demo in the uv group across 1 directory by @dependabot[bot] in #3685
- chore(deps): bump actions/setup-node from 4 to 6 by @dependabot[bot] in #3691
New Contributors
- @Sumanshu-Nankana made their first contribution in #3380
- @ericrobinson-indeed made their first contribution in #3396
- @voidborne-d made their first contribution in #3465
- @mtthidoteu made their first contribution in #3473
- @saschabuehrle made their first contribution in #3456
- @hkc5 made their first contribution in #3468
- @nightcityblade made their first contribution in #3440
- @mhallo made their first contribution in #3510
- @radoshi made their first contribution in #3589
- @shigechika made their first contribution in #3603
- @pandego made their first contribution in #3632
- @4444J99 made their first contribution in #3662
- @mostafa6765 made their first contribution in #3677
Full Changelog: v3.1.0...v3.2.0