Skip to content

fix(mcp_cloud): migrate to MCP Python SDK 2.0 - #865

Merged
neoneye merged 1 commit into
mainfrom
fix/mcp-sdk-2-migration
Aug 29, 2026
Merged

fix(mcp_cloud): migrate to MCP Python SDK 2.0#865
neoneye merged 1 commit into
mainfrom
fix/mcp-sdk-2-migration

Conversation

@neoneye

@neoneye neoneye commented Aug 29, 2026

Copy link
Copy Markdown
Member

The mcp 1.28.1 → 2.0.0 bump (#845 / #851) removed mcp.server.fastmcp, so mcp_cloud crashed on import and the container sat in a restart loop:

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

Four separate API changes had to be handled.

1. FastMCPMCPServer

Now imported from mcp.server.mcpserver. Transport settings (streamable_http_path, json_response, stateless_http) moved off the constructor onto streamable_http_app(). SERVER_VERSION is now passed to the server, so initialize reports serverInfo.version: "1.0.1" instead of an empty string.

2. DNS rebinding protection — behaviour change worth a look

streamable_http_app() auto-enables DNS rebinding protection when host is a loopback address, restricting Host/Origin to localhost. PLANEXE_MCP_HTTP_HOST defaults to 127.0.0.1, so accepting the new default would reject the LAN clients mcp_cloud/README.md documents (http://192.168.1.10:8001/mcp/).

This PR sets transport_security explicitly to preserve the 1.28.1 behaviour. Access remains controlled by the API-key middleware and the configured CORS origins. If you would rather opt into the protection, that is a one-line change plus an allowed-hosts list — say the word and I will do it.

3. Vestigial lowlevel Server removed

The lowlevel Server in db_setup.py lost its list_tools() / call_tool() decorators in 2.0. Nothing ever served through that instance — the HTTP path goes through MCPServer plus the REST routes — so it is removed. handle_list_tools / handle_call_tool stay as plain functions, which is how the tests and the REST bridge already used them. The stdio entry point in app.main() now builds an MCPServer and calls run_stdio_async() rather than being left broken.

4. snake_case model fields

mcp.types models are snake_case in 2.0 with camelCase aliases. Attribute reads become input_schema / output_schema / is_error / structured_content (construction by alias still works). The REST /mcp/tools payload keeps its camelCase wire keys, so that contract is unchanged.

Verification

Against the running docker-compose stack — container healthy, and over Streamable HTTP:

Call Result
initialize protocol 2025-06-18, {'name': 'planexe-mcp-server', 'version': '1.0.1'}
tools/list 11 tools
prompts/list / prompts/get getting_started, plan_a_project — both render
tools/call model_profiles, example_prompts return real data, isError: false
REST /mcp/tools, /mcp/tools/call 200; plan_status + plan_file_info still correctly omit the oneOf outputSchema

The mcp_cloud suite was run against a live Postgres on this branch and on main with mcp 1.28.1. Both give an identical 5 failures + 2 errors — same test names, all pre-existing and unrelated to MCP (description-text drift, an annotation mismatch, SSE timing, auth-header parsing). No regressions.

Note: CI did not catch this

test.py ends with runner.run(tests) and never inspects the result, so it always exits 0. The tests job on the merge of #845/#851 printed FAILED (errors=25, skipped=13) and was still reported green. Not fixed here — flipping the exit code alone turns CI red, because the job has no Postgres service and 19 mcp_cloud tests error on connection. Filed separately.

The mcp 1.28.1 -> 2.0.0 bump (#845/#851) removed mcp.server.fastmcp, so mcp_cloud crashed on import and the container sat in a restart loop. Four separate API changes had to be handled.

FastMCP -> MCPServer, imported from mcp.server.mcpserver. Transport settings (streamable_http_path, json_response, stateless_http) moved off the constructor onto streamable_http_app(). SERVER_VERSION is now passed to the server, so initialize reports serverInfo.version instead of an empty string.

streamable_http_app() auto-enables DNS rebinding protection when host is a loopback address, restricting Host and Origin to localhost. That would reject the LAN clients the README documents and the Railway proxy in production, so transport_security is set explicitly to keep the 1.28.1 behaviour. Access stays controlled by the API-key middleware and the configured CORS origins.

The lowlevel Server in db_setup dropped its list_tools()/call_tool() decorators. Nothing served through that instance -- the HTTP path goes through MCPServer and the REST routes -- so it is removed; handle_list_tools and handle_call_tool stay as plain functions, which is how the tests and the REST bridge already used them. The stdio entry point in app.main() now builds an MCPServer and calls run_stdio_async().

mcp.types models are snake_case in 2.0 with camelCase aliases, so attribute reads become input_schema / output_schema / is_error / structured_content. Construction by alias still works, and the REST /mcp/tools payload keeps its camelCase wire keys.

Verified against the running docker-compose stack: container healthy, initialize/tools/list/prompts/list/prompts/get/tools/call all succeed over Streamable HTTP, and the REST endpoints still advertise the canonical outputSchema. The mcp_cloud suite was run against a live Postgres on this branch and on main with mcp 1.28.1: both give the same 5 failures and 2 errors, all pre-existing and unrelated to MCP.
@neoneye
neoneye merged commit 46d6c41 into main Aug 29, 2026
3 checks passed
@neoneye
neoneye deleted the fix/mcp-sdk-2-migration branch August 29, 2026 14:53
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