Try it without a cluster
The full enforcement path needs a cluster, but you do not need one to see what Norviq does to an MCP server. The MCP action-firewall runs as a plain stdio proxy, so you can put it in front of a server you already use and watch it work before deciding whether the cluster install is worth your afternoon.
This takes about two minutes.
Prerequisites
Section titled “Prerequisites”Python 3.11+. That is the whole list.
pip install norviqWrap a server you already run
Section titled “Wrap a server you already run”Norviq spawns the upstream server as a child process and sits on the stdio pair between it and your
MCP host. Everything after -- is the command it would have run:
python -m norviq.mcp --server-id filesystem -- \ npx -y @modelcontextprotocol/server-filesystem /work--server-id is the stable key the tool-definition pins are stored against. Give each upstream
server its own, and keep it stable across runs — the pin is what lets Norviq tell you a definition
changed since last time.
To point your MCP host at it, replace the server’s own command with the wrapped one. In a Claude
Desktop or Cursor config that is the command and args pair:
{ "mcpServers": { "filesystem": { "command": "python", "args": [ "-m", "norviq.mcp", "--server-id", "filesystem", "--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/work" ] } }}Every tools/list and tools/call now passes through the gates on the way past.
Or front a remote server
Section titled “Or front a remote server”If the server is already running somewhere and speaks streamable HTTP, proxy it instead of spawning it:
python -m norviq.mcp --http --listen 127.0.0.1:9000 --upstream https://mcp.example.com/mcpWhat local mode does — and what it does not
Section titled “What local mode does — and what it does not”This is the part worth reading before you draw conclusions from a clean run.
It does:
- Scan tool definitions at discovery. Gate A reads every tool’s declared text and flags injection
patterns —
<IMPORTANT>-style markers, instructions addressed at the model, homoglyphs and invisible characters in tool names. - Content-hash pin each tool. Six fields per tool —
name,title,description,inputSchema,outputSchema,annotations— are hashed as canonical JSON on first sight. A definition that changes between runs is a visible diff rather than a silent swap.
It does not:
- Enforce policy. Gate B evaluates against policy served by the control plane. With no control plane there is no policy, and a call with no policy behind it is allowed. Local mode is a scanner and a change detector, not an enforcement point.
- Consult the server-level decision. Before any per-tool work, Gate A normally asks the control
plane whether this server should be spoken to at all. With nothing to ask, every local discovery
logs
gate_a.server_decision_unavailableand the listing proceeds — so a server you would have blocked still gets its descriptions into the model’s context.
The scanner is also a heuristic over natural language, and evadable by design. Encoded, translated or paraphrased instructions walk through it; the defeat set is published with per-payload results rather than left for you to discover. A clean scan is a triage signal, not a verdict.
Connect it to a control plane
Section titled “Connect it to a control plane”Local mode reads the same environment the SDK and the injected sidecar use, so a proxy that already
has NRVQ_POLICY_ENGINE_URL and NRVQ_API_TOKEN in its environment gets policy evaluation with no
additional configuration surface:
export NRVQ_POLICY_ENGINE_URL=https://norviq-api.example.comexport NRVQ_API_TOKEN=...
python -m norviq.mcp --server-id filesystem -- npx -y @modelcontextprotocol/server-filesystem /workAt that point Gate B has something to decide with, and the same command is doing real enforcement.
- Get started — the cluster install, from
helm installto a decision flipping fromallowtoblock - How it works — what the two gates actually evaluate
- Security model — the defaults, stated plainly, including which ones ship open