Metadata-Version: 2.4
Name: qualys-mcp
Version: 2.16.0
Summary: MCP server for Qualys security APIs - natural language interaction with vulnerability, asset, and cloud security data
Project-URL: Homepage, https://github.com/nelssec/qualys-mcp
Project-URL: Repository, https://github.com/nelssec/qualys-mcp
Project-URL: Issues, https://github.com/nelssec/qualys-mcp/issues
Author-email: Andrew Nelson <andrew@nelssec.com>
License-Expression: MIT
License-File: LICENSE
Keywords: ai,claude,mcp,qualys,security,vmdr,vulnerability
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Requires-Python: >=3.9
Requires-Dist: fastmcp>=0.1.0
Provides-Extra: eval
Requires-Dist: anthropic; extra == 'eval'
Requires-Dist: mcp; extra == 'eval'
Requires-Dist: python-dotenv; extra == 'eval'
Requires-Dist: pyyaml; extra == 'eval'
Description-Content-Type: text/markdown

# Qualys MCP Server

> ⚠️ **Unofficial project.** This is a personal project to showcase the viability of connecting AI assistants to Qualys via the Model Context Protocol. It is not affiliated with, endorsed by, or supported by Qualys, Inc.

A lightweight MCP server that connects AI assistants to Qualys security data. **35 tools**, pure Python, zero config beyond credentials. Install with `uvx` and start asking security questions in plain English.

**📖 [Full documentation →](https://qualys-mcp.netlify.app/)**

## Setup

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "qualys": {
      "command": "uvx",
      "args": ["qualys-mcp"],
      "env": {
        "QUALYS_USERNAME": "your-username",
        "QUALYS_PASSWORD": "your-password",
        "QUALYS_POD": "US2"
      }
    }
  }
}
```

Set `QUALYS_POD` to your platform POD — the server derives the correct API and gateway URLs automatically.

**Supported pods:** `US1` `US2` `US3` `US4` `EU1` `EU2` `EU3` `IN1` `CA1` `AE1` `UK1` `AU1` `KSA1`

> **Advanced:** If you need to override the auto-derived URLs, set `QUALYS_BASE_URL` and `QUALYS_GATEWAY_URL` explicitly instead of `QUALYS_POD`. Explicit URLs take priority.

Requires [uv](https://docs.astral.sh/uv/): `brew install uv` or `curl -LsSf https://astral.sh/uv/install.sh | sh`

### Alternative

```bash
pip install qualys-mcp
qualys-mcp
```

### Self-Signed Certificates

For environments with self-signed certs, add `"QUALYS_SSL_VERIFY": "false"` to the env block.

## Tools

35 tools covering vulnerability management, threat intelligence, asset risk, cloud security, containers, web application security, certificate monitoring, endpoint detection, file integrity monitoring, patch management, compliance, remediation workflow, aggregator tools, and security program coaching.

### Daily Operations & Coaching

| Tool | What it answers |
|------|----------------|
| `get_morning_report` | What happened overnight? New vulns, ransomware/exploit flags, top risks, action items |
| `get_recommendations` | What should we improve? Module gaps, risk reduction opportunities, prioritized next steps |
| `get_eliminate_status` | What's our patching and mitigation status? Patch jobs, mitigation jobs, catalog coverage |
| `get_etm_findings` | What confirmed vulnerabilities exist across all sources? ETM findings with QID, QDS, TruRisk, CVE, patch status |
| `get_scanner_health` | Are our scanners healthy? Appliance status, failed scans, capacity utilization, signature updates |

### Security Posture & Priorities

| Tool | What it answers |
|------|----------------|
| `get_security_posture` | How secure are we overall? Health score, risk distribution, container and cloud stats |
| `get_weekly_priorities` | What should my team fix this week? Top risk assets ranked by TruRisk |
| `get_patch_status` | What's our patching coverage? Risk distribution and assets needing remediation |
| `get_scan_status` | What scans are running, queued, paused, or failed? Duration, targets, scanner names, completion stats, and error details with scanner health suggestions |
| `get_compliance_posture` | What's our policy compliance rate? Pass/fail by framework (PCI-DSS, CIS, NIST, HIPAA) |

### Vulnerability Intelligence

| Tool | What it answers |
|------|----------------|
| `investigate_cve` | Are we affected by CVE-XXXX? QIDs, severity, patches, threat intel |
| `get_cve_details` | Tell me about these 5 CVEs. Bulk lookup with concurrent fetching |
| `get_new_vulns` | What new vulns dropped this week? Severity breakdown, RTI tags, patch status |
| `get_vulns_by_software` | What vulns affect Apache? Search by software, vendor, or product name |
| `get_threat_intel` | What vulns have ransomware/active exploits? RTI breakdown across 12+ threat categories |
| `get_vuln_exceptions` | What vulnerabilities have approved exceptions? Risk acceptances, false positives, expiring exceptions |

### Asset & Infrastructure Risk

| Tool | What it answers |
|------|----------------|
| `get_asset_risk` | Why is this asset risky? TruRisk score, software inventory, EOL status |
| `get_asset_full_profile` | Full single-asset profile combining CSAM + ETM + VMDR detections in parallel (~5-8s) |
| `get_risk_by_tag` | Risk distribution for a tagged asset group (e.g., 'PCI', 'Production', 'AWS') |
| `get_environment_summary` | Fast all-CSAM environment snapshot: OS, cloud, EOL, criticality tiers (<3s) |
| `get_asset_inventory` | What assets do we have? Search by OS, tag, or query; EOL filtering, stale-asset detection, OS/tag breakdowns with total count |
| `get_tech_debt` | How many EOL/EOS systems do we have? OS and hardware lifecycle status |
| `get_cloud_risk` | What's our cloud security posture? AWS/Azure/GCP accounts and failed controls |
| `get_image_vulns` | What vulns are in this container image? Severity breakdown and fixes |

### Web & Application Security

| Tool | What it answers |
|------|----------------|
| `get_webapp_vulns` | What web app vulnerabilities were found? Per-app breakdown, OWASP Top 10 mapping, vulnerability categories (XSS/SQLi/CSRF), severity stats. Filter by severity, days, app name, or OWASP category |
| `get_expiring_certs` | SSL/TLS cert expiry + issue detection: expiring/expired certs, weak keys, SHA-1, self-signed, TLS 1.0/1.1, per-cert grading |

### Threat Detection

| Tool | What it answers |
|------|----------------|
| `get_edr_events` | What endpoint threats were detected? Malware, ransomware, C2, lateral movement — summary counts, per-category breakdown, top affected hosts, normalized severity labels |
| `get_fim_events` | What file changes happened? Summary counts (modified/created/deleted), top affected hosts, critical-path alerts (/etc/passwd, /etc/shadow, registry keys), off-hours change flagging. Filter by host, path, severity |
| `get_cdr_findings` | What cloud threats were detected? CDR findings from TotalCloud (malware, C2, crypto-miners) |

### Patch Management

| Tool | What it answers |
|------|----------------|
| `get_pm_status` | What's our patch deployment status? Jobs, patch severity breakdown, asset coverage %, failed/active job counts. Filter by platform (Windows/Linux/macOS/all), status, and date range |

### Remediation Workflow

| Tool | What it answers |
|------|----------------|
| `get_remediation_tickets` | What remediation tickets are open? Filter by status, assignee, or overdue flag. Shows QID, severity, due dates, and assignees |
| `create_remediation_ticket` | Create a remediation ticket for a QID + asset combo with assignee |
| `get_sla_status` | What's our SLA compliance? Open/closed/overdue counts, compliance rate, MTTR by severity, overdue ticket details |

### QID Lookups

| Tool | What it answers |
|------|----------------|
| `get_qid_details` | What is this QID? Severity, CVEs, threat intel, affected assets |

### Admin

| Tool | What it answers |
|------|----------------|
| `cache_status` | What's cached? KB entries, detection cache age; use clear=True to reset |

### Threat Intel Categories

`get_threat_intel` supports filtering by any RTI (Real-Time Threat Indicator) tag:

`Ransomware` `Malware` `Active_Attacks` `Exploit_Public` `Easy_Exploit` `Wormable` `Cisa_Known_Exploited_Vulns` `Denial_of_Service` `Privilege_Escalation` `Remote_Code_Execution` `Predicted_High_Risk` `Unauthenticated_Exploitation`

## Example Conversations

### Daily Operations
```
"What happened overnight?"                     → get_morning_report()
"What should my team focus on this week?"      → get_weekly_priorities()
"How secure are we?"                           → get_security_posture()
"What new vulns came out this week?"           → get_new_vulns(days=7)
"What modules should we add?"                  → get_recommendations()
```

### CVE Investigation
```
"Are we affected by Log4Shell?"                → investigate_cve("CVE-2021-44228")
"Show me everything about CVE-2024-3400"       → investigate_cve("CVE-2024-3400")
"Compare CVE-2024-3400 and CVE-2023-4966"      → get_cve_details("CVE-2024-3400,CVE-2023-4966")
```

### Threat Intelligence
```
"What vulns have active ransomware?"           → get_threat_intel(threat_type="Ransomware")
"Show me vulns with public exploits"           → get_threat_intel(threat_type="Exploit_Public")
"Which CISA KEV vulns are we exposed to?"      → get_threat_intel(threat_type="Cisa_Known_Exploited_Vulns")
```

### Software Vulnerabilities
```
"Show me Apache vulnerabilities"               → get_vulns_by_software("Apache")
"Are we running vulnerable Log4j?"            → get_vulns_by_software("log4j")
"What OpenSSL vulnerabilities do we have?"    → get_vulns_by_software("OpenSSL")
```

### Asset & Patching
```
"What should we patch first?"                  → get_patch_status()
"What's wrong with asset 233946644?"           → get_asset_risk("233946644")
"How many EOL systems do we have?"             → get_tech_debt()
"What's our patch/mitigate status?"            → get_eliminate_status()
"Show me all Windows assets"                   → get_asset_inventory(os="Windows")
"What's our patching pipeline for Linux?"      → get_pm_status(platform="Linux")
```

### Cloud & Infrastructure
```
"What's our cloud posture?"                    → get_cloud_risk()
"What cloud threats were detected this week?"  → get_cdr_findings(days=7)
"Show me critical AWS CDR findings"            → get_cdr_findings(severity="CRITICAL", cloud_provider="AWS")
"Are our scanners healthy?"                    → get_scanner_health()
"What scans are running right now?"            → get_scan_status(state="Running")
```

### ETM Findings
```
"Show me all confirmed critical findings"      → get_etm_findings(qql="vulnerabilities.vulnerability.severity:5")
"Am I affected by Log4Shell across all sources?" → get_etm_findings(qql="vulnerabilities.vulnerability.cveIds:CVE-2021-44228")
```

### Web App & Certificate Security
```
"What web app vulnerabilities do we have?"     → get_webapp_vulns()
"Show me critical WAS findings for our portal" → get_webapp_vulns(severity=5, app_name="portal")
"Any SQL injection findings this week?"        → get_webapp_vulns(owasp_category="Injection", days=7)
"OWASP Top 10 breakdown across all apps"       → get_webapp_vulns(severity=0, days=90)
"Which SSL certs expire this month?"           → get_expiring_certs(days=30)
"Are any certs already expired?"               → get_expiring_certs(include_expired=True)
"Show me certs with issues (weak/self-signed)" → get_expiring_certs(weak_only=True)
"Are any servers still using TLS 1.0?"         → get_expiring_certs(weak_only=True)
```

### Endpoint & File Integrity
```
"What malware was detected this week?"         → get_edr_events(days=7)
"Show me critical endpoint threats"            → get_edr_events(severity="Critical")
"What file changes happened today?"            → get_fim_events(days=1)
"Were /etc/passwd or sudoers modified?"        → get_fim_events(path="/etc/passwd")
```

### Compliance
```
"What's our PCI-DSS compliance score?"        → get_compliance_posture(framework="PCI-DSS")
"Show me failing CIS controls"                 → get_compliance_posture(framework="CIS")
"What exceptions expire soon?"                 → get_vuln_exceptions(days_to_expiry=30)
```

### Remediation Workflow
```
"What remediation tickets are open?"           → get_remediation_tickets(status="OPEN")
"Show me overdue remediation tickets"          → get_remediation_tickets(overdue=True)
"What tickets are assigned to jsmith?"         → get_remediation_tickets(assignee="jsmith")
"Create a ticket for QID 376267 on asset 123" → create_remediation_ticket(qid="376267", asset_id="123", assignee="jsmith")
"What's our SLA compliance rate?"             → get_sla_status()
"What's our mean time to remediate?"          → get_sla_status()
```

### Multi-Tool Workflows
```
"New critical CVE just dropped — what do I need to know?"
→ investigate_cve() → get_threat_intel() → get_patch_status()

"Prepare me for the weekly security standup"
→ get_morning_report() → get_weekly_priorities() → get_eliminate_status()

"Briefing the CISO on our security program"
→ get_security_posture() → get_threat_intel() → get_patch_status() → get_recommendations()

"We're about to go through a PCI-DSS audit. Where do we stand?"
→ get_security_posture() → get_cloud_risk() → get_tech_debt() → get_compliance_posture(framework="PCI-DSS") → get_expiring_certs()
```

See [docs/examples.md](docs/examples.md) for the full Q&A reference with 100+ mapped examples.

## Eval Harness

Automated evaluation that sends 500 customer questions from `docs/questions.md` against the live MCP server and scores response quality using Claude-as-judge.

### Requirements

```bash
pip install anthropic mcp
export ANTHROPIC_API_KEY="sk-..."
```

### Usage

```bash
# Full eval (500 questions)
python -m eval

# Smoke test (~20 questions, fast)
python -m eval --quick

# Single category
python -m eval --category "Vulnerability Management"

# First N questions
python -m eval --limit 10

# Set pass/fail threshold (default: 0.7)
python -m eval --threshold 0.8

# Don't update coverage tags in docs/questions.md
python -m eval --no-update
```

### Scoring

Each response is scored by Claude-as-judge:

| Score | Weight | Meaning |
|-------|--------|---------|
| `correct` | 1.0 | Tool called, returned data, answered well |
| `partial` | 0.5 | Tool called but answer incomplete |
| `wrong` | 0.0 | Wrong tool or missed the point |
| `tool-error` | 0.0 | Tool exception or no tool called |

Results are saved to `eval_results/YYYY-MM-DD.json` with per-question detail and automatic regression detection against previous runs.

## CI/CD

### What runs on every PR

| Check | Description |
|-------|-------------|
| Smoke Test | `test_tools.sh fast` — all fast tools return without errors |
| Conversation Tests | `tests/run_conversations.py` — multi-turn context validation (no credentials needed) |
| Benchmark Regression | `benchmark.py --quick` — fails if any tool is >2x baseline latency |
| Eval Harness | `eval.py --quick --limit 50` — fails if score <80% (skipped if eval.py not present) |

A summary comment with benchmark latencies and eval score is posted on the PR automatically.

### Nightly Full Eval

Runs every night at 2am UTC on `main`:
- Full benchmark (all tools)
- Full eval suite (if `eval.py` exists)
- Conversation tests
- Results uploaded as artifacts (retained 90 days)

### Required GitHub Secrets

| Secret | Description |
|--------|-------------|
| `QUALYS_USERNAME` | Qualys API username |
| `QUALYS_PASSWORD` | Qualys API password |
| `QUALYS_BASE_URL` | Qualys API base URL (e.g. `qualysapi.qualys.com`) |
| `QUALYS_GATEWAY_URL` | Qualys Gateway URL (e.g. `gateway.qg1.apps.qualys.com`) |

### Updating the Benchmark Baseline

```bash
python benchmark.py --quick --json benchmark_baseline.json
git add benchmark_baseline.json && git commit -m "chore: update benchmark baseline"
```

### Eval Threshold

Default pass threshold is **80%**. Override by setting the `EVAL_THRESHOLD` repository variable in GitHub Settings → Variables.

## Qualys PODs

| POD | BASE_URL | GATEWAY_URL |
|-----|----------|-------------|
| US1 | qualysapi.qualys.com | gateway.qg1.apps.qualys.com |
| US2 | qualysapi.qg2.apps.qualys.com | gateway.qg2.apps.qualys.com |
| US3 | qualysapi.qg3.apps.qualys.com | gateway.qg3.apps.qualys.com |
| EU1 | qualysapi.qualys.eu | gateway.qg1.apps.qualys.eu |
| EU2 | qualysapi.qg2.apps.qualys.eu | gateway.qg2.apps.qualys.eu |

## CI / Testing

Every pull request runs a CI pipeline with four stages:

| Step | What it does | Fails PR if |
|------|-------------|-------------|
| **Smoke Test** | `bash test_tools.sh fast` — verifies tools return without errors | Any tool errors |
| **Benchmark** | `python benchmark.py --quick --json benchmark_results.json` — measures latency | Any tool >2x baseline |
| **Eval Harness** | `python eval.py --quick --limit 50` — scores responses against expected keywords | Score < 80% (configurable) |
| **Conversation Tests** | `pytest tests/conversations/ -v` — multi-turn conversation flow tests | Any test fails |

A nightly workflow runs the full eval suite (`--limit 500`) and updates `benchmark_baseline.json` on main.

### Required GitHub Secrets

| Secret | Description |
|--------|-------------|
| `QUALYS_USERNAME` | Qualys API username |
| `QUALYS_PASSWORD` | Qualys API password |
| `QUALYS_BASE_URL` | Qualys API base URL (e.g., `qualysapi.qualys.com`) |
| `QUALYS_GATEWAY_URL` | Qualys Gateway URL (e.g., `gateway.qg1.apps.qualys.com`) |

Optional: Set `EVAL_PASS_THRESHOLD` as a repository variable to override the default 80% pass threshold.

### Running Tests Locally

```bash
# Smoke test
bash test_tools.sh fast

# Benchmark (quick mode)
python benchmark.py --quick --json benchmark_results.json

# Eval harness
python eval.py --quick

# Conversation tests
pip install pytest
pytest tests/conversations/ -v

# Update benchmark baseline
python benchmark.py --quick --json benchmark_baseline.json
```

## License

MIT - Copyright (c) 2026 Andrew Nelson
