Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Status of the `main` branch. Changes prior to the next official version change will appear here.

* General:
- Upgrade the `mcp` SDK from 1.28.1 to 2.0.0 (MCP protocol revision 2026-07-28). Two behavioural
consequences: `FASTMCP_*` environment variables no longer configure the MCP server (the SDK's
`Settings` is no longer environment-backed), and `SerenaMCPFactory.create_mcp_server()` no longer
takes `host`/`port`, which the SDK moved to the transport-specific `run()` call
- Fix: the README, the Language Support docs page and the project template omitted several already-supported language servers
- Fix: a tool call exceeding the timeout blocked the task executor indefinitely; the executor now
recovers without user-induced cancellation
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies = [
"requests==2.33.0",
"overrides==7.7.0",
"python-dotenv==1.2.2",
"mcp==1.28.1",
"mcp==2.0.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",
Expand Down
10 changes: 7 additions & 3 deletions src/serena/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,6 @@ def start_mcp_server(

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=LanguageBackend.from_str(language_backend) if language_backend else None,
enable_web_dashboard=enable_web_dashboard,
Expand All @@ -389,7 +387,13 @@ def start_mcp_server(
project_file,
)
log.info("Starting MCP server …")
server.run(transport=transport)
run_kwargs: dict[str, Any] = {"transport": transport}
if transport != "stdio":
# host/port are transport-specific run() kwargs as of mcp>=2.0 (no longer
# accepted at server construction time); stdio has no host/port concept.
run_kwargs["host"] = host
run_kwargs["port"] = port
server.run(**run_kwargs)

@staticmethod
@click.command(
Expand Down
38 changes: 18 additions & 20 deletions src/serena/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@
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.agent import (
Expand Down Expand Up @@ -108,8 +107,8 @@ def execute_fn(**kwargs) -> str:
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__(
Expand All @@ -131,7 +130,7 @@ def execute_fn(**kwargs) -> str:
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
Expand Down Expand Up @@ -317,8 +316,6 @@ def _create_default_serena_config(self) -> SerenaConfig:

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,
enable_web_dashboard: bool | None = None,
Expand All @@ -331,8 +328,9 @@ def create_mcp_server(
"""
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
Note: host/port are no longer accepted here; the underlying SDK (mcp>=2.0) moved them
from server construction to the transport-specific `run()` call (see FastMCP.run_*_async).

:param mode_selection_def: the mode selection definition to apply
:param language_backend: the language backend to use, overriding the configuration setting.
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
Expand Down Expand Up @@ -371,18 +369,18 @@ def create_mcp_server(
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_")
# NOTE: mcp<2.0 read MCP settings from the environment and from `.env` files (`Settings` was a
# pydantic-settings `BaseSettings`), so we had to override its `model_config` here to stop `.env`
# files in user projects (e.g. containing LOG_LEVEL) from overriding MCP settings. As of mcp>=2.0,
# `Settings` is a plain pydantic `BaseModel` that is only populated from the `MCPServer`
# constructor, so it reads neither the environment nor `.env` files and the override is obsolete.
# Behavioural consequence: FASTMCP_* environment variables no longer configure the server.
instructions = self._get_initial_instructions()
log.info("MCP server initial instructions:\n%s", instructions)
mcp = FastMCP(
name="Serena",
lifespan=self.server_lifespan,
website_url="https://oraios.github.io/serena",
host=host,
port=port,
instructions=instructions,
)
return mcp
Expand Down
4 changes: 2 additions & 2 deletions src/serena/tools/tools_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from typing import TYPE_CHECKING, Any, Optional, Protocol, Self, 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.string import dict_string

Expand Down
2 changes: 1 addition & 1 deletion test/serena/test_mcp.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Tests for the mcp.py module in serena."""

import pytest
from mcp.server.fastmcp.tools.base import Tool as MCPTool
from mcp.server.mcpserver.tools.base import Tool as MCPTool

from serena.agent import Tool, ToolRegistry
from serena.config.context_mode import SerenaAgentContext
Expand Down
82 changes: 68 additions & 14 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading