@cyanheads/osv-advisory-mcp-server
Query OSV.dev for package vulnerabilities, batch-audit dependency lists, and fetch full advisory records via MCP. STDIO or Streamable HTTP.
4 Tools
Overview
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 |
Capability reference
- Accepts
name, ecosystem (case-sensitive exact match), and version โ an exact version string, not a range
- Surrounding whitespace is trimmed from all three before the request, and
queryMeta echoes the trimmed values; interior whitespace (Rocky Linux) is kept. Blank or whitespace-only values are rejected before any upstream call
- Returns matching advisories with OSV IDs, CVE
aliases, severity entries (CVSS vectors, Ubuntu priorities), severityLabel with its severitySource, fixedVersions, affectedRanges (SEMVER/ECOSYSTEM/GIT), and cweIds
fixedVersions 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 range
truncated: true means OSV paginated beyond OSV_QUERY_MAX_PAGES (default 10) โ an empty vulns array with truncated: true is NOT a confirmed clean result
- Typed
invalid_ecosystem error when the ecosystem string isn't recognized by OSV โ call osv_list_ecosystems for valid values, then retry
aliases on each vuln chain to nist-nvd-mcp-server for CVSS base scores, EPSS exploitation probability, and CISA KEV status
- Accepts an array of
{name, ecosystem, version} tuples, 1โ1000 per call; results[i] corresponds positionally to packages[i]
- Each row's fields are trimmed of surrounding whitespace the same way as
osv_query_package, and results[i] echoes the trimmed values; a blank field in any row rejects the call
- Per-package
vulnerable, vulnCount, vulns (with aliases, severityLabel, and the row package's fixedVersions), and a nullable error โ one bad ecosystem or upstream failure fails only that row, not the whole batch
- Aggregate
summary: totalPackages, vulnerableCount, cleanCount, truncatedCount, errorCount, totalVulns, worstSeverity
cleanCount excludes truncated rows โ a per-package truncated: true result is never counted clean even with zero findings
- Per-package requests run in parallel, capped by
OSV_BATCH_CONCURRENCY (default 10)
- Accepts one exact, complete advisory ID from any OSV source database, matched case-sensitively โ
GHSA- (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
- Surrounding whitespace is trimmed before the request (
" 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 form
- Returns the full record โ
details 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.)
- Typed
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-server
withdrawn is present only on retracted advisories โ treat as no longer active, not as an error
- No input; returns the static list of valid
ecosystem identifier strings plus an advisory note on currency
- Ecosystem strings are case-sensitive exact matches โ
"pypi" fails where "PyPI" succeeds
- Every ecosystem in the OSV schema's
ecosystemName 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 additions
Features
Built 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:
- No API key required โ OSV.dev is fully public, keyless, and has no published rate limit
osv_query_batch issues parallel per-package requests (capped by OSV_BATCH_CONCURRENCY) and returns full records, including aliases, that the upstream OSV batch endpoint omits
- Per-package failures are isolated in
osv_query_batch โ one invalid ecosystem or upstream error surfaces as that row's error without failing the whole batch
- Ecosystem discovery via
osv_list_ecosystems โ the OSV schema's ecosystems that OSV.dev accepts, checked against both with bun run check:ecosystems
Agent-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 status
severityLabel 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 label
- Truncation is never silently treated as clean โ
truncated (single query) and per-package truncated plus truncatedCount (batch) flag incomplete OSV pagination, and truncated rows are excluded from cleanCount
- Query echo (
queryMeta / effectiveQuery) and aggregate batch summary (worstSeverity, vulnerableCount, cleanCount) let agents verify requests and triage without reading every row
- Advisory text is framed as untrusted data in
content[] and escaped at the render boundary: tag-shaped text (<template>, <script), autolinks, reference definitions, non-http(s) link destinations (javascript:), and forged frame tags can't turn into live HTML or links in a Markdown client. structuredContent keeps every OSV string verbatim
Getting started
Public Hosted Instance
A 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"
}
}
}
Self-Hosted / Local
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
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key required โ OSV.dev is fully public.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/osv-advisory-mcp-server.git
- Navigate into the directory:
cd osv-advisory-mcp-server
- Install dependencies:
- Configure environment:
Configuration
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 osv_query_package follows 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 |
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.
Running the server
Local development
-
Build and run:
bun run rebuild
bun run start:stdio
bun run start:http
-
Run checks and tests:
bun run devcheck
bun run test
bun run lint:mcp
bun run check:ecosystems
Docker
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.
Project structure
| 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/. |
Development guide
See CLAUDE.md/AGENTS.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches โ no
try/catch in tool logic
- Use
ctx.log for request-scoped logging, ctx.enrich for response context, and ctx.signal for cancellable OSV requests
- Register new tools via the barrel in
src/mcp-server/tools/definitions/index.ts
- Wrap external API calls: validate raw โ normalize to domain type โ return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 โ see LICENSE for details.