Skip to main content
Glama
Sabastiaz

tenable-vpr-mcp

by Sabastiaz
README.md
# 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

A4.2/5.0

Scored across 11 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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.

Maintenance

ActivitySlowing
ResponsivenessNo issues