Wiring VulnSea into an agent: a hands-on tutorial
Most vulnerability databases are built for a human with a browser. VulnSea is built so a program — a cron job, a CI step, or a tool-use LLM — can read the whole corpus without scraping HTML or parsing prose. Every CVE is structured markdown with stable frontmatter, mirrored as JSON, and served through an incremental, cache-aware API plus a native MCP endpoint.
This post is a tutorial. By the end you'll have a working triage loop, an SBOM check, and an MCP server your model can call directly — and you'll see why each piece is cheaper to run against VulnSea than against a raw feed.
Everything below works today. Anonymous callers get the latest CVEs (the free public window); an API key unlocks the full corpus and a higher rate limit. Grab a key from your account and export it:
export AUTH="Authorization: Bearer vsk_your_key_here"
1. The one number your loop should sort on: depth score
Base CVSS overstates urgency — a critical nobody is exploiting outranks a high that's already in the wild. VulnSea attaches a depth score to every record: a 0–100 composite that blends impact (CVSS/severity), likelihood (EPSS), and proof of exploitation (KEV, 0day, in-the-wild, public PoC). It deliberately ranks an exploited high above a quiet critical.
Ask for just the fields you need and let the score do the prioritizing:
curl -s -H "$AUTH" \
"https://vulnsea.com/api/cve?severity=critical,high&fields=id,severity,depthScore,depth,kev,epss&limit=20"
{
"count": 20,
"access": "full",
"results": [
{ "id": "CVE-2026-34910", "severity": "critical", "depthScore": 97, "depth": "hadal", "kev": true, "epss": 0.94 },
{ "id": "CVE-2026-34712", "severity": "high", "depthScore": 71, "depth": "abyssal", "kev": true, "epss": 0.61 }
]
}
depth is the coarse triage zone (sunlit → twilight → midnight → abyssal → hadal); depthScore is the fine-grained number. Sort your work queue on
depthScore descending and you're patching in real-world-risk order, not
base-score order. The fields parameter trims the payload to exactly those keys
— fewer tokens when the response feeds an LLM.
2. Poll only what's new (and pay nothing when idle)
The expensive way to stay current is re-fetching everything. VulnSea gives you a high-water mark and a cursor, so an agent only ever processes deltas.
# First run: newest-last, page on `next` until it's null. Save the last id/time.
curl -s -H "$AUTH" \
"https://vulnsea.com/api/cve?since=2026-06-30T00:00:00Z&order=asc&limit=100"
Each response carries a next cursor and every row a stable ingestedAt. Store
the newest one; next run, pass it as since. Between runs, use the cheap probe or
a conditional request so an idle poll costs almost nothing:
# HEAD gives you the corpus size + newest ingest time in headers — no body.
curl -sI -H "$AUTH" "https://vulnsea.com/api/cve" | grep -i x-max-ingested-at
# Or send the previous ETag and get a 304 when nothing changed.
curl -s -H "$AUTH" -H "If-None-Match: \"$ETAG\"" \
"https://vulnsea.com/api/cve?since=$HWM&order=asc"
# 304 => sleep | 200 => new records to process
Why it matters: a poll every few minutes is basically free until something actually lands. That's what makes tight schedules affordable.
3. A complete triage loop
Here's the whole pattern in one script — poll the delta, gate on structured fields (no LLM tokens spent on noise), then deep-read only the survivors.
#!/usr/bin/env bash
set -euo pipefail
AUTH="Authorization: Bearer $VULNSEA_KEY"
HWM=$(cat .hwm 2>/dev/null || echo "2026-06-01T00:00:00Z")
# Server-side narrowing: only critical/high that are actually being exploited,
# oldest-first so we can advance the mark safely. ndjson = one CVE per line.
curl -s -H "$AUTH" \
"https://vulnsea.com/api/digest?since=$HWM&severity=critical,high&exploited=true&order=asc&format=ndjson" \
| while IFS= read -r line; do
id=$(echo "$line" | jq -r .id)
score=$(echo "$line" | jq -r .depthScore)
# Only escalate the genuinely urgent — depth score is the single gate.
if [ "$score" -ge 70 ]; then
# Deep-read the canonical markdown; feed it straight to your model/ticketer.
curl -s -H "$AUTH" "https://vulnsea.com/cve/$id.md" > "/tmp/$id.md"
echo "ESCALATE $id (depth $score)"
fi
echo "$line" | jq -r .ingestedAt > .hwm # advance the high-water mark
done
The gate is pure structured data — severity, exploited, depthScore — so you
never pay for a model call on the 95% that don't matter. The .md you fetch for
survivors is narrative the model can cite verbatim.
4. "Am I affected?" — match your dependencies
The highest-leverage question isn't "what's new" — it's "does any of this hit
my stack." Post a dependency list to /api/sbom and get back the CVEs per
component, ranked by depth, each tagged with whether your version is in range and
the fixed version to jump to.
curl -s -H "$AUTH" -H "content-type: application/json" \
https://vulnsea.com/api/sbom \
-d '{"components":["pkg:npm/[email protected]","pkg:pypi/[email protected]","log4j-core 2.14.1"]}'
{
"summary": { "components": 3, "resolved": 3, "vulnerable": 3, "matches": 7 },
"results": [
{
"name": "log4j-core", "version": "2.14.1",
"matches": [
{ "id": "CVE-2021-44228", "depth": "hadal", "versionMatch": "in-range",
"fixedVersion": "2.15.0", "kev": true }
]
}
]
}
Accepts purls (pkg:npm/[email protected]) or {ecosystem,name,version} objects.
Point it at a package.json, requirements.txt, go.mod, or lockfile in CI and
fail the build on an in-range hadal match. No account? The same engine backs the
no-signup Am I affected? tool — paste a manifest and see
results in ten seconds.
5. Skip the HTTP glue: the MCP endpoint
If your agent runtime speaks the Model Context Protocol (Claude, Cursor, and
friends), you don't need to hand-write any of the above. VulnSea ships a native
MCP server at /api/mcp exposing list_cves, search_cve,
get_cve, batch_get_cve, triage_sbom, related_cve, and get_stats — same
auth and tiering as the REST API, and every result carries the depth +
depthScore.
{
"mcpServers": {
"vulnsea": {
"url": "https://vulnsea.com/api/mcp",
"headers": { "Authorization": "Bearer vsk_your_key_here" }
}
}
}
Now your model can answer "are we exposed to anything like Log4Shell, and which
of our services run it?" by calling search_cve then triage_sbom itself — no
integration code, no OpenAPI codegen, no prompt describing endpoints.
6. Let the spec write your client
Prefer a typed client or your own tool definitions? Point any tool-use model or
codegen at the OpenAPI 3.1 spec and the machine manifest at
/llms.txt. Both describe the full surface — auth, paging, the
fields/ids/probe cost knobs, and every field's meaning — so you generate the
integration instead of writing it.
Why VulnSea, specifically
- One defensible priority number.
depthScorefolds CVSS + EPSS + real exploitation into a 0–100 you can sort on. Sort the UI feed by it withsort=risk. - Delta-first and cache-aware.
since+ cursors +304mean frequent polling is cheap, and your loop only touches what changed. - Structured, then narrative. Gate on typed fields for free, deep-read the
.mdonly when it earns the tokens. - Agent-native, not bolted on. REST, OpenAPI,
/llms.txt, and a hosted MCP server — pick the integration depth you want. - Answers "am I affected," not just "what exists." SBOM triage turns the corpus into a checklist against your actual dependencies.
Start with the About page for the full workflow patterns, grab a key on your account, and check freshness anytime on the status page.
Sound the depths → vulnsea.com