v2.3.0 (#429)
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:
parent
a560a47ce8
commit
5d2e01a6d7
139 changed files with 7152 additions and 1457 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue