Skip to content

feat: migrate to mcp 2.0 (FastMCP → MCPServer) - #104

Merged
GoodbyePlanet merged 3 commits into
mainfrom
feat/mcp-2-migration
Aug 11, 2026
Merged

feat: migrate to mcp 2.0 (FastMCP → MCPServer)#104
GoodbyePlanet merged 3 commits into
mainfrom
feat/mcp-2-migration

Conversation

@GoodbyePlanet

Copy link
Copy Markdown
Owner

Why

Dependabot #103 bumps mcp 1.28.1 → 2.0.0, but that is a major version with no source migration, so it cannot be merged as-is: mcp.server.fastmcp no longer exists in 2.0.0 and there is no back-compat alias. On that branch server.main fails to import and pytest errors during collection.

This PR lands the migration so the bump becomes mergeable.

What changed

  • FastMCPMCPServer, mcp.server.fastmcpmcp.server.mcpserver, across 10 modules (8 source, 2 test).
  • Dropped host=/port= from the constructor — removed in 2.x, they now raise TypeError. The HTTP path already passed host/port to uvicorn.run(), so nothing is lost.
  • Pass host=settings.mcp_host to streamable_http_app() / sse_app() — see below, this one is load-bearing.
  • Bumped mcp>=2.0.0 and relocked. Lock diff is confined to mcp + transitive deps (httpx2, httpcore2, mcp-types, opentelemetry-api, truststore).
  • Report serverInfo.version from package metadata (2.x adds a version kwarg; unset it serialises as ""), and bump the project to 1.1.0.
  • Docs: updated .claude/CLAUDE.md, renamed .claude/rules/fastmcp-http.mdmcp-http.md.

The one non-obvious trap

A rename-only migration compiles, passes CI, and is broken in Docker. In 2.x the app factories auto-enable DNS rebinding protection when host is a loopback address, and host defaults to 127.0.0.1. Since compose sets MCP_HOST=0.0.0.0, every request would be rejected:

host=0.0.0.0    -> HTTP 200
host=127.0.0.1  -> HTTP 421 Invalid Host header

The test suite cannot catch this — it is container-only. Hence threading the configured host through explicitly, with a comment at the call site.

Verified against a real mcp 2.0.0 install

Not just read off the migration guide — I introspected the installed package and ran the server:

  • All 293 tests pass.
  • Three things I expected to break, don't: custom_route, tool/prompt, and settings.log_level all survive the move. The NDJSON endpoints were never at risk.
  • End-to-end in Docker (rebuilt image, EMBEDDINGS_PROVIDER=openai): clean startup with 0 errors; lifespan runs exactly once (no per-connection store re-init); POST /mcp initialize → 200 with "serverInfo":{"name":"semcode","version":"1.1.0"}; POST /reindex and POST /reindex-history → 200 with valid NDJSON frames; POST /nope → 404, confirming real routing.

Notes for the reviewer

  • chore(deps): Bump the dependencies group with 2 updates #103 should be closed in favour of this PR (or re-pinned to mcp<2 if you would rather defer the major). This branch deliberately does not include chore(deps): Bump the dependencies group with 2 updates #103's tree-sitter-language-pack 1.13.3 → 1.13.7 bump, which is unrelated and low-risk.
  • serverInfo is not asserted anywhere in the suite, which is why the empty version went unnoticed. A small test asserting a non-empty version would stop that regressing — not included here, happy to add.
  • Version is 1.1.0; if you consider the transitive mcp major a breaking change for consumers, 2.0.0 would be the semver-strict call.

🤖 Generated with Claude Code

GoodbyePlanet and others added 3 commits August 11, 2026 09:21
mcp 2.0.0 renames FastMCP to MCPServer and moves mcp.server.fastmcp.* to
mcp.server.mcpserver.* with no back-compat alias, so the bump in #103 could
not import server.main at all.

- rename FastMCP -> MCPServer and update imports across 10 modules
- drop host/port from the constructor (removed in 2.x, now a TypeError) and
  pass host to streamable_http_app()/sse_app() instead
- bump mcp to >=2.0.0 and relock

The host kwarg is load-bearing, not cosmetic: the app factories auto-enable
DNS rebinding protection when host is a loopback address, so leaving it at
the 127.0.0.1 default made the containerized server (MCP_HOST=0.0.0.0)
reject every request with 421 Invalid Host header.

Verified: custom_route, tool, prompt and settings.log_level all survive the
move, both NDJSON routes still register, and all 293 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
mcp 2.x adds a `version` kwarg to the server constructor; left unset it
defaults to "" and clients showed an empty version. Source it from installed
package metadata so pyproject stays the single place to bump, falling back to
0.0.0 when the package isn't installed (bare source checkout) rather than
failing at import.

Verified in Docker: serverInfo.version now reports "1.0.0".

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@GoodbyePlanet
GoodbyePlanet merged commit f1f8325 into main Aug 11, 2026
2 checks passed
GoodbyePlanet added a commit that referenced this pull request Aug 11, 2026
Rebuilt on current main and reduced to just the tree-sitter-language-pack
bump. The mcp 1.28.1 -> 2.0.0 bump is dropped from this PR: it is a major
version whose module paths and class names changed, so it cannot land without
a source migration (see #104).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant