# Directory Structure
```
├── Bridge_Zeek_MCP.py
├── images
│ ├── example1.png
│ ├── example2.png
│ ├── example3.png
│ ├── logo.png
│ └── start.png
├── LICENSE
├── pcaps
│ ├── ex1.pcapng
│ ├── ex2.pcapng
│ └── Sample.pcap
├── README.md
└── requirements.txt
```
# Files
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | [](https://www.apache.org/licenses/LICENSE-2.0)
2 | [](https://github.com/Gabbo01/Zeek-MCP/releases)
3 | [](https://www.linkedin.com/in/gabriele-bencivenga-93797b147/)
4 |
5 | 
6 |
7 | # Zeek-MCP
8 |
9 | This repository provides a set of utilities to build an MCP server (Model Context Protocol) that you can integrate with your conversational AI client.
10 |
11 | ---
12 |
13 | ## Table of Contents
14 |
15 | * [Prerequisites](#prerequisites)
16 | * [Installation](#installation)
17 | * [Usage](#usage)
18 |
19 | * [1. Clone the repository](#1-clone-the-repository)
20 | * [2. Install dependencies](#2-install-dependencies)
21 | * [3. Run the MCP server](#3-run-the-mcp-server)
22 | * [4. Use the MCP tools](#4-use-the-mcp-tools)
23 | * [Examples](#examples)
24 | * [License](#license)
25 |
26 | ---
27 |
28 | ## Prerequisites
29 |
30 | * **Python 3.7+**
31 | * **Zeek** installed and available in your `PATH` (for the `execzeek` tool)
32 | * **pip** (for installing Python dependencies)
33 |
34 | ---
35 |
36 | ## Installation
37 |
38 | ### 1. Clone the repository
39 |
40 | ```bash
41 | git clone https://github.com/Gabbo01/Zeek-MCP
42 | cd Zeek-MCP
43 | ```
44 |
45 | ### 2. Install dependencies
46 |
47 | It's recommended to use a virtual environment:
48 |
49 | ```bash
50 | python -m venv venv
51 | source venv/bin/activate # Linux/macOS
52 | venv\Scripts\activate # Windows
53 | pip install -r requirements.txt
54 | ```
55 |
56 | > **Note:** If you don’t have a `requirements.txt`, install directly:
57 | >
58 | > ```bash
59 | > pip install pandas mcp
60 | > ```
61 |
62 | ---
63 |
64 | ## Usage
65 |
66 | The repository exposes two main MCP tools and a command-line entry point:
67 |
68 | ### 3. Run the MCP server
69 |
70 | ```bash
71 | python Bridge_Zeek_MCP.py --mcp-host 127.0.0.1 --mcp-port 8081 --transport sse
72 | ```
73 |
74 | * `--mcp-host`: Host for the MCP server (default: `127.0.0.1`).
75 | * `--mcp-port`: Port for the MCP server (default: `8081`).
76 | * `--transport`: Transport protocol, either `sse` (Server-Sent Events) or `stdio`.
77 |
78 | 
79 |
80 | ### 4. Use the MCP tools
81 | You need to use an LLM that can support the MCP tools usage by calling the following tools:
82 |
83 | 1. **`execzeek(pcap_path: str) -> str`**
84 |
85 | * **Description:** Runs Zeek on the given PCAP file after deleting existing `.log` files in the working directory.
86 | * **Returns:** A string listing generated `.log` filenames or `"1"` on error.
87 |
88 | 2. **`parselogs(logfile: str) -> DataFrame`**
89 |
90 | * **Description:** Parses a single Zeek `.log` file and returns the parsed content.
91 |
92 |
93 | You can interact with these endpoints via HTTP (if using SSE transport) or by embedding in LLM client (eg: Claude Desktop):
94 |
95 | #### Claude Desktop integration:
96 |
97 | To set up Claude Desktop as a Zeek MCP client, go to `Claude` -> `Settings` -> `Developer` -> `Edit Config` -> `claude_desktop_config.json` and add the following:
98 |
99 | ```json
100 | {
101 | "mcpServers": {
102 | "Zeek-mcp": {
103 | "command": "python",
104 | "args": [
105 | "/ABSOLUTE_PATH_TO/Bridge_Zeek_MCP.py",
106 | ]
107 | }
108 | }
109 | }
110 | ```
111 |
112 | Alternatively, edit this file directly:
113 | ```
114 | /Users/YOUR_USER/Library/Application Support/Claude/claude_desktop_config.json
115 | ```
116 | #### 5ire Integration:
117 | Another MCP client that supports multiple models on the backend is [5ire](https://github.com/nanbingxyz/5ire). To set up Zeek-MCP, open 5ire and go to `Tools` -> `New` and set the following configurations:
118 |
119 | 1. Tool Key: ZeekMCP
120 | 2. Name: Zeek-MCP
121 | 3. Command: `python /ABSOLUTE_PATH_TO/Bridge_Zeek_MCP.py`
122 |
123 | ##### Alternatively you can use Chainlit framework and follow the [documentation](https://docs.chainlit.io/advanced-features/mcp) to integrate the MCP server.
124 |
125 | ---
126 |
127 | ## Examples
128 | An example of MCP tools usage from a chainlit chatbot client, it was used an example pcap file (you can find fews in pcaps folder)
129 | ```
130 | In that case the used model was claude-3.7-sonnet-reasoning-gemma3-12b
131 | ```
132 |
133 | 
134 |
135 | 
136 |
137 | 
138 |
139 | ---
140 |
141 | ## License
142 |
143 | See `LICENSE` for more information.
144 |
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
1 | pandas>=1.0
2 | mcp
```
--------------------------------------------------------------------------------
/Bridge_Zeek_MCP.py:
--------------------------------------------------------------------------------
```python
1 | import argparse # Module for parsing command-line arguments
2 | import logging # Module for application logging
3 | import subprocess # Module for running external commands and subprocesses
4 | import pandas as pd # Library for tabular data manipulation and analysis
5 | from mcp.server.fastmcp import FastMCP # Import the FastMCP class from the MCP package
6 | import os # Module for interacting with the operating system (files, directories)
7 | import glob # Module for file pattern matching (glob)
8 | import pandas as pd # (Duplicate) Pandas for DataFrame operations
9 |
10 | # Configure module-level logger
11 | logger = logging.getLogger(__name__)
12 | # Create the main FastMCP instance to expose tools as endpoints
13 | mcp = FastMCP("Zeek-MCP")
14 |
15 | def parse_zeek_log(path):
16 | """
17 | Parse a single Zeek .log file:
18 | 1. Read header lines starting with '#'
19 | 2. Extract fields defined by the '#fields' header line
20 | 3. Build a pandas DataFrame from the tabular data
21 |
22 | Args:
23 | path (str): Path to the Zeek .log file
24 | Returns:
25 | pd.DataFrame: Table with columns corresponding to Zeek fields
26 | Raises:
27 | ValueError: If the '#fields' header line is missing
28 | """
29 | headers = [] # List to collect header lines
30 | data_lines = [] # List of lists to collect data row values
31 |
32 | # Open the log file for reading
33 | with open(path, "r") as f:
34 | for line in f:
35 | line = line.strip() # Remove whitespace and newline
36 | if line.startswith("#"): # Zeek header lines start with '#'
37 | headers.append(line)
38 | elif line:
39 | # Split data rows by tab and add to list
40 | data_lines.append(line.split('\t'))
41 |
42 | # Find the '#fields' header to determine column names
43 | field_line = next((h for h in headers if h.startswith("#fields")), None)
44 | if not field_line:
45 | # Raise an error if '#fields' is missing
46 | raise ValueError(f"Missing '#fields' header in {path}")
47 |
48 | # Build column list by removing the '#fields\t' prefix
49 | columns = field_line.replace("#fields\t", "").split('\t')
50 | # Create a pandas DataFrame with the parsed data
51 | df = pd.DataFrame(data_lines, columns=columns)
52 | return df
53 |
54 |
55 | def parse_all_logs_as_str(directory="."):
56 | """
57 | Search for all .log files in the specified directory and return a single
58 | formatted string containing, for each file:
59 | - File name enclosed by '=== file_name ==='
60 | - Table of data (using DataFrame.to_string)
61 | - Error message if parsing fails
62 |
63 | Args:
64 | directory (str): Directory to search for .log files (default: current)
65 | Returns:
66 | str: Concatenated blocks separated by two blank lines
67 | """
68 | # Find and sort all .log files in the directory
69 | log_files = sorted(glob.glob(os.path.join(directory, "*.log")))
70 | parts = [] # List of text blocks for each log file
71 |
72 | for log_path in log_files:
73 | basename = os.path.basename(log_path)
74 | try:
75 | # Parse the log file and convert to a string table
76 | df = parse_zeek_log(log_path)
77 | table_str = df.to_string(index=False)
78 | part = f"=== {basename} ===\n\n{table_str}"
79 | except Exception as e:
80 | # Include error message if parsing fails
81 | part = f"[ERR] {basename}: {e}"
82 | parts.append(part)
83 |
84 | # Join all text blocks with two blank lines as separators
85 | return "\n\n".join(parts)
86 |
87 |
88 | # Define an MCP tool using the @mcp.tool() decorator
89 | @mcp.tool()
90 | def execzeek(pcap_path: str) -> str:
91 | """
92 | Run Zeek on a specified PCAP file after cleaning existing .log files.
93 |
94 | Args:
95 | pcap_path (str): Path to the input PCAP file
96 | Returns:
97 | str: Comma-separated names of generated log files if successful,
98 | or "1" in case of an error
99 | """
100 | try:
101 | # Remove all existing .log files in the current directory
102 | for old in glob.glob("*.log"):
103 | try:
104 | os.remove(old)
105 | print(f"[INFO] Removed file: {old}")
106 | except Exception as e:
107 | print(f"[WARN] Could not remove {old}: {e}")
108 |
109 | # Execute the Zeek command on the PCAP file
110 | res = subprocess.run(["zeek", "-C", "-r", pcap_path], check=False)
111 | if res.returncode == 0:
112 | # On success, collect the new .log files
113 | new_logs = glob.glob("*.log")
114 | if new_logs:
115 | logs_str = ", ".join(new_logs)
116 | print(f"[INFO] Generated log files: {logs_str}")
117 | return f"Generated the following files:\n{logs_str}"
118 | else:
119 | print("[WARN] No .log files found after running Zeek.")
120 | return ""
121 | else:
122 | # If Zeek exits with an error code, return "1"
123 | print(f"[ERROR] Zeek returned exit code {res.returncode}")
124 | return "1"
125 | except Exception as e:
126 | # Handle unexpected exceptions during Zeek execution
127 | print(f"[ERROR] Error running Zeek: {e}")
128 | return "1"
129 |
130 |
131 | @mcp.tool()
132 | def parselogs(logfile: str):
133 | """
134 | MCP tool for parsing a single log file.
135 |
136 | Args:
137 | logfile (str): Path to the .log file to be parsed
138 | Returns:
139 | pd.DataFrame: DataFrame resulting from parse_zeek_log
140 | """
141 | return parse_zeek_log(logfile)
142 |
143 |
144 | def main():
145 | # Set up command-line argument parser
146 | parser = argparse.ArgumentParser(description="MCP server for mcp")
147 | parser.add_argument("--mcp-host", type=str, default="127.0.0.1",
148 | help="Host to run MCP server on (only used for sse), default: 127.0.0.1")
149 | parser.add_argument("--mcp-port", type=int,
150 | help="Port to run MCP server on (only used for sse), default: 8081")
151 | parser.add_argument("--transport", type=str, default="sse", choices=["stdio", "sse"],
152 | help="Transport protocol for MCP, default: sse")
153 | args = parser.parse_args()
154 |
155 | # Use Server-Sent Events (SSE) transport
156 | if args.transport == "sse":
157 | try:
158 | # Configure basic logging at INFO level
159 | log_level = logging.INFO
160 | logging.basicConfig(level=log_level)
161 | logging.getLogger().setLevel(log_level)
162 |
163 | # Apply FastMCP settings based on arguments
164 | mcp.settings.log_level = "INFO"
165 | mcp.settings.host = args.mcp_host or "127.0.0.1"
166 | mcp.settings.port = args.mcp_port or 8081
167 |
168 | logger.info(f"Starting MCP server on http://{mcp.settings.host}:{mcp.settings.port}/sse")
169 | logger.info(f"Using transport: {args.transport}")
170 |
171 | # Start the MCP server with SSE transport
172 | mcp.run(transport="sse")
173 | except KeyboardInterrupt:
174 | logger.info("Server stopped by user")
175 | else:
176 | # Run MCP in stdio transport mode
177 | mcp.run()
178 |
179 | # Entry point of the script when executed directly
180 | if __name__ == "__main__":
181 | main()
182 |
```