Upgrade to MCP SDK v2.2.0

Resolves #1776
This commit is contained in:
Dominik Jain authored and Dr. Dominik Jain committed 2026-09-19 12:02:08 +02:00
1 parent f0693a10c1
commit c4dc91a7da
7 files changed
+91 -68

No files matched your search

+2 -2
View File
@@ -19,7 +19,7 @@ Status of the `main` branch. Changes prior to the next official version change w
- Add `auth_secret` to `serena_config.yml` for authenticating communication between Serena components
and services. When missing, null, or empty, a random UUID is generated and persisted; existing values
are preserved
- Fix: MCP `initialize` now reports Serena's version instead of the installed mcp SDK version (#1889)
- Fix: MCP server now reports Serena's version instead of the installed MCP SDK version (#1889)
- Fix: importing Serena no longer loads the `anthropic` package unless the Anthropic token counter is
actually used; the unconditional import added seconds to CLI/MCP startup on some machines (#2012)
- Fix: Parallel agents auto-registering projects could overwrite each other's changes to the global
@@ -154,6 +154,7 @@ CLI:
* Dependencies:
- Remove the redundant `dotenv` dependency; the `dotenv` module is provided by `python-dotenv`
- Upgrade the `mcp` SDK from 1.28.1 to 2.2.0
# v1.7.0 (2026-08-09)
@@ -306,7 +307,6 @@ CLI:
`target_file`/`targetFile` file-path keys (shared payload parsing, applies to all hook clients).
- Fix hook input parsing for clients that emit raw control characters in JSON string values #1743.
# v1.6.1 (2026-07-21)
* General:
+1 -1
View File
@@ -21,7 +21,7 @@ dependencies = [
"requests==2.33.0",
"overrides==7.7.0",
"python-dotenv==1.2.2",
"mcp==1.28.1",
"mcp==2.2.0",
"flask==3.1.3", # bumped from 3.1.1 for CVE fix (also fixes werkzeug alert)
"sensai-utils==1.5.0",
"pydantic==2.12.5",
+5 -3
View File
@@ -390,8 +390,6 @@ class TopLevelCommands(AutoRegisteringGroup):
factory = SerenaMCPFactory(transport=transport, context=context, project=project_file, memory_log_handler=memory_log_handler)
server = factory.create_mcp_server(
host=host,
port=port,
mode_selection_def=mode_selection_def,
language_backend=LanguageBackendRegistry.get_instance().resolve(language_backend) if language_backend else None,
agent_interface=AgentInterface.from_str(agent_interface) if agent_interface else None,
@@ -409,7 +407,11 @@ class TopLevelCommands(AutoRegisteringGroup):
project_file,
)
log.info("Starting MCP server …")
server.run(transport=transport)
kwargs = {}
if transport != "stdio":
kwargs["host"] = host
kwargs["port"] = port
server.run(transport=transport, **kwargs)
@staticmethod
@click.command(
+11 -24
View File
@@ -11,17 +11,16 @@ from dataclasses import dataclass
from typing import Any, Literal, cast
import docstring_parser
from mcp.server.fastmcp import server
from mcp.server.fastmcp.exceptions import ToolError
from mcp.server.fastmcp.server import Context, FastMCP, Settings
from mcp.server.fastmcp.tools.base import Tool as FastMCPTool
from mcp.server.session import ServerSessionT
from mcp.shared.context import LifespanContextT, RequestT
from mcp.server.mcpserver import server
from mcp.server.mcpserver.context import LifespanContextT, RequestT
from mcp.server.mcpserver.exceptions import ToolError
from mcp.server.mcpserver.server import Context
from mcp.server.mcpserver.server import MCPServer as FastMCP
from mcp.server.mcpserver.tools.base import Tool as FastMCPTool
from mcp.types import ToolAnnotations
from pydantic_settings import SettingsConfigDict
from sensai.util import logging
from serena import __version__
from serena import __version__ as serena_version_str
from serena.agent import (
SerenaAgent,
)
@@ -110,8 +109,8 @@ class SerenaFastMCPTool(FastMCPTool):
can_edit = tool.can_edit()
annotations = ToolAnnotations(
title=tool_title,
readOnlyHint=not can_edit,
destructiveHint=can_edit,
read_only_hint=not can_edit,
destructive_hint=can_edit,
)
super().__init__(
@@ -133,7 +132,7 @@ class SerenaFastMCPTool(FastMCPTool):
async def run(
self,
arguments: dict[str, Any],
context: Context[ServerSessionT, LifespanContextT, RequestT] | None = None,
context: Context[LifespanContextT, RequestT],
convert_result: bool = False,
) -> Any:
# apply parameter aliases
@@ -323,8 +322,6 @@ class SerenaMCPFactory:
def create_mcp_server(
self,
host: str = "127.0.0.1",
port: int = 8000,
mode_selection_def: ModeSelectionDefinition | None = None,
language_backend: LanguageBackend | None = None,
agent_interface: AgentInterface | None = None,
@@ -339,8 +336,6 @@ class SerenaMCPFactory:
"""
Create an MCP server with process-isolated SerenaAgent to prevent asyncio contamination.
:param host: The host to bind to
:param port: The port to bind to
:param mode_selection_def: the mode selection definition to apply
:param language_backend: the language backend to use, overriding the configuration setting.
:param agent_interface: the agent interface to use, overriding the configuration setting.
@@ -383,23 +378,15 @@ class SerenaMCPFactory:
show_fatal_exception_safe(e)
raise
# Override model_config to disable the use of `.env` files for reading settings, because user projects are likely to contain
# `.env` files (e.g. containing LOG_LEVEL) that are not supposed to override the MCP settings;
# retain only FASTMCP_ prefix for already set environment variables.
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
instructions = self._get_initial_instructions()
log.info("MCP server initial instructions:\n%s", instructions)
mcp = FastMCP(
name="Serena",
version=serena_version_str,
lifespan=self.server_lifespan,
website_url="https://oraios.github.io/serena",
host=host,
port=port,
instructions=instructions,
)
# FastMCP currently falls back to the installed mcp SDK version when no version is set.
# Set the low-level server value explicitly so MCP clients identify Serena correctly.
mcp._mcp_server.version = __version__
return mcp
@asynccontextmanager
+2 -2
View File
@@ -8,8 +8,8 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, cast
from mcp import Implementation
from mcp.server.fastmcp import Context
from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
from mcp.server.mcpserver import Context
from mcp.server.mcpserver.utilities.func_metadata import FuncMetadata, func_metadata
from sensai.util import logging
from sensai.util.helper import mark_used
from sensai.util.string import dict_string
+2 -22
View File
@@ -1,13 +1,11 @@
"""Tests for the mcp.py module in serena."""
import pytest
from mcp.server.fastmcp import Context
from mcp.server.fastmcp.tools.base import Tool as MCPTool
from mcp.server.mcpserver import Context
from mcp.server.mcpserver.tools.base import Tool as MCPTool
from serena import __version__
from serena.agent import Tool, ToolRegistry
from serena.config.context_mode import SerenaAgentContext
from serena.config.serena_config import SerenaConfig
from serena.mcp import SerenaMCPFactory
from serena.repl.facade import ApiScope
from serena.repl.repl import SerenaRepl
@@ -64,24 +62,6 @@ class BasicTool(BaseMockTool):
return self.apply(**kwargs)
def test_create_mcp_server_reports_serena_version(monkeypatch: pytest.MonkeyPatch) -> None:
"""MCP initialize must report Serena's version, not the installed mcp SDK version."""
class MinimalAgent:
def create_connection_prompt(self) -> str:
return ""
monkeypatch.setattr(SerenaConfig, "from_config_file", classmethod(lambda cls: SerenaConfig()))
factory = SerenaMCPFactory(transport="stdio")
monkeypatch.setattr(factory, "_create_serena_agent", lambda *args, **kwargs: MinimalAgent())
mcp_server = factory.create_mcp_server()
initialization_options = mcp_server._mcp_server.create_initialization_options()
assert initialization_options.server_name == "Serena"
assert initialization_options.server_version == __version__
def test_make_tool_basic() -> None:
"""Test that make_tool correctly creates an MCP tool from a Tool object."""
mock_tool = BasicTool()
Generated
+68 -14
View File
@@ -821,6 +821,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpcore2"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "h11" },
{ name = "truststore" },
]
sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
@@ -842,12 +855,19 @@ http2 = [
]
[[package]]
name = "httpx-sse"
version = "0.4.3"
name = "httpx2"
version = "2.9.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" }
dependencies = [
{ name = "anyio" },
{ name = "httpcore2" },
{ name = "idna" },
{ name = "truststore" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" },
{ url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" },
]
[[package]]
@@ -861,11 +881,11 @@ wheels = [
[[package]]
name = "idna"
version = "3.15"
version = "3.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
]
[[package]]
@@ -1353,15 +1373,15 @@ wheels = [
[[package]]
name = "mcp"
version = "1.28.1"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "httpx" },
{ name = "httpx-sse" },
{ name = "httpx2" },
{ name = "jsonschema" },
{ name = "mcp-types" },
{ name = "opentelemetry-api" },
{ name = "pydantic" },
{ name = "pydantic-settings" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "python-multipart" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
@@ -1371,9 +1391,22 @@ dependencies = [
{ name = "typing-inspection" },
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" }
sdist = { url = "https://files.pythonhosted.org/packages/76/31/ac54fb0fdd5b37de704486e288bba4fbbb463f24cfcfedbede407b854513/mcp-2.2.0.tar.gz", hash = "sha256:2dc37ecb1974becdcebdbf7561e7c15a07dbbf20ba21ba16c3593b3038b3afbd", size = 4084129, upload-time = "2026-09-07T16:06:23.439Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" },
{ url = "https://files.pythonhosted.org/packages/1b/ff/8e7eade68b8a28f7da0ed1085544341b51f9c935dbf6b95c76b7edfea6a0/mcp-2.2.0-py3-none-any.whl", hash = "sha256:bde982589473a060ae145e3406e9a5333fe538c97229ba841f5a7f92be004f81", size = 365656, upload-time = "2026-09-07T16:06:19.711Z" },
]
[[package]]
name = "mcp-types"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/91/762d7755d971aff8a28d75f7961656148edf27875c8026e6385aaab08ae7/mcp_types-2.2.0.tar.gz", hash = "sha256:d3ed53703ddd10d9c6399f29d322bb66f3f67ab41348ac8556ba23e07fedefad", size = 65892, upload-time = "2026-09-07T16:06:25.187Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/d7/6ffba5d8cd5dd9b8a19478875c50e04945314ba5074e84d749283f27f62d/mcp_types-2.2.0-py3-none-any.whl", hash = "sha256:ea476b73ee86709ab5abc9452385ed36cc05907e582355622e294595c9a04f13", size = 69106, upload-time = "2026-09-07T16:06:21.461Z" },
]
[[package]]
@@ -1565,6 +1598,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
]
[[package]]
name = "opentelemetry-api"
version = "1.44.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" },
]
[[package]]
name = "oslex"
version = "2.0.0"
@@ -2857,7 +2902,7 @@ requires-dist = [
{ name = "joblib", specifier = "==1.5.1" },
{ name = "jupyter-book", marker = "extra == 'dev'", specifier = "==1.0.4.post1" },
{ name = "lsprotocol", specifier = "==2025.0.0" },
{ name = "mcp", specifier = "==1.28.1" },
{ name = "mcp", specifier = "==2.2.0" },
{ name = "oslex", specifier = "==2.0.0" },
{ name = "overrides", specifier = "==7.7.0" },
{ name = "pathspec", specifier = "==0.12.1" },
@@ -3520,6 +3565,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
]
[[package]]
name = "truststore"
version = "0.10.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" },
]
[[package]]
name = "ty"
version = "0.0.24"