Skip to main content

vulkro scan-mcp-server

Scan MCP server SOURCE CODE for prompt-injection-shaped and tool-poisoning vulnerabilities. Pure offline static analysis; no cloud LLM, no telemetry.

Three Vulkro subcommands work in the MCP space and they do different things. Make sure you are reaching for the right one:

SubcommandWhat it auditsRule family
vulkro mcp-auditMCP host CONFIGS (Claude Desktop, Cursor, Windsurf, ...)MCP-001..006
vulkro mcp serveMakes Vulkro itself BE an MCP server(transport)
vulkro scan-mcp-server (this page)MCP server SOURCE CODE (the Python / TS code that IMPLEMENTS an MCP server)MCP-SERVER-001..008

Usage

vulkro scan-mcp-server src/ # scan a project root recursively
vulkro scan-mcp-server server.py # scan a single file
vulkro scan-mcp-server src/ --format json # machine-readable output
vulkro scan-mcp-server src/ --format sarif > findings.sarif

Both Python (mcp.server, FastMCP) and TypeScript / JavaScript (@modelcontextprotocol/sdk) servers are supported in v1. Other language SDKs (Rust, Go, C#, Swift) will follow as the official MCP server-side SDKs in those ecosystems mature.

Flags

FlagDescriptionDefault
<path>Path to a single source file or to a project root. Required positional argument.(none)
--format <FORMAT>One of table (human), json, sarif, ndjson.table
--fail-on <SEVERITIES>Comma list of severities that cause a non-zero exit. Same shape as vulkro scan --fail-on.critical,high

Exit codes

CodeMeaning
0No findings at or above --fail-on (or no MCP server source files discovered under <path>).
1Findings at or above the --fail-on threshold.
2Error: <path> does not exist, IO failure, or internal parse error.

Detectors

Eight detectors run by default. Each emits a stable rule ID prefix in its finding message so SIEM / SARIF deduplication keys stay constant across releases.

MCP-SERVER-001 tool-description injection

Fires when a tool description (or its inputSchema) is built from non-literal input. The LLM reads the description as canonical tool documentation; an attacker who controls the interpolated value can hide instructions inside the description text.

Triggers on f-strings, .format(...), string concatenation (Python), template literals with ${...} placeholders, and string concatenation in the description argument of server.tool(...) (TypeScript / JavaScript).

Stays quiet when the description is a string literal.

MCP-SERVER-002 tool poisoning

Fires when a tool handler takes a caller-supplied file path, URL, env-var name, or command and passes it directly into a sensitive sink WITHOUT an allowlist or validation step. Classic shape: def read_file(path: str): return open(path).read().

Suppression markers include explicit allow-list set membership tests, pathlib.Path(p).resolve() plus a startswith check, urlparse plus a host check, pydantic field validators, and zod.parse / similar shapes in TypeScript.

MCP-SERVER-003 rug-pull risk

Fires when a tools/list handler returns tool descriptions computed at request time (from a global, a function call, or a lookup). Each tools/list call can return different copy: this is the "rug pull" shape where the server advertises one description at install time and swaps it once the client trusts it.

MCP-SERVER-004 sensitive sink in tool handler

Fires when a tool handler body contains a subprocess call, eval, raw SQL with interpolation, or another sensitive sink. Even when the target is constant, the surface is wide because MCP tools run with the server process's full capability.

Emitted at Medium severity by default; bump --fail-on to include medium if you want non-zero exits to gate on these.

MCP-SERVER-005 manifest vs handler mismatch

Fires when the advertised inputSchema does not match the handler signature: either the handler accepts a parameter the schema does not declare, or the schema declares a property the handler ignores. The first shape is a covert capability surface; the second is a silent capability drift between docs and behaviour.

MCP-SERVER-006 unbounded resource access

Fires when an MCP server is mounted at an overbroad filesystem root (/, /home, ~, $HOME) or when a tool performs outbound fetches with no allowed-host check or rate limit anywhere in the file. The first is a catastrophic disk-scope misconfiguration; the second is an SSRF / data-exfil surface.

MCP-SERVER-007 prompt data leakage

Fires when a tool handler returns env-var-shaped secrets, sensitive identifier-shaped variables (password, api_key, ssn, credit_card, ...), or a literal matching a real provider's credential format (Stripe sk_live_, GitHub ghp_, AWS AKIA..., Slack xox[bpoa]-...). Tool results land in the model's prompt context and may be logged, displayed, or relayed by the MCP client.

MCP-SERVER-008 auth bypass on sensitive tools

Fires when a tool whose name or description advertises a destructive capability (delete, drop, purge, admin, exec, transfer, rotate_key, ...) has no auth check at handler entry. The MCP runtime treats every tool as freely callable; a destructive tool must gate itself.

Examples

Scan a FastMCP server directory and emit SARIF for GitHub Code Scanning:

vulkro scan-mcp-server ./fastmcp-server --format sarif > vulkro-mcp.sarif

Run as a pre-commit step that fails on any new MCP-SERVER finding:

vulkro scan-mcp-server src/ --fail-on critical,high,medium

Hook into CI: the JSON output is the standard Vulkro ScanResult shape, so existing tooling that parses vulkro scan -f json works unchanged.

Limitations

This is static analysis. Detectors err toward false negatives over false positives so they do not bark on a well-built server. The intended workflow is to run alongside vulkro scan: that catches the rest of the OWASP API + LLM Top 10 surface, and scan-mcp-server adds the MCP-specific axis.