Skip to content

Build1 publisher3 min readPublished

Two unpinned requirements lines broke a working MCP server on a fresh install

The MCP Python SDK renamed FastMCP in 2.x. Gemini's Interactions API stopped accepting the schema google-genai 1.x sends. Both arrived on a fresh install of unchanged code, and the mocked unit tests never noticed.

The Engineer · Build desk

Illustration accompanying Two unpinned requirements lines broke a working MCP server on a fresh install

What happened

  • The MCP server's requirements.txt listed mcp and google-genai with no version bounds, so a fresh install pulled two new major versions and code that had not changed stopped running.
  • No application code changed for the API break, because server.py already read interaction.output_image, which google-genai 2.x exposes over the new steps schema.
  • The unit tests passed throughout the outage, since they mock _get_client and the SDK therefore never builds a real response object.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure An operator debugging through the agent sees "Image generation failed". The 400's instruction to upgrade the SDK survives only as buried text inside a tool result string, so the version requirement never reaches the person holding the pager.
  • constraint Mocking at the client boundary puts every server-side schema change beyond the reach of the suite. Catching this class of break needs a fixture built from the SDK's own model class, or a live call.
  • decision Anyone shipping an MCP server picks between bounding both lines to a major version and accepting that the install date, not the commit, decides whether the server works.
  • precedent A vendor deprecating a wire schema on a dated notice makes the SDK version part of the runtime contract, so a working checkout stays working only as long as the resolver keeps handing it the old major.

Tests in the repository mock `_get_client`. The SDK never builds a real response object and no request leaves the process, so the suite passed for the whole time the server was broken [15]. Everything downstream of that mock is the repository's own dictionary handling.

The replacement test builds a real `Interaction` with the SDK's own model class: `Interaction.model_validate` with an id, a completed status, and a `steps` list holding one `model_output` block of type image carrying base64 `data` and a `mime_type`. It runs that object through `_handle_response` [16]. On google-genai 1.x the import for that model does not exist, so the test fails at import instead of the API failing at call time [17]. A live check covers the rest of the gap [18].

Gemini's 400 names the fix. It reads: "The legacy Interactions API schema is no longer supported. Please upgrade your google-genai Python SDK to version >= 2.0.0 (e.g., run pip install -U google-genai) to use the Interactions API." [9] It also links migration notes at a page named for a May 2026 breaking change [10]. Inside an MCP server that text is harder to reach, because each tool catches the exception and returns it as a red string, so the agent reports "Image generation failed" with the version number buried in the body [11].

Two lines of source changed across both breaks [21]. The mcp 2.x port replaced the import from `mcp.server.fastmcp` with `mcp.server.mcpserver` and swapped `FastMCP("NB2Lite Agent")` for `MCPServer("NB2Lite Agent")`; the `@mcp.tool()` decorators, `mcp.run()` and every tool body stayed as they were [19]. For the API break nothing changed at all, because `server.py` already read `interaction.output_image`, which google-genai 2.x exposes as a convenience property with `data` and `mime_type` over the new steps schema [12][13]. The one-line fix transfers to your own code only if your code reads `interaction.output_image`. Anything that walks the legacy output fields by hand gets the migration instead, which the notes linked from the error cover [14].

The API holds the session state, and that keeps the tool surface small. Every call goes up with `store=True` and comes back with an interaction ID; passing that ID as `previous_interaction_id` makes the model edit the image it already produced instead of redrawing the scene [6]. `generate_image` opens a session, `edit_image` continues one, and the agent only carries an ID [7]. The SDK is the only component in that path that knows the response shape, so a server-side schema change arrives entirely through a dependency line.

Reproducing the break took a scratch install: google-genai 1.75.0 in a `pip install --target` directory, so the global interpreter kept 2.x [8]. The call under test asked `gemini-3.1-flash-lite-image` for a small red cube on a white table, with `response_format` set to image and `thinking_level` minimal [8]. After the upgrade the same server was registered with Claude Code, Codex and Antigravity CLI and validated end to end against the live API [5]. The July version of this setup used Claude Code alone [4].

What to watch

  • Whether the steps schema in google-genai 2.x holds, or the May 2026 migration page lists further removals for Interactions callers.
  • Whether list_tools() becoming async in mcp 2.x breaks code that still calls it synchronously.
  • Whether the repository's requirements.txt gains major-version bounds on mcp and google-genai, or keeps resolving to whatever ships next.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories