Skip to main content
Glama
winsecure-mcp

winsecure-mcp

by prinzalx

winsecure-mcp

CI Node License

A Windows CVE-inventory MCP server for agents (Hermes, Claude Code, Codex, …): it reads what software the machine has installed, matches it against the current CVE sources, and turns the findings into a persistent to-do list with an AI effort assessment — the chain the lookup-only CVE servers do not offer:

inventory → CVE hits → to-do list → AI assessment of the replacement effort

The server collects and rates, the human decides. There is no automatic patching in v1 and no writing intervention in the system: every write goes into the server's own JSON files, which is why this server deliberately has no approval gate.

Not in scope for v1: drivers, DLLs, kernel/OS patches (Windows Update), automatic updates.

Quick Start

You need Node.js ≥ 20 and npm. If you have never used Node before: install it with the installer from nodejs.org, then open a terminal (PowerShell) in the cloned repository and run the four commands below — they work from any folder.

git clone https://github.com/prinzalx/winsecure-mcp.git
cd winsecure-mcp
npm ci
npm run build
node dist/index.js

Started in a terminal, the server prints an ASCII logo and announces where it lives. Under an MCP client such as Hermes it does not own a console: stdout carries the protocol and this goes to the client's log instead.

██╗    ██╗██╗███╗   ██╗███████╗███████╗ ██████╗██╗   ██╗██████╗ ███████╗   ███╗   ███╗ ██████╗██████╗
██║    ██║██║████╗  ██║██╔════╝██╔════╝██╔════╝██║   ██║██╔══██╗██╔════╝   ████╗ ████║██╔════╝██╔══██╗
██║ █╗ ██║██║██╔██╗ ██║███████╗█████╗  ██║     ██║   ██║██████╔╝█████╗     ██╔████╔██║██║     ██████╔╝
██║███╗██║██║██║╚██╗██║╚════██║██╔══╝  ██║     ██║   ██║██╔══██╗██╔══╝     ██║╚██╔╝██║██║     ██╔═══╝
╚███╔███╔╝██║██║ ╚████║███████║███████╗╚██████╗╚██████╔╝██║  ██║███████╗   ██║ ╚═╝ ██║╚██████╗██║
 ╚══╝╚══╝ ╚═╝╚═╝  ╚═══╝╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝   ╚═╝     ╚═╝ ╚═════╝╚═╝
  version         v0.6.6
  data directory  C:\Users\<user>\AppData\Roaming\winsecure-mcp
  store file      C:\Users\<user>\AppData\Roaming\winsecure-mcp\winsecure.json
  web interface   http://0.0.0.0:8788
  MCP transport   stdio — this terminal stays attached to the agent

Open http://127.0.0.1:8788 in a browser for the web dashboard: inventory and to-do statistics, the severity breakdown, the top risks confirmed-KEV-first, an activity log of every call this server session answered, and the eight tools with their full docs. Its two buttons run scan_inventory and check_cves through the same handlers an agent calls, so the page can never disagree with a tool answer. A NVD API key section shows the current lookup mode as a badge, takes a pasted key (validated against NVD before it is saved) and clears it again; the key itself never travels back to the browser. --no-web (or WSMD_NO_WEB=1) keeps it stdio-only; --port / WSMD_PORT move it off 8788.

One process, two transports (the winadmin model)

Every start of node dist/index.js is a long-lived process that offers both transports at once, behind one listener:

Path

Purpose

stdio

the default MCP transport; a client may spawn the server as its own child process

POST /mcp

MCP over Streamable HTTP with real sessions; a client connects by url:

/, /api/*

the dashboard and its JSON endpoints

The server binds 0.0.0.0 by default so a client outside the Windows network namespace — WSL — reaches it. --host / WSMD_HOST set the bind address explicitly, --port / WSMD_PORT the port (default 8788, since winadmin-mcp owns 8787). --no-web turns the listener off entirely, so then only stdio remains. Everything else on the port — including a bare curl to /mcp — belongs to the MCP transport and answers in JSON (a bare curl gets its 406 Not Acceptable back, exactly like winadmin); the dashboard HTML lives on /.

Why HTTP exists: a client that spawns the server per stdio runs it in its own environment. On a WSL-hosted agent that means a Linux node looks for powershell.exe, winget.exe and the drive roots and finds none of them, so the inventory comes back degraded with zero programs. scan_inventory reports this honestly in its environment block (platform, node, powershell_found, winget_found) instead of claiming a clean "0 programs". Running the server on the Windows side and connecting by url: is the intended setup on such a host.

Related MCP server: cve-mcp

The Tools

Tool

Purpose

scan_inventory

Refresh the inventory (sources selectable); returns per-source counters + the dedup result

check_cves

Match the inventory against the CVE sources; fill/refresh the to-do list

list_todos

Sorted confirmed-KEV first, then confirmed findings, then severity/EPSS; filterable by status

assess_todo

Effort assessment for one entry (sampling client → verdict; else evidence package)

save_assessment

Write the assessment back (the non-sampling path)

get_program

Detail view of one finding (sources, paths, versions, assigned CVEs)

cve_detail

NVD lookup of a single CVE

reset_todos

Clear the whole list, or reset one entry to open (the way back to an empty list)

Every answer is wrapped the same way: a human summary line plus structuredContent, with english status words and ISO-8601 UTC timestamps throughout, so the output is locale-independent. An empty result is an answer, not an error.

Beyond the tools, the store file is exposed as a read-only MCP resource (winsecure://store.json), so an agent can inspect the inventory and to-do JSON without invoking a tool. Every check_cves answer names its mode (keyed NVD or the keyless fallback) and the product_cache counters, which tell "no hits found" apart from "the answer was thin"; list_todos repeats the mode of the run that produced the list.

Inventory Sources

  1. Registry Uninstall keys — HKLM + HKCU including WOW6432Node.

  2. winget list — for store/winget-managed apps; the package id is carried along because it helps matching. Note: winget 1.29 has no JSON output (--format json errors), so the table text is parsed by locating the separator line and splitting columns on two-or-more spaces.

  3. Portable apps — a bounded walk over the fixed drives for .exe files with FileVersionInfo. Excludes are mandatory and config-extensible (C:\Windows, WinSxS, node_modules, $Recycle.Bin, TEMP); the result is cut at portableMaxResults and the visited paths are reported. Access-denied directories are skipped, not errors.

Deduplication runs over all three sources under a normalised (product, version) key; each finding carries the list of sources that reported it.

CVE Data Sources

Source

Role

Notes

NVD API 2.0

primary — one query per installed product

keywordSearch on the product name, paged at 2000 per request. With an API key the limit is 50 requests / 30 s, keyless 5 / 30 s; since v0.6.3 the requests are paced to the window and an HTTP 429 pauses the whole queue (see Cold Start below). Answers are cached per product with their lastModified, so later runs transfer only what changed.

OSV.dev batch query

keyless fallback

POST /v1/querybatch answers the whole inventory in one request without a key. Covers OSS-packaged software well, proprietary Windows software poorly.

cvelistV5 (GitHub)

fallback index, loaded in every mode

The pre-v0.6.0 model: hourly delta (~5 MB) accumulated into cache/cvelist-index.json. Since v0.6.3 it is merged into every keyed and keyless answer (deduplicated by CVE id), so a keyed cold start never answers fewer hits than a keyless one.

CISA KEV JSON feed

actively-exploited signal

Fully local in both modes — only ~1 700 entries, so it is the cheap critical signal. An exact product-name match hits only ~0.5 % of a real inventory (display name vs CVE product name), so an alias table + match_confidence bridge the gap.

EPSS (FIRST)

prioritisation

Batch of ≤100 CVEs per call; fills max_epss.

How the CVE lookup works (since v0.6.0)

Earlier releases were corpus-centric: download the cvelistV5 deltas, accumulate them into one local index, and grep that index for the installed products. On a fresh install that index starts at under 1 % coverage and takes days to grow, so "no vulnerability found" and "the CVE is not in the index yet" were indistinguishable — and the server stored a 602 MB library to answer questions about ~350 programs.

Now the inventory drives the lookup. For each product name the inventory reports (deduplicated on the normalised name, installer artifacts excluded), check_cves:

  1. queries NVD for the CVE records that mention the product, paged, and filters them locally to the product names that entry is known under;

  2. stores the answer in cache/products.json under the product's newest lastModified;

  3. on the next run asks only for changes since that timestamp (lastModStartDate / lastModEndDate, inside NVD's 120-day window);

  4. applies the v0.5.x version verdict against the fetched affected ranges, so dropped_by_verdict rejects a already-patched version from complete data on day one;

  5. merges the cvelistV5 delta index in (since v0.6.3, deduplicated by CVE id), so ids the NVD keyword search missed still produce hits — marked with their fallback source.

Whether step 1 runs keyed or the whole thing falls back to a single OSV batch depends on the NVD API key: NVD_API_KEY in the environment wins, otherwise the key saved in the dashboard is used, then a single-line nvd_api_key file in the data directory (for hosts where the dashboard is not reachable), otherwise the server runs keyless and says so — on the console (no valid NVD API key — check dashboard) and in every answer (mode: "nvd-keyed" | "keyless-osv+cached"). docs/nvd-key-vs-keyless.md compares the two in one table: cost, speed, completeness, update semantics.

Everything is cached on disk with a TTL, and a scan run never depends on the network. Without network the server still returns the inventory with cve_status: "unknown". Privacy: only product, version and (when present) package id leave the machine — never paths, usernames or hostnames.

Cold Start / first run (since v0.6.3)

The first run needs time, especially keyed: 425 products at NVD's 50-requests-per-30-window are minutes of work, not seconds. check_cves therefore does not block until the last product — it answers after a time budget (nvdCallBudgetMs, default 20 s, well under a typical MCP client transport timeout), products with a KEV hit asked first, and the rest stays in a persistent queue the server works on its own: an idle-drain timer (nvdIdleDrainMs) keeps fetching between calls, so progress no longer depends on how often a client happens to call.

Two rules make the queue finish instead of circling (both since v0.6.5):

  • Position is stored per product. Every product carries its queue state (pending, done, not_found, failed_transient) with attempt count and time. A product that fails moves to the back of the queue, so the next call starts at the products that have never been asked — not again at 7-zip, git, edge, whatsapp. product_cache.deferred is the queue's open remainder and falls towards 0 across runs.

  • A 404 is classified by what NVD actually says, not by its status code alone. Since v0.6.6 the reason of a rejection lives in the message response header (the body is empty), and the four shapes are treated apart:

    what NVD answers

    state

    what the server does

    200 with records

    done

    cached, product closed

    404 + body is an NVD envelope

    not_found

    real "zero CVEs" answer for nvdNotFoundTtlHours (default 7 days), counted apart from failures, never an enrichment

    404 + message: Invalid apiKey. (or 401)

    key_rejected

    the key is the problem: the drain pauses and further requests go out keyless at the keyless limit

    404 + message: Invalid parameter: …

    request_invalid

    a request-building problem: logged per product, retried only nvdRequestInvalidMaxAttempts times, then closed out — not rotated forever

    404 with no message and no envelope

    transient

    Cloudflare/unknown shape, retried at the queue's back

    429 / 5xx

    transient

    whole queue pauses with Retry-After and exponential backoff

  • The requests carry the key as the HTTP header apiKey, never as a query parameter. NVD API 2.0 answers a query-parameter key with 404 and message: Invalid parameter: apiKey., which is what left the live beta's product cache at 0 while the mode still read nvd-keyed.

  • A circuit breaker stops an endless rotation: after nvdBreakerThreshold consecutive lookups without a single 200 the drain pauses for nvdBreakerPauseMs and the answer says so, instead of working through the queue at full speed and reporting nothing but failures.

The answer says how far it got:

"completeness": {
  "status": "incomplete",
  "warming_up": true,
  "reasons": ["425 of 425 product lookups unanswered", "nvd_key_rejected"],
  "lookups": { "total": 425, "cached": 13, "deferred": 412, "failed_429": 0, "failed_404": 4, "failed_other": 3, "not_found": 6, "done": 13, "failed_transient": 7, "failed_request_invalid": 0, "attempts_total": 20 },
  "hint": "NVD API key rejected — check the key in the config (the dashboard shows the reason). Lookups continue keyless at the keyless rate; a low hit count is the data source, not a patched machine.",
  "needs_attention": true
}

Since v0.6.6 reasons also carries three machine-readable tokens, so a client can tell a slow warm-up from a configuration problem it cannot wait out: nvd_key_rejected (the key is wrong — act), nvd_requests_invalid (the request shape is wrong — a bug, act), and nvd_all_lookups_failing (the circuit breaker is open — every recent lookup failed). When any of them is present the answer also carries needs_attention: true, and hint names the action instead of only describing the state.

Few hits right after installation mean the data source was not fully reached yet, not "patched". Check completeness.status (and the additive checked_data field, which sits next to the untouched cve_status) before concluding; while warming_up is true the server is still fetching on its own, so simply looking again after a few minutes is enough — unless needs_attention is set, which means waiting will not help and the key or the config needs a human. Progress is visible in product_cache (products_cached climbing, deferred falling towards 0) and on the dashboard's activity log, which since v0.6.3 names the product behind every per-product HTTP 429/404 with its timestamp (lines marked (idle drain) come from the background timer rather than a call).

The dashboard key badge distinguishes presence from validity (v0.6.6). Green means NVD accepted the key, yellow a key that is saved but not yet proven, red one NVD refused. /api/state reports key_present and key_valid apart for the same reason: a saved key is not a working key.

The Assessment Mechanism (§5)

The server owns no LLM. At the handshake it checks whether the client declared the MCP sampling capability:

  • Yes: assess_todo asks the client directly via sampling and stores the verdict itself.

  • No (fallback, always available): assess_todo returns a structured evidence package plus the fixed schema; the client assesses and writes the result back through save_assessment.

Both paths produce byte-for-byte the same stored shape, on exactly these axes:

Axis

Meaning

(a) exe_swap_only

the fix is reached by replacing the .exe/file alone

(b) new_setup_needed

a new setup package from the vendor is required

(c) breaking_change_expected

a breaking change is expected

(d) affected_part

only a part program of a suite is affected → name it

Configuration

Defaults work out of the box; the server writes them out on first save.

Setting

Meaning

Default

dataDir

store, caches and config

%APPDATA%\winsecure-mcp

kevTtlHours / cvelistTtlHours / epssTtlHours

disk-cache TTLs

24

nvdApiKey

NVD key saved from the dashboard (empty = keyless)

null

nvdProductsPerRun

how many product answers one check_cves may refresh at NVD

400

nvdMaxPages

page cap per product query, so Chrome cannot turn into 60 requests

5

nvdRatePerWindow / nvdWindowMs

keyed NVD pacing: requests per window (NVD's documented keyed limit)

50 / 30000

nvdKeylessRatePerWindow

pacing once the key is absent or NVD rejected it (NVD's documented keyless limit, v0.6.6)

5

nvdBreakerThreshold

consecutive lookups without one 200 that trip the circuit breaker (v0.6.6)

20

nvdBreakerPauseMs

how long the drain rests once the breaker is open, or NVD refused the key (v0.6.6)

900000

nvdRequestInvalidMaxAttempts

retries of a product NVD called an invalid request, before it is closed out (v0.6.6)

3

nvdConcurrency

NVD requests in flight at the same time

1

nvdJitterPct

randomised extra spacing per NVD request, in percent of the window interval — keeps a queue from leaving its pause as a salvo (v0.6.5)

20

nvdBackoffCapMs

upper bound of the exponential 429 backoff

60000

nvdIdleDrainMs

period of the server-side idle drain that works the NVD queue between calls (v0.6.5)

30000

nvdIdleDrainBatch

open products one idle-drain tick processes at most (v0.6.5)

5

nvdCallBudgetMs

time budget of the per-product walk inside one check_cves call; the rest stays queued for the idle drain

20000

nvdNotFoundTtlHours

how long a zero-match (not_found) NVD answer is trusted

168

portableExcludes

paths never walked

see above

portableMaxResults

cut-off for the portable walk

40

portableTimeoutMs

portable walk budget

60000

httpTimeoutMs

per outbound request

15000

Environment overrides: WSMD_CONFIG (config file), WSMD_DATA_DIR (data directory), WSMD_OFFLINE=1 (switch the network off — the offline test), WSMD_HOST / WSMD_PORT (the listener's bind address and port), WSMD_NO_WEB (the dashboard), NVD_API_KEY (overrides the key saved in the config), plus an nvd_api_key file in the data directory as the last fallback. CLI --host / --config / --data-dir / --port / --no-web win.

Development

npm run typecheck   # tsc --noEmit
npm run build       # → dist/ (+ webui.html)
npm test            # build + node --test tests/*.test.mjs
node scripts/e2e.mjs --assess   # full real stdio run on this host
node scripts/sampling-test.mjs  # proves both assessment paths
node scripts/http-test.mjs      # drives a real MCP session on /mcp over HTTP
node scripts/banner.mjs  # regenerate the console logo

End-to-end run on a Windows 11 test host (real numbers)

scan_inventory → 357 programs after dedup (registry 205, winget 209, portable 40 cut at max)
check_cves     → mode nvd-keyed, product cache answers the whole inventory in one run
list_todos     → the v0.5.0 verdict rules turned this run's 6 false positives into 1 unconfirmed row
assess_todo    → evidence package returned (client cannot sample); saved via save_assessment
cve_detail     → CVE-2024-21412: CVSS 8.1 — Internet Shortcut Files Security Feature Bypass

The live beta (v0.4.0) produced six to-dos on this host and all six were unconfirmed name matches, none an actual exposure: two Edge versions matched the same two 2016 EdgeHTML CVEs as two separate rows, WhatsApp 2.2639 a 2019 CVE, Git 2.55 a CVE affecting Git < 2.50.1, 7-Zip 26.02 a CVE affecting < 24.07, and Notepad++ 7.54 came from two installer .exe files in document folders. After the v0.5.0 verdict rules the verification run on the same host reports 8 name match(es) rejected by the version verdict and one remaining to-do. Edge, both WhatsApp CVEs, Git and Notepad++ are gone. The survivor is 7-Zip 26.02 vs CVE-2025-0411: the affected range (< 24.07) is not in the thin 0.7 % index yet, and KEV membership exempts the pair from the era heuristic, so a known-patched program stays as one low-confidence unconfirmed row rather than a claim of exposure. That is the intended leniency for KEV entries, not a hit ranked above a confirmed one.

Offline test (WSMD_OFFLINE=1, cold cache): inventory 357 programs, cve_status: "unknown", the failure reason reported once per distinct message in source_errors, no failures. The same run keyless (no NVD_API_KEY, no saved key) reports mode: "keyless-osv+cached" and carries the keyless hint in source_errors, so a thin answer is never read as a clean machine.

A second probe ran the server in a Linux (WSL) sandbox where neither powershell.exe nor winget.exe is reachable. That is what shaped the source-health rule: a scan on a host it cannot see now reports degraded: true, names every source that contributed nothing (missing interpreter, no fixed drive) and adds the environment block, instead of claiming a clean "0 programs".

Matching Honesty (v0.3.0, verdict rules extended in v0.5.0)

Rules that keep the hit list trustworthy, all of them visible in the answer rather than buried:

  • A KEV product-name match is version-checked. The KEV feed carries no version range, so the hit is verified against the local CVE record: outside the affected range it is dropped (Chrome 120 hit CVE-2020-16017, which NVD limits to Chrome prior to 86.0.4240.198), and when no range can be evaluated the hit is kept unconfirmed — match_confidence: "low", listed in unconfirmed_cves, and it does not lift max_severity. check_cves counts them separately.

  • A name-only match must also be plausible for its era (v0.5.0). The delta index only ever accumulates recent CVEs, so an old CVE (a 2016 EdgeHTML bug, a 2019 WhatsApp bug) may never get local range data at all. When a KEV name match has no range that confirmed it and the installed version's era sits clearly above the CVE's publication year, the pair is impossible on its face and drops before it can become a to-do. Everything the verdict rejected counts up as dropped_by_verdict, so the filtering is visible instead of silent.

  • Several installed versions of one product are ONE to-do (v0.5.0). Edge 152 and Edge 155 are the same finding: the to-do represents the newest version and lists the members in versions, so the list has no near-duplicate rows per version.

  • An installer archive is not installed software (v0.5.0). A *installer*.exe / *setup*.exe the portable walk found outside Program Files is tagged installer_artifact (a downloaded setup in a document folder); if no other source reported the program, check_cves skips it (installer_artifacts_skipped).

  • A KEV hit is never rendered severity: "none" as if that were a fact. Whatever the local index does not score is completed from the disk-cached NVD helper before the to-do is built (nvd_enriched in the answer), and one NVD answer is applied to every hit object of that CVE id (v0.5.0 — the live beta saw the same CVE scored on one to-do and null on another). A hit that stays unscored carries cvss_source: "nvd_unreachable" / "nvd_no_score" so hit level and to-do level cannot disagree.

  • Ranking never puts an unverified hit above a confirmed one (v0.5.0). list_todos and the dashboard sort verified-KEV first, then any confirmed finding, then severity/CVSS/EPSS — an entry whose only KEV signal is an unconfirmed name match cannot headline the list.

Registration in Hermes

Run the server on the Windows host and connect by url, the same way winadmin-mcp is connected:

  winsecure:
    url: http://172.28.176.1:8788/mcp
    enabled: true

172.28.176.1 is the Windows side of the vEthernet (WSL) adapter, i.e. the address a WSL Hermes reaches. Open the port for the WSL subnet only — not for every interface:

netsh advfirewall firewall add rule name="winsecure-mcp" dir=in action=allow protocol=TCP localport=8788 remoteip=172.28.160.0/20

The old stdio entry (command: node, args: [.../dist/index.js]) is what caused the empty inventory, because it started the server inside WSL; replace it with the url: form above.

Register with manage_connections install, then — after any server restart — manage_connections enable. Note: Hermes is not installed on this host, so the registration and the live sampling path could not be exercised here; the tools were all proven callable over real stdio MCP (scripts/e2e.mjs lists and calls all 8) and over HTTP (scripts/http-test.mjs), and the sampling path is proven with a mock-capable client (scripts/sampling-test.mjs). Hermes uses the tool-name prefix mcp__winsecure__; match on the name behind the prefix.

Project Structure

src/
  index.ts            MCP server, stdio + Streamable HTTP transports, sampling handshake, dashboard
  dashboard.ts        dashboard state + the in-memory activity ring buffer
  webui.html          the dashboard page (copied to dist/ by the build)
  config.ts           defaults + config file + CLI/env overrides
  model.ts            domain types (english, locale-independent, ISO-8601 UTC)
  store.ts            single JSON file for inventory + to-do list (atomic rename)
  utils/              result wrapper, powershell, net (TTL disk cache), matching, zip
  modules/inventory/  sources (registry/winget/portable) + dedupe
  modules/cves/       feeds (KEV/cvelistV5/EPSS/NVD) + products.ts (per-product lookup) + matching engine
  modules/todos/      todo model (schema, evidence, merge) + tools
skills/windows-cve-triage/SKILL.md

One listener serves the dashboard, /api/* and the /mcp endpoint, and its run endpoint goes through the same tool handlers, so the "only writes to its own files" rule holds on the web path too. --no-web closes the listener completely; stdio keeps running either way.

Available Tools

8 tools
assess_todoAssess to-doA

Effort assessment for one to-do on axes a-d. With a sampling-capable client the server asks it directly and stores the verdict; otherwise it returns an evidence package for save_assessment.

NameRequiredDescriptionDefault
todo_idYesThe id from list_todos

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it does disclose non-obvious dual-mode behavior plus the fact that a verdict is stored (a mutation). It omits what the evidence package contains, what happens to a previously stored verdict, and what permissions or preconditions the write requires.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tightly written sentences, with the core function front-loaded and the branching behavior second. No filler, but the undefined 'axes a-d' shorthand costs a little clarity within that compact frame.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, no annotations, and the tool performs a write (stores the verdict), so the description should carry more: what the evidence package looks like, how the assessment is consumed downstream, and what 'axes a-d' means. It covers the branching model well but leaves the payload and result semantics undefined.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single `todo_id` parameter is documented as 'The id from list_todos', so the schema does the work. The description adds no format, provenance, or validity detail beyond what the schema already states; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb+resource ('Effort assessment for one to-do') and hints at sibling differentiation by naming `save_assessment`. However, 'axes a-d' is unexplained jargon that an agent cannot act on without outside knowledge, so it is clear but not fully self-contained.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains the two operating contexts: sampling-capable clients get the verdict stored directly, others receive an evidence package destined for `save_assessment`. That routing is genuinely useful, though it never states when an agent should avoid this tool or whether re-assessment is expected.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

check_cvesCheck CVEsA

Match the stored inventory against the CVE sources (per-product NVD lookup, CISA KEV, EPSS) and fill the ToDo list. Answers come from the per-product cache when it is fresh; the network is only touched for what changed. A cold keyed start is paced by NVD's rate limit: the call answers within a time budget and the rest of the queue drains in the background — read completeness before concluding. Never requires the network, but a cold keyed start does need time: see completeness.status "incomplete".

NameRequiredDescriptionDefault
refreshNoForce re-download of the sources even when the cache is fresh
with_epssNoLook up EPSS scores for the matched CVEs. Defaults to true

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it discloses per-product cache reuse, that the network is touched only for changes, NVD rate-limit pacing on a cold keyed start, background queue draining, and that the `completeness` field must be checked. The only blemish is the internally tense claim 'Never requires the network' immediately followed by network-touching behavior, which muddies an otherwise strong disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The primary action is front-loaded and the sentences are information-dense. There is mild redundancy: the 'read completeness before concluding' caveat is repeated as 'see completeness.status "incomplete"', and the closing sentence partly restates the earlier rate-limit point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and no output schema, the description is unusually complete: it names an output field (`completeness`) with a status value and warns about cold-start latency. It still leaves the ToDo list structure and overall return shape unspecified, which keeps it short of a 5.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters (refresh, with_epss) are documented in the schema, so the baseline is 3. The description hints at cache freshness and source re-download behavior but does not add syntax or mapping beyond what the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Match the stored inventory against the CVE sources') plus the effect ('fill the ToDo list'), so the agent knows exactly what it does. It implicitly separates itself from cve_detail (bulk per-product matching vs. single lookup), but it never names a sibling explicitly, so it falls short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: run this to populate the ToDo list from stored inventory. There is no explicit when-to-use versus cve_detail/scan_inventory and no stated prerequisites, so guidance is adequate but not directive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cve_detailCVE detailA

Look up a single CVE at NVD API 2.0: description, CVSS, references. Cached for a day.

NameRequiredDescriptionDefault
cve_idYesA CVE id, e.g. CVE-2024-21413

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are supplied, so the description carries the full behavioral burden. It does disclose the useful trait that results are 'cached for a day,' but it is silent on NVD rate limits, behavior for unknown/invalid CVE ids, and whether auth or a key is required.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single sentence that front-loads the verb, resource, source, and return fields, then appends the caching caveat. Nothing is wasted.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter lookup with no output schema, the description tells the agent what data comes back (description, CVSS, references) and notes caching. Only edge-case behavior (invalid ids, rate limiting, auth) is left uncovered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With schema description coverage at 100% and only one parameter, the schema already documents cve_id, its pattern, and an example. The description adds no format or semantics beyond that, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

It gives a specific verb (look up), a scoped resource (a single CVE), the upstream source (NVD API 2.0), and the returned fields (description, CVSS, references). The word 'single' implicitly separates it from the bulk-checking sibling check_cves, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: 'a single CVE' suggests this is for one-off lookups rather than batch checks like check_cves, but there is no explicit when-to-use/when-not-to-use statement or prerequisite detail.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_programGet programC

Detail view of one inventory entry: every source that reported it, its paths, versions and the CVEs assigned to it.

NameRequiredDescriptionDefault
limitNoMaximum matches to return. Defaults to 10
programYesName substring or the exact inventory id
with_cvesNoInclude the CVE hits of each match. Defaults to true

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It describes the returned fields but omits whether the call is read-only, what happens when nothing matches, and how the 'one inventory entry' framing squares with the schema's multi-match 'limit' parameter (default 10, max 50) and the with_cves toggle — a real ambiguity for a lookup tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence that names the subject first and then lists the returned content. It is efficient and contains no filler, though it spends its words on return values rather than the invocation guidance an agent also needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema and no annotations, so the description is the only source of behavioral context, and it only partially covers this. The return composition is sketched, but match/multiple-match behavior, defaults, and failure modes are left to the schema or entirely unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so limit, program, and with_cves are already fully documented in the schema with defaults and constraints. The description adds no parameter-level detail beyond implying CVE inclusion, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Detail view of one inventory entry') and enumerates the payload (sources, paths, versions, CVEs), which is far more informative than the bare title 'Get program'. It does not name or differentiate itself from siblings like scan_inventory or cve_detail, though, so an agent must infer the boundary itself.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit when-to-use guidance or mention of alternatives such as scan_inventory or cve_detail. The phrase 'Detail view' weakly implies this is the drill-down counterpart to a scan, but nothing states when to prefer this tool or what input form (name substring vs exact id) is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_todosList to-dosA

The CVE to-do list, sorted confirmed-KEV first, then confirmed findings, then severity/EPSS. Filterable by status.

NameRequiredDescriptionDefault
limitNoMaximum entries. Defaults to 40
statusNoFilter by status. Defaults to all non-dismissed
kev_onlyNoOnly entries with at least one actively exploited CVE

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are supplied, so the description carries the behavioral burden. It does disclose the non-obvious sort order (confirmed-KEV, then confirmed findings, then severity/EPSS), which is real added value, but says nothing about permissions, pagination, or the shape of the returned entries.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with no filler; the priority ordering — the most decision-relevant fact — leads the description, and filtering is appended compactly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no annotations and no output schema, the description should carry more. It explains ordering and filtering but omits what a returned to-do actually contains (CVE id, status, KEV flag) and how the KEV-first sort interacts with the kev_only filter.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and each parameter is fully documented (limit default 40, status enum with a default, kev_only boolean). The description's 'filterable by status' merely restates one schema field and adds no syntax or semantics beyond it, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the resource ('The CVE to-do list') and its ordering, which combined with the verb in the tool name makes the operation unambiguous. It distinguishes itself from siblings like assess_todo or reset_todos by being the enumeration tool, though it never explicitly contrasts them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Filterable by status' plus the stated priority ordering implies this is the triage/entry-point read for a vulnerability queue, but the description gives no explicit when-to-use, when-not-to-use, or pointer to an alternative such as scan_inventory or check_cves.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

reset_todosReset to-dosA

Clear the to-do list back to empty, or reset one entry to open. The way back to a clean list without editing the JSON file.

NameRequiredDescriptionDefault
todo_idNoReset a single to-do to open. Omit (or use "*") to clear the entire list

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It does disclose the destructive outcome ('back to empty', entry 'reset to open'), which is genuinely useful context, but it omits reversibility, whether confirmation is needed, and permissions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the primary action, followed by a short rationale. The trailing 'without editing the JSON file' clause is mildly redundant but does justify the tool's existence.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A simple one-optional-parameter tool with no output schema; the description covers both call modes adequately. A brief note on whether the reset is recoverable would make it fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the todo_id semantics (omit or "*" for full clear) are already fully documented in the schema. The description restates the two modes but adds no syntax or format detail beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('clear', 'reset') and resource ('to-do list'), and distinguishes its two operating modes: wiping the whole list vs. resetting one entry to open. It does not explicitly differentiate itself from siblings like list_todos or save_assessment, but the intent is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear context for use: getting back to a clean list without hand-editing the JSON file. There is no explicit when-not-to-use clause and no named alternative sibling, which keeps it short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

save_assessmentSave assessmentA

Write an effort assessment back to a to-do (the non-sampling path). Sets the status to assessed.

NameRequiredDescriptionDefault
reasonYesShort justification
dismissNoSet the status to dismissed instead of assessed
todo_idYesThe id from list_todos
confidenceNoHow sure the assessment is. Defaults to medium
affected_partNo(d) name the part program when only part of a suite is affected; null when the whole program is
exe_swap_onlyYes(a) only the .exe/file has to be replaced
new_setup_neededYes(b) a new setup from the vendor is needed
breaking_change_expectedYes(c) a breaking change is expected

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it does disclose the core side effect: status is set to 'assessed' (with 'dismissed' available via schema). It does not say whether this overwrites an existing assessment, requires prior assessment, is idempotent, or what errors can occur.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero filler, with the mutation and its resulting state change front-loaded. Nothing is wasted and the key effect is stated first.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an 8-parameter mutation tool with no annotations and no output schema, the description covers the write and status outcome but omits prerequisites (must the to-do be previously assessed?), return/error behavior, and what 'non-sampling path' actually governs. The rich schema compensates on parameters, but the behavioral envelope is thin.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all 8 parameters including the (a)/(b)/(c)/(d) tokens, confidence default, and dismiss flag are documented in the schema itself. The description adds no syntax or meaning beyond that, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: writing an effort assessment back to a to-do, plus the resulting status change. The parenthetical '(the non-sampling path)' hints at a distinct route versus a sampling sibling, though it does not name that sibling explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'The non-sampling path' implies an alternative exists, but the description never names it or states when to pick this tool over it. Usage is implied (writing back a non-sampled assessment) rather than explicitly bounded.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scan_inventoryScan inventoryA

Refresh the installed-software inventory from the registry, winget list and a portable .exe scan. Returns per-source counters and the deduplicated result.

NameRequiredDescriptionDefault
rootsNoDrive roots for the portable scan. Defaults to the fixed disks
sourcesNoWhich sources to read. Defaults to all three

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. It discloses the mechanics (three sources read) and the return shape (per-source counters plus deduplicated result), but says nothing about whether the stored inventory is overwritten, how long the scan takes, whether winget/portable scanning needs network or elevated permissions, or what happens on partial failure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences, front-loaded with the action and scope, followed by the return summary. Every clause earns its place; no filler or restatement of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description does the right thing by summarizing return values, and it covers both optional parameters' domains. The remaining gap is side-effect/permission context for a refresh operation that mutates stored inventory state, which matters more given the absence of annotations.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented in the schema and the baseline is 3. The description usefully connects the 'portable .exe scan' wording to the `roots` parameter and enumerates the three source names matching the `sources` enum, but adds no default or format detail beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb+resource: 'Refresh the installed-software inventory', with the three data sources named concretely (registry, winget list, portable .exe scan). That is far clearer than a bare 'scan', though it never names a sibling to distinguish itself from get_program or check_cves, which also touch installed software.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Refresh' implies the use case (re-sync a stale inventory), but there is no explicit when-to-use statement, no exclusion of alternatives, and no note on how this differs from get_program (single lookup) or check_cves. Usage is only inferable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 1 tool updatev0.6.6
    • Changedsave_assessment2 fields changed
      • changedInput schema / properties / affected_part / description
        Previous value: -"(d) name the part program when only part of a suite is affected"New value: +"(d) name the part program when only part of a suite is affected; null when the whole program is"
      • changedInput schema / properties / affected_part / type
        Previous value: -"string"New value: +[
        +  "string",
        +  "null"
        +]
  2. 1 tool updatev0.5.0
    • Addedreset_todos
  3. 7 tool updatesv0.1.0
    • First observedassess_todo
    • First observedcheck_cves
    • First observedcve_detail
    • First observedget_program
    • First observedlist_todos
    • First observedsave_assessment
    • First observedscan_inventory

TDQS

A3.6/5.0

Scored across 8 tools

Disambiguation4/5

Each tool targets a distinct action: inventory scanning, CVE matching, CVE lookup, to-do listing/resetting, assessment, and program detail. The only mild overlap is between assess_todo and save_assessment (assessment generation vs. storage) and between check_cves and cve_detail, but descriptions clearly delineate them.

Naming Consistency4/5

All names use snake_case and mostly follow verb_noun (check_cves, save_assessment, assess_todo, get_program, list_todos, reset_todos, scan_inventory). The lone deviation is cve_detail, which is noun-based but still consistent with snake_case.

Tool Count5/5

8 tools is well-scoped for a vulnerability management server covering inventory, CVE matching, to-do workflow, and assessment. No tool appears redundant or out of scope.

Completeness4/5

Core lifecycle is covered: scan inventory, match CVEs, list/reset to-dos, assess, save, and view program/CVE detail. Minor gaps include no explicit tool to add or edit inventory entries manually and no dedicated inventory list endpoint (though scan_inventory returns results), but these are workable.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables CVE lookups and risk assessment by integrating CISA Known Exploited Vulnerabilities (KEV) data and CVSS metrics. It helps users prioritize patching efforts by ranking vulnerabilities based on exploitation status and calculated risk scores.
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Unifies NVD, EPSS, CISA KEV, GitHub Advisory, and OSV into a single MCP server, enabling AI agents to query vulnerability intelligence conversationally with 23 tools for incident response, prioritization, dependency audits, and threat monitoring.
    41
    292 npm
    30
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to search, correlate, and monitor real-time vulnerability intelligence from NVD, CISA KEV, EPSS, and MITRE ATT&CK, including CVE details, critical CVE tracking, known exploited vulnerabilities, and exploitation probability scores.
    5
    2
    MIT