#
tokens: 3794/50000 3/3 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
  2 | [![GitHub release (latest by date)](https://img.shields.io/badge/release-v1.0-blue)](https://github.com/Gabbo01/Zeek-MCP/releases)
  3 | [![Linkedin](https://img.shields.io/badge/Linked-in-blue)](https://www.linkedin.com/in/gabriele-bencivenga-93797b147/)
  4 | 
  5 | ![Logo](images/logo.png)
  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 | ![start](images/start.png)
 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 | ![example1](images/example1.png)
134 | 
135 | ![example2](images/example2.png)
136 | 
137 | ![example3](images/example3.png)
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 | 
```