# ProcmonMCP ProcmonMCP is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that allows LLMs to autonomously analyse **Process Monitor (Procmon) XML log files**. It exposes a comprehensive set of analysis tools to any MCP-compatible client, including Claude Code, Claude Desktop, Cline, and others.
REMnux logo Included in REMnux
Ships in the REMnux® malware-analysis distro, pre-configured for OpenCode as the procmon MCP server.
## Overview Process Monitor captures detailed system activity — file access, registry operations, network connections, process creation, and more. ProcmonMCP parses these XML logs into an optimised in-memory representation and exposes them as MCP tools, enabling an LLM to investigate system behaviour without manual data wrangling. Key capabilities: - **Load files at runtime** — no need to restart the server to analyse a different capture - **Parsed-capture cache** — reloading an unchanged file is near-instant (parsing is skipped) - **String interning** for reduced memory footprint on large logs - **Indexed lookups** by process name, operation, PID, and file path for fast filtering - **Multiple transport protocols** — stdio (recommended), Streamable HTTP, and SSE (deprecated) - **Progress feedback** during file loading via MCP notifications This project was inspired by the approach taken in [GhidraMCP](https://github.com/LaurieWired/GhidraMCP). > **Security Warning** > > Process Monitor logs can contain extremely sensitive system information (keystrokes, passwords in command lines, file contents, network traffic details, etc.). > > - This tool loads **any file path** that the user running the script has read permissions for. There is **no** directory sandboxing. > - **Only run this server in trusted environments.** > - **Never run this server with Procmon logs captured from systems containing sensitive production or personal data unless you fully understand and accept the risks.** > - **Review the logs you intend to load for sensitive information before using this tool.** ## Installation Current release: **[v0.5.0](https://github.com/JameZUK/ProcmonMCP/releases/tag/v0.5.0)**, which requires MCP SDK v2 (`mcp[cli]>=2.0.0`). Earlier versions are listed on the [releases page](https://github.com/JameZUK/ProcmonMCP/releases); notable changes are recorded in [CHANGELOG.md](CHANGELOG.md). ### Prerequisites - Python 3.10 or newer (required by the MCP SDK; developed and tested with 3.10-3.13) - `pip` (Python package installer) ### Install from source ```bash git clone https://github.com/JameZUK/ProcmonMCP cd ProcmonMCP pip install -r requirements.txt ``` That tracks the latest state of the default branch. To pin the current release instead: ```bash git clone --branch v0.5.0 --depth 1 https://github.com/JameZUK/ProcmonMCP cd ProcmonMCP pip install ".[all]" ``` Or install the package itself (provides a `procmon-mcp` console script): ```bash pip install . # core only (mcp[cli]) pip install ".[all]" # core + optional lxml and psutil pip install -e ".[dev]" # editable install with test tooling ``` ### Dependencies | Package | Required | Purpose | |---------|----------|---------| | `mcp[cli]>=2.0.0` | Yes | MCP SDK with CLI tools and Streamable HTTP support | | `lxml>=4.9.0` | Recommended | Faster XML parsing (falls back to stdlib `ElementTree` if absent) | | `psutil>=5.9.0` | Optional | Memory usage reporting after file loading | | `google-re2` | Optional | Linear-time, ReDoS-safe engine for user filter regexes (`pip install ".[re2]"`; falls back to stdlib `re`) | Install all at once: ```bash pip install "mcp[cli]>=2.0.0" lxml psutil ``` > **MCP SDK v2:** the SDK renamed `FastMCP` to `MCPServer` and moved it to > `mcp.server.mcpserver` in v2. ProcmonMCP targets v2, and still runs against > `mcp<2` via a compatibility fallback. If you previously installed with an > unpinned `mcp[cli]` and saw *"the MCP SDK could not be loaded"*, upgrade with > `pip install --upgrade "mcp[cli]>=2"`. ## Quick Start with Claude Code The recommended way to use ProcmonMCP is via **stdio** transport with Claude Code. The server starts without any file loaded — you (or the LLM) can then use the `load_file` tool to open a Procmon capture. ### Option 1: Add via Claude Code CLI ```bash # Add ProcmonMCP as a stdio server (user-wide) claude mcp add procmon --scope user -- python -m procmon_mcp # Or with a file pre-loaded at startup claude mcp add procmon --scope user -- python -m procmon_mcp --input-file /path/to/capture.xml.gz # Or scoped to the current project only claude mcp add procmon --scope project -- python -m procmon_mcp ``` ### Option 2: Add via JSON ```bash claude mcp add-json procmon '{ "type": "stdio", "command": "python", "args": ["-m", "procmon_mcp"] }' ``` ### Option 3: Edit configuration files directly Claude Code reads MCP server configuration from these locations: | Scope | File | Description | |-------|------|-------------| | Project (shared, version-controlled) | `.mcp.json` in project root | Shared with the team | | Project (personal) | `.claude/settings.local.json` | Your local overrides | | User (global) | `~/.claude.json` | Available across all projects | Example `.mcp.json` for a shared project: ```json { "mcpServers": { "procmon": { "type": "stdio", "command": "python", "args": ["-m", "procmon_mcp"] } } } ``` Example with a pre-loaded file and options: ```json { "mcpServers": { "procmon": { "type": "stdio", "command": "python", "args": [ "-m", "procmon_mcp", "--input-file", "/path/to/capture.xml.gz", "--no-stack-traces" ] } } } ``` ### Option 4: Streamable HTTP transport For network access or multi-client scenarios, use Streamable HTTP: ```bash # Add as an HTTP server (start the server separately first) claude mcp add --transport http procmon http://127.0.0.1:8081/mcp ``` Then start the server: ```bash python -m procmon_mcp --transport streamable-http --mcp-port 8081 ``` ### Verify the connection Once configured, verify that Claude Code can see ProcmonMCP: ```bash claude mcp list ``` Inside a Claude Code session, you can also type `/mcp` to check the status of connected servers. ## Usage ### Typical workflow 1. **Start the server** (Claude Code does this automatically for stdio servers) 2. **Check status**: The LLM calls `get_status` to see if a file is loaded 3. **Load a file**: The LLM calls `load_file` with a path to a Procmon XML capture 4. **Analyse**: The LLM uses the analysis tools to investigate the log data ### Command-line arguments | Argument | Default | Description | |----------|---------|-------------| | `--input-file ` | *(none)* | Pre-load a Procmon XML file at startup. If omitted, use `load_file` from the MCP client. | | `--transport ` | `stdio` | Transport protocol: `stdio`, `streamable-http`, or `sse` (deprecated). | | `--mcp-host ` | `127.0.0.1` | Host address (HTTP transports only). | | `--mcp-port ` | `8081` | Port number (HTTP transports only). | | `--no-stack-traces` | off | Skip loading stack traces to save memory. | | `--no-extra-data` | off | Skip loading unknown/extra event fields to save memory. | | `--no-cache` | off | Bypass the parsed-capture cache (always re-parse, and refresh the cache). | | `--clear-cache` | off | Remove all cached parsed captures, then exit. | | `--debug` | off | Enable verbose debug logging. | | `--log-file ` | *(console)* | Write logs to a file instead of the console. | | `--profile` | off | Enable cProfile profiling (for development). | ### Examples **Start with stdio (no file pre-loaded — use `load_file` from the client):** ```bash python -m procmon_mcp ``` **Pre-load a compressed XML file:** ```bash python -m procmon_mcp --input-file /path/to/capture.xml.gz ``` **Start with Streamable HTTP on a custom port:** ```bash python -m procmon_mcp --transport streamable-http --mcp-port 9000 ``` **Skip stack traces for a very large file:** ```bash python -m procmon_mcp --input-file /path/to/huge_capture.xml --no-stack-traces --no-extra-data ``` ## Transport Protocols | Transport | Use case | Status | |-----------|----------|--------| | **stdio** | Local use with Claude Code, Claude Desktop, etc. | **Recommended** | | **streamable-http** | Network access, multiple clients, remote deployment | Supported | | **sse** | Legacy MCP clients that do not yet support Streamable HTTP | **Deprecated** (MCP spec 2025-03-26) | ### stdio (recommended) The simplest and most reliable transport. Claude Code spawns the server as a child process and communicates over stdin/stdout. No network configuration required. ### Streamable HTTP Uses a single HTTP endpoint (`/mcp`) for all communication. Supports session management, optional SSE streaming for long-running operations, and is designed for scalability. ```bash python -m procmon_mcp --transport streamable-http --mcp-host 0.0.0.0 --mcp-port 8081 ``` The server will be available at `http://:/mcp`. ### SSE (deprecated) > **Deprecated since MCP specification 2025-03-26.** SSE is retained for backwards compatibility but will be removed in a future release. Please migrate to `streamable-http` or `stdio`. ```bash python -m procmon_mcp --transport sse --mcp-port 8081 ``` ## User Configuration ProcmonMCP stores user preferences in `~/.procmonmcp/config.json`. This file is created automatically and remembers: - The last loaded file path (shown as a hint in `get_status` when no file is loaded) - Loading preferences (`no_stack_traces`, `no_extra_data`) No API keys or authentication tokens are required — ProcmonMCP is a purely local analysis tool. ### Parsed-capture cache Parsing a large capture is expensive (tens of seconds to minutes). After a successful parse, ProcmonMCP serializes the optimised in-memory representation (events, interners, indices, process tables) to `~/.procmonmcp/cache/`, keyed on the source file's path, size, and modification time plus the load options. Reloading the same unchanged file is then served from the cache and is near-instant — in practice a 20s parse drops to well under a second. - The cache is invalidated automatically when the file changes (mtime/size) or when the cache format version changes. Different `no_stack_traces`/`no_extra_data` options are cached separately. - Bypass it with `--no-cache` (CLI) or `no_cache: true` (the `load_file` tool); the `from_cache` field in the load response indicates whether the cache was used. - Clear it with `--clear-cache` (CLI) or the `clear_cache` tool. The cache is size-bounded with LRU eviction (default 5 GiB; override with the `PROCMONMCP_CACHE_MAX_BYTES` environment variable). - **Security:** cache files are serialized with Python's `pickle` and are read back only from your user-owned `~/.procmonmcp/cache` directory (only this tool's own output is ever deserialized). Do not point the cache at a location other users can write to. ## Available MCP Tools ### Lifecycle Tools | Tool | Description | |------|-------------| | `get_status()` | Returns the current server state — whether a file is loaded, loading progress, memory usage, and available actions. **Call this first.** | | `load_file(file_path, no_stack_traces?, no_extra_data?, no_cache?)` | Loads a Procmon XML file (.xml, .gz, .bz2, .xz) for analysis. Uses the parsed-capture cache for near-instant reloads (`from_cache` in the response says whether it was used). Set `no_cache: true` to bypass the cache. Provides progress feedback. Replaces any previously loaded data. | | `close_file()` | Closes (unloads) the currently loaded capture and frees its memory. Analysis tools are unavailable until another file is opened with `load_file`. Does not remove the on-disk cache. | | `clear_cache()` | Removes all on-disk parsed-capture caches. Reclaims disk space and forces a clean re-parse on the next load. Does not affect currently loaded data. | ### Data Retrieval Tools | Tool | Description | |------|-------------| | `get_loaded_file_summary()` | Returns a detailed summary of the loaded file — filename, counts, compression, index stats, interner stats, and selective loading flags. | | `get_metadata()` | Returns basic metadata (filename, type, event/process counts). | | `list_processes()` | Lists unique processes (PID, name, image path, parent PID) from the process list section. | | `get_process_details(pid)` | Returns detailed properties for a specific process by PID. | | `query_events(...)` | Queries events with flexible filters — by process, **PID** (`filter_pid`), operation, result, path (contains/regex), detail (regex), timestamp range, and stack module path. Returns event summaries with index. | | `get_event_details(event_index)` | Returns all properties for a specific event by its index. | | `get_event_stack_trace(event_index)` | Returns the call stack for a specific event (module path, location, address). | ### Analysis Tools | Tool | Description | |------|-------------| | `count_events_by_process()` | Counts events per process name. | | `summarize_operations_by_process(process_name_filter)` | Counts operations for a specific process. | | `get_timing_statistics(group_by)` | Calculates duration statistics grouped by process or operation. | | `get_process_lifetime(pid)` | Finds the Process Create and Process Exit timestamps for a given PID. | | `find_file_access(path_contains, limit?)` | Finds file system events matching a path substring (case-insensitive). | | `find_network_connections(process_name)` | Returns enriched records for the remote endpoints a process accessed: endpoint, host/ip/hostname/port, operations, inferred directions (connect/send/receive), results, event count, and first/last-seen timestamps — ranked by count. | | `list_network_connections(limit?)` | Capture-wide network triage: one enriched record per (process, PID, endpoint) across all processes, ranked by event count. | | `get_network_top_talkers(limit?)` | Ranks unique remote endpoints across the whole capture by event count, with the count of distinct processes (and their names) that touched each. | ### Export Tools | Tool | Description | |------|-------------| | `export_query_results(...)` | Exports filtered events to CSV or JSON file. Uses the same filters as `query_events`. | ## MCP Clients ProcmonMCP works with any MCP-compatible client. Below are setup instructions for popular clients. ### Claude Code (recommended) See the [Quick Start with Claude Code](#quick-start-with-claude-code) section above. ### Claude Desktop Add the following to your Claude Desktop configuration file: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` ```json { "mcpServers": { "procmon": { "command": "python", "args": ["-m", "procmon_mcp"] } } } ``` ### Cline For Cline with Streamable HTTP transport, first start the server: ```bash python -m procmon_mcp --transport streamable-http --mcp-port 8081 ``` Then in Cline, select MCP Servers and add: - **Server Name:** ProcmonMCP - **Server URL:** `http://127.0.0.1:8081/mcp` ## Example LLM Prompts for Malware Analysis *(Assuming a Procmon XML file is loaded)* ### Initial Triage - "Get the summary of the loaded file." - "List the unique processes found in the log." - "Count the events per process." *(Identify high-activity processes)* - "Calculate timing statistics grouped by process." *(Identify long-duration events)* ### Investigating a Suspicious Process - "Get details for process PID 4568." *(Check command line, parent PID, image path)* - "Summarise operations for process `suspicious.exe`." - "Query events where filter_process is `suspicious.exe` and filter_operation is `RegSetValue`, limit 10." - "Find network connections for process `suspicious.exe`." *(Enriched: directions, results, counts, first/last-seen)* - "List network connections across the whole capture." *(Capture-wide triage, busiest first)* - "Show the network top talkers." *(Rank remote endpoints by event count)* - "Find file access containing `temp\\suspicious_data`, limit 50." ### Looking for Persistence - "Query events where filter_operation is `RegSetValue` and filter_path_contains is `CurrentVersion\\Run`, limit 20." - "Query events where filter_operation is `CreateFile` and filter_path_contains is `StartUp`, limit 10." ### Troubleshooting Errors - "Query events where filter_result is `ACCESS DENIED`, limit 10." - "Query events where filter_result is `NAME NOT FOUND`, limit 10." - "Get details for event 987." - "Get stack trace for event 987." ### Exporting Data - "Export query results to `suspicious_reg_writes.csv` where filter_process is `suspicious.exe` and filter_operation contains `RegSet`." - "Export query results to `network_activity.json` in json format." ## Performance and Indexing ProcmonMCP builds four indices during file loading for fast filtered lookups: | Index | Used by | Complexity | |-------|---------|------------| | Process name (interned ID) | `query_events`, `count_events_by_process` | O(1) lookup | | Operation (interned ID) | `query_events`, `summarize_operations_by_process` | O(1) lookup | | PID | `query_events` (`filter_pid`), `get_process_lifetime` | O(1) lookup with set intersection | | File path (interned ID) | `find_file_access` | O(unique_paths) substring scan | For filters not backed by an index (e.g., regex, path contains, stack module path), ProcmonMCP falls back to a linear scan of all events. Use indexed filters first to narrow results, then apply more expensive filters. ## Limitations - **Memory usage**: Whilst optimised with string interning, loading extremely large XML files (millions of events with stack traces) can consume significant RAM. Use `--no-stack-traces` and `--no-extra-data` for very large files. - **Loading time**: Parsing and optimising large XML files takes time, particularly compressed ones. Progress is reported during loading. Reloading an unchanged file is near-instant thanks to the [parsed-capture cache](#parsed-capture-cache); only the first parse pays the full cost. - **XML structure**: Relies on the standard Procmon XML export structure. Malformed or non-standard XML will likely cause parsing errors. - **Non-ASCII names**: Procmon's XML export double-encodes non-ASCII process/path names (e.g. Japanese filenames). ProcmonMCP detects and repairs this on load so names are readable and matchable by their true value; if a process name is still awkward to type exactly, filter by `filter_pid` instead. - **Stack traces**: Stack trace quality depends on what Procmon resolved and included in the XML export. Requires running Procmon with symbols configured correctly. - **Single file at a time**: Only one file can be loaded at any given time. Loading a new file replaces the previous data. ## Changelog Notable changes are recorded in [CHANGELOG.md](CHANGELOG.md). ## Contributing Contributions are welcome! Please feel free to submit pull requests or open issues on [GitHub](https://github.com/JameZUK/ProcmonMCP). ## License This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.