Skip to main content
Version: Latest (1.9.x dev)

Find shadow MCP servers your developers wired up

DVARA, the AI governance platform, governs every MCP call that goes through the gateway. A server that a developer adds to Cursor, VS Code or Claude Desktop and calls directly never reaches the gateway, so the gateway cannot report it. OWASP lists these as MCP09, Shadow MCP Servers.

Those servers are written down, though: in the client's MCP config file, or as a package in a project's dependencies. DVARA ships a small scanner that reads those files, and a Flightdeck page that compares what it found with the servers you registered.

Enterprise only

Not included in DVARA Open Source.

The scanner​

mcp_scan.py is one Python file. Download it from the MCP Shadow Servers page in DVARA Flightdeck (see below). It needs Python 3.9 or later and nothing else, and it is short enough to read before you run it.

  • It reads only the files and folders you name. With --home it also reads the known MCP client config files in your own home folder, and nothing else there.
  • It makes no network connection and runs nothing it finds.
  • It needs no DVARA account or key, so it works before DVARA is installed.
  • It writes nothing but its report.

DVARA never scans anyone's machine or network. A person runs the scanner and decides what to share.

It knows the config files of Claude Code, Claude Desktop, Cursor, VS Code, Windsurf, Cline, Gemini CLI, Zed and Codex. It also lists MCP packages in package.json, pyproject.toml and requirements.txt.

# A repository
python3 mcp_scan.py path/to/repo --out mcp-scan.json

# The MCP clients on your own machine
python3 mcp_scan.py --home --out mcp-scan.json

What the report keeps​

The report keeps names, never secrets:

KeptNever kept
The server's name, its client and its fileEnvironment variable values
The package, image or command it runs, or the URL's host and pathHeader values, such as tokens
The transportCommand arguments
The names of the variables and headers it setsA URL's query, user name or password

Compare it with what you registered​

Open MCP Shadow Servers in Flightdeck and upload the report. The same page offers the scanner itself as a download.

  • Platform roles: MCP → Shadow Servers, then choose the workspace.
  • Workspace roles (admin, developer and viewer): Agents → MCP Shadow Servers. The report is compared with your own workspace.

Give your gateway's address too, so that servers reached through it count as governed. Each server is then shown as:

StatusMeaningWhat to do
ShadowNo server registered to the workspace runs or calls itRegister it, or remove it from the client
Not approvedIt is registered, but quarantined, suspended or disabled, and the client calls it directlyRemove it from the client until it is approved
Called directlyIt is registered and active, but the client calls it directlyPoint the client at the gateway
GovernedThe client calls it through the gatewayNothing

Servers are compared by what they run or call, not by the name a developer gave them. npx -y @acme/server@1.2.0 and npx @acme/server are the same server.

The upload is recorded in the audit log with its counts and the shadow servers' names. Flightdeck does not store the report.

Use it as a CI gate​

Download registered list on the same page saves your workspace's registered servers as a file: what each runs or calls, and its status. It holds no credentials. The scanner can then fail a build that adds an unregistered server, still without network access:

python3 mcp_scan.py . --registered mcp-registered.json --fail-on SHADOW,UNAPPROVED

The run exits with code 1 and names each server that has one of the listed statuses. A server the client config turns off does not fail it.

What it does not find​

  • A server nobody wrote down. One started by hand, or by a client whose config the scanner does not know, is not found.
  • Machines nobody scanned. The report covers the files it was given.
  • A local script at another path. It is matched by its path, so the same script elsewhere shows as shadow. That errs on the side of a second look.
  • Version differences. A package is matched by name; the version each side names is shown, not judged.