---
name: mcp-test-suite
description: Generates a comprehensive mock-based pytest test suite for a FastMCP server in pi_mcps. Use this skill when adding test coverage to a new or existing MCP server — produces conftest.py and test_server.py with 100% tool coverage and proper mock isolation.
---

# MCP Test Suite

## When to use
- New MCP server needs a test suite
- Existing server has missing coverage
- Adding new tools to an existing server

## When NOT to use
- Non-MCP Python code (use standard pytest patterns)
- Integration tests that actually hit external APIs (mock instead)

## Inputs required
- **Server name** — `mcp-{name}`
- **Tool list** — each tool's name, parameters, and return type
- **External dependencies** — HTTP clients, SDKs, env vars

## Workflow

### Step 1 — Write `tests/conftest.py`

```python
import sys
from pathlib import Path
import pytest

# Make src/ importable
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

@pytest.fixture
def mock_env(monkeypatch):
    """Set required environment variables."""
    monkeypatch.setenv("API_KEY", "test-key")
    monkeypatch.setenv("API_URL", "https://test.example.com")
```

### Step 2 — Write `tests/test_server.py`

Structure per tool:
```python
import pytest
from unittest.mock import patch, MagicMock
from server import tool_name  # import directly from server module

class TestToolName:
    def test_happy_path(self, mock_env):
        with patch("server.httpx.get") as mock_get:
            mock_get.return_value = MagicMock(
                status_code=200,
                json=lambda: {"key": "value"}
            )
            result = tool_name("test-param")
        assert "expected" in result

    def test_error_handling(self, mock_env):
        with patch("server.httpx.get") as mock_get:
            mock_get.side_effect = Exception("Connection refused")
            result = tool_name("test-param")
        assert "error" in result.lower()

    def test_empty_input(self, mock_env):
        # edge case — empty string, None, etc.
        result = tool_name("")
        assert result is not None
```

### Step 3 — Coverage checklist
For each tool, cover:
- [ ] Happy path with expected response
- [ ] Network/API error (exception raised)
- [ ] Empty or invalid input
- [ ] Edge case specific to the tool's logic

### Step 4 — Run and verify
```bash
cd mcp/{name}
uv run pytest tests/ -v --tb=short
```

Expected: all tests pass, no warnings about missing coverage

### Step 5 — Store result in BigMind
```
memory_store_fact("codebase", "mcp-{name} test suite: N tests, all passing. Coverage: happy path + error + edge cases per tool.")
```

## Troubleshooting
- **ImportError on `from server import ...`:** Check `conftest.py` sys.path insert
- **Mock not intercepting:** Patch the name as used in server.py, not the library's own namespace
  - ✅ `patch("server.httpx.get")` — patches where it's used
  - ❌ `patch("httpx.get")` — patches library origin (may not intercept)
- **Env var not set in test:** Add to `mock_env` fixture in conftest.py
