This lab explores a simple idea: use an LLM as a junior analyst, but rely on deterministic tools for the actual evidence.
Instead of asking an AI to "analyze a PCAP," this project exposes a set of network analysis tools via the Model Context Protocol (MCP). Claude Code calls those tools, inspects the structured results, and writes a triage note grounded in real tool output.
The goal is not full automation. It is assisted investigation with verifiable evidence.
For the full writeup and background on why this was built this way, see the blog post.
Claude Code
↓
MCP tools (netparse)
↓
Docker container (network disabled, non-root, read-only mounts)
↓
tshark / parsers
↓
/srv/evidence (read-only)
Key design principles:
- tshark does the deterministic packet parsing
- MCP tools wrap tshark output into structured JSON
- Claude reasons over that JSON and writes the triage note
- The container has no network access and cannot write back to evidence
- Docker and Docker Compose
- Claude Code (with MCP support)
- Evidence directory at
/srv/evidence(or adjust the volume mount indocker-compose.yml)
1. Clone the repository
git clone https://github.com/desvert/ai-soc-mcp-lab
cd ai-soc-mcp-lab2. Build the container
docker compose build3. Create a case directory and drop in a PCAP
mkdir -p /srv/evidence/soc/cases/testcase/raw
cp your-capture.pcap /srv/evidence/soc/cases/testcase/raw/4. Register the MCP server with Claude Code
Add the following to your Claude Code MCP configuration:
{
"mcpServers": {
"netparse": {
"command": "docker",
"args": [
"compose", "-f", "/path/to/ai-soc-mcp-lab/docker-compose.yml",
"run", "--rm", "-i", "netparse"
]
}
}
}5. Run a triage
In Claude Code, ask:
Run pcap_triage_overview on /evidence/soc/cases/testcase and write a triage note.
All case data lives under a single root:
/srv/evidence/soc/cases/<case>/
├── raw/ # Original evidence (PCAP, eve.json) — never modified
├── derived/ # Outputs from tool processing
└── reports/ # Triage notes and analyst reports
The container mounts /srv/evidence read-only. Nothing the model does can modify original evidence.
The netparse server exposes the following tools. All tools return structured JSON.
| Tool | Description |
|---|---|
pcap_triage_overview |
Runs DNS, HTTP, and conversation analysis in a single call. Main entry point for triage. |
pcap_dns_summary |
Top queried DNS names and top A-record responses with counts |
pcap_http_hosts |
Top HTTP Host header values with counts |
pcap_conversations |
TCP conversation summary (tshark conv,tcp output) |
pcap_extract_fields |
Generic tshark field extractor with display filter support |
| Tool | Description |
|---|---|
suricata_alerts |
Parse Suricata eve.json; supports filtering by signature substring and minimum severity |
- All tools accept a
case_dirpath and operate on the first.pcapor.pcapngfound in<case_dir>/raw/. - All paths are validated against the evidence root to prevent traversal.
pcap_extract_fieldsaccepts arbitrary tshark display filters and field names, making it useful for follow-up pivots after an initial triage.
For targeted follow-up after a triage overview, use pcap_extract_fields to pull specific fields:
Extract fields frame.time, ip.src, ip.dst, dns.qry.name, dns.a
from case /evidence/soc/cases/testcase
using display filter: dns.a == "10.90.90.90"
This is how you would, for example, enumerate every domain name that resolved to a suspicious internal IP.
See examples/testcase/triage-note-example.md for a full sample triage note generated from a real PCAP.
That example covers a Windows AD environment with an active RDP scan from an external IP, suspicious DNS patterns, anomalous internal traffic, and five graded hypotheses with suggested investigation steps.
The container is locked down deliberately:
network_mode: "none" # no outbound or inbound network
user: "1000:1000" # non-root
volumes:
- /srv/evidence:/evidence:ro # read-only evidence mountThe model has no path to the host filesystem and cannot make network calls. If a capture file contains embedded prompt injection attempts, the blast radius is limited to the container context.
.
├── docker-compose.yml
├── docker/
│ └── netparse/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── server.py # MCP server (FastMCP + tshark wrappers)
├── examples/
│ └── testcase/
│ └── triage-note-example.md
└── docs/
├── architecture.md
├── triage-workflow.md
└── usage.md
- TLS SNI analysis (
tls.handshake.extensions_server_name) - JA3 fingerprint extraction
- Zeek log ingestion
- Suricata integration with full alert filtering
- OT protocol analysis tools (Modbus, BACnet, DNP3)
- Blog post — background, design decisions, and lessons learned