Skip to main content
Glama

scapy-mcp

Code style: crackerjack Runtime: oneiric Framework: FastMCP uv Python: 3.14+

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

Related MCP server: mcp-pwntools

Configure

Settings layer: defaults → settings/scapy-mcp.yamlsettings/local.yamlSCAPY_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 in components. For orchestrators and curl smoke 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:

  1. Master kill-switch (transmit_enabled: bool, default false). The default refuses every frame — set SCAPY_MCP_TRANSMIT_ENABLED=true to permit emission at all.

  2. 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.

  3. L2 destination allow-flag (transmit_allow_l2: bool, default false). Pure ARP / ND / RAW frames require this flag; L3 packets do not.

  4. Broadcast opt-in (transmit_allow_broadcast: bool, default false). Even after the L2/L3 allow-list, broadcasts refuse unless this is true.

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_stop calls .stop() and capture_read reads .results.

  • sniff(stop_filter=...) — synchronous fallback executed in the module-level worker pool. stop_filter checks a per-session cancel event so capture_stop works; packet_cap and duration_cap are enforced via count and timeout on 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_packetrdpcap/wrpcapdissect_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.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers