tenable-vpr-mcp
# Tenable VPR MCP
MCP server for **Tenable.io / Tenable One** (Vulnerability Management API),
built for pentest and exposure-management reporting workflows.
Alongside read-only coverage of the usual objects (scans, assets, findings,
plugins, tags, agents), it adds three tools aimed at the reporting step of
an engagement rather than at raw API access:
## `compare_vpr_reprioritization`
Tenable reports two ratings for the same finding:
- **CVSS-based severity** (`severity`): the static, plugin-assigned bucket
- **VPR** (`vpr.score`): threat-intel and exploitability-weighted score
When you re-scope a client's exposure using VPR instead of raw CVSS, some
findings get **escalated** (low CVSS, actively exploited) and some get
**downgraded** (high CVSS, no real-world exploitation activity). That
before/after delta is exactly what you need to show in a CTEM / Tenable One
POC deliverable, and building it by hand from raw exports is tedious.
`compare_vpr_reprioritization` pulls live findings from the vulnerability
workbench and returns a sorted table (escalations first) plus a summary
rollup, ready to drop into a report or slide.
```json
{
"summary": {"escalated": 4, "downgraded": 11, "unchanged": 52, "unrated": 2, "total_findings": 69},
"findings": [
{
"plugin_id": 12345,
"plugin_name": "Example Actively-Exploited RCE",
"cvss_severity": "medium",
"vpr_score": 9.4,
"vpr_severity": "critical",
"rerating": "escalated",
"affected_assets": 5
}
]
}
```
## `check_kev_epss_exposure`
VPR is a Tenable proprietary score. This tool backs a re-prioritization
argument with two independent, public data sources instead: the **CISA
KEV catalog** (confirmed real-world exploitation) and **FIRST.org EPSS**
(30-day exploitation probability). Each finding gets a `signal`:
`confirmed_exploited` > `high_probability` > `low_signal` > `no_cve_data`,
sorted most urgent first, plus a ransomware-association flag from KEV.
Note: this does one extra Tenable API call per distinct plugin (to resolve
CVEs via `workbenches.vuln_info`), so keep `limit` modest for interactive
use.
## `scan_delta`
Compares a baseline scan against a re-test scan by plugin ID and buckets
findings into `fixed`, `still_open`, and `new_since_baseline`, with a
remediation-rate percentage. Built for the re-test report every pentest
engagement ends with.
## All tools
| Tool | Description |
|---|---|
| `list_scans` | List scans, optionally by folder |
| `get_scan_details` | Latest results for one scan (hosts, findings, severity counts) |
| `list_assets` | List known assets (capped) |
| `get_asset_details` | Full detail for one asset by UUID |
| `search_vulnerabilities` | Workbench findings, filterable by severity / plugin family |
| `get_plugin_details` | Plugin description, solution, CVEs, VPR drivers |
| `list_tags` | Asset tag categories and values |
| `list_agents` | Nessus Agent inventory and status |
| `compare_vpr_reprioritization` | CVSS vs. VPR before/after comparison table |
| `check_kev_epss_exposure` | CVSS/VPR findings cross-referenced against CISA KEV + EPSS |
| `scan_delta` | Baseline vs. re-test comparison for remediation validation |
This server is **read-only** by design: no scan launch, edit, or delete
tools are exposed, so it's safe to point at a production tenant.
### Filter arguments
`search_vulnerabilities`, `compare_vpr_reprioritization`, and
`check_kev_epss_exposure` share two optional filters:
- `severity` — a list of `info` / `low` / `medium` / `high` / `critical`.
Case-insensitive; the server capitalizes them to the form the workbench
API requires.
- `plugin_family` — a list of family names (`["Windows", "Web Servers"]`)
or numeric family IDs. Names are resolved to IDs on first use, since the
workbench only filters on `plugin.family_id`. An unknown name raises
before any API call is made.
## Output
Every tool returns the same envelope, so credential, authentication, and
API errors reach the client as readable text instead of a crash:
```json
{"ok": true, "data": ...}
{"ok": false, "error": "UnexpectedValueError: ..."}
```
`compare_vpr_reprioritization` and `check_kev_epss_exposure` return a
`summary` rollup plus a `findings` list sorted most-urgent-first, in the
shape shown in the `compare_vpr_reprioritization` example above.
`scan_delta` returns `fixed` / `still_open` / `new_since_baseline` lists
plus a `summary` with counts and `remediation_rate_pct`. The remaining
tools return the Tenable API payload as-is under `data`.
## Prerequisites
- Python 3.10 or newer
- A Tenable.io / Tenable One account that can generate API keys
## Setup
See [USAGE.md](USAGE.md) for full setup, client configuration, and example
prompts. Quick version:
```bash
git clone https://github.com/Sabastiaz/tenable-vpr-mcp
cd tenable-vpr-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e .
export TIO_ACCESS_KEY=your_access_key
export TIO_SECRET_KEY=your_secret_key
# optional, defaults to https://cloud.tenable.com
export TIO_URL=https://cloud.tenable.com
tenable-vpr-mcp
```
Generate API keys in Tenable.io / Tenable One under
**Settings > My Account > API Keys**. Never pass keys as CLI arguments;
use environment variables only.
### Claude Code
```bash
claude mcp add tenable-vpr -- tenable-vpr-mcp
```
(with `TIO_ACCESS_KEY` / `TIO_SECRET_KEY` set in your shell environment
before running the command above).
### Claude Desktop (`claude_desktop_config.json`)
```json
{
"mcpServers": {
"tenable-vpr": {
"command": "tenable-vpr-mcp",
"env": {
"TIO_ACCESS_KEY": "your_access_key",
"TIO_SECRET_KEY": "your_secret_key"
}
}
}
}
```
## Limitations
- **The vulnerability workbench caps every query at 5,000 findings.** This
is a Tenable API limit, not a limit of this server — raising `limit`
past 5,000 returns no more records. On a large tenant, filter by
`severity` or `plugin_family` to keep each query under the cap, or the
results are silently truncated. (pyTenable also marks the workbench
module deprecated in favour of the exports API; moving to exports is the
fix for full-tenant extraction and is not implemented yet.)
- **`check_kev_epss_exposure` issues one extra Tenable API call per
distinct plugin** to resolve CVEs via `workbenches.vuln_info`. Keep
`limit` modest (20–30) for interactive use. Plugin CVE lookups are not
cached between calls; the CISA KEV catalog is cached in-process for six
hours.
- **Findings are aggregated per plugin, not per host.** `affected_assets`
is a host count; use `get_scan_details` or `scan_delta` when you need
scan-scoped, per-host detail.
- **`search_vulnerabilities` and `compare_vpr_reprioritization` read
tenant-wide state**, not the results of one scan.
- **A VPR score of `None` is reported as `unrated`, not as low risk.**
Tenable does not score every plugin — end-of-life and configuration
findings frequently have no VPR at all, so they need to be reviewed
separately rather than sorted to the bottom.
- **No write operations** (scan launch/configure, tag assignment, asset
deletion) are implemented, by design.
## Development
```bash
pip install -e ".[dev]"
pytest tests/ -v
```
(Quote the extras — unquoted `.[dev]` is a glob pattern in zsh.)
The suite runs with no live Tenable / CISA / FIRST.org calls:
| File | Covers |
|---|---|
| `tests/test_findings.py` | Normalizing the workbench's flat records and integer severities |
| `tests/test_vpr.py` | CVSS vs. VPR comparison and bucket boundaries |
| `tests/test_kev.py` | KEV/EPSS signal classification and CVE extraction |
| `tests/test_scan_diff.py` | Baseline vs. re-test bucketing |
| `tests/test_server_filters.py` | Severity capitalization and family-name resolution |
| `tests/fixtures.py` | Payloads captured verbatim from a live tenant |
Keep `tests/fixtures.py` faithful to what the API actually returns. The
workbench sends `plugin_id` / `plugin_name` / `vpr_score` at the top level
and `severity` as an integer 0–4, not the nested, label-severity shape
most Tenable API examples use — fixtures written in the nested shape pass
while every live call fails.
`demo.py` exercises all 11 tools through their real code path with the
Tenable client and KEV/EPSS lookups faked, so it needs no credentials:
```bash
python demo.py
```
## License
MIT
TDQS
Scored across 11 tools
Each tool targets a clearly distinct resource or action: assets, vulnerabilities, plugins, tags, agents, scans, and specialized analyses (VPR comparison, KEV/EPSS cross-reference, scan delta). No two tools have overlapping purposes.
Most tools follow a verb_noun pattern (get_asset_details, search_vulnerabilities, list_scans, compare_vpr_reprioritization, check_kev_epss_exposure). However, 'scan_delta' deviates as a noun-noun compound, breaking the otherwise consistent convention.
11 tools is well within the ideal 3-15 range and each tool serves a specific purpose for the server's VPR-focused analysis domain. The count feels neither excessive nor thin.
The server covers core retrieval and comparison workflows well, including asset and scan details, vulnerability search, and specialized reporting tools. A minor gap is the lack of a per-asset vulnerability findings endpoint, but existing tools can work around this.