New to Claude Skills? Learn how to install them →

Pprowler-cloud on GitHub

Prowler MCP Testing

Free

Streamlined testing for Prowler MCP Server components.

Get this skill

Free · Opens the source repo

What Prowler MCP Testing does

Prowler MCP Testing provides a structured approach to testing components within the Prowler MCP Server environment. This skill is designed specifically for developers working with the Prowler API, enabling them to create robust tests for tools, models, and API clients. It leverages in-memory FastMCP clients and includes a suite of fixtures to facilitate accurate and efficient testing without the need for real network connections.

The skill emphasizes best practices for writing tests, such as using the mock_api_client fixture to avoid direct instantiation of the ProwlerAPIClient. It also establishes critical rules for managing tool parameters and environment variables, ensuring that tests are deterministic and do not inadvertently affect each other. By adhering to these guidelines, developers can avoid common pitfalls that lead to hard-to-diagnose issues, particularly with asynchronous operations.

Included in the skill are example test files that demonstrate how to implement tool tests, model tests, and contract tests. These examples serve as a practical reference for developers, showcasing how to register routes, assert on structured output, and manage dependencies effectively. The skill also outlines the necessary directory structure for organizing tests, ensuring that they remain modular and maintainable.

Overall, Prowler MCP Testing is an essential tool for any developer working with the Prowler MCP Server, providing them with the resources needed to ensure their code is reliable and well-tested.

When to use it

Use this skill when developing or testing components of the Prowler MCP Server, particularly when you need to ensure the reliability of your API interactions.

When not to use it

This skill is not suitable for testing components outside the Prowler MCP ecosystem or for scenarios requiring real network interactions.

What you can build with it

Testing API Clients

Utilize the mock API client fixture to test interactions with the Prowler API without making real network calls.

Contract Testing

Ensure the API contract remains consistent by writing contract tests that validate the expected behavior of your tools.

Model Validation

Test your data models using the provided helpers to assert that they correctly handle API responses.

How to install Prowler MCP Testing

View source

1. Install with the skills CLI

npx skills add prowler-cloud/prowler/prowler-test-mcp --agent claude-code

2. Or install it manually

Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.

Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs

Inside SKILL.md

Written by prowler-cloud

Critical Rules

  • ALWAYS drive tools through an in-memory client: async with Client(mcp_root_server). Tool parameters use pydantic Field(default=...), and only FastMCP's wrapper resolves those defaults. Calling a tool method directly with an argument omitted leaves it as a raw FieldInfo — which is truthy, so if email: silently builds a filter out of the FieldInfo repr. Direct calls MUST pass every argument.
  • NEVER open a fastmcp.Client inside a fixture. FastMCP warns this causes hard-to-diagnose event-loop issues; open it inline in the test.
  • ALWAYS use the mock_api_client fixture; NEVER construct a ProwlerAPIClient. Tool instances captured the singleton by reference at import time, so only an in-place patch of .client reaches them.
  • NEVER clear SingletonMeta._instances. It orphans every registered tool on an instance holding a real httpx.AsyncClient. Use isolated_api_client if you genuinely need a fresh instance.
  • NEVER strip PROWLER_API_KEY. Tools are built at import time and a construction failure is swallowed, so the whole prowler_* namespace silently drops to zero tools. It is pinned in [tool.pytest_env].
  • For ProwlerAppAuth, pass mode= / base_url= explicitly. Those are resolved in default arguments, evaluated once at module import, so monkeypatch.setenv has no effect on them.
  • NEVER assert an exact tool count — every future branch would have to bump it.
  • Assert on result.data (structured output), not result.content[0].text.
  • Tests are test_*.py (prefix), like the API — not the SDK's *_test.py suffix.
  • __init__.py IS required in every tests/ subdirectory here (unlike the SDK's repo-root tests/), or same-named modules collide under pytest's import mode.
  • Async tests need no marker (asyncio_mode = "auto"). Do not use @pytest.mark.anyio.
  • Use only obviously-fake credentials from tests.helpers.tokens (TruffleHog).
  • One behaviour per test; keep tests self-contained and order-independent.

1. Layout

Mirror the source tree below the package root — drop the prowler_mcp_server/ level, exactly as the SDK maps prowler/providers/... to tests/providers/.... So prowler_mcp_server/prowler_app/tools/ is tested in tests/prowler_app/tools/.

mcp_server/tests/
├── conftest.py                  # all shared fixtures
├── helpers/                     # jsonapi.py, http.py, assertions.py, tokens.py
├── test_server.py               # mounted-server contract
├── test_health.py
├── prowler_app/{models,tools,utils}/
├── prowler_hub/
└── prowler_documentation/

2. Fixtures

FixtureAutouseWhat it gives you
_pinned_environmentyesDeterministic env; blocks a developer's .env from leaking
_no_real_networkyesAny real socket connect raises RuntimeError
_singleton_registry_guardyesSnapshots/restores SingletonMeta._instances
mock_routernoRoute registry + request recorder
api_clientnoThe live ProwlerAPIClient singleton
mock_api_clientnoThe workhorse — singleton with a mocked transport
isolated_api_clientnoEvicts the singleton, for construction/identity tests
mcp_root_servernoThe mounted root server (session-scoped)
health_clientnoStarlette TestClient for /health
http_request_headersnoInjects headers for HTTP-mode auth
hub_routernoMocks the Hub sub-server's two sync clients
docs_routernoMocks the docs search engine's two sync clients

MockRouter

mock_router.add("GET", "/api/v1/users", json=jsonapi_collection([...]))
mock_router.add("GET", "/api/v1/tasks/t1", json=task_document("t1", "completed"))

mock_router.request_for("GET", "/api/v1/users")     # last request, for header asserts
mock_router.query_params("GET", "/api/v1/users")    # decoded query string
mock_router.paths()                                 # everything requested so far

Register a route more than once to return a sequence — the last response repeats. That is how you drive poll_task_until_complete (executing, executing, completed). An unregistered request raises, listing what was registered.


3. Patterns

Tool test — see assets/mcp_tool_test.py. Register routes, call through the in-memory client, assert on result.data and on the recorded request. When a tool chooses between endpoints, assert mock_router.paths() — a wrong choice is invisible in the response body.

Model test — see assets/mcp_model_test.py. Build the document with the jsonapi helpers, run from_api_response(), assert on both the model and model_dump(). MinimalSerializerMixin makes those differ, and an absent relationship (None) must never be conflated with an empty one ([]).

Contract test — see assets/mcp_contract_test.py. Namespacing and description coverage across every registered tool.

The worked example in the repo is findings, covered across both layers in tests/prowler_app/{models,tools}/test_findings.py. Read those first — they exercise every foundation capability in one feature.

Reading coverage

Coverage has a meaningless high floor. Model modules are almost entirely class-body Field(...) declarations that execute at import, and prowler_app/server.py imports every model module at import time. Importing the package with zero tests already reports 36% overall, and individual model modules 54–84%.

So a model module at ~68% with no tests has none of its logic covered — the missing ranges are the from_api_response() bodies, which is the only part worth testing. Compare against the import-only floor, never against zero, and do not set a Codecov target from the raw total.

Where fixture data lives

tests/helpers/ is feature-agnostic and must stay that way: it holds the JSON:API shape, not any feature's data. Per-feature attribute dictionaries (FINDING_ATTRIBUTES, CHECK_METADATA, …) belong as module-level constants in the test module that uses them. Do not add feature fixtures to helpers/.


4. Commands

From mcp_server/:

cd mcp_server

uv run pytest                              # whole suite
uv run pytest tests/prowler_app/models     # one area
uv run pytest --cov=./prowler_mcp_server   # with coverage

From the repository root:

make test-mcp   # runs the MCP suite exactly as CI does

5. Reference

  • Fixtures and the reasoning behind them: mcp_server/tests/conftest.py
  • Testing section of docs/developer-guide/mcp-server.mdx
  • Official FastMCP testing guide: https://gofastmcp.com/development/tests

Frequently asked questions about Prowler MCP Testing

Similar skills