FastMCP 3.3 ships fastmcp-slim, a new lightweight distribution that separates the client from the server stack. It also closes out a meaningful backlog of security hardening, observability improvements, and auth additions that accumulated through the 3.2 cycle.
fastmcp-slim
The full FastMCP package pulls in Starlette, Uvicorn, and the rest of the server machinery — necessary for running a server, but wasteful if you're writing a client, a script, or an agent that just needs to talk to MCP. fastmcp-slim is a dependency-light distribution that ships the client and transport layer without any of that.
The import namespace is unchanged:
from fastmcp import Client
async with Client("https://example.com/mcp") as client:
result = await client.call_tool("my_tool", {"arg": "value"})Install fastmcp-slim[client] anywhere you want FastMCP's client without the server footprint — CI environments, lightweight agents, library dependencies that shouldn't force Uvicorn on downstream users.
Security
The OAuth proxy received three hardening upgrades. Silent consent is now guarded against AS-in-the-middle attacks — a malicious authorization server can no longer silently approve a consent it wasn't meant to handle. Redirect URI allowlist matching now rejects dot-segment paths (/../, /./) that could otherwise bypass prefix checks. And ResponseCachingMiddleware partitions its cache by access token, closing a gap where different users could see each other's cached responses.
Auth
AzureB2CProvider adds first-class support for Azure AD B2C user flows. The OCI provider is fixed for 3.x installs. And OAuthProxy gains a public update_scopes() API for updating the proxy's required scopes after initialization — useful for servers that determine scope requirements at runtime.
Observability
OTEL instrumentation is now fully compliant with MCP semantic conventions. List operations (list_tools, list_resources, list_prompts, list_resource_templates) are instrumented, and delegate spans on proxy servers are enriched with backend attributes.
Thread Affinity
Sync tools run in a thread pool by default. If your tool holds thread-local state or is bound to a specific thread (UI frameworks, some database drivers), you can now opt out:
@mcp.tool(run_in_thread=False)
def my_tool() -> str:
...Under the Hood
Docket is now reentrant, and mounted servers enter their own lifespan — so a server with startup/shutdown logic works correctly when composed into a larger server. The FastMCP constructor accepts experimental_capabilities for passing raw capability flags. Tool errors accept a log_level parameter to control how they're logged. FormInput supports a default prefill value.
Fixes: ping loop now exits cleanly when a stream closes; sampling from background tasks works correctly; Windows startup no longer crashes on non-UTF-8 console output; blank query string values are preserved in OpenAPI routing; $defs introduced by ArgTransform are hoisted to the schema root; HTTP transports are terminated before lifespan shutdown.
13 new contributors this release.
What's Changed
New Features 🎉
Enhancements ✨
- Add default prefill to FormInput.collect_input by @jlowin in #3937
- OTEL: Fix attribute compliance with MCP semantic conventions by @strawgate in #3889
- OTEL: Instrument all MCP list operations and enrich delegate spans by @strawgate in #3890
- Improve real-world schema crash test: failure dump, cluster analysis, TypeErrors baseline ratchet by @jlowin in #3958
- feat: add AzureB2CProvider for Azure AD B2C user flows by @carlos-rian in #3995
- Add run_in_thread opt-out for sync tools with thread affinity by @jlowin in #4010
- Add missing return type annotation to getattr by @ZLeventer in #4026
- Add experimental_capabilities kwarg to FastMCP constructor by @jlowin in #4042
- Add log_level parameter to FastMCP errors by @daniel-tsiang in #4036
- Bump pydocket to 0.20.0 by @chrisguidry in #4031
- enh: Add public API for updating OAuthProxy scopes after initialization by @taylorwilsdon in #4091
- Refine fastmcp-slim packaging by @jlowin in #4125
Security 🔒
- Harden OAuth Proxy silent consent against AS-in-the-middle by @jlowin in #3960
- Reject dot-segments in redirect URI allowlist matching by @jlowin in #3963
- Bump deps with open dependabot alerts by @jlowin in #3965
- Partition ResponseCachingMiddleware cache by access token by @jlowin in #4041
Fixes 🐞
- fix: reject self-mount to prevent infinite recursion by @strawgate in #3925
- fix: ProxyTool crashes on non-TextContent error responses by @strawgate in #3926
- fix: _prune_param and _convert_nullable_field mutate input schemas by @strawgate in #3927
- fix: narrow OpenAI audio format dict to Literal for ty by @jlowin in #3936
- fix: allow hyphens in resource template parameter names by @strawgate in #3929
- fix: OpenAPI request director sends multipart and form-urlencoded as JSON by @strawgate in #3932
- Fix raise_on_error handling for tool tasks by @gnanirahulnutakki in #3946
- fix: FileSystemProvider reload race condition by @strawgate in #3938
- fix tests that relied on task=True returning error results by @jlowin in #3954
- Restore task snapshot via a worker-level dependency by @chrisguidry in #3945
- Forward backend capabilities in ProxyProvider by @jlowin in #3956
- Allow upstream client_id to be used directly without DCR by @jlowin in #3957
- Graceful fallback for unsupported regex patterns in json_schema_to_type by @jlowin in #3959
- Revert "Forward backend capabilities in ProxyProvider (#3956)" by @jlowin in #3964
- fix: skip stdio subprocess test on Windows CI by @jlowin in #3966
- fix: bound _refresh_locks with LRU eviction to prevent memory leak by @jlowin in #3968
- fix: handle circular JSON Pointer $ref in dereference_refs by @lawrence3699 in #3896
- fix: honor upstream refresh token expiry in OAuthProxy by @jlowin in #3990
- fix: narrow _token_validator with isinstance for ty in AzureProvider.from_b2c by @jlowin in #4007
- fix: cancel orphaned session_task when Client._disconnect times out by @jlowin in #4011
- fix: preserve @tool metadata in from_function by @lawrence3699 in #4072
- fix(openapi): keep blank values in parse_qs (refs #4056) by @MukundaKatta in #4076
- Fix #4056: keep blank query values, add token bucket regression test by @MukundaKatta in #4069
- fix(ping): exit ping loop cleanly when session stream is closed by @ashwin153 in #4087
- Fix sampling from background tasks by @cuyua9 in #4068
- Make Docket reentrant; mounted servers enter their own lifespan by @jlowin in #4095
- fix(tool_transform): hoist $defs to schema root when ArgTransform introduces them by @SarthakB11 in #4101
- fix(auth): silence authlib.jose DeprecationWarning at JWT import by @SarthakB11 in #4100
- fix: don't cache import map in dev apps bundle by @jlowin in #4106
- #4084 [Issues] Windows startup crash due to UnicodeDecodeError when l… by @doneman536 in #4092
- fix: drop exc_info for expected tool failures, remove unreachable ValidationError by @sergeykad in #4029
- fix: cli option --no-banner is NOT passed to cli but server-spec in-correctly when cli --reload option is specified. by @itaru2622 in #4083
- Fix None backend_* span attributes on un-renamed proxy components by @ringerc in #4109
- Fix OCI Provider issue in 3.x version. Add OCI auth provider example … by @kiranthakkar in #4116
- fix(http): terminate active streamable-HTTP transports before lifespan shutdown by @SarthakB11 in #4118
Docs 📚
- Restructure docs navigation by @jlowin in #3951
- docs: standardize ToolAnnotations examples by @gnanirahulnutakki in #3952
- Be constructively skeptical of bot reviews on own PRs by @jlowin in #3971
- Add UTM params to Horizon docs links by @aaazzam in #4018
- Add a sandboxed-agents deployment guide by @strawgate in #4027
- docs: add best practices for custom telemetry spans by @MukundaKatta in #4001
- Refresh landing page copy by @jlowin in #4043
- Refresh landing page copy by @jlowin in #4047
- Add UTM tracking to Horizon links by @jlowin in #4064
- docs(integrations): add Pydantic AI FastMCP toolset guide by @MukundaKatta in #4070
- docs: fix broken links in Pydantic AI guide by @jlowin in #4094
Dependencies 📦
- chore(deps-dev): bump pydantic-monty from 0.0.11 to 0.0.12 by @dependabot[bot] in #3940
- chore(deps-dev): bump pydantic-monty from 0.0.14 to 0.0.16 by @dependabot[bot] in #3984
Other Changes 🦾
New Contributors
- @gnanirahulnutakki made their first contribution in #3946
- @lawrence3699 made their first contribution in #3896
- @carlos-rian made their first contribution in #3995
- @ZLeventer made their first contribution in #4026
- @MukundaKatta made their first contribution in #4001
- @daniel-tsiang made their first contribution in #4036
- @ashwin153 made their first contribution in #4087
- @cuyua9 made their first contribution in #4068
- @taylorwilsdon made their first contribution in #4091
- @SarthakB11 made their first contribution in #4101
- @doneman536 made their first contribution in #4092
- @sergeykad made their first contribution in #4029
- @ringerc made their first contribution in #4109
Full Changelog: v3.2.4...v3.3.0