Kali Server
БесплатноНе проверенA Docker-based MCP server exposing a curated set of Kali Linux security tools for authorized, hands-on network scanning on private ranges, with enforced trustwo
Описание
A Docker-based MCP server exposing a curated set of Kali Linux security tools for authorized, hands-on network scanning on private ranges, with enforced trustworthiness and honest output.
README
A Docker-based Model Context Protocol (MCP) server that
exposes a curated set of Kali Linux security tools to an MCP client (Claude Desktop / Claude
Code) over stdio. It is built on kalilinux/kali-rolling, written in Python with
FastMCP, and runs as a non-root user with only the
specific Linux capabilities the offensive tools need. It exists for one purpose: authorized,
hands-on security testing of the operator's own home network and lab devices. Targets are
restricted in code to private ranges (192.168.x.x, 10.x.x.x, 172.16–31.x.x) — that permits the
full toolset against every device you own, and blocks pointing a tool at anything that isn't
yours. Nothing else.
The contract: no BS, no hallucination
This server's whole identity is trustworthy output. Mark makes real security decisions based on what these tools report, and a fabricated result is worse than no result — it can send you chasing a vulnerability that doesn't exist, or quietly reassure you about one that does. So every wrapper is built to the same rules:
- Real output or a real failure — never a fabrication. A tool reports its actual stdout/stderr and the exact command that ran, or it says plainly that it didn't run.
- "Errored" is not "found nothing." A tool that times out, isn't installed, or hits a permission error is a different fact from a tool that ran cleanly and found nothing. The two are never conflated.
- Verdicts are earned, not defaulted. The rogue-host watcher's "all clear" only happens when devices were actually seen and every one matched — an empty or failed scan is reported as exactly that, not as a clean bill of health.
- No invented specifics. IPs, MACs, ports, versions, CVEs — if there's no real result, the honest answer is "couldn't determine."
The authoritative statement of these rules — and of the locked architecture/scope decisions — is CLAUDE.md. That file is the source of truth for this project; this README is the front door.
Tools implemented today
These are the tools the server actually wraps and registers right now (verified in kali_mcp/registry.py). Each one validates its input, runs the real binary via an argument list (never a shell), captures real stdout/stderr, and surfaces a non-zero exit as a clear failure:
| Tool | What it does | Safety model |
|---|---|---|
list_tools |
Reports every tool in the roster with its real install status (a live shutil.which check, never hardcoded). |
Read-only; no target. |
nmap_scan |
Port / service / version discovery. scan_type is an allow-list: ping, quick, connect, syn, version, default. |
Scope-gated (private-range only); scan_type/ports are validated keys, not arbitrary flags. |
masscan_scan |
High-speed asynchronous port sweep. | Scope-gated + rate-limited (max_rate default 1000 pps, ceiling 100000). |
tshark_capture |
Bounded packet capture & protocol/talker summary; can also read an offline .pcap. |
Bounded (mandatory duration_s ≤ 300 and/or packet_count ≤ 100000) + interface/BPF shape validation. |
arp_scan |
Layer-2 host discovery on the local segment → {ip, mac, vendor} per responder. |
No scope gate needed (ARP is non-routable, self-limiting to the segment); interface shape-validated, explicit ranges must be private CIDRs. |
arp_watch |
Runs arp_scan and diffs the result against your device whitelist, flagging unlisted devices. Persists its last result to state/last_watch.json so the dashboard can read it. |
Whitelist-diffed; refuses to give a verdict if the whitelist can't load. |
network_status |
Composes the whole platform's state into one honest snapshot: each roster tool's real install status, audit-log tallies, the whitelist's load state, and the last arp_watch result (read from persisted state — it does not scan). |
Read-only; no target. Never fabricates a verdict — "no scan data", "all clear", and "whitelist broken" stay distinct (no data ≠ all clear). |
generate_dashboard |
Renders network_status into a self-contained static HTML dashboard file — no server, no port, opened straight from disk. A point-in-time snapshot stamped generated_at. |
Read-only + writes one HTML file (default state/dashboard.html, gitignored). Writes nothing if the snapshot fails — never a stale/blank file. |
Roster vs. reality. CLAUDE.md §5 lists a much larger offensive + defensive roster (nikto, nuclei, sqlmap, hydra, metasploit, suricata, zeek, …). That roster is the build target, not a claim of completion. Of the eight tools above, six wrap a roster binary and two (
network_status,generate_dashboard) are platform tools that compose the others.list_toolsreports each roster binary's real install status — and everything outside the six wrapped binaries currently reportsinstalled: false, honestly, because it hasn't been wrapped or installed yet.
Security architecture
- Non-root by design. The container runs as the unprivileged
pentesteruser — never root, never--privileged. - Capabilities, not root. Raw-socket tools (nmap
-sS, masscan, arp-scan, tshark's dumpcap) get exactlyCAP_NET_RAW+CAP_NET_ADMINvia the composecap_addbounding set plus a reusablesetcap …+eipstep in the Dockerfile. This is the Phase-2 caps pattern: a tool that ships without file-caps gets them added to one explicit list — never a blanket grant. (nmapalready ships its own caps and rides the bounding set for free.) - Scope gate. kali_mcp/scope.py validates every routable target
against private ranges (loopback + RFC1918 + ULA allowed; CGNAT, global, and link-local
denied). It is enforced in both network modes — host networking gives more reach, never a
weaker gate (8.8.8.8 stays refused under
--network host). - Append-only audit log. Every command run is logged as JSONL (tool, argv, target, exit code,
duration, timestamp) to
logs/audit.jsonl(override withKALI_MCP_AUDIT_LOG). No code path bypasses it. A failed audit write (disk full, read-only mount, permission) never discards the real result or masquerades as a tool crash — the command's output is returned intact, the failure is surfaced on the result (audit_error) and shouted to stderr, so a run that couldn't be logged is loud rather than silent. - DNS resolution off the event loop. Scope validation of a hostname target does a blocking
getaddrinfo; like the tool subprocess itself, it runs in a worker thread so a slow/hung resolver can't stall the MCP server. - Input validation, no shell injection. Inputs are Pydantic-validated and commands are built
as argument lists — no
shell=Truewith interpolated input.
Honest caveat — VPN killswitch vs. layer. On a host with an active Mullvad VPN killswitch (policy-routing
fwmarkrule), kernel-socket tools (nmap-sT/-sS, curl) read LAN TCP ports as filtered/down, while layer-2 / raw tools (masscan via AF_PACKET, arp-scan via ARP) still see the true LAN state — because they bypass the IP-socket policy routing. So on such a host the L2 tools reflect reality and the socket-based modes can disagree. That's the VPN, not a bug; keep it in mind when nmap and masscan/arp-scan diverge.
Network modes
Same image, same caps, same stdio — only the container's network stack differs (full reasoning in NETWORKING.md):
| Compose service | Network | Use it for |
|---|---|---|
kali-mcp |
bridge (default, isolated) | loopback / self-tests. Start here. On bridge, host-discovery to LAN hosts is filtered, so your own router reads "down". |
kali-mcp-lan |
--network host (opt-in, profile lan) |
real LAN scans — your actual devices become reachable. A deliberate opt-in because it drops the bridge isolation layer. |
Quick start
1. Build the image
cd ~/building-mcps
docker compose build # tags kali-mcp:phase1
docker images | grep kali-mcp # confirm it exists
2. Connect an MCP client
The server speaks MCP over the container's stdio (no network port is ever opened). The client launches the container per session. Full instructions — Claude Desktop and Claude Code, both network modes — are in CONNECTING.md; copy from mcp-client-config.example.json. The short version:
# Claude Code, bridge (default):
claude mcp add-json kali-mcp '{"command":"docker","args":["run","-i","--rm","--cap-add","NET_RAW","--cap-add","NET_ADMIN","kali-mcp:phase1","python","server.py"]}'
# Claude Code, real-LAN (opt-in):
claude mcp add-json kali-mcp-lan '{"command":"docker","args":["run","-i","--rm","--network","host","--cap-add","NET_RAW","--cap-add","NET_ADMIN","kali-mcp:phase1","python","server.py"]}'
3. Set up your device whitelist (for arp_watch)
cp whitelist.example.yaml whitelist.yaml # whitelist.yaml is gitignored — your real inventory
$EDITOR whitelist.yaml # add your devices: mac (required) + name; ip/note optional
4. Run a first scan
Ask the client (in real-LAN mode) to run list_tools, then e.g. an arp_scan on your interface,
then arp_watch to diff it against your whitelist.
The whitelist & rogue-host watcher
arp_watch is the platform's headline feature: it answers "is there anything on my network I
don't recognize?"
Copy
whitelist.example.yaml→whitelist.yaml(gitignored, so your real MACs are never committed) and list your known devices. Amacis accepted in any common form (colon, hyphen, Cisco-dot, any case) and canonicalized internally, so format differences never cause a false alarm.nameis required;ipandnoteare optional.Run
arp_watchon your interface. It loads the whitelist, runs a realarp_scan, and classifies every discovered host into exactly one bucket, plus computes what's missing:Verdict Meaning KNOWN MAC is in the whitelist (and, if an IP was specified, it matches). ROGUE MAC is not in the whitelist — the headline alert; reported with ip/mac/vendor so you can hunt it. IP_MISMATCH Known MAC, but on a different IP than expected — reported neutrally (could be DHCP, could be spoofing; stated as a fact, not an accusation). ABSENT A whitelisted device that didn't answer this scan — neutral (off/asleep/away). A broken whitelist produces no verdict. If the whitelist is missing or malformed,
arp_watchrefuses and surfaces the load error instead of scanning and calling everything a rogue — a false alarm on a security tool is itself a failure. Likewise, if the underlyingarp_scanerrors or finds nothing, that real status is propagated — never repackaged as a fake "all clear."
The whitelist loader lives in kali_mcp/whitelist.py; the pure diff in kali_mcp/watch.py.
The dashboard (Phase 4)
generate_dashboard turns the platform's real state into a self-contained static HTML file —
no web server, no open port, no external fonts or CDN. You open it straight from disk (file://).
It is a point-in-time snapshot, stamped with generated_at, not a live view — and it says so.
The full loop
The dashboard never scans on its own. It reads state that arp_watch produced, so the data flow
is explicit and each stage is independently honest:
arp_watch network_status generate_dashboard
│ runs a REAL │ reads the persisted │ inlines that snapshot into a
│ arp-scan + diff │ last_watch + probes │ self-contained HTML file
▼ ▼ tools/audit/whitelist ▼
state/last_watch.json ──► one honest snapshot ──────► state/dashboard.html ──► open in a browser
arp_watchruns a realarp_scanand diffs it against your whitelist, then persists the result tostate/last_watch.json(gitignored — it holds real device MAC/IP).network_statusreads that persisted result (it does not run a scan), and probes the live install status, audit log, and whitelist, returning one structured snapshot.generate_dashboardembeds that snapshot into the dashboard template and writesstate/dashboard.html— self-contained, open it anywhere.
Honest states (this is the project's identity, in the UI)
The dashboard's whole reason to exist is that the three things a lazy dashboard collapses into one green light stay visually distinct — a green "all clear" is shown only when it's genuinely earned, never because data failed to load or is old:
| State | What the dashboard shows |
|---|---|
| Fresh + all clear | Green "ALL CLEAR — every device known" — only when devices were seen and all matched. |
| Rogues present | A loud red alert with the rogue count and each rogue's ip/mac/vendor — the loudest thing on the page. |
| Stale | An amber "⏱ DATA IS N OLD — re-run arp_watch" banner above the panel. A days-old all-clear is not a current all-clear. |
| No data | A neutral grey/blue "ⓘ NO SCAN DATA — run arp_watch" panel — deliberately not green, never a pass. |
| Whitelist broken | An amber "⚠ WHITELIST ERROR" with the load error — not green, not a rogue count. |
| Generation failed | generate_dashboard writes nothing and returns an error rather than a misleading file. |
Two timestamps are shown and never blurred: generated_at (when the snapshot was built) in the
header, and the network panel's own as_of (when arp_watch actually scanned). A fresh
snapshot can still carry an old scan — staleness is measured on as_of (default threshold 1 hour).
Accessibility (a requirement, not a nicety)
The dark-terminal theme is built for legibility: near-black background with bright (~17:1, past
WCAG AAA) foreground, large monospace text (≥19px), and status is never signalled by colour
alone — every state carries a symbol + text label (✓ KNOWN, ⚠ ROGUE, ≠ IP MISMATCH,
○ ABSENT), so it reads without colour perception.
Generate one
# 1. whitelist in place (see above), then run arp_watch in the real-LAN container so it persists state:
docker compose --profile lan run --rm -v "$PWD":/app kali-mcp-lan \
python -c "import asyncio; from kali_mcp.tools.arpwatch import watch; \
print(asyncio.run(watch(interface='wlan0'))['verdict'])"
# 2. build the dashboard from that real state:
docker compose --profile lan run --rm -v "$PWD":/app kali-mcp-lan \
python -c "from kali_mcp.dashboard import generate_dashboard as g; print(g()['path'])"
# 3. open it (it's self-contained — no server):
xdg-open state/dashboard.html
In normal use you'd drive steps 1–2 by asking your MCP client to run the arp_watch and
generate_dashboard tools; the commands above are the equivalent direct invocations. The
dashboard template + mock-state viewer are documented in
dashboard/README.md.
Desktop control panel
A native PySide6 GUI (desktop/) is the operator-facing companion to
the stdio MCP tools. It displays the honest platform state (the same rogue / all-clear /
no-data / stale / whitelist-broken distinctions the dashboard enforces) and can trigger
arp_watch on a chosen interface — through the same audited, scope-gated wrapper the MCP layer
uses, run in the container, so scope validation + the audit log apply to a GUI-triggered scan
exactly as to an MCP one. It's a native window — no port, no server (that's why a desktop app
was chosen over a web UI: it keeps the §4 no-open-port rule intact). The logic lives in a Qt-free,
fully-tested backend.py; the Qt layer is a thin view.
sudo apt-get install -y python3-pyside6.qtcore python3-pyside6.qtgui python3-pyside6.qtwidgets
python3 -m desktop.app
Project status
Built one scoped task at a time; each commit on the branch is one task. Where things stand:
- Phase 1 — core + first wrappers (done): the faithful executor (
run_tool) + audit logging, the private-range scope gate, honestlist_tools, and the first two tool archetypes —nmap_scan(active) andtshark_capture(passive/bounded). - Phase 2 — capabilities & networking (done): the reusable non-root
setcapraw-socket pattern, the bridge-vs-host network modes, and two more wrappers —masscan_scanandarp_scan. - Phase 3 — drivable + rogue-host watcher (done): real MCP-client connection config
(CONNECTING.md), the validated device whitelist store, and the
arp_watchrogue-host watcher. - Phase 4 — the dashboard (done): the
network_statushonest snapshot contract, the high-contrast accessible dashboard template, andgenerate_dashboard— a self-contained static HTML view of real platform state, with the fresh / stale / no-data / whitelist-broken / generation-failure states all kept visually distinct.
Deliberately not done yet (so this README doesn't imply more than exists):
- Most of the CLAUDE.md §5 roster is unwrapped and uninstalled (nikto, nuclei, gobuster, sqlmap, hydra, john/hashcat, metasploit, enum4linux, the NIDS suite, etc.).
nmap_scanexposes a fixed scan-type allow-list only — no-Pn(host-discovery skip) and no UDP (-sU) scanning yet.tshark_capture's BPF filter is a conservative allow-list (letters/digits/spaces and. : / -); brackets, arithmetic, and byte-offset filters are rejected, trading expressiveness for safety. Widening it is a future decision.- GPU cracking, monitor-mode wireless, and session tools (responder/bettercap) need hardware passthrough and are not wrapped.
Tests
python -m pytest -q # 133 tests, all green
The suite is fully offline: run_tool is monkeypatched with canned ToolResults built from
real sample tool output, so no live scanning or capture happens during tests. Every wrapper
has both happy-path and failure-path coverage (tool missing, bad input, timeout, permission
error) — failure handling is treated as a feature, not an afterthought.
Layout
.
├── CLAUDE.md # source of truth: rules, scope, locked decisions
├── README.md # this file
├── CONNECTING.md # connect an MCP client (Desktop / Code)
├── NETWORKING.md # bridge vs --network host, and why
├── Dockerfile # kali-rolling image; non-root pentester + setcap caps
├── docker-compose.yml # kali-mcp (bridge) + kali-mcp-lan (host, profile lan)
├── mcp-client-config.example.json
├── requirements.txt # fastmcp, pyyaml, pytest
├── pyproject.toml # pytest config (pythonpath, testpaths)
├── server.py # FastMCP entry point (stdio); wires the roster
├── whitelist.example.yaml # placeholder whitelist (committed)
├── whitelist.yaml # your real device inventory (gitignored)
├── dashboard/
│ ├── template.html # self-contained dark-terminal dashboard + render()
│ ├── mock_snapshots.js # fixture states for the offline mock viewer
│ └── README.md # dashboard shell + live-generation notes
├── desktop/ # native PySide6 control panel (display + trigger arp_watch)
│ ├── backend.py # Qt-free, tested core: snapshot + view-model + scan runner
│ ├── app.py # thin PySide6 view
│ └── README.md # run + architecture + accessibility notes
├── kali_mcp/
│ ├── executor.py # run_tool — the single faithful executor
│ ├── audit.py # append-only JSONL audit log
│ ├── scope.py # private-range target validator
│ ├── registry.py # the tool ROSTER + register_all wiring
│ ├── whitelist.py # device whitelist store + normalize_mac
│ ├── watch.py # pure rogue-host diff (KNOWN/ROGUE/IP_MISMATCH/ABSENT)
│ ├── state.py # persist/read the last arp_watch result (state/last_watch.json)
│ ├── status.py # build_status — the honest whole-platform snapshot
│ ├── dashboard.py # generate_dashboard core (snapshot -> self-contained HTML)
│ └── tools/
│ ├── meta.py # list_tools
│ ├── nmap.py # nmap_scan
│ ├── masscan.py # masscan_scan
│ ├── tshark.py # tshark_capture
│ ├── arpscan.py # arp_scan
│ ├── arpwatch.py # arp_watch
│ ├── status_tool.py # network_status
│ └── dashboard_tool.py # generate_dashboard
├── tests/ # offline unit tests (run_tool monkeypatched)
├── state/ # last_watch.json + generated dashboard.html (gitignored)
└── logs/audit.jsonl # runtime audit log (gitignored)
Установка Kali Server
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/t1964n/building-mcpsFAQ
Kali Server MCP бесплатный?
Да, Kali Server MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Kali Server?
Нет, Kali Server работает без API-ключей и переменных окружения.
Kali Server — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Kali Server в Claude Desktop, Claude Code или Cursor?
Открой Kali Server на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Kali Server with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
