feat(app_controller): Integrate MCP configuration loading and add tests

This commit is contained in:
2026-03-12 15:33:37 -04:00
parent 1c863f0f0c
commit c09e0f50be
2 changed files with 119 additions and 0 deletions

View File

@@ -197,6 +197,7 @@ class AppController:
self._pending_dialog_open: bool = False
self._pending_actions: Dict[str, ConfirmDialog] = {}
self._pending_ask_dialog: bool = False
self.mcp_config: models.MCPConfiguration = models.MCPConfiguration()
# AI settings state
self._current_provider: str = "gemini"
self._current_model: str = "gemini-2.5-flash-lite"
@@ -894,6 +895,18 @@ class AppController:
self.tool_presets = self.tool_preset_manager.load_all_presets()
self.bias_profiles = self.tool_preset_manager.load_all_bias_profiles()
mcp_path = self.project.get('project', {}).get('mcp_config_path') or self.config.get('ai', {}).get('mcp_config_path')
if mcp_path:
mcp_p = Path(mcp_path)
if not mcp_p.is_absolute() and self.active_project_path:
mcp_p = Path(self.active_project_path).parent / mcp_path
if mcp_p.exists():
self.mcp_config = models.load_mcp_config(str(mcp_p))
else:
self.mcp_config = models.MCPConfiguration()
else:
self.mcp_config = models.MCPConfiguration()
from src.personas import PersonaManager
self.persona_manager = PersonaManager(Path(self.active_project_path).parent if self.active_project_path else None)
self.personas = self.persona_manager.load_all()

View File

@@ -0,0 +1,106 @@
import os
import json
import pytest
from pathlib import Path
from src.app_controller import AppController
from src import models
@pytest.fixture
def controller(tmp_path):
# Setup mock config and project files
config_path = tmp_path / "config.toml"
project_path = tmp_path / "project.toml"
mcp_config_path = tmp_path / "mcp_config.json"
config_data = {
"ai": {
"mcp_config_path": str(mcp_config_path)
},
"projects": {
"paths": [str(project_path)],
"active": str(project_path)
}
}
project_data = {
"project": {
"name": "test-project",
"mcp_config_path": "project_mcp.json" # Relative path
}
}
mcp_data = {
"mcpServers": {
"global-server": {"command": "echo"}
}
}
project_mcp_data = {
"mcpServers": {
"project-server": {"command": "echo"}
}
}
# We can't easily use models.save_config because it uses a hardcoded path
# But AppController.init_state calls models.load_config() which uses CONFIG_PATH
return AppController()
def test_app_controller_mcp_loading(tmp_path, monkeypatch):
# Mock CONFIG_PATH to point to our temp config
config_file = tmp_path / "config.toml"
monkeypatch.setattr(models, "CONFIG_PATH", str(config_file))
mcp_global_file = tmp_path / "mcp_global.json"
mcp_global_file.write_text(json.dumps({"mcpServers": {"global": {"command": "echo"}}}))
config_content = f"""
[ai]
mcp_config_path = "{mcp_global_file.as_posix()}"
[projects]
paths = []
active = ""
"""
config_file.write_text(config_content)
ctrl = AppController()
# Mock _load_active_project to not do anything for now
monkeypatch.setattr(ctrl, "_load_active_project", lambda: None)
ctrl.project = {}
ctrl.init_state()
assert "global" in ctrl.mcp_config.mcpServers
assert ctrl.mcp_config.mcpServers["global"].command == "echo"
def test_app_controller_mcp_project_override(tmp_path, monkeypatch):
config_file = tmp_path / "config.toml"
monkeypatch.setattr(models, "CONFIG_PATH", str(config_file))
project_file = tmp_path / "project.toml"
mcp_project_file = tmp_path / "mcp_project.json"
mcp_project_file.write_text(json.dumps({"mcpServers": {"project": {"command": "echo"}}}))
config_content = f"""
[ai]
mcp_config_path = "non-existent.json"
[projects]
paths = ["{project_file.as_posix()}"]
active = "{project_file.as_posix()}"
"""
config_file.write_text(config_content)
ctrl = AppController()
ctrl.active_project_path = str(project_file)
ctrl.project = {
"project": {
"mcp_config_path": "mcp_project.json"
}
}
# Mock _load_active_project to keep our manual project dict
monkeypatch.setattr(ctrl, "_load_active_project", lambda: None)
ctrl.init_state()
assert "project" in ctrl.mcp_config.mcpServers
assert "non-existent" not in ctrl.mcp_config.mcpServers