Case Study

Local-Environment Context Server

Zero-dependency Python MCP-style server — exposes Linux system tools via native http.server so agentic workflows can query load, memory, and disk securely.

  • Python
  • http.server
  • subprocess
  • JSON Schema
  • Fedora
  • python
  • mcp
  • agents
  • linux
  • homelab
  • zero-dependency
  • nobara

Overview

Building a bridge between a local Linux operating system and an autonomous AI agent is a massive flex. While the official Model Context Protocol (MCP) often uses complex SDKs, the underlying architectural concept is simple: exposing structured system tools via a stateless API that an LLM can understand and trigger.

This zero-dependency Python build leverages the native http.server, json, and subprocess libraries. It creates a lightweight context server tailored for a Nobara or Fedora environment, allowing agentic tools (like n8n, OpenClaw, or standard LLM API calls) to securely query real-time system metrics.

Part of the DIY build series: Projects 1 · 2 · 3 · 4 · 5.

What it implements

  • Tool manifestGET /tools serves a JSON schema LLMs use to discover capabilities
  • Deterministic executionPOST /execute routes only approved tool names to safe Bash commands
  • Linux-native metrics/proc/loadavg, free -h, and df -h / for real host telemetry
  • CORS headers — local web dashboards and orchestrators can call the server from the browser
  • Sandbox boundaries — no arbitrary shell passthrough; hardcoded tool list only

Project setup

mkdir local-context-server && cd local-context-server
touch mcp_server.py

The code (mcp_server.py)

Notice how it strictly defines a TOOLS manifest — the JSON schema format modern LLMs (Claude, GPT-4o, etc.) require to understand available tools:

#!/usr/bin/env python3
import http.server
import json
import subprocess
import os
import sys

PORT = 8080

# --- The Tool Manifest (Schema for the LLM) ---
TOOLS = [
    {
        "name": "get_system_load",
        "description": "Returns the current CPU load averages for the Linux host.",
        "parameters": {}
    },
    {
        "name": "get_memory_usage",
        "description": "Returns total and available RAM from /proc/meminfo.",
        "parameters": {}
    },
    {
        "name": "get_disk_space",
        "description": "Returns the current storage utilization of the root partition.",
        "parameters": {}
    }
]

# --- Native Linux Execution Commands ---
def execute_tool(tool_name: str, args: dict) -> dict:
    """Routes the LLM's requested tool name to actual local Bash commands."""
    try:
        if tool_name == "get_system_load":
            with open("/proc/loadavg", "r") as f:
                load = f.read().strip().split()[:3]
            return {"status": "success", "data": f"1m: {load[0]}, 5m: {load[1]}, 15m: {load[2]}"}

        elif tool_name == "get_memory_usage":
            result = subprocess.run(["free", "-h"], capture_output=True, text=True, check=True)
            lines = result.stdout.strip().split("\n")
            return {"status": "success", "data": f"{lines[0]}\n{lines[1]}"}

        elif tool_name == "get_disk_space":
            result = subprocess.run(["df", "-h", "/"], capture_output=True, text=True, check=True)
            return {"status": "success", "data": result.stdout.strip()}

        else:
            return {"status": "error", "message": f"Tool '{tool_name}' not found."}

    except Exception as e:
        return {"status": "error", "message": str(e)}

# --- Native HTTP Server Implementation ---
class ContextServerHandler(http.server.BaseHTTPRequestHandler):

    def _set_headers(self, status_code=200):
        self.send_response(status_code)
        self.send_header('Content-type', 'application/json')
        self.send_header('Access-Control-Allow-Origin', '*')
        self.end_headers()

    def do_GET(self):
        """Endpoint to serve the tool manifest to the agentic orchestrator."""
        if self.path == '/tools':
            self._set_headers()
            self.wfile.write(json.dumps({"tools": TOOLS}).encode('utf-8'))
        else:
            self._set_headers(404)
            self.wfile.write(json.dumps({"error": "Use GET /tools to list capabilities."}).encode('utf-8'))

    def do_POST(self):
        """Endpoint for the agent to execute a specific tool."""
        if self.path == '/execute':
            content_length = int(self.headers.get('Content-Length', 0))
            post_data = self.rfile.read(content_length)

            try:
                request_body = json.loads(post_data.decode('utf-8'))
                tool_name = request_body.get('tool')
                tool_args = request_body.get('parameters', {})

                if not tool_name:
                    self._set_headers(400)
                    self.wfile.write(json.dumps({"error": "Missing 'tool' in request body."}).encode('utf-8'))
                    return

                print(f"\033[96m[Agent Triggered] Executing: {tool_name}\033[0m")
                result = execute_tool(tool_name, tool_args)

                self._set_headers(200 if result['status'] == 'success' else 500)
                self.wfile.write(json.dumps(result).encode('utf-8'))

            except json.JSONDecodeError:
                self._set_headers(400)
                self.wfile.write(json.dumps({"error": "Invalid JSON payload."}).encode('utf-8'))
        else:
            self._set_headers(404)
            self.wfile.write(json.dumps({"error": "Use POST /execute to run a tool."}).encode('utf-8'))

    def log_message(self, format, *args):
        pass

if __name__ == '__main__':
    server_address = ('', PORT)
    httpd = http.server.HTTPServer(server_address, ContextServerHandler)
    print(f"\033[92m🚀 Local Context Server running on http://localhost:{PORT}\033[0m")
    print("Available endpoints:")
    print("  - \033[1mGET /tools\033[0m    (View the manifest)")
    print("  - \033[1mPOST /execute\033[0m (Run a tool)")
    print("\nWaiting for agent connections...")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        print("\n\033[91mServer terminated.\033[0m")
        sys.exit(0)

Execution & testing

Make the script executable and start the server:

chmod +x mcp_server.py
./mcp_server.py

In a second terminal, simulate an AI agent discovering and calling tools:

curl http://localhost:8080/tools
curl -X POST http://localhost:8080/execute \
     -H "Content-Type: application/json" \
     -d '{"tool": "get_memory_usage", "parameters": {}}'

Hook this into a local n8n workflow or OpenClaw instance and your agentic pipelines can monitor machine health in real time.


Why this shines on a portfolio

  1. Architectural bridging — proves you understand how AI escapes the browser and interacts with bare-metal hardware. Pair with agentic workflow notes and Nobara homelab basics.
  2. Security and boundaries — hardcoded TOOLS list with deterministic routing. Not blindly piping LLM text into a shell; a safe, auditable gateway.