{
  "id": 6170421,
  "title": "FastMCP 3 4 migration: the breaking changes that compile",
  "url": "https://urgent.news/2026/09/07/fastmcp-3-4-migration-the-breaking-changes-that-compile",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-09-07T20:31:26.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/wolfejam/fastmcp-3-4-migration-the-breaking-changes-that-compile-k6p"
  },
  "original_language": "en",
  "account": "FastMCP 4 has been released as a GA version, but upgrading from FastMCP 3.x can be challenging. Most changes are painless, such as the unchanged FastMCP(...), @mcp.tool, and mcp.run(transport=...) functions. However, there are breaking changes that can lead to issues when upgrading.\n\nOne issue is that the pip install -U fastmcp command can leave an in-place upgrade half-broken. FastMCP 4 is now split into extras, with the fastmcp package serving as a thin meta-package that depends on fastmcp-slim[client,server]. When upgrading in place using pip install -U fastmcp, the base extras are not re-resolved, resulting in an importable shell with nothing in it. To fix this, uninstall and reinstall fastmcp and fastmcp-slim, or recreate the virtual environment.\n\nAnother issue is the transition from httpx to httpx2. FastMCP 4 dropped httpx for httpx2 internally, so a FastMCP client call that previously raised httpx.ConnectError now raises httpx2.ConnectError. This can be resolved by migrating the except clauses to catch httpx2.ConnectError or catching FastMCP's own fastmcp.exceptions.ToolError instead. Direct httpx calls remain unaffected as long as httpx is kept as a dependency.\n\nFastMCP 4 also introduces a default mode= auto, which negotiates the modern protocol era. This changes runtime behavior, as no on_initialize handshake occurs, ctx.set_state() doesn't persist, and ctx.elicit() raises. If your client relies on session state, an init hook, or elicitation, pin it back to the legacy mode (Client(server, mode=legacy)).\n\nSome methods have been removed, including ctx.sample(), ctx.sample_step(), and ctx.list_roots(). If your server borrowed the caller's model via ctx.sample(), you'll need to call an LLM directly from the server or stay on FastMCP 3.x. Background tasks have also moved to an extension, so @mcp.tool(task=True) and @mcp.resource/task no longer work by themselves. You must install fastmcp[tasks] and register mcp.add_extension(TasksExtension()).\n\nFinally, FastMCP 4 has version floors for pydantic and starlette. If you use the server's FastAPI extra or starlette 1.x, you must pin the exact versions in your app's dependencies and tests.",
  "summary": "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…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}