---
summary: "weaknesses[].source and references[].source now resolve NVD's contributor identifiers to published names (e.g. CVE, CISA-ADP) instead of raw GUIDs — a breaking value-format change with no schema change to warn consumers on upgrade."
breaking: true
security: false
---

# 0.2.0 — 2026-07-31

## Added

- **`NvdSourceService`** (`src/services/nvd-source/`) — fetches NVD's source dictionary (`/rest/json/source/2.0`) once per process, cached behind a 24-hour TTL with single-flighted loads so a burst of tool calls shares one upstream request. ([#46](https://github.com/cyanheads/nist-nvd-mcp-server/issues/46))

## Changed

- **`weaknesses[].source` and `references[].source` resolve to NVD's published contributor name** instead of the raw source identifier — `af854a3a-2127-422b-91ae-364da2661108` now emits `CVE`, `134c704f-9b21-4f2e-91b3-4a467353bcc0` emits `CISA-ADP`. Email-form identifiers (`security@apache.org`) are left untouched, and an identifier absent from the dictionary passes through as its raw value. Resolution happens in `normalizeCve()`, so both `nvd_get_cve` and `nvd_audit_cpe` inherit it. **⚠️ Breaking:** the raw GUID is not preserved in any sibling field — a consumer string-matching the old value breaks on upgrade with no schema change to warn it. ([#46](https://github.com/cyanheads/nist-nvd-mcp-server/issues/46))
- **`nvd_get_cve` and `nvd_audit_cpe`'s `source` field descriptions** now say the value is the resolved contributor name and that an unresolvable identifier passes through as-is.

## Fixed

- **A dictionary lookup could stall a CVE fetch for the full CVE retry budget** — `NvdSourceService` loaded `source/2.0` through the same client used for CVE requests, so an unreachable dictionary spent every retry attempt and timeout the keyed CVE budget allows. `NvdHttpClient.get` gained an optional per-request `NvdRequestBudget` (`maxRetries`, `timeoutMs`), and the source service now loads on a bounded budget (`maxRetries: 0`, `timeoutMs: 3_000`) instead of inheriting the caller's.
- **A caller-cancelled dictionary load was recorded as an upstream outage** — cancelling the load (the caller's own `ctx.signal` aborting) set the same 5-minute failure TTL as a real NVD outage, degrading every subsequent caller to raw identifiers for that window. The load now checks `ctx.signal.aborted` before treating a failure as an outage and leaves the cache untouched on cancellation.
- **`nvd_get_cve` with `brief: true` resolved source names it then discarded** — brief rows carry no `source` field, so resolving one spent an upstream dictionary request (on a cold cache) for values `toBriefCve` drops. `nvd_get_cve` now passes `resolveSources: false` for brief fetches, and `nvd_search_cves` never resolves at all.

## Dependencies

- `@biomejs/biome` ^2.5.5 → ^2.5.6
