VulnSea

MCP server

VulnSea speaks the Model Context Protocol so an agent can triage vulnerabilities without a custom integration. One remote URL, 11 tools, no install: https://beta.vulnsea.com/api/mcp. Stateless Streamable HTTP (one JSON-RPC POST, one JSON reply), protocol 2025-06-18, server version 1.0.0.

The whole corpus is open. Anonymous callers get 60 requests a minute per IP; an API key sent as Authorization: Bearer vsk_… raises that to 600.

Connect a client

Pick your runtime. Every snippet is the same URL; add the key header if you have one.

Claude Code

claude mcp add --transport http vulnsea https://beta.vulnsea.com/api/mcp
# with an API key:
claude mcp add --transport http vulnsea https://beta.vulnsea.com/api/mcp --header "Authorization: Bearer vsk_…"

Claude Desktop / claude.ai

Settings → Connectors → Add custom connector. Name VulnSea, URL https://beta.vulnsea.com/api/mcp. No OAuth: leave the client id blank.

Cursor

// .cursor/mcp.json
{
  "mcpServers": {
    "vulnsea": { "url": "https://beta.vulnsea.com/api/mcp" }
  }
}

VS Code (Copilot agent mode)

// .vscode/mcp.json
{
  "servers": {
    "vulnsea": { "type": "http", "url": "https://beta.vulnsea.com/api/mcp" }
  }
}

Anything else (raw JSON-RPC)

curl -X POST https://beta.vulnsea.com/api/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

curl -X POST https://beta.vulnsea.com/api/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"list_cves","arguments":{"depth":"hadal","limit":5}}}'

Tool results carry both a text block and structuredContent with the same JSON. Argument errors come back in-band with isError: true; only malformed JSON-RPC gets a protocol error.

Tools

Scan with list_cves or search_cve, deep-fetch with get_cve, then poll changes_since for re-triage triggers. Rows are ranked by depth score; the depth filter narrows to a zone (hadal = critical and actively exploited).

Any real CVE id works with get_cve, resolve_id and batch_get_cve: a record that is not in the corpus yet is fetched live from CVE.org and NVD on first request (a few seconds), written, and is an ordinary hit afterwards. A GHSA id is mapped to its CVE through the GitHub advisory API first.

ToolArgumentsWhat it does
list_cvesq?, severity?, vendor?, exploited?, depth?, since?, order?, limit?, cursor?List or incrementally poll CVEs, newest first. Filter by severity, vendor, exploited, or depth zone (sunlit|twilight|midnight|abyssal|hadal; deeper = graver + more proven, hadal = critical & actively exploited). Pass `since` (ISO or epoch ms) and follow `next` to poll for new additions. Returns compact rows; deep-fetch one with get_cve.
search_cveq, severity?, exploited?, kev?, depth?, limit?Typo-tolerant full-text search across the corpus (FTS5, bm25-ranked). Optional severity / exploited / kev / depth filters. Returns compact rows.
get_cveidFetch one CVE in full: structured metadata plus the complete markdown writeup (body). Use after list_cves / search_cve narrows to a record of interest. Any real CVE id works: a record not yet in the corpus is fetched live from CVE.org / NVD on first request (a few seconds) and is an ordinary hit afterwards.
batch_get_cveidsFetch a known set of CVEs in one call (e.g. an SBOM / dependency match). Up to 200 IDs; returns compact rows in request order. Up to 5 ids unknown to the corpus are fetched live first; the rest are listed in `missing`.
triage_sbomcomponentsMatch a dependency list against the corpus. Pass `components` as purls (pkg:npm/[email protected]) or {ecosystem,name,version} objects. Returns, per component, affected CVEs ranked by depth with a versionMatch (in-range | unknown). Package data is GHSA-sourced. Up to 200 components.
related_cveid, limit?CVEs related to a given id, connected by shared product / vendor / CWE / references / tags, with the reasons and a relation score (highest first). Walk an attack chain or find sibling flaws in the same component.
changes_sinceafter?, since?, field?, limit?Mutation feed: per-CVE field changes (kev added, severity bumped, exploited flipped, material epss move) as an append-only event stream with an exact numeric cursor. list_cves answers what's NEW; this answers what CHANGED about records already triaged — the re-triage trigger. Store the returned nextSeq and pass it as `after` next call.
resolve_ididResolve any advisory identifier (CVE-…, GHSA-…, or another alias) to its canonical VulnSea record — id, aliases, url, and compact metadata. Use when an upstream tool hands you a non-CVE id. Unknown ids are fetched live (a GHSA id via the GitHub advisory API, then CVE.org / NVD).
get_epss_moversdays?, limit?, minDelta?CVEs whose EPSS (exploit-prediction probability) climbed the most over a recent window — rising exploitation risk before it lands in KEV. Needs ≥2 snapshot days of history inside the window.
get_statsCorpus summary: total CVEs, critical / exploited / KEV counts, last-7-day count, and the newest ingest timestamp. Cheap freshness check before polling.
get_digestkind?, key?One digest period in compact rows: what was published, KEV additions, EPSS risers, most-changed records and top vendors. `kind` week (default) or day; `key` (2026-W36 / 2026-09-08) picks a past period, omit for the current one. The same content as the dated pages under /digest.

Required arguments in white; ? marks optional ones. The full JSON schema for each tool comes back from tools/list. Every tool is annotated readOnlyHint and idempotentHint; the three id lookups above carry openWorldHint because they may reach upstream.

Resources & prompts

Clients that speak MCP resources can attach a record or the current digest as context without a tool call (resources/read). Prompts are ready-made workflows the model fills in (prompts/get); each one names the tools to call and the shape of the answer.

ResourceTypeContent
vulnsea://statsapplication/jsonTotals, critical / exploited / KEV counts, newest ingest time.
vulnsea://digest/weekapplication/jsonPublished, KEV additions, EPSS risers, most-changed — current ISO week.
vulnsea://digest/dayapplication/jsonThe same digest for the current UTC day.
vulnsea://cve/{id}text/markdownCanonical frontmatter + writeup for one CVE. Any real id; fetched live if unknown.
vulnsea://cve/{id}/jsonapplication/jsonThe get_cve payload (record + depth score parts) for one CVE.
PromptArgumentsWhat it does
triage_sbomcomponentsMatch a dependency list against the corpus and produce a ranked patch list.
explain_cveidPlain-language brief for one CVE: what, who is affected, exploitation status, fix, urgency.
weekly_patch_planfocus?Turn this week's digest and the EPSS movers into one ordered patch plan.

A triage loop

  1. get_stats for the corpus size and newest ingest time. Nothing changed since last run, stop.
  2. list_cves with since set to your last high-water mark, following next. New records.
  3. changes_since with the stored after cursor. Records you already triaged that gained a KEV entry, a severity bump, or a material EPSS move.
  4. triage_sbom with your dependency purls when a lockfile changes. related_cve to walk sibling flaws.
  5. get_cve only for the handful worth reading in full. Records may carry owner-curated notes above the generated writeup.

Registry

VulnSea is listed in the MCP Registry as io.github.vulnsea/vulnsea. The manifest at /api/mcp/server.json is what registries and client directories read: name, description, the remote URL and the optional auth header.