The inventory

Start here. Every other page in this section is about writing rules, and the hard part of writing a capability rule is not the syntax — it is knowing what to name.

The inventory is a list of every tool, MCP server and skill your organisation’s agents have been observed carrying, and which client carried it.

Why a repository scan is not enough

Vulnetix already ships an AI-BOM: it scans a repository and reports the AI agents, SDKs and models it finds in config files, environment-variable names, source literals and commit trailers. That answers “what AI is written down here”.

It cannot answer “what is our estate actually running”, because the interesting cases are not written down:

  • An MCP server added to a developer’s local ~/.claude.json, holding a long-lived token for the issue tracker.
  • A tool that exists only inside a coding agent’s own binary.
  • An agent a contractor is using that nobody procured.

All three are in every request those agents make. These APIs are stateless, so the client re-declares its complete tool list on every turn, and the firewall is already reading the body to apply guardrails.

Static discovery plus runtime observation is a complete AI asset inventory. Either alone is not.

What is recorded

FieldWhat it is
kindtool, mcp_server, or skill
identityThe name — the string a rule’s pattern matches against
detailSupporting context: an MCP server’s URL, or a built-in tool’s versioned type
sourcedeclared, invoked, or inferred — see below
clientName / clientVersionThe calling agent, from its headers
providerSlug, wireWhere it was going, and on which surface
firstSeenAt / lastSeenAtWhen
observationsHow many distinct sightings windows — not a request count

What is not recorded

No prompt. No completion. No tool argument. No tool result. No message content of any kind, and no record that any particular request happened.

Nothing about a request’s text is read to build an inventory row. The gateway reads the tools array and the request headers, which is the same class of configuration as the provider slug and model name it already records.

MCP server URLs are stored as scheme://host/path only — query string and userinfo are discarded before the value leaves the parser. That is a requirement, not tidiness: the Anthropic mcp_servers entry carries an authorization_token beside the URL, and a server that authenticates by query parameter is a real pattern. Nothing token-shaped is allowed into a row that outlives the request.

Note

The inventory is configuration, not usage. It records that your estate offers a mcp__github__* toolset; it does not record that anybody used it, what they asked it to do, or what came back.

That is why it runs for every organisation on every plan and is independent of inference logging — the two answer different questions. Logging is about conversations and is a paid opt-in. This is the list your guardrails are written against, in the same way the provider and model catalogs are.

The three sources

source is on every row because how firmly something is known matters, and flattening the difference would let a naming convention be read later as a protocol fact.

declared — read verbatim from a field of the request. The tool was in the tools array. This is the strongest ordinary case.

invoked — the tool appears in the replayed conversation as having actually been called. Stronger than declared: it was not merely offered to the model, it ran. It is also what makes a strip unsafe on that name, because removing the declaration would orphan the call. See rollout.

inferred — derived from a naming convention rather than read. A local stdio MCP server is only identifiable by its mcp__server__tool tool-name prefix, and a skill list read off a dispatching tool’s schema is a read of a shape rather than of a documented field. Treat these as strong hints, not facts.

observations counts windows, not requests

A Claude Code turn declares around forty tools and re-declares the same forty on every request for hours. Recording that per request would be forty rows a second for one unchanged fact.

Instead the gateway hashes the capability set plus the client into a contextHash, keeps an in-process set of the hashes it has already written, and issues one statement for the whole set when it sees a new one. Steady-state cost is one database write per distinct agent configuration per organisation per hour.

So observations is “how many times has this configuration turned up in a fresh window”, not traffic volume. It is named for what it counts rather than implying something it does not measure. If you want per-request attribution, that is what AiFirewallLog.contextHash is for — it joins an individual inference to the exact configuration in play, and a change in that hash between two consecutive rows is the moment somebody wired something new in.

Reading it

Dashboard

The Tools & MCP tab of the AI Firewall dashboard.

Filter by kind and client, search by name, and use the per-row Deny or Allow button to turn an observation into a rule. Rules created that way start at flag on purpose: you have just discovered the capability, you have not yet found out what else in the estate depends on it.

CLI
# everything
vulnetix ai-firewall inventory

# just the MCP servers — the shadow-MCP question, answered
vulnetix ai-firewall inventory --kind mcp_server

# what one agent carries
vulnetix ai-firewall inventory --client claude-code

Resetting it

The inventory can be cleared, from the dashboard or with vulnetix ai-firewall inventory --clear.

That exists because an inventory nobody can make current is one people stop trusting and then stop reading. An organisation that has retired an MCP server wants the row gone, not explained. The gateway rebuilds from live traffic within its cache window, so this is a reset rather than a destruction — anything still in use reappears within the hour.

  • Rolling out a rule — what to do with what you find.
  • MCP servers — why the mcp_server rows and the mcp__* tool rows are telling you different things.
  • Opt-in logging — what else the firewall does and does not retain.