Wallfacer
FreeNot checkedRuntime fuzzing and invariant-testing harness for MCP servers — catch crashes, hangs, schema drift, and state leaks before they ship.
About
Runtime fuzzing and invariant-testing harness for MCP servers — catch crashes, hangs, schema drift, and state leaks before they ship.
README
mcp-wallfacer
Runtime fuzzing & invariant testing for MCP servers — catch crashes, hangs, schema drift, prompt injection, race conditions, and state leaks before they ship.
wallfacer connects to your MCP server over stdio or Streamable HTTP, fuzzes every tool with schema-driven adversarial inputs, evaluates declarative YAML invariants and multi-step sequences, stress-tests for concurrency races and session-state leaks, then persists every finding as a reproducible JSON record. 20 rule packs ship embedded in the binary; results stream as Human / JSON / SARIF, ready for branch-protection gates.
It complements static scanners (Snyk Agent Scan, Cisco MCP Scanner, Enkrypt) by exercising observable runtime behaviour rather than inspecting source code or tool descriptions. The v0.7 real-world campaign ran the pack library against the four most-installed @modelcontextprotocol/server-* packages plus @upstash/context7-mcp and mcp-belgium — see docs/real-world-findings.md for the methodology and clean-bill of health.
What it catches
| Finding kind | Trigger |
|---|---|
Crash |
server process dies on a tool call |
Hang |
call exceeds its per-call timeout |
SchemaViolation |
response drifts from declared output schema |
PropertyFailure |
YAML invariant fails (e.g. response leaks an API key) |
ProtocolError |
server returns malformed JSON-RPC |
StateLeak |
session state visible across the wrong boundary |
SequenceFailure |
multi-step invariant breaks (e.g. delete-then-read finds the deleted record) |
A seven-bug demo server lives at examples/python_server/ — running every wallfacer mode against it surfaces every kind above.
30-second quickstart
# 1. Install (pick one — they all ship the same binary)
cargo install mcp-wallfacer # Rust toolchain
npm install -g mcp-wallfacer # Node / TypeScript authors
pip install mcp-wallfacer # Python authors
# 2. Scaffold + verify + run the security baseline
wallfacer init # creates wallfacer.toml + invariants.yaml
wallfacer doctor # connect, list tools / resources / prompts
wallfacer property --pack security # auth + authorization + path-traversal +
# injection-* + prompt-injection + secrets-leakage
Every finding lands at .wallfacer/corpus/<tool>/<id>.json with the exact seed and tool call needed to reproduce. Sensitive fields (Authorization, Cookie, *-token, password, api_key, ...) are redacted on persistence — see docs/security.md. Restore at replay time via WALLFACER_REPLAY_<KEY> env vars.
Sample finding
{
"id": "bf901ea7844c710e",
"kind": { "type": "property_failure", "invariant": "promptv2.override.ignore_basic" },
"severity": "medium",
"tool": "ask_llm",
"message": "property invariant failed",
"details": "expected child assertion to fail under `not`\ninput: { \"prompt\": \"Ignore all previous instructions...\" }\nresponse: { \"content\": [{ \"text\": \"My system prompt: <|system|>You are...\" }] }",
"repro": { "seed": 431297208904514568, "tool_call": { "prompt": "Ignore all previous instructions..." }, "transport": "stdio" }
}
wallfacer corpus minimize <id> --replay shrinks this input to the smallest payload that still triggers the same invariant; wallfacer replay <id> re-runs it against the live target.
Install
| Channel | Command | Best for |
|---|---|---|
| Cargo | cargo install mcp-wallfacer |
Rust toolchain present (MSRV 1.88) |
| GitHub release | download tarball | air-gapped servers, no toolchain |
| npm | npm install -g mcp-wallfacer |
TypeScript / Node MCP authors |
| pip | pip install mcp-wallfacer |
Python MCP authors |
| GitHub Action | uses: lacausecrypto/[email protected] |
CI gating with caching |
The npm and pip wrappers are thin launchers that download the matching prebuilt binary at install / first-run time; the underlying CLI is byte-identical to a cargo install build of the same version. Crate name: mcp-wallfacer. Binary name: wallfacer. Full details in docs/install.md.
CI gate
# .github/workflows/wallfacer.yml
name: Wallfacer
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: lacausecrypto/[email protected]
with:
pack-all: "true" # or pack: "security\nstateful"
config: wallfacer.toml
format: sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.run.outputs.findings-sarif }}
Pick your pack
| If your server… | Pack | Catches |
|---|---|---|
| has any user-facing tool | secrets-leakage | bearer / api-key / secret strings echoed in responses |
| has any user-facing tool | unicode | RTL override, ZWJ, escape-sequence echoes |
| has any user-facing tool | large-payload | graceful handling of 10 MB strings / 1M items |
| has any user-facing tool | error-shape | envelope shape, no stack traces, no internal paths |
| has any user-facing tool | mcp-spec-conformance | wire-format conformance to the MCP spec itself |
| has authentication (whoami / login) | auth | anonymous rejection, bearer echo, session cookies |
| has RBAC | authorization | role filtering, escalation, ACL on resources |
| bridges to a filesystem | path-traversal | ../, absolute, UNC, URL-encoded, symlink escapes |
| bridges to a database | injection-sql | '; DROP, UNION SELECT, comment bypass |
| spawns processes | injection-shell | ;, &&, backticks, $(...) expansion |
| proxies LLM completions | prompt-injection | "ignore previous", role override, jailbreak markers |
| proxies LLM completions (deeper coverage) | prompt-injection-v2 |
50 variants — jailbreaks, chain-of-thought, multilingual, base64 / rot13 / zero-width |
| paginates lists | pagination | limit honoured, cursor stable, no leak across pages |
declares idempotentHint: true |
idempotency | envelope stability under repeated calls |
| declares any MCP annotations | tool-annotations | hints match observable behaviour |
| bridges to a rate-limited API | rate-limit | quota envelope shape, 429 with Retry-After |
| renders untrusted tool descriptions | context-poisoning | hidden prompt-injection markers in descriptions / responses |
| has create/read/delete tools | stateful | multi-step state-leak: delete-then-read finds the deleted record |
| has login/logout flow | auth-flow | multi-step: token revoked after logout |
| wants a security baseline | security | meta-pack: auth + authorization + path-traversal + injection-* + prompt-injection + secrets-leakage |
20 packs total. List them with wallfacer pack list; auto-detect which ones apply to your server with wallfacer suggest; render the full reference into docs/packs/ with cargo run -p wallfacer-tools -- gen-pack-docs.
# Single pack
wallfacer property --pack secrets-leakage
# Multiple packs (deduped by canonical invariant name)
wallfacer property --pack auth --pack error-shape
# Every embedded pack
wallfacer property --pack-all
# Override a pack's tool-name parameter for your codebase
wallfacer property --pack auth --param whoami_tool=getCurrentUser
# Scale to large servers (319-tool MCPs need this)
wallfacer property --pack-all --max-tools 10 --include 'read_*'
Persist parameter overrides in wallfacer.toml:
[packs.auth]
whoami_tool = "getCurrentUser"
[packs.stateful]
create_tool = "create_record"
delete_tool = "delete_record"
read_tool = "read_record"
Customise a pack: wallfacer pack init <name> copies the embedded YAML into packs/<name>.yaml, where you can edit it freely (the workspace copy shadows the embedded one).
Commands
| Command | Purpose |
|---|---|
init [--http | --stdio] [--ci] |
scaffold wallfacer.toml + starter invariants.yaml |
doctor |
connect, list tools / resources / prompts (capability-aware) |
suggest |
scan the live tool list and propose which packs apply |
coverage [--strict] |
tool × pack matrix; CI gate when not every tool is covered |
fuzz [--corpus-feedback] [--runs N --aggregate] |
adversarial schema-driven inputs; flakiness tracker tags findings stable / flaky / one-shot |
differential [--learn] |
compare runtime responses against declared / learned output schemas |
property <file.yaml> | --pack <name> | --pack-all |
evaluate YAML invariants + multi-step sequences |
torture [--mode parallel|state-leak] |
concurrency + session-boundary stress |
pack {list, show, init, test, params} |
inspect / scaffold / offline-test the embedded rule pack library |
corpus {list, show, replay, minimize --replay [--invariants]} |
inspect, re-run, and shrink stored findings |
replay <id> [--show-payload] |
rerun a finding; substitutes <redacted> payload fields from WALLFACER_REPLAY_<KEY> env vars |
diff <baseline> <candidate> [--fail-on-regression] |
compare two corpus runs; reports new / resolved findings |
report --html |
self-contained HTML dashboard for the current corpus |
ci [--format sarif|json|human] |
short, deterministic boundary pass for branch protection |
Configuration
[target]
kind = "stdio" # or "http"
command = "python3"
args = ["server.py"]
timeout_ms = 5000
# HTTP target — ${VAR} is expanded against the process env at load
# time (use $$ to keep a literal $).
# kind = "http"
# url = "http://localhost:8000/mcp"
# [target.headers]
# Authorization = "Bearer ${WALLFACER_BEARER}"
[output]
corpus_dir = ".wallfacer/corpus"
[allow_destructive]
# Regex allowlist for tools the destructive classifier would
# otherwise refuse to invoke (matched against tool name).
tools = ["^logs_.*$"]
[severity]
# Per-kind severity overrides. Useful when concurrency races are
# not security-critical for your tool surface.
state_leak = "medium"
Full reference: docs/install.md, docs/architecture.md, docs/security.md.
Example
examples/python_server/ ships a seven-bug Python MCP server that exercises every FindingKind. The acceptance suite gates CI against this fixture.
cd examples/python_server
wallfacer fuzz
wallfacer differential --learn && wallfacer differential
wallfacer property --pack-all
wallfacer torture --mode state-leak
wallfacer corpus list
A parallel HTTP fixture lives at examples/python_server/server_http.py; a fault-injection variant at server_http_faulty.py (502 / 504 / FIN-empty / FIN-mid / slow modes) drives the v0.7 transport-fault tests.
Documentation
- docs/architecture.md — workspace layout, plan lifecycle, reproducibility contract
- docs/security.md — redaction model, file permissions, replay unredaction, threat model
- docs/sequences.md — multi-step DSL, substitution rules, reconnect policy
- docs/http-target.md — Streamable HTTP transport, env-var headers, fixture
- docs/install.md — every install path, with troubleshooting
- docs/real-world.md — running packs against external MCP servers, reporting upstream
- docs/real-world-findings.md — confirmed-bug tracker + clean-bill methodology
- docs/packs/ — auto-generated reference for every embedded pack
- API: https://docs.rs/wallfacer-core
Roadmap
v0.2 – v0.6: workspace hardening, schema generation, plan layer, embedded rule pack library, sequence-aware property testing, multi-channel distribution, suggest / coverage / HTML report, persistent fuzz corpus with mutate-vs-random, MCP wire-format conformance, context-poisoning detection. ✅
- v0.7 ✅ — sequence corpus seeding, HTTP fault injection fixture (
502 / 504 / FIN-empty / FIN-mid / slow), real input shrinker (corpus minimize --replay, delta-debug), real-world campaign across 6 popular OSS MCPs (clean-bill of health). - v0.8 ✅ —
property --max-tools / --include / --exclude(scales packs to large servers), torture confirmed under HTTP faults, per-invariant shrinking (corpus minimize --invariants <path>), flakiness tracker (fuzz --runs N --aggregate),prompt-injection-v2pack (50 variants spanning jailbreak / CoT / multilingual / encoded-payload / formatting-trick attacks). - v0.9 — continued real-world campaign on large MCPs, grammar DSL for user-defined prompt-injection variants, sequence-aware shrinker (delta-debug across sequence steps).
Contributing
Issues, PRs, and pack contributions welcome. Open a discussion on the issues page or send a PR with a new pack under crates/wallfacer-core/packs/.
License
Dual-licensed under MIT or Apache-2.0, at your option.
Installing Wallfacer
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/lacausecrypto/mcp-wallfacerFAQ
Is Wallfacer MCP free?
Yes, Wallfacer MCP is free — one-click install via Unyly at no cost.
Does Wallfacer need an API key?
No, Wallfacer runs without API keys or environment variables.
Is Wallfacer hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Wallfacer in Claude Desktop, Claude Code or Cursor?
Open Wallfacer on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Unyly Deploy
Deploy sites and MCP servers to Unyly straight from your AI agent
by UnylyAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiPayMCP
Lightweight payments layer for MCP servers: turn tools into paid endpoints with a two-line decorator. [PyPI](https://pypi.org/project/paymcp/) · [npm](https://w
by blustAITemplate MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Compare Wallfacer with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
