Skip to content

docs: no guidance for supporting SDK 1.x and 2.x at the same time #3309

Description

@haiiibin

Problem

docs/migration.md is a one-way guide: it tells you what to change once you move to 2.x. It has no guidance for the case where you cannot hard-cut, which is the situation of anyone maintaining a published MCP server or library while the ecosystem is split across both majors.

Concretely: the day 2.0.0 shipped, fresh installs of our published servers started resolving mcp==2.0.0 and crashed at import (ModuleNotFoundError: No module named 'mcp.server.fastmcp'), while existing users were still on 1.x. The realistic fix for a package author in that window is not "migrate", it is "support both majors for a transition period". I could not find a recipe for that anywhere in docs/.

What we ended up doing

Running in production since late July on two registry-listed servers (data-profiler-mcp, acb-tax-mcp):

try:
    # MCP SDK 2.x: FastMCP was renamed to MCPServer and the module moved.
    from mcp.server.mcpserver import MCPServer as FastMCP
except ImportError:
    # MCP SDK 1.x keeps the original path.
    from mcp.server.fastmcp import FastMCP

together with:

  • a dependency pin of mcp>=1.2.0,<3 so installs may resolve either major, and
  • a dedicated CI job that force-installs "mcp>=1.2.0,<2" and re-runs the test suite, so the 1.x fallback path stays tested while the main matrix resolves 2.x.

For code that stays on the FastMCP-level API surface (constructor, @mcp.tool(), run()), this has been sufficient: both lines pass the same test suite unchanged.

Proposal

A short section, roughly "Supporting 1.x and 2.x during the transition", covering:

  1. the import shim above,
  2. dependency-pin guidance (mcp>=1.2.0,<3, and why an upper bound of <2 alone strands your users),
  3. testing both lines in CI (one extra pinned job is enough), and
  4. a sentence on when to drop the 1.x path.

I would keep it to roughly 40 to 60 lines.

Scoping questions before I write anything

  • Trim the migration guide to genuine v1-to-v2 breaking changes #3183 is trimming migration.md down to genuine breaking changes, so this may not belong there. Would you rather see it as a short section in migration.md, or as a separate small docs page (e.g. docs/compatibility.md)?
  • If the answer is "we deliberately do not want to encourage dual-major support", that is a fair position; happy to close.

If maintainers think it is worth having, I will send the PR.

Activity

  1. MilkyWay008 commented on Aug 14, 2026

    @MilkyWay008

    Your shim + pin setup is basically the standard dual-support pattern and it's solid — the CI job force-installing 1.x is a nice touch. One gap to keep in mind: it only covers the FastMCP-level surface, so anything else that moved between majors (client config, streamable http, low-level server bits) still breaks fresh installs on 2.0.0. For placement I'd go a separate compat page linked from migration.md rather than stuffing it into a doc that keeps getting trimmed — a one-way migration guide plus a running compat note reads cleaner.

  2. haiiibin commented on Aug 14, 2026

    @haiiibin
    Author

    Thanks, that matches my read. A separate compat page linked from migration.md keeps the migration guide one-way and trimmed, so I would scope it that way: a short docs/compatibility.md with the shim, the pin guidance, and the CI job.

    Good point on surface coverage. The shim only protects FastMCP-level code, so the page should say that plainly and list the other import moves (client config, streamable HTTP, low-level server) with pointers to their migration entries rather than pretending one shim covers everything.

    I will hold off on the PR until a maintainer weighs in on whether they want the page at all.

  3. maxisbey commented on Aug 17, 2026

    @maxisbey
    Contributor

    Hey! Yea unfortunately that's the state of the Python ecosystem, it's pretty common to not have a version cap and it results in breakages when a new major version releases.

    As for adding backwards compat shims I decided against doing that for the v2 SDK to avoid having to essentially support two SDK versions in a single SDK. It would have been near double the number of public interfaces in some cases and very gross/hacky to get working.

    The best option for those wanting to support both is yes writing shims yourself and trying to import one or the other. This is what the Anthropic SDK for example: anthropics/anthropic-sdk-python@8b327c3

  4. pragati243 commented on Aug 18, 2026

    @pragati243

    Thanks for the clarification. I'd be interested in contributing the documentation for this use case.

    Based on the discussion, my proposed approach would be:

    • Add a small docs/compatibility.md page for package authors supporting both SDK 1.x and 2.x during a transition.
    • Document the explicit import shim pattern rather than adding backward-compatibility shims to the SDK itself.
    • Clearly scope the guidance to the FastMCP-level API surface and link to the migration guide for other breaking changes.
    • Include dependency and CI testing guidance.

    I'll prepare the documentation locally, but before opening a PR I'd appreciate maintainer confirmation that this documentation page is something the project wants to include and where it should live in the docs navigation.

  5. haiiibin commented on Aug 18, 2026

    @haiiibin
    Author

    @maxisbey Thanks, that settles the approach, and the anthropic-sdk-python commit is a better precedent than anything I had.

    Since the SDK will not carry shims itself, I went ahead and wrote the page on a branch so there is something concrete to evaluate: main...haiiibin:docs-dual-version-support. One new docs/compatibility.md (~60 lines), a nav entry, and one sentence linking it from the migration guide's "Not ready to migrate yet?" note. It covers:

    • the import shim, and a warning about the constructor's positional trap (FastMCP("name", "instructions...") sets instructions on v1 but silently sets title on v2)
    • why both bounds of mcp>=1.2.0,<3 are deliberate
    • a CI job that forces the v1 line so the fallback path stays tested instead of becoming dead code
    • an explicit scope note that the shim only covers the FastMCP rename, deferring everything else to the migration guide
    • when and how to retire the v1 path

    Everything on the page is lifted from two registry-listed servers that have run this pattern in production since late July, with both SDK lines green in CI.

    The contribution policy asks for the linked issue to be assigned before a PR from outside the team, so if you want this page: assign this issue to me and I will open the PR right away. @pragati243 you offered to help here as well, so review on the PR would be very welcome once it is up, and if you spot gaps I am glad to fold in additions.

  6. pragati243 commented on Aug 18, 2026

    @pragati243

    Thanks for putting together a concrete implementation. I’d be happy to review the PR once it’s open and test the examples against both SDK versions. I’ll take a close look at the compatibility scope and the v1/v2 behavior, and I’ll share any concrete gaps or findings.

  7. added
    v1Affects the v1.x maintenance line
    v2Affects the v2 line (2.x on main)
    and removed
    v1Affects the v1.x maintenance line
    on Aug 18, 2026
  8. nortesoftware commented on Sep 14, 2026

    @nortesoftware

    Numbers for what you described. On 2026-09-11 I took a seeded random sample of 180 PyPI servers out of the 3,497 stdio PyPI packages listed in the official MCP registry, installed each one into a clean venv with uv pip install name==version (the version the registry lists, Python 3.13, nothing pinned by me), started the console script and sent initialize.

    175 installed. 53 died at import with the 2.x message, No module named 'mcp.server.fastmcp'. Of the other 122, 65 completed the handshake and 57 failed for other reasons, judging from stderr: other exceptions at startup, a usage banner because the registry entry omits a subcommand, a missing module, a credential check. That is 30% of the cells, but it is not 30% of the ecosystem. 21 of the 53 belong to one publisher, io.github.CSOAI-ORG, which has 353 PyPI servers in the registry; all 21 I sampled failed the same way. Counting publishers instead of packages, 34 of 143 had at least one broken server, 23.8%. The cluster-robust 95% interval for the per-package rate is 15.9 to 44.7%, design effect 4.5. So between a fifth and a third of registry-listed PyPI servers don't start on a fresh install, 45 days after 2.0.0.

    The registry shows all of them as status: active. That is the default value at publish time. The registry doesn't claim they run.

    What I don't know: whether these servers work under mcp<2 (I only ran fresh installs, which resolve to 2.x), how many of the 175 pin mcp at all, and whether the authors have noticed. On the npm side of the same sample I didn't see a comparable pattern, five module errors with different causes.

    The per-package list is 53 lines and mostly one publisher; if it's useful here I'll add it.

  9. haiiibin commented on Sep 14, 2026

    @haiiibin
    Author

    @nortesoftware thank you, that turns an anecdote into a measurement. A fifth to a third of registry-listed PyPI servers failing a fresh install 45 days after 2.0.0, with a cluster-robust interval, is a much better opening line for this page than my two packages were.

    Two updates since the last round:

    @maxisbey the contribution policy needs this issue assigned before I can open the PR. If you want the page, assign it to me and it is up within the hour; if you would rather not carry it, say so and I will close this out. Either answer is fine, I just do not want to leave it ambiguous.

  10. nortesoftware commented on Sep 14, 2026

    @nortesoftware

    Follow-up on the "how many pin mcp at all" part, plus one correction to my comment above.

    I read the Requires-Dist of the same 175 packages from PyPI, no reinstall. 119 declare mcp directly. 71 of those give a floor and no ceiling (>=1.0.0, >=1.2, >=1.28.0 and so on). Not one of the 71 started: 51 hit the FastMCP import error, 20 failed for other reasons. 26 bound it below 2 (<2, one ==1.x); 22 of them started and none hit the error. 17 already require 2.x, 5 declare mcp with no constraint at all and started fine on 2.x, and 56 don't depend on mcp directly (24 go through fastmcp). So 15% of the installed servers pinned below 2. The whole failure sits in the 41% of installed servers that set a floor and forgot the ceiling, which is the situation your issue describes.

    The correction: I wrote that 21 of the 53 failures came from one publisher and that all 21 failed the same way. Going cell by cell for this, it is 19 of the 53. That publisher had 21 packages in my sample and none of them started, but two died with different errors. The per-publisher count (34 of 143) and the interval don't change, they were computed on the per-cell flag. Without that publisher the rate is 34 of 154, 22.1%, not the 20.8% I had.

  11. haiiibin commented on Sep 17, 2026

    @haiiibin
    Author

    @nortesoftware the pin breakdown is the part the page most needed: 71 floor-without-ceiling packages and none of them started, 26 capped below 2 and none of them hit the error. I added one sentence citing that under the <3 cap bullet, so the pin section now argues from the sample instead of from principle (diff). Noted the 19 of 53 correction; the page only cites the overall range, so nothing there changes.

  12. nortesoftware commented on Sep 26, 2026

    @nortesoftware

    Corrections to my two comments above.

    52 of the 175 died with the 2.x message, not 53. darwin-memo was counted because its error text names mcp.server.fastmcp, but what it hit is No module named 'mcp': mcp is an optional extra of that package and was not installed. The count is of packages whose error is the rename itself, not any import failure that mentions mcp, and no other cell carries that message. That is 29.7% of the cells; of the other 123, 65 completed the handshake and 58 failed for other reasons.

    The rename is not all that 2.0 broke. Ten of those 58, all among the 71 that set a floor and no ceiling, stop at a list_tools() decorator with AttributeError: 'Server' object has no attribute 'list_tools': 2.0 removed that decorator from the low-level Server, which now takes on_list_tools= in its constructor. So at least 62 of 175, 35.4%, are broken by 2.0. I did not examine the other 48 PyPI failures for a 2.0 cause.

    The publisher key split io.github.CSOAI-ORG in two: 18 of its packages under its GitHub owner, 3 under its registry namespace. Counted as one publisher, 32 of 142 publishers had at least one server broken by the rename, 22.5%, and the cluster-robust 95% interval for the per-package rate is 14.1 to 45.3%, design effect 5.3. 19 of the 52 are that publisher's; without it the rate is 33 of 154, 21.4%. The range in my first comment, between a fifth and a third, is the rename alone.

    13 packages require mcp 2.x, not 17. The other four declare >=1.x,<3, which accepts both majors, and all four started.

    51 of the 52 are among the 71 that set a floor and no ceiling, not all of them. The other one, vs-filesystem-mcp-server, gets mcp through fastmcp.

  13. maxisbey commented on Oct 1, 2026

    @maxisbey
    Contributor

    Sorry for the slow answer here, and thanks for making it easy by saying either answer works: it's a no. We won't be adding a dual-version page.

    We don't want to encourage carrying both majors. 1.x is feature-frozen: it stops at the 2025-11-25 spec and only gets critical bug fixes and security fixes, while the 2026-07-28 spec and everything after it are 2.x only. A shim keeps a package running against an SDK that won't gain anything, and doubles what the package has to test. Our guidance for package authors is to move to 2.x and depend on mcp>=2,<3. The migration guide lists every change, and since #3388 the old import error points straight at it.

    AI Disclaimer

  14. haiiibin commented on Oct 9, 2026

    @haiiibin
    Author

    Thanks for the clear answer and the reasoning behind it. Pointing the old import error at the migration guide (#3388) fixes the install failures where they happen, which a page would not have. @nortesoftware, thanks for the sample and the corrections above; the list_tools removal was the part I had not seen.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions