mcpcap 0.2.3__py3-none-any.whl → 0.3.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 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.2.3'
32
- __version_tuple__ = version_tuple = (0, 2, 3)
31
+ __version__ = version = '0.3.1'
32
+ __version_tuple__ = version_tuple = (0, 3, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
mcpcap/cli.py CHANGED
@@ -5,6 +5,7 @@ and server initialization.
5
5
  """
6
6
 
7
7
  import argparse
8
+ import sys
8
9
 
9
10
  from .core import Config, MCPServer
10
11
 
@@ -23,16 +24,46 @@ def main():
23
24
  KeyboardInterrupt: If the user interrupts the server
24
25
  Exception: For any unexpected errors during server operation
25
26
  """
26
- parser = argparse.ArgumentParser(description="PCAP DNS Analyzer MCP Server")
27
+ parser = argparse.ArgumentParser(description="mcpcap MCP Server")
28
+
29
+ # PCAP source options (mutually exclusive)
30
+ source_group = parser.add_mutually_exclusive_group(required=True)
31
+ source_group.add_argument(
32
+ "--pcap-path", help="Path to PCAP file or directory containing PCAP files"
33
+ )
34
+ source_group.add_argument(
35
+ "--pcap-url",
36
+ help="HTTP URL to PCAP file (direct link) or directory containing PCAP files",
37
+ )
38
+
39
+ # Analysis options
40
+ parser.add_argument(
41
+ "--modules",
42
+ help="Comma-separated list of modules to load (default: dns)",
43
+ default="dns",
44
+ )
45
+ parser.add_argument(
46
+ "--protocols",
47
+ help="Comma-separated list of protocols to analyze (default: dns)",
48
+ default="dns",
49
+ )
27
50
  parser.add_argument(
28
- "--pcap-path", required=True, help="Path to directory containing PCAP files"
51
+ "--max-packets",
52
+ type=int,
53
+ help="Maximum number of packets to analyze per file (default: unlimited)",
29
54
  )
30
55
 
31
56
  args = parser.parse_args()
32
57
 
33
58
  try:
34
59
  # Initialize configuration
35
- config = Config(args.pcap_path)
60
+ config = Config(
61
+ pcap_path=args.pcap_path,
62
+ pcap_url=args.pcap_url,
63
+ modules=args.modules.split(",") if args.modules else ["dns"],
64
+ protocols=args.protocols.split(",") if args.protocols else ["dns"],
65
+ max_packets=args.max_packets,
66
+ )
36
67
 
37
68
  # Create and start MCP server
38
69
  server = MCPServer(config)
@@ -40,13 +71,13 @@ def main():
40
71
  return 0
41
72
 
42
73
  except ValueError as e:
43
- print(f"Error: {e}")
74
+ print(f"Error: {e}", file=sys.stderr)
44
75
  return 1
45
76
  except KeyboardInterrupt:
46
- print("\\nServer stopped by user")
77
+ print("\\nServer stopped by user", file=sys.stderr)
47
78
  return 0
48
79
  except Exception as e:
49
- print(f"Unexpected error: {e}")
80
+ print(f"Unexpected error: {e}", file=sys.stderr)
50
81
  return 1
51
82
 
52
83
 
mcpcap/core/config.py CHANGED
@@ -1,52 +1,230 @@
1
1
  """Configuration management for mcpcap."""
2
2
 
3
3
  import os
4
+ from urllib.parse import urljoin, urlparse
5
+
6
+ import requests
4
7
 
5
8
 
6
9
  class Config:
7
10
  """Configuration management for mcpcap server."""
8
11
 
9
- def __init__(self, pcap_path: str):
12
+ def __init__(
13
+ self,
14
+ pcap_path: str | None = None,
15
+ pcap_url: str | None = None,
16
+ modules: list[str] | None = None,
17
+ protocols: list[str] | None = None,
18
+ max_packets: int | None = None,
19
+ ):
10
20
  """Initialize configuration.
11
21
 
12
22
  Args:
13
23
  pcap_path: Path to directory containing PCAP files
24
+ pcap_url: HTTP server URL containing PCAP files
25
+ modules: List of modules to load
26
+ protocols: List of protocols to analyze
27
+ max_packets: Maximum number of packets to analyze per file
14
28
  """
15
29
  self.pcap_path = pcap_path
16
- self._validate_pcap_path()
30
+ self.pcap_url = pcap_url
31
+ self.modules = modules or ["dns"]
32
+ self.protocols = protocols or ["dns"]
33
+ self.max_packets = max_packets
34
+ self.is_remote = pcap_url is not None
35
+ self.is_direct_file_url = False # Will be set during validation
36
+ self.is_direct_file_path = (
37
+ False # Will be set during validation for local files
38
+ )
39
+
40
+ self._validate_configuration()
41
+
42
+ def _validate_configuration(self) -> None:
43
+ """Validate the configuration parameters."""
44
+ if not self.pcap_path and not self.pcap_url:
45
+ raise ValueError("Either --pcap-path or --pcap-url must be specified")
46
+
47
+ if self.pcap_path and self.pcap_url:
48
+ raise ValueError("Cannot specify both --pcap-path and --pcap-url")
49
+
50
+ if self.pcap_path:
51
+ self._validate_pcap_path()
52
+
53
+ if self.pcap_url:
54
+ self._validate_pcap_url()
55
+
56
+ if self.max_packets is not None and self.max_packets <= 0:
57
+ raise ValueError("max_packets must be a positive integer")
17
58
 
18
59
  def _validate_pcap_path(self) -> None:
19
- """Validate that the PCAP path exists and is a directory."""
60
+ """Validate that the PCAP path exists and is either a directory or a PCAP file."""
20
61
  if not os.path.exists(self.pcap_path):
21
- raise ValueError(f"PCAP directory '{self.pcap_path}' does not exist")
62
+ raise ValueError(f"PCAP path '{self.pcap_path}' does not exist")
63
+
64
+ if os.path.isfile(self.pcap_path):
65
+ # Check if it's a PCAP file
66
+ if not self.pcap_path.lower().endswith((".pcap", ".pcapng", ".cap")):
67
+ raise ValueError(
68
+ f"File '{self.pcap_path}' is not a supported PCAP file (.pcap/.pcapng/.cap)"
69
+ )
70
+ self.is_direct_file_path = True
71
+ elif os.path.isdir(self.pcap_path):
72
+ self.is_direct_file_path = False
73
+ else:
74
+ raise ValueError(f"'{self.pcap_path}' is neither a file nor a directory")
75
+
76
+ def _validate_pcap_url(self) -> None:
77
+ """Validate that the PCAP URL is accessible."""
78
+ try:
79
+ parsed = urlparse(self.pcap_url)
80
+ if not parsed.scheme or not parsed.netloc:
81
+ raise ValueError(f"Invalid URL format: {self.pcap_url}")
82
+
83
+ # Determine if this is a direct file URL or directory URL
84
+ self.is_direct_file_url = self._is_direct_file_url()
22
85
 
23
- if not os.path.isdir(self.pcap_path):
24
- raise ValueError(f"'{self.pcap_path}' is not a directory")
86
+ # Test connectivity with a HEAD request
87
+ response = requests.head(self.pcap_url, timeout=10)
88
+ if response.status_code >= 400:
89
+ raise ValueError(
90
+ f"Cannot access PCAP URL: {self.pcap_url} (HTTP {response.status_code})"
91
+ )
92
+
93
+ except requests.RequestException as e:
94
+ raise ValueError(
95
+ f"Cannot connect to PCAP URL '{self.pcap_url}': {str(e)}"
96
+ ) from e
97
+
98
+ def _is_direct_file_url(self) -> bool:
99
+ """Determine if the URL points directly to a PCAP file."""
100
+ parsed = urlparse(self.pcap_url)
101
+ path = parsed.path.lower()
102
+
103
+ # Check if URL ends with a PCAP file extension
104
+ return (
105
+ path.endswith(".pcap") or path.endswith(".pcapng") or path.endswith(".cap")
106
+ )
25
107
 
26
108
  def get_pcap_file_path(self, pcap_file: str) -> str:
27
- """Get full path to a PCAP file.
109
+ """Get full path or URL to a PCAP file.
28
110
 
29
111
  Args:
30
112
  pcap_file: Filename or relative path to PCAP file
31
113
 
32
114
  Returns:
33
- Full path to the PCAP file
115
+ Full path or URL to the PCAP file
34
116
  """
35
- if os.path.isabs(pcap_file):
36
- return pcap_file
37
- return os.path.join(self.pcap_path, pcap_file)
117
+ if self.is_remote:
118
+ # If it's already a full URL, return as-is
119
+ if pcap_file.startswith("http"):
120
+ return pcap_file
121
+
122
+ # If this is a direct file URL, return the URL directly
123
+ if self.is_direct_file_url:
124
+ return self.pcap_url
125
+
126
+ # Otherwise, treat as directory and join with filename
127
+ return urljoin(self.pcap_url.rstrip("/") + "/", pcap_file)
128
+ else:
129
+ # Local file handling
130
+ if os.path.isabs(pcap_file):
131
+ return pcap_file
132
+
133
+ # If this is a direct file path, return it directly
134
+ if self.is_direct_file_path:
135
+ return self.pcap_path
136
+
137
+ # Otherwise, join with directory
138
+ return os.path.join(self.pcap_path, pcap_file)
38
139
 
39
140
  def list_pcap_files(self) -> list[str]:
40
- """List all PCAP files in the configured directory.
141
+ """List all PCAP files in the configured directory or remote URL.
41
142
 
42
143
  Returns:
43
144
  List of PCAP filenames
44
145
  """
146
+ if self.is_remote:
147
+ return self._list_remote_pcap_files()
148
+ else:
149
+ if self.is_direct_file_path:
150
+ # Return just the filename from the direct file path
151
+ return [os.path.basename(self.pcap_path)]
152
+ else:
153
+ # List files in directory
154
+ try:
155
+ return [
156
+ f
157
+ for f in os.listdir(self.pcap_path)
158
+ if f.endswith((".pcap", ".pcapng", ".cap"))
159
+ ]
160
+ except Exception:
161
+ return []
162
+
163
+ def _list_remote_pcap_files(self) -> list[str]:
164
+ """List PCAP files from a remote HTTP server.
165
+
166
+ Returns:
167
+ List of PCAP filenames found on the remote server
168
+ """
169
+ # If this is a direct file URL, return just that filename
170
+ if self.is_direct_file_url:
171
+ filename = os.path.basename(urlparse(self.pcap_url).path)
172
+ return [filename] if filename else []
173
+
174
+ # Otherwise try to parse directory listing
45
175
  try:
46
- return [
47
- f
48
- for f in os.listdir(self.pcap_path)
49
- if f.endswith((".pcap", ".pcapng"))
50
- ]
51
- except Exception:
176
+ response = requests.get(self.pcap_url, timeout=30)
177
+ response.raise_for_status()
178
+
179
+ # Parse HTML to find .pcap and .pcapng files
180
+ # This is a simple implementation that looks for href attributes
181
+ import re
182
+
183
+ pcap_files = []
184
+
185
+ # Look for links to .pcap, .pcapng, and .cap files
186
+ pattern = r'href=["\']([^"\']*\.(?:pcap|pcapng|cap))["\']'
187
+ matches = re.findall(pattern, response.text, re.IGNORECASE)
188
+
189
+ for match in matches:
190
+ # Extract just the filename, not the full path
191
+ filename = os.path.basename(match)
192
+ if filename and filename not in pcap_files:
193
+ pcap_files.append(filename)
194
+
195
+ return sorted(pcap_files)
196
+
197
+ except requests.RequestException:
52
198
  return []
199
+
200
+ def download_pcap_file(self, pcap_file: str, local_path: str) -> str:
201
+ """Download a remote PCAP file to local storage.
202
+
203
+ Args:
204
+ pcap_file: Name of the PCAP file to download
205
+ local_path: Local path to save the file
206
+
207
+ Returns:
208
+ Local path to the downloaded file
209
+ """
210
+ if not self.is_remote:
211
+ raise ValueError("Cannot download file: not using remote source")
212
+
213
+ url = self.get_pcap_file_path(pcap_file)
214
+
215
+ try:
216
+ response = requests.get(url, timeout=60, stream=True)
217
+ response.raise_for_status()
218
+
219
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
220
+
221
+ with open(local_path, "wb") as f:
222
+ for chunk in response.iter_content(chunk_size=8192):
223
+ f.write(chunk)
224
+
225
+ return local_path
226
+
227
+ except requests.RequestException as e:
228
+ raise ValueError(
229
+ f"Failed to download PCAP file '{pcap_file}': {str(e)}"
230
+ ) from e
mcpcap/core/server.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from fastmcp import FastMCP
4
4
 
5
5
  from ..modules.dns import DNSModule
6
- from ..resources.references import setup_resources
7
6
  from .config import Config
8
7
 
9
8
 
@@ -17,7 +16,7 @@ class MCPServer:
17
16
  config: Configuration instance
18
17
  """
19
18
  self.config = config
20
- self.mcp = FastMCP("PCAP DNS Analyzer")
19
+ self.mcp = FastMCP("mcpcap")
21
20
 
22
21
  # Initialize modules
23
22
  self.dns_module = DNSModule(config)
@@ -25,8 +24,7 @@ class MCPServer:
25
24
  # Register tools
26
25
  self._register_tools()
27
26
 
28
- # Setup resources and prompts
29
- setup_resources(self.mcp)
27
+ # Setup prompts
30
28
  self.dns_module.setup_prompts(self.mcp)
31
29
 
32
30
  def _register_tools(self) -> None:
@@ -37,5 +35,13 @@ class MCPServer:
37
35
 
38
36
  def run(self) -> None:
39
37
  """Start the MCP server."""
40
- print(f"Starting MCP server with PCAP directory: {self.config.pcap_path}")
38
+ import sys
39
+
40
+ # Log to stderr to avoid breaking MCP JSON-RPC protocol
41
+ source = (
42
+ self.config.pcap_url if self.config.is_remote else self.config.pcap_path
43
+ )
44
+ source_type = "remote URL" if self.config.is_remote else "directory"
45
+ print(f"Starting MCP server with PCAP {source_type}: {source}", file=sys.stderr)
46
+
41
47
  self.mcp.run()
mcpcap/modules/dns.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """DNS analysis module."""
2
2
 
3
3
  import os
4
+ import tempfile
4
5
  from datetime import datetime
5
6
  from typing import Any
6
7
 
@@ -18,45 +19,118 @@ class DNSModule(BaseModule):
18
19
  """Return the name of the protocol this module analyzes."""
19
20
  return "DNS"
20
21
 
21
- def list_dns_packets(self, pcap_file: str = "example.pcap") -> dict[str, Any]:
22
+ def list_dns_packets(self, pcap_file: str = "") -> dict[str, Any]:
22
23
  """
23
24
  Analyze DNS packets from a PCAP file and return a summary of each packet.
24
25
 
25
26
  Args:
26
- pcap_file: Path to the PCAP file to analyze (defaults to 'example.pcap')
27
+ pcap_file: Path to the PCAP file to analyze. Leave empty for direct URL remotes
28
+ or when using the first available file in local directories.
27
29
 
28
30
  Returns:
29
31
  A structured dictionary containing DNS packet analysis results
30
32
  """
31
- # Get full path to PCAP file
32
- full_path = self.config.get_pcap_file_path(pcap_file)
33
+ # Handle remote files
34
+ if self.config.is_remote:
35
+ # For direct file URLs, always use the URL file (ignore pcap_file parameter)
36
+ if self.config.is_direct_file_url:
37
+ available_files = self.config.list_pcap_files()
38
+ if not available_files:
39
+ return {
40
+ "error": "No PCAP file found at the provided URL",
41
+ "pcap_url": self.config.pcap_url,
42
+ }
43
+ pcap_file = available_files[0] # Use the actual filename from URL
44
+ elif not pcap_file:
45
+ # For directory URLs, if no file specified, use the first available
46
+ available_files = self.config.list_pcap_files()
47
+ if not available_files:
48
+ return {
49
+ "error": "No PCAP files found at the provided URL",
50
+ "pcap_url": self.config.pcap_url,
51
+ "available_files": [],
52
+ }
53
+ pcap_file = available_files[0]
33
54
 
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
- }
55
+ # Download remote file to temporary location
56
+ try:
57
+ with tempfile.NamedTemporaryFile(
58
+ suffix=".pcap", delete=False
59
+ ) as tmp_file:
60
+ temp_path = tmp_file.name
61
+
62
+ local_path = self.config.download_pcap_file(pcap_file, temp_path)
63
+ result = self.analyze_packets(local_path)
64
+
65
+ # Clean up temporary file
66
+ try:
67
+ os.unlink(local_path)
68
+ except OSError:
69
+ pass # Ignore cleanup errors
70
+
71
+ return result
72
+
73
+ except Exception as e:
74
+ # List available PCAP files for help
75
+ available_files = self.config.list_pcap_files()
76
+ return {
77
+ "error": f"Failed to download PCAP file '{pcap_file}': {str(e)}",
78
+ "available_files": available_files,
79
+ "pcap_source": self.config.pcap_url,
80
+ }
81
+ else:
82
+ # Local file handling
83
+ if not pcap_file:
84
+ # If no file specified, use the first available local 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
+ # List available PCAP files for help
99
+ available_files = self.config.list_pcap_files()
100
+ return {
101
+ "error": f"PCAP file '{pcap_file}' not found",
102
+ "available_files": available_files,
103
+ "pcap_directory": self.config.pcap_path,
104
+ }
43
105
 
44
- return self.analyze_packets(full_path)
106
+ return self.analyze_packets(full_path)
45
107
 
46
108
  def list_pcap_files(self) -> str:
47
109
  """
48
- List all available PCAP files in the default directory.
110
+ List all available PCAP files in the configured directory or remote URL.
49
111
 
50
112
  Returns:
51
113
  A list of available PCAP files that can be analyzed
52
114
  """
53
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
+
54
120
  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
- )
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
+ )
58
131
  else:
59
- return f"No PCAP files found in {self.config.pcap_path}"
132
+ source_type = "remote server" if self.config.is_remote else "directory"
133
+ return f"No PCAP files found in {source_type} {source}"
60
134
 
61
135
  def analyze_packets(self, pcap_file: str) -> dict[str, Any]:
62
136
  """Analyze DNS packets in a PCAP file.
@@ -79,8 +153,15 @@ class DNSModule(BaseModule):
79
153
  "message": "No DNS packets found in this capture",
80
154
  }
81
155
 
156
+ # Apply max_packets limit if specified
157
+ packets_to_analyze = dns_packets
158
+ limited = False
159
+ if self.config.max_packets and len(dns_packets) > self.config.max_packets:
160
+ packets_to_analyze = dns_packets[: self.config.max_packets]
161
+ limited = True
162
+
82
163
  packet_details = []
83
- for i, pkt in enumerate(dns_packets, 1):
164
+ for i, pkt in enumerate(packets_to_analyze, 1):
84
165
  packet_info = self._analyze_dns_packet(pkt, i)
85
166
  packet_details.append(packet_info)
86
167
 
@@ -92,10 +173,17 @@ class DNSModule(BaseModule):
92
173
  "analysis_timestamp": datetime.now().isoformat(),
93
174
  "total_packets_in_file": len(packets),
94
175
  "dns_packets_found": len(dns_packets),
176
+ "dns_packets_analyzed": len(packet_details),
95
177
  "statistics": stats,
96
178
  "packets": packet_details,
97
179
  }
98
180
 
181
+ # Add information about packet limiting
182
+ if limited:
183
+ result["note"] = (
184
+ f"Analysis limited to first {self.config.max_packets} DNS packets due to --max-packets setting"
185
+ )
186
+
99
187
  return result
100
188
 
101
189
  except Exception as e:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcpcap
3
- Version: 0.2.3
3
+ Version: 0.3.1
4
4
  Summary: A modular Python MCP Server for analyzing PCAP files
5
5
  Author: mcpcap contributors
6
6
  License: MIT
@@ -23,6 +23,7 @@ Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: fastmcp
25
25
  Requires-Dist: scapy
26
+ Requires-Dist: requests
26
27
  Provides-Extra: test
27
28
  Requires-Dist: pytest; extra == "test"
28
29
  Requires-Dist: pytest-cov; extra == "test"
@@ -85,10 +86,26 @@ uvx mcpcap
85
86
 
86
87
  1. **Start the MCP Server**:
87
88
 
89
+ **Local PCAP file:**
90
+ ```bash
91
+ mcpcap --pcap-path /path/to/specific/file.pcap
92
+ ```
93
+
94
+ **Local PCAP directory:**
88
95
  ```bash
89
96
  mcpcap --pcap-path /path/to/pcap/files
90
97
  ```
91
98
 
99
+ **Remote PCAP file:**
100
+ ```bash
101
+ mcpcap --pcap-url https://example.com/sample.pcap
102
+ ```
103
+
104
+ **With advanced options:**
105
+ ```bash
106
+ mcpcap --pcap-path /path/to/pcaps --max-packets 100 --protocols dns
107
+ ```
108
+
92
109
  2. **Connect your LLM client** to the MCP server
93
110
 
94
111
  3. **Ask questions** about your network traffic:
@@ -121,27 +138,80 @@ The DNS module analyzes Domain Name System packets in PCAP files.
121
138
 
122
139
  ### PCAP Sources
123
140
 
124
- **Local Directory**:
141
+ mcpcap supports multiple ways to specify PCAP data sources:
125
142
 
143
+ **Local PCAP File**:
144
+ ```bash
145
+ mcpcap --pcap-path /local/path/to/specific.pcap
146
+ ```
147
+
148
+ **Local Directory**:
126
149
  ```bash
127
150
  mcpcap --pcap-path /local/path/to/pcaps
128
151
  ```
129
152
 
130
- **Remote HTTP Server**:
153
+ **Remote PCAP File (Direct Link)**:
154
+ ```bash
155
+ mcpcap --pcap-url https://wiki.wireshark.org/uploads/dns.cap
156
+ ```
131
157
 
158
+ **Remote Directory Listing**:
132
159
  ```bash
133
160
  mcpcap --pcap-url http://example.com/pcaps/
134
161
  ```
135
162
 
136
- ### Module Selection
163
+ ### Analysis Options
137
164
 
165
+ **Module Selection**:
138
166
  ```bash
139
167
  mcpcap --modules dns --pcap-path /path/to/files
140
168
  ```
141
169
 
170
+ **Protocol Filtering**:
171
+ ```bash
172
+ mcpcap --protocols dns --pcap-path /path/to/files
173
+ ```
174
+
175
+ **Packet Limiting** (for large files):
176
+ ```bash
177
+ mcpcap --max-packets 1000 --pcap-path /path/to/files
178
+ ```
179
+
180
+ **Combined Options**:
181
+ ```bash
182
+ mcpcap --pcap-path /data/capture.pcap --max-packets 500 --protocols dns
183
+ ```
184
+
185
+ ## CLI Reference
186
+
187
+ ```bash
188
+ mcpcap [--pcap-path PATH | --pcap-url URL] [OPTIONS]
189
+ ```
190
+
191
+ **Source Options** (choose one):
192
+ - `--pcap-path PATH`: Local PCAP file or directory
193
+ - `--pcap-url URL`: Remote PCAP file URL or directory listing
194
+
195
+ **Analysis Options**:
196
+ - `--modules MODULES`: Comma-separated modules to load (default: dns)
197
+ - `--protocols PROTOCOLS`: Comma-separated protocols to analyze (default: dns)
198
+ - `--max-packets N`: Maximum packets to analyze per file (default: unlimited)
199
+
200
+ **Examples**:
201
+ ```bash
202
+ # Analyze specific file
203
+ mcpcap --pcap-path ./capture.pcap
204
+
205
+ # Remote file with packet limit
206
+ mcpcap --pcap-url https://example.com/dns.cap --max-packets 100
207
+
208
+ # Directory with protocol filter
209
+ mcpcap --pcap-path /captures --protocols dns --modules dns
210
+ ```
211
+
142
212
  ## Example
143
213
 
144
- An example PCAP file (`example.pcap`) containing DNS traffic is included with the project to help you get started.
214
+ An example PCAP file (`dns.pcap`) containing DNS traffic is included in the `examples/` directory to help you get started.
145
215
 
146
216
  ## Architecture
147
217
 
@@ -170,7 +240,26 @@ Future modules might include:
170
240
 
171
241
  ## Remote Access
172
242
 
173
- mcpcap supports reading PCAP files from remote HTTP servers without authentication. Future versions may include support for Basic Authentication and other security mechanisms.
243
+ mcpcap supports reading PCAP files from remote HTTP servers in two modes:
244
+
245
+ **Direct File Access**: Point directly to a PCAP file URL
246
+ ```bash
247
+ mcpcap --pcap-url https://wiki.wireshark.org/uploads/__moin_import__/attachments/SampleCaptures/dns.cap
248
+ ```
249
+
250
+ **Directory Listing**: Parse HTML directory listings to find PCAP files
251
+ ```bash
252
+ mcpcap --pcap-url http://server.com/pcap-files/
253
+ ```
254
+
255
+ **Supported File Types**: `.pcap`, `.pcapng`, `.cap`
256
+
257
+ **Current Limitations**:
258
+ - HTTP/HTTPS only (no authentication)
259
+ - Directory listings require standard HTML format
260
+ - Files are downloaded temporarily for analysis
261
+
262
+ Future versions may include support for Basic Authentication and other security mechanisms.
174
263
 
175
264
  ## Contributing
176
265
 
@@ -190,8 +279,10 @@ MIT
190
279
  ## Requirements
191
280
 
192
281
  - Python 3.10+
193
- - scapy
194
- - MCP server dependencies (automatically installed)
282
+ - scapy (packet parsing and analysis)
283
+ - requests (HTTP remote file access)
284
+ - fastmcp (MCP server framework)
285
+ - All dependencies are automatically installed via pip
195
286
 
196
287
  ## Support
197
288
 
@@ -0,0 +1,15 @@
1
+ mcpcap/__init__.py,sha256=rJwCpBXkhIvmsqHFpeR33Vg8kuipNPJ2JdlAjsTk7I4,1408
2
+ mcpcap/_version.py,sha256=gGLpQUQx-ty9SEy9PYw9OgJWWzJLBnCpfJOfzL7SjlI,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.1.dist-info/licenses/LICENSE,sha256=Ltj0zxftQyBYQMNva935v0i5QXQQOF8ygE8dQxGEtjk,1063
11
+ mcpcap-0.3.1.dist-info/METADATA,sha256=GCTSg601dKs06-KMnD6IqwSqXSubYSdYbsLY_eUVxhA,7854
12
+ mcpcap-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ mcpcap-0.3.1.dist-info/entry_points.txt,sha256=ck69gPBEopmU6mzQy9P6o6ssMr89bQbrvv51IaJ50Gc,39
14
+ mcpcap-0.3.1.dist-info/top_level.txt,sha256=YkRkVGjuM3nI7cVB1l8zIAeqiS_5_vrzbUcHNkH3OXE,7
15
+ mcpcap-0.3.1.dist-info/RECORD,,
@@ -1,5 +0,0 @@
1
- """Resources for mcpcap analysis."""
2
-
3
- from .references import setup_resources
4
-
5
- __all__ = ["setup_resources"]
@@ -1,90 +0,0 @@
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
- """
@@ -1,17 +0,0 @@
1
- mcpcap/__init__.py,sha256=rJwCpBXkhIvmsqHFpeR33Vg8kuipNPJ2JdlAjsTk7I4,1408
2
- mcpcap/_version.py,sha256=kBRz0P2plw1eVdIpt70W6m1LMbEIhLY3RyOfVGdubaI,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.3.dist-info/licenses/LICENSE,sha256=Ltj0zxftQyBYQMNva935v0i5QXQQOF8ygE8dQxGEtjk,1063
13
- mcpcap-0.2.3.dist-info/METADATA,sha256=kGG7xi-5-Oxr84LypLbq_rnTP838VCiKXvBaynOOLQU,5530
14
- mcpcap-0.2.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- mcpcap-0.2.3.dist-info/entry_points.txt,sha256=ck69gPBEopmU6mzQy9P6o6ssMr89bQbrvv51IaJ50Gc,39
16
- mcpcap-0.2.3.dist-info/top_level.txt,sha256=YkRkVGjuM3nI7cVB1l8zIAeqiS_5_vrzbUcHNkH3OXE,7
17
- mcpcap-0.2.3.dist-info/RECORD,,
File without changes