OpenVAS-MCP
README.md
# OpenVAS-MCP
[](https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/actions/workflows/ci.yml)
[](https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/actions/workflows/docker.yml)
[](https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/actions/workflows/integration.yml)
[](https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/actions/workflows/startup-egress.yml)

A self-hosted MCP server that gives AI agents structured access to [OpenVAS / Greenbone](https://github.com/greenbone/openvas-scanner) vulnerability scanning — without sending your data anywhere.
OpenVAS has no native interface for AI agents. Most integrations require cloud connectivity or expose GVM credentials to every client. OpenVAS-MCP solves this:
- **Local-first.** Talks only to your GVM instance. No telemetry, no external calls — [verified by CI](.github/workflows/startup-egress.yml).
- **Credential isolation.** AI agents authenticate to the MCP server; the server holds the single GVM service account.
- **Thin bridge.** Returns structured scan data as-is. Analysis and reporting logic belong in the agent or a platform built on top.
See [docs/architecture.md](docs/architecture.md) for a full architecture diagram and design details.
## Quick start
### 0. Vibeinstall (optional, if you trust claude more than yourself)
Run in your terminal:
```bash
claude "install this, make no mistake."
```
If you prefer to stay in control, follow the manual setup below.
### 1. Get a GVM instance
Don't have one? Spin up the bundled Greenbone Community Edition stack:
```bash
docker compose -f docker/openvas/compose.yaml up -d
```
### 2. Connect an MCP client
#### stdio (Claude Desktop, Cursor, Windsurf, Cline, …)
**Requirements:** Python 3.10+
```bash
git clone https://github.com/CyberSecAuto-Labs/OpenVAS-MCP
cd OpenVAS-MCP
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Add to `mcpServers` in your client config file:
```json
{
"mcpServers": {
"openvas": {
"command": "/path/to/.venv/bin/python", // ← edit this to your venv path
"args": ["-m", "openvas_mcp"],
"env": { "GVM_PASSWORD": "secret" } // ← edit this to your GVM password
}
}
}
```
Config file locations:
| Client | Path |
|---|---|
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
| Cursor | `~/.cursor/mcp.json` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
| Cline / Roo Code | via the MCP panel in the VS Code extension |
#### HTTP/SSE (networked agents)
**Requirements:** Docker
Download the compose files from the [latest release](https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/releases/latest) and run:
```bash
# GVM running locally via Unix socket
MCP_API_KEYS="supersecrettoken:my-agent" GVM_PASSWORD=secret docker compose up
# GVM on a remote host via TCP
MCP_API_KEYS="supersecrettoken:my-agent" GVM_HOST=192.168.1.10 GVM_PASSWORD=secret docker compose up
```
> [!NOTE]
> `MCP_API_KEYS` is a comma-separated list of `token:name` pairs sent as a Bearer token by the MCP client. Multiple clients: `"tok1:agent1,tok2:agent2"`. Pass `MCP_ALLOW_UNAUTHENTICATED=1` instead to skip auth on a trusted network.
Point your MCP client at the server:
```json
{
"mcpServers": {
"openvas": {
"url": "http://your-server:8000/sse", // ← edit this to your server address
"headers": {
"Authorization": "Bearer supersecrettoken" // ← your MCP_API_KEYS token
}
}
}
}
```
> [!WARNING]
> Plain TCP connections (`GVM_HOST` set, `GVM_TLS` unset) send GVM credentials unencrypted. Use `GVM_TLS=1` or a Unix socket for anything beyond local dev.
### All-in-one dev setup
Greenbone Community Edition + MCP server from source in one go:
```bash
# Start the Greenbone stack
docker compose -f docker/openvas/compose.yaml up -d
# Start the MCP server, connected via gvmd socket
GVM_PASSWORD=secret docker compose -f compose.yaml -f compose.override.yaml up --build
```
> [!TIP]
> See [`compose.override.yaml`](compose.override.yaml) for how the socket volume is mounted.
## Configuration
| Variable | Default | Description |
|---|---|---|
| `GVM_PASSWORD` | — | GVM password (required) |
| `GVM_SOCKET_PATH` | `/run/gvmd/gvmd.sock` | Unix socket path (default connection) |
| `GVM_HOST` | — | Connect via TCP instead of socket (IPv4 and IPv6) |
| `MCP_TRANSPORT` | `stdio` | `stdio`, `sse`, or `streamable-http` |
| `MCP_API_KEYS` | — | Bearer API keys for HTTP transport auth (`token:name,...`) |
| `MCP_FILTER_VALIDATION` | `strict` | `strict` rejects a filter term GVM would silently drop; `warn` logs it and passes it through |
See [docs/configuration.md](docs/configuration.md) for the full reference, including TLS options, policy file, scan limits, and logging.
## Available tools
| Tool | Description |
|---|---|
| `list_targets` | Return all scan targets, with host counts |
| `create_target` | Create a target with specified hosts/CIDRs |
| `list_tasks` | Return scan tasks with severity, last-report and target info, optionally narrowed with a GMP filter term |
| `start_scan` | Create and start a scan against a target |
| `start_task` | Re-run an existing scan task by UUID |
| `get_scan_status` | Poll status and progress of a running scan |
| `fetch_scan_results` | Retrieve findings, optionally filtered by minimum severity |
**Example:** `"Scan 192.168.1.0/24 and show me anything above severity 7"` — the agent calls `create_target` → `start_scan` → `get_scan_status` → `fetch_scan_results(min_severity=7.0)`.
**Example:** `"Re-run the weekly scan"` — the agent calls `list_tasks(filter_string="name~weekly")` to resolve the task UUID, then `start_task` → `get_scan_status` → `fetch_scan_results`. Unlike `start_scan`, this adds a report to the existing task's history instead of creating a duplicate task. GVM applies a default page size to `list_tasks`; pass `rows=-1` to return every task.
**Example:** `"Re-run every single-host task whose last report is over a month old and scored above 5"` — a single `list_tasks` call returns every field those three criteria need, with no follow-up call to resolve host counts:
```python
list_tasks(filter_string="severity>5 and last<-1M rows=-1")
# [{"id": "11111111-1111-1111-1111-111111111111", "name": "weekly-dmz-scan",
# "status": "Done", "progress": "-1",
# "last_report": "22222222-2222-2222-2222-222222222222",
# "last_report_date": "2026-08-14T02:15:00+02:00",
# "severity": 6.5, "report_count": 3, "finished_report_count": 3, "trend": "same",
# "target_id": "33333333-3333-3333-3333-333333333333",
# "target_name": "dmz-web-01", "host_count": 1}, …]
```
The agent then keeps the rows with `host_count == 1` and calls `start_task` on each. A `null` `severity` or `host_count` means *unresolved* (no report yet, or the target is gone), never zero.
### GMP filter syntax
Three things about GMP filters are easy to get wrong, so `list_tasks` documents them in its tool description and rejects what GVM would quietly mishandle:
| | |
|---|---|
| **Terms OR by default** | `severity>5 total<4` returns the *union* — more rows, not fewer. Write `severity>5 and total<4` to intersect. |
| **`m` is minutes, `M` is months** | `last<-1m` means "older than one minute" and matches nearly everything. Use `last<-1M`. Units: `s m h d w M y`. |
| **Unknown terms are dropped, not rejected** | GVM silently ignores `zzzbogus<4` and returns every task. The server rejects unsupported keywords and unparseable values with `validation_error` instead — set `MCP_FILTER_VALIDATION=warn` to pass them through. |
## Release integrity
Every release image is:
- **Signed** with [cosign](https://github.com/sigstore/cosign) keyless OIDC signing — no long-lived key to compromise.
- **SBOM attached** — a [CycloneDX](https://cyclonedx.org) JSON bill of materials is generated with [syft](https://github.com/anchore/syft) and attached to each GitHub Release.
- **Vulnerability-scanned** — [grype](https://github.com/anchore/grype) scans the SBOM on every PR (`vuln-scan.yml`) and at release time, failing on fixable `high` severity findings (`--only-fixed`). CPython CVEs whose only fixes land on a release line the image does not ship are filtered out as unactionable — see [CI pipelines](docs/ci.md#interpreter-cves-and-the-release-line-filter).
- **Egress-audited** — the [`startup-egress` workflow](.github/workflows/startup-egress.yml) traces `connect()` syscalls via `strace` on every push and PR, asserting no unexpected outbound connections at startup. Integration tests extend this to live GMP code paths.
Verify the image signature before running:
```bash
cosign verify \
--certificate-identity-regexp "https://github.com/CyberSecAuto-Labs/OpenVAS-MCP/.github/workflows/release.yml@refs/tags/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/cybersecauto-labs/openvas-mcp:<version>
```
## Docs
- [docs/architecture.md](docs/architecture.md) — architecture diagram, component overview, and transport details
- [docs/configuration.md](docs/configuration.md) — full environment variable reference, TLS, policy file, scan limits, logging
- [docs/design.md](docs/design.md) — design decisions and known limitations
- [docs/ci.md](docs/ci.md) — CI workflows, guarantees, and tradeoffs
## License
[Apache 2.0](LICENSE)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive