Skip to main content
This guide explains how to extend the Prowler MCP Server with new tools and features.
New to Prowler MCP Server? Start with the user documentation:
  • Overview - Key capabilities, use cases, and deployment options
  • Installation - Install locally or use the managed server
  • Configuration - Configure Claude Desktop, Cursor, and other MCP hosts
  • Tools Reference - Complete list of all available tools

Introduction

The Prowler MCP Server brings the entire Prowler ecosystem to AI assistants through the Model Context Protocol (MCP). It enables seamless integration with AI tools like Claude Desktop, Cursor, and other MCP clients. The server follows a modular architecture with three independent sub-servers:
The core Prowler sub-server is served under the prowler_ tool prefix, while its source lives in the prowler_app/ module for historical reasons. Tool names use the prefix; import paths use the module.
For a complete list of tools and their descriptions, see the Tools Reference.

Architecture Overview

The MCP Server architecture is illustrated in the Overview documentation. AI assistants connect through the MCP protocol to access Prowler’s three main components.

Server Structure

The main server orchestrates three sub-servers with prefixed namespacing:

Tool Registration Patterns

The MCP Server uses two patterns for tool registration:
  1. Direct Decorators (Prowler Hub/Docs): Tools are registered using @mcp.tool() decorators
  2. Auto-Discovery (prowler_app): All public methods of BaseTool subclasses are auto-registered

Adding Tools to the prowler_app Sub-Server

Step 1: Create the Tool Class

Create a new file or add to an existing file in prowler_app/tools/:

Step 2: Create the Models

Create corresponding models in prowler_app/models/:

Step 3: Verify Auto-Discovery

No manual registration is needed. The tool_loader.py automatically discovers and registers all BaseTool subclasses. Verify your tool is loaded by checking the server logs:

Adding Tools to Prowler Hub/Docs

For Prowler Hub or Documentation tools, use the @mcp.tool() decorator directly:

Model Design Patterns

MinimalSerializerMixin

All models should use MinimalSerializerMixin to optimize responses for LLM consumption:
This mixin automatically excludes:
  • None values
  • Empty strings
  • Empty lists
  • Empty dictionaries

Two-Tier Model Pattern

Use two-tier models for efficient responses:
  • Simplified: Lightweight models for list operations
  • Detailed: Extended models for single-item retrieval

Factory Method Pattern

Always implement from_api_response() for API transformation:

API Client Usage

The ProwlerAPIClient is a singleton that handles authentication and HTTP requests:

Helper Methods

The API client provides useful helper methods:

Best Practices

Tool Docstrings

Tool docstrings become the description that is going to be read by the LLM. Provide clear usage instructions and common workflows:

Error Handling

Return structured error responses instead of raising exceptions:

Parameter Descriptions

Use Pydantic Field() with clear descriptions. This also helps LLMs understand the purpose of each parameter, so be as descriptive as possible:

Development Commands

For complete installation and deployment options, see: For development I recommend to use the Model Context Protocol Inspector as MCP client to test and debug your tools.

Testing

Tests live in mcp_server/tests/, mirroring the source tree, and use the test_*.py prefix (the same convention as the API, not the SDK’s *_test.py suffix). From mcp_server/:
From the repository root:
Async tests need no marker — asyncio_mode is set to auto.

Reading the Coverage Numbers

Coverage here has a high floor that means nothing. coverage.py measures statements, and in a Pydantic model module nearly every statement is a class-body field declaration that runs at import time. prowler_app/server.py imports every tool module — and therefore every model module — when it is first imported, so all of those declarations execute and count as covered before a single test runs.Importing the package and executing no tests at all already reports 36% overall, with individual model modules between 54% and 84%. A model module sitting at ~68% with no tests written for it has none of its behaviour covered: the covered lines are its imports, class statements and Field(...) declarations, and the missing ranges are its from_api_response() bodies.Judge a module against that import-only floor, not against zero, and do not set a Codecov target from the raw total.

Shared Fixtures

All fixtures live in mcp_server/tests/conftest.py. Three are autouse and apply to every test: the environment is pinned to deterministic values, real socket connections are blocked, and the API client singleton registry is snapshotted and restored. Helpers live in mcp_server/tests/helpers/: JSON:API document builders (jsonapi.py), the MockRouter (http.py), tool-contract assertions (assertions.py) and fake credentials (tokens.py).

Writing a Tool Test

Drive tools through an in-memory MCP client, and open the client inside the test — FastMCP warns that holding a client in a fixture causes event-loop problems.
The exemplar suite covers findings end to end — tests/prowler_app/models/test_findings.py and tests/prowler_app/tools/test_findings.py. It is deliberately one feature across both layers rather than a scattering of unrelated samples, and findings is the feature that exercises the whole foundation: two-tier models, nested sub-models, both relationship shapes, endpoint switching on a date range, list-to-CSV filter encoding, and a tool that returns prose instead of a model. Note the two files share a name. That is why __init__.py is required in every tests/ subdirectory here — without it they would collide on import.
Tool parameters are declared with pydantic Field(default=...), and only FastMCP’s tool wrapper resolves those defaults. Calling a tool method directly with an argument omitted leaves it as a raw FieldInfo object, which is truthy — so a filter such as if email: silently builds a query out of the FieldInfo repr. Call tools through the client, or pass every argument explicitly.

Why the API Key Is Pinned, Not Stripped

prowler_app/server.py builds every tool at import time. Constructing a tool reaches ProwlerAppAuth, which raises when PROWLER_API_KEY is missing, and load_all_tools swallows that error per tool class. The result is that the whole prowler_* namespace registers zero tools while the server still logs “Successfully mounted Prowler tools server”. The suite therefore pins a fake key in [tool.pytest_env], which is applied before any test module is imported, and tests/test_server.py asserts each namespace is non-empty so this failure can never return silently.
ProwlerAppAuth resolves PROWLER_MCP_TRANSPORT_MODE and API_BASE_URL in its default arguments, which Python evaluates once at module import. monkeypatch.setenv cannot change them — pass mode= and base_url= explicitly in auth tests.
For the full set of rules and templates, see the prowler-test-mcp skill and the official FastMCP testing guide.

MCP Server Overview

Key capabilities, use cases, and deployment options

Tools Reference

Complete reference of all available tools

Prowler Hub

Security checks and compliance frameworks catalog

Lighthouse AI

AI-powered security analyst

Additional Resources