Production-grade MCP server giving Claude 27 security intelligence tools across 21 APIs — CVE lookup, EPSS scoring, CISA KEV, MITRE ATT&CK, Shodan, VirusTotal, and more.
Triaging a single CVE requires querying multiple disparate sources—NVD for CVSS scores, EPSS for exploitation probability, CISA KEV for active exploitation status—then mentally correlating the results. For a security team handling dozens of vulnerabilities, this manual process consumes entire workdays.
What This Does
The CVE MCP Server provides 28 security tools across 24 APIs fronted by a triage_cve orchestrator (defined in src/cve_mcp/server.py). It allows a single prompt to fan out to NVD, EPSS, CISA KEV, Shodan, and VirusTotal in parallel, calculate a composite risk score, and return a prioritized remediation recommendation with evidence. The system is built on Python, FastMCP, httpx, aiosqlite, and Pydantic v2.
How It Is Wired
Execution begins at src/cve_mcp/server.py, which initializes the FastMCP server via stdio for Claude integration. The triage_cve function acts as the primary entry point for analysts. It routes the request through utils/risk_scorer.py and utils/source_trust.py, executing API calls via httpx to clients such as nvd_client.py, epss_client.py, and kev_client.py concurrently. Results are persisted to the sqlite_cache (src/cve_mcp/cache/sqlite_cache.py). The control flow fans out to 24 data sources; the widest blast radius resides in the orchestration logic within server.py, which coordinates the parallel queries and score aggregation.
How To Use It
Setup requires Python 3.10+ and environment configuration. Consult the .env.example file for required variables, then install dependencies declared in pyproject.toml. The repository provides a Dockerfile for containerized deployment. To start the server, invoke the entry point via uv run or Docker, referencing src/cve_mcp/server.py.
Real-World Use
An analyst queries: "Should we patch CVE-2024-3400?" Claude fans out to every relevant source in parallel via triage_cve. The orchestrator calculates a composite risk score with a CISA KEV hard override and delivers a prioritized recommendation with evidence in seconds, eliminating the need to toggle between 15 browser tabs.
Code Health & Issues
Low/Risk - Dependencies declared without a lockfile - non-reproducible builds - pyproject.toml. Note: uv.lock exists in the file structure, which may mitigate this for tooling, but the runtime dependency declaration in pyproject.toml lacks a matching lockfile commitment.
The Bottom Line
This repo effectively consolidates 24 security APIs behind a single MCP interface, significantly reducing the operational overhead of CVE triage. The primary consideration is the dependency management setup; teams requiring strict reproducibility should verify the lockfile strategy. It is well-suited for security teams already invested in the Model Context Protocol workflow.
What the analyser found
Deployment readiness
6/7
✓Container image
✓CI pipeline
✗Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
66 files
Python44
YAML6
Markdown6
JSON1
TOML1
Docker
Dependencies
1 of 8 declared dependencies are a major version behind. Resolved 8 of 8 against the registry.
Package
Declared
Latest
Behind
mcp
>=1.7.0
2.0.0
1 major
Findings
LowDependencies declared without a lockfile - non-reproducible builds pyproject.toml
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high2
medium6
low1
Pin third-party GitHub Actions to a commit SHAhigh2 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Remove continue-on-error from steps that gate correctnesshigh
.github/workflows/ci.yml
line 88 on a correctness step
A failing test suite reports a green check, which is precisely the path this audit exists to close.
Fix: Delete continue-on-error, or move a genuinely advisory step into a separate non-required job.
Declare least-privilege permissions for GITHUB_TOKENmedium
.github/workflows/ci.yml
1 workflow(s) declare no permissions
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
1 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium2 occurrences
Dockerfile
python:3.12-slim, python:3.12-slim
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Move large binaries to Git LFS or out of the repositorymedium
assets/banner.png
1 blobs over 5MB: banner.png 5.4MB
One repository here carries twenty blobs over 5MB including a 10.9MB spreadsheet, so every clone and every CI checkout pays for data nobody diffs.
Fix: Track those extensions with LFS, or move datasets to object storage and fetch them in a setup step.
Give the outbound request a timeoutmedium
Dockerfile
1 outbound call(s) with no timeout
requests has no default timeout, so a peer that accepts the connection and then says nothing holds the worker forever, and with a small worker pool a single slow dependency takes the whole service down without anything appearing to fail.
Fix: Pass timeout= on every call, or use a session with one configured.
Set persist-credentials: false on checkoutmedium
.github/workflows/ci.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Set timeout-minutes on the workflow jobslow3 occurrences
.github/workflows/ci.yml
3 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.