github PrefectHQ/fastmcp v3.2.0
v3.2.0: Show Don't Tool

one hour ago

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 app

Built-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 ⚠️

  • Route app tool calls via ___-prefixed names by @jlowin in #3667

Enhancements ✨

  • feat: add --config-path flag 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 verify parameter 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 McpError in 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 list and fastmcp call by @jlowin in #3409
  • fix: propagate version to 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=0 as 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 encoding parameter to FileResource by @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 💡

  • Block recursive self-invocation in BulkToolCaller by @jlowin in #3433

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

Full Changelog: v3.1.0...v3.2.0

Don't miss a new fastmcp release

NewReleases is sending notifications on new releases.