Skip to main content
Glama
Maneesh-Rajbhar

Enterprise Infrastructure & Metrics MCP Server

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.


Related MCP server: GhostInTheShell MCP

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. 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

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:

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)

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

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

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):

{
  "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

Available Tools

3 tools
analyze_local_logsA

Tail and optionally filter a log file. The path is resolved relative to a server-configured, sandboxed log directory -- absolute paths and '..' traversal are rejected. Supports a coarse severity filter (error_and_above / warning_and_above) and a case-insensitive substring search.

ParametersJSON Schema
NameRequiredDescriptionDefault
payloadYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Implies read-only behavior through 'tail', but does not explicitly state that no modifications occur. No annotations are provided, so the description carries the burden.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence that front-loads the primary function and includes key security constraints.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Sufficient for an agent to understand the tool's purpose and constraints. Output format is not described in the description, but an output schema is provided.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description does not add parameter details beyond the schema, which already includes comprehensive descriptions for tail_lines, severity_filter, search_substring, and relative_log_path.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states the tool's function: tailing and optionally filtering a log file. Also specifies security constraints for path resolution.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear action and scope but does not explicitly compare with sibling tools. However, the tool's purpose is self-evident for log analysis.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_system_metricsA

Fetch a live snapshot of host CPU (aggregate + per-core), memory (RAM + swap), and disk (usage + I/O counters) metrics. Use scope to limit the response to one subsystem.

ParametersJSON Schema
NameRequiredDescriptionDefault
payloadYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It states it fetches a 'live snapshot', implying read-only behavior, but does not mention that `cpu_sample_seconds` can block the call or any other side effects. The schema provides this detail, but since the description does not, it is only minimally transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and efficient, consisting of two sentences that front-load the primary purpose. It avoids unnecessary words and clearly conveys the tool's function and a key parameter hint. No wasted words or redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with a single nested parameter, and the output schema is present, so return format is known. The description covers the main purpose and the scope parameter, but does not explain edge cases or error handling. However, given the simplicity, the description is sufficiently complete for an agent to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description mentions `scope` and its purpose (limiting response to one subsystem), but this information is already present in the schema's description for the enum and the input object. It does not mention `cpu_sample_seconds` at all. Given the context signal of 0% schema description coverage, the description should compensate for undocumented parameters, but it fails to do so for one of the two parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: fetching a live snapshot of host CPU, memory, and disk metrics. It uses a specific verb ('fetch') and resource ('host metrics'), and the scope of the tool is obvious. It is easily distinguishable from sibling tools (manage_docker_containers, analyze_local_logs) which handle containers and logs, not system metrics.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context on what the tool does (metrics fetching) but does not explicitly mention when not to use it or name alternatives. However, the purpose is self-evident and the siblings are clearly different, so an agent can infer when to select this tool. It also offers guidance on using the `scope` parameter, which aids in efficient usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

manage_docker_containersA

Read-only Docker introspection: list containers, inspect a specific container's health/state, or fetch the tail of a container's logs. Does NOT start, stop, restart, or remove containers -- this tool cannot mutate container state.

ParametersJSON Schema
NameRequiredDescriptionDefault
payloadYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility. It explicitly declares read-only behavior and notes the hard cap on log tail lines to protect the LLM context window, disclosing important runtime constraints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The main description is a single, focused sentence that lists actions and explicitly excludes mutations. The schema descriptions are also concise, with no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description and schema together fully cover what the tool does, its inputs, defaults, and important safety details (read-only, log cap). No critical information is missing for an agent to correctly invoke it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although the top-level schema description coverage is 0%, the nested input schema includes detailed descriptions for each parameter (action, container_id, include_stopped, log_tail_lines), explaining their purpose, defaults, and constraints (e.g., minimum/maximum for log_tail_lines).

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's verb (introspection), resource (Docker containers), and specific actions (list, inspect, fetch logs). It distinguishes itself from sibling tools (get_system_metrics, analyze_local_logs) by focusing on Docker-specific operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states what the tool does NOT do (does not start/stop/restart/remove containers), providing clear guidance for when not to use it. It also names the valid actions, giving a quick reference for operation selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv1.0.0
    • First observedanalyze_local_logs
    • First observedget_system_metrics
    • First observedmanage_docker_containers

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

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    A sandboxed, read-only MCP server that safely exposes system metrics, container diagnostics, and logs to AI agents with intelligent context compression and strict security measures.
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to safely explore and diagnose remote servers by providing a read-only sandbox with controlled access to files, logs, Docker, and databases. It exposes MCP tools that allow natural-language investigation and direct command execution without write permissions.
    3
    -