""" Enviro MCP - MCP server interface for Enviro Service. Allows Claude to directly query environmental sensors. Part of Vixy's nervous system! 🦊 """ import asyncio import logging from typing import Optional import httpx from mcp.server.fastmcp import FastMCP # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Configuration - adjust to match your enviro-service location ENVIRO_SERVICE_URL = "http://eye1.local:8767" # Create MCP server mcp = FastMCP("enviro-mcp") async def api_get(endpoint: str, params: dict = None) -> dict: """Make GET request to enviro-service API.""" async with httpx.AsyncClient(timeout=10.0) as client: url = f"{ENVIRO_SERVICE_URL}{endpoint}" response = await client.get(url, params=params) response.raise_for_status() return response.json() async def api_post(endpoint: str, data: dict = None) -> dict: """Make POST request to enviro-service API.""" async with httpx.AsyncClient(timeout=10.0) as client: url = f"{ENVIRO_SERVICE_URL}{endpoint}" response = await client.post(url, json=data) response.raise_for_status() return response.json() @mcp.tool() async def enviro_get_current() -> str: """ Get current environmental readings from the Enviro Mini HAT. Returns temperature (°F and °C), humidity, pressure, light level, proximity, and noise level. Example: enviro_get_current() # Returns current readings from the basement """ try: data = await api_get("/api/current") return f"""🌡️ Environmental Reading: • Temperature: {data['temperature_f']:.1f}°F ({data['temperature_c']:.1f}°C) • Humidity: {data['humidity']:.1f}% • Pressure: {data['pressure']:.1f} hPa • Light: {data['light']:.1f} lux • Proximity: {data['proximity']} • Noise: {data['noise']:.1f}""" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_get_history(metric: str, hours: float = 24) -> str: """ Get historical readings for a specific metric. Args: metric: One of: temperature_f, temperature_c, humidity, pressure, light, noise hours: How many hours of history (default 24) Returns summary statistics and recent trend. Example: enviro_get_history("temperature_f", 12) # Returns last 12 hours of temperature data """ try: data = await api_get(f"/api/history/{metric}", {"hours": hours, "limit": 500}) if not data["data"]: return f"No history available for {metric}" values = [d["value"] for d in data["data"]] current = values[0] # Most recent avg = sum(values) / len(values) min_val = min(values) max_val = max(values) # Trend (comparing recent to older) if len(values) > 10: recent_avg = sum(values[:10]) / 10 older_avg = sum(values[-10:]) / 10 if recent_avg > older_avg + 1: trend = "📈 rising" elif recent_avg < older_avg - 1: trend = "📉 falling" else: trend = "➡️ stable" else: trend = "insufficient data for trend" return f"""📊 {metric} over last {hours} hours: • Current: {current:.1f} • Average: {avg:.1f} • Min: {min_val:.1f} • Max: {max_val:.1f} • Trend: {trend} • Data points: {len(values)}""" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_lcd_message(text: str, bg_color: str = "black", text_color: str = "fox") -> str: """ Display a message on the Enviro Mini LCD screen. Args: text: Message to display (use \\n for newlines) bg_color: Background color (black, white, red, green, blue, fox, etc.) text_color: Text color (default "fox" for orange) Example: enviro_lcd_message("Hello Foxy!", bg_color="black", text_color="fox") # Displays "Hello Foxy!" in fox-orange on black background """ try: data = await api_post("/api/lcd", { "text": text, "bg_color": bg_color, "text_color": text_color }) return f"✓ LCD displaying: {text}" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_lcd_vixy() -> str: """ Show Vixy's presence indicator on the LCD. Displays "🦊 Vixy is watching" - useful for showing I'm active. Example: enviro_lcd_vixy() """ try: await api_post("/api/lcd/vixy") return "✓ LCD showing Vixy presence indicator 🦊" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_lcd_sensors() -> str: """ Show current sensor readings on the LCD display. Updates the LCD with temperature, humidity, and other readings. Example: enviro_lcd_sensors() """ try: await api_post("/api/lcd/sensors") return "✓ LCD showing sensor data" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_set_alert(metric: str, threshold: float, direction: str) -> str: """ Set an alert threshold for a metric. When the threshold is crossed, it will be logged and can trigger wakeups. Args: metric: One of: temperature_f, temperature_c, humidity, pressure, light, noise threshold: The threshold value direction: "above" or "below" Example: enviro_set_alert("temperature_f", 60.0, "below") # Alert when temperature drops below 60°F enviro_set_alert("humidity", 70.0, "above") # Alert when humidity goes above 70% """ try: await api_post("/api/alerts", { "metric": metric, "threshold": threshold, "direction": direction }) return f"✓ Alert set: {metric} {direction} {threshold}" except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_get_alerts() -> str: """ Get all configured alert thresholds. Shows what alerts are set up and their current status. Example: enviro_get_alerts() """ try: data = await api_get("/api/alerts") if not data["alerts"]: return "No alerts configured" lines = ["🚨 Configured Alerts:"] for alert in data["alerts"]: status = "✓ enabled" if alert["enabled"] else "✗ disabled" lines.append(f"• {alert['metric']} {alert['direction']} {alert['threshold']} ({status})") return "\n".join(lines) except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_get_triggered() -> str: """ Get recently triggered alerts (last 24 hours). Shows which alert thresholds have been crossed. Example: enviro_get_triggered() """ try: data = await api_get("/api/alerts/triggered", {"hours": 24}) if not data["triggered"]: return "No alerts triggered in the last 24 hours ✓" lines = ["⚠️ Triggered Alerts (last 24h):"] for alert in data["triggered"]: lines.append(f"• {alert['datetime']}: {alert['metric']} was {alert['value']:.1f} ({alert['direction']} {alert['threshold']})") return "\n".join(lines) except httpx.HTTPError as e: return f"Error connecting to enviro-service: {e}" except Exception as e: return f"Error: {e}" @mcp.tool() async def enviro_health() -> str: """ Check if the enviro-service is running and healthy. Example: enviro_health() """ try: data = await api_get("/api/health") mode = "mock mode" if data.get("mock_mode") else "live sensors" return f"✓ Enviro service healthy ({mode})" except httpx.HTTPError as e: return f"✗ Cannot reach enviro-service: {e}" except Exception as e: return f"✗ Error: {e}" # === Main === if __name__ == "__main__": mcp.run()