Skip to main content
Glama
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).

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues