mcpcap 0.2.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/__init__.py +38 -0
- mcpcap/_version.py +34 -0
- mcpcap/cli.py +54 -0
- mcpcap/core/__init__.py +6 -0
- mcpcap/core/config.py +52 -0
- mcpcap/core/server.py +41 -0
- mcpcap/modules/__init__.py +6 -0
- mcpcap/modules/base.py +36 -0
- mcpcap/modules/dns.py +349 -0
- mcpcap/resources/__init__.py +5 -0
- mcpcap/resources/references.py +90 -0
- mcpcap-0.2.1.dist-info/METADATA +198 -0
- mcpcap-0.2.1.dist-info/RECORD +17 -0
- mcpcap-0.2.1.dist-info/WHEEL +5 -0
- mcpcap-0.2.1.dist-info/entry_points.txt +2 -0
- mcpcap-0.2.1.dist-info/licenses/LICENSE +21 -0
- mcpcap-0.2.1.dist-info/top_level.txt +1 -0
mcpcap/__init__.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
"""mcpcap - A modular Python MCP Server for analyzing PCAP files.
|
2
|
+
|
3
|
+
mcpcap provides a comprehensive solution for analyzing network packet captures (PCAP files)
|
4
|
+
through the Model Context Protocol (MCP). It enables LLMs to perform network traffic analysis
|
5
|
+
with support for DNS protocol analysis and extensible architecture for additional protocols.
|
6
|
+
|
7
|
+
Key Features:
|
8
|
+
- Modular architecture for easy protocol extension
|
9
|
+
- Robust DNS packet analysis with error handling
|
10
|
+
- MCP integration for seamless LLM interaction
|
11
|
+
- Security-focused analysis prompts and indicators
|
12
|
+
- Support for both .pcap and .pcapng file formats
|
13
|
+
|
14
|
+
Example:
|
15
|
+
Start the MCP server with a directory containing PCAP files::
|
16
|
+
|
17
|
+
$ mcpcap --pcap-path /path/to/pcap/files
|
18
|
+
|
19
|
+
Then connect with an MCP client to analyze DNS traffic.
|
20
|
+
"""
|
21
|
+
|
22
|
+
# Dynamic version detection
|
23
|
+
try:
|
24
|
+
# First try to import from _version.py (created by setuptools-scm in built packages)
|
25
|
+
from ._version import version as __version__
|
26
|
+
except ImportError:
|
27
|
+
try:
|
28
|
+
# Fall back to setuptools_scm for development environments
|
29
|
+
from setuptools_scm import get_version
|
30
|
+
|
31
|
+
__version__ = get_version(root="..", relative_to=__file__)
|
32
|
+
except (ImportError, LookupError):
|
33
|
+
# Final fallback for cases where setuptools_scm isn't available
|
34
|
+
__version__ = "dev-unknown"
|
35
|
+
|
36
|
+
from .cli import main
|
37
|
+
|
38
|
+
__all__ = ["main", "__version__"]
|
mcpcap/_version.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# file generated by setuptools-scm
|
2
|
+
# don't change, don't track in version control
|
3
|
+
|
4
|
+
__all__ = [
|
5
|
+
"__version__",
|
6
|
+
"__version_tuple__",
|
7
|
+
"version",
|
8
|
+
"version_tuple",
|
9
|
+
"__commit_id__",
|
10
|
+
"commit_id",
|
11
|
+
]
|
12
|
+
|
13
|
+
TYPE_CHECKING = False
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from typing import Tuple
|
16
|
+
from typing import Union
|
17
|
+
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
19
|
+
COMMIT_ID = Union[str, None]
|
20
|
+
else:
|
21
|
+
VERSION_TUPLE = object
|
22
|
+
COMMIT_ID = object
|
23
|
+
|
24
|
+
version: str
|
25
|
+
__version__: str
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
27
|
+
version_tuple: VERSION_TUPLE
|
28
|
+
commit_id: COMMIT_ID
|
29
|
+
__commit_id__: COMMIT_ID
|
30
|
+
|
31
|
+
__version__ = version = '0.2.1'
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 1)
|
33
|
+
|
34
|
+
__commit_id__ = commit_id = None
|
mcpcap/cli.py
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
"""CLI entry point for mcpcap.
|
2
|
+
|
3
|
+
This module provides the command-line interface for mcpcap, handling argument parsing
|
4
|
+
and server initialization.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import argparse
|
8
|
+
|
9
|
+
from .core import Config, MCPServer
|
10
|
+
|
11
|
+
|
12
|
+
def main():
|
13
|
+
"""Main function to parse arguments and start the MCP server.
|
14
|
+
|
15
|
+
Parses command-line arguments, initializes the configuration and MCP server,
|
16
|
+
and handles graceful shutdown and error conditions.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
int: Exit code (0 for success, 1 for error)
|
20
|
+
|
21
|
+
Raises:
|
22
|
+
ValueError: If the provided PCAP path is invalid
|
23
|
+
KeyboardInterrupt: If the user interrupts the server
|
24
|
+
Exception: For any unexpected errors during server operation
|
25
|
+
"""
|
26
|
+
parser = argparse.ArgumentParser(description="PCAP DNS Analyzer MCP Server")
|
27
|
+
parser.add_argument(
|
28
|
+
"--pcap-path", required=True, help="Path to directory containing PCAP files"
|
29
|
+
)
|
30
|
+
|
31
|
+
args = parser.parse_args()
|
32
|
+
|
33
|
+
try:
|
34
|
+
# Initialize configuration
|
35
|
+
config = Config(args.pcap_path)
|
36
|
+
|
37
|
+
# Create and start MCP server
|
38
|
+
server = MCPServer(config)
|
39
|
+
server.run()
|
40
|
+
return 0
|
41
|
+
|
42
|
+
except ValueError as e:
|
43
|
+
print(f"Error: {e}")
|
44
|
+
return 1
|
45
|
+
except KeyboardInterrupt:
|
46
|
+
print("\\nServer stopped by user")
|
47
|
+
return 0
|
48
|
+
except Exception as e:
|
49
|
+
print(f"Unexpected error: {e}")
|
50
|
+
return 1
|
51
|
+
|
52
|
+
|
53
|
+
if __name__ == "__main__":
|
54
|
+
exit(main())
|
mcpcap/core/__init__.py
ADDED
mcpcap/core/config.py
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
"""Configuration management for mcpcap."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
|
5
|
+
|
6
|
+
class Config:
|
7
|
+
"""Configuration management for mcpcap server."""
|
8
|
+
|
9
|
+
def __init__(self, pcap_path: str):
|
10
|
+
"""Initialize configuration.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
pcap_path: Path to directory containing PCAP files
|
14
|
+
"""
|
15
|
+
self.pcap_path = pcap_path
|
16
|
+
self._validate_pcap_path()
|
17
|
+
|
18
|
+
def _validate_pcap_path(self) -> None:
|
19
|
+
"""Validate that the PCAP path exists and is a directory."""
|
20
|
+
if not os.path.exists(self.pcap_path):
|
21
|
+
raise ValueError(f"PCAP directory '{self.pcap_path}' does not exist")
|
22
|
+
|
23
|
+
if not os.path.isdir(self.pcap_path):
|
24
|
+
raise ValueError(f"'{self.pcap_path}' is not a directory")
|
25
|
+
|
26
|
+
def get_pcap_file_path(self, pcap_file: str) -> str:
|
27
|
+
"""Get full path to a PCAP file.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
pcap_file: Filename or relative path to PCAP file
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
Full path to the PCAP file
|
34
|
+
"""
|
35
|
+
if os.path.isabs(pcap_file):
|
36
|
+
return pcap_file
|
37
|
+
return os.path.join(self.pcap_path, pcap_file)
|
38
|
+
|
39
|
+
def list_pcap_files(self) -> list[str]:
|
40
|
+
"""List all PCAP files in the configured directory.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
List of PCAP filenames
|
44
|
+
"""
|
45
|
+
try:
|
46
|
+
return [
|
47
|
+
f
|
48
|
+
for f in os.listdir(self.pcap_path)
|
49
|
+
if f.endswith((".pcap", ".pcapng"))
|
50
|
+
]
|
51
|
+
except Exception:
|
52
|
+
return []
|
mcpcap/core/server.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
"""MCP server setup and configuration."""
|
2
|
+
|
3
|
+
from fastmcp import FastMCP
|
4
|
+
|
5
|
+
from ..modules.dns import DNSModule
|
6
|
+
from ..resources.references import setup_resources
|
7
|
+
from .config import Config
|
8
|
+
|
9
|
+
|
10
|
+
class MCPServer:
|
11
|
+
"""MCP server for PCAP analysis."""
|
12
|
+
|
13
|
+
def __init__(self, config: Config):
|
14
|
+
"""Initialize MCP server.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
config: Configuration instance
|
18
|
+
"""
|
19
|
+
self.config = config
|
20
|
+
self.mcp = FastMCP("PCAP DNS Analyzer")
|
21
|
+
|
22
|
+
# Initialize modules
|
23
|
+
self.dns_module = DNSModule(config)
|
24
|
+
|
25
|
+
# Register tools
|
26
|
+
self._register_tools()
|
27
|
+
|
28
|
+
# Setup resources and prompts
|
29
|
+
setup_resources(self.mcp)
|
30
|
+
self.dns_module.setup_prompts(self.mcp)
|
31
|
+
|
32
|
+
def _register_tools(self) -> None:
|
33
|
+
"""Register all available tools with the MCP server."""
|
34
|
+
# Register DNS module tools
|
35
|
+
self.mcp.tool(self.dns_module.list_dns_packets)
|
36
|
+
self.mcp.tool(self.dns_module.list_pcap_files)
|
37
|
+
|
38
|
+
def run(self) -> None:
|
39
|
+
"""Start the MCP server."""
|
40
|
+
print(f"Starting MCP server with PCAP directory: {self.config.pcap_path}")
|
41
|
+
self.mcp.run()
|
mcpcap/modules/base.py
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
"""Base module interface for protocol analyzers."""
|
2
|
+
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from ..core.config import Config
|
7
|
+
|
8
|
+
|
9
|
+
class BaseModule(ABC):
|
10
|
+
"""Base class for protocol analysis modules."""
|
11
|
+
|
12
|
+
def __init__(self, config: Config):
|
13
|
+
"""Initialize the module.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
config: Configuration instance
|
17
|
+
"""
|
18
|
+
self.config = config
|
19
|
+
|
20
|
+
@abstractmethod
|
21
|
+
def analyze_packets(self, pcap_file: str) -> dict[str, Any]:
|
22
|
+
"""Analyze packets in a PCAP file.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
pcap_file: Path to the PCAP file
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
Analysis results as a dictionary
|
29
|
+
"""
|
30
|
+
pass
|
31
|
+
|
32
|
+
@property
|
33
|
+
@abstractmethod
|
34
|
+
def protocol_name(self) -> str:
|
35
|
+
"""Return the name of the protocol this module analyzes."""
|
36
|
+
pass
|
mcpcap/modules/dns.py
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
"""DNS analysis module."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Any
|
6
|
+
|
7
|
+
from fastmcp import FastMCP
|
8
|
+
from scapy.all import DNS, IP, TCP, UDP, IPv6, rdpcap
|
9
|
+
|
10
|
+
from .base import BaseModule
|
11
|
+
|
12
|
+
|
13
|
+
class DNSModule(BaseModule):
|
14
|
+
"""Module for analyzing DNS 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 "DNS"
|
20
|
+
|
21
|
+
def list_dns_packets(self, pcap_file: str = "example.pcap") -> dict[str, Any]:
|
22
|
+
"""
|
23
|
+
Analyze DNS 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 (defaults to 'example.pcap')
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
A structured dictionary containing DNS packet analysis results
|
30
|
+
"""
|
31
|
+
# Get full path to PCAP file
|
32
|
+
full_path = self.config.get_pcap_file_path(pcap_file)
|
33
|
+
|
34
|
+
# Check if file exists
|
35
|
+
if not os.path.exists(full_path):
|
36
|
+
# List available PCAP files for help
|
37
|
+
available_files = self.config.list_pcap_files()
|
38
|
+
return {
|
39
|
+
"error": f"PCAP file '{pcap_file}' not found",
|
40
|
+
"available_files": available_files,
|
41
|
+
"pcap_directory": self.config.pcap_path,
|
42
|
+
}
|
43
|
+
|
44
|
+
return self.analyze_packets(full_path)
|
45
|
+
|
46
|
+
def list_pcap_files(self) -> str:
|
47
|
+
"""
|
48
|
+
List all available PCAP files in the default directory.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
A list of available PCAP files that can be analyzed
|
52
|
+
"""
|
53
|
+
files = self.config.list_pcap_files()
|
54
|
+
if files:
|
55
|
+
return f"Available PCAP files in {self.config.pcap_path}:\\n" + "\\n".join(
|
56
|
+
f"- {f}" for f in sorted(files)
|
57
|
+
)
|
58
|
+
else:
|
59
|
+
return f"No PCAP files found in {self.config.pcap_path}"
|
60
|
+
|
61
|
+
def analyze_packets(self, pcap_file: str) -> dict[str, Any]:
|
62
|
+
"""Analyze DNS packets in a PCAP file.
|
63
|
+
|
64
|
+
Args:
|
65
|
+
pcap_file: Full path to the PCAP file
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
Analysis results as a dictionary
|
69
|
+
"""
|
70
|
+
try:
|
71
|
+
packets = rdpcap(pcap_file)
|
72
|
+
dns_packets = [pkt for pkt in packets if pkt.haslayer(DNS)]
|
73
|
+
|
74
|
+
if not dns_packets:
|
75
|
+
return {
|
76
|
+
"file": pcap_file,
|
77
|
+
"total_packets": len(packets),
|
78
|
+
"dns_packets_found": 0,
|
79
|
+
"message": "No DNS packets found in this capture",
|
80
|
+
}
|
81
|
+
|
82
|
+
packet_details = []
|
83
|
+
for i, pkt in enumerate(dns_packets, 1):
|
84
|
+
packet_info = self._analyze_dns_packet(pkt, i)
|
85
|
+
packet_details.append(packet_info)
|
86
|
+
|
87
|
+
# Generate statistics
|
88
|
+
stats = self._generate_statistics(packet_details)
|
89
|
+
|
90
|
+
result = {
|
91
|
+
"file": pcap_file,
|
92
|
+
"analysis_timestamp": datetime.now().isoformat(),
|
93
|
+
"total_packets_in_file": len(packets),
|
94
|
+
"dns_packets_found": len(dns_packets),
|
95
|
+
"statistics": stats,
|
96
|
+
"packets": packet_details,
|
97
|
+
}
|
98
|
+
|
99
|
+
return result
|
100
|
+
|
101
|
+
except Exception as e:
|
102
|
+
return {
|
103
|
+
"error": f"Error reading PCAP file '{pcap_file}': {str(e)}",
|
104
|
+
"file": pcap_file,
|
105
|
+
}
|
106
|
+
|
107
|
+
def _analyze_dns_packet(self, pkt: Any, packet_number: int) -> dict[str, Any]:
|
108
|
+
"""Analyze a single DNS packet.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
pkt: Scapy packet object
|
112
|
+
packet_number: Packet sequence number
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
Dictionary containing packet analysis
|
116
|
+
"""
|
117
|
+
dns_layer = pkt[DNS]
|
118
|
+
|
119
|
+
# Extract IP information
|
120
|
+
src_ip = dst_ip = "unknown"
|
121
|
+
if pkt.haslayer(IP):
|
122
|
+
src_ip = pkt[IP].src
|
123
|
+
dst_ip = pkt[IP].dst
|
124
|
+
elif pkt.haslayer(IPv6):
|
125
|
+
src_ip = pkt[IPv6].src
|
126
|
+
dst_ip = pkt[IPv6].dst
|
127
|
+
|
128
|
+
# Extract protocol (UDP/TCP)
|
129
|
+
protocol = "unknown"
|
130
|
+
if pkt.haslayer(UDP):
|
131
|
+
protocol = "UDP"
|
132
|
+
elif pkt.haslayer(TCP):
|
133
|
+
protocol = "TCP"
|
134
|
+
|
135
|
+
# Extract DNS questions
|
136
|
+
questions = []
|
137
|
+
if dns_layer.qd:
|
138
|
+
for q in dns_layer.qd:
|
139
|
+
try:
|
140
|
+
name = (
|
141
|
+
q.qname.decode().rstrip(".")
|
142
|
+
if hasattr(q.qname, "decode")
|
143
|
+
else str(q.qname).rstrip(".")
|
144
|
+
)
|
145
|
+
questions.append(
|
146
|
+
{
|
147
|
+
"name": name,
|
148
|
+
"type": getattr(q, "qtype", 0),
|
149
|
+
"class": getattr(q, "qclass", 0),
|
150
|
+
}
|
151
|
+
)
|
152
|
+
except (AttributeError, UnicodeDecodeError) as e:
|
153
|
+
# Skip malformed questions but log the issue
|
154
|
+
questions.append(
|
155
|
+
{
|
156
|
+
"name": f"<parsing_error: {str(e)}>",
|
157
|
+
"type": getattr(q, "qtype", 0),
|
158
|
+
"class": getattr(q, "qclass", 0),
|
159
|
+
}
|
160
|
+
)
|
161
|
+
|
162
|
+
# Extract DNS answers
|
163
|
+
answers = []
|
164
|
+
if dns_layer.an:
|
165
|
+
for a in dns_layer.an:
|
166
|
+
try:
|
167
|
+
# Safely extract the resource record name
|
168
|
+
if hasattr(a, "rrname"):
|
169
|
+
if hasattr(a.rrname, "decode"):
|
170
|
+
name = a.rrname.decode().rstrip(".")
|
171
|
+
else:
|
172
|
+
name = str(a.rrname).rstrip(".")
|
173
|
+
else:
|
174
|
+
name = "<unknown>"
|
175
|
+
|
176
|
+
answer_data = {
|
177
|
+
"name": name,
|
178
|
+
"type": getattr(a, "type", 0),
|
179
|
+
"class": getattr(a, "rclass", 0),
|
180
|
+
"ttl": getattr(a, "ttl", 0),
|
181
|
+
}
|
182
|
+
|
183
|
+
# Handle different answer types
|
184
|
+
if hasattr(a, "rdata"):
|
185
|
+
try:
|
186
|
+
if a.type == 1: # A record
|
187
|
+
answer_data["address"] = str(a.rdata)
|
188
|
+
elif a.type == 28: # AAAA record
|
189
|
+
answer_data["address"] = str(a.rdata)
|
190
|
+
elif a.type == 5: # CNAME
|
191
|
+
answer_data["cname"] = str(a.rdata).rstrip(".")
|
192
|
+
elif a.type == 15: # MX
|
193
|
+
answer_data["mx"] = str(a.rdata)
|
194
|
+
else:
|
195
|
+
answer_data["data"] = str(a.rdata)
|
196
|
+
except Exception as rdata_error:
|
197
|
+
answer_data["data"] = (
|
198
|
+
f"<rdata_parsing_error: {str(rdata_error)}>"
|
199
|
+
)
|
200
|
+
|
201
|
+
answers.append(answer_data)
|
202
|
+
|
203
|
+
except (AttributeError, UnicodeDecodeError) as e:
|
204
|
+
# Skip malformed answers but include error info
|
205
|
+
answers.append(
|
206
|
+
{
|
207
|
+
"name": f"<parsing_error: {str(e)}>",
|
208
|
+
"type": getattr(a, "type", 0),
|
209
|
+
"class": getattr(a, "rclass", 0),
|
210
|
+
"ttl": getattr(a, "ttl", 0),
|
211
|
+
"data": "<malformed_record>",
|
212
|
+
}
|
213
|
+
)
|
214
|
+
|
215
|
+
return {
|
216
|
+
"packet_number": packet_number,
|
217
|
+
"timestamp": datetime.fromtimestamp(float(pkt.time)).isoformat(),
|
218
|
+
"source_ip": src_ip,
|
219
|
+
"destination_ip": dst_ip,
|
220
|
+
"protocol": protocol,
|
221
|
+
"dns_id": dns_layer.id,
|
222
|
+
"flags": {
|
223
|
+
"is_response": bool(dns_layer.qr),
|
224
|
+
"authoritative": bool(dns_layer.aa),
|
225
|
+
"truncated": bool(dns_layer.tc),
|
226
|
+
"recursion_desired": bool(dns_layer.rd),
|
227
|
+
"recursion_available": bool(dns_layer.ra),
|
228
|
+
},
|
229
|
+
"questions": questions,
|
230
|
+
"answers": answers,
|
231
|
+
"summary": pkt.summary(),
|
232
|
+
}
|
233
|
+
|
234
|
+
def _generate_statistics(self, packet_details: list) -> dict[str, Any]:
|
235
|
+
"""Generate statistics from analyzed packets.
|
236
|
+
|
237
|
+
Args:
|
238
|
+
packet_details: List of analyzed packet dictionaries
|
239
|
+
|
240
|
+
Returns:
|
241
|
+
Dictionary containing statistics
|
242
|
+
"""
|
243
|
+
query_count = sum(1 for p in packet_details if not p["flags"]["is_response"])
|
244
|
+
response_count = sum(1 for p in packet_details if p["flags"]["is_response"])
|
245
|
+
unique_domains = set()
|
246
|
+
for p in packet_details:
|
247
|
+
for q in p["questions"]:
|
248
|
+
unique_domains.add(q["name"])
|
249
|
+
|
250
|
+
return {
|
251
|
+
"queries": query_count,
|
252
|
+
"responses": response_count,
|
253
|
+
"unique_domains_queried": len(unique_domains),
|
254
|
+
"unique_domains": list(unique_domains),
|
255
|
+
}
|
256
|
+
|
257
|
+
def setup_prompts(self, mcp: FastMCP) -> None:
|
258
|
+
"""Set up DNS-specific analysis prompts for the MCP server.
|
259
|
+
|
260
|
+
Args:
|
261
|
+
mcp: FastMCP server instance
|
262
|
+
"""
|
263
|
+
|
264
|
+
@mcp.prompt
|
265
|
+
def security_analysis():
|
266
|
+
"""Prompt for analyzing DNS traffic from a security perspective"""
|
267
|
+
return """You are a cybersecurity analyst examining DNS traffic. Focus your analysis on:
|
268
|
+
|
269
|
+
1. **Threat Detection:**
|
270
|
+
- Look for suspicious domain patterns (DGA, long random strings)
|
271
|
+
- Identify potential DNS tunneling (unusually long queries, high TXT record volume)
|
272
|
+
- Spot potential C2 communication patterns
|
273
|
+
- Check for queries to known malicious domains
|
274
|
+
|
275
|
+
2. **Behavioral Analysis:**
|
276
|
+
- Identify unusual query frequencies or patterns
|
277
|
+
- Look for reconnaissance activities (PTR lookups, zone transfers)
|
278
|
+
- Check for DNS cache poisoning attempts
|
279
|
+
- Monitor for subdomain enumeration
|
280
|
+
|
281
|
+
3. **Infrastructure Assessment:**
|
282
|
+
- Identify DNS servers being used
|
283
|
+
- Check for DNS over non-standard ports
|
284
|
+
- Look for failed queries (NXDOMAIN) patterns
|
285
|
+
- Assess query distribution across time
|
286
|
+
|
287
|
+
Provide specific examples and recommend follow-up investigations for any suspicious findings."""
|
288
|
+
|
289
|
+
@mcp.prompt
|
290
|
+
def network_troubleshooting():
|
291
|
+
"""Prompt for troubleshooting DNS-related network issues"""
|
292
|
+
return """You are a network engineer troubleshooting DNS issues. Focus on:
|
293
|
+
|
294
|
+
1. **Performance Issues:**
|
295
|
+
- Identify slow DNS responses (high latency)
|
296
|
+
- Look for timeouts and retransmissions
|
297
|
+
- Check for load balancing issues
|
298
|
+
- Analyze response times across different servers
|
299
|
+
|
300
|
+
2. **Connectivity Problems:**
|
301
|
+
- Find failed DNS queries and their causes
|
302
|
+
- Identify unreachable DNS servers
|
303
|
+
- Look for network path issues
|
304
|
+
- Check for DNS server failures
|
305
|
+
|
306
|
+
3. **Configuration Issues:**
|
307
|
+
- Verify proper DNS server assignments
|
308
|
+
- Check for mismatched recursion settings
|
309
|
+
- Look for incorrect domain configurations
|
310
|
+
- Identify forwarding problems
|
311
|
+
|
312
|
+
4. **Capacity Planning:**
|
313
|
+
- Analyze query volumes and patterns
|
314
|
+
- Identify peak usage times
|
315
|
+
- Look for resource exhaustion indicators
|
316
|
+
- Assess server response capabilities
|
317
|
+
|
318
|
+
Provide actionable recommendations for resolving identified issues."""
|
319
|
+
|
320
|
+
@mcp.prompt
|
321
|
+
def forensic_investigation():
|
322
|
+
"""Prompt for forensic analysis of DNS traffic"""
|
323
|
+
return """You are conducting a digital forensics investigation involving DNS traffic. Approach this systematically:
|
324
|
+
|
325
|
+
1. **Timeline Reconstruction:**
|
326
|
+
- Create a chronological sequence of DNS events
|
327
|
+
- Correlate DNS queries with potential incident timeframes
|
328
|
+
- Identify patterns in query timing and frequency
|
329
|
+
- Map DNS activity to user/system behavior
|
330
|
+
|
331
|
+
2. **Evidence Collection:**
|
332
|
+
- Document all suspicious or anomalous DNS queries
|
333
|
+
- Preserve query-response pairs for analysis
|
334
|
+
- Record DNS server interactions and responses
|
335
|
+
- Note any encrypted or tunneled DNS traffic
|
336
|
+
|
337
|
+
3. **Attribution and Tracking:**
|
338
|
+
- Trace DNS queries to source systems/users
|
339
|
+
- Identify external domains contacted
|
340
|
+
- Map communication patterns and relationships
|
341
|
+
- Document potential data exfiltration via DNS
|
342
|
+
|
343
|
+
4. **Impact Assessment:**
|
344
|
+
- Determine scope of DNS-related compromise
|
345
|
+
- Assess potential data exposure through DNS
|
346
|
+
- Identify systems that may be affected
|
347
|
+
- Evaluate ongoing security risks
|
348
|
+
|
349
|
+
Present findings with timestamps, evidence preservation notes, and clear documentation suitable for legal proceedings."""
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"""Reference resources for DNS analysis."""
|
2
|
+
|
3
|
+
from fastmcp import FastMCP
|
4
|
+
|
5
|
+
|
6
|
+
def setup_resources(mcp: FastMCP) -> None:
|
7
|
+
"""Set up reference resources for the MCP server.
|
8
|
+
|
9
|
+
Args:
|
10
|
+
mcp: FastMCP server instance
|
11
|
+
"""
|
12
|
+
|
13
|
+
@mcp.resource("dns-record-types://reference")
|
14
|
+
def get_dns_record_types() -> str:
|
15
|
+
"""Reference guide for DNS record types"""
|
16
|
+
return """
|
17
|
+
# DNS Record Types Reference
|
18
|
+
|
19
|
+
## Common Record Types:
|
20
|
+
- **A (1)**: IPv4 address record
|
21
|
+
- **AAAA (28)**: IPv6 address record
|
22
|
+
- **CNAME (5)**: Canonical name (alias)
|
23
|
+
- **MX (15)**: Mail exchange record
|
24
|
+
- **NS (2)**: Name server record
|
25
|
+
- **PTR (12)**: Pointer record (reverse DNS)
|
26
|
+
- **SOA (6)**: Start of authority
|
27
|
+
- **TXT (16)**: Text record
|
28
|
+
- **SRV (33)**: Service record
|
29
|
+
|
30
|
+
## Security-Related Types:
|
31
|
+
- **DNSKEY (48)**: DNS public key
|
32
|
+
- **RRSIG (46)**: Resource record signature
|
33
|
+
- **DS (43)**: Delegation signer
|
34
|
+
- **NSEC (47)**: Next secure record
|
35
|
+
"""
|
36
|
+
|
37
|
+
@mcp.resource("dns-flags://reference")
|
38
|
+
def get_dns_flags_reference() -> str:
|
39
|
+
"""Reference guide for DNS flags and their meanings"""
|
40
|
+
return """
|
41
|
+
# DNS Flags Reference
|
42
|
+
|
43
|
+
## Header Flags:
|
44
|
+
- **QR**: Query/Response (0=Query, 1=Response)
|
45
|
+
- **AA**: Authoritative Answer
|
46
|
+
- **TC**: Truncated (message was truncated)
|
47
|
+
- **RD**: Recursion Desired
|
48
|
+
- **RA**: Recursion Available
|
49
|
+
- **Z**: Reserved (must be zero)
|
50
|
+
- **AD**: Authenticated Data
|
51
|
+
- **CD**: Checking Disabled
|
52
|
+
|
53
|
+
## Response Codes (RCODE):
|
54
|
+
- **0**: No error
|
55
|
+
- **1**: Format error
|
56
|
+
- **2**: Server failure
|
57
|
+
- **3**: Name error (domain doesn't exist)
|
58
|
+
- **4**: Not implemented
|
59
|
+
- **5**: Refused
|
60
|
+
"""
|
61
|
+
|
62
|
+
@mcp.resource("suspicious-domains://indicators")
|
63
|
+
def get_suspicious_domain_indicators() -> str:
|
64
|
+
"""Common indicators of suspicious or malicious domains"""
|
65
|
+
return """
|
66
|
+
# Suspicious Domain Indicators
|
67
|
+
|
68
|
+
## Common Patterns:
|
69
|
+
- Long random-looking subdomains
|
70
|
+
- Domains with excessive hyphens or numbers
|
71
|
+
- Recently registered domains
|
72
|
+
- Domains using punycode (internationalized domains)
|
73
|
+
- DGA (Domain Generation Algorithm) patterns
|
74
|
+
|
75
|
+
## Suspicious TLDs (often abused):
|
76
|
+
- .tk, .ml, .ga, .cf (free TLDs)
|
77
|
+
- .bit (blockchain domains)
|
78
|
+
- Newly introduced gTLDs
|
79
|
+
|
80
|
+
## Behavioral Indicators:
|
81
|
+
- High frequency of DNS queries
|
82
|
+
- Queries to non-existent domains (NXDOMAIN)
|
83
|
+
- Unusual query patterns or timing
|
84
|
+
- Queries for infrastructure domains (.arpa, .root-servers.net)
|
85
|
+
|
86
|
+
## DNS Tunneling Indicators:
|
87
|
+
- Unusually long DNS queries
|
88
|
+
- High volume of TXT record queries
|
89
|
+
- Queries with encoded data in subdomain names
|
90
|
+
"""
|
@@ -0,0 +1,198 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: mcpcap
|
3
|
+
Version: 0.2.1
|
4
|
+
Summary: A modular Python MCP Server for analyzing PCAP files
|
5
|
+
Author: mcpcap contributors
|
6
|
+
License: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/danohn/mcpcap
|
8
|
+
Project-URL: Repository, https://github.com/danohn/mcpcap
|
9
|
+
Project-URL: Issues, https://github.com/danohn/mcpcap/issues
|
10
|
+
Keywords: pcap,network,analysis,mcp,dns
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
19
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
20
|
+
Classifier: Topic :: Security
|
21
|
+
Requires-Python: >=3.10
|
22
|
+
Description-Content-Type: text/markdown
|
23
|
+
License-File: LICENSE
|
24
|
+
Requires-Dist: fastmcp
|
25
|
+
Requires-Dist: scapy
|
26
|
+
Provides-Extra: test
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
28
|
+
Requires-Dist: pytest-cov; extra == "test"
|
29
|
+
Requires-Dist: setuptools-scm[toml]; extra == "test"
|
30
|
+
Provides-Extra: dev
|
31
|
+
Requires-Dist: setuptools-scm[toml]; extra == "dev"
|
32
|
+
Requires-Dist: build; extra == "dev"
|
33
|
+
Requires-Dist: twine; extra == "dev"
|
34
|
+
Requires-Dist: ruff; extra == "dev"
|
35
|
+
Provides-Extra: docs
|
36
|
+
Requires-Dist: sphinx>=7.0; extra == "docs"
|
37
|
+
Requires-Dist: sphinx-rtd-theme; extra == "docs"
|
38
|
+
Requires-Dist: myst-parser; extra == "docs"
|
39
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
|
40
|
+
Requires-Dist: sphinx-copybutton; extra == "docs"
|
41
|
+
Requires-Dist: linkify-it-py; extra == "docs"
|
42
|
+
Dynamic: license-file
|
43
|
+
|
44
|
+
# mcpcap
|
45
|
+
|
46
|
+

|
47
|
+
|
48
|
+
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.
|
49
|
+
|
50
|
+
## Overview
|
51
|
+
|
52
|
+
mcpcap uses a modular architecture to analyze different network protocols found in PCAP files. Each module focuses on a specific protocol, allowing for targeted analysis and easy extensibility. The server leverages the powerful scapy library for packet parsing and analysis.
|
53
|
+
|
54
|
+
### Key Features
|
55
|
+
|
56
|
+
- **Modular Architecture**: Easily extensible to support new protocols
|
57
|
+
- **Local & Remote PCAP Support**: Read files from local directories or HTTP servers
|
58
|
+
- **Scapy Integration**: Leverages scapy's comprehensive packet parsing capabilities
|
59
|
+
- **MCP Server**: Integrates seamlessly with LLM clients via Model Context Protocol
|
60
|
+
- **JSON Responses**: Structured data format for easy LLM consumption
|
61
|
+
|
62
|
+
## Installation
|
63
|
+
|
64
|
+
mcpcap requires Python 3.10 or greater.
|
65
|
+
|
66
|
+
### Using pip
|
67
|
+
|
68
|
+
```bash
|
69
|
+
pip install mcpcap
|
70
|
+
```
|
71
|
+
|
72
|
+
### Using uv
|
73
|
+
|
74
|
+
```bash
|
75
|
+
uv add mcpcap
|
76
|
+
```
|
77
|
+
|
78
|
+
### Using uvx (for one-time usage)
|
79
|
+
|
80
|
+
```bash
|
81
|
+
uvx mcpcap
|
82
|
+
```
|
83
|
+
|
84
|
+
## Quick Start
|
85
|
+
|
86
|
+
1. **Start the MCP Server**:
|
87
|
+
|
88
|
+
```bash
|
89
|
+
mcpcap --pcap-path /path/to/pcap/files
|
90
|
+
```
|
91
|
+
|
92
|
+
2. **Connect your LLM client** to the MCP server
|
93
|
+
|
94
|
+
3. **Ask questions** about your network traffic:
|
95
|
+
- "What domain was queried the most in the DNS traffic?"
|
96
|
+
- "Show me all DNS queries for example.com"
|
97
|
+
- "What are the top 5 queried domains?"
|
98
|
+
|
99
|
+
## Modules
|
100
|
+
|
101
|
+
### DNS Module
|
102
|
+
|
103
|
+
The DNS module analyzes Domain Name System packets in PCAP files.
|
104
|
+
|
105
|
+
**Capabilities**:
|
106
|
+
|
107
|
+
- Extract DNS queries and responses
|
108
|
+
- Identify queried domains and subdomains
|
109
|
+
- Analyze query types (A, AAAA, MX, etc.)
|
110
|
+
- Track query frequency and patterns
|
111
|
+
- Identify DNS servers used
|
112
|
+
|
113
|
+
**Example Usage**:
|
114
|
+
|
115
|
+
```python
|
116
|
+
# LLM can ask: "What domains were queried in this PCAP?"
|
117
|
+
# mcpcap will return structured JSON with DNS query information
|
118
|
+
```
|
119
|
+
|
120
|
+
## Configuration
|
121
|
+
|
122
|
+
### PCAP Sources
|
123
|
+
|
124
|
+
**Local Directory**:
|
125
|
+
|
126
|
+
```bash
|
127
|
+
mcpcap --pcap-path /local/path/to/pcaps
|
128
|
+
```
|
129
|
+
|
130
|
+
**Remote HTTP Server**:
|
131
|
+
|
132
|
+
```bash
|
133
|
+
mcpcap --pcap-url http://example.com/pcaps/
|
134
|
+
```
|
135
|
+
|
136
|
+
### Module Selection
|
137
|
+
|
138
|
+
```bash
|
139
|
+
mcpcap --modules dns --pcap-path /path/to/files
|
140
|
+
```
|
141
|
+
|
142
|
+
## Example
|
143
|
+
|
144
|
+
An example PCAP file (`example.pcap`) containing DNS traffic is included with the project to help you get started.
|
145
|
+
|
146
|
+
## Architecture
|
147
|
+
|
148
|
+
mcpcap's modular design makes it easy to extend support for new protocols:
|
149
|
+
|
150
|
+
1. **Core Engine**: Handles PCAP file loading and basic packet processing
|
151
|
+
2. **Protocol Modules**: Individual modules for specific protocols (DNS, etc.)
|
152
|
+
3. **MCP Interface**: Translates between LLM queries and packet analysis results
|
153
|
+
4. **Output Formatter**: Converts analysis results to structured JSON
|
154
|
+
|
155
|
+
### Adding New Modules
|
156
|
+
|
157
|
+
New protocol modules can be added by:
|
158
|
+
|
159
|
+
1. Implementing the module interface
|
160
|
+
2. Defining scapy display filters for the protocol
|
161
|
+
3. Creating analysis functions specific to the protocol
|
162
|
+
4. Registering the module with the core engine
|
163
|
+
|
164
|
+
Future modules might include:
|
165
|
+
|
166
|
+
- BGP (Border Gateway Protocol)
|
167
|
+
- HTTP/HTTPS traffic analysis
|
168
|
+
- TCP connection tracking
|
169
|
+
- And more!
|
170
|
+
|
171
|
+
## Remote Access
|
172
|
+
|
173
|
+
mcpcap supports reading PCAP files from remote HTTP servers without authentication. Future versions may include support for Basic Authentication and other security mechanisms.
|
174
|
+
|
175
|
+
## Contributing
|
176
|
+
|
177
|
+
Contributions are welcome! Whether you want to:
|
178
|
+
|
179
|
+
- Add support for new protocols
|
180
|
+
- Improve existing modules
|
181
|
+
- Enhance the MCP interface
|
182
|
+
- Add new features
|
183
|
+
|
184
|
+
Please feel free to open issues and submit pull requests.
|
185
|
+
|
186
|
+
## License
|
187
|
+
|
188
|
+
MIT
|
189
|
+
|
190
|
+
## Requirements
|
191
|
+
|
192
|
+
- Python 3.10+
|
193
|
+
- scapy
|
194
|
+
- MCP server dependencies (automatically installed)
|
195
|
+
|
196
|
+
## Support
|
197
|
+
|
198
|
+
For questions, issues, or feature requests, please open an issue on GitHub.
|
@@ -0,0 +1,17 @@
|
|
1
|
+
mcpcap/__init__.py,sha256=rJwCpBXkhIvmsqHFpeR33Vg8kuipNPJ2JdlAjsTk7I4,1408
|
2
|
+
mcpcap/_version.py,sha256=vYqoJTG51NOUmYyL0xt8asRK8vUT4lGAdal_EZ59mvw,704
|
3
|
+
mcpcap/cli.py,sha256=8afFamCifC44vMAAi1gNqr2c6CdBgXk33IoRgqmSH2A,1416
|
4
|
+
mcpcap/core/__init__.py,sha256=WM5GTl06ZwwqHTPiKaYB-9hwOOXe3hyHG16FshwSsjE,127
|
5
|
+
mcpcap/core/config.py,sha256=0LmnzHWWOwtHrikW2o5C9N0SLVvNuCzkNYTQQEys27U,1476
|
6
|
+
mcpcap/core/server.py,sha256=1wGGhTMLPspeH45NoVvhcnjUcLWyD6kY1u9tpt4jos4,1140
|
7
|
+
mcpcap/modules/__init__.py,sha256=iIeoZuLA-EOv0OS8WU8qDCitXJnarq9F0hA5-Y97zis,140
|
8
|
+
mcpcap/modules/base.py,sha256=3h8lGt6d6ob4SbgP6THC5PnTeMRcKfTGoJ9ZlZsQje0,826
|
9
|
+
mcpcap/modules/dns.py,sha256=NSBVfx1KWgEDFcvOpvrxIVPAXXE-SvK2E4HS3vqvlVk,12627
|
10
|
+
mcpcap/resources/__init__.py,sha256=BPXV29wIG360w9Y9iNpQdA93H2PhT3a6CrnMZX2aaaU,109
|
11
|
+
mcpcap/resources/references.py,sha256=HCciAutgLHodlifC8goAZcWpvup3DfbVZ1rxPaXKggA,2516
|
12
|
+
mcpcap-0.2.1.dist-info/licenses/LICENSE,sha256=Ltj0zxftQyBYQMNva935v0i5QXQQOF8ygE8dQxGEtjk,1063
|
13
|
+
mcpcap-0.2.1.dist-info/METADATA,sha256=24bkSs35339PMi70HVhJziGIcm9XeurIsMT2fFrufCI,5523
|
14
|
+
mcpcap-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
mcpcap-0.2.1.dist-info/entry_points.txt,sha256=ck69gPBEopmU6mzQy9P6o6ssMr89bQbrvv51IaJ50Gc,39
|
16
|
+
mcpcap-0.2.1.dist-info/top_level.txt,sha256=YkRkVGjuM3nI7cVB1l8zIAeqiS_5_vrzbUcHNkH3OXE,7
|
17
|
+
mcpcap-0.2.1.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 danohn
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1 @@
|
|
1
|
+
mcpcap
|