scapy-mcp
MCP server wrapping scapy for packet crafting, dissection,
pcap staging, and live BPF capture. Tools operate on a worker that holds a
scapy_mcp.config.settings.ScapySettings instance and respect a four-stage
transmit policy before any frame leaves the host.
Version: 0.1.1 — BSD-3-Clause — Python ≥ 3.14 — alpha status
Install
uv pip install scapy-mcp
scapy-mcp
The console script scapy-mcp resolves to scapy_mcp.__main__:main, which
builds the FastMCP ASGI app and serves it on uvicorn (so the custom
/readyz route is mounted). Set SCAPY_MCP_HTTP_PORT to override the
default port 3056.
For local development:
uv sync --group dev
uv run scapy-mcp
Configure
Settings layer: defaults → settings/scapy-mcp.yaml → settings/local.yaml
→ SCAPY_MCP_* environment variables (pydantic-settings nested delimiter
is __). Nested config (e.g. auth) uses SCAPY_MCP_AUTH__ENABLED=true.
| Setting | Default | Purpose |
|---|---|---|
http_port |
3056 |
MCP HTTP listener port |
tool_profile |
full |
MINIMAL, STANDARD, or FULL profile |
log_level |
INFO |
oneiric LoggingConfig level |
default_iface |
None |
capture iface when caller omits one |
capture_packet_cap |
10000 |
per-session packet ceiling |
capture_duration_cap_seconds |
60 |
per-session wall-clock ceiling |
capture_default_bpf_filter |
None |
global BPF filter applied when caller omits one |
capture_executor_workers |
2 |
ThreadPoolExecutor size for the sync sniff fallback |
pcap_write_dir |
<tmpdir>/scapy_mcp/pcap-staging |
write-side staging root (path-containment enforced) |
pcap_read_max_bytes |
52428800 |
read-side ceiling; read_pcap refuses larger files before parsing |
transmit_enabled |
false |
master kill-switch — must be true to emit any frame |
transmit_allow_l3_cidrs |
[] |
L3 CIDR allow-list |
transmit_allow_l2 |
false |
allow L2 (ARP / ND / RAW) emission |
transmit_allow_broadcast |
false |
allow broadcast destination |
transmit_max_pps |
10 |
per-session packets-per-second cap |
transmit_max_per_session |
100 |
per-session total packet cap |
transmit_max_probe_targets |
16 |
per-call probe-target count cap |
Tools
The server registers 9 domain tools + the mcp-common baseline tools.
| Group | Tool | Purpose |
|---|---|---|
| craft | craft_packet |
Build a Packet from a LayerSpec discriminated union |
| dissect | dissect_bytes |
Parse raw bytes into layers + summary text |
| pcap | read_pcap |
Read packet summaries from a pcap at path, with offset/limit windowing |
| pcap | write_pcap |
Stage a pcap into pcap_write_dir (paths outside the dir refused) |
| capture | capture_start |
Start a BPF-filtered capture (optional feed) |
| capture | capture_stop |
Stop an in-progress capture session |
| capture | capture_read |
Read summaries from an active capture session |
| transmit | transmit_packet |
Send a single packet after every transmit control passes |
| transmit | probe_packet |
One-shot emit at a small BPF, used to confirm the surface works |
| health | health |
Per-feed aggregate status (counts + last error) |
Plus the four mcp-common baseline tools — discover_tools,
get_liveness, get_readiness, health_check_all — registered
unconditionally via bootstrap_baseline_tools.
read_pcap contract
read_pcap accepts an arbitrary path (a file produced by
write_pcap, a pre-existing capture, or a fixture) and returns
summaries. Before parsing, the file size is checked against
pcap_read_max_bytes; oversize files are refused with
ConfigurationError so multi-GB captures cannot exhaust memory by
being loaded whole. Path containment under pcap_write_dir is the
write path's concern, not read's.
Health
Two routes, answering different questions:
/health— always HTTP 200. Reports per-feed detail incomponents. For orchestrators andcurlsmoke probes./readyz— HTTP 503 when a required feed has not yet returned data, 200 otherwise. For readiness probes.
The capture feed is optional: its absence never flips /readyz
to 503. All other registered feeds are required.
Four transmit controls
Every transmit_packet call is checked against:
- Master kill-switch (
transmit_enabled: bool, defaultfalse). The default refuses every frame — setSCAPY_MCP_TRANSMIT_ENABLED=trueto permit emission at all. - L3 CIDR allow-list (
transmit_allow_l3_cidrs: list[str]). Set to["0.0.0.0/0"]for unrestricted L3; production deployments pin the specific CIDRs the worker is allowed to reach. - L2 destination allow-flag (
transmit_allow_l2: bool, defaultfalse). Pure ARP / ND / RAW frames require this flag; L3 packets do not. - Broadcast opt-in (
transmit_allow_broadcast: bool, defaultfalse). Even after the L2/L3 allow-list, broadcasts refuse unless this istrue.
A refusal emits an EmissionRefusedError with the failing control name and a
human-readable reason. The wrapper logs a scapy-write-would-refuse /
scapy-transmit-refused warning so the refusal is visible without polluting
the caller's error stream.
BPF probe (capture)
Capture is OPTIONAL. When /dev/bpf* is missing capture_start refuses
with CapabilityUnavailableError and the capture feed is marked
unavailable. /readyz stays 200 because capture is not a required feed.
Backend selection
The capture worker picks one of two backends at session start:
AsyncSniffer— preferred when scapy exposes it (≥ 2.5). Each session owns a sniffer;capture_stopcalls.stop()andcapture_readreads.results.sniff(stop_filter=...)— synchronous fallback executed in the module-level worker pool.stop_filterchecks a per-session cancel event socapture_stopworks;packet_capandduration_capare enforced viacountandtimeouton the underlying sniff call.
If scapy exposes neither backend, capture_start refuses with
CapabilityUnavailableError (reason = "no sniffer backend").
Deterministic fixtures
Tests do not open raw sockets. All packet construction is exercised
against in-memory fixtures under tests/fixtures/. Each fixture includes a
hand-crafted bytes() body that round-trips through craft_packet →
rdpcap/wrpcap → dissect_bytes. No real network frames in the suite.
Regenerate via:
python -m scripts.gen_pcap_fixtures
Default = closed-by-default transmit
The shipped defaults cannot emit a frame:
transmit_enabled = false
transmit_allow_l3_cidrs = []
transmit_allow_l2 = false
transmit_allow_broadcast = false
A worker installs only what its operator explicitly approves. See
scapy_mcp/config/settings.py for the full settings surface.
License
BSD-3-Clause.
Built on Oneiric for runtime configuration and mcp-common for the FastMCP baseline. Crackerjack gates every commit.
Metadata
Release files for scapy-mcp 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| scapy_mcp-0.1.2.tar.gz | 232.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scapy_mcp-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 263.5 kB
Release files / scapy_mcp-0.1.2.tar.gz
| Download URL | scapy_mcp-0.1.2.tar.gz |
|---|---|
| Size | 232.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a545e60203e22881a49397cb5a1000ead6e8d0f61510cc52e11e845467183df8
|
|
BLAKE2b-256 checksum How to use checksums |
6d3c65d1c6ac113911604a24d61e606b6306e98495d14c930e3323fd070f8c92
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / scapy_mcp-0.1.2-py3-none-any.whl
| Download URL | scapy_mcp-0.1.2-py3-none-any.whl |
|---|---|
| Size | 31.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a793b5cc714cd555f37a8ca8f20f51bebc7100282df400a0306cf9171a013c84
|
|
BLAKE2b-256 checksum How to use checksums |
5296edb631c609a371a806fb3abbd41c4bb263a6a09b3c3ca92466c658249411
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for scapy-mcp 0.1.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.