Pin mcp<2 now, then migrate to mcp 2.x #4

Open
opened 2026-09-10 14:28:37 -04:00 by claude · 0 comments
Contributor

This repo is UNPINNED (unpinned) — it is one rebuild away from an outage.
Landing mcp<2 is a one-line change and should happen now, independently of the
migration below.

What happens if this is left alone

seed-mcp had exactly this line. Its 2026-09-01 rebuild resolved to mcp 2.1.1 and the
container crash-looped for nine days before anyone noticed:

File ".../server.py", line 28, in <module>
    from mcp.server.fastmcp import FastMCP
ModuleNotFoundError: No module named 'mcp.server.fastmcp'

mcp 2.0.0 (2026-07-28) removed mcp.server.fastmcp. zerto-docs went down the same
way on 2026-08-11. There is no upper bound here, so the next image build picks up 2.x.

Worse, the crashloop blocks its own fix: Watchtower only scans running containers,
so a crash-looping service is skipped forever and needs a manual compose pull && up -d.

The port (proven, not theoretical)

Ported end-to-end on seed-mcp in justin/seed-mcp#23. Three mechanical changes:

# 1. import
from mcp.server.mcpserver import MCPServer          # was: from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings

# 2. constructor — transport options moved OFF it
mcp = MCPServer(NAME)                                # was: FastMCP(NAME, stateless_http=True)

# 3. run() — host/port/stateless_http/transport_security are now kwargs,
#    and `mcp.settings` no longer exists
mcp.run(transport="streamable-http", host=..., port=..., stateless_http=True,
        transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False))

@mcp.tool() is unchanged — decorators and handler signatures stay as they are.
(If this server calls mcp.get_context(), that was removed: take ctx: Context as a
parameter instead.)

Verification this needs (do not skip)

The real risk is tool routing, since 2.x moved the protocol types to snake_case
internally and tool schemas decide routing.

  1. Diff tools/list before and after, dumped in wire format:
    tools = asyncio.run(mcp.list_tools())
    json.dumps([t.model_dump(by_alias=True, mode="json", exclude_none=True) for t in tools],
               indent=2, sort_keys=True)
    
    On seed-mcp this came out byte-for-byte identical between 1.27.1 and 2.x.
  2. Boot it for real on its production transport and confirm initialize returns 200.
  3. Add an import smoke-test to CI so a green build can never ship a non-importing
    image again:
    docker run --rm --entrypoint python "${IMAGE}:latest" -c "import <pkg>.server"
    

Notes

  • 2.x has no [fastmcp] extra — the requirement becomes plain mcp>=2,<3.
  • 2.x swaps httpx for httpx2 and requires opentelemetry-api. A local httpx
    pin is unaffected — httpx2 is a separate distribution.
  • An unversioned server now reports serverInfo.version as "" instead of the SDK
    version. Cosmetic, but it is a visible change to clients.
  • zerto-msp-mcp has run 2.x in production since 2026-07-30, and seed-mcp since
    today — the shape is proven in this estate.

Related: justin/seed-mcp#22 (the pin), justin/seed-mcp#23 (the port), justin/zerto-docs#80.

This server has its own shape (not the docs_mcp lineage), so it needs its own tools/list baseline and boot check.

> **This repo is UNPINNED (`unpinned`) — it is one rebuild away from an outage.** > Landing `mcp<2` is a one-line change and should happen now, independently of the > migration below. ## What happens if this is left alone `seed-mcp` had exactly this line. Its 2026-09-01 rebuild resolved to mcp 2.1.1 and the container crash-looped for **nine days** before anyone noticed: ``` File ".../server.py", line 28, in <module> from mcp.server.fastmcp import FastMCP ModuleNotFoundError: No module named 'mcp.server.fastmcp' ``` mcp 2.0.0 (2026-07-28) removed `mcp.server.fastmcp`. `zerto-docs` went down the same way on 2026-08-11. There is no upper bound here, so the next image build picks up 2.x. Worse, the crashloop **blocks its own fix**: Watchtower only scans *running* containers, so a crash-looping service is skipped forever and needs a manual `compose pull && up -d`. ## The port (proven, not theoretical) Ported end-to-end on `seed-mcp` in justin/seed-mcp#23. Three mechanical changes: ```python # 1. import from mcp.server.mcpserver import MCPServer # was: from mcp.server.fastmcp import FastMCP from mcp.server.transport_security import TransportSecuritySettings # 2. constructor — transport options moved OFF it mcp = MCPServer(NAME) # was: FastMCP(NAME, stateless_http=True) # 3. run() — host/port/stateless_http/transport_security are now kwargs, # and `mcp.settings` no longer exists mcp.run(transport="streamable-http", host=..., port=..., stateless_http=True, transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False)) ``` `@mcp.tool()` is **unchanged** — decorators and handler signatures stay as they are. (If this server calls `mcp.get_context()`, that was removed: take `ctx: Context` as a parameter instead.) ## Verification this needs (do not skip) The real risk is tool routing, since 2.x moved the protocol types to snake_case internally and tool schemas decide routing. 1. **Diff `tools/list` before and after**, dumped in wire format: ```python tools = asyncio.run(mcp.list_tools()) json.dumps([t.model_dump(by_alias=True, mode="json", exclude_none=True) for t in tools], indent=2, sort_keys=True) ``` On seed-mcp this came out **byte-for-byte identical** between 1.27.1 and 2.x. 2. **Boot it for real** on its production transport and confirm `initialize` returns 200. 3. **Add an import smoke-test to CI** so a green build can never ship a non-importing image again: ```yaml docker run --rm --entrypoint python "${IMAGE}:latest" -c "import <pkg>.server" ``` ## Notes - 2.x has **no `[fastmcp]` extra** — the requirement becomes plain `mcp>=2,<3`. - 2.x swaps `httpx` for `httpx2` and requires `opentelemetry-api`. A local `httpx` pin is unaffected — `httpx2` is a separate distribution. - An unversioned server now reports `serverInfo.version` as `""` instead of the SDK version. Cosmetic, but it is a visible change to clients. - `zerto-msp-mcp` has run 2.x in production since 2026-07-30, and `seed-mcp` since today — the shape is proven in this estate. Related: justin/seed-mcp#22 (the pin), justin/seed-mcp#23 (the port), justin/zerto-docs#80. _This server has its own shape (not the `docs_mcp` lineage), so it needs its own `tools/list` baseline and boot check._
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: justin/ag-bids-mcp#4