Security audit CLI for Model Context Protocol (MCP) servers.
Static and runtime checks for prompt-injection vectors, tool poisoning, hidden Unicode,
permissive schemas, insecure transports, and capability drift.
Zero third-party Go dependencies. Cross-platform. Runs offline. No telemetry.
Checks · Install · Quick start · Usage · CI · FAQ · About Penaxtra
pnx-mcp-audit is a single-binary command-line auditor for Model Context
Protocol (MCP) servers. It connects to a
server over stdio or Streamable HTTP, performs the MCP initialize handshake,
enumerates the server's tools, resources, and prompts, and runs a battery of
static safety checks against the descriptors returned. The output is a
deterministic finding list mapped to severity tiers and exit codes that fit
straight into a CI pipeline.
The tool exists because MCP servers expose a wide and almost entirely
text-based attack surface to any LLM that connects to them. A tool description
is an instruction stream the model will read. A schema with additionalProperties: true
is a free-form argument bag. An invisible Unicode tag inside a tool name is a
tool-poisoning channel. Today most of these issues are caught, if at all, by
manual review. pnx-mcp-audit lets you check them in a second, every commit.
The project is sponsored, but not gated, by Penaxtra, an enterprise AI Security Posture Management (AI-SPM) platform. Penaxtra runs this and a much wider catalogue of checks against production LLM deployments for regulated mid-market customers in Europe and Turkiye. The audit logic shipped here is the same logic Penaxtra uses against the MCP attack surface; the project is open-source so that the broader MCP ecosystem can adopt it.
The Model Context Protocol is, by design, a way for a language model to discover and invoke external capabilities. A server publishes:
- Tools -- callable functions with a JSON Schema for arguments.
- Resources -- addressable read-only data the model can pull in.
- Prompts -- reusable prompt templates with parameters.
Three properties of this design make MCP servers a high-leverage target:
- Tool descriptions are model-trusted prose. The text in
tools/list -> descriptionreaches the host model as part of its instruction context. A malicious or sloppy description is a direct prompt- injection vector against every model that connects. - Schemas are advisory, not enforcing. A loose schema (
additionalProperties: true,type: objectwith noproperties, or acommand: string-style argument) lets the model pass anything the server is willing to execute. - Capability declaration is voluntary. Servers can ship tools without
declaring the
toolscapability, exposing surface area that hosts may route to without expecting it.
pnx-mcp-audit codifies the resulting checklist into 13 production rules
(MCP-001 ... MCP-013) plus an opt-in active-probe slot (MCP-100+).
Each finding carries a severity, a stable rule ID, the exact descriptor that
triggered it, and a remediation hint. The audit terminates with an exit code
that encodes the highest severity found, so --fail-on high becomes a one-line
CI gate.
| ID | Severity | What it catches |
|---|---|---|
| MCP-001 | HIGH | Tool description contains prompt-injection prose ("ignore previous", "system:", "you are now", role-override patterns). |
| MCP-002 | MEDIUM | Tool description carries inline HTML or Markdown that hosts may render verbatim into the model context. |
| MCP-003 | HIGH | Hidden Unicode in tool descriptions: zero-width, bidi override, tag-block characters used as a tool-poisoning channel. |
| MCP-004 | MEDIUM | Destructive verbs in tool names (delete, drop, wipe, rm, truncate) without an explicit confirmation argument. |
| MCP-005 | LOW | Tool ships with no description at all -- host cannot reason about safety. |
| MCP-006 | MEDIUM | Permissive input schema: missing schema, additionalProperties: true, or empty properties object. |
| MCP-007 | HIGH | Tool argument is a free-form shell-like string (command, cmd, shell, exec, script, bash). |
| MCP-008 | HIGH | Duplicate tool names returned by tools/list -- behaviour depends on host tie-break. |
| MCP-009 | LOW | Server exposes a very large tool catalogue (high cognitive load + context pollution risk). |
| MCP-010 | HIGH | Resource URI uses a sensitive scheme or absolute filesystem path (file:///etc/, file:///home/, etc.). |
| MCP-011 | CRITICAL | Transport is unencrypted: bare http:// or stdio over an untrusted boundary. |
| MCP-012 | LOW | Server exposes surface (tools / resources / prompts) without declaring the matching capability in initialize. |
| MCP-013 | MEDIUM | Server negotiated a protocol version this auditor does not recognise. |
Full rule documentation: docs/checks.md.
Grab the archive for your platform from the
releases page.
Platforms in the v0.1.0 release matrix: linux/amd64, linux/arm64,
darwin/amd64, darwin/arm64, windows/amd64. Each archive ships with
SHA256SUMS and a detached signature; verify before running.
# Linux/macOS
curl -sSL https://github.com/projectboot/pnx-mcp-audit/releases/latest/download/pnx-mcp-audit_linux_amd64.tar.gz | tar xz
./pnx-mcp-audit versiongo install github.com/projectboot/pnx-mcp-audit/cmd/pnx-mcp-audit@latestRequires Go 1.22 or newer. The binary lands in $(go env GOBIN) or
$(go env GOPATH)/bin.
git clone https://github.com/projectboot/pnx-mcp-audit
cd pnx-mcp-audit
make build # produces ./bin/pnx-mcp-audit
make test # runs the table-driven test suite with -race
make smoke # builds the mock server + runs the audit against itThe build is stdlib-only. The single dependency declared in go.mod is
the Go standard library itself, so the supply chain is auditable in a
single go list -m all line.
Audit a local stdio MCP server (its binary or npx invocation):
pnx-mcp-audit scan stdio --command "/usr/local/bin/my-mcp-server" --fail-on highAudit a remote Streamable HTTP MCP server:
pnx-mcp-audit scan http --url "https://mcp.example.com/mcp" \
--header "Authorization: Bearer $MCP_TOKEN" \
--format json \
--fail-on criticalExit codes:
| Code | Meaning |
|---|---|
| 0 | Audit completed; no finding at or above --fail-on. |
| 1 | Auditor itself failed (transport error, bad flags). |
| 2 | One or more LOW or MEDIUM findings (only if --fail-on low/medium). |
| 3 | One or more HIGH findings. |
| 4 | One or more CRITICAL findings. |
The CLI exposes three subcommands:
pnx-mcp-audit scan stdio --command <path> [flags]
pnx-mcp-audit scan http --url <url> [flags]
pnx-mcp-audit checks
pnx-mcp-audit version
Common flags:
| Flag | Purpose |
|---|---|
| `--format text | json |
| `--fail-on low | medium |
--no-color |
Force monochrome output (CI logs). |
--timeout <duration> |
Per-request timeout (default 30s). |
--max-pages <n> |
Pagination guard for tools/list, etc. (default 32). |
--include-mcp <id,id> |
Run only the listed rules. |
--exclude-mcp <id,id> |
Skip the listed rules. |
--probe |
Enable active probes (MCP-100+). Off by default. |
--header <k: v> |
Repeatable; adds an HTTP header (Streamable HTTP only). |
Full flag reference: docs/usage.md.
pnx-mcp-audit v0.1.0
target: stdio:///usr/local/bin/my-mcp-server
duration: 812ms
findings: 5 (1 critical, 2 high, 1 medium, 1 low)
[CRITICAL] MCP-011 Insecure transport
scope: transport
detail: server reachable via plain http://; prompts and tool I/O are
observable to any on-path attacker
fix: terminate TLS in front of the MCP server; require https://
[HIGH] MCP-001 Prompt-injection patterns in tool description
scope: tool "send_email"
detail: description contains "ignore previous instructions" (offset 142)
fix: remove instruction-like prose; describe behaviour, not directives
[HIGH] MCP-007 Free-form shell argument
scope: tool "shell_exec" -> input.command (type: string)
detail: argument name + type allows arbitrary command strings
fix: replace with structured arguments + an allow-list
[MEDIUM] MCP-006 Permissive input schema
scope: tool "query" -> input
detail: additionalProperties: true
fix: set additionalProperties: false and enumerate properties
[LOW] MCP-009 Large tool catalogue (47 tools)
scope: server
detail: beyond ~30 tools the host model context fills quickly with
descriptions; consider splitting servers
{
"tool": "pnx-mcp-audit",
"version": "0.1.0",
"target": "stdio:///usr/local/bin/my-mcp-server",
"duration_ms": 812,
"summary": { "critical": 1, "high": 2, "medium": 1, "low": 1 },
"findings": [
{
"id": "MCP-011",
"severity": "critical",
"title": "Insecure transport",
"scope": "transport",
"detail": "server reachable via plain http://",
"remediation": "terminate TLS in front of the MCP server",
"references": ["OWASP-LLM-02"]
}
]
}JSON output is stable; new fields are additive. It is intended to be the input to upstream tooling -- SIEM rules, dashboard ingest, evidence packs.
--format markdown produces a single Markdown table suitable for posting
into a GitHub PR comment via the gh pr comment command.
name: mcp-audit
on:
pull_request:
paths: ['mcp-server/**']
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- run: go install github.com/projectboot/pnx-mcp-audit/cmd/pnx-mcp-audit@latest
- run: |
go build -o ./bin/server ./mcp-server
pnx-mcp-audit scan stdio \
--command "./bin/server" \
--format json \
--fail-on high \
> mcp-audit.json
- uses: actions/upload-artifact@v4
if: always()
with:
name: mcp-audit-report
path: mcp-audit.jsonmcp-audit:
image: golang:1.22
script:
- go install github.com/projectboot/pnx-mcp-audit/cmd/pnx-mcp-audit@latest
- pnx-mcp-audit scan stdio --command "./bin/server" --fail-on high --format json > mcp-audit.json
artifacts:
when: always
paths: [mcp-audit.json]pnx-mcp-audit scan stdio --command "./server" --fail-on high
RC=$?
[ "$RC" -ge 3 ] && { echo "audit failed (rc=$RC)"; exit 1; }More recipes: docs/usage.md.
The repository ships an intentionally vulnerable MCP server in
examples/mock-server/. It exposes a tool with a prompt-injection-laden
description, a free-form command argument, a duplicate tool name, and a
file:///etc/ resource URI. Use it to verify your install end-to-end:
make mock # build ./bin/mock-server
./bin/pnx-mcp-audit scan stdio \
--command "./bin/mock-server" \
--fail-on high
# expected exit code: 3make smoke chains build + mock + audit + exit-code assertion into a
single target, used by CI as the integration test.
pnx-mcp-audit (single static binary)
-----------------------------------
cmd/pnx-mcp-audit/main.go (flag parsing, subcommands)
|
v
internal/mcp/ (transport + JSON-RPC client)
internal/audit/ (Severity, Finding, Registry)
internal/checks/ (MCP-001 ... MCP-013)
internal/report/ (text / JSON / Markdown)
internal/version/ (build metadata)
|
v
stdout
No network egress except to the MCP target.
No telemetry. No phone-home. No analytics.
The transport layer is split per protocol. transport_stdio.go spawns the
target as a child process and frames JSON-RPC messages over its
stdin/stdout. transport_http.go speaks the MCP Streamable HTTP profile,
including the SSE chunking that some servers emit on tool responses.
The check engine is a registry of pure functions over the captured server
snapshot (tools, resources, prompts, serverInfo, negotiatedVersion).
Adding a rule means writing one function plus a table-driven test. Active
probes (--probe) run in a separate phase against a deliberately
restricted client to avoid side effects on the target.
To set expectations correctly:
- It does not test LLM safety. It tests the MCP server that an LLM might connect to. The LLM itself is out of scope.
- It does not modify state on the server. All checks are read-only against
initialize,tools/list,resources/list,prompts/list. Active probes are gated behind--probeand explicitly documented per rule. - It does not catch logic bugs in tool implementations. It catches descriptor-level safety issues. Runtime behaviour of a tool requires a different class of testing (which Penaxtra ships in its commercial platform).
- It does not ship rules-as-data updates over the wire. New rules ship in new releases; the binary you run yesterday will produce the same report tomorrow. Reproducibility is a feature.
- MCP-014 ... MCP-019: depth checks on prompt templates, resource MIME- type drift, recursive tool references, schema-vs-example contradictions.
- MCP-100 ... MCP-110 (active probes): instruction-confusion echo, parameter-fuzzing for permissive schemas, large-response truncation behaviour, capability misreporting, partial-result handling.
- WebSocket transport: a third transport once the MCP WebSocket profile is final.
- SARIF output: optional
--format sariffor native GitHub Code Scanning ingest.
The roadmap is intentionally narrow. Anything that requires shipping a service or accepting customer data lives in the commercial Penaxtra platform, not here.
Does this replace a manual MCP server code review? No. It catches the descriptor-level issues a reviewer would otherwise spot manually, freeing the human reviewer to focus on logic, state-handling, and authn/authz boundaries.
Does it work without an internet connection? Yes. The audit logic is fully local. The only network traffic is to your MCP target when you scan a remote HTTP server.
Does it phone home?
No. There is no telemetry, no usage analytics, no checksum upload. The only
network calls are the ones you ask for via scan stdio or scan http.
Is the rule set extensible?
Yes. Rules are plain Go functions registered into a Registry. See
CONTRIBUTING.md for the process and
internal/checks/static.go for examples.
Does it map to OWASP / NIST / MITRE?
Yes. Findings carry references such as OWASP-LLM-02, OWASP-LLM-06,
MITRE-ATLAS-TA0043, where applicable. The commercial Penaxtra platform
extends this with EU AI Act, NIST AI 600-1, and ISO 42001 control mapping.
What is the licence?
Apache-2.0. Use it in commercial products, modify it, redistribute it. See
LICENSE and NOTICE.
If you find a vulnerability in this tool itself, please disclose it
privately. The process and timelines are documented in
SECURITY.md.
If you find an MCP server in the wild that triggers a new class of issue
this tool does not catch, open an issue with a minimal reproducible sample
(redact secrets first). New rules ship with MCP-NNN IDs and stay backward
compatible.
Contributions are welcome, especially:
- New check rules backed by a real-world MCP server sample.
- False-positive / false-negative bug reports with a fixture.
- Active probes (MCP-100+) with a written safety argument.
- CI recipes for additional platforms.
Read CONTRIBUTING.md before opening a PR. Every PR
must pass go vet, gofmt, and go test -race -count=1 ./....
Penaxtra is an enterprise AI Security Posture Management (AI-SPM) platform built by Seccops Siber Güvenlik Teknolojileri A.Ş., headquartered in Istanbul, Turkiye. Penaxtra runs continuous adversarial test suites against production LLM applications, maps findings to OWASP LLM Top 10, OWASP Agentic Top 10, NIST AI 600-1, MITRE ATLAS, EU AI Act, and ISO/IEC 42001, and produces audit-grade evidence packs for regulated mid-market customers in Europe and Turkiye.
pnx-mcp-audit is one open-source slice of Penaxtra's wider check
catalogue. The commercial platform additionally provides:
- A multi-judge adversarial scanning pipeline (three independent LLM judges plus a meta-judge for consensus and bias reduction).
- A runtime AI gateway agent that enforces signed DLP policy at the request boundary, with zero prompt bytes leaving customer infrastructure.
- Continuous compliance evidence collection across six regulatory frameworks, exportable as auditor-ready PDF or JSON.
- Risk scoring across applications, models, datasets, prompts, and tools.
If your team is shipping production LLM workloads under a compliance obligation (EU AI Act, ISO 42001, banking / healthcare / insurance / public sector), start a conversation with Penaxtra.
pnx-mcp-audit is authored and maintained by
Tolga SEZER, Founder of Penaxtra. Tolga has spent more
than fifteen years on the offensive and defensive sides of enterprise
security, leading penetration testing and red-team engagements for banking,
public sector, and critical-infrastructure customers before turning to the
LLM security problem space full-time.
"MCP servers are the new high-leverage attack surface for LLM applications. Treating them like any other untrusted service is the minimum bar.
pnx-mcp-auditis the smallest possible tool that lets a security engineer enforce that bar in CI on day one." -- Tolga SEZER
| Topic | Link |
|---|---|
| What AI-SPM is and why regulated teams need it | penaxtra.com/ai-security-posture-management |
| Runtime AI gateway architecture (zero prompt egress) | penaxtra.com/platform/runtime-ai-gateway |
| MCP server security in production | penaxtra.com/platform/mcp-security |
| MCP security checklist for engineering teams | penaxtra.com/learn/mcp-security-checklist |
| Multi-judge consensus methodology | penaxtra.com/methodology/judge-consensus |
| OWASP LLM Top 10 coverage map | penaxtra.com/compliance/owasp-llm-top-10 |
| OWASP Agentic Top 10 coverage map | penaxtra.com/compliance/owasp-agentic-top-10 |
| Case study: prompt-injection cross-customer data leak | penaxtra.com/case-studies/chatbot-prompt-injection-cross-customer-leak |
If you use pnx-mcp-audit in academic or industry research:
@software{pnx_mcp_audit_2026,
title = {pnx-mcp-audit: A static security auditor for Model Context Protocol servers},
author = {SEZER, Tolga},
year = {2026},
publisher = {Penaxtra},
url = {https://github.com/projectboot/pnx-mcp-audit},
note = {Apache-2.0 licensed}
}Copyright 2026 Penaxtra
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Full text: LICENSE. Attribution requirements: NOTICE.
Built and maintained by Penaxtra -
AI Security Posture Management for regulated teams.
penaxtra.com ·
Contact ·
Founder on LinkedIn