neuvector-mcp
Provides tools for interacting with SUSE NeuVector container security, including inventory, vulnerability scanning, compliance, events, and policy management.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@neuvector-mcpshow prod workloads with critical CVEs in Discover mode"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
neuvector-mcp
A Model Context Protocol server over the SUSE NeuVector container security
control plane. It exposes NeuVector's inventory, vulnerability, compliance,
event and policy surface as 72 typed MCP tools, so an operator or an agent can
ask questions like "which prod workloads run images with critical CVEs and are
still in Discover mode" and, behind an explicit two-step confirmation, act on
the answer. It ships as one SUSE BCI image that runs either over stdio for
a desktop MCP client or over authenticated HTTP in-cluster.
Read-only by default. Of the 72 tools, only the 41 read tools are registered unless you deliberately turn a mutating toolset on.
Contents
Related MCP server: Nutanix AIops
Quick start — stdio
stdio inherits the trust boundary of the parent process, so it carries no
transport auth. Use it only for a local desktop client.
Four environment variables and one command:
export NV_CONTROLLER_URL=https://<controller-host>:10443
export NV_API_ACCESS_KEY=<apikey_name>
export NV_API_SECRET_KEY=<apikey_secret>
export NV_VERIFY_TLS=false # the controller serves a self-signed cert by default
neuvector-mcpNV_TRANSPORT defaults to stdio and NV_READ_ONLY defaults to true, so the
above gives you the 41 read tools and nothing else.
Client configuration block
{
"mcpServers": {
"neuvector": {
"command": "neuvector-mcp",
"env": {
"NV_CONTROLLER_URL": "https://192.0.2.10:10443",
"NV_API_ACCESS_KEY": "mcp-reader",
"NV_API_SECRET_KEY": "…",
"NV_VERIFY_TLS": "false"
}
}
}
}If you installed into a virtualenv rather than onto PATH, use the absolute
path to the neuvector-mcp console script as command. The server writes
nothing to stdout except MCP JSON-RPC framing; all diagnostics go to stderr.
Verify the connection
python3 scripts/smoke_stdio.pyReports the tool count, the system summary, and up to five workloads with their
policy mode and high-severity vulnerability count. Read tools only — it performs
no mutation regardless of NV_READ_ONLY.
Quick start — in-cluster HTTP
HTTP is network-reachable, so it must be authenticated. The server refuses
to start on NV_TRANSPORT=http without at least one bearer token in
NV_HTTP_BEARER_TOKENS.
1. Build and push the image
make image # podman build, SUSE BCI base
podman push localhost:5000/neuvector-mcp:1.0.3 <your-registry>/neuvector-mcp:1.0.3Override the tag or registry with make image REGISTRY=<host> TAG=<tag>.
The base is registry.suse.com/bci/python:3.13 (SLE-based, SUSE-supported
lifecycle). The openSUSE equivalent, registry.opensuse.org/opensuse/bci/python,
is the only other permitted base — but as of 2026-07 its :3.13 manifest list
carries no linux/amd64 image, so it cannot build on x86_64. Its :3.12 tag
can. No base outside those two is permitted.
No local container runtime? The image builds fine in-cluster with a
throwaway rootless BuildKit pod. kubectl cp the build context (pyproject.toml,
README.md, src/, deploy/Dockerfile) to /workspace/src, mount a registry
credential as /home/user/.docker/config.json, then:
kubectl exec <pod> -- sh -c 'BUILDKITD_FLAGS=--oci-worker-no-process-sandbox \
buildctl-daemonless.sh build --frontend dockerfile.v0 \
--local context=/workspace/src --local dockerfile=/workspace/src/deploy \
--opt platform=linux/amd64 \
--output type=image,name=<registry>/neuvector-mcp:1.0.3,push=true'--oci-worker-no-process-sandbox is required: without it every RUN step dies
with error mounting "proc" to rootfs ... operation not permitted, because a
rootless pod cannot nest the process sandbox's user namespace.
2. Fill in the three placeholder values
deploy/deployment.yaml ships with three REPLACE_ME values. Do not commit
real ones; manage the two Secrets with SOPS or External Secrets instead.
Location | Replace with |
| The NeuVector API key pair — see provisioning |
|
|
| Your registry path |
The bearer-token string is token:scope|scope,token2:scope. A token with no
scope suffix defaults to nv:read.
Check the NetworkPolicy egress selector against your cluster: it targets
kubernetes.io/metadata.name: cattle-neuvector-system on port 10443. If
NeuVector runs in another namespace, change the selector, not the port.
3. Apply
kubectl apply --dry-run=server -f deploy/deployment.yaml # check first
kubectl apply -f deploy/deployment.yamlThe namespace is labelled pod-security.kubernetes.io/enforce: restricted, so
the server-side dry run is a real admission check rather than a formality. Note
that a dry run against a cluster where the neuvector-mcp namespace does not
yet exist will report namespaces "neuvector-mcp" not found for every
namespaced object — the dry-run Namespace is never persisted. Create the
namespace first if you want the dry run to exercise PodSecurity.
The namespace, PodSecurity restricted admission, and the image have all been
exercised for real on a k3s cluster — the pod is admitted and runs as uid 10001
with a read-only root filesystem.
deploy/fleet.yaml is a Fleet bundle for the same manifests. It excludes both
Secrets from Fleet's diff, so GitOps never reverts externally managed
credentials.
For MCP clients outside the cluster, apply deploy/service-loadbalancer.yaml
instead of the ClusterIP Service in deployment.yaml — both use the name
neuvector-mcp. It carries a MetalLB annotation for a fixed address; other
LoadBalancer implementations spell that differently, and omitting it lets the
controller pick. The NetworkPolicy needs no change: its ingress rule has no
from selector, so off-cluster sources on port 8080 are already admitted, and
the bearer token is what actually authorises them.
4. Point a client at it
POST https://<service>:8080/mcp
Authorization: Bearer <the token you generated>Configuration
Every knob is an environment variable prefixed NV_. Each also accepts
NV_<NAME>_FILE pointing at a file whose contents are the value, so Kubernetes
Secrets can be mounted rather than injected.
Variable | Default | Meaning |
|
| Controller REST base URL. Must start |
|
| Set |
| — | Path to a CA bundle. Takes precedence over |
|
| Timeout for normal calls, in seconds. |
|
| Timeout for scan, bench and repository-scan calls, in seconds. |
|
|
|
| — | Required when |
| — | Required when |
| — | Required when |
| — | Required when |
|
|
|
|
| HTTP transport bind address. |
|
| HTTP transport bind port. |
|
| HTTP transport request path. |
| — | Required for |
|
|
|
| the six read toolsets | Comma-separated list; see Toolsets. |
|
|
|
| — | When set, mutations targeting a namespace outside this list are refused. |
|
| Gates tools that use routes absent from the controller's Swagger. |
|
| Hard cap on any list tool's page size. |
|
| Truncation budget per tool result. Truncated results set |
|
| Standard Python log level name. |
|
|
|
| — | Optional second sink for audit records. |
NV_READ_ONLY=true together with any mutating toolset is a startup error,
not a silent downgrade. Contradictory configuration fails loudly.
Toolsets
There are 12 toolsets. A toolset is either read-only or mutating, never mixed —
the gate derives each tool's readOnlyHint from its toolset tag.
Toolset | Kind | On by default | Tools | Contents |
| read | yes | 11 | workloads, hosts, groups, services, enforcers, namespaces, network conversations |
| read | yes | 7 | image/workload/host scan reports, registries, scanners, vulnerability profiles |
| read | yes | 4 | workload/host compliance, CIS bench reports, compliance profiles |
| read | yes | 5 | threats, violations, incidents, audits, system events, alerts |
| read | yes | 10 | network rules, process/file profiles, DLP/WAF sensors, response rules, admission state and rules, admission rule assessment |
| read | yes | 4 | users, roles, auth servers, API keys (metadata only) |
| write | no | 8 | create/update/delete groups, network rules, process and file-monitor profiles |
| write | no | 4 | admission control state and rules |
| write | no | 7 | trigger/stop scans, registry CRUD, repository scan, bench runs |
| write | no | 4 | quarantine, service mode changes, packet capture |
| write | no | 5 | user, role and API key mutations |
| write | no | 3 | system config, namespace tags, scan config |
72 |
The default surface is 41 read tools. All 31 mutating tools are OFF by
default. With NV_TOOLSETS and NV_READ_ONLY unset, tools/list returns
exactly the 41 read tools; the mutating ones are never registered, so they are
not merely refused, they are absent.
To enable a mutating toolset you must do two things:
export NV_READ_ONLY=false
export NV_TOOLSETS=inventory,vulnerability,compliance,events,policy_read,iam_read,policy_writeNaming a mutating toolset without also setting NV_READ_ONLY=false is a startup
error. Enable the narrowest set that does the job, and provision the API key's
NeuVector role to match — see the safety model below.
Safety model
Five independent layers. A mutation must clear all five.
1. Transport auth ── HTTP requires a bearer token; stdio inherits process trust
2. Controller RBAC ─ the API key's NeuVector role is the real ceiling
3. NV_READ_ONLY ──── mutating toolsets are not registered at all
4. NV_TOOLSETS ───── unenabled toolsets are absent from tools/list
5. Confirm token ─── per-operation, payload-bound, two-step handshakeLayer 2 is the one that actually protects the cluster. The other four protect against model error, not against a compromised credential.
The confirmation handshake
Every mutating tool takes an optional confirm argument. Call it once without
confirm and nothing is sent to the controller — you get back a plan describing
the exact effect, the exact request body, and a token. Call it again with
identical arguments plus that token to execute.
The token is sha256(operation | target | canonical_json(payload))[:12]. Change
any argument that feeds the payload and the token stops matching, so a token
issued for one change cannot be replayed against a different one. This is a
guard rail, not a security boundary — a model could compute it in principle;
the point is that it cannot do so accidentally.
target is usually the bare object name, but it is not required to be. Where an
argument reaches the controller in the URL path rather than the body, it is
in neither the payload nor the object name, and folding it into target is the
only thing that binds the token to it. nv_update_user_role is the case that
matters: its role travels in the path, so its target is
"<fullname> role=<role>", not "<fullname>". Without that, a token previewed
for role="reader" would also authorise role="admin" — the handshake would
fail to gate the single field the tool exists to change. The controller call and
the request body are untouched by this; only the hashed target differs. Keep this
in mind when adding any mutating tool with path parameters.
Worked example
Call 1 — preview:
{"name": "nv_set_group_policy_mode",
"arguments": {"group_name": "nv.api.prod", "mode": "Protect"}}Response. Nothing was sent to the controller:
{
"status": "confirmation_required",
"operation": "nv_set_group_policy_mode",
"target": "nv.api.prod",
"effect": "Set policy mode of group 'nv.api.prod' to Protect. Traffic and process activity outside the learned policy will be blocked immediately.",
"payload": {"config": {"name": "nv.api.prod", "policy_mode": "Protect"}},
"confirm_token": "b670afec803f",
"next_step": "Review 'effect' and 'payload'. To execute, call nv_set_group_policy_mode again with identical arguments plus confirm='b670afec803f'."
}Call 2 — execute:
{"name": "nv_set_group_policy_mode",
"arguments": {"group_name": "nv.api.prod", "mode": "Protect",
"confirm": "b670afec803f"}}Response:
{
"status": "applied",
"operation": "nv_set_group_policy_mode",
"target": "nv.api.prod",
"effect": "policy_mode of nv.api.prod set to Protect",
"payload": {"config": {"name": "nv.api.prod", "policy_mode": "Protect"}}
}Change mode to Monitor while keeping the old token and the guard raises a
mismatch error telling you to call again without confirm for a fresh plan.
NeuVector API key provisioning
NeuVector UI → Settings → API Keys → Add.
Role:
readersuffices for read-only deployments — that is, for the default 41-tool surface. For mutating toolsets, pick the narrowest role that covers them; useadminonly wheniam_writeorsystem_writeis enabled.Set an expiry and calendar the rotation. An expired key surfaces as the controller error
code=3.Store
apikey_nameasNV_API_ACCESS_KEYandapikey_secretasNV_API_SECRET_KEY. The secret is shown once.
The API key's role is the real ceiling on what this server can do. A reader
key makes every mutating tool fail at the controller even if NV_READ_ONLY is
misconfigured.
Verified vs unverified
Read this before you rely on any of it.
Every tool in this server was built from appendices generated by parsing the NeuVector source. The majority of the surface is schema-backed: the request paths come from a generated endpoint inventory, and the response field names come from a generated field-level schema reference. The offline test suite remains authoritative for CI, which has no cluster.
A small subset has now been exercised against a live 5.4 controller —
nv_list_registries, nv_create_registry, nv_update_registry,
nv_trigger_scan, nv_delete_registry, and the startup login() probe against
GET /v1/system/summary. Everything outside that list is still unvalidated
against a running controller. That first live run immediately found a defect the
table below had mis-predicted; see What the live run changed.
The items below are the exceptions. For each, the type that would have pinned the shape was absent from the schema reference, so the implementation either inferred an envelope key or wrote a defensive extraction. A real controller can contradict any of them.
Endpoint | Tool | What is unverified | Failure mode if wrong |
|
| Envelope key | Degrades to |
|
|
| Degrades to an empty list |
|
| No Appendix B type at all; best-effort projection | Fields may come back empty |
|
|
| Returns |
|
|
| Wrong or empty filter list |
|
|
| Degrades to an empty list |
|
|
| Degrades to an empty list |
|
|
| Missing or empty scanner fields |
|
|
| Controller rejects the body, or applies a partial change |
|
|
| Controller rejects the body |
|
|
| Controller rejects the body as incomplete |
Each row is a place where the implementation had to choose without evidence. Treat the first run against a real controller as the actual verification step, and when a field comes back empty, fix both the projection and its fixture so the test would have caught it.
What the live run changed
The two RESTRegistryConfigV2 rows used to sit in that table, predicting that a
wrong wrapper would make the "controller reject the body". The first live run
showed the real behaviour is worse, and silent:
POST /v2/scan/registry nests every credential under config.auth. Sent
flat — which is what the documented V1 shape does — the controller answers
200 and stores the entry with no credentials at all. Nothing in the
response says so. The entry looks correct in nv_list_registries apart from an
empty username, and then every scan of a private repository fails. A flat
auth_with_token: true reads back as False the same way. PATCH behaves
identically, which is worse: a password rotation reports success and leaves the
stored credential untouched.
Both tools now send {"config": {..., "auth": {...}}}, pinned by
test_create_registry_credentials_nest_under_auth and
test_update_registry_credentials_nest_under_auth, and confirmed end to end
against a live controller. The lesson generalises to the rows still in the
table: "the controller rejects the body" is the optimistic failure mode. Silent
partial acceptance is the one to design for.
Known issues
These are real defects and gaps, stated as such.
1. Empty-string environment variables are treated as unset.
config._env() returns the fallback when a variable is present but empty, so
NV_READ_ONLY="" yields True (the default) rather than False. This
contradicts the bool parsing PHASE-1 states. It is fail-safe — the server stays
read-only — and is pinned by a non-strict xfail in
tests/test_config.py::test_bool_parsing_empty_string_is_false.
2. Credentials are redacted before the confirm token is computed. For registry and user mutations, the payload that feeds the token already has the password or secret replaced. Consequence: changing only a credential does not invalidate a previously issued token. This is deliberate — the alternative is hashing a live credential — but it is worth knowing before you rely on the token to detect payload drift.
3. The offline test suite is authoritative for CI, which has no cluster. Fixtures are hand-written and therefore agree with the implementation by construction — a fixture cannot catch a wrong wire shape, which is exactly how the registry credential defect survived to the first live run. Only the handful of tools listed in Verified vs unverified have been exercised against a real controller.
Running the gate
make verify is the definition of done. CI runs exactly this.
make verify # = lint + types + test + specTarget | What it runs |
|
|
|
|
|
|
|
|
|
|
|
|
Pass a specific interpreter with PY=, e.g. make verify PY=.venv/bin/python.
The nine spec rules
make spec builds the server with all toolsets enabled, introspects the
assembled tool list, and checks:
Rule | Requirement |
R1 | Tool name matches |
R2 | Description has at least 3 lines and at least 80 characters. |
R3 |
|
R4 | Exactly one toolset tag, drawn from the 12 valid toolsets. |
R5 | Every mutating tool accepts |
R6 | Every |
R7 | A structured output schema is declared — no bare |
R8 | The tool's name appears in at least one test file. |
R9 | With all toolsets enabled, at least one mutating tool is actually registered. |
Current state:
verify_spec: 72 tools introspected
[ok ] R1: 72 checked, 0 violation(s)
[ok ] R2: 72 checked, 0 violation(s)
[ok ] R3: 72 checked, 0 violation(s)
[ok ] R4: 72 checked, 0 violation(s)
[ok ] R5: 72 checked, 0 violation(s)
[ok ] R6: 72 checked, 0 violation(s)
[ok ] R7: 72 checked, 0 violation(s)
[ok ] R8: 72 checked, 0 violation(s)
[ok ] R9: 1 checked, 0 violation(s)Repository layout
src/neuvector_mcp/ config, client, guard, audit, models, server + 12 tool modules
tests/ offline suite; every controller call is mocked with respx
scripts/verify_spec.py the nine-rule gate
scripts/smoke_stdio.py the only script that talks to a real controller
deploy/ Dockerfile (SUSE BCI), deployment.yaml, fleet.yaml
neuvector-mcp-spec/ the normative spec package this server was built fromFull per-tool contracts are in neuvector-mcp-spec/tools/. The normative
specification is SPEC.md.
MCP server for the SUSE NeuVector container security platform. Built to the
specification in spec/SPEC.md; make verify is the definition of done.
License
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityAmaintenanceUnifies 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.Last updated4132418MIT
- AlicenseAqualityAmaintenanceGoverned AI-ops MCP server for Nutanix Prism Central v4 REST API, offering 47 read/write tools for clusters, VMs, storage, network, catalog, data protection, alerts, and LCM upgrades with built-in audit, budget, and risk-tier governance.Last updated51MIT
- AlicenseAqualityCmaintenanceMCP server that provides deterministic remediation plans from vulnerability scan data (Trivy, Grype, Sysdig) via tools to generate plans from scan content or fetch live from Sysdig API, enabling AI agents to produce concrete, distro-aware fix commands.Last updated22Apache 2.0
- Flicense-qualityCmaintenanceMCP server for ConnectSecure vulnerability management, exposing 285 read-only tools to query assets, vulnerabilities, Active Directory, and more via natural language.Last updated
Related MCP Connectors
Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.
MCP server for Pentest-Tools.com: run scans, manage findings and reports via your preffered LLM.
Markdown-first MCP server for Notion API with 8 composite tools and 39 actions.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/darthzen/neuvector-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server