#!/usr/bin/env python3
"""
meshpop.mcp — Unified MeshPOP MCP Server

Single MCP server that aggregates tools from all installed MeshPOP packages.
Auto-discovers packages via the "meshpop.mcp" entry point group.

Usage (claude_desktop_config.json):
    {
      "mcpServers": {
        "meshpop": {
          "command": "python3",
          "args": ["-m", "meshpop_mcp"]
        }
      }
    }

Replaces all individual MCPs:
    mpop, meshdb, vssh, wire, rtlinux, meshclaw, radiomcp
"""

import json
import sys
import importlib
import importlib.metadata


def _discover_servers() -> dict:
    """Discover all installed meshpop MCP servers via entry_points.

    Returns:
        dict mapping package_name → (TOOLS list, handle_tool function)
    """
    servers = {}

    try:
        eps = importlib.metadata.entry_points(group="meshpop.mcp")
    except Exception:
        return servers

    for ep in eps:
        try:
            module = ep.load()
            # Each MCP module must expose TOOLS and handle_tool
            if hasattr(module, "TOOLS") and hasattr(module, "handle_tool"):
                servers[ep.name] = {
                    "tools": module.TOOLS,
                    "handle": module.handle_tool,
                }
            # Some modules expose the server as a class/object — handle both styles
            elif hasattr(module, "TOOLS"):
                servers[ep.name] = {
                    "tools": module.TOOLS,
                    "handle": None,
                }
        except Exception as e:
            # Package installed but broken — skip, don't crash the whole server
            print(f"[meshpop.mcp] Warning: could not load {ep.name}: {e}",
                  file=sys.stderr)

    return servers


def _build_tool_registry(servers: dict) -> tuple:
    """Build aggregated TOOLS list and a routing table.

    Returns:
        (all_tools, routing) where routing maps tool_name → handle_tool fn
    """
    all_tools = []
    routing = {}

    for pkg_name, server in servers.items():
        for tool in server["tools"]:
            name = tool.get("name", "")
            if name and name not in routing:
                all_tools.append(tool)
                if server["handle"]:
                    routing[name] = server["handle"]

    return all_tools, routing


def _get_version() -> str:
    try:
        return importlib.metadata.version("meshpop")
    except Exception:
        return "?"


def run():
    """Run the unified MCP stdio server."""
    servers  = _discover_servers()
    all_tools, routing = _build_tool_registry(servers)

    pkg_names = list(servers.keys())
    tool_count = len(all_tools)

    print(
        f"[meshpop.mcp] Loaded {tool_count} tools from: {pkg_names}",
        file=sys.stderr,
    )

    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue

        try:
            msg = json.loads(line)
        except json.JSONDecodeError:
            continue

        method  = msg.get("method", "")
        msg_id  = msg.get("id")
        params  = msg.get("params", {})

        if method == "initialize":
            resp = {
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {"listChanged": False}},
                    "serverInfo": {
                        "name": "meshpop",
                        "version": _get_version(),
                    },
                },
            }

        elif method == "notifications/initialized":
            continue

        elif method == "tools/list":
            resp = {
                "jsonrpc": "2.0",
                "id": msg_id,
                "result": {"tools": all_tools},
            }

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

            try:
                if handler:
                    result_text = handler(tool_name, arguments)
                else:
                    result_text = json.dumps({
                        "error": f"Tool '{tool_name}' found but handler not available"
                    })
                resp = {
                    "jsonrpc": "2.0",
                    "id": msg_id,
                    "result": {
                        "content": [{"type": "text", "text": result_text}]
                    },
                }
            except Exception as e:
                resp = {
                    "jsonrpc": "2.0",
                    "id": msg_id,
                    "result": {
                        "content": [{"type": "text", "text": f"Error: {e}"}],
                        "isError": True,
                    },
                }

        else:
            resp = {
                "jsonrpc": "2.0",
                "id": msg_id,
                "error": {"code": -32601, "message": f"Unknown method: {method}"},
            }

        sys.stdout.write(json.dumps(resp) + "\n")
        sys.stdout.flush()


if __name__ == "__main__":
    run()
