FastMCP 4.1.0 is a maintenance release at heart: a broad sweep of fixes across OpenAPI, proxies, auth, tasks, and the CLI, plus full Python 3.15 support. It doesn't change how most servers behave. A few security fixes do tighten inputs that FastMCP previously accepted, though, and our versioning policy keeps breaking changes out of patch releases, so this ships as a point-one release.
Check these before upgrading:
- MultiAuth now qualifies client IDs by source, so existing MultiAuth sessions and tasks start new ownership scopes. Finish pending tasks before upgrading.
- Regex tool search uses Pydantic's regex engine, which doesn't support lookarounds or backreferences.
- Skill files must resolve inside their configured directories.
- OpenAPI path parameters reject
.and..segments. - HTTP sessions now expire after the MCP SDK's 30-minute idle default instead of never.
- CodeMode requires Monty 1.1, which renames
max_duration_secstomax_feed_duration_secs.
Thanks to our 48 contributors, including 21 first-time contributors!
What's Changed
Breaking Changes ⚠️
- fix(http): preserve the SDK's default session idle timeout by @asts-top in #5229
- Restore full Python 3.15 support by @Kludex in #5558
Enhancements ✨
- enhancement: Configure token introspection audiences by @jlowin and @sajdakabir in #5461
- fix: Make proxy HTTP header forwarding configurable by @jlowin in #5462
- enhancement: Configure upload retention in memory by @jlowin and @warlocksmurf in #5463
- Clarify maintainer review decisions by @jlowin in #5466
- feat: persist encrypted CLI OAuth tokens by @andreivince in #5405
- feat(auth): add TokenCache support to GoogleTokenVerifier and GoogleProvider by @jerive in #5366
- Add gitleaks secret scanning to prek by @strawgate in #5355
- Keep review design decisions with maintainers by @jlowin in #5468
Security 🔒
- fix!: Qualify MultiAuth client identities by @jlowin in #5455
- fix!: Validate OpenAPI path parameter segments by @jlowin in #5457
- fix!: Resolve skill files within their configured directories by @jlowin and @GEONWOOHAN in #5459
- fix!: Use Pydantic’s regex engine for tool search by @jlowin in #5467
Fixes 🐞
- fix: Share JWKS refreshes across key lookups by @jlowin in #5456
- fix: Reuse shared OpenAPI composition members by @jlowin in #5460
- Resolve app tool names against the transformed catalog by @jlowin in #5464
- fix: keep generator iteration inside dependency lifetime by @mikamikasuki in #5438
- fix(tools): consume synchronous generators within dispatched invocation by @HootRock in #5376
- fix(tools): reject results returned after timeout deadlines by @HootRock in #5379
- Fix #5368: Tool.from_tool: a transform_fn that declares ctx: Context fails on eve by @nandanadileep and @chrikrah in #5371
- fix(auth): preserve AuthKit issuer identity by @mikamikasuki and @paddynatte in #5452
- fix(auth): advertise resource_metadata only when a route serves it by @alex-feel in #5363
- fix: decode escaped JSON Pointer tokens during hydration by @HuaTNA in #5407
- fix: read MCP config files as UTF-8 in the install commands by @ch-z-hc in #5359
- Add regression coverage for root-context schema caching by @hulkbig in #5397
- Keep the original cause when ErrorHandlingMiddleware re-raises an error unchanged by @asasemahmed in #5344
- Resolve ASGIServer.http_client relative URLs against the server origin by @asasemahmed in #5341
- Raise startup failures from run_server_async instead of hanging by @asasemahmed in #5340
- Restrict Windows CLI state access without requiring SeSecurityPrivilege by @asasemahmed in #5312
- Advertise the same list_changed capabilities over stdio as other transports by @asasemahmed in #5311
- Validate responses to dict and nested-list elicitation choices by @asasemahmed in #5343
- Inline enums shared by several fields in elicitation schemas by @asasemahmed in #5342
- Honor the SDK ElicitResult returned by an elicitation handler by @asasemahmed in #5339
- Look up call_tool output schemas across all tools/list pages by @asasemahmed in #5338
- Deduplicate versions in PromptsAsTools and ResourcesAsTools list tools by @asasemahmed in #5331
- Map File text formats to canonical text MIME types by @asasemahmed in #5334
- Keep generated OpenAPI component names unique when a suffixed name is taken by @asasemahmed in #5337
- Keep is_error when ResponseLimitingMiddleware truncates a result by @asasemahmed in #5309
- Read +json OpenAPI resource responses as JSON text by @asasemahmed in #5310
- Preserve provider challenge defaults through MultiAuth by @taylorwilsdon in #5454
- Write generate-cli output as UTF-8 by @asasemahmed and @ch-z-hc in #5335
- Strip internal visibility meta from prompt results and static resources by @asasemahmed in #5330
- Register browser OAuth clients as public clients by @goran-revops in #5231
- fix(client): preserve unique JSON arrays by @lzyruc in #5270
- Keep filesystem reloads serialized during cancellation by @sherxlg-gif in #5184
- Treat upstream HTTP errors from OpenAPI tools as HTTP status errors by @asasemahmed in #5308
- chore(ty): pin the checked platform to the one CI uses (#5302) by @sclfcz in #5303
- fix(tools): take partial tool names and docstrings from the wrapped function by @BlueX888 and @Ares16x16 in #5269
- fix(tools): keep structured content for object schemas without explicit type by @BlueX888 in #5271
- fix: return current tool schemas from cached BM25 searches by @baba9811 in #5153
- fix(azure): send prompt=select_account to Entra instead of the consent URL by @mayankkumar-cmd in #5162
- fix(auth): preserve upstream refresh error semantics by @77cjj in #5011
- Clean up run_server_async when startup exits early by @syf2211 and @asasemahmed in #5469
- Pass resource security policy through FileSystemProvider discovery by @asasemahmed in #5498
- Forward annotations on proxied resources and resource templates by @asasemahmed in #5499
- Forward the resources/read result meta through proxies by @asasemahmed in #5500
- Fix proxy dropping log messages whose data is not a msg/extra dict by @asasemahmed in #5501
- Forward stop_sequences in the proxy's sampling relay by @asasemahmed in #5502
- Treat non-Bearer Authorization schemes as missing authentication by @asasemahmed in #5503
- Reject positional-only parameters in prompts and resource templates by @asasemahmed in #5505
- Carry domain and prefers_border onto the Prefab renderer resource by @asasemahmed in #5611
- Keep GenerativeUI CSP on the renderer resource only by @asasemahmed in #5612
- Keep OpenAPI path segments that contain a colon by @asasemahmed in #5613
- Print install mcp-json and inspect JSON without Rich wrapping by @asasemahmed in #5615
- Coerce optional parameters in fastmcp call by their non-null type by @asasemahmed in #5616
- Return the existing component when an ignored duplicate is added by @asasemahmed in #5617
- Keep a config client's servers connected when a new() clone connects by @asasemahmed in #5618
- Strip discriminator under properties named default, const, enum or examples by @asasemahmed in #5619
- Keep a finished task's status when it is cancelled by @asasemahmed in #5621
- Forward --skip-source to the uv run subprocess in fastmcp run by @asasemahmed in #5623
- Send numeric and boolean OpenAPI raw bodies as text by @asasemahmed in #5624
- Map Anthropic stop_sequence to the MCP stopSequence stop reason by @asasemahmed in #5625
- Escape the server spec and name in the generate-cli header docstring by @asasemahmed in #5626
- Fix context docs: request_context.meta is a dict, not an attribute object by @asasemahmed in #5631
- Validate strict tool input in JSON mode by @asasemahmed in #5504
- Raise when a required OpenAPI path parameter is missing by @asasemahmed in #5614
- Omit writeOnly properties from OpenAPI tool output schemas by @asasemahmed in #5336
- fix(tasks): keep stored arguments alive with the other per-task keys by @sclfcz in #5144
- fix(auth): omit scopes from generic authorization code exchange by @liang0417 and @kiranthakkar in #5183
- Reject missing and unexpected arguments at task submission by @asasemahmed in #5622
- fix(auth): preserve verifier-defined challenge scopes through wrappers by @mikamikasuki in #5478
- fix(server): VersionFilter get_tool/get_resource/get_prompt respect include_unversioned=False by @Ashfaqbs and @Purin1410 in #4949
- Stop asserting that the GitHub remote server lacks structured content by @jlowin in #5634
- Skip skills whose SKILL.md is not valid UTF-8 during directory discovery by @asasemahmed in #5497
- Fix tool body validation errors at the MCP boundary by @qingshungLI and @alastair in #5485
- Detect client extensions without requiring clientInfo by @alex-feel in #5196
- Propagate child cancellation from gather by @dennisimoo and @lokesh0186 in #5161
- fix(resources): separate URI fragments from query values by @Harsh23Kashyap in #5132
- fix: preserve input requests in resource and prompt tools by @HootRock and @andreivince in #5476
- fix(docstrings): don't leak Returns/Raises into description for parameterless functions by @Ashfaqbs and @pvliesdonk in #5067
- fix(client): hydrate formatted scalars and positional array items by @FanouZeng-TT, @MarcHuang168, @lzyruc, and @onk3sh in #5203
Docs 📚
- docs: credit contributors in v4.0.11 changelog entries by @jlowin in #5449
- Clarify CodeMode execution context by @jlowin in #5458
- docs: explain Inspector entrypoint initialization by @andreivince and @nick-youngblut in #5404
- Docs: show post-call inspection of tool text results by @Amazinghorseli and @roli-lpci in #5385
- Document FastMCP Client use cases by @andreivince in #5482
- docs: show tests for MCP mounted in FastAPI by @andreivince and @DeoLeung in #5471
- Document OpenTelemetry log export by @andreivince in #5483
- docs: explain OpenAPI client timeouts by @andreivince and @dimitribarbot in #5470
- Clarify Python projects and Cursor workspaces in install docs by @andreivince and @nick-youngblut in #5475
- Remove comments from fastmcp.json docs examples by @asasemahmed in #5627
- Fix mcp-json Advanced Configuration example output by @asasemahmed in #5628
- Remove FASTMCP_SERVER_DEPENDENCIES from the settings reference by @asasemahmed in #5629
- Fix versioning docs: get_tool takes a VersionSpec and returns None for a missing version by @asasemahmed in #5630
- Fix authorization docs: AccessToken field types and built-in check count by @asasemahmed in #5632
Examples & Contrib 💡
- Don't evaluate properties when registering MCPMixin methods by @asasemahmed in #5620
Dependencies 📦
- chore(deps): bump the uv group across 2 directories with 2 updates by @dependabot[bot] in #5393
New Contributors
- @mikamikasuki made their first contribution in #5438
- @HootRock made their first contribution in #5376
- @alex-feel made their first contribution in #5363
- @HuaTNA made their first contribution in #5407
- @andreivince made their first contribution in #5404
- @ch-z-hc made their first contribution in #5359
- @Amazinghorseli made their first contribution in #5385
- @hulkbig made their first contribution in #5397
- @jerive made their first contribution in #5366
- @goran-revops made their first contribution in #5231
- @lzyruc made their first contribution in #5270
- @sherxlg-gif made their first contribution in #5184
- @baba9811 made their first contribution in #5153
- @mayankkumar-cmd made their first contribution in #5162
- @77cjj made their first contribution in #5011
- @liang0417 made their first contribution in #5183
- @Ashfaqbs made their first contribution in #4949
- @qingshungLI made their first contribution in #5485
- @dennisimoo made their first contribution in #5161
- @FanouZeng-TT made their first contribution in #5203
- @GEONWOOHAN made their first contribution in #5459
Full Changelog: v4.0.11...v4.1.0