FastMCP 4 is GA. If you have an MCP server or client on fastmcp 3.x, you'll upgrade soon. Most of it is painless — FastMCP(...), @mcp.tool, and mcp.run(transport=...) are all unchanged. The parts that aren't painless are the parts that don't announce themselves. These are field notes on top of the official Upgrading from FastMCP 3 guide — the items that bit hardest when I moved one MCP server and two clients, in the order they bit. 1. pip install -U fastmcp can leave you half-broken FastMCP 4 is split into extras. The fastmcp package is now a thin meta-package that depends on fastmcp-slim[client,server]; fastmcp-slim carries the actual code, and its extras are client, server, mcp, anthropic, apps, azure, code-mode, gemini, openai. On a fresh install this is invisible — pip install fastmcp pulls fastmcp-slim[client,server] and everything works. I upgraded in place with pip install -U fastmcp over fastmcp 3.2.x, and pip did not re-resolve those base extras. Result: an importable shell with nothing in it. >>> import fastmcp >>> fastmcp.__file__ is None True >>> dir(fastmcp) [] >>> from fastmcp import Client ImportError: cannot import name 'Client' from 'fastmcp' (unknown location) This looks exactly like a broken release. It isn't — it's the 4.x extras split not getting re-resolved on an in-place upgrade. (FastMCP separately documents a different pip file-manifest issue on the 3.2 → 3.3 hop and notes uv is unaffected by that one; this is a distinct problem, and I hit it with pip -U — I didn't test uv pip install -U.) The fix, either way: python -m pip uninstall -y fastmcp fastmcp-slim python -m pip install fastmcp # or fastmcp==4.0.x to pin the version you tested Or just recreate the venv. It cost me a false-alarm debugging session — twice, because the symptom (ModuleNotFoundError on a submodule that's genuinely in the wheel) is so convincing. Also: fastmcp in 4.x no longer exposes __version__. If you assert on it anywhere, switch to importlib.metadata.version("fastmcp"). 2. httpx → httpx2: your except clauses go quiet FastMCP 4 dropped httpx for httpx2 (a next-gen fork) internally. So a FastMCP client call that used to raise httpx.ConnectError now raises httpx2.ConnectError. The trap: httpx is still transitively installed in most environments, so this keeps importing and type-checking: try: async with Client(StreamableHttpTransport(url)) as c: result = await c.call_tool("do_thing", args) except httpx.ConnectError: # never matches on FastMCP 4 ... It just silently stops catching. Grep for except httpx. and check whether each one wraps a FastMCP Client / transport call — if it does, migrate it to httpx2 (or catch FastMCP's own fastmcp.exceptions.ToolError, which is usually what you actually want). Your own direct httpx calls are unaffected as long as you keep httpx as a dependency. Same silent class, elsewhere: anything you hand into FastMCP that's built on httpx — a custom httpx_client_factory, an httpx.AsyncClient passed to a transport, an httpx.Auth — now needs to be httpx2. The official guide lists this right next to the except trap. One more downstream effect: TLS verification now uses the OS trust store via truststore (honouring SSL_CERT_FILE / SSL_CERT_DIR) instead of bundled certifi — corporate-CA setups may verify differently. HTTP log records also move from httpx / httpcore.* to httpx2 / httpcore2.* — update logging filters. 3. Client now defaults to mode="auto" In 4.x, Client(...) defaults to mode="auto" and negotiates the modern 2026-07-28 protocol era. That era is sessionless, and it changes runtime behaviour even though your code compiles fine: No on_initialize handshake — middleware / init hooks tied to it never run. ctx.set_state() doesn't persist to the next call. ctx.elicit() raises — the modern era has no server-initiated back-channel. If your client only does plain reads and writes (call_tool, read_resource), you're fine — that's the common case and it needs no change. If it relies on session state, an init hook, or elicitation, pin it back: Client(server, mode="legacy") StreamableHttpTransport also dropped sse_read_timeout= — pass timeout= on the Client instead. 4. Removed ctx methods These are gone and raise AttributeError: ctx.sample() ctx.sample_step() ctx.list_roots() If your server's job was to borrow the caller's model via ctx.sample() (or FastMCP(sampling_handler=...), also removed), you either call an LLM directly from the server now or stay on 3.x. ctx.elicit() still exists but requires a response_type argument and raises on modern connections — rewrite it as a guard tool that returns an "input required" result, or branch on ctx.request_context.protocol_version. Background tasks moved to an extension. @mcp.tool(task=True) no longer runs anything by itself — install fastmcp[tasks] and register mcp.add_extension(TasksExtension()), or startup raises. Drop task= from @mcp.resource / @mcp.prompt (tools only). 5. Version floors # hard requirement — resolution fails without it pydantic = ">=2.12" # only if you use the server's FastAPI extra starlette = ">=1.0.1" # → FastAPI >= 0.133.0 (first version admitting Starlette 1.x) Pin style unchanged: an app pins the exact version it tested (fastmcp==4.0.x); a library floors at fastmcp>=4.0.0 in its own dependencies and tests against the current release. 6. Import moves (quick reference) 3.x 4.x from fastmcp.tools.tool import Tool, ToolResult from fastmcp.tools import Tool, ToolResult from fastmcp.resources.resource import Resource from fastmcp.resources import Resource TextContent, Tool protocol types from fastmcp.types from mcp.types import ... (fastmcp.types now holds only FastMCP-defined types) mcp.as_proxy(sub) create_proxy(sub) from fastmcp.server mcp.import_server(sub) mcp.mount(sub) (live composition, not a snapshot) mcp.add_tool_transformation(name, cfg) mcp.add_transform(ToolTransform({name: cfg})) CachableToolResult (old typo) CacheableToolResult — no compat alias McpError(ErrorData(code=..., message=...)) McpError(code=..., message=...) SDK v2 also renamed model fields camelCase → snake_case (inputSchema → input_schema, isError → is_error). Old reads are auto-bridged and emit a FastMCPDeprecationWarning. The bridge is fastmcp.settings.mcp_camelcase_compat (env FASTMCP_MCP_CAMELCASE_COMPAT), bool, default true. Set it false once — that turns every remaining camelCase read into a hard error, so you can find and clear them before the bridge is removed. 7. New defaults from the settings page gofastmcp.com/more/settings lists every setting — each has a fastmcp.settings.<name> attribute and a FASTMCP_<NAME> environment variable. Three defaults changed behaviour in 4.x and don't get a line in the upgrade guide: telemetry_mode defaults to "native" — FastMCP 4 auto-instruments OpenTelemetry spans for MCP calls. If you don't want that, FASTMCP_TELEMETRY_MODE=off (or propagation_only). check_for_updates defaults to "stable" — the CLI checks PyPI for a newer FastMCP on startup. Set FASTMCP_CHECK_FOR_UPDATES=off in CI and containers. client_raise_first_exceptiongroup_error defaults to true — a client error surfaces as the first underlying exception, not the ExceptionGroup. That's why except ToolError: still works; if you were catching with except*, revisit. Also worth a look while you're there: stateless_http (new-transport-per-request, the sessionless/Cloud-Run knob), http_host_origin_protection (new, opt-in Host/ Origin validation for Streamable HTTP), and mask_error_details (default false — error text is passed through unless you raise an explicit ToolError / ResourceError / PromptError). Keep FASTMCP_DEPRECATION_WARNINGS=true (the default) for the whole migration — it's how you find the rest of this list in your own code. The good news: the minimal server barely changes If your server is only @mcp.tool-decorated functions plus mcp.run(transport="stdio") or mcp.run(transport="streamable-http"), there is no code change. The constructor, the decorator, and the transport call are all the same. You: bump pydantic (and FastAPI, if you use it), grep for except httpx. and migrate the ones around FastMCP calls, run your tests. That's it. What I actually changed Shape Change An MCP server — @mcp.tool + mcp.run("stdio" / "streamable-http") dependency floor only — zero code Two MCP clients — Client + StreamableHttpTransport + except ToolError dependency floor only — verified mode="auto" is fine for plain reads / writes No API changes in either. The real cost was the pip install -U false alarm (twice) and one test that hard-coded a version string in an assertion. The checklist [ ] Recreate the venv (or `pip3 uninstall fastmcp fastmcp-slim` first) — don't `-U` over 3.x [ ] pydantic >= 2.12 (+ FastAPI >= 0.133.0 if you use the server's FastAPI extra) [ ] grep `except httpx.` — migrate the ones wrapping FastMCP Client/transport calls to httpx2 [ ] grep `httpx_client_factory` / `httpx.AsyncClient` / `httpx.Auth` handed to FastMCP — same, → httpx2 [ ] grep `ctx.sample` / `ctx.sample_step` / `ctx.list_roots` — removed (and `FastMCP(sampling_handler=)`) [ ] grep `ctx.elicit` — needs response_type + fails on modern connections [ ] grep `@mcp.tool(task=True)` — now needs fastmcp[tasks] + TasksExtension() [ ] grep `Client(` — needs mode="legacy" only if it relies on session state / on_initialize / elicit [ ] grep `sse_read_timeout` — moved to Client(timeout=...) [ ] grep imports: fastmcp.tools.tool, fastmcp.resources.resource, fastmcp.types, mcp.as_proxy, import_server [ ] grep `fastmcp.__version__` — gone; use importlib.metadata.version("fastmcp") [ ] set `fastmcp.settings.mcp_camelcase_compat = False` once — clear the camelCase deprecation warnings [ ] CI: `FASTMCP_CHECK_FOR_UPDATES=off`; decide on `FASTMCP_TELEMETRY_MODE` (default is `native` = OTel on) [ ] keep `FASTMCP_DEPRECATION_WARNINGS=true` (default) for the whole migration [ ] run the test suite If you're just @mcp.tool + mcp.run, the whole list is "bump two floors and check your httpx catches." Everything else is for the code that does more. More reading Upgrading from FastMCP 3 — the official guide this checklist rides on. FastMCP settings — every fastmcp.settings.* / FASTMCP_* knob, including the §7 defaults. The MCP landscape — how MCP servers are built (the official SDKs, FastMCP) and how they run (stdio, Streamable HTTP), and where the 2026-07-28 sessionless shift sits.