← Back to list

The Navi MCP — 11 Skills driving accurate Exposure Management automation.

How navi-mcp Stops Claude From Hallucinating Your Vulnerability Data

Casey Reid · 2026-05-14 01:53 · 0 claps · 8.8 min read paywalled
#mcp-server #navi #navimcp #naviskills #mcp-protocol
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents BIZ · Business Strategy 🔒 · Cybersecurity

The Navi MCP — 11 Skills driving accurate Exposure Management automation.

How navi-mcp Stops Claude From Hallucinating Your Vulnerability Data

This is a companion to “MCP Isn’t Dead. You’re Just Deploying It Wrong.” That article makes the case that most MCP failures aren’t protocol failures — they’re developers shipping tools with no skills, no scoping, no discipline. This one shows what doing it right actually looks like, using a real MCP server I built.

If you haven’t read the “MCP isn’t dead” piece, the short version is: an LLM that doesn’t have a grounded place to look will fabricate, fluently and confidently, because it has no “I don’t know” output token. The protocol gives the model the ability to go look. Skills give it the discipline to actually look, and the structure to know where. Strip either one out and you get the experience the “MCP is dead” crowd is describing.

So here’s the proof point. navi-mcp is the MCP layer on top of navi — a CLI I wrote that wraps the Tenable Vulnerability Management API and pulls platform data into a local SQLite database (navi.db). The MCP server exposes navi's surface to Claude as a set of tools — navi_explore_query, navi_enrich_tag, navi_export, navi_scan, and so on.

The tools alone are not what makes it work. What makes it work is the skills layered on top — written instructions that tell Claude what each tool is for, what order to use them in, when to look before acting, and what never to do without confirmation. Six mechanisms come out of that combination. Let me walk through each one.

1. Read-first as a hard rule

Under navi-mcp, Claude is told explicitly: before tagging anything, before adjusting any ACR score, before proposing any write, look at the underlying data first. Schemas. Counts. Sample rows. Scope.

Sounds procedural. It’s actually the single biggest defense against hallucination, because it kills the moment where the model is most likely to fabricate — the gap between “user asked” and “I’d better produce something.”

A grounded interaction:

  1. You: “How many production assets have a critical vulnerability?”
  2. Claude reads the schema of the relevant tables.
  3. Claude runs a real query against navi.db.
  4. The query returns 47 rows. Claude tells you 47.

Now the ungrounded version. Same question, but the model is wired up to raw APIs with no skill telling it how to use them:

  1. You: “How many production assets have a critical vulnerability?”
  2. Model picks an asset-listing endpoint, sees it returns pages of 100, realizes the tenant has 40,000 assets, and now has to make a choice it was never designed to make — iterate through 400 pages of JSON to count accurately, or give a mathematical estimate based on a few pages.
  3. Model takes the estimate path based on a handful of pages using statistics to estimate the rest. The number comes back as 312. The real answer is 47.

Or, worse: the model skips the API call entirely because it correctly intuits that iterating ten thousand rows is not what it does well, and instead says “Based on typical environments of your size, you likely have around 150 to 200.” The number is invented.

The skill is what makes both patterns impossible. It tells Claude, in plain English, that it doesn’t get to estimate, doesn’t get to guess at filter semantics, and doesn’t get to trust an API count without checking it against the schema. It runs a real query through a tool that was built for the data shape — or it tells you it can’t answer.

2. Schema resources kill column-name fabrication

The most common SQL hallucination I see is the invented column name. Models trained on a lot of SQL will confidently spit out last_scanned_at when the actual column is last_licensed_scan_date. The query looks right. It fails when you actually run it.

navi-mcp exposes navi://schema/{table} as a read-only resource — a cheap call that returns real column names for the actual table. The navi skill tells Claude to read it before composing a query when there's any ambiguity. The column names that go into the SQL are the column names that exist in navi.db. Plausibility becomes verifiability with zero extra effort from you.

This is a small thing that matters disproportionately, because schema fabrication is exactly the kind of error that sounds right. “Filter on severity_level" reads as plausible English even when the column is just severity. The schema resource turns a guess into a lookup.

3. Write operations require explicit confirmation

Reading the wrong thing wastes time. Writing the wrong thing changes your environment.

Several navi-mcp tools are write-gated — they refuse to execute unless the calling assistant passes confirm=True and the server itself was launched with NAVI_MCP_ALLOW_WRITES=1. Two keys, two locks. Tags, ACR adjustments, asset additions, scan creation, deletion, key rotation — all gated.

For each of those, the navi-mcp skill tells Claude to do four things in order: describe the operation in prose, state the exact tool call with its arguments, wait for you to confirm in chat, and only then invoke with confirm=True. There's no "batch these writes for me" shortcut. Each write gets its own confirmation. Errors get caught at confirmation, not in a platform audit three weeks later.

Here’s what a write looks like in practice. You ask Claude to tag production servers with ACR 10. Before anything fires, Claude says something like:

I’ll tag the assets in the Production Servers group with Environment:Production and set ACR to 10. That's about 1,200 assets based on the current group membership. The tag will propagate to Tenable in about 30 minutes.

Tool call: navi_enrich_tag(category="Environment", value="Production", group="Production Servers", confirm=True)

Confirm and I’ll apply it.

You see the scope, you see the exact call, you see the side effects. If something’s wrong — say the group has more assets than you expected, or the category name doesn’t match what’s in your platform — you catch it before it ships. That’s the entire point. Confirmation isn’t a politeness. It’s an audit checkpoint.

4. Freshness checks prevent stale-data confidence

Here’s a sneaky failure pattern: Claude answers your question correctly against navi.db, but navi.db hasn’t been synced in six weeks. The query was right. The data was wrong. Nothing in the response surfaced the gap.

The navi-mcp skill handles this by telling Claude to run a cheap freshness check at the start of any data-dependent workflow — basically “what’s the newest last_found timestamp in the vulns table?" If it's older than thirty days, you get a firm recommendation to refresh before proceeding. Seven to thirty days, a one-sentence heads-up. Newer than that, silent pass-through.

“We have no Log4j exposure” lands very differently when it’s followed by “(as of the last sync, 41 days ago).” The skill is what makes that parenthetical appear automatically, without you having to remember to ask.

The freshness check is also what catches the apparent-first-run problem. New users hit navi-mcp before running navi config update full, ask a question, and the model would otherwise return zero results with no context. With the check in place, Claude can say "navi.db looks empty — you'll want to run navi config update full at your terminal before we can answer this" instead of confidently reporting that you have no critical vulnerabilities.

5. Some commands are deliberately not exposed

The list of navi commands I intentionally didn’t expose through MCP is itself a hallucination-prevention design.

navi config update full — the foundational sync — stays as a CLI command. It can take hours on a large tenant and pull hundreds of gigabytes on the first run. Keeping it CLI-only means Claude can't silently re-sync your database mid-workflow. You run it, you see what's happening, you own the data state.

navi action push — remote command execution against Linux hosts — also stays CLI-only. A model that could push commands to remediation targets without explicit human invocation would be a security incident waiting to happen. The skill is explicit: there is no MCP path to push. Period.

navi action mail, navi keys, navi action automate, navi enrich tagrule, and others sit in the same not-exposed list. Each one excluded for a specific reason — too heavy, too hazardous, wrong shape for tool calls, or replaced by smaller primitives Claude can compose more auditably.

The principle: where automation through an LLM increases risk without proportionate benefit, the command stays on the human side of the line. The model doesn’t get to compose its way around the gap.

6. The skill itself is the orchestration layer

This is the piece most “MCP is dead” deployments are missing entirely.

The navi MCP doesn’t just expose tools. It ships with a layered skill set — a router skill (navi) that points at domain skills (navi-core, navi-mcp, navi-explore, navi-enrich, navi-acr, navi-export, navi-scan, navi-action, navi-was, navi-troubleshooting). Each one loads only when relevant. Each one tells Claude what the domain is, what the gotchas are, what to look up first, and what the canonical phrasings of common requests are.

So when you ask “tag every asset with a cert expiring in April,” the router skill points Claude at navi-enrich, which tells it to use the remove=True pattern to keep the tag UUID stable, which tells it the cert plugin IDs to query, which references the schema for the certs table. That's a chain of four lookups before any write happens — and the model didn't have to figure any of it out on its own. The skill wrote that flow down once. Claude follows it every time.

Compare that to the default MCP deployment: a folder full of tool definitions, no guidance, no priorities, no read-first convention, no description of what “good” looks like. Then we’re surprised when the model burns context on irrelevant tools and hallucinates field names.

Skills aren’t a nice-to-have wrapper around MCP. They’re what makes MCP usable at production scale.

What this looks like end to end

Put it all together and a practitioner asking Claude “which assets have CVE-2024–3094, and tag them for remediation” through navi-mcp gets this sequence — most of it invisible, all of it grounded:

  1. Claude reads the freshness check. Data is current, no warning needed.
  2. Claude reads the schema for the vulns table to confirm column names.
  3. Claude runs a real navi_explore_query with actual SQL. Real rows come back.
  4. Claude describes the tag operation in prose, states the exact navi_enrich_tag call, and waits.
  5. You confirm.
  6. The write fires with confirm=True.
  7. Claude reminds you tag propagation in Tenable takes about thirty minutes, and that the local navi.db will reflect the change only after a re-sync.

At no point did Claude invent an asset count, a column name, a CVE relationship, a tag UUID, or a propagation timeline. At no point did the model produce a confident answer without a real query backing it. At no point did a write execute without an audit trail through user confirmation.

The result isn’t just a more accurate workflow. It’s a workflow where the failure modes are visible. If something’s wrong, you can see where — the query returned the wrong shape, or the schema didn’t match the question, or the data was stale. You’re not staring at a fluent paragraph wondering whether to trust it.

Wrapping Up

navi-mcp isn’t special because it uses MCP. It’s effective because I wrapped the protocol in skills that force the model to ground every answer in real data, and refused to expose anything dangerous without a human in the loop.

The lessons here aren’t specific to Tenable. Any time you connect an LLM to operational systems — ticketing, cloud infra, identity, incident response — the same patterns apply. Give the model a real backing store. Tell it to look before it speaks. Gate every write on explicit confirmation. Surface staleness automatically. Refuse to expose anything that’s hazardous to automate. Make skills the orchestration layer, not an afterthought.

Do all six and you get an MCP integration that earns its place in your stack. Skip them and you get exactly the experience the “MCP is dead” crowd is rightly complaining about.

If you haven’t read the “MCP Isn’t Dead” piece that frames this whole argument, it’s over here: “MCP Isn’t Dead. You’re Just Deploying It Wrong.”


메타데이터
post_id
461dcae63b2a
slug
the-navi-mcp-11-skills-driving-accurate-exposure-management-automation-461dcae63b2a
url
https://medium.com/@packetchaos/the-navi-mcp-11-skills-driving-accurate-exposure-management-automation-461dcae63b2a
canonical_url
https://medium.com/@packetchaos/the-navi-mcp-11-skills-driving-accurate-exposure-management-automation-461dcae63b2a
author_url
https://medium.com/@packetchaos
status
ok
fetched_at
2026-06-20 20:29:01