"""mcp_server.py, M16: expose your tools over MCP so ANY app can use them.

In M9 your agent's tools only worked inside your own script. MCP (Model Context Protocol)
is a standard "plug", wrap your tools in an MCP **server** and any MCP-aware app (Claude
Desktop, an IDE, an agent) can discover and call them, with no custom glue per app.

This server exposes three tools (a safe calculator + two synthetic security tools, same idea
as M9). Run it directly and it waits for a client over stdio.

Setup:  pip install mcp
Run (usually started by a client, but you can run it directly):
    python mcp_server.py
"""

import ast
import operator
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("greenleaf-tools")          # the server's name

# ---- a SAFE calculator (never eval untrusted input) -------------------------
_OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul,
        ast.Div: operator.truediv, ast.Pow: operator.pow, ast.USub: operator.neg}

def _eval(node):
    if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
        return node.value
    if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
        return _OPS[type(node.op)](_eval(node.left), _eval(node.right))
    if isinstance(node, ast.UnaryOp) and type(node.op) in _OPS:
        return _OPS[type(node.op)](_eval(node.operand))
    raise ValueError("unsupported expression")


# The @mcp.tool() decorator turns a plain function into an MCP tool. The name,
# description (from the docstring), and input schema (from the type hints) are
# published to clients automatically, same idea as M9's tool schemas.
@mcp.tool()
def calculate(expression: str) -> str:
    """Evaluate a basic arithmetic expression like '3 * (4 + 5)'. Safe: numbers and + - * / ** only."""
    try:
        return str(_eval(ast.parse(expression, mode="eval").body))
    except Exception:
        return f"Could not calculate '{expression}'."


@mcp.tool()
def lookup_ioc(indicator: str) -> str:
    """Look up the reputation of an IP, domain, or file hash in the (synthetic) threat feed."""
    fake = {
        "185.220.101.45": "MALICIOUS, known Tor exit node (synthetic).",
        "8.8.8.8": "CLEAN, public DNS resolver (synthetic).",
    }
    return fake.get(indicator.strip(), f"UNKNOWN, no record for '{indicator}' (synthetic).")


@mcp.tool()
def search_logs(term: str) -> str:
    """Search the (synthetic) sample logs for lines containing a term (e.g. an IP or user)."""
    logs = [
        "09:14 login FAIL user=admin ip=185.220.101.45",
        "09:15 login OK   user=admin ip=185.220.101.45",
        "09:20 download   user=admin file=customer_db.csv ip=185.220.101.45",
    ]
    hits = [l for l in logs if term.lower() in l.lower()]
    return "\n".join(hits) if hits else f"No log lines match '{term}'."


if __name__ == "__main__":
    mcp.run()                              # serve over stdio (the default transport)
