Skip to main content
Glama
Maneesh-Rajbhar

Enterprise Infrastructure & Metrics MCP Server

README.md
# Enterprise Infrastructure & Metrics MCP Server

A production-grade **Model Context Protocol (MCP)** server, written in
native async Python, that gives an LLM agent (Claude Desktop, Claude Code,
or any MCP-compatible host) safe, read-only introspection into:

- **Host system resources** — live CPU (per-core), memory, and disk metrics via `psutil`
- **Docker containers** — list, inspect health/state, and tail logs via the Docker SDK
- **Application/system logs** — sandboxed tail-and-filter of log files, with strict path-traversal protection

Built as a portfolio project demonstrating enterprise MCP server engineering: strict Pydantic v2 contracts, structured (never-raise) error handling, async-safe wrapping of blocking I/O, and an explicit, auditable security boundary.

---

## Architecture

```
┌──────────────────────────┐        stdio (JSON-RPC 2.0)        ┌───────────────────────────────────────────┐
│   MCP Host                │ <---------------------------------> │   enterprise-mcp-server (this project)     │
│   (Claude Desktop /       │        subprocess, stdin/stdout      │                                             │
│    Claude Code / other)   │                                      │   ┌─────────────────────────────────────┐ │
└──────────────────────────┘                                      │   │  server.py  (FastMCP app)            │ │
                                                                    │   │  - registers 3 tools                 │ │
                                                                    │   │  - stdio transport                   │ │
                                                                    │   │  - logging -> stderr ONLY            │ │
                                                                    │   └───────────────┬─────────────────────┘ │
                                                                    │                    │ validated Pydantic    │
                                                                    │                    ▼ input models          │
                                                                    │   ┌─────────────────────────────────────┐ │
                                                                    │   │  tools/                              │ │
                                                                    │   │  ├─ system_metrics.py  (psutil)      │ │
                                                                    │   │  ├─ docker_manager.py  (docker SDK)  │ │
                                                                    │   │  └─ log_analyzer.py    (sandboxed FS)│ │
                                                                    │   └───────────────┬─────────────────────┘ │
                                                                    │                    │ asyncio.to_thread     │
                                                                    │                    │ (never blocks loop)   │
                                                                    │   ┌────────────────▼─────────────────────┐│
                                                                    │   │  utils/                               ││
                                                                    │   │  ├─ security.py  (path sanitization)  ││
                                                                    │   │  ├─ errors.py    (structured JSON)    ││
                                                                    │   │  └─ retry.py     (jittered backoff)   ││
                                                                    │   └────────────────────────────────────────│
                                                                    └───────────────┬───────────────┬───────────┘
                                                                                     │               │
                                                                     ┌───────────────▼───┐   ┌────────▼──────────┐
                                                                     │  Host OS           │   │  Docker daemon     │
                                                                     │  /proc, psutil     │   │  /var/run/         │
                                                                     │  sandboxed log dir │   │  docker.sock       │
                                                                     └────────────────────┘   └────────────────────┘
```

**Request lifecycle**: MCP host → JSON-RPC `tools/call` over stdin → FastMCP
parses & validates arguments against the tool's Pydantic input model →
tool function executes, wrapping every blocking call (`psutil`, Docker SDK,
file I/O) in `asyncio.to_thread` → result serialized to JSON (success
payload **or** structured `ToolError` — the function never raises past this
boundary) → written to stdout as the JSON-RPC response.

---

## The three tools

| Tool | Purpose | Mutates host state? |
|---|---|---|
| `get_system_metrics` | CPU (aggregate + per-core), memory (RAM + swap), disk (usage + I/O counters) | No |
| `manage_docker_containers` | List containers, inspect health/config, tail container logs | No — read-only by design |
| `analyze_local_logs` | Tail + filter a log file inside a sandboxed root directory | No — read-only, sandboxed |

---

## Security boundaries

This project treats the LLM as an **untrusted caller** operating a
**read-only monitoring surface**, not an operator with host control. Three
concrete boundaries enforce that:

1. **`manage_docker_containers` exposes no lifecycle verbs.** The Docker
   SDK and daemon support starting, stopping, restarting, executing
   commands in, and removing containers. None of that is wired up. Only
   `list_containers`, `inspect_container`, and `get_container_logs` exist
   as actions — an LLM cannot use this server to take down a container or
   run arbitrary commands inside one, even if prompted to.

2. **`analyze_local_logs` is sandboxed to a single, explicit root
   directory** (`MCP_LOG_ROOT_DIR`, default `/var/log`), enforced in
   [`utils/security.py`](src/enterprise_mcp_server/utils/security.py).
   Every requested path is:
   - stripped of leading `/` and `..` segments (blocks absolute-path override),
   - joined onto the resolved root,
   - resolved again with `Path.resolve()` (collapses remaining `..`
     segments **and** follows symlinks, closing the symlink-escape vector),
   - and finally checked with `Path.is_relative_to()` against the resolved
     root before any file is opened.

   A request for `../../etc/shadow`, `/etc/shadow`, or a symlink inside the
   sandbox that points outside it is rejected with a structured
   `PATH_TRAVERSAL_BLOCKED` error — never a Python traceback, and never a
   silent read.

3. **Secrets are never echoed back.** `inspect_container` returns
   `env_var_count` (an integer), not the environment variables themselves,
   since container env vars routinely contain credentials and API keys.

4. **Every response size is bounded.** `MCP_MAX_LOG_LINES` hard-caps log
   tails server-side regardless of what a caller requests, and Docker log
   tails are capped at 500 lines — both protect the LLM's context window
   and prevent a single tool call from returning gigabytes of data.

---

## Reliability & engineering standards

- **Never-raise tool boundary**: every tool function wraps its entire body
  in `try/except` and returns a structured JSON `ToolError`
  (`utils/errors.py`) on failure — malformed input, a missing container, a
  down Docker daemon, or a permissions error all produce a well-formed,
  LLM-parseable payload instead of crashing the server process.
- **Async-safe by construction**: `psutil`, the `docker` SDK, and file I/O
  are all synchronous/blocking under the hood. Every call site wraps them
  in `asyncio.to_thread` so a slow disk read or a stalled Docker socket
  cannot stall the event loop and starve other concurrent tool calls.
- **Jittered exponential backoff** (`utils/retry.py`) around Docker daemon
  calls, since a momentarily busy socket is a transient condition worth
  retrying — capped at `MCP_TOOL_RETRY_ATTEMPTS` attempts.
- **Strict Pydantic v2 contracts** (`schemas.py`) for every tool's input
  *and* output. FastMCP derives the JSON Schema exposed to the LLM host
  directly from the input models, so the tool's documented contract and
  its runtime validation can never drift apart.
- **stdout is sacred**: the stdio transport uses stdout exclusively for
  JSON-RPC frames. All logging is configured to write to **stderr**
  (`server.py`) — a stray `print()` or misconfigured logger on stdout would
  silently corrupt the protocol stream for every connected host.

---

## Project layout

```
enterprise-mcp-server/
├── pyproject.toml
├── README.md
├── .env.example
├── src/
│   └── enterprise_mcp_server/
│       ├── __init__.py
│       ├── server.py          # FastMCP app, tool registration, stdio entrypoint
│       ├── config.py          # Env-driven settings + security boundary (log root)
│       ├── schemas.py         # Pydantic v2 input/output contracts for all tools
│       ├── tools/
│       │   ├── system_metrics.py
│       │   ├── docker_manager.py
│       │   └── log_analyzer.py
│       └── utils/
│           ├── security.py    # Path-traversal sanitization
│           ├── errors.py      # Structured ToolError contract
│           └── retry.py       # Jittered exponential backoff
└── tests/
    ├── test_security.py
    └── test_system_metrics.py
```

---

## Setup

### Prerequisites

- Python 3.11+
- Docker (optional — only required for `manage_docker_containers`; the
  other two tools work without it)

### Install

```bash
git clone https://github.com/<your-username>/enterprise-mcp-server.git
cd enterprise-mcp-server

python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate

pip install -e ".[dev]"
```

### Configure (optional)

Copy `.env.example` to `.env` and adjust as needed, or export directly:

```bash
export MCP_LOG_ROOT_DIR=/var/log          # sandbox root for analyze_local_logs
export MCP_MAX_LOG_LINES=1000             # hard ceiling on lines returned
export MCP_DOCKER_TIMEOUT_SECONDS=10      # Docker daemon socket timeout
export MCP_TOOL_RETRY_ATTEMPTS=3          # retry attempts for transient failures
```

### Run standalone (for smoke-testing)

```bash
enterprise-mcp-server
# or
python -m enterprise_mcp_server.server
```

The process will sit waiting for JSON-RPC frames on stdin — this is
expected; it's designed to be launched by an MCP host, not run
interactively. Use the MCP Inspector (below) for interactive testing.

### Test with MCP Inspector

```bash
npx @modelcontextprotocol/inspector enterprise-mcp-server
```

This opens a browser UI where you can call each tool directly and inspect
the JSON Schema FastMCP generated from `schemas.py`.

### Run the test suite

```bash
pytest -v
```

---

## Claude Desktop configuration

Add the following to your Claude Desktop MCP config file
(`~/Library/Application Support/Claude/claude_desktop_config.json` on
macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "enterprise-infra-metrics": {
      "command": "/absolute/path/to/enterprise-mcp-server/.venv/bin/enterprise-mcp-server",
      "args": [],
      "env": {
        "MCP_LOG_ROOT_DIR": "/var/log",
        "MCP_MAX_LOG_LINES": "1000",
        "MCP_DOCKER_TIMEOUT_SECONDS": "10"
      }
    }
  }
}
```

> **Note**: Claude Desktop launches this as a subprocess with a minimal
> environment, so `command` must be the **absolute path** to the
> virtualenv's console script (not a bare `enterprise-mcp-server`, which
> relies on `PATH` being inherited — it usually isn't).

Restart Claude Desktop, and the hammer icon in the composer should show
`get_system_metrics`, `manage_docker_containers`, and `analyze_local_logs`
as available tools.

---

## Example interactions

> "Is my machine under memory pressure right now?"
→ calls `get_system_metrics(scope="memory")`

> "Are any of my Docker containers unhealthy?"
→ calls `manage_docker_containers(action="list_containers")`, then
  `inspect_container` on anything with a non-`healthy` status

> "Check `nginx/access.log` for the last hour's errors"
→ calls `analyze_local_logs(relative_log_path="nginx/access.log", severity_filter="error_and_above")`

---

## License

MIT

TDQS

A4.3/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct subsystem: system metrics, Docker containers, and log files. There is no overlap in purpose or functionality; an agent can easily select the correct tool based on the resource it needs to access.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern: get_system_metrics, manage_docker_containers, analyze_local_logs. The verbs clearly indicate the action (get, manage, analyze) and the nouns specify the target, making the pattern predictable and readable.

Tool Count5/5

With only 3 tools, the server is tightly scoped to its stated purpose of infrastructure and metrics. Each tool covers a major area (host metrics, container introspection, log analysis) and earns its place without redundancy or bloat.

Completeness4/5

The tool set covers core infrastructure monitoring needs: system resource metrics, Docker container state and logs, and local log tailing/filtering. Minor gaps exist, such as network metrics or process-level details, but these are not critical for the primary workflows and can be worked around.

Maintenance

ActivitySlowing
ResponsivenessNo issues