logs-mcp
by 0xmortuex
README.md
# logs-mcp
**Let your AI agent investigate log files that are far too big to read.**
<!-- mcp-name: io.github.0xmortuex/logs-mcp -->
logs-mcp is an [MCP](https://modelcontextprotocol.io) server that streams through gigabytes of
logs and hands the agent only the part that matters: what format it is, what is failing, when it
started, which request it touched, and what changed since last hour.
**How it differs from other log MCP servers.** The existing ones I found
([Log Detective MCP](https://pypi.org/project/logdetective-mcp/) and the
[Log Analyzer MCP](https://glama.ai/mcp/servers/f02ew6z010)) chunk logs and rely on Drain3,
embeddings/vector stores or a cloud model. logs-mcp has **zero dependencies beyond `mcp`**, needs no
model, index or network, and works on the file where it sits: constant memory, `.gz` and rotated
files read in place, multi-line stack traces as single events, a `since`/`until` window that seeks
instead of scanning, plus timeline, request tracing and "what's new in the last hour" comparison.
## What it looks like
Real output (a generated 17 MiB app log, `tests/loggen.py`):
```
> error_clusters app.log top=2
6019 event(s) at level >= ERROR grouped into 2 template(s) (of 200000 events scanned). Showing top 2 by count.
#1 x4000 first 2026-11-06 00:00:01 last 2026-11-06 02:46:37 levels ERROR:4000
template: [db] req=<hex> connection timeout after <n>ms to <ip>
e.g. app.log:35 [2026-11-06 00:00:01] ERROR | 2026-11-06 00:00:01,750 ERROR [db] req=c541013d connection timeout after 3204ms to 10.0.0.5:5432
#2 x2019 first 2026-11-06 00:00:20 last 2026-11-06 02:46:34 levels ERROR:2019
template: [pay] req=<hex> charge failed order=<n> || ConnectionResetError: [Errno <n>] Connection reset by peer
e.g. app.log:405 [2026-11-06 00:00:20] ERROR | 2026-11-06 00:00:20,250 ERROR [pay] req=d065c0e7 charge failed order=5263
Traceback (most recent call last):
File "/srv/app/handlers.py", line 88, in charge
gateway.post(order)
ConnectionResetError: [Errno 104] Connection reset by peer
```
## Tools
| Tool | Use it to |
|------|-----------|
| `log_overview` | Start here. Size, exact line count, detected format, multi-line style, time range, level histogram. |
| `search_logs` | Text/regex + minimum level + time window, with context events, hard caps and an exact "N more matches" notice; `newest_first` keeps the newest hits. |
| `error_clusters` | Group errors into templates (numbers, UUIDs, IPs, hex ids, paths, URLs normalised) with count, first/last seen, example. |
| `timeline` | Counts per time bucket for a pattern/level with an ASCII sparkline: *when* did it start? |
| `tail_logs` | Last N lines read from the end of the file; optional filter and a short follow window. |
| `trace_request` | Follow a request/correlation/trace id across many files, merged into time order. |
| `compare_windows` | Which error templates are new, spiked or gone in window B versus A. Default: last hour vs the hour before. |
Supported inputs: plain text and `.gz`, rotated sets (`app.log` pulls in `app.log.1`, `app.log.2.gz`,
dated names), directories and globs (`/var/log/app/*.log*`). Formats: JSON lines, logfmt, syslog
(no year: inferred from the file's modification time), nginx/apache access logs (5xx counts as
ERROR), and ISO-8601-style text. Python tracebacks and Java stack traces stay attached to the
event that caused them. Timestamps with zones (`Z`, `+02:00`) are normalised to UTC; zone-less ones
use the `tz` argument (default `+00:00`).
`since` / `until` accept ISO times (`2026-11-06T12:30:00Z`, `2026-11-06 12:30`, `2026-11-06`) or
relative values like `-30m`, `-2h`, which count back from the **newest entry in the log**, not from
the wall clock (you are usually looking at yesterday's file).
## Install
Needs Python 3.10+ and [uv](https://docs.astral.sh/uv/).
**Claude Code**
```bash
claude mcp add logs -- uvx logs-mcp --dir /var/log/myapp
```
**Claude Desktop** (`claude_desktop_config.json`)
```json
{
"mcpServers": {
"logs": {
"command": "uvx",
"args": ["logs-mcp", "--dir", "C:\\logs", "--dir", "D:\\exports\\prod"]
}
}
}
```
Windows paths work with backslashes or forward slashes. Until it is on PyPI:
`uvx --from git+https://github.com/0xmortuex/logs-mcp logs-mcp --dir ...`
## Try asking
- "Give me an overview of everything in the logs folder."
- "What is erroring in `api.log`? Group it and show me the top five."
- "When did the `connection timeout` errors start? Show a timeline."
- "What changed in the last hour compared to the hour before?"
- "Follow request `3f9c1ab2` through all the service logs."
- "Show me the 20 newest `payment` errors with 2 events of context."
- "Anything at WARN or above between 02:00 and 02:30 yesterday (my logs are UTC+3)?"
## Security
- **Read-only.** The server never writes, deletes or executes anything.
- **`--dir` sandbox.** Every file is resolved with `realpath` (symlinks followed) and must sit inside
an allowed directory, from `--dir` (repeatable) or `LOGS_MCP_DIR`. With neither, only the
current working directory is allowed. Path traversal (`../`) and symlink escapes are rejected;
globs are checked per match.
- **Logs are untrusted input.** Anything in a log line reaches the agent verbatim; a hostile line
can contain prompt-injection text. Do not point the server at logs that attackers can write to
unless you accept that, and treat suggestions derived from them with care.
- Output is capped per call (default 30,000 characters, 400 characters per line, 500 matches),
so a hostile or runaway file cannot flood the agent's context.
- Logs often hold secrets and personal data. Whatever the agent reads is sent to your model
provider; restrict `--dir` to what you are willing to share.
## Performance (measured, not promised)
Generated 601 MiB file, 7,279,444 lines, one process, Windows 11, Python 3.12, `tests/test_big.py`:
| Call | Time |
|------|------|
| `log_overview` (default, sampled histogram) | 1.5 s |
| `log_overview` (`max_scan_mb=2048`, exact full scan) | 20.5 s |
| `search_logs` rare text, full scan | 24.3 s |
| `search_logs` level + 1-minute window (bisect seek) | 0.5 s |
| `error_clusters`, full scan | 29.7 s |
| `timeline`, full scan | 20.8 s |
| `trace_request`, full scan | 23.4 s |
| `compare_windows` default (last hour vs before) | 1.8 s |
| `tail_logs` 100 lines | under 0.01 s |
| **Peak resident memory of the whole process** | **25.5 MiB** |
That is roughly 25-30 MiB/s of text for a full scan in pure Python, and memory does not grow with
the file. A 2 GB file is therefore expected to take about a minute or more for a full scan; I
have **not** run a 2 GB file. Windowed calls (`since`/`until`) on large uncompressed, roughly
chronological files seek by bisection and stay fast. `.gz` files are always read from the start.
Files above `max_scan_mb` (default 256) get a *sampled* level histogram, labelled ESTIMATED.
Limits worth knowing: early stop and bisection assume each file is chronological to within an hour;
an event keeps at most 200 lines for matching and clustering; at most 5,000 distinct templates are
tracked (overflow is counted and reported); the template normaliser is regex-based, not Drain-style
learning, so unusual id formats can produce more templates than you expect.
## Development
```bash
uv sync --extra test
uv run pytest tests -v # ~45 tests, real files and a real stdio server
LOGS_MCP_BIG=1 LOGS_MCP_BIG_MB=600 uv run pytest tests/test_big.py -s # the benchmark above
uvx ruff==0.16.0 check src tests && uvx --with mcp mypy==2.3.0 --strict src
```
There is a companion tutorial that builds a minimal version from scratch:
[logs-mcp-the-hard-way](https://github.com/0xmortuex/logs-mcp-the-hard-way).
## License
MIT. Built by [0xmortuex](https://github.com/0xmortuex).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo recent community issues