diff --git a/pyproject.toml b/pyproject.toml index 759cd103..d760a684 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ [project.optional-dependencies] mcp = [ - "mcp>=1.0,<2.0", + "mcp>=2.0,<3.0", ] benchmark = [ "sentence-transformers>=3.0", diff --git a/src/semble/mcp.py b/src/semble/mcp.py index abfc283b..1d52878f 100644 --- a/src/semble/mcp.py +++ b/src/semble/mcp.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Annotated, Literal -from mcp.server.fastmcp import FastMCP +from mcp.server.mcpserver import MCPServer from pydantic import Field from semble.cache import get_validated_cache, save_index_to_cache @@ -18,6 +18,7 @@ from semble.index.dense import load_model from semble.types import ContentType from semble.utils import format_results, is_git_url, resolve_chunk +from semble.version import __version__ logger = logging.getLogger(__name__) @@ -68,10 +69,11 @@ def _resolve_content_selection( return (ContentType(content),) -def create_server(cache: _IndexCache, default_content: Sequence[ContentType] = (ContentType.CODE,)) -> FastMCP: - """Build and return a configured FastMCP server backed by the given cache.""" - server = FastMCP( +def create_server(cache: _IndexCache, default_content: Sequence[ContentType] = (ContentType.CODE,)) -> MCPServer: + """Build and return a configured MCP server backed by the given cache.""" + server = MCPServer( "semble", + version=__version__, instructions=( "Instant code search for any local or remote git repository. " "Call `search` once with a focused query, it returns the file path and exact line. " diff --git a/tests/test_cli.py b/tests/test_cli.py index 2b6a9ac3..3a581ed1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -175,7 +175,7 @@ def test_cli_entrypoint_works_without_mcp_installed( monkeypatch.setattr(sys, "argv", argv) monkeypatch.setitem(sys.modules, "mcp", None) monkeypatch.setitem(sys.modules, "mcp.server", None) - monkeypatch.setitem(sys.modules, "mcp.server.fastmcp", None) + monkeypatch.setitem(sys.modules, "mcp.server.mcpserver", None) monkeypatch.setitem(sys.modules, "semble.mcp", None) with patch("semble.cli.SembleIndex.from_path", return_value=fake_index): if expect_system_exit: diff --git a/tests/test_mcp.py b/tests/test_mcp.py index faa81f7d..197fd456 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -16,8 +16,8 @@ def _tool_text(result: Any) -> str: - """Extract the text string from a FastMCP call_tool result.""" - return result[0][0].text + """Extract the text string from an MCP call_tool result.""" + return result.content[0].text async def _call_tool( @@ -430,7 +430,7 @@ async def fake_stdio() -> None: ) with ( patch("semble.mcp.load_model", **load_kwargs), - patch("mcp.server.fastmcp.FastMCP.run_stdio_async", side_effect=fake_stdio) as mock_run, + patch("mcp.server.mcpserver.MCPServer.run_stdio_async", side_effect=fake_stdio) as mock_run, ): await serve() @@ -452,7 +452,7 @@ async def fake_run_stdio() -> None: with ( patch("semble.mcp.load_model", side_effect=blocking_load_model), - patch("mcp.server.fastmcp.FastMCP.run_stdio_async", side_effect=fake_run_stdio), + patch("mcp.server.mcpserver.MCPServer.run_stdio_async", side_effect=fake_run_stdio), ): await serve() diff --git a/uv.lock b/uv.lock index 04ba3aba..f089a0ed 100644 --- a/uv.lock +++ b/uv.lock @@ -1084,15 +1084,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, ] -[[package]] -name = "httpx-sse" -version = "0.4.3" -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" } -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" }, -] - [[package]] name = "httpx2" version = "2.12.0" @@ -1843,15 +1834,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.29.1" +version = "2.3.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'" }, @@ -1861,9 +1852,22 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/48/0bb26fdfe7ac16875f534a101ce2405eae192bdef37e7451f2f4507c13ec/mcp-1.29.1.tar.gz", hash = "sha256:1967ba4c315f7a375146209949f45950d18b0efd2f913d7cf3400bc723ee5f04", size = 646823, upload-time = "2026-08-24T18:30:41.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/8d/e0d339616f4810e9051d4aba6887afab289ab1f81875fe908b606cdfd0e3/mcp-2.3.0.tar.gz", hash = "sha256:8b147a50441cf059dc88c684e0aeed3687f0aa0f39c6cde7b90330effd2b34d8", size = 4171955, upload-time = "2026-10-02T22:06:56.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/04/d6b4fb82eefe9e81807aabca1ac98f460ae0883974b83a997aaa20c52545/mcp-1.29.1-py3-none-any.whl", hash = "sha256:b6310eeb59153300c4ab8b9aec4c52f4819a2d6a8e429eb43d908bed7c783648", size = 224653, upload-time = "2026-08-24T18:30:39.573Z" }, + { url = "https://files.pythonhosted.org/packages/20/d7/4a2878f128df729db3ca04e71bfce26405cc2e0c9ebe19c592feeb4c61b9/mcp-2.3.0-py3-none-any.whl", hash = "sha256:dd0c44c089d16453e8ae31a3877a0054d7a2314caaa81f5e0541b9b1734b2377", size = 368664, upload-time = "2026-10-02T22:06:52.119Z" }, +] + +[[package]] +name = "mcp-types" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/2d/7c251e34207f6c51000312fc8839111ac45cfe02023f90b44e7f1051dd8e/mcp_types-2.3.0.tar.gz", hash = "sha256:d1e46549edb35ee19a94940fcee6d1addd7e589ab7ea92dda83f5d84781fc362", size = 65898, upload-time = "2026-10-02T22:06:57.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/48/32583066b155bb5d4507e7887dc88ee23118cc4de8e2a12870c49719e2a8/mcp_types-2.3.0-py3-none-any.whl", hash = "sha256:968efdbdaedfab06adae40d378a34395f1090c5921d4be3c9cde283aaf76d91d", size = 69108, upload-time = "2026-10-02T22:06:54.099Z" }, ] [[package]] @@ -2422,6 +2426,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/a4/c7e89d3bfb8b7c7ffa38ec9fe527bccf4df723f752fb11bdb3da6c750b76/openai-3.8.0-py3-none-any.whl", hash = "sha256:514736aa1e4ef1033c1209ad53897392845ccd4f2c4fae6413b2cf5f91c2c926", size = 1740349, upload-time = "2026-09-03T19:51:08.596Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.45.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/dc/e12c1fe1ed8a7b7149777127b1a0e12ce5bd5a81d97408bedc2128c260f5/opentelemetry_api-1.45.0.tar.gz", hash = "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", size = 72115, upload-time = "2026-09-25T12:32:28.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b9/040d1a1c7836922828e6480cd2366bb8fe0ebf75b413d2bb51a9b0e7f78f/opentelemetry_api-1.45.0-py3-none-any.whl", hash = "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3", size = 60020, upload-time = "2026-09-25T12:32:03.192Z" }, +] + [[package]] name = "orjson" version = "3.12.0" @@ -2792,20 +2808,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/1d/068464f23075f66a8f1b806935e9cd9363ee446636ea70d2c22ee8659dbf/pydantic_core-2.46.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d0a24b40877af2de4950252be9d21eaf7fb07660f3c2cae1f56c6b599ada5266", size = 2140686, upload-time = "2026-08-28T10:01:28.947Z" }, ] -[[package]] -name = "pydantic-settings" -version = "2.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, -] - [[package]] name = "pydoclint" version = "0.9.1" @@ -2911,15 +2913,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/5e/21abf578182fb15006a57faf3711a1e659e29d600d19b6e557eae908c81d/python_discovery-1.6.0-py3-none-any.whl", hash = "sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a", size = 38451, upload-time = "2026-08-28T17:30:01.236Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, -] - [[package]] name = "python-multipart" version = "0.0.32" @@ -3872,7 +3865,7 @@ mcp = [ requires-dist = [ { name = "einops", marker = "extra == 'benchmark'", specifier = ">=0.8.2" }, { name = "matplotlib", marker = "extra == 'benchmark'", specifier = ">=3.7" }, - { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0,<2.0" }, + { name = "mcp", marker = "extra == 'mcp'", specifier = ">=2.0,<3.0" }, { name = "model2vec", specifier = ">=0.4.0,<0.10" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0" }, { name = "numpy", specifier = ">=1.24.0" },