Skip to main content
Glama
README.md
# CVE MCP server

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
![Node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)
![MCP](https://img.shields.io/badge/MCP-stdio%20%7C%20HTTP-purple)

An [MCP](https://modelcontextprotocol.io) server that finds known vulnerabilities in your
dependencies and tells you which ones matter now.

## The problem

`npm audit`, `composer audit` and similar tools list every known vulnerability in your
dependencies, and on a real project that is often dozens or hundreds of entries, most marked
"high". They don't tell you which ones attackers are using today, they only cover what is already
installed, and turning the list into a decision (what to fix first, what to upgrade to, does it
even apply to how we use the library) is left to you.

This server gives Claude (Claude Code, claude.ai, mobile) live access to the public vulnerability
databases, so you can ask in plain language:

- "Scan this project's lockfiles: what must I fix today?"
- "Is laravel/framework 8.0.0 safe? What's the smallest upgrade that fixes the critical ones?"
- "Any new Laravel or Vite CVEs in the last 30 days?"
- "Does CVE-2025-30208 affect us, given we never expose the Vite dev server?"

It doesn't replace your audit in CI; it is what you use after it, to decide what to do.

## How it works

1. **Lookup.** Packages and versions are checked against [OSV.dev](https://osv.dev) (which merges
   GitHub advisories, npm, Packagist, PyPI and more, including reports of malicious packages).
   CVE records come from [CVE.org](https://www.cve.org) and keyword searches from
   [NVD](https://nvd.nist.gov).
2. **Risk.** Every finding is enriched with two signals:
   - **CISA KEV:** is this vulnerability being exploited in the wild right now?
   - **EPSS** (FIRST.org): the probability it gets exploited in the next 30 days.
3. **Ranking.** Results are sorted most urgent first: actively exploited, then by EPSS. On one real
   project, 144 findings came down to 1 that was actively exploited.
4. **Reasoning.** Claude reads the advisories and your code to judge whether each one really
   applies and proposes the upgrade path.

Everything uses free public APIs; no account needed.

## Tools

| Tool | Source | What it does |
|---|---|---|
| `get_cve` | CVE.org | Official record of one CVE |
| `search_cves` | NVD | Recent CVEs by keyword (e.g. "laravel", last 30 days) |
| `check_package` | OSV.dev | Known vulnerabilities of one package version |
| `scan_lockfile` | OSV.dev | Every dependency in a `composer.lock` / `package-lock.json` at once |

Every result carries `exploited` (listed in CISA KEV = attacked in the wild now) and `epss`
(FIRST probability of exploitation in the next 30 days), and lists are sorted most urgent first.
All sources are free and need no account. `NVD_API_KEY` (optional) raises NVD's rate limit.

## Installation

### Requirements

- Node.js 20 or newer
- An MCP client: Claude Code, Claude Desktop, claude.ai, or any other client that speaks MCP

No API keys are needed, and nothing to clone: `npx` downloads and runs the server from GitHub.
Optionally get a free [NVD API key](https://nvd.nist.gov/developers/request-an-api-key) to raise
the NVD rate limit for `search_cves`, and pass it as the `NVD_API_KEY` environment variable.

### Claude Code

```bash
claude mcp add cve --scope user -- npx -y github:AlessioMealli/mcp-cve-server
```

`--scope user` makes it available in every project. Check it with `claude mcp list`.

### Claude Desktop

Open **Settings → Developer → Edit Config** (or edit the file directly:
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS,
`%APPDATA%\Claude\claude_desktop_config.json` on Windows) and add:

```json
{
  "mcpServers": {
    "cve": {
      "command": "npx",
      "args": ["-y", "github:AlessioMealli/mcp-cve-server"]
    }
  }
}
```

Restart Claude Desktop.

### Other MCP clients (Cursor, VS Code, Windsurf, ...)

Use the same stdio command in the client's MCP config: `npx -y github:AlessioMealli/mcp-cve-server`.

### From source

```bash
git clone https://github.com/AlessioMealli/mcp-cve-server.git
cd mcp-cve-server
npm install --omit=dev
```

Then use `node /absolute/path/to/mcp-cve-server/index.js` as the command instead of `npx`.

### Remote (claude.ai, Claude mobile, teams)

Run your own hosted instance (see [Self-hosting](#self-hosting)), then:

- **claude.ai / Claude Desktop / mobile:** Settings → Connectors → **Add custom connector**,
  URL `https://your-domain/mcp`. Claude opens a consent page; paste your access token once.
- **Claude Code:** `claude mcp add --transport http cve https://your-domain/mcp`

## Usage

Just ask Claude. Some prompts that work well:

```text
Scan the lockfiles in this project and tell me what to fix first.
Check axios 0.21.0 on npm. Which version fixes everything?
Any CVEs about "wordpress" published in the last 7 days?
Explain CVE-2021-44228 and whether it is being exploited.
```

Locally, `scan_lockfile` reads a project folder by `path` (it finds `composer.lock` and
`package-lock.json`). On a hosted server it takes the lockfile text in `content` + `filename`
instead and never reads the server's disk.

## Configuration

| Variable | Mode | Required | Description |
|---|---|---|---|
| `NVD_API_KEY` | both | no | Raises the NVD rate limit for `search_cves` |
| `PORT` | hosted | no | HTTP port, default 3000. Setting it (or passing `--http`) starts the hosted mode instead of stdio |
| `CVE_AUTH_TOKEN` | hosted | yes | Owner token, at least 24 characters. Generate: `node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"` |
| `PUBLIC_URL` | hosted | yes | The public HTTPS address of the server, e.g. `https://cve.example.com` |
| `TOKENS_FILE` | hosted | no | People's tokens (default `tokens.json` next to the app) |
| `OAUTH_CLIENTS_FILE` | hosted | no | Registered OAuth clients (default `oauth-clients.json` next to the app) |

Under Phusion Passenger (Plesk, cPanel) the hosted mode starts automatically and Passenger
handles the port.

## Self-hosting

The hosted mode serves `/mcp` (Streamable HTTP), the OAuth 2.1 endpoints Claude uses to log in, and
`/health`.

```bash
PORT=3000 PUBLIC_URL=https://cve.example.com CVE_AUTH_TOKEN=... node index.js
```

Put it behind an HTTPS reverse proxy (nginx, Caddy, ...) that sets `X-Real-IP` or `X-Forwarded-For`,
then check `https://cve.example.com/health` returns `{"ok":true,...}`. Keep the token in the
environment, never in a file in the repo.

<details>
<summary>Example: Plesk with Git auto-deploy</summary>

1. **DNS:** an A record for the subdomain pointing to your server.
2. **Plesk:** add the subdomain with Document Root `<subdomain>/public` (an empty folder, so the
   app's files are never served) and a Let's Encrypt certificate.
3. **Git:** add this repository, deploy to the subdomain root, deployment action `bash deploy.sh`
   (installs dependencies and restarts the app). Add Plesk's webhook URL to the GitHub repo to
   deploy on every push.
4. **Node.js:** Application Root = subdomain root, Startup File `app.js`, mode production, and
   set `CVE_AUTH_TOKEN`, `PUBLIC_URL` and optionally `NVD_API_KEY`. Then NPM install and Restart.

</details>

### Giving access to other people

The owner token always works. Everyone else gets their own token, kept in `tokens.json` (mode 600,
never committed). Run these in the app folder, as the user the app runs as:

```bash
node tokens.js add elena "Elena Rossi"   # prints her token once
node tokens.js list                      # who has access (never prints tokens)
node tokens.js remove elena              # revoke
```

Give the person the connector URL and their token. Removing someone cuts off their Claude
connection on its next request, with no restart. Lost token: remove and add again.

## Security

- The hosted server refuses to start without a strong `CVE_AUTH_TOKEN`.
- Access and refresh tokens are HMAC-signed and checked against the people list on every request,
  so revoking someone is immediate.
- Wrong tokens on the consent page are limited to 10 per minute per IP, and the consent form only
  redirects to addresses the OAuth client registered.
- Every login and tool call is logged as one JSON line with the person's id
  (`"tenant":"elena"`), never the token or lockfile contents.
- The hosted `scan_lockfile` does not accept file paths.

Found a security issue in this server? Please open a private
[security advisory](https://github.com/AlessioMealli/mcp-cve-server/security/advisories/new)
rather than a public issue.

## Development

```bash
npm install
npm test
```

`npm test` runs two live tests against the real APIs: `test.js` (stdio: every tool, lockfile
parsing and ranking) and `test-http.js` (hosted mode: full OAuth flow, throttle, redirect check,
people add/login/remove, tampered token, 2 MB lockfile, no server paths).

## Data sources

[CVE.org](https://www.cve.org), [NVD](https://nvd.nist.gov), [OSV.dev](https://osv.dev),
[CISA KEV](https://www.cisa.gov/known-exploited-vulnerabilities-catalog) and
[FIRST EPSS](https://www.first.org/epss). This project is not affiliated with any of them.

## License

[MIT](LICENSE)