Skip to main content
Glama
parajiholkar

Multi-Container Log Correlator MCP Server

by parajiholkar
README.md
# Multi-Container Log Correlator MCP Server

An MCP (Model Context Protocol) server that continuously ingests logs from all
running Docker containers, indexes them by trace/request ID, and exposes a
clean tools so an LLM can debug distributed transactions without ever
switching terminal windows.

---

## How it works

```
Docker daemon
  Windows  →  //./pipe/docker_engine  (named pipe, auto-detected)
  Linux/Mac →  /var/run/docker.sock   (Unix socket, auto-detected)
  Custom   →  DOCKER_HOST=tcp://...   (TCP via env var)
        │  (multiplexed stdout/stderr streams)
        ▼
  DockerLogWatcher  ──►  LogParser  ──►  TraceIndexedRingBuffer
  (per-container)         (zero-copy         (10 000 lines/container +
   log streams)            parsing)           O(1) trace_id index)
                                                      │
                                                      ▼
                                            MCP stdio transport
                                                      │
                                                      ▼
                                              LLM tool calls
```

The ring buffer retains the last 10 000 log lines **per container**. A
secondary `Map<traceId, Set<slotKey>>` index lets `search_by_trace_id` resolve
cross-service timelines in **O(1)** time regardless of buffer size.

---

## Exposed MCP Tools

The server exposes 7 tools. Use them in the order shown below for a typical debugging session start with topology, narrow to errors, then correlate by trace ID.

---

### 1. `list_active_containers`

Lists all Docker containers currently being monitored by the server.

**Use this first** to understand the service topology before calling any other tool it tells you the exact service names you will need to pass as arguments.

| Argument | Type | Required | Description |
|---|---|---|---|
| `project_name` | string | No | Filter by Docker Compose project name. Omit to list all running containers. |

**Returns:** Service name, short container ID (12-char), Docker image, running status, and Compose project for each container.

**Example:**
```
list_active_containers()
→ api-gateway      (id: a1b2c3d4e5f6, image: nginx:latest,   Up 3 hours)
→ checkout-worker  (id: b2c3d4e5f6a1, image: node:20,        Up 3 hours)
→ inventory-db     (id: c3d4e5f6a1b2, image: postgres:16,    Up 3 hours)
```

---

### 2. `search_by_trace_id` — Primary debug tool

The core power tool. Takes a trace/request/correlation ID and returns a **chronologically merged timeline** of every log event across all containers that share that ID resolved in O(1) time using the in-memory trace index.

This is the tool that turns a 500 error into a root cause in seconds. A single call spans every service that touched the request.

| Argument | Type | Required | Description |
|---|---|---|---|
| `trace_id` | string | **Yes** | The correlation ID to search for e.g. `req-998877`, a W3C traceparent hex, or an X-Request-ID value. |
| `limit` | number | No | Maximum log events to return. Default `200`, max `500`. |

**Returns:** A merged, chronologically sorted list of log lines from all services that logged this trace ID, including the service name, timestamp, log level, and full message for each event.

**Example:**
```
search_by_trace_id({ trace_id: "req-998877" })
→ 14:03:21.001  [api-gateway]      INFO   POST /checkout received
→ 14:03:21.045  [checkout-worker]  INFO   Reserving 2x item #42
→ 14:03:22.100  [inventory-db]     ERROR  Deadlock detected on stock_reservations
→ 14:03:22.441  [checkout-worker]  ERROR  DB timeout after 1400ms
→ 14:03:22.443  [api-gateway]      ERROR  Responding 500 to client
```

---

### 3. `tail_service_errors`

Retrieves the most recent `ERROR` and `FATAL` log lines from a specific container. The fastest way to find an initial failure point when you do not yet have a trace ID grab the error, extract its trace ID, then pass it to `search_by_trace_id`.

| Argument | Type | Required | Description |
|---|---|---|---|
| `service_name` | string | **Yes** | Docker container name or Compose service name (e.g. `checkout-worker`). |
| `limit` | number | No | Number of most-recent error lines to return. Default `50`, max `500`. |
| `include_warnings` | boolean | No | Also include `WARN`-level lines alongside errors. Default `false`. |

**Returns:** The N most-recent ERROR/FATAL (and optionally WARN) log lines from the named service in chronological order.

**Detected levels:** `FATAL`, `CRITICAL`, `ERROR` and optionally `WARN` / `WARNING`.

**Example:**
```
tail_service_errors({ service_name: "checkout-worker", limit: 10, include_warnings: true })
→ 14:03:22.441  ERROR  DB timeout after 1400ms trace_id=req-998877
→ 14:03:19.100  WARN   Slow query detected (980ms) trace_id=req-998800
```

---

### 4. `tail_service_logs`

Retrieves the N most-recent log lines from a specific container with optional level filtering. Use this to get general context around a service see what it was doing before or after an event without restricting to errors only.

| Argument | Type | Required | Description |
|---|---|---|---|
| `service_name` | string | **Yes** | Docker container name or Compose service name. |
| `limit` | number | No | Number of most-recent lines to return. Default `100`, max `500`. |
| `level_filter` | string[] | No | Restrict to specific levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, `UNKNOWN`. Omit for all levels. |

**Returns:** The most-recent matching log lines in chronological order with timestamp, service name, level, and message.

**Example:**
```
tail_service_logs({ service_name: "api-gateway", limit: 50, level_filter: ["INFO", "ERROR"] })
→ Returns the last 50 INFO and ERROR lines from api-gateway
```

---

### 5. `search_logs`

Case-insensitive full-text search across the buffered log lines of one or all containers. Useful when you know part of an error message but not which service threw it, or when hunting for a specific SQL query, function name, or stack trace snippet that does not carry a trace ID.

| Argument | Type | Required | Description |
|---|---|---|---|
| `query` | string | **Yes** | Case-insensitive substring to match against log messages (e.g. `"connection refused"`, `"NullPointerException"`). |
| `service_name` | string | No | Restrict search to one container. Omit to search all containers simultaneously. |
| `limit` | number | No | Maximum matching lines to return. Default `100`, max `500`. |
| `level_filter` | string[] | No | Restrict results to specific log levels. |

**Returns:** All matching log lines in chronological order annotated with service name, timestamp, and level. Indicates when results are truncated.

**Example:**
```
search_logs({ query: "connection refused", limit: 20 })
→ Finds every "connection refused" line across all running containers
```

---

### 6. `get_container_stats`

Returns buffer statistics for one or all containers at a glance: total lines buffered, error/warn counts, a sample of known trace IDs, and the oldest/newest log timestamps. Useful for a quick system health overview or to verify the buffer is actively filling with live data.

| Argument | Type | Required | Description |
|---|---|---|---|
| `service_name` | string | No | A specific container to inspect. Omit to get stats for all running containers. |

**Returns per container:**
- Lines buffered (ring buffer holds up to 10,000 lines per container)
- Error count and warning count
- Oldest and newest log timestamps in the buffer
- Sample of known trace IDs seen in that container's logs

**Example:**
```
get_container_stats()
→ checkout-worker:  4,821 lines | Errors: 12 | Warnings: 34 | Traces: req-998877, req-998800 …
→ inventory-db:     2,103 lines | Errors: 3  | Warnings: 1  | Traces: req-998877 …
→ api-gateway:      9,441 lines | Errors: 12 | Warnings: 89 | Traces: req-998877, req-998800 …
```

---

### 7. `list_trace_ids`

Lists all trace/request IDs currently held in the in-memory buffer. Use this when a user reports "something went wrong" with no trace ID browse the available IDs, pick the one closest to the reported time, and feed it directly into `search_by_trace_id`.

| Argument | Type | Required | Description |
|---|---|---|---|
| `service_name` | string | No | Only list trace IDs seen in this specific service. Omit for all services. |
| `limit` | number | No | Maximum number of trace IDs to return. Default `50`, max `1000`. |

**Returns:** A numbered list of trace ID strings currently in the buffer.

**Example:**
```
list_trace_ids({ service_name: "checkout-worker", limit: 5 })
→ 1. req-998877
→ 2. req-998800
→ 3. req-998741
→ 4. req-998603
→ 5. req-998512
```

---

## Trace ID Detection

The parser automatically extracts trace identifiers from:

- JSON fields: `trace_id`, `traceId`
- W3C `traceparent` header values embedded in logs
- `X-Request-ID`, `X-Trace-ID`, `requestid`, `request_id` key-value pairs
- `req-XXXXX` style short IDs (common in Node.js / Express apps)
- OpenTelemetry `trace_id=<hex32>` fields
- OpenTelemetry `span_id` / `spanId` for span-level correlation

---

## Prerequisites

- **Node.js 18+**
- **Docker** running locally (or accessible socket)
- **Windows**: Docker Desktop running uses `//./pipe/docker_engine` automatically
- **Linux/Mac**: Read access to `/var/run/docker.sock`

---

## Installation & Usage

```bash
npm install
node index.js
```

### Environment variables

The server auto-detects the Docker connection for your platform no configuration needed in most cases.

| Variable | Default | Description |
|---|---|---|
| `DOCKER_HOST` | *(auto)* | Override with `tcp://localhost:2375` for TCP mode (useful on Windows if named pipe access is blocked) |
| `DOCKER_SOCKET_PATH` | `/var/run/docker.sock` | Linux/Mac only override the Unix socket path |

> **Windows tip**: If the server cannot find containers, enable **"Expose daemon on tcp://localhost:2375"** in Docker Desktop → Settings → General, then set `DOCKER_HOST=tcp://localhost:2375` in your MCP config (see below).

---

## Claude Desktop / Cursor config

Add this to your `claude_desktop_config.json`:

> **Config file location**
> - Windows: `C:\Users\<you>\AppData\Roaming\Claude\claude_desktop_config.json`
> - Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
> - Linux: `~/.config/Claude/claude_desktop_config.json`

**Linux / Mac**
```json
{
  "mcpServers": {
    "multi-container-log-correlator-mcp-server": {
      "command": "node",
      "args": ["/absolute/path/to/multi-container-log-correlator-mcp-server/index.js"]
    }
  }
}
```

**Windows** (named pipe works out of the box with Docker Desktop)
```json
{
  "mcpServers": {
    "multi-container-log-correlator-mcp-server": {
      "command": "node",
      "args": ["C:\\path\\to\\multi-container-log-correlator-mcp-server\\index.js"]
    }
  }
}
```

**Windows** (TCP fallback if named pipe does not work, enable TCP in Docker Desktop → Settings → General first)
```json
{
  "mcpServers": {
    "multi-container-log-correlator-mcp-server": {
      "command": "node",
      "args": ["C:\\path\\to\\multi-container-log-correlator-mcp-server\\index.js"],
      "env": {
        "DOCKER_HOST": "tcp://localhost:2375"
      }
    }
  }
}
```

---

## Typical debugging session

```
User: "The frontend is throwing a 500 error on checkout. Trace ID is req-998877. What happened?"

LLM:
  1. list_active_containers()             → sees api-gateway, checkout-worker, inventory-db
  2. search_by_trace_id("req-998877")     → gets merged timeline across all 3 services
  3. Reads the timeline, spots the DB deadlock in inventory-db at 14:03:22.441
  4. Reports: "inventory-db hit a deadlock on the stock reservation table,
     which caused checkout-worker to time out, propagating a 500 to api-gateway."
```

---

## Project structure

```
src/
├── constants.js              # Buffer sizes, regex patterns, socket path
├── services/
│   ├── ringBuffer.js         # TraceIndexedRingBuffer (core data structure)
│   ├── dockerClient.js       # DockerLogWatcher streams from daemon
│   ├── logParser.js          # Stateless line parser (timestamps, levels, trace IDs)
│   └── formatter.js          # Human-readable output helpers
├── schemas/
│   └── toolSchemas.js        # Zod input schemas for every MCP tool
└── tools/
    └── containerTools.js     # All 7 MCP tool registrations
── index.js                  # Entry point wires everything together
```

TDQS

A4/5.0

Scored across 7 tools

Disambiguation4/5

Most tools have clearly distinct purposes, with the main overlap being tail_service_errors and tail_service_logs when level_filter includes ERROR/FATAL. The descriptions mitigate this by positioning tail_service_errors as the fast error-spotting tool, but an agent could still reasonably confuse which to call.

Naming Consistency5/5

All tool names follow a consistent snake_case verb-first pattern: list, search, get, tail. The naming style is predictable and makes it easy to infer what each tool does before reading its description.

Tool Count5/5

Seven tools is a well-scoped count for a log correlation server. Each tool covers a distinct aspect of the debugging workflow without feeling bloated or too sparse.

Completeness4/5

The set covers container discovery, stats, error tailing, general log tailing, full-text search, trace ID listing, and trace-correlated timelines. A notable minor gap is the lack of explicit time-range based log queries, but the core distributed debugging workflow is well supported.

Maintenance

ActivitySlowing
ResponsivenessNo issues