Co-authored-by: Albert Jiang <aj@mistral.ai> Co-authored-by: Clément Drouin <clement.drouin@mistral.ai> Co-authored-by: Laure Hugo <201583486+laure0303@users.noreply.github.com> Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai> Co-authored-by: Mert Unsal <mert.unsal@mistral.ai> Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com> Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com> Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai> Co-authored-by: Quentin <quentin.torroba@mistral.ai> Co-authored-by: Mistral Vibe <vibe@mistral.ai>
213 lines
6.5 KiB
Python
213 lines
6.5 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
import tomllib
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import build_test_vibe_config
|
|
from vibe.core.config import MCPHttp, MCPOAuth, MCPStreamableHttp, VibeConfig
|
|
from vibe.core.tools.mcp_settings import (
|
|
MCPServerAddError,
|
|
parse_mcp_add_transport,
|
|
persist_oauth_mcp_server,
|
|
)
|
|
|
|
|
|
def _persisted_servers(config_dir: Path) -> list[dict]:
|
|
with (config_dir / "config.toml").open("rb") as file:
|
|
return tomllib.load(file).get("mcp_servers", [])
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("url", "expected_url"),
|
|
[
|
|
("https://mcp.example.com/mcp", "https://mcp.example.com/mcp"),
|
|
("http://localhost:8000/mcp", "http://localhost:8000/mcp"),
|
|
("http://127.0.0.1:8000/mcp", "http://127.0.0.1:8000/mcp"),
|
|
("https://example.com:443/mcp?tenant=a", "https://example.com/mcp?tenant=a"),
|
|
],
|
|
)
|
|
def test_add_oauth_mcp_server_accepts_supported_urls(
|
|
url: str, expected_url: str, config_dir: Path
|
|
) -> None:
|
|
result = persist_oauth_mcp_server(build_test_vibe_config(), url=url)
|
|
|
|
assert result.created is True
|
|
assert result.url == expected_url
|
|
assert _persisted_servers(config_dir)[0]["url"] == expected_url
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url",
|
|
[
|
|
"mcp.example.com/mcp",
|
|
"ftp://mcp.example.com/mcp",
|
|
"https:///mcp",
|
|
"http://mcp.example.com/mcp",
|
|
"https://mcp.example.com/mcp#section",
|
|
],
|
|
)
|
|
def test_add_oauth_mcp_server_rejects_invalid_urls(url: str) -> None:
|
|
with pytest.raises(MCPServerAddError):
|
|
persist_oauth_mcp_server(build_test_vibe_config(), url=url)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("url", "expected_name"),
|
|
[
|
|
("https://mcp.linear.app/mcp", "linear"),
|
|
("https://example.com/mcp", "example"),
|
|
("https://mcp.example.com/notion/mcp", "example"),
|
|
("https://mcp.localhost/notion/mcp", "notion"),
|
|
],
|
|
)
|
|
def test_add_oauth_mcp_server_generates_alias(url: str, expected_name: str) -> None:
|
|
result = persist_oauth_mcp_server(build_test_vibe_config(), url=url)
|
|
|
|
assert result.name == expected_name
|
|
|
|
|
|
def test_add_oauth_mcp_server_suffixes_generated_alias_collision(
|
|
config_dir: Path,
|
|
) -> None:
|
|
config = build_test_vibe_config(
|
|
mcp_servers=[
|
|
MCPStreamableHttp(
|
|
name="linear",
|
|
transport="streamable-http",
|
|
url="https://other.example.com/mcp",
|
|
)
|
|
]
|
|
)
|
|
|
|
result = persist_oauth_mcp_server(config, url="https://mcp.linear.app/mcp")
|
|
|
|
assert result.name == "linear_2"
|
|
assert _persisted_servers(config_dir)[0]["name"] == "linear_2"
|
|
|
|
|
|
def test_add_oauth_mcp_server_is_idempotent_for_existing_url(config_dir: Path) -> None:
|
|
first = persist_oauth_mcp_server(
|
|
build_test_vibe_config(), url="https://mcp.linear.app/mcp"
|
|
)
|
|
second = persist_oauth_mcp_server(
|
|
VibeConfig.load(), url="https://mcp.linear.app:443/mcp/"
|
|
)
|
|
|
|
assert first.created is True
|
|
assert second.created is False
|
|
assert second.name == "linear"
|
|
assert len(_persisted_servers(config_dir)) == 1
|
|
|
|
|
|
def test_add_oauth_mcp_server_rejects_active_static_url_match() -> None:
|
|
config = build_test_vibe_config(
|
|
mcp_servers=[
|
|
MCPStreamableHttp(
|
|
name="docs",
|
|
transport="streamable-http",
|
|
url="https://mcp.example.com/mcp",
|
|
)
|
|
]
|
|
)
|
|
|
|
with pytest.raises(MCPServerAddError, match="`docs` with static auth"):
|
|
persist_oauth_mcp_server(config, url="https://mcp.example.com/mcp")
|
|
|
|
|
|
def test_add_oauth_mcp_server_rejects_persisted_static_url_match() -> None:
|
|
VibeConfig.save_updates({
|
|
"mcp_servers": [
|
|
{
|
|
"name": "docs",
|
|
"transport": "streamable-http",
|
|
"url": "https://mcp.example.com/mcp",
|
|
}
|
|
]
|
|
})
|
|
|
|
with pytest.raises(MCPServerAddError, match="`docs` with static auth"):
|
|
persist_oauth_mcp_server(
|
|
build_test_vibe_config(), url="https://mcp.example.com/mcp"
|
|
)
|
|
|
|
|
|
def test_add_oauth_mcp_server_rejects_existing_url_with_different_name() -> None:
|
|
persist_oauth_mcp_server(build_test_vibe_config(), url="https://mcp.linear.app/mcp")
|
|
|
|
with pytest.raises(MCPServerAddError, match="already configured as `linear`"):
|
|
persist_oauth_mcp_server(
|
|
VibeConfig.load(), url="https://mcp.linear.app/mcp", name="other"
|
|
)
|
|
|
|
|
|
def test_add_oauth_mcp_server_rejects_explicit_alias_collision() -> None:
|
|
config = build_test_vibe_config(
|
|
mcp_servers=[
|
|
MCPStreamableHttp(
|
|
name="linear",
|
|
transport="streamable-http",
|
|
url="https://other.example.com/mcp",
|
|
)
|
|
]
|
|
)
|
|
|
|
with pytest.raises(MCPServerAddError, match="name `linear` is already configured"):
|
|
persist_oauth_mcp_server(
|
|
config, url="https://mcp.example.com/mcp", name="linear"
|
|
)
|
|
|
|
|
|
def test_add_oauth_mcp_server_persists_loadable_oauth_config() -> None:
|
|
persist_oauth_mcp_server(
|
|
build_test_vibe_config(),
|
|
url="https://mcp.example.com/mcp",
|
|
name="docs",
|
|
scopes=["read", "write"],
|
|
)
|
|
|
|
server = VibeConfig.load().mcp_servers[0]
|
|
|
|
assert isinstance(server, MCPStreamableHttp)
|
|
assert server.name == "docs"
|
|
assert isinstance(server.auth, MCPOAuth)
|
|
assert server.auth.scopes == ["read", "write"]
|
|
|
|
|
|
def test_add_oauth_mcp_server_persists_http_transport() -> None:
|
|
persist_oauth_mcp_server(
|
|
build_test_vibe_config(),
|
|
url="https://mcp.example.com/mcp",
|
|
name="docs",
|
|
transport="http",
|
|
)
|
|
|
|
server = VibeConfig.load().mcp_servers[0]
|
|
|
|
assert isinstance(server, MCPHttp)
|
|
assert server.transport == "http"
|
|
assert isinstance(server.auth, MCPOAuth)
|
|
|
|
|
|
def test_parse_mcp_add_transport_rejects_unsupported_transport() -> None:
|
|
with pytest.raises(MCPServerAddError, match="http, streamable-http"):
|
|
parse_mcp_add_transport("sse")
|
|
|
|
|
|
def test_vibe_config_rejects_duplicate_mcp_server_names() -> None:
|
|
with pytest.raises(ValueError, match="Duplicate MCP server name found: 'figma'"):
|
|
VibeConfig.model_validate({
|
|
"mcp_servers": [
|
|
{
|
|
"name": "figma",
|
|
"transport": "streamable-http",
|
|
"url": "https://a.example.com/mcp",
|
|
},
|
|
{
|
|
"name": "figma",
|
|
"transport": "streamable-http",
|
|
"url": "https://b.example.com/mcp",
|
|
},
|
|
]
|
|
})
|