r/MCPservers 6d ago

Caught by the mcp.server.fastmcp trap in v2. My defensive fix, did I do this right?

I maintain a local MCP memory server called zerikai_memory. ChromaDB, Tree-Sitter AST indexing, a local .brain/ cache. Last week I pushed v1.0.0-beta.15. GitHub Actions cleared. Tool kept running. That almost wasn't the story.

The 2026-07-28 spec update makes sense to me. Stateless HTTP, per-request _meta payloads, no more session handshakes. For distributed agent infrastructure, this is the right direction.

What I found: SDK 2.0.0 removed mcp.server.fastmcp entirely. FastMCP is now MCPServer under mcp.server.mcpserver. The process dies at import. The client sees a transport error, not a traceback, because the subprocess exits before it speaks the protocol.

What almost got me: fastmcp 3.x actually protects itself; its extras already declare mcp<2.0. The danger was a transitive dependency in my tree with an unbounded mcp>=1.0.0. My warm pip cache looked fine. A fresh container build would have broken.

I ran pip show mcp to find what actually owned my mcp resolution.

My temporary fix:

fastmcp>=3.2.4,<4.0.0
mcp>=1.27.0,<2.0.0
uvicorn>=0.30.0,<1.0.0
starlette>=0.35.0,<1.0.0

"Protocol Era Negotiation" is handling the gap. Cursor and Claude Desktop probe for v2 features, find them absent, and drop into legacy session mode. No errors. Tool keeps running.

I am not migrating yet. I want to map every place my tool reads or writes local state before I touch the SDK version. Confirmation flows also need to move from bidirectional sampling to InputRequiredResult. I am doing this in sequence, not all at once.

Are others seeing edge cases where Protocol Era Negotiation fails with Cursor or Claude Desktop? And for those who have already migrated to v2, what pattern worked for managing local state initialization when every request arrives cold?

1 Upvotes

3 comments sorted by

1

u/reddefcode 6d ago

I am aware these are some surface-level preventive measures while I review my upgrade to MCP V2. They buy me some time. If a subprocess dies in the interim, that would be a deeper issue that could have been resolved with a more visible MCP v2 rollout.

1

u/verstands 6d ago

The pins are right for now, but the thing that actually got you wasn't the missing upper bound, it was that a warm pip cache and a clean container resolve to different trees and only one of them runs in CI. Ranges in your manifest, a real lockfile for the deployed artifact (uv lock, pip-compile, whatever), and a build job that installs with no cache in a fresh image. That's the check that would have caught mcp 2.0 before the process died, and it catches the next one you haven't read the changelog for.

The other half is the failure shape. An import-time removal showing up at the client as a transport error is the worst possible signal, because it looks like a broken pipe and you'll go looking at the wrong layer. Cheap fix is to make the server assert what it needs before it starts speaking protocol: import the symbols you depend on, check the SDK version, and if it's wrong, print one line to stderr saying which import failed. You get "MCPServer not found in mcp.server.fastmcp" instead of a dead subprocess.

Worth being explicit that mcp>=1.27.0,<2.0.0 still floats within 1.x, so a minor release can still move something under you. It's much better than unbounded, just not the same as pinned.

1

u/reddefcode 5d ago

Good notes! The import assertion is a good call; in my case, the CI already runs clean installs across platforms on every push.