5.2 Brancher le MCP et tester en mémoire
Intention: attacher les tools à l'instance FastMCP et garder des tests rapides sans dépendre de
MongoDB.
Registration
Créer src/todo_list_service/adapters/inbound/fastmcp/register.py:
| Python |
|---|
| from __future__ import annotations
import fastmcp
from arclith import Arclith
from todo_list_service.adapters.inbound.fastmcp.tools import TodoMCP
from todo_list_service.infrastructure.containers.todo_container import (
build_create_todo_use_case,
build_list_todos_use_case,
)
def register_tools(mcp: fastmcp.FastMCP, arclith: Arclith) -> None:
create_todo = build_create_todo_use_case(arclith)
list_todos = build_list_todos_use_case(arclith)
TodoMCP(create_todo, list_todos, mcp)
|
Entrypoint API + MCP
Modifier main.py:
| Python |
|---|
| """Application entrypoint for todo-list-service."""
from __future__ import annotations
import os
import sys
from pathlib import Path
import fastmcp
from arclith import Arclith
from todo_list_service.adapters.inbound.fastapi.register import register_routers
from todo_list_service.adapters.inbound.fastmcp.register import register_tools
_DEFAULT_CONFIG = Path(__file__).parent / "config"
_CONFIG = Path(os.getenv("TODO_LIST_CONFIG_DIR", str(_DEFAULT_CONFIG)))
_VALID_MODES = {"api", "mcp_http", "all"}
MODE = os.getenv("MODE", "api")
if MODE not in _VALID_MODES:
print(f"MODE invalide: {MODE!r}. Valeurs: {sorted(_VALID_MODES)}", file=sys.stderr)
sys.exit(1)
arclith = Arclith(_CONFIG)
app = arclith.fastapi()
register_routers(app, arclith)
def build_mcp(config_dir: Path | str | None = None) -> fastmcp.FastMCP:
current_arclith = arclith if config_dir is None else Arclith(Path(config_dir))
mcp = current_arclith.fastmcp("Todo MCP")
register_tools(mcp, current_arclith)
current_arclith.instrument_mcp(mcp)
return mcp
def _run_api() -> None:
arclith.run_api("main:app")
def _run_mcp_http() -> None:
arclith.run_mcp_http(build_mcp())
if __name__ == "__main__":
match MODE:
case "api":
arclith.run_with_probes(_run_api, transports=["api"])
case "mcp_http":
arclith.run_with_probes(_run_mcp_http, transports=["mcp_http"])
case "all":
arclith.run_with_probes(_run_api, _run_mcp_http, transports=["api", "mcp_http"])
|
Tests en mémoire
Créer tests/conftest.py:
| Python |
|---|
| from collections.abc import Iterator
from pathlib import Path
import shutil
import pytest
from todo_list_service.infrastructure.containers.todo_container import clear_todo_repository_cache
_PROJECT_ROOT = Path(__file__).resolve().parents[1]
_RUNTIME_CONFIG = _PROJECT_ROOT / "config"
@pytest.fixture
def memory_config(tmp_path: Path) -> Path:
config_dir = tmp_path / "config"
shutil.copytree(_RUNTIME_CONFIG, config_dir)
(config_dir / "adapters" / "adapters.yaml").write_text(
"logger: console\n"
"repository: memory\n"
"observability:\n"
" enabled: []\n",
encoding="utf-8",
)
return config_dir
@pytest.fixture(autouse=True)
def reset_todo_repository_cache() -> Iterator[None]:
clear_todo_repository_cache()
yield
clear_todo_repository_cache()
|
Modifier tests/test_project_bootstrap.py:
| Python |
|---|
| from pathlib import Path
from arclith import Arclith
def test_project_config_loads(memory_config: Path) -> None:
app = Arclith(memory_config)
assert app.config.app.name
assert app.config.adapters.repository == "memory"
def test_package_imports() -> None:
import todo_list_service
assert todo_list_service.__name__ == "todo_list_service"
|
Créer tests/test_todo_mcp.py:
| Python |
|---|
| from pathlib import Path
import pytest
from fastmcp import Client
from main import build_mcp
@pytest.mark.asyncio
async def test_mcp_create_and_list_todos(memory_config: Path) -> None:
async with Client(build_mcp(memory_config)) as client:
tools = await client.list_tools()
assert {tool.name for tool in tools} >= {"create_todo_item", "list_todo_items"}
result = await client.call_tool(
"create_todo_item",
{
"title": "Tester le MCP",
"description": "Appeler le meme use case que l'API",
"due_date": "2026-09-01",
"status": "todo",
},
)
assert not result.is_error
assert isinstance(result.structured_content, dict)
listed = await client.call_tool("list_todo_items", {})
assert not listed.is_error
assert isinstance(listed.structured_content, dict)
assert listed.structured_content["result"] == [result.structured_content]
|
Ces tests construisent une configuration memory temporaire. Le MCP crée une todo puis vérifie que
list_todo_items relit exactement le même payload dans le même processus.
Lancer:
| Bash |
|---|
| uv run python -m pytest tests/test_project_bootstrap.py tests/test_todo_mcp.py
|
Smoke HTTP MCP
| Bash |
|---|
| MODE=mcp_http uv run python main.py
|
Le serveur écoute sur:
| Text Only |
|---|
| http://127.0.0.1:8121/mcp
|
Étape suivante: tester dans LM Studio.