Co-authored-by: Carlo <carloantonio.patti@mistral.ai>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Clement Sirieix <clem.sirieix@gmail.com>
Co-authored-by: Michel Thomazo <michel.thomazo@mistral.ai>
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Vincent Guilloux <vincent.guilloux@mistral.ai>
Co-authored-by: Thomas Kenbeek <thomas.kenbeek@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Quentin 2026-02-27 17:31:58 +01:00 committed by GitHub
parent a560a47ce8
commit 5d2e01a6d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 7152 additions and 1457 deletions

View file

@ -11,6 +11,7 @@ import pytest
from vibe.core.config import MCPHttp, MCPStdio, MCPStreamableHttp
from vibe.core.tools.mcp import (
MCPRegistry,
MCPToolResult,
RemoteTool,
_mcp_stderr_capture,
@ -372,3 +373,181 @@ class TestMCPConfigModels:
# Trailing special chars become underscores which are then stripped
assert config.name == "my_server"
class TestMCPRegistry:
def _make_http_server(
self, name: str, url: str = "http://localhost:8080"
) -> MCPHttp:
return MCPHttp(name=name, transport="http", url=url)
def _make_stdio_server(self, name: str, command: str = "python -m srv") -> MCPStdio:
return MCPStdio(name=name, transport="stdio", command=command)
def test_server_key_is_stable(self):
srv = self._make_http_server("s1")
registry = MCPRegistry()
assert registry._server_key(srv) == registry._server_key(srv)
def test_different_configs_produce_different_keys(self):
registry = MCPRegistry()
s1 = self._make_http_server("s1", url="http://a:1")
s2 = self._make_http_server("s2", url="http://b:2")
assert registry._server_key(s1) != registry._server_key(s2)
def test_get_tools_caches_discovery(self):
registry = MCPRegistry()
srv = self._make_http_server("cached")
remote = RemoteTool(name="tool_a", description="A tool")
proxy = create_mcp_http_proxy_tool_class(
url="http://localhost:8080", remote=remote, alias="cached"
)
key = registry._server_key(srv)
registry._cache[key] = {proxy.get_name(): proxy}
tools = registry.get_tools([srv])
assert "cached_tool_a" in tools
assert tools["cached_tool_a"] is proxy
def test_get_tools_returns_empty_for_no_servers(self):
registry = MCPRegistry()
assert registry.get_tools([]) == {}
def test_clear_drops_cache(self):
registry = MCPRegistry()
srv = self._make_http_server("s")
proxy = create_mcp_http_proxy_tool_class(
url="http://localhost:8080", remote=RemoteTool(name="t"), alias="s"
)
key = registry._server_key(srv)
registry._cache[key] = {proxy.get_name(): proxy}
registry.clear()
assert len(registry._cache) == 0
def test_cache_survives_multiple_get_tools_calls(self):
registry = MCPRegistry()
srv = self._make_http_server("stable")
remote = RemoteTool(name="t1")
proxy = create_mcp_http_proxy_tool_class(
url="http://localhost:8080", remote=remote, alias="stable"
)
key = registry._server_key(srv)
registry._cache[key] = {proxy.get_name(): proxy}
first = registry.get_tools([srv])
second = registry.get_tools([srv])
assert first == second
assert first["stable_t1"] is second["stable_t1"]
def test_disjoint_server_lists_across_agents(self):
registry = MCPRegistry()
srv_x = self._make_http_server("x", url="http://x:1")
srv_y = self._make_http_server("y", url="http://y:2")
proxy_x = create_mcp_http_proxy_tool_class(
url="http://x:1", remote=RemoteTool(name="tx"), alias="x"
)
proxy_y = create_mcp_http_proxy_tool_class(
url="http://y:2", remote=RemoteTool(name="ty"), alias="y"
)
registry._cache[registry._server_key(srv_x)] = {proxy_x.get_name(): proxy_x}
registry._cache[registry._server_key(srv_y)] = {proxy_y.get_name(): proxy_y}
agent_a_tools = registry.get_tools([srv_x])
agent_b_tools = registry.get_tools([srv_y])
assert "x_tx" in agent_a_tools
assert "y_ty" not in agent_a_tools
assert "y_ty" in agent_b_tools
assert "x_tx" not in agent_b_tools
@pytest.mark.asyncio
async def test_discover_http_success(self):
registry = MCPRegistry()
srv = self._make_http_server("demo", url="http://demo:9090")
remote = RemoteTool(name="hello", description="Hi")
with patch(
"vibe.core.tools.mcp.registry.list_tools_http", return_value=[remote]
):
tools = await registry._discover_http(srv)
assert tools is not None
assert len(tools) == 1
name = next(iter(tools))
assert name == "demo_hello"
@pytest.mark.asyncio
async def test_discover_http_failure_returns_none(self):
registry = MCPRegistry()
srv = self._make_http_server("fail", url="http://fail:1")
with patch(
"vibe.core.tools.mcp.registry.list_tools_http",
side_effect=ConnectionError("down"),
):
tools = await registry._discover_http(srv)
assert tools is None
@pytest.mark.asyncio
async def test_discover_stdio_success(self):
registry = MCPRegistry()
srv = self._make_stdio_server("local", command="python -m local_srv")
remote = RemoteTool(name="run", description="Run it")
with patch(
"vibe.core.tools.mcp.registry.list_tools_stdio", return_value=[remote]
):
tools = await registry._discover_stdio(srv)
assert tools is not None
assert len(tools) == 1
name = next(iter(tools))
assert name == "local_run"
@pytest.mark.asyncio
async def test_discover_stdio_failure_returns_none(self):
registry = MCPRegistry()
srv = self._make_stdio_server("broken")
with patch(
"vibe.core.tools.mcp.registry.list_tools_stdio",
side_effect=OSError("no binary"),
):
tools = await registry._discover_stdio(srv)
assert tools is None
def test_get_tools_discovers_only_uncached(self):
registry = MCPRegistry()
cached_srv = self._make_http_server("cached", url="http://c:1")
new_srv = self._make_http_server("new", url="http://n:2")
cached_proxy = create_mcp_http_proxy_tool_class(
url="http://c:1", remote=RemoteTool(name="ct"), alias="cached"
)
registry._cache[registry._server_key(cached_srv)] = {
cached_proxy.get_name(): cached_proxy
}
new_remote = RemoteTool(name="nt")
with patch(
"vibe.core.tools.mcp.registry.list_tools_http", return_value=[new_remote]
):
tools = registry.get_tools([cached_srv, new_srv])
assert "cached_ct" in tools
assert "new_nt" in tools
assert len(registry._cache) == 2