MCP 2.0 Breaking Changes Every QA Engineer Must Know
Table of Contents
- What Shipped and When: Two Shifts at Once
- The Version Split Nobody Explains Clearly
- Breaking Change One: FastMCP Is Now MCPServer
- Breaking Change Two: A First-Class Client Replaces the Old Layering
- Breaking Change Three: The Server Can No Longer Call the Client
- Breaking Change Four: Types Moved, Names Changed, Dependencies Shuffled
- How This Changes MCP Server Testing for QA
- A Migration Roadmap That Does Not Blow Up Your Sprint
- Traps That Break Silently, With No Import Error
- India Context: MCP Skills Are Showing Up in SDET Job Descriptions
- Key Takeaways
- FAQ
On July 28, 2026, the MCP Python SDK shipped version 2.0.0 as a stable release, and pip install mcp now installs 2.x by default. If your team built test tooling, AI agents, or browser automation on top of the Model Context Protocol in the last year, some of it broke on the next dependency update, and a chunk of it broke without an import error. I have spent the last few weeks migrating our own Playwright and agent tooling at Tekion and across the Testing Academy stack, and this is the field guide I wish I had on day one. These MCP 2.0 breaking changes are not cosmetic renames; they sit on top of a protocol revision that removes the connection handshake entirely.
Contents
What Shipped and When: Two Shifts at Once
Two things happened at the same time, and most blog posts blur them together. First, the Python SDK was rebuilt: a new dispatcher engine under both the client and the server, a first-class Client class, and a set of renames that a v1 codebase hits on its first import. Second, the protocol itself moved, from the 2025-11-25 revision to the 2026-07-28 revision. The new revision removes the connection handshake, removes the session concept, and removes every server-initiated request. That is a bigger deal than the rename, because it changes what your tests can even assert.
The stable release notes are short and worth reading. The v2.0.0 release states plainly that v1.x is now in maintenance mode and will only receive security fixes. If your project is not ready to migrate, the official guidance is to pin an upper bound: mcp>=1.28,<2. The install line changed too: pip install "mcp[cli]" or uv add "mcp[cli]". Python 3.10 is now the floor.
One detail I like: v2 serves both protocol eras from the same MCPServer. It speaks the 2026-07-28 revision (stateless requests with no handshake, server/discover, subscriptions/listen, multi-round-trip requests) and still serves every 2025-era client over Streamable HTTP and stdio with nothing to configure. So a migration does not force you to upgrade every client in your fleet on the same day. That one fact saved us from a big-bang release.
The Version Split Nobody Explains Clearly
Here is the part that confuses people, and I get why. If you check the npm registry today, the main package @modelcontextprotocol/sdk is still on 1.30.0, not 2.0.0. The Python SDK is on 2.0.0. The TypeScript server package @modelcontextprotocol/server hit 2.0.0 on July 27, 2026, a day before the Python release. So you have npm SDK at 1.30.0, TypeScript server at 2.0.0, and Python at 2.0.0, all describing the same protocol revision. Three version lines, one spec.
Why does this matter for a QA lead? Because your test harness probably pins one SDK version while the tool it tests pins another. I have seen teams where the agent under test runs the Python server at 2.0.0 while the test harness drives it through a TypeScript client at 1.30.0, then spend a day chasing a handshake mismatch that was actually a version mismatch. The npm SDK has been downloaded 195,971,908 times in the last month alone, so this is not a niche tool. A lot of teams are running both ecosystems in the same pipeline.
The TypeScript side also moved its schema source modules into @modelcontextprotocol/core and now versions the four packages together. The practical effect: an app importing more than one of the packages evaluates a single shared schema graph, which fixes a class of subtle interop bugs where two copies of a schema disagreed. For testers, that means fewer false positives from version-skew between client and server packages.
Breaking Change One: FastMCP Is Now MCPServer
This is the first thing every v1 server hits, because the old import path is gone rather than deprecated. FastMCP is now MCPServer, and it moved modules. The official before-and-after from the What’s new in v2 doc is one line:
from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP
mcp = MCPServer("Demo") # v1: FastMCP("Demo")
The decorator API is unchanged, which is the good news. Your @mcp.tool() and @mcp.resource() handlers mostly survive. What changes around them is subtle. The default server name changed from FastMCP to mcp-server, so if your tests assert against the server name in a discover response, that assertion now fails. The MCPServer constructor also added title, description, and version as positional parameters, and the mount_path parameter was removed entirely. If you were mounting your test fixtures on a path, that code path is gone.
Breaking Change Two: A First-Class Client Replaces the Old Layering
In v1, talking to an MCP server meant wiring a transport, a ClientSession, and a manual initialize() call. That layering is gone. One Client object replaces all of it, and it can connect to a URL, a stdio subprocess, a custom transport, or straight to a server object in memory. That last option is the one I care about most for testing, because it means you can exercise a server in-process without standing up a socket or a subprocess.
from mcp import Client
from mcp.server import MCPServer
mcp = MCPServer("Bookshop", instructions="Search the catalog before recommending a book.")
@mcp.tool()
def search_books(query: str) -> str:
"""Search the catalog by title or author."""
return f"Found 3 books matching {query!r}."
async def main() -> None:
async with Client(mcp) as client:
print(client.server_info)
print(client.server_capabilities)
print(client.protocol_version)
print(client.instructions)
Two things jump out here. First, Client(mcp) takes the server object directly, which collapses an entire test harness boilerplate file into three lines. Second, client.protocol_version and client.server_capabilities are now first-class attributes you can assert on. For a QA team that used to reach for initialize() and then parse a capabilities blob by hand, this is a real quality-of-life upgrade, but it also means every v1 test that called initialize() directly has to be rewritten, not patched.
Breaking Change Three: The Server Can No Longer Call the Client
This is the change that will bite your integration tests hardest, because it is a behavioral change, not a rename. Under the 2026-07-28 revision, the server cannot initiate a request back to the client. In v1, a tool could ask the model or the host for something mid-run through sampling or roots. That is gone. Tools now return the question instead of asking it, through a mechanism called multi-round-trip requests.
The v2 way to get input from a user or host mid-tool is a Resolve parameter, filled by your function invisibly to the model. Here is the pattern from the docs:
from mcp.server.mcpserver import AcceptedElicitation, Elicit, ElicitationResult, Resolve
from mcp import Client
from mcp.client import ClientRequestContext
from mcp.types import ElicitRequestParams, ElicitResult
mcp = MCPServer("Bookshop")
class Quantity(BaseModel):
copies: int
async def ask_quantity() -> Elicit[Quantity]:
"""Resolver: ask the user how many copies to put aside."""
return Elicit("How many copies?", Quantity)
@mcp.tool()
async def reserve(title: str, quantity: Annotated[ElicitationResult[Quantity], Resolve(ask_quantity)]) -> str:
if isinstance(quantity, AcceptedElicitation):
return f"Reserved {quantity.data.copies} of {title!r}."
return "Nothing reserved."
async def answer(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
return ElicitResult(action="accept", content={"copies": 2})
Notice the mode="legacy" and elicitation_callback=answer arguments in the full example from the docs: one tool body serves both protocol eras, so you can keep a legacy client in your test matrix and still run the same tool. For QA, the lesson is that a tool that used to pull data from the host now yields a question and waits for the next round trip. Any test that mocked a v1 callback now has to mock an elicitation result instead. If you test AI agents that call MCP tools, this is the single biggest source of post-migration test failures I have hit.
Breaking Change Four: Types Moved, Names Changed, Dependencies Shuffled
The rest of the changes are mechanical but numerous. The migration guide lists every one with before-and-after code, and I will summarize the ones that actually showed up in our codebase:
mcp.typesmoved to a standalonemcp-typespackage, imported asmcp_types. Every field name changed from camelCase to snake_case, soinputSchemais nowinput_schema.McpErrorwas renamed toMCPError, and it now surfaces from an@mcp.tool()handler as a proper JSON-RPC error instead of a swallowed exception.httpxandhttpx-ssewere replaced byhttpx2, which changes the transport dependency tree under your server.- Sync handler functions now run on a worker thread, which is great for blocking test fixtures but changes your threading assumptions.
- Resource URI types changed from
AnyUrltostr, so type-checked tests that assertedAnyUrlnow fail CI. - OpenTelemetry tracing ships on by default, which is useful and also means your servers emit traces you did not ask for.
The server_info change on the TypeScript side is worth flagging separately. In the final 2026-07-28 wire shape, serverInfo moved from the DiscoverResult body into the result _meta, and the per-request envelope’s clientInfo was demoted from required to optional. If your tests asserted on serverInfo in the discover body, that assertion moved house.
How MCP 2.0 Breaking Changes Affect Server Testing for QA
I will give you the honest summary: v2 made MCP servers easier to test in isolation and harder to test end to end, at the same time.
The win is the in-memory client. Client(mcp) against a server object means your unit tests run in milliseconds with no subprocess, no port, and no flaky socket. You can assert on server_capabilities and protocol_version directly. Before v2, testing an MCP server meant spawning it over stdio or Streamable HTTP and parsing JSON-RPC envelopes by hand, which is why most teams skipped it. Now the barrier is low enough that you have no excuse.
Here is the shape of a real test once you are on v2. You build a server, hand it to Client(mcp), and assert on the attributes directly, with pytest’s async support doing the heavy lifting. There is no transport to mock, because there is no transport in play. The first three tests I write for any new MCP tool are a capability check on client.server_capabilities, a happy-path call_tool, and a Resolve elicitation that verifies the tool yields a question and resumes with the injected answer. Those three catch most of what a rename or protocol change can break.
The cost is the multi-round-trip behavior. A tool that asks the host for input no longer completes in a single call, so a naive call_tool test that expects one response now sees a pending elicitation instead. Your test doubles have to model both eras: a legacy client with an elicitation_callback, and a modern client that handles the Resolve path. If you already have an LLM testing stack, this slots in next to your existing RAG and agent evaluations. We cover the evaluation side in our guide to RAG testing and the agent-side in our LangChain LangGraph testing guide.
A concrete test plan for an MCP 2.0 migration looks like this:
- Pin the current v1 dependency with
mcp>=1.28,<2and freeze it in your lockfile, so the migration is a decision, not an accident. - List every import of
FastMCP,ClientSession,initialize(), andMcpErrorin the codebase with grep before you touch anything. - Rewrite the server entry points to
MCPServerfirst, and keep the decorators unchanged. - Port the client harness to the new
Clientclass, starting with an in-memory test so you get a green unit suite fast. - Re-model any callback or sampling test as a
Resolveelicitation test, and keep a legacy-mode client in the matrix. - Add smoke tests for the things that silently changed: server name, discover
_meta, and the 4 MiB request body limit.
A Migration Roadmap That Does Not Blow Up Your Sprint
I would not treat this as a weekend refactor. The protocol change underneath means it is a sprint-sized piece of work, and it touches test code just as much as product code. Here is the sequence I used, and it held up:
- Freeze first. Add the
<2upper bound everywhere and ship that as a no-op change. This stops surprise breakage on the nextpip install. - Inventory the call sites. Grep for the four names above. The count tells you the size of the job. If you have ten call sites, do it in a day. If you have three hundred, plan for a week.
- Lift the server, then the client. Servers are usually the smaller surface. Port them first, then tackle the client harness.
- Keep a legacy client in the test matrix. v2 serves both eras from one server, so a
Client(mcp, mode="legacy")gives you a compatibility safety net without a second server. - Re-model elicitation last. The
Resolvepattern is where most of the subtle bugs live, so give it its own pass with dedicated tests. - Turn on the OpenTelemetry traces you now get for free and use them to find the handlers that still assume v1 semantics.
- Cut the pin. Once the legacy client and the elicitation tests pass, remove the
<2bound and letpip install mcppull 2.x.
One thing I will say plainly: do not half-migrate. A codebase with some servers on MCPServer and some on FastMCP in the same process is a debugging tax you pay every sprint. The version split across Python and npm already gives you enough cross-ecosystem skew to manage.
This post covers the testing implications of the upgrade. If you want the full line-by-line porting, I have a deeper MCP 2.0 migration walkthrough for SDETs, and a validation plan for the Python SDK 2.0 upgrade that lays out the rollout checklist. Read those after this one for the complete picture.
MCP 2.0 Breaking Changes That Fail Silently (No Import Error)
The renames fail loudly, which is actually a gift. The dangerous changes are the ones that keep your import working but change behavior underneath. I hit most of these, and I will list them so you can skip the debugging:
- Default server name. It went from
FastMCPtomcp-server. A discover assertion on the name fails with no stack trace pointing at the cause. - Environment variables and .env files are no longer read. v1 read
MCP_*environment variables and dotenv files for transport config. v2 does not. If your CI passed a host or port through an env var, it silently stops being honored. - Streamable HTTP request bodies are capped at 4 MiB. A test that uploaded a large fixture as a tool argument now gets a rejected request, not a helpful error.
- Extra fields on MCP types are no longer preserved. If you relied on passing arbitrary extra keys through a tool result, those keys vanish.
- Sync handlers moved to a worker thread. Anything that depended on handler code running on the event loop thread now behaves differently under load.
Resource not foundreturns -32602 and resource lookups raise typed exceptions, so error-handling tests that matched on a generic message now fail.
The pattern across all of these is the same: v2 tightened the contract. Where v1 was permissive, v2 is strict, and strictness is exactly what a test suite should be built to catch. The irony is that a good migration test plan finds these in an afternoon, while a team without one finds them in production over a quarter.
India Context: MCP Skills Are Showing Up in SDET Job Descriptions
If you are an SDET in India and wondering whether learning MCP is worth the time, the job market has already answered. Over the last two quarters I have watched MCP move from a conference-slide term to a line item in SDET and AI-testing job descriptions, mostly at product companies and AI startups in Bengaluru and Hyderabad, and increasingly at the bigger service firms building AI testing practices. The demand clusters around one thing: testers who can build and test MCP tool servers that AI agents call.
The salary signal is real but uneven. A mid-level SDET with Playwright plus hands-on MCP and agent-testing experience is quoting ₹25 to 40 LPA and, at the top end, getting it. A manual tester with only Selenium and no AI tooling is seeing that gap widen. The MCP 2.0 migration is a chance to stand out, because most testers are not reading protocol release notes. If you can walk into an interview and explain why FastMCP became MCPServer and what the 2026-07-28 revision removed, you are already ahead of the field. For the automation roadmap that pairs with this, see our guide to LLM performance testing, which is the next skill hiring managers ask for after MCP.
Key Takeaways
- The MCP 2.0 breaking changes shipped on July 28, 2026 with the Python SDK 2.0.0 stable release, and
pip install mcpnow installs 2.x. Pinmcp>=1.28,<2until you migrate. FastMCPis nowMCPServer, and a first-classClientreplaces the transport-plus-session-plus-initialize layering.- The 2026-07-28 protocol revision removed the handshake and every server-initiated request. Tools now return questions through
Resolveinstead of calling back to the client. - Versions split across ecosystems: npm SDK at 1.30.0, TypeScript server at 2.0.0, Python at 2.0.0, all describing one spec.
- Testing got easier in isolation (in-memory
Client(mcp)) and harder end to end (multi-round-trip elicitation).
FAQ
Do I have to migrate to MCP 2.0 right now?
No. v1.x is in maintenance mode and still gets security fixes. The official guidance is to pin mcp>=1.28,<2 until you are ready. But new features and the protocol revision only land in 2.x, so you are renting time, not buying it.
Is the decorator API changing?
The @mcp.tool() and @mcp.resource() decorators are unchanged. What changes is the server class name, the client, the type package, and the field naming. Most handler bodies survive; the wiring around them does not.
What is the fastest thing to break after upgrading?
Two things: the FastMCP import, which fails immediately, and any test that asserts on the server name or discover body, which fails silently. Fix the import first, then audit your assertions.
Can I test an MCP 2.0 server without a network connection?
Yes, and this is the biggest testing win in v2. Client(mcp) takes a server object in memory, so you can run tools and assert on capabilities in a unit test with no subprocess or port.
Does v2 still work with v1 clients?
Yes. One MCPServer serves both the 2026-07-28 revision and 2025-era clients over Streamable HTTP and stdio, and Client(mcp, mode="legacy") lets you keep a legacy client in your test matrix during the migration.
Sources: MCP Python SDK v2.0.0 release notes, What’s new in v2, Migration Guide v1 to v2, @modelcontextprotocol/server 2.0.0 release, and the npm registry.
