Upgrade mcp SDK to 2.0.0 (protocol revision 2026-07-28) - #1777
Upgrade mcp SDK to 2.0.0 (protocol revision 2026-07-28)#1777Kayaba-Attribution wants to merge 1 commit into
Conversation
mcp 2.0.0 renames `mcp.server.fastmcp` to `mcp.server.mcpserver` and `FastMCP` to `MCPServer`, and removes some symbols Serena imported. - Update imports in `mcp.py`, `tools_base.py` and `test_mcp.py`. Note `from mcp.server.mcpserver import server` deliberately binds the submodule, not the package, so the existing `server.configure_logging` override still lands in the namespace the SDK reads it from. - `ToolAnnotations` fields are now snake_case (`readOnlyHint` -> `read_only_hint`, `destructiveHint` -> `destructive_hint`). - `Context` lost its `ServerSessionT` type parameter (3 -> 2), and the base `Tool.run()` no longer accepts an optional context. - `host`/`port` moved from the `MCPServer` constructor to the transport-specific `run()` call, so `SerenaMCPFactory.create_mcp_server()` no longer accepts them; `cli.py` now passes them to `server.run()` for non-stdio transports. - Drop the `Settings.model_config` override. `Settings` is no longer a pydantic-settings `BaseSettings` but a plain pydantic `BaseModel` populated from the constructor, so it reads neither the environment nor `.env` files. The override the `.env` problem required had become a silent no-op. Serena consequently no longer needs pydantic-settings, which mcp 1.x had provided transitively. Behavioural consequences, both noted in CHANGELOG.md: `FASTMCP_*` environment variables no longer configure the server, and `create_mcp_server()` no longer takes `host`/`port`.
There was a problem hiding this comment.
Pull request overview
Updates Serena’s MCP integration to be compatible with mcp SDK 2.0.0 (protocol revision 2026-07-28), adapting to upstream module/type renames and SDK API shifts while documenting the two intentional user-visible behavior changes.
Changes:
- Bump
mcpfrom1.28.1to2.0.0, including updated lockfile dependencies. - Port Serena’s MCP server/tool wiring to the new
mcp.server.mcpserver.*APIs and updatedToolAnnotationsfield names. - Move
host/porthandling from server construction toserver.run()for non-stdio transports, and document behavioral changes inCHANGELOG.md.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
pyproject.toml |
Pins mcp==2.0.0 to pick up the new SDK/protocol revision. |
uv.lock |
Updates the resolved dependency graph for mcp 2.0.0 (including newly introduced transitive packages). |
src/serena/mcp.py |
Migrates imports/types to mcpserver, updates tool annotations, and updates MCP server factory behavior per SDK changes. |
src/serena/tools/tools_base.py |
Updates MCP Context / metadata imports to the new SDK module layout. |
src/serena/cli.py |
Passes host/port to server.run() for non-stdio transports per SDK 2.0 behavior. |
test/serena/test_mcp.py |
Updates test import paths to the new mcpserver tool base module. |
CHANGELOG.md |
Records the SDK upgrade and the two intentional behavioral consequences for users. |
ebarkhordar
left a comment
There was a problem hiding this comment.
Nice migration, and thanks for putting the two behaviour changes in the CHANGELOG rather than leaving them to be discovered.
I checked the SDK claims in a clean container against mcp==2.0.0 and they hold: MCPServer.__init__ no longer accepts host/port, run_sse_async and run_streamable_http_async both take them keyword-only, and run_stdio_async takes neither, so the transport != "stdio" guard in cli.py is doing real work rather than being defensive. Settings is a plain BaseModel with an empty model_config, so dropping the env_prefix override is right. ToolAnnotations keeps populate_by_name=True, so read_only_hint= and readOnlyHint= serialise identically. test/serena/test_mcp.py is 64 passed at 1e436e5.
One thing I think the migration misses, src/serena/tools/tools_base.py:346:
client_info = cast(Implementation, client_params.clientInfo)2.0 renamed the protocol models to snake_case with camelCase aliases, and InitializeRequestParams.clientInfo is one of them. Pydantic aliases apply to validation and serialisation, not to attribute access, so this raises AttributeError. It sits inside the except Exception at :351, so it does not break a tool call: it logs Failed to get client info at INFO on every call instead, and leaves Tool._last_tool_call_client_str at None, which is what dashboard.py:632 passes as current_client.
Reproduced in a clean container, in the venv uv sync --frozen resolves for this branch at 1e436e5, executing line 346 as read from the file against an InitializeRequestParams validated from the camelCase payload a client sends on initialize:
mcp == 2.0.0 | serena.tools.tools_base imported OK
--- executing: client_info = cast(Implementation, client_params.clientInfo)
RESULT: AttributeError: 'InitializeRequestParams' object has no attribute 'clientInfo'
The same script on mcp==1.28.1, so it is the upgrade and not my harness:
mcp == 1.28.1
RESULT: client_str = 'claude-code 2.1.0'
client_params.client_info should be all it needs. session.client_params is unchanged in 2.0, and Implementation keeps name, title and version, so nothing else on those two lines has to move.
On the rest of the tree: I diffed the pydantic field names of every model the two SDK versions share and got 78 camelCase fields removed across 45 models, then searched for all 39 distinct names. clientInfo at that line is the only hit, so I do not think a second one is hiding.
I did not drive a real client handshake, so the dashboard consequence is read off the call path rather than observed in the UI.
Closes #1776 (opened first per CONTRIBUTING.md, since two user-visible behaviours change).
mcp2.0.0 shipped on 2026-07-28. Serena pinsmcp==1.28.1, and on 2.0.0 the current code fails at import (No module named 'mcp.server.fastmcp'), so this is a rename-and-reshape port rather than a version bump.Checklist
CONTRIBUTING.mdregarding the scope of PRs.CHANGELOG.md, which concisely describes the change.What changed
Mechanical:
mcp.server.fastmcp.*->mcp.server.mcpserver.*inmcp.py,tools_base.py,test_mcp.py;FastMCP->MCPServer(kept under the existingFastMCPlocal alias to keep the diff small).ToolAnnotations:readOnlyHint/destructiveHint->read_only_hint/destructive_hint.Context[ServerSessionT, LifespanContextT, RequestT]->Context[LifespanContextT, RequestT];ServerSessionTno longer exists. The| Nonedefault also goes, because the baseTool.run()no longer accepts an optional context (no call site passedNone).Behaviour-affecting, both described in #1776 and in
CHANGELOG.md:FASTMCP_*env vars no longer configure the server. TheSettings.model_config = SettingsConfigDict(...)override is deleted:Settingsis now a plain pydanticBaseModelpopulated from the constructor, so it reads neither the environment nor.envfiles, and the override had become a no-op that does not raise. The.env-bleed problem it existed to prevent is handled by the SDK now. This also removes Serena's need for pydantic-settings (previously transitive via mcp 1.x). If you would rather keepFASTMCP_*support, it needs reintroducing explicitly and I am happy to do that instead.create_mcp_server()no longer takeshost/port. The SDK moved them to the transport-specificrun()call, socli.pypasses them toserver.run()for non-stdio transports. Only one in-repo caller exists; if you consider the factory external API, I can keep the parameters and store them untilrun().One subtlety worth a review look:
from mcp.server.mcpserver import serverintentionally binds the submodule, not the package. The SDK resolvesconfigure_loggingfrommcp/server/mcpserver/server.py's own namespace, so binding the package instead would leave the existingserver.configure_logging = configure_loggingoverride inmcp.pysilently ineffective.Verification
poe type-check: clean (both_ty_coreand_ty_test).poe lint: clean (ruff format --check,ruff check).pytest test/serena/test_mcp.py: 64 passed.pytest test/serena: 746 passed, 14 failed, 8 skipped. All 14 failures are Go/Rust/C#/PowerShell language-server tests that fail on this machine for lack of those toolchains (RuntimeError: Go is not installed); none reference mcp. I could not exercise the Go/Rust/C#/PowerShell paths locally, so CI coverage there is worth confirming.