Skip to content

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.

Python 3.11+. That is the whole list.

Terminal window
pip install norviq

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:

Terminal window
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.

If the server is already running somewhere and speaks streamable HTTP, proxy it instead of spawning it:

Terminal window
python -m norviq.mcp --http --listen 127.0.0.1:9000 --upstream https://mcp.example.com/mcp

What 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_unavailable and 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.

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:

Terminal window
export NRVQ_POLICY_ENGINE_URL=https://norviq-api.example.com
export NRVQ_API_TOKEN=...
python -m norviq.mcp --server-id filesystem -- npx -y @modelcontextprotocol/server-filesystem /work

At that point Gate B has something to decide with, and the same command is doing real enforcement.

  • Get started — the cluster install, from helm install to a decision flipping from allow to block
  • How it works — what the two gates actually evaluate
  • Security model — the defaults, stated plainly, including which ones ship open