vulnerability-registry-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@vulnerability-registry-mcpHow many critical vulnerabilities are still open?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Vulnerability Registry — MCP Server
A natural-language access layer over a legacy CVE registry, built as an MCP
server (TypeScript, official @modelcontextprotocol/sdk), plus an agent CLI
(Phase 2) that answers analyst questions end-to-end.
The Problem & Who It Serves
The registry's data lives in undocumented pipe-delimited text files on an internal server — no API, no search. Two personas suffer daily:
Security analysts need instant lookups during live incidents ("What is the CVSS score of Log4Shell?") and filtered investigations ("Which Linux Kernel CVEs from the past year are still open?").
Risk managers need aggregate numbers for reporting ("How many critical vulnerabilities are still open?").
Success criterion: an analyst asks a free-form question and gets a correct, data-grounded, verifiable answer in seconds instead of minutes of manual file searching.
Related MCP server: MCP Cybersecurity Server
Quick Start
npm install
npm run build
npm test # parser test suiteConnect to Claude Desktop
Add to claude_desktop_config.json (Settings → Developer → Edit Config):
{
"mcpServers": {
"vulnerability-registry": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/vulnerability-registry-mcp/dist/server.js"]
}
}
}Restart Claude Desktop and ask: "How many critical vulnerabilities are still open?"
The data directory defaults to ./data; override with a CLI argument
(node dist/server.js /path/to/data) or the VULN_DATA_DIR env var.
Phase 2 — Agent CLI
export GEMINI_API_KEY=... # free key: https://aistudio.google.com/apikey
npm run agent # interactive
npm run agent -- "Which Linux CVEs are still open?" # one-shotThe default model is the rolling alias gemini-flash-latest (resilient to
model deprecations); override with the GEMINI_MODEL env var. The default
Gemini Flash is a thinking model — for snappy answers on this registry's
simple tool routing, GEMINI_REASONING=low cuts latency dramatically.
DEBUG=1 prints raw LLM responses.
Swapping providers: the agent depends only on the OpenAI-compatible chat-completions format, so any such endpoint works via env vars alone:
# Groq
LLM_BASE_URL=https://api.groq.com/openai/v1 GEMINI_API_KEY=<groq-key> \
GEMINI_MODEL=llama-3.3-70b-versatile npm run agent
# Ollama (local, no key needed — pass any non-empty string)
LLM_BASE_URL=http://localhost:11434/v1 GEMINI_API_KEY=ollama \
GEMINI_MODEL=llama3.1 npm run agentCaveat: tool-use quality varies by model — small local models route tools less reliably than hosted ones.
Web UI (stretch)
export GEMINI_API_KEY=...
npm run ui # then open http://localhost:3000A SOC-console chat (designed with Google Stitch) wired to the same agent
module as the CLI — one agent, two frontends, mirroring the one-server
many-clients principle of MCP. Two things worth noticing: the header stats
(Open / Critical Open / Total) are live get_statistics MCP calls, not
hard-coded numbers; and every tool call the agent makes is rendered as a
chip in the conversation — the same transparency principle as the CLI's 🔧
lines, because an analyst should always see where an answer came from.
The CLI prints every tool call it makes (🔧) — an analyst should always see where an answer came from.
Tools
Tool | What it does | Example question | Persona |
| Combinable filters: free text, vendor, severity, status, date range. Paginated, newest first, always returns | "Which Linux CVEs from 2024 are open?" | Analyst (investigation) |
| Full record by CVE ID or common name ("Log4Shell"). | "What's the CVSS of Heartbleed?" | Analyst (incident) |
| Counts grouped by severity / status / vendor, with the same filters. Returns numbers, not records. | "How many critical vulns are open?" | Risk manager |
| Full vendor list — lets the LLM resolve names to IDs. | (infrastructure for the other tools) | — |
Four tools, deliberately few: each maps to a real analyst job, and the three example questions in the brief are each answerable in one or two calls.
How This Was Built — Process Notes
A note on how I actually worked, because the process was a deliberate choice.
I spent the first stretch of this assignment entirely away from code. Before deciding anything technical, I broke the problem down as a product problem: who actually suffers from data trapped in text files, what each persona asks in their worst moment (an analyst mid-incident vs. a risk manager before a board report), and whether a natural-language layer genuinely solves their problem better than a dashboard or a REST API would. I also treated the brief itself as evidence — its three example questions map one-to-one to personas, and its hints (the VERSION field, "thousands of records", the free-text version field) read as requirements, not trivia. The tool set was derived from those persona questions before a single line of implementation.
I built this with AI as a deliberate workflow, not a shortcut: I ran a structured product-thinking session with Claude (a dedicated PM prompt) to pressure-test personas, scope boundaries and tool design before development, then used it as a pair programmer during implementation. Every design decision documented in this README is one I made and can defend; the AI compressed the execution time into the assignment's 3–5 hour budget. Verification was mine too: unit tests for the parser, a protocol-level smoke test, an eval set with ground truths computed directly from the data, and live runs through both Claude Desktop and the agent CLI. The web UI was designed with Google Stitch and wired to the same agent module that powers the CLI.
Design Decisions
Dynamic format parsing — the VERSION hint. The parser reads the
# FORMAT: metadata line at load time and maps fields by column name,
never by position. Reordered or added columns in a future format version
require zero code changes (covered by tests). Unknown major versions load
with a loud warning rather than crashing — an analyst is better served by
partial data plus a warning than by a dead server. Malformed rows are
skipped, counted, and reported with line numbers: never crash, never hide.
Built for thousands of records — but the real constraint is the LLM's
context. In-memory linear scans are microseconds at this scale; the real
risk is a tool dumping hundreds of records into the model's context. Every
record-returning tool paginates (default 20, max 100) and always reports the
total match count, and get_statistics exists so counting questions cost a
few dozen tokens instead of a full listing.
Primitives, not reports. There is no generate_weekly_report tool on
purpose. Tools expose precise data primitives; the LLM composes them into
any report, for any audience, in any format. Baking presentation into the
data layer would turn every report-format change into a deployment.
Why MCP + tools, not RAG. The data is structured and the questions demand exactness — counts, filters, cross-references. Embedding retrieval answers "approximately"; a risk manager asking "how many critical open?" needs the number. Tools return ground truth; the LLM only narrates it. MCP specifically decouples the data layer from the AI layer: same server, any client, swappable model.
Faithful to the source. The client's files are the source of truth. The
server flags structural problems (orphan vendor_ids, invalid severities,
non-numeric scores) at load time but never silently "fixes" data — a system
that corrects records behind the analyst's back forfeits trust in every
answer.
SDK v1 stable, not v2 beta. The official SDK's v2 is in beta at the time
of writing. An internal server for security teams shouldn't ride a beta;
dependencies are pinned to v1 with the current registerTool API.
Errors that steer the agent. Error messages are written as prompts:
Unknown vendor_id "OpenSSL". Known vendors: V1=Microsoft, … Call list_vendors for details. The agent self-corrects in one round instead of
flailing. Zod schemas reject malformed parameters with equally actionable
messages.
stdio discipline. On stdio transport, stdout is the JSON-RPC channel.
All logging goes to stderr — there is not a single console.log in the
server path.
Resilience against a moving provider. Three real-world failures surfaced
during development, and each got a permanent fix rather than a workaround:
a deprecated model name (→ default to Google's rolling gemini-flash-latest
alias, overridable), a silently hanging request (→ 90s timeout that fails
loudly), and provider overload 503s (→ exponential-backoff retry on 429/503,
visible to the user, non-retryable errors thrown immediately). Same principle
as the dynamic parser: don't assume today's external contract holds tomorrow.
Phase 2 talks MCP, not imports. The agent connects to the Phase 1 server
as a subprocess through the real protocol (client + stdio) and discovers
tools dynamically via listTools. Importing the store directly would have
been easier — and would have proven nothing about the server. The agent core
is also provider-portable: it depends only on the OpenAI-compatible
chat-completions format (Gemini today; Groq/OpenAI is a base-URL change) and
is frontend-agnostic (the CLI is a thin wrapper; a web UI would reuse the
same module).
Data Quality Observations
Structural validation passes cleanly on the provided files — but human review found semantic misattributions the system cannot detect on its own, since the referenced vendor IDs exist:
CVE-2024-21762(Fortinet SSL VPN) is attributed to V4 = GoogleCVE-2024-27198(TeamCity, a JetBrains product) is attributed to V1 = Microsoft
Per the "faithful to the source" rule, the server reports the data as-is. This structural-vs-semantic gap is exactly what the NVD cross-checking idea below would close.
In practice, this design paid off: when queried through Claude Desktop, the model flagged both misattributions on its own — the faithful-to-source server hands over ground truth, and the LLM layer catches semantic issues that structural validation cannot.
Business Impact (ROI)
Conservative estimate: a manual lookup in raw text files takes ~10 minutes (locate file, grep, cross-reference vendor). A team of 5 analysts averaging 6 queries a day spends ~5 analyst-hours daily on lookups — roughly 1,200 analyst-hours a year reduced to seconds per query. The harder-to-quantify value is larger: during a live incident (a Log4Shell-class disclosure), time-to-answer for "are we exposed, and where?" drops from hours of manual searching to a single question — and incident exposure time is the most expensive time there is.
Evaluation
eval/questions.md contains 10 questions with
ground-truth answers computed directly from the data (via
scripts/truth.ts), including the brief's three example questions and
multi-step questions that require chained tool calls
(list_vendors → search_vulnerabilities). A protocol-level smoke test
(scripts/smoke.ts) exercises the server through a real MCP client:
TOOLS: list_vendors, get_vulnerability, search_vulnerabilities, get_statistics
Q1 (CVSS of Log4Shell): 10.0
Q2 (critical still open): 2 {"critical": 2, "high": 2 open in total}
Q3 (Linux Kernel CVEs): 4 CVE-2024-1086, CVE-2023-35829, CVE-2022-0847, CVE-2016-5195
ERR (steering check): Unknown vendor_id "OpenSSL". Known vendors: V1=Microsoft, ...Real agent transcript (Gemini, multi-tool chain chosen by the model on its own):
❓ How many critical vulnerabilities are still open?
🔧 get_statistics({"group_by":"severity","status":"open"})
🔧 search_vulnerabilities({"status":"open","severity":"critical"})
Based on the registry, there are 2 critical vulnerabilities that are still open:
1. CVE-2024-27198 (TeamCity Auth Bypass) — CVSS Score: 9.8
2. CVE-2024-21762 (Fortinet SSL VPN OOB) — CVSS Score: 9.6Note the model's own strategy: it answered the count from get_statistics
(cheap) and then enriched with search_vulnerabilities for the specific CVE
IDs — the two primitives composing exactly as designed. Implementation
detail that matters: the agent echoes the assistant message back verbatim,
preserving Gemini's thought_signature reasoning chain across tool rounds.
Multi-step question (name-to-ID resolution chosen by the model on its own):
❓ Which Linux Kernel vulnerabilities are still open?
🔧 list_vendors({})
🔧 search_vulnerabilities({"status":"open","vendor_id":"V5"})
There are 2 open vulnerabilities for the Linux Kernel (V5):
1. CVE-2024-1086 "Linux Netfilter UAF" — high (7.8), kernel 5.14-6.6
2. CVE-2023-35829 "Linux Race Condition" — high (7.0), kernel < 6.3.2Architecture
flowchart LR
subgraph Data layer
F[".db files<br/>(pipe format)"] --> P["parser.ts<br/>dynamic FORMAT mapping"]
P --> S["store.ts<br/>in-memory + query primitives"]
end
subgraph MCP server
S --> T["tools.ts<br/>4 tools, Zod schemas"]
T --> M["server.ts<br/>stdio transport"]
end
M <-->|MCP / JSON-RPC| C1["Claude Desktop"]
M <-->|MCP / JSON-RPC| C2["Agent CLI (Phase 2)<br/>Gemini via OpenAI-compat API"]Each layer knows only the one beneath it: the parser knows nothing about
MCP, the tools know nothing about file formats. Moving to PostgreSQL means
replacing store.ts and nothing else.
What I'd Build With More Time
NVD cross-checking — compare each record against the official NIST registry and flag divergences (wrong vendor attribution, changed severity, newly available patches). Directly addresses the semantic data issues found above.
Exploit-aware prioritization (EPSS / CISA KEV) — the registry ranks by CVSS alone, which modern vulnerability management considers insufficient: most CVSS-critical vulns are never exploited in the wild. Enriching records with an EPSS probability and a KEV flag would point analysts at what is actually being exploited.
Alerting layer — push a notification when a new critical vulnerability enters the registry, instead of waiting to be asked.
Scheduled digest — a weekly summary for the risk manager, composed by the agent from
get_statisticscalls.Structured
affected_versions— parse the free-text version ranges to support "is version X vulnerable?" queries. Deliberately skipped in this iteration: the field is free text by design, and a partial parser would give confidently wrong answers.A small web UI over the same agent module (it is already frontend-agnostic).
Assumptions
The client's files are read-only inputs and the single source of truth; the access layer never mutates or corrects them.
ISO dates in the data (
YYYY-MM-DD) — string comparison is used for ranges.severityandstatusvocabularies are as documented (plus validation warnings if a future file deviates).Claude Desktop fulfills the "connectable via an MCP client" requirement; the agent CLI is submitted as Phase 2 on top of it.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/feldboy/vulnerability-registry-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server