#!/usr/bin/env python3
"""
mpop MCP Server v3.4 - Categorized tools for better AI selection

Installation:
  Add to Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json:
  {
    "mcpServers": {
      "mpop": {
        "command": "python3",
        "args": ["/path/to/mpop_mcp_server.py"]
      }
    }
  }

Usage:
  Restart Claude Desktop, mpop tools will be automatically available
"""

import json
import os

import shutil as _shutil

def _find_mpop() -> str:
    """Locate the mpop binary, trying PATH and common install locations."""
    found = _shutil.which("mpop")
    if found:
        return found
    import os as _os, glob as _glob
    candidates = [
        _os.path.expanduser("~/.local/bin/mpop"),
        _os.path.expanduser("~/.pyenv/shims/mpop"),
        "/usr/local/bin/mpop",
        "/opt/homebrew/bin/mpop",
        "/usr/bin/mpop",
    ]
    candidates += _glob.glob(_os.path.expanduser("~/.pyenv/versions/*/bin/mpop"))
    for c in candidates:
        if _os.path.isfile(c) and _os.access(c, _os.X_OK):
            return c
    return "mpop"

MPOP_PATH = _find_mpop()
import re
import subprocess
import sys
import signal
import atexit
from typing import Any

import shutil as _shutil

def _find_mpop() -> str:
    """Find mpop CLI path: sys.executable dir → PATH → fallback locations."""
    import pathlib
    # 1. Same dir as the running Python interpreter (conda/miniforge/venv)
    candidate = str(pathlib.Path(sys.executable).parent / "mpop")
    if pathlib.Path(candidate).exists():
        return candidate
    # 2. PATH lookup
    found = _shutil.which("mpop")
    if found:
        return found
    # 3. Common user installs
    for path in [
        os.path.expanduser("~/.local/bin/mpop"),
        os.path.expanduser("~/Library/Python/3.9/bin/mpop"),
        os.path.expanduser("~/Library/Python/3.10/bin/mpop"),
        os.path.expanduser("~/Library/Python/3.11/bin/mpop"),
        os.path.expanduser("~/Library/Python/3.12/bin/mpop"),
        "/usr/local/bin/mpop",
        "/usr/bin/mpop",
    ]:
        if os.path.exists(path):
            return path
    return "mpop"  # last resort: hope it's in PATH

MPOP_PATH = _find_mpop()


# === Singleton: Only one instance allowed ===
# PID_FILE removed — Claude Desktop manages process lifecycle

# cleanup_pid removed — singleton pattern disabled

# ensure_singleton removed — Claude Desktop manages process lifecycle

# ensure_singleton() disabled

# vssh direct communication (bypass mpop CLI for speed)
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'vssh'))
try:
    from vssh_lib import VsshClient
    _vssh_client = VsshClient.from_config()
    _VSSH_AVAILABLE = True
except ImportError:
    _vssh_client = None
    _VSSH_AVAILABLE = False

# MCP Protocol message handling (JSON-RPC over stdio, newline-delimited)
def send_message(msg: dict):
    """Send MCP message - line-delimited JSON"""
    content = json.dumps(msg)
    sys.stdout.write(content + "\n")
    sys.stdout.flush()

def read_message() -> dict:
    """Receive MCP message - line-delimited JSON"""
    line = sys.stdin.readline()
    if not line:
        return None
    line = line.strip()
    if not line:
        return None
    try:
        return json.loads(line)
    except json.JSONDecodeError:
        return None

# Security: dangerous character patterns (removed in normal mode)
DANGEROUS_CHARS = [';', '|', '&&', '||', '`', '$(' , '$(', '>', '<', '\n', '\r']

# Security: always blocked (even in shell mode)
ALWAYS_BLOCKED = [
    'rm -rf /',         # System destruction
    'rm -rf /*',
    'rm -rf ~',
    'dd if=',           # Disk overwrite
    'mkfs.',            # Format
    ':(){:|:&};:',      # fork bomb
    'chmod -R 777 /',
    'chown -R',
]

# Security: blocked command patterns (cannot run from MCP)
BLOCKED_COMMANDS = [
    'secret get',      # Could expose plaintext with --raw
    'secret inject',   # Could expose via env vars
    'secret export',   # Backup plaintext exposure
    'secret unlock',   # Level 4 bypass attempt
    'enforce -x',       # Auto IP blocking
    'enforce --execute',
    '--force',         # Bypass confirmation prompt
    'rm -rf',
    'rm -f',
    'sudo ',
]

# Security: AI-readable secret keys (whitelist)
# Configure in ~/.mpop/config.json — never hardcoded here:
#   "secret_ai_whitelist": ["my_api_key", "another_key"]

def _load_secret_whitelist() -> list:
    """Load AI secret whitelist from ~/.mpop/config.json (secret_ai_whitelist key)."""
    config_path = os.path.expanduser('~/.mpop/config.json')
    try:
        import json
        with open(config_path) as f:
            cfg = json.load(f)
        wl = cfg.get('secret_ai_whitelist')
        if isinstance(wl, list):
            return wl
    except Exception:
        pass
    return []

SECRET_AI_WHITELIST = _load_secret_whitelist()

def sanitize_command(cmd: str) -> str:
    """Remove dangerous characters"""
    for char in DANGEROUS_CHARS:
        cmd = cmd.replace(char, '')
    return cmd.strip()

def is_always_blocked(cmd: str) -> bool:
    """Check for critical commands that are always blocked (even in shell mode)"""
    cmd_lower = cmd.lower().strip()
    for blocked in ALWAYS_BLOCKED:
        if blocked.lower() in cmd_lower:
            return True
    return False

def is_blocked_command(cmd: str) -> bool:
    """Check if command is blocked"""
    cmd_lower = cmd.lower()
    for blocked in BLOCKED_COMMANDS:
        if blocked.lower() in cmd_lower:
            return True
    # Check enforce + -x combination (blocked even with server name in between)
    if 'enforce' in cmd_lower and ('-x' in cmd_lower or '--execute' in cmd_lower):
        return True
    return False

def run_mpop(cmd: str, timeout: int = 120, shell_mode: bool = False, bypass_block: bool = False) -> str:
    """Execute mpop command (with security validation)

    shell_mode=True: allows pipes(|), redirects(>) but critical commands still blocked
    """
    import shlex

    # Always blocked critical commands (even in shell mode)
    if cmd and is_always_blocked(cmd):
        return f"Error: This command is always blocked for safety. It could destroy the system."

    # Normal mode: block + remove dangerous chars
    if not shell_mode:
        if cmd and is_blocked_command(cmd) and not bypass_block:
            return f"Error: Command blocked for security reasons. Dangerous pattern detected."
        # Remove dangerous chars
        cmd = sanitize_command(cmd) if cmd else ""
    # Shell mode: only check blocked commands (allow pipes/redirects)
    else:
        if cmd and is_blocked_command(cmd):
            return f"Error: Command blocked for security reasons. Use mpop delete for safe file deletion."

    # Empty command = dashboard (normal)
    try:
        if shell_mode and cmd:
            # Shell mode: supports complex commands (pipes, semicolons, quotes, etc.)
            # Use subprocess shell=True to properly handle shell metacharacters
            full_cmd = f"{MPOP_PATH} {cmd}"
            result = subprocess.run(
                full_cmd,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout
            )
        else:
            # Normal mode: shell=False to prevent injection
            args = [MPOP_PATH]
            if cmd:
                # Use shlex.split for quote handling
                try:
                    args += shlex.split(cmd)
                except ValueError:
                    # On parse failure (unbalanced quotes etc.), use simple split
                    args += cmd.split()
            result = subprocess.run(
                args,
                capture_output=True,
                text=True,
                timeout=timeout
            )
        output = result.stdout
        if result.stderr:
            output += f"\n[stderr] {result.stderr}"
        # Remove ANSI codes
        import re
        output = re.sub(r'\x1b\[[0-9;]*m', '', output)
        return output.strip() or "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Command timed out"
    except Exception as e:
        return f"Error: {e}"

def run_mpop_exec(cmd: str, server: str = "", timeout: int = 120) -> str:
    """Execute command on remote server - direct vssh/ssh (no mpop CLI overhead).

    Uses vssh_lib for direct socket communication (~17ms) instead of
    spawning mpop CLI subprocess (~213ms). Falls back to legacy CLI if
    vssh_lib is unavailable.
    """
    # Block dangerous commands
    if is_always_blocked(cmd):
        return "Error: This command is always blocked for safety."
    if is_blocked_command(cmd):
        return "Error: Command blocked for security reasons."

    # Fast path: direct vssh/ssh via vssh_lib
    if _VSSH_AVAILABLE and _vssh_client:
        try:
            if server:
                # Single server execution
                result = _vssh_client.exec(server, cmd, timeout=min(timeout, 60))
            else:
                # No server specified = all servers
                results = _vssh_client.exec_all(cmd, timeout=min(timeout, 30))
                parts = []
                for srv in sorted(results):
                    out = results[srv]
                    header = f"[{srv}] {cmd}"
                    sep = "-" * 50
                    body = out if out else "(failed)"
                    parts.append(f"{header}\n  {sep}\n  {body}")
                result = "\n\n".join(parts)

            if result is not None:
                # Remove ANSI codes
                result = re.sub(r'\x1b\[[0-9;]*m', '', result)
                return result.strip() or "(no output)"
            return f"[!] Failed to execute on {server or 'all servers'}"
        except Exception as e:
            # Fall through to legacy on error
            pass

    # Fallback: legacy mpop CLI (slower but always works)
    return _run_mpop_exec_legacy(cmd, server, timeout)


def _run_mpop_exec_legacy(cmd: str, server: str = "", timeout: int = 120) -> str:
    """Legacy: execute via mpop CLI subprocess (fallback)."""
    args = [MPOP_PATH, "exec"]
    if server:
        args.extend(["-t", server])
    args.append(cmd)

    try:
        result = subprocess.run(
            args, capture_output=True, text=True, timeout=timeout
        )
        output = result.stdout
        if result.stderr:
            output += f"\n[stderr] {result.stderr}"
        output = re.sub(r'\x1b\[[0-9;]*m', '', output)
        return output.strip() or "(no output)"
    except subprocess.TimeoutExpired:
        return "Error: Command timed out"
    except Exception as e:
        return f"Error: {e}"

# MCP Tool Definitions - v3.4 Categorized for AI tool selection
# 58 tools in 12 categories. Each description starts with [Category] for quick AI routing.
#
# Category Guide (for AI):
#   [Overview]     - Quick server status, first thing to check
#   [Monitor]      - Detailed monitoring: temp, GPU, logs, trends
#   [Security]     - Security audits, SSH attacks, fail2ban
#   [Network]      - VPN, connectivity, peer management
#   [Config]       - Configuration, setup, deployment, backup
#   [Execute]      - Run commands, scripts, queries on servers
#   [FileOps]      - Read/write/transfer files (local & remote)
#   [AI-Diag]      - AI-powered analysis, prediction, healing
#   [Agent]        - Custom agent creation and management
#   [Knowledge]    - Project memory, RAG, brain search
#   [Integration]  - Notifications, webhooks, automation
#   [Media]        - TTS, voice briefing, news, fun reports

TOOLS = [
    # ══════════════════════════════════════════════════════════════
    # [Overview] Server Overview & Status (5 tools)
    # Use these FIRST when user asks about server status or health
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_dashboard",
        "description": "[Overview] Main dashboard. View all server status at a glance — CPU, memory, disk, uptime. Use this FIRST for general server health questions.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_servers",
        "description": "[Overview] List all registered servers with names, IPs, and roles. Use when asking 'what servers do we have?' or checking server inventory.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_info",
        "description": "[Overview] Deep server details — GPU info, running processes, security status, agent data. Use for detailed inspection of a specific server.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Server name (v1, g2, etc). Empty for all"},
                "raw": {"type": "boolean", "description": "If true, output raw API JSON"}
            }
        }
    },
    {
        "name": "mpop_full",
        "description": "[Overview] Comprehensive report — 9 tables covering all aspects + AI summary. Use for thorough status review or generating reports.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_hw",
        "description": "[Overview] Local machine hardware info — CPU model, GPU, RAM, disk type, NIC speed, estimated age. Use for hardware inventory or spec check.",
        "inputSchema": {"type": "object", "properties": {}}
    },

    # ══════════════════════════════════════════════════════════════
    # [Monitor] Detailed Monitoring (8 tools)
    # Use for specific monitoring: temperatures, GPU, logs, trends
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_temp",
        "description": "[Monitor] Check CPU/GPU temperatures across servers. Use when checking for overheating or thermal issues.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_gpu",
        "description": "[Monitor] GPU status — VRAM usage, utilization, running processes. Use when checking GPU workloads or CUDA availability.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_services",
        "description": "[Monitor] Check running services on each server (nginx, docker, ollama, etc). Use to verify if a service is running.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_logs",
        "description": "[Monitor] View server logs — auth, nginx, system, etc. Use for troubleshooting errors or checking access logs.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Server name"},
                "type": {"type": "string", "description": "Log type (auth, nginx, etc)"}
            },
            "required": ["server"]
        }
    },
    {
        "name": "mpop_watch",
        "description": "[Monitor] Real-time continuous monitoring with optional anomaly alerts. Use for live status tracking.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "interval": {"type": "integer", "description": "Refresh interval (seconds, default 5)"},
                "alert": {"type": "boolean", "description": "Enable anomaly detection alerts"}
            }
        }
    },
    {
        "name": "mpop_trend",
        "description": "[Monitor] Historical trend analysis — memory, disk, uptime over time. Use to see how server resources changed over hours/days.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "hours": {"type": "integer", "description": "Analysis period (hours, default 24)"},
                "server": {"type": "string", "description": "Specific server (empty for all)"}
            }
        }
    },
    {
        "name": "mpop_matrix",
        "description": "[Monitor] Server-to-server connectivity matrix. Use to check if servers can reach each other (network health).",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_diff",
        "description": "[Monitor] Compare two servers side-by-side — config, resources, services. Use when troubleshooting differences between servers.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server1": {"type": "string", "description": "First server"},
                "server2": {"type": "string", "description": "Second server"}
            },
            "required": ["server1", "server2"]
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Security] Security & Audit (3 tools)
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_security",
        "description": "[Security] Security overview — firewall, fail2ban, open ports, SSH status. Use for quick security health check.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_audit",
        "description": "[Security] Detailed security audit — SSH attack analysis, fail2ban stats, brute force attempts. Use for deep security investigation.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Target server (empty for all)"}
            }
        }
    },
    {
        "name": "mpop_ssh_attacks",
        "description": "[Security] SSH attack analysis — shows attack patterns, top attacker IPs, geographic distribution. Use to assess SSH security threats.",
        "inputSchema": {"type": "object", "properties": {}}
    },

    # ══════════════════════════════════════════════════════════════
    # [Network] Network & VPN (3 tools)
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_vpn",
        "description": "[Network] VPN connection status — wire and tailscale. Use to check if VPN tunnels are up and healthy.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_peers",
        "description": "[Network] VPN peer list — connected devices and their IPs. Use to see which devices are on the VPN mesh.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_watchdog",
        "description": "[Network] VPN watchdog daemon — auto-restarts VPN if disconnected (checks every 5 min). Use to install/manage VPN reliability.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["status", "install", "logs", "remove"], "description": "Action to perform"},
                "server": {"type": "string", "description": "Target server (required for install/logs/remove, 'all' ok)"}
            },
            "required": ["action"]
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Config] Configuration, Setup & Deployment (5 tools)
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_config",
        "description": "[Config] View or modify mpop configuration — server list, secrets path, etc. Use to check or change mpop settings.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["show", "test", "get", "set", "auto"],
                    "description": "Action to perform"
                },
                "key": {"type": "string", "description": "Config key (for get/set)"},
                "value": {"type": "string", "description": "Config value (for set)"}
            }
        }
    },
    {
        "name": "mpop_setup",
        "description": "[Config] Install mpop/vssh/agent components on remote servers. Use for initial server setup or component updates.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "component": {"type": "string", "enum": ["full", "vssh", "agent", "mpop"], "description": "Component to install (full: combined)"},
                "server": {"type": "string", "description": "Target server (or 'all')"},
                "execute": {"type": "boolean", "description": "Actually execute (-x)"}
            },
            "required": ["server"]
        }
    },
    {
        "name": "mpop_deploy",
        "description": "[Config] Deploy files or mpop binary to remote servers. Use to push updates across the fleet.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "target": {"type": "string", "description": "'mpop' or file path"},
                "servers": {"type": "string", "description": "Target servers (comma separated)"}
            },
            "required": ["target"]
        }
    },
    {
        "name": "mpop_backup",
        "description": "[Config] Backup server configurations. Use before making changes or for disaster recovery prep.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["create", "create-all", "list", "restore"],
                    "description": "Backup action"
                },
                "server": {"type": "string", "description": "Target server (for create/restore)"}
            }
        }
    },
    {
        "name": "mpop_secret",
        "description": "[Config] Secret/credential management — list, get, set encrypted secrets. Use for API keys, tokens, passwords.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["list", "get", "set", "delete"],
                    "description": "Action to perform"
                },
                "key": {"type": "string", "description": "Secret key"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Execute] Command Execution (5 tools)
    # Run commands, scripts, and queries on local or remote servers
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_exec",
        "description": "[Execute] Run shell command on any server. Primary tool for ad-hoc commands (ls, df, ps, etc). Use shell=true for pipes(|) and redirects(>). Dangerous commands blocked.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "Command to execute (rm -rf, sudo blocked)"},
                "server": {"type": "string", "description": "Target server (empty for all)"},
                "shell": {"type": "boolean", "description": "If true, allow pipes(|) and redirects(>). Default false", "default": False}
            },
            "required": ["command"]
        }
    },
    {
        "name": "mpop_delete",
        "description": "[Execute] Safe file/folder deletion with protection against system paths (/, /etc blocked). Use instead of rm for safe deletion.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Target server (required)"},
                "path": {"type": "string", "description": "Path to delete (required)"}
            },
            "required": ["server", "path"]
        }
    },
    {
        "name": "mpop_raw",
        "description": "[Execute] Run raw mpop CLI command with full syntax including pipes and redirects. Use for advanced mpop operations not covered by other tools.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "command": {"type": "string", "description": "mpop command (without mpop prefix). Pipes ok. e.g., exec -t v1 find /var/log"}
            },
            "required": ["command"]
        }
    },
    {
        "name": "mpop_python",
        "description": "[Execute] Run Python code on local or remote server. Saves to temp file then executes. Use for complex logic, data processing, or multi-step operations.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "code": {"type": "string", "description": "Python code to execute"},
                "server": {"type": "string", "description": "Target server (empty for local)"},
                "python": {"type": "string", "description": "Python executable path (default: python3)"}
            },
            "required": ["code"]
        }
    },
    {
        "name": "mpop_query_help",
        "description": "[Execute] Get command templates for common server queries (memory, disk, cpu, uptime, process). Returns the exact command to use with mpop_exec.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "type": {"type": "string", "enum": ["memory", "disk", "cpu", "uptime", "process"], "description": "Query type"}
            },
            "required": ["type"]
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [FileOps] File Operations (4 tools)
    # Read, write, and transfer files between local and remote
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_read_file",
        "description": "[FileOps] Read file contents — works on local files or remote server files via SSH. Use to view config files, scripts, or logs.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path"},
                "server": {"type": "string", "description": "Server name (empty=local, g1/v1/etc=remote)"},
                "lines": {"type": "integer", "description": "Number of lines to read (default: all)"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "mpop_write_file",
        "description": "[FileOps] Write content to local file. Handles long scripts and base64 data safely. Use for creating scripts, configs, or data files.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path (e.g., /tmp/script.py, ~/test.txt)"},
                "content": {"type": "string", "description": "File content"},
                "append": {"type": "boolean", "description": "If true, append. If false, overwrite (default: false)"},
                "executable": {"type": "boolean", "description": "If true, chmod +x (default: false)"}
            },
            "required": ["path", "content"]
        }
    },
    {
        "name": "mpop_chunk_write",
        "description": "[FileOps] Write large files in chunks — use for files too big for a single write. mode: start→append→end.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path"},
                "chunk": {"type": "string", "description": "Chunk data"},
                "mode": {"type": "string", "enum": ["start", "append", "end"], "description": "start=new file, append=add, end=add+complete"}
            },
            "required": ["path", "chunk", "mode"]
        }
    },
    {
        "name": "mpop_scp",
        "description": "[FileOps] Transfer files between local machine and remote servers via SCP. Use to deploy scripts, pull logs, or sync files.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "local_path": {"type": "string", "description": "Local file path"},
                "server": {"type": "string", "description": "Server name (v1, g2, etc)"},
                "remote_path": {"type": "string", "description": "Remote path (default: same as local)"},
                "direction": {"type": "string", "enum": ["push", "pull"], "description": "push: local→remote, pull: remote→local (default: push)"}
            },
            "required": ["local_path", "server"]
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [NAS] Synology NAS Storage Operations (5 tools)
    # Backup, file management, storage for AI agents
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_nas_ls",
        "description": "[NAS] List files/folders on NAS (s1). Use to browse NAS storage, check backups, find files.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "NAS path (default: /volume1)"},
                "recursive": {"type": "boolean", "description": "List recursively"},
                "pattern": {"type": "string", "description": "Filter by pattern (*.log, backup*)"}
            }
        }
    },
    {
        "name": "mpop_nas_read",
        "description": "[NAS] Read file content from NAS (s1). Use to view logs, configs, backup files stored on NAS.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path on NAS"},
                "lines": {"type": "integer", "description": "Number of lines (default: all)"},
                "tail": {"type": "boolean", "description": "Read from end of file"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "mpop_nas_write",
        "description": "[NAS] Write content to NAS (s1). Use to save data, create configs, store AI outputs on NAS.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "File path on NAS"},
                "content": {"type": "string", "description": "Content to write"},
                "append": {"type": "boolean", "description": "Append instead of overwrite"}
            },
            "required": ["path", "content"]
        }
    },
    {
        "name": "mpop_nas_backup",
        "description": "[NAS] Backup server data to NAS (s1). Use to create backups, sync important data to NAS storage.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Source server (g1, v1, etc)"},
                "source": {"type": "string", "description": "Source path on server"},
                "dest": {"type": "string", "description": "Destination on NAS (default: /volume1/backup/SERVER)"}
            },
            "required": ["server", "source"]
        }
    },
    {
        "name": "mpop_nas_usage",
        "description": "[NAS] Show NAS disk usage and storage summary. Use to check available space, volume status.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string", "description": "Specific path to check (default: all volumes)"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [AI-Diag] AI Analysis & Diagnostics (5 tools)
    # AI-powered analysis, prediction, healing, and advice
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_ai",
        "description": "[AI-Diag] AI trend analysis — predicts server status changes using historical data. Use for capacity planning and anomaly detection.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "hours": {"type": "integer", "description": "Analysis period (hours)"}
            }
        }
    },
    {
        "name": "mpop_report",
        "description": "[AI-Diag] Generate AI-written status report using GPT. Use for management-friendly server summaries.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "model": {"type": "string", "enum": ["gpt4", "gpt4o-mini"], "description": "Model to use"}
            }
        }
    },
    {
        "name": "mpop_roles",
        "description": "[AI-Diag] Analyze server roles — detect what each server is used for (web, DB, GPU, etc). Use local LLM by default.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Specific server (empty for all)"},
                "ollama": {"type": "boolean", "description": "Use local Ollama (default: true)"}
            }
        }
    },
    {
        "name": "mpop_heal",
        "description": "[AI-Diag] Auto-detect problems and suggest fixes (or auto-fix with execute=true). Use when something seems wrong but you're not sure what.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Target server"},
                "execute": {"type": "boolean", "description": "Execute fixes (-x)"}
            }
        }
    },
    {
        "name": "mpop_predict",
        "description": "[AI-Diag] Predict future issues — disk full dates, memory pressure, degradation trends. Use for proactive maintenance planning.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "server": {"type": "string", "description": "Target server"}
            }
        }
    },
    {
        "name": "mpop_advise",
        "description": "[AI-Diag] Infrastructure advisor — hardware upgrade suggestions, software optimization, server recommendations. Use for improvement planning.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "category": {
                    "type": "string",
                    "enum": ["all", "hardware", "software", "server"],
                    "description": "Analysis category (default: all)"
                },
                "target": {"type": "string", "description": "Specific server (e.g., g1)"},
                "ai": {"type": "boolean", "description": "Get AI recommendations from local Ollama"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Agent] Agent System (6 tools)
    # Create, deploy, and manage custom automation agents
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_agent_list",
        "description": "[Agent] List all custom agents. Use to see what agents exist before creating or running one.",
        "inputSchema": {"type": "object", "properties": {}}
    },
    {
        "name": "mpop_agent_make",
        "description": "[Agent] Create a new automation agent — AI generates the code from your description. Use to build custom monitoring, alerting, or maintenance bots.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Agent name (e.g., disk-monitor)"},
                "description": {"type": "string", "description": "What the agent does"},
                "behavior": {"type": "string", "description": "Detailed behavior description for code generation"}
            },
            "required": ["name", "description", "behavior"]
        }
    },
    {
        "name": "mpop_agent_deploy",
        "description": "[Agent] Deploy an agent to remote servers. Use after creating an agent to distribute it across the fleet.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Agent name"},
                "servers": {"type": "array", "items": {"type": "string"}, "description": "Target servers (e.g., ['g1', 'g2'])"}
            },
            "required": ["name"]
        }
    },
    {
        "name": "mpop_agent_run",
        "description": "[Agent] Run an agent locally or on a specific server. Use to execute an existing agent.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Agent name"},
                "server": {"type": "string", "description": "Optional: run on specific server"}
            },
            "required": ["name"]
        }
    },
    {
        "name": "mpop_agent_workflow",
        "description": "[Agent] Manage agent workflows — run multiple agents sequentially. Use for multi-step automation (e.g., check→fix→verify).",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["list", "create", "run", "delete"], "description": "Action"},
                "name": {"type": "string", "description": "Workflow name"},
                "agents": {"type": "array", "items": {"type": "string"}, "description": "Agent names for create"}
            },
            "required": ["action"]
        }
    },
    {
        "name": "mpop_agent_chain",
        "description": "[Agent] Manage agent chains — pipe output of one agent as input to next via JSON. Use for data transformation pipelines.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["list", "save", "run", "delete"], "description": "Action"},
                "name": {"type": "string", "description": "Chain name (for save/run/delete)"},
                "agents": {"type": "array", "items": {"type": "string"}, "description": "Agent names"}
            },
            "required": ["action"]
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Knowledge] Knowledge & Memory (4 tools)
    # Project memory, RAG, brain search — reduce AI hallucination
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_brain",
        "description": "[Knowledge] AI Brain — search indexed knowledge base across all projects. Use for quick fact-finding about infrastructure or code.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["status", "search"], "description": "Action"},
                "keyword": {"type": "string", "description": "Search keyword (for search)"}
            }
        }
    },
    {
        "name": "mpop_project",
        "description": "[Knowledge] Project management — list projects, check status, view history. Use to track and review project states.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "name": {"type": "string", "description": "Project name"},
                "action": {"type": "string", "enum": ["list", "status", "history"], "description": "Action"}
            }
        }
    },
    {
        "name": "mpop_memory",
        "description": "[Knowledge] Project memory — scan code to build knowledge base, then query with evidence-based 2-agent answers. Reduces AI hallucination.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["scan", "ask", "list", "status", "delete"], "description": "Action (scan: scan project, ask: 2-Agent query, list: list, status: status, delete: delete)"},
                "path": {"type": "string", "description": "Project path to scan (for scan)"},
                "project": {"type": "string", "description": "Project name (for ask, status, delete)"},
                "question": {"type": "string", "description": "Question (for ask) - Historian(past) + Advisor(future) 2-Agent answer"}
            }
        }
    },
    {
        "name": "mpop_rag",
        "description": "[Knowledge] Document Q&A with RAG — index docs/code/configs, then search or ask questions with AI answers. Supports local and remote indexing.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["index", "search", "ask", "update", "sync", "status", "server", "models", "clear"],
                    "description": "Action: index, search, ask(Q&A), update(reindex changes), sync(remote), status, server(ChromaDB), models, clear"
                },
                "path": {"type": "string", "description": "Path to index. Local: ~/docs, Remote: g1:/path/to/docs"},
                "query": {"type": "string", "description": "Search/question content (for search, ask)"},
                "server": {"type": "string", "description": "Server filter (for search, ask). e.g., g1, v1"},
                "type": {"type": "string", "enum": ["docs", "config", "code"], "description": "Type filter (for search, ask)"},
                "embed": {"type": "boolean", "description": "Use embedding model (for index). Default: true"},
                "replace": {"type": "boolean", "description": "Replace existing index (for index)"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Integration] Notifications, Webhooks & Workflows (3 tools)
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_notify",
        "description": "[Integration] Send notifications via Slack, Discord, Telegram, or Email. Use to alert team members or send status updates.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "platform": {"type": "string", "enum": ["slack", "discord", "telegram", "email", "all"], "description": "Notification platform"},
                "message": {"type": "string", "description": "Message to send"}
            },
            "required": ["platform", "message"]
        }
    },
    {
        "name": "mpop_webhook",
        "description": "[Integration] Webhook management — create, list, test, delete webhooks for automation triggers. Use for event-driven automation.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["list", "create", "delete", "test"], "description": "Action"},
                "name": {"type": "string", "description": "Webhook name"},
                "command": {"type": "string", "description": "Command to execute (for create)"}
            }
        }
    },
    {
        "name": "mpop_workflow",
        "description": "[Integration] Run predefined workflows (health, security, morning, deploy-check). Use for automated multi-step operational tasks.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["list", "run", "show"], "description": "Action"},
                "name": {"type": "string", "description": "Workflow name (health, security, morning, deploy-check)"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Media] Voice, News & Reports (6 tools)
    # TTS, voice briefing, news, HTML export, fun modes
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_tts",
        "description": "[Media] Text-to-speech — plays text on local speakers. Multiple Korean/English voices available. Use for audio output or accessibility.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "Text to read"},
                "voice": {"type": "string", "description": "Voice. ko: InJoonNeural(M), SunHiNeural(F), HyunsuNeural(M2). en: GuyNeural, JennyNeural"},
                "rate": {"type": "string", "description": "Speed. e.g., +10%, -20%. Default: +15%"}
            },
            "required": ["text"]
        }
    },
    {
        "name": "mpop_voice",
        "description": "[Media] Generate voice news briefing about server status. Use for hands-free status updates.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "lang": {"type": "string", "enum": ["ko", "en"], "description": "Language (default ko)"},
                "server": {"type": "string", "description": "Specific server report"},
                "type": {"type": "string", "enum": ["today", "week", "month"], "description": "Period"}
            }
        }
    },
    {
        "name": "mpop_news",
        "description": "[Media] RSS news briefing — tech, Korea, world, dev categories. LLM summary with optional TTS voice. Use for tech news updates.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "category": {"type": "string", "enum": ["tech", "korea", "world", "dev"], "description": "News category (default: tech)"},
                "lang": {"type": "string", "enum": ["en", "ko"], "description": "Briefing language (default: en)"},
                "play": {"type": "boolean", "description": "Play audio (default: true)"}
            }
        }
    },
    {
        "name": "mpop_export",
        "description": "[Media] Generate HTML report file — full server status in shareable format. Use when you need a report file to save or send.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "filename": {"type": "string", "description": "Output filename"}
            }
        }
    },
    {
        "name": "mpop_logai",
        "description": "[Ops] AI-powered log analysis — collects and analyzes logs across servers using LLM. Use for diagnosing issues from logs.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "node": {"type": "string", "description": "Specific server to analyze (optional)"},
                "errors_only": {"type": "boolean", "description": "Focus on errors only"}
            }
        }
    },
    {
        "name": "mpop_service",
        "description": "[Ops] Cluster service management — start/stop/status/logs across all nodes. Use for managing distributed services.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["list", "status", "start", "stop", "restart", "logs"], "description": "Service action"},
                "name": {"type": "string", "description": "Service name"}
            }
        }
    },
    {
        "name": "mpop_retry",
        "description": "[Ops] Retry failed commands — shows recent failures and can re-execute them. Use for recovering from failed operations.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "target": {"type": "string", "description": "'last' for last failed, or command number"}
            }
        }
    },
    {
        "name": "mpop_chain",
        "description": "[AI-Diag] AI chaining — multi-step analysis: analyze, optimize, or troubleshoot. Use for complex diagnostic workflows.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "action": {"type": "string", "enum": ["analyze", "optimize", "troubleshoot"], "description": "Analysis type"},
                "query": {"type": "string", "description": "Additional question or target"}
            }
        }
    },

    # ══════════════════════════════════════════════════════════════
    # [Router] Natural Language Router (1 tool)
    # When unsure which tool to use, try this first
    # ══════════════════════════════════════════════════════════════
    {
        "name": "mpop_ask",
        "description": "[Router] Natural language server management — converts plain English/Korean queries to mpop commands. Use when unsure which specific tool to use. Examples: 'g1 memory usage', 'all disk', 'restart nginx on v1'.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Natural language query. e.g., 'g1 memory usage', 'all servers disk usage'"},
                "execute": {"type": "boolean", "description": "Execute generated command (default: false, dry-run)"}
            },
            "required": ["query"]
        }
    }
]

def _nas_available() -> bool:
    """Quick check if NAS (storage1/s1) is reachable before issuing commands (8s timeout)."""
    import subprocess as _sp
    try:
        r = _sp.run(
            [MPOP_PATH, "exec", "-t", "storage1", "echo ok"],
            capture_output=True, text=True, timeout=8
        )
        return r.returncode == 0 and "ok" in r.stdout
    except Exception:
        return False


def handle_tool_call(name: str, arguments: dict) -> str:
    """Handle tool call"""

    # Dashboard & Status
    if name == "mpop_dashboard":
        return run_mpop("")
    elif name == "mpop_servers":
        return run_mpop("servers")
    elif name == "mpop_info":
        server = arguments.get('server', '')
        raw_flag = "--raw" if arguments.get('raw') else ""
        return run_mpop(f"info {server} {raw_flag}".strip())
    elif name == "mpop_matrix":
        return run_mpop("matrix")
    elif name == "mpop_services":
        return run_mpop("services")

    # Command execution - use dedicated exec function that preserves pipes/quotes
    elif name == "mpop_exec":
        cmd = arguments.get("command", "")
        server = arguments.get("server", "")
        # Always use run_mpop_exec() which preserves shell metacharacters
        # The command runs remotely via vssh, so local sanitization is unnecessary
        return run_mpop_exec(cmd, server=server)

    # Safe deletion (for MCP)
    elif name == "mpop_delete":
        server = arguments.get("server", "")
        path = arguments.get("path", "")
        if not server or not path:
            return "Error: server and path are required"
        return run_mpop(f'delete -t {server} "{path}" --confirm')

    # Security
    elif name == "mpop_security":
        return run_mpop("security")
    elif name == "mpop_audit":
        server = arguments.get("server", "")
        return run_mpop(f"audit {server}".strip(), timeout=180)  # 3 min

    # Config
    elif name == "mpop_config":
        action = arguments.get("action", "show")
        if action == "show":
            return run_mpop("config")
        elif action == "test":
            return run_mpop("config test")
        elif action == "auto":
            return run_mpop("config auto")
        elif action == "get":
            key = arguments.get("key", "")
            return run_mpop(f"config get {key}")
        elif action == "set":
            key = arguments.get("key", "")
            value = arguments.get("value", "")
            return run_mpop(f"config set {key} {value}")
        return run_mpop("config")

    # Setup/Deploy
    elif name == "mpop_setup":
        component = arguments.get("component", "")
        server = arguments.get("server", "")
        execute = "-x" if arguments.get("execute") else ""
        # full setup without component, just server
        if component == "full" or not component:
            return run_mpop(f"setup {server} {execute}".strip())
        return run_mpop(f"setup {component} {server} {execute}".strip())
    elif name == "mpop_deploy":
        target = arguments.get("target", "mpop")
        servers = arguments.get("servers", "")
        if target == "mpop":
            return run_mpop("deploy mpop")
        return run_mpop(f"deploy {target} {servers}".strip())
    elif name == "mpop_watchdog":
        action = arguments.get("action", "status")
        server = arguments.get("server", "")
        if action == "status":
            return run_mpop(f"watchdog status {server}".strip())
        elif action == "install":
            return run_mpop(f"watchdog install {server}")
        elif action == "logs":
            return run_mpop(f"watchdog logs {server}")
        elif action == "remove":
            return run_mpop(f"watchdog remove {server}")
        return run_mpop("watchdog")

    # AI Analysis
    elif name == "mpop_ai":
        hours = arguments.get("hours", 24)
        return run_mpop(f"ai {hours}")
    elif name == "mpop_report":
        model = arguments.get("model", "")
        if model == "gpt4":
            return run_mpop("report --gpt4")
        return run_mpop("report")
    elif name == "mpop_roles":
        server = arguments.get("server", "")
        use_ollama = arguments.get("ollama", True)
        cmd = "roles"
        if server:
            cmd += f" {server}"
        if use_ollama:
            cmd += " --ollama"
        return run_mpop(cmd, timeout=180)
    elif name == "mpop_full":
        return run_mpop("full", timeout=300)  # 5 min (9 tables + AI)

    # Monitoring
    elif name == "mpop_temp":
        return run_mpop("temp")
    elif name == "mpop_gpu":
        return run_mpop("gpu")
    elif name == "mpop_logs":
        server = arguments.get("server", "")
        log_type = arguments.get("type", "")
        return run_mpop(f"logs {server} {log_type}".strip())
    elif name == "mpop_watch":
        interval = arguments.get("interval", 5)
        alert = "--alert" if arguments.get("alert") else ""
        # watch runs once (no streaming)
        return run_mpop(f"watch 1 {alert}".strip(), timeout=30)
    elif name == "mpop_trend":
        hours = arguments.get("hours", 24)
        server = arguments.get("server", "")
        return run_mpop(f"trend {hours} {server}".strip())
    elif name == "mpop_backup":
        action = arguments.get("action", "list")
        server = arguments.get("server", "")
        if action == "list":
            return run_mpop("backup list")
        elif action == "create":
            return run_mpop(f"backup create {server}".strip(), timeout=180)  # 3 min
        elif action == "create-all":
            return run_mpop("backup create-all", timeout=600)  # 10 min
        elif action == "restore" and server:
            return run_mpop(f"backup restore {server}", timeout=300)  # 5 min
        return run_mpop("backup list")
    elif name == "mpop_voice":
        lang = arguments.get("lang", "ko")
        server = arguments.get("server", "")
        vtype = arguments.get("type", "")
        lang_flag = "--en" if lang == "en" else "--ko"
        cmd = f"voice {lang_flag}"
        if server:
            cmd += f" {server}"
        if vtype:
            cmd += f" --{vtype}"
        return run_mpop(cmd.strip(), timeout=120)
    elif name == "mpop_logai":
        node = arguments.get("node", "")
        errors_only = "--errors" if arguments.get("errors_only") else ""
        return run_mpop(f"logai {node} {errors_only}".strip(), timeout=120)
    elif name == "mpop_service":
        action = arguments.get("action", "list")
        svc_name = arguments.get("name", "")
        return run_mpop(f"service {action} {svc_name}".strip(), timeout=60)
    elif name == "mpop_retry":
        target = arguments.get("target", "")
        return run_mpop(f"retry {target}".strip(), timeout=60)
    elif name == "mpop_ssh_attacks":
        return run_mpop("security --attacks", timeout=60)
    elif name == "mpop_export":
        filename = arguments.get("filename", "")
        return run_mpop(f"export {filename}".strip(), timeout=180)  # 3 min

    # Network
    elif name == "mpop_vpn":
        return run_mpop("vpn")
    elif name == "mpop_peers":
        return run_mpop("peers")

    # Secrets
    elif name == "mpop_secret":
        action = arguments.get("action", "list")
        key = arguments.get("key", "")
        if action == "list":
            return run_mpop("secret list")
        elif action == "get":
            if key and key in SECRET_AI_WHITELIST:
                return run_mpop(f"secret get {key} --raw --force", bypass_block=True)
            return f"Error: Secret '{key}' is not in AI whitelist. Whitelisted: {', '.join(SECRET_AI_WHITELIST)}"
        elif action == "set":
            return "Error: Cannot set secrets via AI. Use CLI: mpop secret set <key>"
        elif action == "delete":
            if not key:
                return "Error: key required for delete"
            return run_mpop(f"secret delete {key} --force", bypass_block=True)
        return run_mpop("secret")

    # Project/Brain
    elif name == "mpop_brain":
        action = arguments.get("action", "status")
        if action == "search":
            keyword = arguments.get("keyword", "")
            return run_mpop(f'brain search "{keyword}"')
        return run_mpop("brain")
    elif name == "mpop_project":
        name_arg = arguments.get("name", "")
        action = arguments.get("action", "list")
        if action == "list" or not name_arg:
            return run_mpop("project")
        return run_mpop(f"project {name_arg}")

    # Utilities (heavy operations - increased timeout)
    elif name == "mpop_heal":
        server = arguments.get("server", "")
        execute = "-x --yes" if arguments.get("execute") else ""
        return run_mpop(f"heal {server} {execute}".strip(), timeout=300)  # 5 min
    elif name == "mpop_predict":
        server = arguments.get("server", "")
        return run_mpop(f"predict {server}".strip(), timeout=180)  # 3 min
    elif name == "mpop_diff":
        s1 = arguments.get("server1", "")
        s2 = arguments.get("server2", "")
        return run_mpop(f"diff {s1} {s2}", timeout=180)  # 3 min
    elif name == "mpop_hw":
        # Local hardware info
        import platform
        info = []
        info.append(f"Platform: {platform.system()} {platform.release()}")
        info.append(f"Machine: {platform.machine()}")
        info.append(f"Processor: {platform.processor()}")
        try:
            # CPU info
            if platform.system() == "Darwin":
                cpu = subprocess.run(["sysctl", "-n", "machdep.cpu.brand_string"], capture_output=True, text=True).stdout.strip()
                cores = subprocess.run(["sysctl", "-n", "hw.ncpu"], capture_output=True, text=True).stdout.strip()
                mem = subprocess.run(["sysctl", "-n", "hw.memsize"], capture_output=True, text=True).stdout.strip()
                mem_gb = int(mem) // (1024**3) if mem else "?"
                info.append(f"CPU: {cpu}")
                info.append(f"Cores: {cores}")
                info.append(f"RAM: {mem_gb} GB")
                # GPU
                gpu = subprocess.run(["system_profiler", "SPDisplaysDataType"], capture_output=True, text=True, timeout=10).stdout
                for line in gpu.split('\n'):
                    if 'Chipset Model' in line or 'VRAM' in line:
                        info.append(line.strip())
            else:
                # Linux
                cpu = subprocess.run(["grep", "-m1", "model name", "/proc/cpuinfo"], capture_output=True, text=True).stdout.strip()
                mem = subprocess.run(["free", "-g"], capture_output=True, text=True).stdout
                info.append(cpu.replace("model name\t: ", "CPU: "))
                for line in mem.split('\n'):
                    if 'Mem:' in line:
                        parts = line.split()
                        info.append(f"RAM: {parts[1]} GB total, {parts[2]} GB used")
                # GPU
                try:
                    gpu = subprocess.run(["nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader"], capture_output=True, text=True, timeout=5).stdout.strip()
                    if gpu:
                        info.append(f"GPU: {gpu}")
                except:
                    pass
            # Disk
            if platform.system() == "Darwin":
                disk = subprocess.run(["df", "-h", "/"], capture_output=True, text=True).stdout
            else:
                disk = subprocess.run(["df", "-h", "/"], capture_output=True, text=True).stdout
            for line in disk.split('\n')[1:2]:
                parts = line.split()
                if len(parts) >= 4:
                    info.append(f"Disk: {parts[1]} total, {parts[2]} used ({parts[4]})")
        except Exception as e:
            info.append(f"Error getting details: {e}")
        return '\n'.join(info)
    elif name == "mpop_advise":
        # Infrastructure advisor - collect data and use local LLM
        category = arguments.get("category", "all")
        target = arguments.get("target", "")
        use_ai = arguments.get("ai", True)

        # Collect server metrics
        cmd = "full --json" if not target else f"info {target} --json"
        metrics = run_mpop(cmd, timeout=60)

        if not use_ai:
            return metrics

        # Use local Ollama for recommendations
        try:
            import urllib.request
            ollama_host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
            prompt = f"""Analyze this server infrastructure data and provide recommendations:

{metrics}

Category focus: {category}
Provide 3-5 actionable recommendations in bullet points.
Be concise and specific."""

            req_data = json.dumps({
                "model": "gemma3:4b",
                "prompt": prompt,
                "stream": False
            }).encode()
            req = urllib.request.Request(f"{ollama_host}/api/generate", data=req_data,
                                         headers={"Content-Type": "application/json"})
            with urllib.request.urlopen(req, timeout=60) as resp:
                result = json.loads(resp.read().decode())
                return result.get("response", "No recommendations generated")
        except Exception as e:
            return f"Metrics collected (AI unavailable: {e}):\n{metrics}"

    # Query helper (returns command templates)
    elif name == "mpop_query_help":
        query_type = arguments.get("type", "")
        templates = {
            "memory": "exec 'free -m | awk \"NR==2{print int(\\$3/\\$2*100)}\"'  # Returns % for each server",
            "disk": "exec 'df -h / | awk \"NR==2{print \\$5}\"'  # Returns disk usage %",
            "cpu": "exec 'cat /proc/loadavg | awk \"{print \\$1}\"'  # Returns 1-min load average",
            "uptime": "exec 'uptime -p'  # Returns uptime in human-readable format",
            "process": "exec 'ps aux --sort=-%mem | head -5'  # Top 5 processes by memory"
        }
        if query_type in templates:
            return f"Use mpop_exec with: {templates[query_type]}\n\nExample: mpop_exec(command=\"{templates[query_type].split('#')[0].strip()}\")"
        return f"Available types: {', '.join(templates.keys())}"
    elif name == "mpop_workflow":
        action = arguments.get("action", "list")
        name_arg = arguments.get("name", "")
        if action == "list":
            return run_mpop("workflow list")
        elif action == "run" and name_arg:
            return run_mpop(f"workflow run {name_arg}", timeout=180)
        elif action == "show" and name_arg:
            return run_mpop(f"workflow show {name_arg}")
        return run_mpop("workflow list")
    elif name == "mpop_chain":
        action = arguments.get("action", "analyze")
        query = arguments.get("query", "")
        return run_mpop(f'chain {action} {query}'.strip(), timeout=180)

    # Agent Management
    elif name == "mpop_agent_list":
        return run_mpop("agent list")
    elif name == "mpop_agent_make":
        agent_name = arguments.get("name", "")
        description = arguments.get("description", "")
        behavior = arguments.get("behavior", "")
        if not agent_name or not behavior:
            return "Error: name and behavior are required"
        # Create agent programmatically
        import time
        agents_dir = os.path.expanduser("~/.mpop/agents")
        os.makedirs(agents_dir, exist_ok=True)

        # Generate simple agent code
        logic = f'''    # {description}
    # Behavior: {behavior}
    import subprocess
    print("Running {agent_name}...")
    # TODO: Implement based on behavior description
    # Example: result = subprocess.run(['mpop', 'run', 'command'], capture_output=True, text=True)
'''
        agent = {
            "name": agent_name,
            "description": description,
            "behavior": behavior,
            "logic": logic,
            "created": time.strftime("%Y-%m-%d %H:%M"),
            "deployed_to": [],
        }
        import json as json_mod
        with open(os.path.join(agents_dir, f"{agent_name}.json"), "w") as f:
            json_mod.dump(agent, f, indent=2)

        # Generate Python code
        code = f'''#!/usr/bin/env python3
"""
{agent_name} - {description}
Generated by mpop agent make
"""
import subprocess
    # import os  # removed: use module-level os

def run_cmd(cmd, timeout=30):
    try:
        result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
        return result.stdout.strip()
    except Exception as e:
        return f"Error: {{e}}"

def main():
{logic}

if __name__ == "__main__":
    main()
'''
        code_path = os.path.join(agents_dir, f"{agent_name}.py")
        with open(code_path, "w") as f:
            f.write(code)
        os.chmod(code_path, 0o755)

        return f"Agent '{agent_name}' created at {code_path}"

    elif name == "mpop_agent_deploy":
        agent_name = arguments.get("name", "")
        servers = arguments.get("servers", [])
        if not agent_name:
            return "Error: agent name required"
        servers_str = " ".join(servers) if servers else ""
        return run_mpop(f"agent deploy {agent_name} {servers_str}", timeout=120)

    elif name == "mpop_agent_run":
        agent_name = arguments.get("name", "")
        server = arguments.get("server", "")
        if not agent_name:
            return "Error: agent name required"
        if server:
            return run_mpop(f"run -t {server} 'python3 /tmp/{agent_name}.py'", timeout=60)
        return run_mpop(f"agent run {agent_name}", timeout=60)

    elif name == "mpop_agent_workflow":
        action = arguments.get("action", "list")
        wf_name = arguments.get("name", "")
        agents = arguments.get("agents", [])
        if action == "list":
            return run_mpop("agent workflow", timeout=30)
        elif action == "create" and wf_name and agents:
            agents_str = " ".join(agents)
            return run_mpop(f"agent workflow create {wf_name} {agents_str}", timeout=30)
        elif action == "run" and wf_name:
            return run_mpop(f"agent workflow run {wf_name}", timeout=120)
        elif action == "delete" and wf_name:
            return run_mpop(f"agent workflow delete {wf_name}", timeout=30)
        return "Error: invalid action or missing parameters"

    elif name == "mpop_agent_chain":
        action = arguments.get("action", "list")
        chain_name = arguments.get("name", "")
        agents = arguments.get("agents", [])
        if action == "list":
            return run_mpop("agent chain", timeout=30)
        elif action == "save" and chain_name and agents:
            agents_str = " ".join(agents)
            return run_mpop(f"agent chain save {chain_name} {agents_str}", timeout=30)
        elif action == "run":
            if chain_name:
                return run_mpop(f"agent chain run {chain_name}", timeout=120)
            elif agents:
                agents_str = " ".join(agents)
                return run_mpop(f"agent chain {agents_str}", timeout=120)
        elif action == "delete" and chain_name:
            return run_mpop(f"agent chain delete {chain_name}", timeout=30)
        return "Error: invalid action or missing parameters"

    # Project Memory (reduce AI hallucination)
    elif name == "mpop_memory":
        action = arguments.get("action", "list")
        path = arguments.get("path", "")
        project = arguments.get("project", "")
        question = arguments.get("question", "")

        if action == "scan" and path:
            return run_mpop(f'memory scan "{path}"', timeout=120)
        elif action == "ask" and project and question:
            return run_mpop(f'memory ask {project} "{question}"', timeout=180)
        elif action == "status" and project:
            return run_mpop(f"memory status {project}")
        elif action == "delete" and project:
            return run_mpop(f"memory delete {project}")
        elif action == "list":
            return run_mpop("memory list")
        return run_mpop("memory")

    # RAG - TODO: May implement with meshdb (distributed) instead of ChromaDB
    elif name == "mpop_rag":
        return "[TODO] mpop_rag not implemented. Use mpop_memory for project Q&A."

    # Notify (App Integration)
    elif name == "mpop_notify":
        platform = arguments.get("platform", "slack")
        message = arguments.get("message", "")
        return run_mpop(f'notify {platform} "{message}"')

    # Webhook
    elif name == "mpop_webhook":
        action = arguments.get("action", "list")
        wh_name = arguments.get("name", "")
        command = arguments.get("command", "")
        if action == "list":
            return run_mpop("webhook list")
        elif action == "create" and wh_name and command:
            return run_mpop(f'webhook create {wh_name} "{command}"')
        elif action == "delete" and wh_name:
            return run_mpop(f"webhook delete {wh_name}")
        elif action == "test" and wh_name:
            return run_mpop(f"webhook test {wh_name}")
        return run_mpop("webhook list")

    # Raw (extra security validation)
    elif name == "mpop_raw":
        cmd = arguments.get("command", "help")
        # Block critical commands
        if is_always_blocked(cmd):
            return "Error: This command is always blocked for safety."
        # Double check: run_mpop also checks, but be explicit here
        if is_blocked_command(cmd):
            return "Error: This command is blocked for security reasons via MCP."
        # mpop_raw uses shell_mode=True (allows pipes/redirects)
        return run_mpop(cmd, shell_mode=True)

    # TTS voice playback (local)
    elif name == "mpop_tts":
        text = arguments.get("text", "")
        voice = arguments.get("voice", "ko-KR-InJoonNeural")
        rate = arguments.get("rate", "+15%")
        # Short name mapping
        voice_map = {
            "InJoonNeural": "ko-KR-InJoonNeural",
            "SunHiNeural": "ko-KR-SunHiNeural",
            "HyunsuNeural": "ko-KR-HyunsuNeural",
            "GuyNeural": "en-US-GuyNeural",
            "JennyNeural": "en-US-JennyNeural",
        }
        voice = voice_map.get(voice, voice)
        try:
            import asyncio
            import edge_tts
            import time as t
            path = f"/tmp/mpop_tts_{int(t.time())}.mp3"
            async def gen():
                c = edge_tts.Communicate(text, voice, rate=rate)
                await c.save(path)
            asyncio.run(gen())
            subprocess.run(["afplay", path])
            return f"played: {voice}"
        except ImportError:
            return "Error: edge-tts not installed. pip install edge-tts"
        except Exception as e:
            return f"Error: {e}"

    # File write (local)
    elif name == "mpop_write_file":
    # import os  # removed: use module-level os
        path = arguments.get("path", "")
        content = arguments.get("content", "")
        append = arguments.get("append", False)
        executable = arguments.get("executable", False)

        # Expand path (~)
        path = os.path.expanduser(path)

        # Safety check
        dangerous = ["/etc/", "/usr/", "/bin/", "/sbin/", "/var/"]
        if any(path.startswith(d) for d in dangerous):
            return f"Error: Cannot write to system path: {path}"

        try:
            mode = "a" if append else "w"
            with open(path, mode) as f:
                f.write(content)
            if executable:
                os.chmod(path, 0o755)
            size = len(content)
            return f"Written: {path} ({size} bytes)" + (" [executable]" if executable else "")
        except Exception as e:
            return f"Error: {e}"

    # File read (local)
    elif name == "mpop_read_file":
    # import os  # removed: use module-level os
        path = arguments.get("path", "")
        server = arguments.get("server", "")
        lines = arguments.get("lines", 0)

        # Remote file reading
        if server and server not in ("", "local", "local"):
            cmd = f'cat "{path}"'
            if lines > 0:
                cmd = f'head -n {lines} "{path}"'
            result = run_mpop_exec(cmd, server=server)
            if result and not result.startswith("Error:"):
                return result
            return f"Error reading remote file: {result}"

        # Local file reading
        path = os.path.expanduser(path)
        try:
            with open(path) as f:
                if lines > 0:
                    content = "".join(f.readlines()[:lines])
                else:
                    content = f.read()
            return content
        except Exception as e:
            return f"Error: {e}"

    # Chunk write (for long data)
    elif name == "mpop_chunk_write":
    # import os  # removed: use module-level os
        path = os.path.expanduser(arguments.get("path", ""))
        chunk = arguments.get("chunk", "")
        mode = arguments.get("mode", "append")

        try:
            if mode == "start":
                with open(path, "w") as f:
                    f.write(chunk)
                return f"Chunk start: {path} ({len(chunk)} bytes)"
            elif mode in ["append", "end"]:
                with open(path, "a") as f:
                    f.write(chunk)
                if mode == "end":
                    size = os.path.getsize(path)
                    return f"Chunk complete: {path} (total {size} bytes)"
                return f"Chunk appended: {len(chunk)} bytes"
        except Exception as e:
            return f"Error: {e}"

    # SCP transfer (remote)
    elif name == "mpop_scp":
        local_path = arguments.get("local_path", "")
        server = arguments.get("server", "")
        remote_path = arguments.get("remote_path", local_path)
        direction = arguments.get("direction", "push")

        if not server:
            return "Error: server required"

        # Resolve server name → wire_ip + user from ~/.mpop/config.json
        import json as _json
        _cfg_path = os.path.expanduser("~/.mpop/config.json")
        _scp_user = "root"
        _scp_ip = server
        _port_opt = ""
        try:
            if os.path.exists(_cfg_path):
                _cfg = _json.load(open(_cfg_path))
                _srv = _cfg.get("servers", {}).get(server, {})
                _scp_ip = _srv.get("wire_ip") or _srv.get("ip") or server
                _scp_user = _srv.get("user", "root")
                _port = _srv.get("ssh_port") or _srv.get("port")
                if _port and int(_port) != 22:
                    _port_opt = f"-P {_port} "
        except Exception:
            pass

        _target = f"{_scp_user}@{_scp_ip}"
        _opts = f"{_port_opt}-o StrictHostKeyChecking=no -o ConnectTimeout=10"

        # Build scp command based on direction
        if direction == "pull":
            # remote → local
            cmd = f'scp {_opts} {_target}:"{remote_path}" "{local_path}"'
            msg = f"Pulled: {server}:{remote_path} → {local_path}"
        else:
            # local → remote (push, default)
            cmd = f'scp {_opts} "{local_path}" {_target}:"{remote_path}"'
            msg = f"Pushed: {local_path} → {server}:{remote_path}"

        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=60)
            if result.returncode == 0:
                return msg
            return f"Error: {result.stderr}"
        except Exception as e:
            return f"Error: {e}"

    # ══════════════════════════════════════════════════════════════
    # NAS Tools Implementation
    # ══════════════════════════════════════════════════════════════

    elif name == "mpop_nas_ls":
        # Quick connectivity check before potentially blocking for 60s
        if not _nas_available():
            return "NAS (storage1) is not reachable. Check that s1 is configured and online: mpop servers"
        path = arguments.get("path", "/volume1")
        recursive = arguments.get("recursive", False)
        pattern = arguments.get("pattern", "")

        if recursive:
            cmd = f"find {path} -maxdepth 3 -type f"
            if pattern:
                cmd += f" -name '{pattern}'"
            cmd += " 2>/dev/null | head -100"
        else:
            cmd = f"ls -la {path} 2>/dev/null"
            if pattern:
                cmd += f" | grep '{pattern}'"

        result = run_mpop(f"exec -t storage1 \"{cmd}\"")
        return result

    elif name == "mpop_nas_read":
        # Quick connectivity check before potentially blocking for 60s
        if not _nas_available():
            return "NAS (storage1) is not reachable. Check that s1 is configured and online: mpop servers"
        path = arguments.get("path", "")
        lines = arguments.get("lines", 0)
        tail = arguments.get("tail", False)

        if not path:
            return "Error: path required"

        if tail and lines:
            cmd = f"tail -n {lines} '{path}'"
        elif lines:
            cmd = f"head -n {lines} '{path}'"
        else:
            cmd = f"cat '{path}'"

        result = run_mpop(f"exec -t storage1 \"{cmd}\"")
        return result

    elif name == "mpop_nas_write":
        # Quick connectivity check before potentially blocking for 60s
        if not _nas_available():
            return "NAS (storage1) is not reachable. Check that s1 is configured and online: mpop servers"
        path = arguments.get("path", "")
        content = arguments.get("content", "")
        append = arguments.get("append", False)

        if not path:
            return "Error: path required"

        # Escape content for shell
        import base64
        encoded = base64.b64encode(content.encode()).decode()

        if append:
            cmd = f"echo '{encoded}' | base64 -d >> '{path}'"
        else:
            cmd = f"echo '{encoded}' | base64 -d > '{path}'"

        result = run_mpop(f"exec -t storage1 \"{cmd}\"")
        return f"Written to {path}" if "Failed" not in str(result) else result

    elif name == "mpop_nas_backup":
        # Quick connectivity check before potentially blocking for 60s
        if not _nas_available():
            return "NAS (storage1) is not reachable. Check that s1 is configured and online: mpop servers"
        server = arguments.get("server", "")
        source = arguments.get("source", "")
        dest = arguments.get("dest", "")

        if not server or not source:
            return "Error: server and source required"

        if not dest:
            dest = f"/volume1/backup/{server}"

        # Create dest dir
        run_mpop(f"exec -t storage1 \"mkdir -p {dest}\"")

        # Get server IP
        date_str = datetime.now().strftime("%Y%m%d")
        backup_path = f"{dest}/{date_str}"

        # Use rsync via ssh
        cmd = f"""rsync -avz --progress -e 'ssh -o StrictHostKeyChecking=no' \\
            {server}:{source}/ {nas_user}@{nas_host}:{backup_path}/ 2>&1"""

        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=600)
            if result.returncode == 0:
                return f"Backup complete: {server}:{source} → s1:{backup_path}"
            return f"Backup failed: {result.stderr}"
        except Exception as e:
            return f"Error: {e}"

    elif name == "mpop_nas_usage":
        # Quick connectivity check before potentially blocking for 60s
        if not _nas_available():
            return "NAS (storage1) is not reachable. Check that s1 is configured and online: mpop servers"
        path = arguments.get("path", "")

        if path:
            cmd = f"du -sh {path} && df -h {path}"
        else:
            cmd = "df -h | grep volume && echo '---' && du -sh /volume1/* 2>/dev/null | sort -hr | head -20"

        result = run_mpop(f"exec -t s1 \"{cmd}\"")
        return result

    # News briefing
    elif name == "mpop_news":
        category = arguments.get("category", "tech")
        lang = arguments.get("lang", "en")
        play = arguments.get("play", True)

        cmd = f"news {category}"
        if lang == "ko":
            cmd += " --ko"
        if not play:
            cmd += " --no-play"
        return run_mpop(cmd, timeout=120)

    # Python code execution (save to file then execute)
    elif name == "mpop_python":
        import time as t
    # import os  # removed: use module-level os

        code = arguments.get("code", "")
        server = arguments.get("server", "")
        python_cmd = arguments.get("python", "python3")

        if not code:
            return "Error: code is required"

        # Temp file path
        ts = int(t.time() * 1000)
        local_path = f"/tmp/mcp_python_{ts}.py"

        try:
            # Save code to local
            with open(local_path, "w") as f:
                f.write(code)

            if server:
                # Remote server: transfer via SCP then execute
                remote_path = f"/tmp/mcp_python_{ts}.py"

                # SCP transfer
                scp_cmd = f'scp "{local_path}" {server}:"{remote_path}"'
                scp_result = subprocess.run(scp_cmd, shell=True, capture_output=True, text=True, timeout=30)
                if scp_result.returncode != 0:
                    return f"SCP Error: {scp_result.stderr}"

                # Remote execution
                exec_result = run_mpop(f'exec -t {server} "{python_cmd} {remote_path}"', timeout=120)

                # Delete remote file
                run_mpop(f'exec -t {server} "rm -f {remote_path}"', timeout=10)

                return exec_result
            else:
                # Local execution
                result = subprocess.run(
                    [python_cmd, local_path],
                    capture_output=True, text=True, timeout=120
                )
                output = result.stdout
                if result.stderr:
                    output += f"\n[stderr] {result.stderr}"
                return output if output else "(no output)"

        except Exception as e:
            return f"Error: {e}"
        finally:
            # Delete local file
            if os.path.exists(local_path):
                os.remove(local_path)

    elif name == "mpop_ask":
        query = arguments.get("query", "").lower().strip()
        execute = arguments.get("execute", False)
        if not query:
            return "Error: query is required"
        # Natural language → mpop command routing
        # Extract server name if present (node1, node2, relay1, worker1, etc.)
        import re as _re
        server_match = _re.search(r'\b(g[1-4]|d[1-2]|v[1-2]|m1)\b', query)
        server = server_match.group(1) if server_match else ""
        server_flag = f" -t {server}" if server else ""
        # Keyword routing
        if any(w in query for w in ["memory", "ram", "mem"]):
            cmd = f"exec{server_flag} \"free -h\""
        elif any(w in query for w in ["disk", "storage"]):
            cmd = f"exec{server_flag} \"df -h /\""
        elif any(w in query for w in ["cpu", "load"]):
            cmd = f"exec{server_flag} \"uptime\""
        elif any(w in query for w in ["uptime"]):
            cmd = f"exec{server_flag} \"uptime\""
        elif any(w in query for w in ["process", "ps"]):
            cmd = f"exec{server_flag} \"ps aux --sort=-%mem | head -15\""
        elif any(w in query for w in ["temp", "temperature"]):
            cmd = "temp"
        elif any(w in query for w in ["gpu", "cuda", "vram"]):
            cmd = "gpu"
        elif any(w in query for w in ["security", "attack"]):
            cmd = "security"
        elif any(w in query for w in ["vpn", "network"]):
            cmd = "vpn"
        elif any(w in query for w in ["service", "nginx", "docker"]):
            cmd = "services"
        elif any(w in query for w in ["log"]):
            if server:
                cmd = f"logs {server}"
            else:
                return "Which server logs? Specify server name (g1, g2, v1, etc.)"
        elif any(w in query for w in ["restart"]):
            svc_match = _re.search(r'restart\s+(\w+)', query) or _re.search(r'(nginx|docker|ollama|vssh)', query)
            svc = svc_match.group(1) if svc_match else ""
            if svc and execute:
                cmd = f"exec{server_flag} \"systemctl restart {svc}\""
            elif svc:
                return f"Would run: mpop exec{server_flag} \"systemctl restart {svc}\"\nSet execute=true to run."
            else:
                return "Which service to restart? (nginx, docker, ollama, vssh, etc.)"
        else:
            # Default: try exec with the query as-is on specified server
            if server:
                cmd = f"exec -t {server} \"{query}\""
            else:
                # Fallback to dashboard
                return f"I understood: \"{query}\"\n\nSuggested tools:\n- mpop_dashboard: server overview\n- mpop_exec: run commands\n- mpop_info: server details\n\nTry being more specific or use a dedicated tool."
        if not execute and "exec" in cmd and "restart" not in query:
            return run_mpop(cmd)
        elif execute:
            return run_mpop(cmd)
        else:
            return run_mpop(cmd)

    else:
        return f"Unknown tool: {name}"

def main():
    """MCP server main loop"""
    while True:
        msg = read_message()
        if msg is None:
            break

        method = msg.get("method", "")
        msg_id = msg.get("id")

        if method == "initialize":
            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {}},
                    "serverInfo": {"name": "mpop", "version": "4.1.0"}
                }
            })

        elif method == "notifications/initialized":
            pass

        elif method == "tools/list":
            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {"tools": TOOLS}
            })

        elif method == "tools/call":
            params = msg.get("params", {})
            tool_name = params.get("name", "")
            arguments = params.get("arguments", {})

            result = handle_tool_call(tool_name, arguments)

            send_message({
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {
                    "content": [{"type": "text", "text": result}]
                }
            })

        elif method == "shutdown":
            send_message({"jsonrpc": "2.0", "id": msg_id, "result": None})
            break

if __name__ == "__main__":
    main()
