Skip to content

Upgrade mcp SDK to 2.0.0 (protocol revision 2026-07-28) - #1777

Open
Kayaba-Attribution wants to merge 1 commit into
oraios:mainfrom
Kayaba-Attribution:upgrade-mcp-2.0
Open

Upgrade mcp SDK to 2.0.0 (protocol revision 2026-07-28)#1777
Kayaba-Attribution wants to merge 1 commit into
oraios:mainfrom
Kayaba-Attribution:upgrade-mcp-2.0

Conversation

@Kayaba-Attribution

Copy link
Copy Markdown

Closes #1776 (opened first per CONTRIBUTING.md, since two user-visible behaviours change).

mcp 2.0.0 shipped on 2026-07-28. Serena pins mcp==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

  • This PR follows the guidelines in CONTRIBUTING.md regarding the scope of PRs.
  • For changes that add features or fix problems, I have added an entry to CHANGELOG.md, which concisely describes the change.

What changed

Mechanical:

  • mcp.server.fastmcp.* -> mcp.server.mcpserver.* in mcp.py, tools_base.py, test_mcp.py; FastMCP -> MCPServer (kept under the existing FastMCP local alias to keep the diff small).
  • ToolAnnotations: readOnlyHint/destructiveHint -> read_only_hint/destructive_hint.
  • Context[ServerSessionT, LifespanContextT, RequestT] -> Context[LifespanContextT, RequestT]; ServerSessionT no longer exists. The | None default also goes, because the base Tool.run() no longer accepts an optional context (no call site passed None).

Behaviour-affecting, both described in #1776 and in CHANGELOG.md:

  • FASTMCP_* env vars no longer configure the server. The Settings.model_config = SettingsConfigDict(...) override is deleted: Settings is now a plain pydantic BaseModel populated from the constructor, so it reads neither the environment nor .env files, 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 keep FASTMCP_* support, it needs reintroducing explicitly and I am happy to do that instead.
  • create_mcp_server() no longer takes host/port. The SDK moved them to the transport-specific run() call, so cli.py passes them to server.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 until run().

One subtlety worth a review look: from mcp.server.mcpserver import server intentionally binds the submodule, not the package. The SDK resolves configure_logging from mcp/server/mcpserver/server.py's own namespace, so binding the package instead would leave the existing server.configure_logging = configure_logging override in mcp.py silently ineffective.

Verification

  • poe type-check: clean (both _ty_core and _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.

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`.
Copilot AI review requested due to automatic review settings July 29, 2026 01:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mcp from 1.28.1 to 2.0.0, including updated lockfile dependencies.
  • Port Serena’s MCP server/tool wiring to the new mcp.server.mcpserver.* APIs and updated ToolAnnotations field names.
  • Move host/port handling from server construction to server.run() for non-stdio transports, and document behavioral changes in CHANGELOG.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 ebarkhordar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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.

Upgrade to mcp 2.0 (MCP protocol revision 2026-07-28)

4 participants