mcpcap 0.3.2__py3-none-any.whl → 0.4.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcpcap/_version.py +2 -2
- mcpcap/cli.py +6 -7
- mcpcap/core/server.py +21 -6
- mcpcap/modules/__init__.py +2 -1
- mcpcap/modules/dhcp.py +446 -0
- {mcpcap-0.3.2.dist-info → mcpcap-0.4.1.dist-info}/METADATA +47 -14
- mcpcap-0.4.1.dist-info/RECORD +16 -0
- mcpcap-0.3.2.dist-info/RECORD +0 -15
- {mcpcap-0.3.2.dist-info → mcpcap-0.4.1.dist-info}/WHEEL +0 -0
- {mcpcap-0.3.2.dist-info → mcpcap-0.4.1.dist-info}/entry_points.txt +0 -0
- {mcpcap-0.3.2.dist-info → mcpcap-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {mcpcap-0.3.2.dist-info → mcpcap-0.4.1.dist-info}/top_level.txt +0 -0
mcpcap/_version.py
CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
28
28
|
commit_id: COMMIT_ID
|
29
29
|
__commit_id__: COMMIT_ID
|
30
30
|
|
31
|
-
__version__ = version = '0.
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
31
|
+
__version__ = version = '0.4.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 4, 1)
|
33
33
|
|
34
34
|
__commit_id__ = commit_id = None
|
mcpcap/cli.py
CHANGED
@@ -42,11 +42,6 @@ def main():
|
|
42
42
|
help="Comma-separated list of modules to load (default: dns)",
|
43
43
|
default="dns",
|
44
44
|
)
|
45
|
-
parser.add_argument(
|
46
|
-
"--protocols",
|
47
|
-
help="Comma-separated list of protocols to analyze (default: dns)",
|
48
|
-
default="dns",
|
49
|
-
)
|
50
45
|
parser.add_argument(
|
51
46
|
"--max-packets",
|
52
47
|
type=int,
|
@@ -56,12 +51,16 @@ def main():
|
|
56
51
|
args = parser.parse_args()
|
57
52
|
|
58
53
|
try:
|
54
|
+
# Parse modules and automatically set protocols to match
|
55
|
+
modules = args.modules.split(",") if args.modules else ["dns"]
|
56
|
+
protocols = modules # Protocols automatically match loaded modules
|
57
|
+
|
59
58
|
# Initialize configuration
|
60
59
|
config = Config(
|
61
60
|
pcap_path=args.pcap_path,
|
62
61
|
pcap_url=args.pcap_url,
|
63
|
-
modules=
|
64
|
-
protocols=
|
62
|
+
modules=modules,
|
63
|
+
protocols=protocols,
|
65
64
|
max_packets=args.max_packets,
|
66
65
|
)
|
67
66
|
|
mcpcap/core/server.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from fastmcp import FastMCP
|
4
4
|
|
5
|
+
from ..modules.dhcp import DHCPModule
|
5
6
|
from ..modules.dns import DNSModule
|
6
7
|
from .config import Config
|
7
8
|
|
@@ -18,20 +19,34 @@ class MCPServer:
|
|
18
19
|
self.config = config
|
19
20
|
self.mcp = FastMCP("mcpcap")
|
20
21
|
|
21
|
-
# Initialize modules
|
22
|
-
self.
|
22
|
+
# Initialize modules based on configuration
|
23
|
+
self.modules = {}
|
24
|
+
if "dns" in self.config.modules:
|
25
|
+
self.modules["dns"] = DNSModule(config)
|
26
|
+
if "dhcp" in self.config.modules:
|
27
|
+
self.modules["dhcp"] = DHCPModule(config)
|
23
28
|
|
24
29
|
# Register tools
|
25
30
|
self._register_tools()
|
26
31
|
|
27
32
|
# Setup prompts
|
28
|
-
self.
|
33
|
+
for module in self.modules.values():
|
34
|
+
module.setup_prompts(self.mcp)
|
29
35
|
|
30
36
|
def _register_tools(self) -> None:
|
31
37
|
"""Register all available tools with the MCP server."""
|
32
|
-
# Register
|
33
|
-
self.
|
34
|
-
|
38
|
+
# Register tools for each loaded module
|
39
|
+
for module_name, module in self.modules.items():
|
40
|
+
if module_name == "dns":
|
41
|
+
self.mcp.tool(module.list_dns_packets)
|
42
|
+
elif module_name == "dhcp":
|
43
|
+
self.mcp.tool(module.list_dhcp_packets)
|
44
|
+
|
45
|
+
# Register shared list_pcap_files tool (same for all modules)
|
46
|
+
if self.modules:
|
47
|
+
# Use the first available module for listing PCAP files
|
48
|
+
first_module = next(iter(self.modules.values()))
|
49
|
+
self.mcp.tool(first_module.list_pcap_files)
|
35
50
|
|
36
51
|
def run(self) -> None:
|
37
52
|
"""Start the MCP server."""
|
mcpcap/modules/__init__.py
CHANGED
mcpcap/modules/dhcp.py
ADDED
@@ -0,0 +1,446 @@
|
|
1
|
+
"""DHCP analysis module."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
import tempfile
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from fastmcp import FastMCP
|
8
|
+
from scapy.all import BOOTP, DHCP, IP, UDP, rdpcap
|
9
|
+
|
10
|
+
from .base import BaseModule
|
11
|
+
|
12
|
+
|
13
|
+
class DHCPModule(BaseModule):
|
14
|
+
"""Module for analyzing DHCP packets in PCAP files."""
|
15
|
+
|
16
|
+
@property
|
17
|
+
def protocol_name(self) -> str:
|
18
|
+
"""Return the name of the protocol this module analyzes."""
|
19
|
+
return "DHCP"
|
20
|
+
|
21
|
+
def list_dhcp_packets(self, pcap_file: str = "") -> dict[str, Any]:
|
22
|
+
"""
|
23
|
+
Analyze DHCP packets from a PCAP file and return a summary of each packet.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
pcap_file: Path to the PCAP file to analyze. Leave empty for direct URL remotes
|
27
|
+
or when using the first available file in local directories.
|
28
|
+
|
29
|
+
Returns:
|
30
|
+
A structured dictionary containing DHCP packet analysis results
|
31
|
+
"""
|
32
|
+
# Handle remote files
|
33
|
+
if self.config.is_remote:
|
34
|
+
# For direct file URLs, always use the URL file (ignore pcap_file parameter)
|
35
|
+
if self.config.is_direct_file_url:
|
36
|
+
available_files = self.config.list_pcap_files()
|
37
|
+
if not available_files:
|
38
|
+
return {
|
39
|
+
"error": "No PCAP file found at the provided URL",
|
40
|
+
"pcap_url": self.config.pcap_url,
|
41
|
+
}
|
42
|
+
pcap_file = available_files[0] # Use the actual filename from URL
|
43
|
+
elif not pcap_file:
|
44
|
+
# For directory URLs, if no file specified, use the first available
|
45
|
+
available_files = self.config.list_pcap_files()
|
46
|
+
if not available_files:
|
47
|
+
return {
|
48
|
+
"error": "No PCAP files found at the provided URL",
|
49
|
+
"pcap_url": self.config.pcap_url,
|
50
|
+
"available_files": [],
|
51
|
+
}
|
52
|
+
pcap_file = available_files[0]
|
53
|
+
|
54
|
+
# Download remote file to temporary location
|
55
|
+
try:
|
56
|
+
with tempfile.NamedTemporaryFile(
|
57
|
+
suffix=".pcap", delete=False
|
58
|
+
) as tmp_file:
|
59
|
+
temp_path = tmp_file.name
|
60
|
+
|
61
|
+
local_path = self.config.download_pcap_file(pcap_file, temp_path)
|
62
|
+
result = self.analyze_packets(local_path)
|
63
|
+
|
64
|
+
# Clean up temporary file
|
65
|
+
try:
|
66
|
+
os.unlink(local_path)
|
67
|
+
except OSError:
|
68
|
+
pass # Ignore cleanup errors
|
69
|
+
|
70
|
+
return result
|
71
|
+
|
72
|
+
except Exception as e:
|
73
|
+
# List available PCAP files for help
|
74
|
+
available_files = self.config.list_pcap_files()
|
75
|
+
return {
|
76
|
+
"error": f"Failed to download PCAP file '{pcap_file}': {str(e)}",
|
77
|
+
"pcap_url": self.config.pcap_url,
|
78
|
+
"available_files": available_files,
|
79
|
+
}
|
80
|
+
|
81
|
+
else:
|
82
|
+
# Handle local files
|
83
|
+
if not pcap_file:
|
84
|
+
# If no file specified, use the first available file
|
85
|
+
available_files = self.config.list_pcap_files()
|
86
|
+
if not available_files:
|
87
|
+
return {
|
88
|
+
"error": "No PCAP files found in directory",
|
89
|
+
"pcap_directory": self.config.pcap_path,
|
90
|
+
"available_files": [],
|
91
|
+
}
|
92
|
+
pcap_file = available_files[0]
|
93
|
+
|
94
|
+
full_path = self.config.get_pcap_file_path(pcap_file)
|
95
|
+
|
96
|
+
# Check if local file exists
|
97
|
+
if not os.path.exists(full_path):
|
98
|
+
available_files = self.config.list_pcap_files()
|
99
|
+
return {
|
100
|
+
"error": f"PCAP file '{pcap_file}' not found",
|
101
|
+
"file_path": full_path,
|
102
|
+
"available_files": available_files,
|
103
|
+
"pcap_directory": self.config.pcap_path,
|
104
|
+
}
|
105
|
+
|
106
|
+
return self.analyze_packets(full_path)
|
107
|
+
|
108
|
+
def list_pcap_files(self) -> str:
|
109
|
+
"""
|
110
|
+
List all available PCAP files in the configured directory or remote URL.
|
111
|
+
|
112
|
+
Returns:
|
113
|
+
A list of available PCAP files that can be analyzed
|
114
|
+
"""
|
115
|
+
files = self.config.list_pcap_files()
|
116
|
+
source = (
|
117
|
+
self.config.pcap_url if self.config.is_remote else self.config.pcap_path
|
118
|
+
)
|
119
|
+
|
120
|
+
if files:
|
121
|
+
if self.config.is_remote and self.config.is_direct_file_url:
|
122
|
+
return f"Direct PCAP file URL: {source}\\n- {files[0]}"
|
123
|
+
elif not self.config.is_remote and self.config.is_direct_file_path:
|
124
|
+
return f"Direct PCAP file path: {source}\\n- {files[0]}"
|
125
|
+
else:
|
126
|
+
source_type = "remote server" if self.config.is_remote else "directory"
|
127
|
+
return (
|
128
|
+
f"Available PCAP files in {source_type} {source}:\\n"
|
129
|
+
+ "\\n".join(f"- {f}" for f in sorted(files))
|
130
|
+
)
|
131
|
+
else:
|
132
|
+
source_type = "remote server" if self.config.is_remote else "directory"
|
133
|
+
return f"No PCAP files found in {source_type} {source}"
|
134
|
+
|
135
|
+
def analyze_packets(self, pcap_file: str) -> dict[str, Any]:
|
136
|
+
"""Analyze DHCP packets in a PCAP file.
|
137
|
+
|
138
|
+
Args:
|
139
|
+
pcap_file: Path to the PCAP file to analyze
|
140
|
+
|
141
|
+
Returns:
|
142
|
+
Dictionary containing DHCP packet analysis results
|
143
|
+
"""
|
144
|
+
try:
|
145
|
+
packets = rdpcap(pcap_file)
|
146
|
+
dhcp_packets = [pkt for pkt in packets if pkt.haslayer(BOOTP)]
|
147
|
+
|
148
|
+
if not dhcp_packets:
|
149
|
+
return {
|
150
|
+
"file": pcap_file,
|
151
|
+
"total_packets": len(packets),
|
152
|
+
"dhcp_packets_found": 0,
|
153
|
+
"message": "No DHCP packets found in this capture",
|
154
|
+
}
|
155
|
+
|
156
|
+
# Apply max_packets limit if specified
|
157
|
+
packets_to_analyze = dhcp_packets
|
158
|
+
limited = False
|
159
|
+
if self.config.max_packets and len(dhcp_packets) > self.config.max_packets:
|
160
|
+
packets_to_analyze = dhcp_packets[: self.config.max_packets]
|
161
|
+
limited = True
|
162
|
+
|
163
|
+
packet_details = []
|
164
|
+
for i, pkt in enumerate(packets_to_analyze, 1):
|
165
|
+
packet_info = self._analyze_dhcp_packet(pkt, i)
|
166
|
+
packet_details.append(packet_info)
|
167
|
+
|
168
|
+
# Generate statistics
|
169
|
+
stats = self._generate_statistics(packet_details)
|
170
|
+
|
171
|
+
result = {
|
172
|
+
"file": pcap_file,
|
173
|
+
"total_packets": len(packets),
|
174
|
+
"dhcp_packets_found": len(dhcp_packets),
|
175
|
+
"dhcp_packets_analyzed": len(packet_details),
|
176
|
+
"statistics": stats,
|
177
|
+
"packets": packet_details,
|
178
|
+
}
|
179
|
+
|
180
|
+
# Add information about packet limiting
|
181
|
+
if limited:
|
182
|
+
result["note"] = (
|
183
|
+
f"Analysis limited to first {self.config.max_packets} DHCP packets due to --max-packets setting"
|
184
|
+
)
|
185
|
+
|
186
|
+
return result
|
187
|
+
|
188
|
+
except Exception as e:
|
189
|
+
return {
|
190
|
+
"error": f"Error reading PCAP file '{pcap_file}': {str(e)}",
|
191
|
+
"file": pcap_file,
|
192
|
+
}
|
193
|
+
|
194
|
+
def _analyze_dhcp_packet(self, packet, packet_num: int) -> dict[str, Any]:
|
195
|
+
"""Analyze a single DHCP packet and extract relevant information."""
|
196
|
+
info = {
|
197
|
+
"packet_number": packet_num,
|
198
|
+
"timestamp": packet.time,
|
199
|
+
}
|
200
|
+
|
201
|
+
# Basic IP information
|
202
|
+
if packet.haslayer(IP):
|
203
|
+
ip_layer = packet[IP]
|
204
|
+
info.update(
|
205
|
+
{
|
206
|
+
"src_ip": ip_layer.src,
|
207
|
+
"dst_ip": ip_layer.dst,
|
208
|
+
}
|
209
|
+
)
|
210
|
+
|
211
|
+
# UDP information
|
212
|
+
if packet.haslayer(UDP):
|
213
|
+
udp_layer = packet[UDP]
|
214
|
+
info.update(
|
215
|
+
{
|
216
|
+
"src_port": udp_layer.sport,
|
217
|
+
"dst_port": udp_layer.dport,
|
218
|
+
}
|
219
|
+
)
|
220
|
+
|
221
|
+
# BOOTP/DHCP information
|
222
|
+
if packet.haslayer(BOOTP):
|
223
|
+
bootp_layer = packet[BOOTP]
|
224
|
+
info.update(
|
225
|
+
{
|
226
|
+
"op": "Request" if bootp_layer.op == 1 else "Reply",
|
227
|
+
"transaction_id": f"0x{bootp_layer.xid:08x}",
|
228
|
+
"client_ip": str(bootp_layer.ciaddr),
|
229
|
+
"your_ip": str(bootp_layer.yiaddr),
|
230
|
+
"server_ip": str(bootp_layer.siaddr),
|
231
|
+
"relay_ip": str(bootp_layer.giaddr),
|
232
|
+
"client_mac": bootp_layer.chaddr[:6].hex(":"),
|
233
|
+
}
|
234
|
+
)
|
235
|
+
|
236
|
+
# DHCP options analysis
|
237
|
+
if packet.haslayer(DHCP):
|
238
|
+
dhcp_layer = packet[DHCP]
|
239
|
+
dhcp_info = self._parse_dhcp_options(dhcp_layer.options)
|
240
|
+
info.update(dhcp_info)
|
241
|
+
|
242
|
+
return info
|
243
|
+
|
244
|
+
def _parse_dhcp_options(self, options) -> dict[str, Any]:
|
245
|
+
"""Parse DHCP options and return structured information."""
|
246
|
+
dhcp_info = {"options": {}}
|
247
|
+
|
248
|
+
# DHCP message type mappings
|
249
|
+
message_types = {
|
250
|
+
1: "DISCOVER",
|
251
|
+
2: "OFFER",
|
252
|
+
3: "REQUEST",
|
253
|
+
4: "DECLINE",
|
254
|
+
5: "ACK",
|
255
|
+
6: "NAK",
|
256
|
+
7: "RELEASE",
|
257
|
+
8: "INFORM",
|
258
|
+
}
|
259
|
+
|
260
|
+
for opt in options:
|
261
|
+
if isinstance(opt, tuple) and len(opt) == 2:
|
262
|
+
key, value = opt
|
263
|
+
|
264
|
+
if key == "message-type":
|
265
|
+
dhcp_info["message_type"] = message_types.get(
|
266
|
+
value, f"Unknown({value})"
|
267
|
+
)
|
268
|
+
dhcp_info["message_type_code"] = value
|
269
|
+
elif key == "lease_time":
|
270
|
+
dhcp_info["lease_time"] = value
|
271
|
+
dhcp_info["options"]["lease_time"] = f"{value} seconds"
|
272
|
+
elif key == "renewal_time":
|
273
|
+
dhcp_info["renewal_time"] = value
|
274
|
+
dhcp_info["options"]["renewal_time"] = f"{value} seconds"
|
275
|
+
elif key == "rebinding_time":
|
276
|
+
dhcp_info["rebinding_time"] = value
|
277
|
+
dhcp_info["options"]["rebinding_time"] = f"{value} seconds"
|
278
|
+
elif key == "server_id":
|
279
|
+
dhcp_info["server_id"] = str(value)
|
280
|
+
elif key == "subnet_mask":
|
281
|
+
dhcp_info["options"]["subnet_mask"] = str(value)
|
282
|
+
elif key == "router":
|
283
|
+
dhcp_info["options"]["router"] = str(value)
|
284
|
+
elif key == "name_server":
|
285
|
+
dhcp_info["options"]["dns_servers"] = str(value)
|
286
|
+
elif key == "requested_addr":
|
287
|
+
dhcp_info["requested_ip"] = str(value)
|
288
|
+
elif key == "client_id":
|
289
|
+
if isinstance(value, bytes):
|
290
|
+
dhcp_info["client_id"] = value.hex(":")
|
291
|
+
else:
|
292
|
+
dhcp_info["client_id"] = str(value)
|
293
|
+
elif key == "param_req_list":
|
294
|
+
dhcp_info["options"]["parameter_request_list"] = list(value)
|
295
|
+
else:
|
296
|
+
# Store other options as strings
|
297
|
+
dhcp_info["options"][key] = str(value)
|
298
|
+
|
299
|
+
return dhcp_info
|
300
|
+
|
301
|
+
def _generate_statistics(self, packets: list[dict[str, Any]]) -> dict[str, Any]:
|
302
|
+
"""Generate statistics from analyzed DHCP packets."""
|
303
|
+
stats = {
|
304
|
+
"unique_clients": set(),
|
305
|
+
"unique_servers": set(),
|
306
|
+
"message_types": {},
|
307
|
+
"transactions": {},
|
308
|
+
}
|
309
|
+
|
310
|
+
for pkt in packets:
|
311
|
+
# Count unique clients and servers
|
312
|
+
if "client_mac" in pkt:
|
313
|
+
stats["unique_clients"].add(pkt["client_mac"])
|
314
|
+
if "server_id" in pkt:
|
315
|
+
stats["unique_servers"].add(pkt["server_id"])
|
316
|
+
|
317
|
+
# Count message types
|
318
|
+
if "message_type" in pkt:
|
319
|
+
msg_type = pkt["message_type"]
|
320
|
+
stats["message_types"][msg_type] = (
|
321
|
+
stats["message_types"].get(msg_type, 0) + 1
|
322
|
+
)
|
323
|
+
|
324
|
+
# Track transactions
|
325
|
+
if "transaction_id" in pkt:
|
326
|
+
tx_id = pkt["transaction_id"]
|
327
|
+
if tx_id not in stats["transactions"]:
|
328
|
+
stats["transactions"][tx_id] = []
|
329
|
+
stats["transactions"][tx_id].append(
|
330
|
+
{
|
331
|
+
"packet_number": pkt["packet_number"],
|
332
|
+
"message_type": pkt.get("message_type", "Unknown"),
|
333
|
+
"timestamp": pkt["timestamp"],
|
334
|
+
}
|
335
|
+
)
|
336
|
+
|
337
|
+
# Convert sets to counts for JSON serialization
|
338
|
+
return {
|
339
|
+
"unique_clients_count": len(stats["unique_clients"]),
|
340
|
+
"unique_servers_count": len(stats["unique_servers"]),
|
341
|
+
"unique_clients": list(stats["unique_clients"]),
|
342
|
+
"unique_servers": list(stats["unique_servers"]),
|
343
|
+
"message_type_counts": stats["message_types"],
|
344
|
+
"transaction_count": len(stats["transactions"]),
|
345
|
+
"transactions": stats["transactions"],
|
346
|
+
}
|
347
|
+
|
348
|
+
def setup_prompts(self, mcp: FastMCP) -> None:
|
349
|
+
"""Set up DHCP-specific analysis prompts for the MCP server.
|
350
|
+
|
351
|
+
Args:
|
352
|
+
mcp: FastMCP server instance
|
353
|
+
"""
|
354
|
+
|
355
|
+
@mcp.prompt
|
356
|
+
def dhcp_network_analysis():
|
357
|
+
"""Prompt for analyzing DHCP traffic from a network perspective"""
|
358
|
+
return """You are a network administrator analyzing DHCP traffic. Focus your analysis on:
|
359
|
+
|
360
|
+
1. **IP Address Management:**
|
361
|
+
- Track DHCP lease assignments and renewals
|
362
|
+
- Identify IP address pool usage and availability
|
363
|
+
- Look for lease conflicts or duplicate IP assignments
|
364
|
+
- Monitor lease duration patterns and optimization
|
365
|
+
|
366
|
+
2. **Client Behavior Analysis:**
|
367
|
+
- Identify DHCP client types and operating systems
|
368
|
+
- Track client renewal patterns and timing
|
369
|
+
- Look for unusual client behavior (rapid requests, failures)
|
370
|
+
- Monitor mobile devices vs. static clients
|
371
|
+
|
372
|
+
3. **Server Performance:**
|
373
|
+
- Analyze DHCP server response times
|
374
|
+
- Look for server failures or timeouts
|
375
|
+
- Check for multiple DHCP servers (potential conflicts)
|
376
|
+
- Monitor server load and capacity planning
|
377
|
+
|
378
|
+
4. **Network Troubleshooting:**
|
379
|
+
- Identify DHCP DISCOVER floods or storms
|
380
|
+
- Look for relay agent issues
|
381
|
+
- Check for scope exhaustion problems
|
382
|
+
- Find misconfigurations in options delivery
|
383
|
+
|
384
|
+
Provide specific recommendations for network optimization and problem resolution."""
|
385
|
+
|
386
|
+
@mcp.prompt
|
387
|
+
def dhcp_security_analysis():
|
388
|
+
"""Prompt for analyzing DHCP traffic from a security perspective"""
|
389
|
+
return """You are a security analyst examining DHCP traffic for threats. Focus on:
|
390
|
+
|
391
|
+
1. **Rogue DHCP Detection:**
|
392
|
+
- Identify unauthorized DHCP servers on the network
|
393
|
+
- Look for conflicting server responses or options
|
394
|
+
- Check for suspicious server_id values
|
395
|
+
- Monitor for DHCP server impersonation attempts
|
396
|
+
|
397
|
+
2. **DHCP Attacks:**
|
398
|
+
- Detect DHCP starvation attacks (rapid lease consumption)
|
399
|
+
- Look for DHCP spoofing or man-in-the-middle attempts
|
400
|
+
- Identify abnormal request patterns or frequencies
|
401
|
+
- Check for MAC address spoofing in client requests
|
402
|
+
|
403
|
+
3. **Client Anomalies:**
|
404
|
+
- Find clients with suspicious hostnames or identifiers
|
405
|
+
- Look for rapid MAC address changes from single sources
|
406
|
+
- Detect clients requesting unusual or dangerous options
|
407
|
+
- Monitor for clients bypassing expected DHCP flow
|
408
|
+
|
409
|
+
4. **Data Exfiltration Risks:**
|
410
|
+
- Check for unusual data in DHCP option fields
|
411
|
+
- Look for DNS server redirection attempts
|
412
|
+
- Monitor for suspicious router or gateway assignments
|
413
|
+
- Identify potential DNS tunneling via DHCP options
|
414
|
+
|
415
|
+
Provide threat assessment and recommended security controls."""
|
416
|
+
|
417
|
+
@mcp.prompt
|
418
|
+
def dhcp_forensic_investigation():
|
419
|
+
"""Prompt for forensic analysis of DHCP traffic"""
|
420
|
+
return """You are conducting a digital forensics investigation involving DHCP traffic. Approach systematically:
|
421
|
+
|
422
|
+
1. **Timeline Reconstruction:**
|
423
|
+
- Create chronological sequence of DHCP transactions
|
424
|
+
- Map client activity periods and network presence
|
425
|
+
- Correlate DHCP assignments with incident timeframes
|
426
|
+
- Track device mobility through IP address changes
|
427
|
+
|
428
|
+
2. **Device Attribution:**
|
429
|
+
- Link MAC addresses to specific devices or users
|
430
|
+
- Track device behavior patterns across time
|
431
|
+
- Identify device types through DHCP fingerprinting
|
432
|
+
- Document lease history for evidence correlation
|
433
|
+
|
434
|
+
3. **Network Mapping:**
|
435
|
+
- Reconstruct network topology from DHCP data
|
436
|
+
- Identify network segments and VLAN assignments
|
437
|
+
- Map DHCP server infrastructure and relationships
|
438
|
+
- Document network configuration at incident time
|
439
|
+
|
440
|
+
4. **Evidence Collection:**
|
441
|
+
- Extract client identifiers and hostnames
|
442
|
+
- Document IP address assignments for legal proceedings
|
443
|
+
- Preserve transaction timing for event correlation
|
444
|
+
- Collect option data that may contain forensic artifacts
|
445
|
+
|
446
|
+
Present findings with precise timestamps, evidence preservation notes, and clear documentation suitable for legal proceedings."""
|
@@ -1,12 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcpcap
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
4
4
|
Summary: A modular Python MCP Server for analyzing PCAP files
|
5
5
|
Author: mcpcap contributors
|
6
6
|
License: MIT
|
7
|
-
Project-URL: Homepage, https://github.com/
|
8
|
-
Project-URL: Repository, https://github.com/
|
9
|
-
Project-URL: Issues, https://github.com/
|
7
|
+
Project-URL: Homepage, https://github.com/mcpcap/mcpcap
|
8
|
+
Project-URL: Repository, https://github.com/mcpcap/mcpcap
|
9
|
+
Project-URL: Issues, https://github.com/mcpcap/mcpcap/issues
|
10
10
|
Keywords: pcap,network,analysis,mcp,dns
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
12
12
|
Classifier: Intended Audience :: Developers
|
@@ -44,7 +44,7 @@ Dynamic: license-file
|
|
44
44
|
|
45
45
|
# mcpcap
|
46
46
|
|
47
|
-

|
48
48
|
|
49
49
|
A modular Python MCP (Model Context Protocol) Server for analyzing PCAP files. mcpcap enables LLMs to read and analyze network packet captures from local or remote sources, providing structured JSON responses about network traffic.
|
50
50
|
|
@@ -115,6 +115,8 @@ uvx mcpcap
|
|
115
115
|
|
116
116
|
## Modules
|
117
117
|
|
118
|
+
mcpcap supports multiple protocol analysis modules:
|
119
|
+
|
118
120
|
### DNS Module
|
119
121
|
|
120
122
|
The DNS module analyzes Domain Name System packets in PCAP files.
|
@@ -127,11 +129,26 @@ The DNS module analyzes Domain Name System packets in PCAP files.
|
|
127
129
|
- Track query frequency and patterns
|
128
130
|
- Identify DNS servers used
|
129
131
|
|
132
|
+
### DHCP Module
|
133
|
+
|
134
|
+
The DHCP module analyzes Dynamic Host Configuration Protocol packets in PCAP files.
|
135
|
+
|
136
|
+
**Capabilities**:
|
137
|
+
|
138
|
+
- Track DHCP transactions (DISCOVER, OFFER, REQUEST, ACK)
|
139
|
+
- Identify DHCP clients and servers
|
140
|
+
- Monitor IP address assignments and lease information
|
141
|
+
- Analyze DHCP options and configurations
|
142
|
+
- Detect DHCP anomalies and security issues
|
143
|
+
|
130
144
|
**Example Usage**:
|
131
145
|
|
132
|
-
```
|
133
|
-
#
|
134
|
-
|
146
|
+
```bash
|
147
|
+
# Analyze DHCP traffic only
|
148
|
+
mcpcap --pcap-path /path/to/dhcp.pcap --modules dhcp
|
149
|
+
|
150
|
+
# Analyze both DNS and DHCP
|
151
|
+
mcpcap --pcap-path /path/to/mixed.pcap --modules dns,dhcp
|
135
152
|
```
|
136
153
|
|
137
154
|
## Configuration
|
@@ -164,12 +181,23 @@ mcpcap --pcap-url http://example.com/pcaps/
|
|
164
181
|
|
165
182
|
**Module Selection**:
|
166
183
|
```bash
|
184
|
+
# Single module
|
167
185
|
mcpcap --modules dns --pcap-path /path/to/files
|
186
|
+
|
187
|
+
# Multiple modules
|
188
|
+
mcpcap --modules dns,dhcp --pcap-path /path/to/files
|
168
189
|
```
|
169
190
|
|
170
|
-
**Protocol
|
191
|
+
**Protocol Selection** (automatically matches loaded modules):
|
171
192
|
```bash
|
172
|
-
|
193
|
+
# DNS analysis only
|
194
|
+
mcpcap --modules dns --pcap-path /path/to/files
|
195
|
+
|
196
|
+
# DHCP analysis only
|
197
|
+
mcpcap --modules dhcp --pcap-path /path/to/files
|
198
|
+
|
199
|
+
# Both DNS and DHCP analysis
|
200
|
+
mcpcap --modules dns,dhcp --pcap-path /path/to/files
|
173
201
|
```
|
174
202
|
|
175
203
|
**Packet Limiting** (for large files):
|
@@ -179,7 +207,7 @@ mcpcap --max-packets 1000 --pcap-path /path/to/files
|
|
179
207
|
|
180
208
|
**Combined Options**:
|
181
209
|
```bash
|
182
|
-
mcpcap --pcap-path /data/capture.pcap --max-packets 500 --
|
210
|
+
mcpcap --pcap-path /data/capture.pcap --max-packets 500 --modules dns,dhcp
|
183
211
|
```
|
184
212
|
|
185
213
|
## CLI Reference
|
@@ -194,7 +222,8 @@ mcpcap [--pcap-path PATH | --pcap-url URL] [OPTIONS]
|
|
194
222
|
|
195
223
|
**Analysis Options**:
|
196
224
|
- `--modules MODULES`: Comma-separated modules to load (default: dns)
|
197
|
-
-
|
225
|
+
- Available modules: `dns`, `dhcp`
|
226
|
+
- Protocols are automatically set to match loaded modules
|
198
227
|
- `--max-packets N`: Maximum packets to analyze per file (default: unlimited)
|
199
228
|
|
200
229
|
**Examples**:
|
@@ -205,8 +234,8 @@ mcpcap --pcap-path ./capture.pcap
|
|
205
234
|
# Remote file with packet limit
|
206
235
|
mcpcap --pcap-url https://example.com/dns.cap --max-packets 100
|
207
236
|
|
208
|
-
# Directory with
|
209
|
-
mcpcap --pcap-path /captures --
|
237
|
+
# Directory with DHCP analysis
|
238
|
+
mcpcap --pcap-path /captures --modules dhcp
|
210
239
|
```
|
211
240
|
|
212
241
|
## Example
|
@@ -284,6 +313,10 @@ MIT
|
|
284
313
|
- fastmcp (MCP server framework)
|
285
314
|
- All dependencies are automatically installed via pip
|
286
315
|
|
316
|
+
## Documentation
|
317
|
+
|
318
|
+
Full documentation is available at [docs.mcpcap.ai](https://docs.mcpcap.ai)
|
319
|
+
|
287
320
|
## Support
|
288
321
|
|
289
322
|
For questions, issues, or feature requests, please open an issue on GitHub.
|
@@ -0,0 +1,16 @@
|
|
1
|
+
mcpcap/__init__.py,sha256=rJwCpBXkhIvmsqHFpeR33Vg8kuipNPJ2JdlAjsTk7I4,1408
|
2
|
+
mcpcap/_version.py,sha256=k7cu0JKra64gmMNU_UfA5sw2eNc_GRvf3QmesiYAy8g,704
|
3
|
+
mcpcap/cli.py,sha256=nsHg-c72BrJaIUiVPoj-JWahQu8k0ldxYmWzbnljEBk,2448
|
4
|
+
mcpcap/core/__init__.py,sha256=WM5GTl06ZwwqHTPiKaYB-9hwOOXe3hyHG16FshwSsjE,127
|
5
|
+
mcpcap/core/config.py,sha256=WdHYu14Cvn9C3xs3KsQ-SVRru00IH86nQfnDL57V9zE,8190
|
6
|
+
mcpcap/core/server.py,sha256=H2ttfokMzW3vICxjCOFCoQTe78HHs-bGUxMd6i7rE1k,2011
|
7
|
+
mcpcap/modules/__init__.py,sha256=kA91h_-f7RE7pvEgcsQTZigDptK5v17-mbqTXZTRTK8,183
|
8
|
+
mcpcap/modules/base.py,sha256=3h8lGt6d6ob4SbgP6THC5PnTeMRcKfTGoJ9ZlZsQje0,826
|
9
|
+
mcpcap/modules/dhcp.py,sha256=3t9_2Tci5ZKhJlSMfgFbgCg44l5-0FKefAWoDZ_sY-Y,17251
|
10
|
+
mcpcap/modules/dns.py,sha256=cc77RxJOf-JxTLTCY8kfc_64uMawWKB3rjme9Q5H1pI,16632
|
11
|
+
mcpcap-0.4.1.dist-info/licenses/LICENSE,sha256=Ltj0zxftQyBYQMNva935v0i5QXQQOF8ygE8dQxGEtjk,1063
|
12
|
+
mcpcap-0.4.1.dist-info/METADATA,sha256=db8XtdJfxCA9rMnhx6Vs9ogy-4TxN7N40kie3l1HbUo,8709
|
13
|
+
mcpcap-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
mcpcap-0.4.1.dist-info/entry_points.txt,sha256=ck69gPBEopmU6mzQy9P6o6ssMr89bQbrvv51IaJ50Gc,39
|
15
|
+
mcpcap-0.4.1.dist-info/top_level.txt,sha256=YkRkVGjuM3nI7cVB1l8zIAeqiS_5_vrzbUcHNkH3OXE,7
|
16
|
+
mcpcap-0.4.1.dist-info/RECORD,,
|
mcpcap-0.3.2.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
mcpcap/__init__.py,sha256=rJwCpBXkhIvmsqHFpeR33Vg8kuipNPJ2JdlAjsTk7I4,1408
|
2
|
-
mcpcap/_version.py,sha256=e8NqPtZ8fggRgk3GPrqZ_U_BDV8aSULw1u_Gn9NNbnk,704
|
3
|
-
mcpcap/cli.py,sha256=DflEb7i2ATn9lBx4rK43Qpf-aQjKhwSqT20BofzVrFs,2480
|
4
|
-
mcpcap/core/__init__.py,sha256=WM5GTl06ZwwqHTPiKaYB-9hwOOXe3hyHG16FshwSsjE,127
|
5
|
-
mcpcap/core/config.py,sha256=WdHYu14Cvn9C3xs3KsQ-SVRru00IH86nQfnDL57V9zE,8190
|
6
|
-
mcpcap/core/server.py,sha256=BrLgT-zsa2uFQ2B_bNSSrCFID3xXruinnnfRrHo1GKs,1312
|
7
|
-
mcpcap/modules/__init__.py,sha256=iIeoZuLA-EOv0OS8WU8qDCitXJnarq9F0hA5-Y97zis,140
|
8
|
-
mcpcap/modules/base.py,sha256=3h8lGt6d6ob4SbgP6THC5PnTeMRcKfTGoJ9ZlZsQje0,826
|
9
|
-
mcpcap/modules/dns.py,sha256=cc77RxJOf-JxTLTCY8kfc_64uMawWKB3rjme9Q5H1pI,16632
|
10
|
-
mcpcap-0.3.2.dist-info/licenses/LICENSE,sha256=Ltj0zxftQyBYQMNva935v0i5QXQQOF8ygE8dQxGEtjk,1063
|
11
|
-
mcpcap-0.3.2.dist-info/METADATA,sha256=HeumLFw2sguQyc3FFkP9cGr6ne8Rg513YuVEWziYxJs,7854
|
12
|
-
mcpcap-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
-
mcpcap-0.3.2.dist-info/entry_points.txt,sha256=ck69gPBEopmU6mzQy9P6o6ssMr89bQbrvv51IaJ50Gc,39
|
14
|
-
mcpcap-0.3.2.dist-info/top_level.txt,sha256=YkRkVGjuM3nI7cVB1l8zIAeqiS_5_vrzbUcHNkH3OXE,7
|
15
|
-
mcpcap-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|