← Discover MCPs and Agents
W
MCPAI & MLGitHub

Wazuh-MCP-Server

Production-grade MCP server for Wazuh SIEM — 55 security tools for alert triage, threat hunting, vulnerability management, compliance (PCI DSS, GDPR, HIPAA, NIST CSF, ISO 27001) and active response. Connect Claude or any LLM to your SOC. OAuth 2.1, RBAC, multi-cluster, air-gap ready.

Links

README

From the repo.

Wazuh MCP Server

A Model Context Protocol (MCP) server for the Wazuh SIEM.

Lets an MCP client — Claude, Open WebUI backed by a local model, or any client that speaks Streamable HTTP — query alerts, agents, vulnerabilities and compliance data, and dispatch active responses, with scope-based access control and audit logging.

CI Security Audit Release License: MIT

Python 3.11+ MCP 2026-07-28 Wazuh 4.8.0–4.14.7 GHCR image

Quick Start · Clients · Tools · Security · Configuration · Docs · Changelog · Upgrading


Overview

  • 55 tools in 8 toolsets: alerts, agents, vulnerabilities, threat analysis, compliance (PCI-DSS, HIPAA, SOX, GDPR, NIST, ISO 27001:2022), manager/cluster health, and active response with verification and rollback. Also 5 guided prompts, 6 resources and 3 resource templates.
  • Read-only by default. The 14 state-changing tools require the wazuh:write scope, which is never granted implicitly.
  • MCP transport: Streamable HTTP at /mcp. Serves protocol revision 2026-07-28 (stateless requests) and the initialize handshake for 2025-11-25, 2025-06-18, 2025-03-26 and 2024-11-05. The legacy HTTP+SSE endpoint /sse returns 410 Gone.
  • Authentication: bearer tokens minted from an API key; OAuth 2.0 (authorization code + PKCE) with sign-in by API key or at an OpenID Connect provider (Entra ID, Google Workspace, Okta, Keycloak); or no auth for local development.
  • Deployment: Docker Compose or a published multi-arch image; optional Redis for multi-instance sessions; optional multi-cluster routing.
  • Local models: a vLLM + Open WebUI stack (compose.local-llm.yml) and toolset filtering for small models. The only tool that calls a service outside your Wazuh deployment is the optional search_external_context (You.com), which can be disabled on its own.

Supported Wazuh versions: 4.8.0 through 4.14.7. Alert, vulnerability and alert-backed compliance tools need the Wazuh Indexer. See WAZUH_COMPATIBILITY.md.


Quick Start

Requires Docker with Compose v2 and a Wazuh Manager API user.

git clone https://github.com/gensecaihq/Wazuh-MCP-Server.git
cd Wazuh-MCP-Server
cp .env.example .env

Set the Wazuh connection in .env:

WAZUH_HOST=your-wazuh-manager
WAZUH_USER=your-api-user
WAZUH_PASS=your-api-password

# Needed for alert, vulnerability and alert-backed compliance tools
WAZUH_INDEXER_HOST=your-wazuh-indexer
WAZUH_INDEXER_USER=your-indexer-user
WAZUH_INDEXER_PASS=your-indexer-password

The Manager's TLS certificate is verified. A stock Wazuh install uses a self-signed API certificate that cannot pass verification, so either reissue it for your host and set WAZUH_CA_BUNDLE, or, for a first test, add WAZUH_ALLOW_SELF_SIGNED=true (connects without verification; logged at startup). Details: Manager TLS.

Generate the signing secret and an API key. compose.yml runs the server with ENVIRONMENT=production, which refuses to start without AUTH_SECRET_KEY:

echo "AUTH_SECRET_KEY=$(openssl rand -hex 32)" >> .env
echo "MCP_API_KEY=wazuh_$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')" >> .env

Start the server and check it:

docker compose up -d
curl http://localhost:3000/health     # liveness
curl http://localhost:3000/ready      # checks Manager/Indexer reachability

Exchange the API key for a bearer token (valid for TOKEN_LIFETIME_HOURS, default 24):

curl -s -X POST http://localhost:3000/auth/token -H 'Content-Type: application/json' \
  -d "{\"api_key\": \"$(grep ^MCP_API_KEY= .env | cut -d= -f2)\"}"

The key is read-only. To allow active-response tools, add MCP_API_KEY_SCOPES="wazuh:read wazuh:write" to .env, recreate the container with docker compose up -d (restart keeps the old environment), and mint a new token.

Compose publishes the port on 127.0.0.1 only. The server speaks plain HTTP; put a TLS-terminating reverse proxy in front before exposing it (set MCP_BIND to change the host bind address).

python3 deploy.py (or deploy.bat on Windows) performs the same steps, generating AUTH_SECRET_KEY and MCP_API_KEY if they are missing.

Pre-built image

Multi-arch images (amd64, arm64) are published to GitHub Container Registry and can be pulled without logging in:

docker pull ghcr.io/gensecaihq/wazuh-mcp-server:latest   # tracks main
docker pull ghcr.io/gensecaihq/wazuh-mcp-server:5.0.0    # latest tagged release

latest is built from main and may include changes listed under Unreleased in the changelog. Release images are tagged 5.0.0, 5.0 and v5.0.0 (4.3.0 and earlier have no v-prefixed tag). Upgrading from 4.x: read UPGRADING.md first.

docker run -d --name wazuh-mcp-server --env-file .env -e MCP_HOST=0.0.0.0 -e ENVIRONMENT=production \
  -p 127.0.0.1:3000:3000 ghcr.io/gensecaihq/wazuh-mcp-server:latest

MCP_HOST=0.0.0.0 is required inside a container because .env.example sets MCP_HOST=127.0.0.1 for bare-metal installs. -e wins over --env-file, so ENVIRONMENT=production holds even if your .env sets another value.


Connecting Clients

All clients use the Streamable HTTP endpoint https://<your-host>/mcp.

ClientAuth modeHow it authenticates
Claude custom connectors (claude.ai, Claude Desktop)oauthOAuth authorization code with PKCE. The server pre-registers a public client, claude-desktop, for Claude's callback URLs. Users sign in on the server's /oauth/authorize page with a wazuh_ API key (the grant is capped at that key's scopes), or at your OpenID Connect provider when OAUTH_IDP_ISSUER is set.
Open WebUI, LibreChat, scripts and other MCP clientsbearer (default)Authorization: Bearer <token> using a token from POST /auth/token.

In OAuth mode, set OAUTH_ISSUER_URL to the server's public HTTPS URL (otherwise it is derived from each request, which behind a proxy may not be the public URL). Dynamic Client Registration (/oauth/register) is off unless OAUTH_ENABLE_DCR=true, and cannot be combined with OAUTH_IDP_ISSUER.

Guides: Claude Integration · Local LLMs


Local LLMs

The server does not call a model; it only executes tools. To keep SIEM data on-premises, pair it with a local model:

cat >> .env <<EOF
VLLM_API_KEY=$(openssl rand -hex 32)
WEBUI_SECRET_KEY=$(openssl rand -hex 32)
EOF
docker compose -f compose.yml -f compose.local-llm.yml up -d

This adds vLLM (default model Qwen3.6-35B-A3B FP8, about 42 GB of VRAM on one NVIDIA GPU; not published on a host port) and Open WebUI on 127.0.0.1:8080. In Open WebUI's admin settings, add an MCP (Streamable HTTP) tool server at http://wazuh-main-server:3000/mcp with a bearer token.

For smaller models, expose fewer tools with WAZUH_TOOLSETS / WAZUH_DISABLED_TOOLS, and check tool selection before rollout with evals/tool_selection.py (25 SOC scenarios, including two prompt-injection cases, against any OpenAI-compatible endpoint; no tools are executed). Model sizing, Ollama and LiteLLM are covered in the Local LLM Guide.


Tools

55 tools, grouped into toolsets that can be enabled with WAZUH_TOOLSETS (comma-separated; default all). R = wazuh:read, W = wazuh:write.

ToolsetCountTools
alerts5 Rget_wazuh_alerts, get_wazuh_alert_summary, get_alerts_aggregated, analyze_alert_patterns, search_security_events
agents6 Rget_wazuh_agents, get_wazuh_running_agents, check_agent_health, get_agent_processes, get_agent_ports, get_agent_configuration
vulnerabilities3 Rget_wazuh_vulnerabilities, get_wazuh_critical_vulnerabilities, get_wazuh_vulnerability_summary
analysis5 Ranalyze_security_threat, check_ioc_reputation, perform_risk_assessment, get_top_security_threats, generate_security_report
web_search1 Rsearch_external_context — You.com web search; returns a "not enabled" result unless YDC_API_KEY is set
compliance6 Rrun_compliance_check (PCI-DSS, HIPAA, SOX, GDPR, NIST, ISO27001), get_iso27001_dashboard, get_iso27001_control_detail, get_iso27001_gap_analysis, get_iso27001_alerts, get_sca_policy_checks
system10 Rget_wazuh_statistics, get_wazuh_weekly_stats, get_wazuh_cluster_health, get_wazuh_cluster_nodes, get_wazuh_rules_summary, get_wazuh_remoted_stats, get_wazuh_log_collector_stats, search_wazuh_manager_logs, get_wazuh_manager_error_logs, validate_wazuh_connection
response — containment9 Wwazuh_block_ip, wazuh_isolate_host, wazuh_kill_process, wazuh_disable_user, wazuh_quarantine_file, wazuh_firewall_drop, wazuh_host_deny, wazuh_active_response, wazuh_restart
response — rollback5 Wwazuh_unisolate_host, wazuh_enable_user, wazuh_restore_file, wazuh_firewall_allow, wazuh_host_allow
response — verification5 Rwazuh_check_blocked_ip, wazuh_check_agent_isolation, wazuh_check_process, wazuh_check_user_status, wazuh_check_file_quarantine
  • Totals: 41 read tools, 14 write tools. Tokens without wazuh:write do not see the write tools in tools/list.
  • In multi-cluster mode a 56th tool, list_wazuh_clusters (system, read), is added and every tool accepts an optional cluster_id.
  • WAZUH_DISABLED_TOOLS hides individual tools. Hidden tools are removed from tools/list and refused by tools/call; unknown toolset or tool names stop the server at startup.
  • Every tool carries MCP annotations derived from its scope: read tools are readOnlyHint: true; containment tools are destructiveHint: true; rollback tools are destructiveHint: false; only search_external_context is openWorldHint: true.
  • Input schemas are closed (additionalProperties: false); undeclared arguments are refused.
  • Timestamp filters accept ISO 8601 or OpenSearch date math (now-24h).
  • Prompts: security_investigation, threat_hunt, compliance_audit, vulnerability_assessment, iso27001_assessment.

Per-tool parameters: API documentation.

Active response behaviour

  • Results report execution_status: "dispatched": Wazuh confirms the command was delivered to the agent, not that it ran. Confirm the effect with the matching wazuh_check_* tool.
  • Blocks are permanent until removed. Wazuh ignores the timeout for API-triggered commands, so a positive duration is refused.
  • wazuh_firewall_allow and wazuh_host_allow require an operator-deployed undo command (WAZUH_AR_FIREWALL_UNDO_COMMAND, WAZUH_AR_HOSTDENY_UNDO_COMMAND); without one they refuse.

Security Model

ControlBehaviour
Scopes (RBAC)Each tool requires wazuh:read or wazuh:write. A token without a scope claim is read-only. MCP_API_KEY is read-only unless MCP_API_KEY_SCOPES includes wazuh:write. With AUTH_MODE=none, write tools are disabled unless AUTHLESS_ALLOW_WRITE=true.
Bearer tokensJWTs signed with AUTH_SECRET_KEY, must carry exp, and are bound to the API key they were minted from: revoking or rotating the key invalidates its tokens. Refresh tokens are not accepted as access tokens.
OAuthAuthorization code flow with mandatory S256 PKCE, single-use codes, refresh-token rotation with replay detection, and revocation. Users sign in with a wazuh_ API key (the grant is capped at that key's scopes, and its tokens end when the key is revoked) or at an OpenID Connect provider (ID token signature, issuer, audience, expiry and nonce verified; tenant, domain and user allow-lists; group-to-scope mapping), so scopes, rate limits and audit entries are per user.
Action guardrailsIP-blocking tools refuse loopback, the Manager's address (when WAZUH_HOST is an IP) and anything in WAZUH_PROTECTED_IPS; the generic active-response tool does not dispatch IP blocks, quarantine, process kills or account disables. Actions and restarts aimed at agent 000 (the Manager) need WAZUH_ALLOW_MANAGER_AR=true; fleet-wide blocks need WAZUH_ALLOW_FLEET_AR=true; quarantine refuses system and agent directories. In production, write tools require confirm=true (WAZUH_REQUIRE_ACTION_CONFIRMATION).
Wazuh TLSThe Manager and Indexer certificates are verified by default, against the system store or WAZUH_CA_BUNDLE. Disabling Manager verification (WAZUH_ALLOW_SELF_SIGNED=true) is logged at startup, as an error in production.
Audit logEvery write-tool call that passes the scope and confirmation checks is logged before and after execution (logger wazuh_mcp_server.audit) with the principal, session, arguments and outcome.
RedactionCredentials and tokens are redacted from tool output in every response format, and from server logs.
Input validationTyped validation of agent IDs, IPs, paths and command names; Indexer queries are built as Query DSL, not by string interpolation.
Rate limitingSliding window, default 100 requests per 60 s (RATE_LIMIT_REQUESTS, RATE_LIMIT_WINDOW): per principal and client IP on /mcp and /, per client IP on other endpoints except the probe endpoints (/health, /ready, /metrics and their /healthz, /readyz, /live, /livez aliases). Failed authentication is rate limited by client IP. Set TRUSTED_PROXIES when running behind a proxy.
Resource boundsCircuit breaker on Wazuh calls: opens after 5 consecutive failures, retries after 60 s. Oversized tool results are truncated with a note (MAX_TOOL_RESPONSE_CHARS). limit on get_wazuh_alerts and search_security_events is capped at MAX_ALERTS_PER_QUERY (default 1000). The in-memory session store is capped at MAX_SESSIONS (1000) and MAX_SESSIONS_PER_PRINCIPAL (100), and stored client metadata is truncated.
ContainerRuns as UID 1000; compose.yml sets a read-only root filesystem, cap_drop: ALL and no-new-privileges. The runtime image does not include pip.

There is no built-in TLS listener; terminate TLS at a reverse proxy or load balancer. Report vulnerabilities as described in SECURITY.md.


Configuration

All settings are environment variables (usually via .env). The ones most deployments touch:

VariableDefaultPurpose
WAZUH_HOST, WAZUH_USER, WAZUH_PASSManager API connection (required)
WAZUH_PORT55000Manager API port
WAZUH_CA_BUNDLECA PEM used instead of the system store to verify the Manager and Indexer; see Manager TLS
WAZUH_ALLOW_SELF_SIGNEDfalsetrue connects to the Manager without certificate verification (logged at startup)
WAZUH_INDEXER_HOST, WAZUH_INDEXER_USER, WAZUH_INDEXER_PASSIndexer connection; an http:// host prefix selects plain HTTP
WAZUH_INDEXER_PORT9200Indexer port
ENVIRONMENTdevelopmentproduction requires a strong AUTH_SECRET_KEY (unless AUTH_MODE=none)
AUTH_MODEbearerbearer, oauth or none
AUTH_SECRET_KEYgenerated per process outside productionToken signing key; use the same value on every instance
MCP_API_KEY / API_KEYSgenerated per process if unset (printed only in development)A single wazuh_ key, or a JSON list of hashed keys with per-key scopes
MCP_API_KEY_SCOPESwazuh:readSpace-separated scopes for MCP_API_KEY
OAUTH_ISSUER_URLderived from the requestPublic HTTPS URL of the server, for AUTH_MODE=oauth
OAUTH_IDP_ISSUEROpenID Connect provider for OAuth sign-in (with OAUTH_IDP_CLIENT_ID); API-key sign-in when unset
WAZUH_REQUIRE_ACTION_CONFIRMATIONtrue in production, else falseWrite tools require confirm=true
MCP_HOST, MCP_PORT127.0.0.1 (0.0.0.0 in the Docker image), 3000Bind address and port
ALLOWED_ORIGINShttps://claude.ai,http://localhost:3000CORS allow-list (exact match)
WAZUH_TOOLSETS, WAZUH_DISABLED_TOOLSall enabledLimit the exposed tools
REDIS_URLShared session store for multi-instance deployments
WAZUH_CLUSTERS_FILE./config/clusters.jsonMulti-cluster topology; single-cluster mode when absent
RESPONSE_FORMATjsongcf encodes alert, event and vulnerability collections in the compact GCF format (lossless; falls back to JSON if the encoder is unavailable)
YDC_API_KEYEnables search_external_context

Complete reference, including OAuth TTLs, rate limits, sessions and active-response settings: Configuration Guide. Multi-cluster setup: Multi-Cluster Guide and config/clusters.json.example.

Running from source

python -m venv .venv && source .venv/bin/activate
pip install -e ".[redis,gcf]"      # extras are optional
set -a; . ./.env; set +a           # the server reads the environment, not .env
python -m wazuh_mcp_server

Requires Python 3.11 or later.


HTTP Endpoints

EndpointMethodDescription
/mcpPOST, GET, DELETEMCP Streamable HTTP
/POST, GETSame handler as /mcp
/sseGET, POSTReturns 410 Gone; use /mcp
/healthGETLiveness; no dependency checks
/readyGETReadiness; 503 when the Manager, Indexer or memory headroom check fails
/metricsGETPrometheus metrics
/auth/tokenPOSTExchange an API key for a bearer JWT
/.well-known/oauth-authorization-serverGETOAuth metadata (RFC 8414), AUTH_MODE=oauth only
/.well-known/oauth-protected-resourceGETProtected-resource metadata (RFC 9728), AUTH_MODE=oauth only
/oauth/authorize, /oauth/token, /oauth/revoke, /oauth/register, /oauth/callbackGET/POSTOAuth endpoints, AUTH_MODE=oauth only (/oauth/register requires OAUTH_ENABLE_DCR=true and no OAUTH_IDP_ISSUER; /oauth/callback is the OpenID Connect redirect URI and needs OAUTH_IDP_ISSUER)
/docs, /redoc, /openapi.jsonGETOpenAPI documentation

Project Layout

src/wazuh_mcp_server/
├── server.py          # FastAPI app, MCP protocol handling, CORS/Origin checks, tool definitions and dispatch
├── toolsets.py        # Toolset membership, WAZUH_TOOLSETS resolution, tool annotations
├── auth.py            # API keys and bearer JWTs
├── oauth.py           # OAuth 2.0 authorization server (PKCE, API-key sign-in)
├── oidc.py            # OpenID Connect sign-in at an external identity provider
├── config.py          # Environment configuration and startup validation
├── security.py        # Rate limiting, request and input validation, log redaction
├── clusters.py        # Multi-cluster registry and Cross-Cluster Search routing
├── session_store.py   # In-memory and Redis session storage
├── resilience.py      # Circuit breakers, retries, graceful shutdown
├── monitoring.py      # Prometheus metrics, structured logging
├── gcf_format.py      # Optional GCF response encoding
└── api/
    ├── wazuh_client.py    # Wazuh Manager REST API client
    └── wazuh_indexer.py   # Wazuh Indexer (OpenSearch) client

Documentation

DocumentContents
Configuration GuideEvery environment variable, auth modes, RBAC
Claude IntegrationConnecting Claude custom connectors
Local LLM GuidevLLM, Open WebUI, Ollama, LiteLLM, tool-selection eval
Multi-ClusterNamed clusters and Cross-Cluster Search
OperationsDeployment, monitoring, maintenance
Advanced FeaturesMulti-instance deployment, compact output
TroubleshootingCommon problems and fixes
API ReferencePer-tool parameters
Security HardeningHardening guidance
MCP ComplianceProtocol conformance notes
Wazuh CompatibilitySupported Wazuh versions
Upgrading · Changelog · Security PolicyRelease notes and policies

Related project: Wazuh Autopilot builds automated SOC workflows on top of this server.


Contributing

See CONTRIBUTING.md. Bugs, feature requests and questions go to Issues, and security reports to a private security advisory.


License

MIT


Acknowledgments

Thanks to everyone who has contributed code, reviews, bug reports and design feedback. See also ACKNOWLEDGMENTS.md.

Code and pull requests

  • @alokemajumder — maintainer; architecture, MCP transport, security hardening, releases
  • @gensecai-dev — the 19 action, verification and rollback tools, broken-endpoint fixes, production hardening
  • @andrzej-piotrowski-pl — ISO 27001:2022 compliance tools: Annex A control mapping, domain scoring, gap analysis (#74)
  • @blackwell-systems — opt-in GCF response encoding for record tools (#102, #104)
  • @lucascruzb — period-wide alert aggregation via scroll, the basis of get_alerts_aggregated (#79)
  • @kanylbullen — compact output mode for token-efficient responses (#65)
  • @mouse-value-add — optional You.com web-search context (#85)
  • @DrRSatzteiltools/list pagination fix (#70)
  • @SiM22 — MCP 2025-06-18 support for Windsurf compatibility (#66)
  • @aiunmukto.env.example (#12), an early CI workflow and the Glama registry listing
  • @Karibusan — dependency fixes (#38)
  • @lwsinclair — MseeP.ai listing (#9)
  • @markeclaudio — OpenID Connect sign-in (#123), active-response guard-rails (#124, #125), session-store bounds (#126), Manager TLS verification by default (#127)
  • @MilkyWay88 and @taylorwalton — early pull requests on configuration, logging and packaging

Bug reports and discussions

@cbassonbgroup, @cybersentinel-06, @daod-arshad, @mamema, @marcolinux46, @matveevandrey, @punkpeye, @tonyliu9189, @Uberkarhu, @bl4ck5w4n07, @gnix45, @hackdefendr, @melmasry1987, @Vasanth120v, @wqfh

Built on and works with


Contributors

Contributors

AvatarUsernameContributions
@alokemajumder💻 Code, 🐛 Issues, 🔀 PRs, 💬 Discussions
@Karibusan💻 Code, 🐛 Issues, 🔀 PRs
@gensecai-dev💻 Code, 🔀 PRs, 💬 Discussions
@aiunmukto💻 Code, 🔀 PRs
@andrzej-piotrowski-pl💻 Code, 🔀 PRs
@blackwell-systems💻 Code, 🔀 PRs
@kanylbullen💻 Code, 🔀 PRs
@lucascruzb💻 Code, 🔀 PRs
@lwsinclair💻 Code, 🔀 PRs
@mouse-value-add💻 Code, 🔀 PRs
@SiM22💻 Code, 🔀 PRs
@DrRSatzteil🔀 PRs
@MilkyWay88🔀 PRs
@taylorwalton🔀 PRs
@cbassonbgroup🐛 Issues
@cybersentinel-06🐛 Issues
@daod-arshad🐛 Issues
@mamema🐛 Issues
@marcolinux46🐛 Issues
@matveevandrey🐛 Issues
@punkpeye🐛 Issues
@tonyliu9189🐛 Issues
@Uberkarhu🐛 Issues
@bl4ck5w4n07💬 Discussions
@gnix45💬 Discussions
@hackdefendr💬 Discussions
@melmasry1987💬 Discussions
@Vasanth120v💬 Discussions
@wqfh💬 Discussions

Legend: 💻 Code · 🐛 Issues · 🔀 Pull Requests · 💬 Discussions

Auto-updated by GitHub Actions

Collected info

  • 237 stars
  • 65 forks
  • Language: Python
  • Source updated: 9/21/2026

Config for your environment

Replace {MCP_ENDPOINT_URL} with this MCP’s endpoint URL (from its repo or docs above). No API key — you connect directly.

Tool

OS

Config file: ~/.cursor/mcp.json

{
  "mcpServers": {
    "mcp-server": {
      "url": "{MCP_ENDPOINT_URL}"
    }
  }
}

Paste into mcpServers in the config file. Restart Cursor after saving.

If this MCP is also published on mcpchannel.ai, you can subscribe from Browse and use the gateway config there instead.