Problem
Each MCP repo needs read-only smoke tests that verify CLI and MCP tools work against live backends. Currently:
mcp_common.testing provides mcp_client, assert_tool_exists, assert_tool_success for in-process MCP testing
pyproject.toml across all MCP repos declares smoke / integration / e2e markers but no shared test patterns exist for smoke tests
- CI workflows in netbox-mcp and redfish-mcp have ad-hoc "smoke" steps (import check,
--help, pytest) but nothing reusable
- Every MCP repo reimplements .env loading, CLI subprocess helpers, credential skip logic, and MCP client initialization
The awx-mcp repo now has a reference implementation (tests/test_smoke.py, PR #25) that exercises both CLI and MCP paths. The reusable patterns should be extracted to mcp_common.testing.
What to Extract
1. .env loader for test contexts
from mcp_common.testing import load_dotenv_for_tests
load_dotenv_for_tests() # searches .env up the directory tree
Simple helper that loads .env without requiring python-dotenv — just key=value parsing with comment/quote stripping. Every MCP smoke test needs this since secrets live in .env locally.
2. CLI subprocess runner
from mcp_common.testing import cli_runner, cli_json
# Run any CLI command, auto-discovers binary or falls back to `uv run`
result = cli_runner("awx-cli", "ping")
assert result.returncode == 0
# Run with --json and return parsed output
data = cli_json("netbox-cli", "search", "dcim.device", "--query", "gpu001")
assert data["count"] > 0
Features:
- Auto-discovers CLI binary on PATH, falls back to
uv run <name>
- Configurable timeout (default 30s)
- Sets cwd to repo root
cli_json variant parses JSON output
3. Credential-gated skip decorator
from mcp_common.testing import require_env
@require_env("AWX_HOST", "AWX_TOKEN")
class TestAWXSmoke:
...
@require_env("NETBOX_URL", "NETBOX_TOKEN")
class TestNetBoxSmoke:
...
Replaces the boilerplate pytestmark = [pytest.mark.skipif(not os.getenv(...), reason=...)] pattern that every MCP repo duplicates.
4. MCP server initializer for smoke tests
from mcp_common.testing import smoke_mcp_client
@pytest.fixture
async def client():
# Auto-discovers the FastMCP server and initializes backend client from Settings
async for c in smoke_mcp_client("awx_mcp.server", "mcp", "awx"):
yield c
Handles the common pattern of:
- Import the server module
- Check if the backend client global is None
- Initialize it from the project's Settings class
- Wrap in
mcp_client()
5. Pytest marker registration
Add smoke to the standard marker set in mcp_common docs/template so mcp-template and all downstream repos get it.
Reference Implementation
See vhspace/awx-mcp PR #25, specifically tests/test_smoke.py:
_load_dotenv() — .env loading
_awx_cli() / _cli_json() — CLI subprocess helpers
pytestmark skip logic — credential gating
TestMCPSmoke.client fixture — MCP server initialization
Acceptance Criteria
Why This Matters
There are 11+ downstream MCP repos. Each will need smoke tests. Without shared helpers, each will reimplement .env loading, CLI subprocess wrappers, and credential skip logic slightly differently. The awx-mcp implementation took ~180 lines — with shared helpers it would be ~50 lines of actual test logic.
Problem
Each MCP repo needs read-only smoke tests that verify CLI and MCP tools work against live backends. Currently:
mcp_common.testingprovidesmcp_client,assert_tool_exists,assert_tool_successfor in-process MCP testingpyproject.tomlacross all MCP repos declaressmoke/integration/e2emarkers but no shared test patterns exist for smoke tests--help, pytest) but nothing reusableThe
awx-mcprepo now has a reference implementation (tests/test_smoke.py, PR #25) that exercises both CLI and MCP paths. The reusable patterns should be extracted tomcp_common.testing.What to Extract
1.
.envloader for test contextsSimple helper that loads
.envwithout requiringpython-dotenv— just key=value parsing with comment/quote stripping. Every MCP smoke test needs this since secrets live in.envlocally.2. CLI subprocess runner
Features:
uv run <name>cli_jsonvariant parses JSON output3. Credential-gated skip decorator
Replaces the boilerplate
pytestmark = [pytest.mark.skipif(not os.getenv(...), reason=...)]pattern that every MCP repo duplicates.4. MCP server initializer for smoke tests
Handles the common pattern of:
mcp_client()5. Pytest marker registration
Add
smoketo the standard marker set inmcp_commondocs/template somcp-templateand all downstream repos get it.Reference Implementation
See
vhspace/awx-mcpPR #25, specificallytests/test_smoke.py:_load_dotenv()— .env loading_awx_cli()/_cli_json()— CLI subprocess helperspytestmarkskip logic — credential gatingTestMCPSmoke.clientfixture — MCP server initializationAcceptance Criteria
mcp_common.testingexports:load_dotenv_for_tests,cli_runner,cli_json,require_env,smoke_mcp_clientpython-dotenvneeded)mcp_common.testingmodule docstringmcp-templateupdated with example smoke test using the new helpersmcp_client,assert_tool_exists,assert_tool_successremain unchangedWhy This Matters
There are 11+ downstream MCP repos. Each will need smoke tests. Without shared helpers, each will reimplement .env loading, CLI subprocess wrappers, and credential skip logic slightly differently. The awx-mcp implementation took ~180 lines — with shared helpers it would be ~50 lines of actual test logic.