
Wraps the OSV.dev vulnerability database for querying package security advisories via MCP. Exposes four tools: single package lookups by name/ecosystem/version, batch audits for entire dependency lists (up to 1000 packages per call), full advisory retrieval by OSV ID, and ecosystem enumeration. Every vulnerability result includes CVE aliases, CVSS severity vectors, affected version ranges, and fix versions. The batch tool runs parallel queries to preserve full records that OSV's native batch endpoint omits. No API key required. Useful for triaging lockfiles, auditing SBOMs, or checking a dependency before adding it. The CVE aliases in each result chain cleanly to NIST NVD servers for EPSS scores and KEV status.
Query OSV.dev for package vulnerabilities, batch-audit dependency lists, and fetch full advisory records via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://osv-advisory.caseyjhand.com/mcp
Vulnerability data from OSV.dev, the open-source vulnerability database. Query a single package version, batch-audit a full dependency list or SBOM, and fetch complete advisory records with CVSS severity, CVE aliases, and affected version ranges. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
osv_query_package | Query known vulnerabilities for a single package version by name, ecosystem, and version |
osv_query_batch | Batch vulnerability query for an array of package tuples — one call for a full dependency list or SBOM audit |
osv_get_vulnerability | Fetch the full advisory record for a single OSV vulnerability ID |
osv_list_ecosystems | Return the list of supported ecosystem identifier strings |
The three advisory tools return at most 24,000 UTF-8 bytes per serialized CallToolResult, including content[], structuredContent, and enrichment. Fitting results keep their usual shape. Larger replies carry overflow with omitted pointers and re-call arguments; preview contains only the compact values that fit. Package previews state advisoryListComplete: true when every ID, alias, scoped severity label, and fix is included. Without that preview, retrieve /vulns before treating the advisory list as complete. Initial batch overflow always retains the complete scan summary.
Reuse the original input and add the returned page arguments. page.path is an RFC 6901 JSON Pointer; offset defaults to zero. Array offsets count entries, object offsets count keys in source order, and text offsets count Unicode code points. Each reply includes type, total, revision, and nextOffset; continue until nextOffset is null. An oversized entry carries deferred.path instead of its value: retrieve that child separately, then continue the parent. Structured values remain verbatim.
{"id":"V8-FRESHNESS","page":{"path":"/affected/0/versions","offset":0}}
{"id":"V8-FRESHNESS","page":{"path":"/details","offset":0}}
{"name":"Django","ecosystem":"PyPI","version":"3.2.0","page":{"advisoryId":"PYSEC-2022-190","path":"/affectedRanges/0/versions","offset":0}}
Package collection pages re-query one package. page.advisoryId fetches one announced advisory by ID and preserves the original package matcher for fixes and severity. Batch pages accept /results or /results/<original-index>/... with the original packages array: row pages fetch only successive needed rows, while a selected descendant queries only that row. They retain original indices and do not recompute aggregate statistics. Other batch pointers require a fresh initial scan.
Every page is a fresh lookup, not a saved historical snapshot. A supplied node revision rejects changed data with stale_page_revision; restart that node at offset zero without a revision. Batch collection revisions identify the original input order; selected row and descendant revisions identify their fresh normalized values. Unknown paths fail with invalid_page_path and bounded recovery guidance. Paths are limited to 1,024 characters, offsets must be nonnegative integers, and the advisory selector is accepted only by osv_query_package.
Transmission overflow is separate from OSV pagination truncated. Neither omitted data nor a truncated empty list confirms that a package is clean.
osv_query_package toolname, ecosystem (case-sensitive exact match), and version — an exact version string, not a rangequeryMeta echoes the trimmed values; interior whitespace (Rocky Linux) is kept. Blank or whitespace-only values are rejected before any upstream callaliases, severity entries (CVSS vectors, Ubuntu priorities), severityLabel with its severitySource, fixedVersions, affectedRanges (SEMVER/ECOSYSTEM/GIT), and cweIdsfixedVersions lists every fix the advisory records for the queried package (one per affected interval), matched the way OSV matches the query — release-suffixed ecosystems (Debian → Debian:12, Ubuntu:22.04 → Ubuntu:22.04:LTS) and PEP 503 names on PyPI. Other packages' fixes and GIT commits stay out of it; affectedRanges keeps every rangetruncated: true means OSV paginated beyond OSV_QUERY_MAX_PAGES (default 10) — an empty vulns array with truncated: true is NOT a confirmed clean resultinvalid_ecosystem error when the ecosystem string isn't recognized by OSV — call osv_list_ecosystems for valid values, then retryaliases on each vuln chain to nist-nvd-mcp-server for CVSS base scores, EPSS exploitation probability, and CISA KEV statusosv_query_batch tool{name, ecosystem, version} tuples, 1–1000 per call; results[i] corresponds positionally to packages[i]osv_query_package, and results[i] echoes the trimmed values; a blank field in any row rejects the callvulnerable, vulnCount, vulns (with aliases, severityLabel, and the row package's fixedVersions), and a nullable error — invalid-package recovery isolates rows; a failed shared advisory affects its dependent rows, while a failed preflight affects the submitted groupsummary: totalPackages, vulnerableCount, cleanCount, truncatedCount, errorCount, totalVulns, worstSeveritycleanCount excludes truncated rows — a per-package truncated: true result is never counted clean even with zero findings/v1/querybatch preflight follows independent row tokens, then fetches each unique advisory once per call; clean rows need no advisory request. Raw records are shared, while fixes and severity are derived separately for each packageOSV_BATCH_CONCURRENCY (default 10) and caller cancellation. The complete scan has a 50-second deadline; unfinished rows carry errorsosv_get_vulnerability toolGHSA- (GitHub), PYSEC- (PyPI), RUSTSEC- (Rust), GO- (Go), DSA-/DLA- (Debian), USN- (Ubuntu), RHSA- (Red Hat), CVE-, and the rest. IDs come from osv_query_package / osv_query_batch results" GHSA-29mw-wpgm-hmr9 " resolves); input that can't be an OSV ID (wildcards, a bare package name, a prefix with no ID) is rejected before any upstream call, with a message naming the expected formdetails text, all CVE aliases, every affected package with its version ranges, ordered fixed events, and any package-level severity, severity entries with severityLabel and severitySource, cweIds, and references (ADVISORY, FIX, REPORT, etc.)vulnerability_not_found error when the ID doesn't exist in OSV — the recovery covers case and the Debian/Ubuntu/SUSE revision suffix (DSA-5678-1, not DSA-5678); a CVE-style alias may still resolve via nist-nvd-mcp-serverwithdrawn is present only on retracted advisories — treat as no longer active, not as an errorosv_list_ecosystems toolecosystem identifier strings plus an advisory note on currency"pypi" fails where "PyPI" succeedsecosystemName enum that OSV.dev accepts at query time, plus GIT (accepted via the ecosystemWithSuffix pattern); a schema ecosystem OSV.dev still rejects is left out. The note carries the verification date; the list may lag later additionsBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
OSV-specific:
osv_query_batch combines native batch ID preflight with deduplicated advisory retrieval to preserve aliases, scoped fixes, and severity. An indexed validation rejection isolates that row and retries the remaining group once; repeated or non-indexed rejection uses bounded per-package recoveryosv_query_batch reports failures in each affected row's error; invalid-package recovery isolates rows, shared advisory failures affect dependent rows, and preflight failures affect the submitted grouposv_list_ecosystems — the OSV schema's ecosystems that OSV.dev accepts, checked against both with bun run check:ecosystemsAgent-friendly output:
aliases (CVE IDs) surfaced on every vuln entry — the composition point for chaining to nist-nvd-mcp-server for CVSS base scores, EPSS, and CISA KEV statusseverityLabel from the first source that yields one: database_specific.severity (GHSA, openEuler, and others; Medium reads as MODERATE), an Ubuntu priority, then the highest CVSS v3/v4 score computed from the vector as published (every metric group of a CVSS 4.0 vector; base and temporal for CVSS 3.x). Package-level severity counts when the record has none. severitySource names the entry used, with the computed score for CVSS; both are null rather than fabricated when no source yields a labeltruncated (single query) and per-package truncated plus truncatedCount (batch) flag incomplete OSV pagination, and truncated rows are excluded from cleanCountqueryMeta / effectiveQuery) and aggregate batch summary (worstSeverity, vulnerableCount, cleanCount) let agents verify requests and triage without reading every rowcontent[] and escaped at the render boundary: tag-shaped text, autolinks, reference definitions, and non-http(s) link destinations cannot become live HTML or links in a Markdown client. Recognized variants of the two advisory frame tokens render as annotated text, including in code blocks; this is finite token handling, not semantic prompt-injection prevention. structuredContent keeps every OSV string verbatimA public instance is available at https://osv-advisory.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"osv-advisory-mcp-server": {
"type": "streamable-http",
"url": "https://osv-advisory.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file. No API key is required — OSV.dev is fully public.
{
"mcpServers": {
"osv-advisory-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/osv-advisory-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"osv-advisory-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/osv-advisory-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"osv-advisory-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"ghcr.io/cyanheads/osv-advisory-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/osv-advisory-mcp-server.git
cd osv-advisory-mcp-server
bun install
cp .env.example .env
# edit .env if needed (no required vars)
All configuration is validated at startup. No server-specific env vars are required — OSV.dev is keyless and fully public.
| Variable | Description | Default |
|---|---|---|
OSV_REQUEST_TIMEOUT_MS | HTTP request timeout for OSV.dev API calls, in milliseconds. | 10000 |
OSV_BATCH_CONCURRENCY | Maximum concurrent OSV.dev requests issued by osv_query_batch. | 10 |
OSV_QUERY_MAX_PAGES | Maximum OSV.dev result pages followed per package by the query tools before marking a result truncated. | 10 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path. | /mcp |
MCP_PUBLIC_URL | Public origin override for TLS-terminating reverse-proxy deployments. | none |
MCP_SESSION_MODE | HTTP session mode: stateful, stateless, or auto. createApp() declares stateless — no tool has a multi-round input flow — and setting this overrides it. | stateless |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
LOGS_DIR | Directory for log files (Node.js only). | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
STORAGE_PROVIDER_TYPE | Storage backend. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
Build and run:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
bun run check:ecosystems # Compare osv_list_ecosystems with the live OSV schema and API
docker build -t osv-advisory-mcp-server .
docker run --rm -p 3010:3010 osv-advisory-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/osv-advisory-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts) — osv_query_package, osv_query_batch, osv_get_vulnerability, osv_list_ecosystems. |
src/services/osv-api | OSV.dev REST API service — fetch, retry, response normalization. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.enrich for response context, and ctx.signal for cancellable OSV requestssrc/mcp-server/tools/definitions/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
OSV_REQUEST_TIMEOUT_MSdefault: 10000HTTP request timeout in milliseconds for OSV.dev API calls.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.