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 manifest —
GET /toolsserves a JSON schema LLMs use to discover capabilities - Deterministic execution —
POST /executeroutes only approved tool names to safe Bash commands - Linux-native metrics —
/proc/loadavg,free -h, anddf -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
- 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.
- Security and boundaries — hardcoded
TOOLSlist with deterministic routing. Not blindly piping LLM text into a shell; a safe, auditable gateway.
Related work
- Project 2: Log Anomaly CLI — same stdlib Python + local LLM pattern, different layer (logs vs. system metrics)
- Project 5: Link Sweeper — network-side validation for the content this stack publishes
- Gnomad CRM — agent-ready APIs at the application layer
- Project 7: Multi-Tenant Migration Engine — 2PC fleet migrations for tenant databases
- Project 8: Async TCP Gateway — raw TCP sockets vs. HTTP context server