#
tokens: 10277/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── index.js
├── LICENSE
├── package.json
├── README.md
└── Wire-MCP.png
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | npm-debug.log
 4 | yarn-debug.log
 5 | yarn-error.log
 6 | package-lock.json
 7 | yarn.lock
 8 | .pcap
 9 | *.cap
10 | 
11 | # Environment variables
12 | .env
13 | .env.local
14 | .env.*.local
15 | 
16 | # PCAP files (usually large and contain sensitive data)
17 | *.pcap
18 | *.pcapng
19 | captures/
20 | 
21 | # Operating System
22 | .DS_Store
23 | .DS_Store?
24 | ._*
25 | .Spotlight-V100
26 | .Trashes
27 | ehthumbs.db
28 | Thumbs.db
29 | 
30 | # IDE and Editor files
31 | .idea/
32 | .vscode/
33 | *.swp
34 | *.swo
35 | *~
36 | 
37 | # Logs
38 | logs/
39 | *.log
40 | npm-debug.log*
41 | yarn-debug.log*
42 | yarn-error.log*
43 | 
44 | # Build output
45 | dist/
46 | build/
47 | out/
48 | 
49 | # Test coverage
50 | coverage/
51 | 
52 | # Temporary files
53 | tmp/
54 | temp/
55 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | ![Wire-MCP Banner](Wire-MCP.png)
  2 | 
  3 | 
  4 | # WireMCP
  5 | WireMCP is a Model Context Protocol (MCP) server designed to empower Large Language Models (LLMs) with real-time network traffic analysis capabilities. By leveraging tools built on top of Wireshark's `tshark`, WireMCP captures and processes live network data, providing LLMs with structured context to assist in tasks like threat hunting, network diagnostics, and anomaly detection.
  6 | 
  7 | # Features
  8 | WireMCP exposes the following tools to MCP clients, enhancing LLM understanding of network activity:
  9 | 
 10 | - **`capture_packets`**: Captures live traffic and returns raw packet data as JSON, enabling LLMs to analyze packet-level details (e.g., IP addresses, ports, HTTP methods).
 11 | - **`get_summary_stats`**: Provides protocol hierarchy statistics, giving LLMs an overview of traffic composition (e.g., TCP vs. UDP usage).
 12 | - **`get_conversations`**: Delivers TCP/UDP conversation statistics, allowing LLMs to track communication flows between endpoints.
 13 | - **`check_threats`**: Captures IPs and checks them against the URLhaus blacklist, equipping LLMs with threat intelligence context for identifying malicious activity.
 14 | - **`check_ip_threats`**: Performs targeted threat intelligence lookups for specific IP addresses against multiple threat feeds, providing detailed reputation and threat data.
 15 | - **`analyze_pcap`**: Analyzes PCAP files to provide comprehensive packet data in JSON format, enabling detailed post-capture analysis of network traffic.
 16 | - **`extract_credentials`**: Scans PCAP files for potential credentials from various protocols (HTTP Basic Auth, FTP, Telnet), aiding in security audits and forensic analysis.
 17 | 
 18 | 
 19 | ## How It Helps LLMs
 20 | WireMCP bridges the gap between raw network data and LLM comprehension by:
 21 | - **Contextualizing Traffic**: Converts live packet captures into structured outputs (JSON, stats) that LLMs can parse and reason about.
 22 | - **Threat Detection**: Integrates IOCs (currently URLhaus) to flag suspicious IPs, enhancing LLM-driven security analysis.
 23 | - **Diagnostics**: Offers detailed traffic insights, enabling LLMs to assist with troubleshooting or identifying anomalies.
 24 | - **Narrative Generation**: LLM's can Transform complex packet captures into coherent stories, making network analysis accessible to non-technical users.
 25 | 
 26 | # Installation
 27 | 
 28 | ## Prerequisites
 29 | - Mac / Windows / Linux
 30 | - [Wireshark](https://www.wireshark.org/download.html) (with `tshark` installed and accessible in PATH)
 31 | - Node.js (v16+ recommended)
 32 | - npm (for dependency installation)
 33 | 
 34 | ## Setup
 35 | 1. Clone the repository:
 36 |    ```bash
 37 |    git clone https://github.com/0xkoda/WireMCP.git
 38 |    cd WireMCP
 39 |    ```
 40 | 
 41 | 2. Install dependencies:
 42 |    ```bash
 43 |    npm install
 44 |    ```
 45 | 
 46 | 3. Run the MCP server:
 47 |    ```bash
 48 |    node index.js
 49 |    ```
 50 | 
 51 | > **Note**: Ensure `tshark` is in your PATH. WireMCP will auto-detect it or fall back to common install locations (e.g., `/Applications/Wireshark.app/Contents/MacOS/tshark` on macOS).
 52 | 
 53 | # Usage with MCP Clients
 54 | 
 55 | WireMCP works with any MCP-compliant client. Below are examples for popular clients:
 56 | 
 57 | ## Example 1: Cursor
 58 | 
 59 | Edit `mcp.json` in Cursor -> Settings -> MCP :
 60 | 
 61 | ```json
 62 | {
 63 |   "mcpServers": {
 64 |     "wiremcp": {
 65 |       "command": "node",
 66 |       "args": [
 67 |         "/ABSOLUTE_PATH_TO/WireMCP/index.js"
 68 |       ]
 69 |     }
 70 |   }
 71 | }
 72 | ```
 73 | 
 74 | **Location (macOS)**: `/Users/YOUR_USER/Library/Application Support/Claude/claude_desktop_config.json`
 75 | 
 76 | ## Other Clients
 77 | 
 78 | This MCP will work well with any client. Use the command `node /path/to/WireMCP/index.js` in their MCP server settings.
 79 | 
 80 | # Example Output
 81 | 
 82 | Running `check_threats` might yield:
 83 | 
 84 | ```
 85 | Captured IPs:
 86 | 174.67.0.227
 87 | 52.196.136.253
 88 | 
 89 | Threat check against URLhaus blacklist:
 90 | No threats detected in URLhaus blacklist.
 91 | ```
 92 | 
 93 | Running `analyze_pcap` on a capture file:
 94 | 
 95 | ```json
 96 | {
 97 |   "content": [{
 98 |     "type": "text",
 99 |     "text": "Analyzed PCAP: ./capture.pcap\n\nUnique IPs:\n192.168.0.2\n192.168.0.1\n\nProtocols:\neth:ethertype:ip:tcp\neth:ethertype:ip:tcp:telnet\n\nPacket Data:\n[{\"layers\":{\"frame.number\":[\"1\"],\"ip.src\":[\"192.168.0.2\"],\"ip.dst\":[\"192.168.0.1\"],\"tcp.srcport\":[\"1550\"],\"tcp.dstport\":[\"23\"]}}]"
100 |   }]
101 | }
102 | ```
103 | 
104 | 
105 | LLMs can use these outputs to:
106 | - Provide natural language explanations of network activity
107 | - Identify patterns and potential security concerns
108 | - Offer context-aware recommendations
109 | - Generate human-readable reports
110 | 
111 | # Roadmap
112 | 
113 | - **Expand IOC Providers**: Currently uses URLhaus for threat checks. Future updates will integrate additional sources (e.g., IPsum, Emerging Threats) for broader coverage.
114 | 
115 | 
116 | # Contributing
117 | 
118 | Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
119 | 
120 | # License
121 | 
122 | [MIT](LICENSE)
123 | 
124 | # Acknowledgments
125 | 
126 | - Wireshark/tshark team for their excellent packet analysis tools
127 | - Model Context Protocol community for the framework and specifications
128 | - URLhaus for providing threat intelligence data
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "wiremcp",
 3 |   "version": "1.0.0",
 4 |   "description": "An MCP for network sleuthing",
 5 |   "main": "index.js",
 6 |   "scripts": {
 7 |     "test": "echo \"Error: no test specified\" && exit 1"
 8 |   },
 9 |   "author": "0xKoda",
10 |   "license": "MIT",
11 |   "dependencies": {
12 |     "@modelcontextprotocol/sdk": "^1.8.0",
13 |     "axios": "^1.8.4",
14 |     "child_process": "^1.0.2",
15 |     "util": "^0.12.5",
16 |     "which": "^5.0.0",
17 |     "zod": "^3.24.2"
18 |   }
19 | }
20 | 
```

--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------

```javascript
  1 | // index.js - WireMCP Server
  2 | const axios = require('axios');
  3 | const { exec } = require('child_process');
  4 | const { promisify } = require('util');
  5 | const which = require('which');
  6 | const fs = require('fs').promises;
  7 | const execAsync = promisify(exec);
  8 | const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
  9 | const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
 10 | const { z } = require('zod');
 11 | 
 12 | // Redirect console.log to stderr
 13 | const originalConsoleLog = console.log;
 14 | console.log = (...args) => console.error(...args);
 15 | 
 16 | // Dynamically locate tshark
 17 | async function findTshark() {
 18 |   try {
 19 |     const tsharkPath = await which('tshark');
 20 |     console.error(`Found tshark at: ${tsharkPath}`);
 21 |     return tsharkPath;
 22 |   } catch (err) {
 23 |     console.error('which failed to find tshark:', err.message);
 24 |     const fallbacks = process.platform === 'win32'
 25 |       ? ['C:\\Program Files\\Wireshark\\tshark.exe', 'C:\\Program Files (x86)\\Wireshark\\tshark.exe']
 26 |       : ['/usr/bin/tshark', '/usr/local/bin/tshark', '/opt/homebrew/bin/tshark', '/Applications/Wireshark.app/Contents/MacOS/tshark'];
 27 |     
 28 |     for (const path of fallbacks) {
 29 |       try {
 30 |         await execAsync(`${path} -v`);
 31 |         console.error(`Found tshark at fallback: ${path}`);
 32 |         return path;
 33 |       } catch (e) {
 34 |         console.error(`Fallback ${path} failed: ${e.message}`);
 35 |       }
 36 |     }
 37 |     throw new Error('tshark not found. Please install Wireshark (https://www.wireshark.org/download.html) and ensure tshark is in your PATH.');
 38 |   }
 39 | }
 40 | 
 41 | // Initialize MCP server
 42 | const server = new McpServer({
 43 |   name: 'wiremcp',
 44 |   version: '1.0.0',
 45 | });
 46 | 
 47 | // Tool 1: Capture live packet data
 48 | server.tool(
 49 |   'capture_packets',
 50 |   'Capture live traffic and provide raw packet data as JSON for LLM analysis',
 51 |   {
 52 |     interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'),
 53 |     duration: z.number().optional().default(5).describe('Capture duration in seconds'),
 54 |   },
 55 |   async (args) => {
 56 |     try {
 57 |       const tsharkPath = await findTshark();
 58 |       const { interface, duration } = args;
 59 |       const tempPcap = 'temp_capture.pcap';
 60 |       console.error(`Capturing packets on ${interface} for ${duration}s`);
 61 | 
 62 |       await execAsync(
 63 |         `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`,
 64 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
 65 |       );
 66 | 
 67 |       const { stdout, stderr } = await execAsync(
 68 |         `${tsharkPath} -r "${tempPcap}" -T json -e frame.number -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e tcp.flags -e frame.time -e http.request.method -e http.response.code`,
 69 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
 70 |       );
 71 |       if (stderr) console.error(`tshark stderr: ${stderr}`);
 72 |       let packets = JSON.parse(stdout);
 73 | 
 74 |       const maxChars = 720000;
 75 |       let jsonString = JSON.stringify(packets);
 76 |       if (jsonString.length > maxChars) {
 77 |         const trimFactor = maxChars / jsonString.length;
 78 |         const trimCount = Math.floor(packets.length * trimFactor);
 79 |         packets = packets.slice(0, trimCount);
 80 |         jsonString = JSON.stringify(packets);
 81 |         console.error(`Trimmed packets from ${packets.length} to ${trimCount} to fit ${maxChars} chars`);
 82 |       }
 83 | 
 84 |       await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`));
 85 | 
 86 |       return {
 87 |         content: [{
 88 |           type: 'text',
 89 |           text: `Captured packet data (JSON for LLM analysis):\n${jsonString}`,
 90 |         }],
 91 |       };
 92 |     } catch (error) {
 93 |       console.error(`Error in capture_packets: ${error.message}`);
 94 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
 95 |     }
 96 |   }
 97 | );
 98 | 
 99 | // Tool 2: Capture and provide summary statistics
100 | server.tool(
101 |   'get_summary_stats',
102 |   'Capture live traffic and provide protocol hierarchy statistics for LLM analysis',
103 |   {
104 |     interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'),
105 |     duration: z.number().optional().default(5).describe('Capture duration in seconds'),
106 |   },
107 |   async (args) => {
108 |     try {
109 |       const tsharkPath = await findTshark();
110 |       const { interface, duration } = args;
111 |       const tempPcap = 'temp_capture.pcap';
112 |       console.error(`Capturing summary stats on ${interface} for ${duration}s`);
113 | 
114 |       await execAsync(
115 |         `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`,
116 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
117 |       );
118 | 
119 |       const { stdout, stderr } = await execAsync(
120 |         `${tsharkPath} -r "${tempPcap}" -qz io,phs`,
121 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
122 |       );
123 |       if (stderr) console.error(`tshark stderr: ${stderr}`);
124 | 
125 |       await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`));
126 | 
127 |       return {
128 |         content: [{
129 |           type: 'text',
130 |           text: `Protocol hierarchy statistics for LLM analysis:\n${stdout}`,
131 |         }],
132 |       };
133 |     } catch (error) {
134 |       console.error(`Error in get_summary_stats: ${error.message}`);
135 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
136 |     }
137 |   }
138 | );
139 | 
140 | // Tool 3: Capture and provide conversation stats
141 | server.tool(
142 |   'get_conversations',
143 |   'Capture live traffic and provide TCP/UDP conversation statistics for LLM analysis',
144 |   {
145 |     interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'),
146 |     duration: z.number().optional().default(5).describe('Capture duration in seconds'),
147 |   },
148 |   async (args) => {
149 |     try {
150 |       const tsharkPath = await findTshark();
151 |       const { interface, duration } = args;
152 |       const tempPcap = 'temp_capture.pcap';
153 |       console.error(`Capturing conversations on ${interface} for ${duration}s`);
154 | 
155 |       await execAsync(
156 |         `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`,
157 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
158 |       );
159 | 
160 |       const { stdout, stderr } = await execAsync(
161 |         `${tsharkPath} -r "${tempPcap}" -qz conv,tcp`,
162 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
163 |       );
164 |       if (stderr) console.error(`tshark stderr: ${stderr}`);
165 | 
166 |       await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`));
167 | 
168 |       return {
169 |         content: [{
170 |           type: 'text',
171 |           text: `TCP/UDP conversation statistics for LLM analysis:\n${stdout}`,
172 |         }],
173 |       };
174 |     } catch (error) {
175 |       console.error(`Error in get_conversations: ${error.message}`);
176 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
177 |     }
178 |   }
179 | );
180 | 
181 | // Tool 4: Capture traffic and check threats against URLhaus
182 | server.tool(
183 |   'check_threats',
184 |   'Capture live traffic and check IPs against URLhaus blacklist',
185 |   {
186 |     interface: z.string().optional().default('en0').describe('Network interface to capture from (e.g., eth0, en0)'),
187 |     duration: z.number().optional().default(5).describe('Capture duration in seconds'),
188 |   },
189 |   async (args) => {
190 |     try {
191 |       const tsharkPath = await findTshark();
192 |       const { interface, duration } = args;
193 |       const tempPcap = 'temp_capture.pcap';
194 |       console.error(`Capturing traffic on ${interface} for ${duration}s to check threats`);
195 | 
196 |       await execAsync(
197 |         `${tsharkPath} -i ${interface} -w ${tempPcap} -a duration:${duration}`,
198 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
199 |       );
200 | 
201 |       const { stdout } = await execAsync(
202 |         `${tsharkPath} -r "${tempPcap}" -T fields -e ip.src -e ip.dst`,
203 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
204 |       );
205 |       const ips = [...new Set(stdout.split('\n').flatMap(line => line.split('\t')).filter(ip => ip && ip !== 'unknown'))];
206 |       console.error(`Captured ${ips.length} unique IPs: ${ips.join(', ')}`);
207 | 
208 |       const urlhausUrl = 'https://urlhaus.abuse.ch/downloads/text/';
209 |       console.error(`Fetching URLhaus blacklist from ${urlhausUrl}`);
210 |       let urlhausData;
211 |       let urlhausThreats = [];
212 |       try {
213 |         const response = await axios.get(urlhausUrl);
214 |         console.error(`URLhaus response status: ${response.status}, length: ${response.data.length} chars`);
215 |         console.error(`URLhaus raw data (first 200 chars): ${response.data.slice(0, 200)}`);
216 |         const ipRegex = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
217 |         urlhausData = [...new Set(response.data.split('\n')
218 |           .map(line => {
219 |             const match = line.match(ipRegex);
220 |             return match ? match[0] : null;
221 |           })
222 |           .filter(ip => ip))];
223 |         console.error(`URLhaus lookup successful: ${urlhausData.length} blacklist IPs fetched`);
224 |         console.error(`Sample URLhaus IPs: ${urlhausData.slice(0, 5).join(', ') || 'None'}`);
225 |         urlhausThreats = ips.filter(ip => urlhausData.includes(ip));
226 |         console.error(`Checked IPs against URLhaus: ${urlhausThreats.length} threats found - ${urlhausThreats.join(', ') || 'None'}`);
227 |       } catch (e) {
228 |         console.error(`Failed to fetch URLhaus data: ${e.message}`);
229 |         urlhausData = [];
230 |       }
231 | 
232 |       const outputText = `Captured IPs:\n${ips.join('\n')}\n\n` +
233 |         `Threat check against URLhaus blacklist:\n${
234 |           urlhausThreats.length > 0 ? `Potential threats: ${urlhausThreats.join(', ')}` : 'No threats detected in URLhaus blacklist.'
235 |         }`;
236 | 
237 |       await fs.unlink(tempPcap).catch(err => console.error(`Failed to delete ${tempPcap}: ${err.message}`));
238 | 
239 |       return {
240 |         content: [{ type: 'text', text: outputText }],
241 |       };
242 |     } catch (error) {
243 |       console.error(`Error in check_threats: ${error.message}`);
244 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
245 |     }
246 |   }
247 | );
248 | 
249 | // Tool 5: Check a specific IP against URLhaus IOCs
250 | server.tool(
251 |   'check_ip_threats',
252 |   'Check a given IP address against URLhaus blacklist for IOCs',
253 |   {
254 |     ip: z.string().regex(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/).describe('IP address to check (e.g., 192.168.1.1)'),
255 |   },
256 |   async (args) => {
257 |     try {
258 |       const { ip } = args;
259 |       console.error(`Checking IP ${ip} against URLhaus blacklist`);
260 | 
261 |       const urlhausUrl = 'https://urlhaus.abuse.ch/downloads/text/';
262 |       console.error(`Fetching URLhaus blacklist from ${urlhausUrl}`);
263 |       let urlhausData;
264 |       let isThreat = false;
265 |       try {
266 |         const response = await axios.get(urlhausUrl);
267 |         console.error(`URLhaus response status: ${response.status}, length: ${response.data.length} chars`);
268 |         console.error(`URLhaus raw data (first 200 chars): ${response.data.slice(0, 200)}`);
269 |         const ipRegex = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
270 |         urlhausData = [...new Set(response.data.split('\n')
271 |           .map(line => {
272 |             const match = line.match(ipRegex);
273 |             return match ? match[0] : null;
274 |           })
275 |           .filter(ip => ip))];
276 |         console.error(`URLhaus lookup successful: ${urlhausData.length} blacklist IPs fetched`);
277 |         console.error(`Sample URLhaus IPs: ${urlhausData.slice(0, 5).join(', ') || 'None'}`);
278 |         isThreat = urlhausData.includes(ip);
279 |         console.error(`IP ${ip} checked against URLhaus: ${isThreat ? 'Threat found' : 'No threat found'}`);
280 |       } catch (e) {
281 |         console.error(`Failed to fetch URLhaus data: ${e.message}`);
282 |         urlhausData = [];
283 |       }
284 | 
285 |       const outputText = `IP checked: ${ip}\n\n` +
286 |         `Threat check against URLhaus blacklist:\n${
287 |           isThreat ? 'Potential threat detected in URLhaus blacklist.' : 'No threat detected in URLhaus blacklist.'
288 |         }`;
289 | 
290 |       return {
291 |         content: [{ type: 'text', text: outputText }],
292 |       };
293 |     } catch (error) {
294 |       console.error(`Error in check_ip_threats: ${error.message}`);
295 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
296 |     }
297 |   }
298 | );
299 | 
300 | // Tool 6: Analyze an existing PCAP file for general context
301 | server.tool(
302 |   'analyze_pcap',
303 |   'Analyze a PCAP file and provide general packet data as JSON for LLM analysis',
304 |   {
305 |     pcapPath: z.string().describe('Path to the PCAP file to analyze (e.g., ./demo.pcap)'),
306 |   },
307 |   async (args) => {
308 |     try {
309 |       const tsharkPath = await findTshark();
310 |       const { pcapPath } = args;
311 |       console.error(`Analyzing PCAP file: ${pcapPath}`);
312 | 
313 |       // Check if file exists
314 |       await fs.access(pcapPath);
315 | 
316 |       // Extract broad packet data
317 |       const { stdout, stderr } = await execAsync(
318 |         `${tsharkPath} -r "${pcapPath}" -T json -e frame.number -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport -e http.host -e http.request.uri -e frame.protocols`,
319 |         { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
320 |       );
321 |       if (stderr) console.error(`tshark stderr: ${stderr}`);
322 |       const packets = JSON.parse(stdout);
323 | 
324 |       const ips = [...new Set(packets.flatMap(p => [
325 |         p._source?.layers['ip.src']?.[0],
326 |         p._source?.layers['ip.dst']?.[0]
327 |       ]).filter(ip => ip))];
328 |       console.error(`Found ${ips.length} unique IPs: ${ips.join(', ')}`);
329 | 
330 |       const urls = packets
331 |         .filter(p => p._source?.layers['http.host'] && p._source?.layers['http.request.uri'])
332 |         .map(p => `http://${p._source.layers['http.host'][0]}${p._source.layers['http.request.uri'][0]}`);
333 |       console.error(`Found ${urls.length} URLs: ${urls.join(', ') || 'None'}`);
334 | 
335 |       const protocols = [...new Set(packets.map(p => p._source?.layers['frame.protocols']?.[0]))].filter(p => p);
336 |       console.error(`Found protocols: ${protocols.join(', ') || 'None'}`);
337 | 
338 |       const maxChars = 720000;
339 |       let jsonString = JSON.stringify(packets);
340 |       if (jsonString.length > maxChars) {
341 |         const trimFactor = maxChars / jsonString.length;
342 |         const trimCount = Math.floor(packets.length * trimFactor);
343 |         packets.splice(trimCount);
344 |         jsonString = JSON.stringify(packets);
345 |         console.error(`Trimmed packets from ${packets.length} to ${trimCount} to fit ${maxChars} chars`);
346 |       }
347 | 
348 |       const outputText = `Analyzed PCAP: ${pcapPath}\n\n` +
349 |         `Unique IPs:\n${ips.join('\n')}\n\n` +
350 |         `URLs:\n${urls.length > 0 ? urls.join('\n') : 'None'}\n\n` +
351 |         `Protocols:\n${protocols.join('\n') || 'None'}\n\n` +
352 |         `Packet Data (JSON for LLM):\n${jsonString}`;
353 | 
354 |       return {
355 |         content: [{ type: 'text', text: outputText }],
356 |       };
357 |     } catch (error) {
358 |       console.error(`Error in analyze_pcap: ${error.message}`);
359 |       return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
360 |     }
361 |   }
362 | );
363 | 
364 | // Tool 7: Extract credentials from a PCAP file
365 | server.tool(
366 |     'extract_credentials',
367 |     'Extract potential credentials (HTTP Basic Auth, FTP, Telnet) from a PCAP file for LLM analysis',
368 |     {
369 |       pcapPath: z.string().describe('Path to the PCAP file to analyze (e.g., ./demo.pcap)'),
370 |     },
371 |     async (args) => {
372 |       try {
373 |         const tsharkPath = await findTshark();
374 |         const { pcapPath } = args;
375 |         console.error(`Extracting credentials from PCAP file: ${pcapPath}`);
376 |   
377 |         await fs.access(pcapPath);
378 |   
379 |         // Extract plaintext credentials
380 |         const { stdout: plaintextOut } = await execAsync(
381 |           `${tsharkPath} -r "${pcapPath}" -T fields -e http.authbasic -e ftp.request.command -e ftp.request.arg -e telnet.data -e frame.number`,
382 |           { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
383 |         );
384 | 
385 |         // Extract Kerberos credentials
386 |         const { stdout: kerberosOut } = await execAsync(
387 |           `${tsharkPath} -r "${pcapPath}" -T fields -e kerberos.CNameString -e kerberos.realm -e kerberos.cipher -e kerberos.type -e kerberos.msg_type -e frame.number`,
388 |           { env: { ...process.env, PATH: `${process.env.PATH}:/usr/bin:/usr/local/bin:/opt/homebrew/bin` } }
389 |         );
390 | 
391 |         const lines = plaintextOut.split('\n').filter(line => line.trim());
392 |         const packets = lines.map(line => {
393 |           const [authBasic, ftpCmd, ftpArg, telnetData, frameNumber] = line.split('\t');
394 |           return {
395 |             authBasic: authBasic || '',
396 |             ftpCmd: ftpCmd || '',
397 |             ftpArg: ftpArg || '',
398 |             telnetData: telnetData || '',
399 |             frameNumber: frameNumber || ''
400 |           };
401 |         });
402 |   
403 |         const credentials = {
404 |           plaintext: [],
405 |           encrypted: []
406 |         };
407 |   
408 |         // Process HTTP Basic Auth
409 |         packets.forEach(p => {
410 |           if (p.authBasic) {
411 |             const [username, password] = Buffer.from(p.authBasic, 'base64').toString().split(':');
412 |             credentials.plaintext.push({ type: 'HTTP Basic Auth', username, password, frame: p.frameNumber });
413 |           }
414 |         });
415 |   
416 |         // Process FTP
417 |         packets.forEach(p => {
418 |           if (p.ftpCmd === 'USER') {
419 |             credentials.plaintext.push({ type: 'FTP', username: p.ftpArg, password: '', frame: p.frameNumber });
420 |           }
421 |           if (p.ftpCmd === 'PASS') {
422 |             const lastUser = credentials.plaintext.findLast(c => c.type === 'FTP' && !c.password);
423 |             if (lastUser) lastUser.password = p.ftpArg;
424 |           }
425 |         });
426 |   
427 |         // Process Telnet
428 |         packets.forEach(p => {
429 |           if (p.telnetData) {
430 |             const telnetStr = p.telnetData.trim();
431 |             if (telnetStr.toLowerCase().includes('login:') || telnetStr.toLowerCase().includes('password:')) {
432 |               credentials.plaintext.push({ type: 'Telnet Prompt', data: telnetStr, frame: p.frameNumber });
433 |             } else if (telnetStr && !telnetStr.match(/[A-Z][a-z]+:/) && !telnetStr.includes(' ')) {
434 |               const lastPrompt = credentials.plaintext.findLast(c => c.type === 'Telnet Prompt');
435 |               if (lastPrompt && lastPrompt.data.toLowerCase().includes('login:')) {
436 |                 credentials.plaintext.push({ type: 'Telnet', username: telnetStr, password: '', frame: p.frameNumber });
437 |               } else if (lastPrompt && lastPrompt.data.toLowerCase().includes('password:')) {
438 |                 const lastUser = credentials.plaintext.findLast(c => c.type === 'Telnet' && !c.password);
439 |                 if (lastUser) lastUser.password = telnetStr;
440 |                 else credentials.plaintext.push({ type: 'Telnet', username: '', password: telnetStr, frame: p.frameNumber });
441 |               }
442 |             }
443 |           }
444 |         });
445 | 
446 |         // Process Kerberos credentials
447 |         const kerberosLines = kerberosOut.split('\n').filter(line => line.trim());
448 |         kerberosLines.forEach(line => {
449 |           const [cname, realm, cipher, type, msgType, frameNumber] = line.split('\t');
450 |           
451 |           if (cipher && type) {
452 |             let hashFormat = '';
453 |             // Format hash based on message type
454 |             if (msgType === '10' || msgType === '30') { // AS-REQ or TGS-REQ
455 |               hashFormat = '$krb5pa$23$';
456 |               if (cname) hashFormat += `${cname}$`;
457 |               if (realm) hashFormat += `${realm}$`;
458 |               hashFormat += cipher;
459 |             } else if (msgType === '11') { // AS-REP
460 |               hashFormat = '$krb5asrep$23$';
461 |               if (cname) hashFormat += `${cname}@`;
462 |               if (realm) hashFormat += `${realm}$`;
463 |               hashFormat += cipher;
464 |             }
465 | 
466 |             if (hashFormat) {
467 |               credentials.encrypted.push({
468 |                 type: 'Kerberos',
469 |                 hash: hashFormat,
470 |                 username: cname || 'unknown',
471 |                 realm: realm || 'unknown',
472 |                 frame: frameNumber,
473 |                 crackingMode: msgType === '11' ? 'hashcat -m 18200' : 'hashcat -m 7500'
474 |               });
475 |             }
476 |           }
477 |         });
478 | 
479 |         console.error(`Found ${credentials.plaintext.length} plaintext and ${credentials.encrypted.length} encrypted credentials`);
480 |   
481 |         const outputText = `Analyzed PCAP: ${pcapPath}\n\n` +
482 |           `Plaintext Credentials:\n${credentials.plaintext.length > 0 ? 
483 |             credentials.plaintext.map(c => 
484 |               c.type === 'Telnet Prompt' ? 
485 |                 `${c.type}: ${c.data} (Frame ${c.frame})` : 
486 |                 `${c.type}: ${c.username}:${c.password} (Frame ${c.frame})`
487 |             ).join('\n') : 
488 |             'None'}\n\n` +
489 |           `Encrypted/Hashed Credentials:\n${credentials.encrypted.length > 0 ?
490 |             credentials.encrypted.map(c =>
491 |               `${c.type}: User=${c.username} Realm=${c.realm} (Frame ${c.frame})\n` +
492 |               `Hash=${c.hash}\n` +
493 |               `Cracking Command: ${c.crackingMode}\n`
494 |             ).join('\n') :
495 |             'None'}\n\n` +
496 |           `Note: Encrypted credentials can be cracked using tools like John the Ripper or hashcat.\n` +
497 |           `For Kerberos hashes:\n` +
498 |           `- AS-REQ/TGS-REQ: hashcat -m 7500 or john --format=krb5pa-md5\n` +
499 |           `- AS-REP: hashcat -m 18200 or john --format=krb5asrep`;
500 |   
501 |         return {
502 |           content: [{ type: 'text', text: outputText }],
503 |         };
504 |       } catch (error) {
505 |         console.error(`Error in extract_credentials: ${error.message}`);
506 |         return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true };
507 |       }
508 |     }
509 |   );
510 | 
511 | // Add prompts for each tool
512 | server.prompt(
513 |   'capture_packets_prompt',
514 |   {
515 |     interface: z.string().optional().describe('Network interface to capture from'),
516 |     duration: z.number().optional().describe('Duration in seconds to capture'),
517 |   },
518 |   ({ interface = 'en0', duration = 5 }) => ({
519 |     messages: [{
520 |       role: 'user',
521 |       content: {
522 |         type: 'text',
523 |         text: `Please analyze the network traffic on interface ${interface} for ${duration} seconds and provide insights about:
524 | 1. The types of traffic observed
525 | 2. Any notable patterns or anomalies
526 | 3. Key IP addresses and ports involved
527 | 4. Potential security concerns`
528 |       }
529 |     }]
530 |   })
531 | );
532 | 
533 | server.prompt(
534 |   'summary_stats_prompt',
535 |   {
536 |     interface: z.string().optional().describe('Network interface to capture from'),
537 |     duration: z.number().optional().describe('Duration in seconds to capture'),
538 |   },
539 |   ({ interface = 'en0', duration = 5 }) => ({
540 |     messages: [{
541 |       role: 'user',
542 |       content: {
543 |         type: 'text',
544 |         text: `Please provide a summary of network traffic statistics from interface ${interface} over ${duration} seconds, focusing on:
545 | 1. Protocol distribution
546 | 2. Traffic volume by protocol
547 | 3. Notable patterns in protocol usage
548 | 4. Potential network health indicators`
549 |       }
550 |     }]
551 |   })
552 | );
553 | 
554 | server.prompt(
555 |   'conversations_prompt',
556 |   {
557 |     interface: z.string().optional().describe('Network interface to capture from'),
558 |     duration: z.number().optional().describe('Duration in seconds to capture'),
559 |   },
560 |   ({ interface = 'en0', duration = 5 }) => ({
561 |     messages: [{
562 |       role: 'user',
563 |       content: {
564 |         type: 'text',
565 |         text: `Please analyze network conversations on interface ${interface} for ${duration} seconds and identify:
566 | 1. Most active IP pairs
567 | 2. Conversation durations and data volumes
568 | 3. Unusual communication patterns
569 | 4. Potential indicators of network issues`
570 |       }
571 |     }]
572 |   })
573 | );
574 | 
575 | server.prompt(
576 |   'check_threats_prompt',
577 |   {
578 |     interface: z.string().optional().describe('Network interface to capture from'),
579 |     duration: z.number().optional().describe('Duration in seconds to capture'),
580 |   },
581 |   ({ interface = 'en0', duration = 5 }) => ({
582 |     messages: [{
583 |       role: 'user',
584 |       content: {
585 |         type: 'text',
586 |         text: `Please analyze traffic on interface ${interface} for ${duration} seconds and check for security threats:
587 | 1. Compare captured IPs against URLhaus blacklist
588 | 2. Identify potential malicious activity
589 | 3. Highlight any concerning patterns
590 | 4. Provide security recommendations`
591 |       }
592 |     }]
593 |   })
594 | );
595 | 
596 | server.prompt(
597 |   'check_ip_threats_prompt',
598 |   {
599 |     ip: z.string().describe('IP address to check'),
600 |   },
601 |   ({ ip }) => ({
602 |     messages: [{
603 |       role: 'user',
604 |       content: {
605 |         type: 'text',
606 |         text: `Please analyze the following IP address (${ip}) for potential security threats:
607 | 1. Check against URLhaus blacklist
608 | 2. Evaluate the IP's reputation
609 | 3. Identify any known malicious activity
610 | 4. Provide security recommendations`
611 |       }
612 |     }]
613 |   })
614 | );
615 | 
616 | server.prompt(
617 |   'analyze_pcap_prompt',
618 |   {
619 |     pcapPath: z.string().describe('Path to the PCAP file'),
620 |   },
621 |   ({ pcapPath }) => ({
622 |     messages: [{
623 |       role: 'user',
624 |       content: {
625 |         type: 'text',
626 |         text: `Please analyze the PCAP file at ${pcapPath} and provide insights about:
627 | 1. Overall traffic patterns
628 | 2. Unique IPs and their interactions
629 | 3. Protocols and services used
630 | 4. Notable events or anomalies
631 | 5. Potential security concerns`
632 |       }
633 |     }]
634 |   })
635 | );
636 | 
637 | server.prompt(
638 |   'extract_credentials_prompt',
639 |   {
640 |     pcapPath: z.string().describe('Path to the PCAP file'),
641 |   },
642 |   ({ pcapPath }) => ({
643 |     messages: [{
644 |       role: 'user',
645 |       content: {
646 |         type: 'text',
647 |         text: `Please analyze the PCAP file at ${pcapPath} for potential credential exposure:
648 | 1. Look for plaintext credentials (HTTP Basic Auth, FTP, Telnet)
649 | 2. Identify Kerberos authentication attempts
650 | 3. Extract any hashed credentials
651 | 4. Provide security recommendations for credential handling`
652 |       }
653 |     }]
654 |   })
655 | );
656 | 
657 | // Start the server
658 | server.connect(new StdioServerTransport())
659 |   .then(() => console.error('WireMCP Server is running...'))
660 |   .catch(err => {
661 |     console.error('Failed to start WireMCP:', err);
662 |     process.exit(1);
663 |   });
```