"""
cortex/api/mcp_server.py — MCP Server for Cortex (Module 13 / M13).

Exposes Cortex memory operations as Model Context Protocol (MCP) tools and
resources. This is the primary integration point for AI agents — any
MCP-compliant client (Claude Desktop, OpenClaw, etc.) can use Cortex memory
through this server.

Transport: stdio (standard MCP pattern). The server reads JSON-RPC from stdin
and writes responses to stdout.

Tools exposed (15 tools):
    cortex_remember          — Extract + store memories from conversation text
    cortex_retrieve          — Search memories for a query (hybrid retrieval)
    cortex_forget            — Soft-delete a memory by ID (cornerstone-protected)
    cortex_ask               — Ask a question, get answer synthesised from memory (M14)
    cortex_cornerstones_list — List all sealed cornerstone memories
    cortex_cornerstones_seal — Seal a new cornerstone (identity protection)
    cortex_cornerstones_unseal — Retire a cornerstone
    cortex_health            — System status report (module health, DB stats)
    cortex_feedback          — Submit helpful/harmful feedback for a memory
    brain_graph_query        — Query the brain graph for matching nodes/edges (#66)
    brain_graph_build        — Build/rebuild the brain graph from source (#66)
    session_wake             — Start a new session (load cornerstones) (#66)
    session_sleep            — End the current session (consolidate) (#66)
    dream                    — Trigger a dream cycle consolidation (#66)
    stats                    — Get system statistics (claims, cornerstones, etc.) (#66)

Resources exposed (2 resources):
    cortex://memory/stats      — Memory storage statistics (counts, sizes)
    cortex://cornerstones/list — Current cornerstone list with integrity status

Architecture:
    MCP Server → CortexPlugin (M13) → all other modules
    The MCP server is a thin wrapper — all business logic lives in CortexPlugin.

Usage:
    python -m cortex.api.mcp_server     # stdio transport
    cortex-mcp                           # if installed as console script

Dependencies: fastmcp SDK, cortex.api.plugin.CortexPlugin.
"""
from __future__ import annotations

import asyncio
import json
import logging
from typing import Any, Optional

logger = logging.getLogger(__name__)


def _build_mcp_server() -> Any:
    """Build and return a configured FastMCP server instance."""
    try:
        from mcp.server.fastmcp import FastMCP
    except ImportError as exc:
        raise ImportError(
            "mcp package is required for the MCP server. "
            "Install with: pip install 'evaos[mcp]' or pip install mcp"
        ) from exc

    mcp = FastMCP(
        name="cortex",
        instructions=(
            "Cortex — Identity-preserving cognitive memory for AI agents. "
            "Use cortex_remember to store memories, cortex_retrieve to recall context, "
            "cortex_forget to delete a memory, and cortex_health for system status."
        ),
    )

    # -------------------------------------------------------------------------
    # Lazy plugin initialisation (singleton per process)
    # -------------------------------------------------------------------------
    _plugin_ref: list[Any] = [None]  # mutable container so closures can mutate

    def _get_plugin() -> Any:
        """Return a shared CortexPlugin instance, initialising on first call."""
        if _plugin_ref[0] is None:
            from cortex.api.plugin import CortexPlugin
            _plugin_ref[0] = CortexPlugin()
        return _plugin_ref[0]

    # =========================================================================
    # TOOLS
    # =========================================================================

    @mcp.tool(
        name="cortex_remember",
        description=(
            "Extract memory claims from a conversation and store them in Cortex. "
            "Pass raw conversation text or a JSON-encoded list of message dicts "
            "({'role': 'user'|'assistant', 'content': '...'})."
        ),
    )
    async def cortex_remember(
        conversation: str,
        session_id: Optional[str] = None,
        scope: str = "user",
    ) -> str:
        """Store memories extracted from *conversation*.

        Args:
            conversation: Raw text or JSON list of {role, content} message dicts.
            session_id: Optional session identifier to group related memories.
            scope: Memory scope / owner tag (default: 'user').

        Returns:
            JSON-encoded RememberResult summary.
        """
        plugin = _get_plugin()

        # Accept both plain text and JSON-encoded message list
        parsed: Any = conversation
        try:
            candidate = json.loads(conversation)
            if isinstance(candidate, (list, dict)):
                parsed = candidate
        except (json.JSONDecodeError, TypeError):
            pass  # treat as raw text

        result = await plugin.remember(
            conversation=parsed,
            session_id=session_id,
            scope=scope,
        )
        return json.dumps(
            {
                "session_id": result.session_id,
                "claims_extracted": result.claims_extracted,
                "claims_stored": result.claims_stored,
                "entities_resolved": result.entities_resolved,
                "noise_filtered": result.noise_filtered,
                "ok": result.ok,
                "errors": result.errors,
                "warnings": getattr(result, "warnings", []),
                "zero_claim_warning": getattr(result, "zero_claim_warning", False),
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_retrieve",
        description=(
            "Retrieve relevant memories for a query within an optional token budget. "
            "Returns a context block ready to inject into a prompt."
        ),
    )
    async def cortex_retrieve(
        query: str,
        token_budget: Optional[int] = None,
        mode: str = "auto",
    ) -> str:
        """Retrieve memory context relevant to *query*.

        Args:
            query: Natural language search query.
            token_budget: Max tokens to return (uses config default if omitted).
            mode: Retrieval mode — 'auto' | 'semantic' | 'bm25' | 'hybrid'.

        Returns:
            Retrieved context as a formatted string, or an error message.
        """
        plugin = _get_plugin()
        result = await plugin.retrieve(
            query=query,
            token_budget=token_budget,
            mode=mode,
        )
        if result is None:
            return json.dumps({"ok": False, "context": "", "error": "RetrievalPipeline unavailable"})

        # RetrievalResult may have different shapes depending on M8 implementation.
        # We try common attribute names and fall back to str() representation.
        context_text = ""
        for attr in ("context_block", "context", "text", "formatted"):
            val = getattr(result, attr, None)
            if val is not None:
                context_text = str(val)
                break
        if not context_text:
            context_text = str(result)

        tokens_used = getattr(result, "tokens_used", None)
        memories_count = len(getattr(result, "memories", getattr(result, "claims", [])))

        return json.dumps(
            {
                "ok": True,
                "query": query,
                "context": context_text,
                "tokens_used": tokens_used,
                "memories_count": memories_count,
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_forget",
        description=(
            "Delete a memory by its ID. Sealed cornerstones are protected — "
            "call cortex_cornerstones_unseal first if needed."
        ),
    )
    async def cortex_forget(
        memory_id: str,
        hard_delete: bool = False,
    ) -> str:
        """Delete memory *memory_id*.

        Args:
            memory_id: UUID of the memory to delete.
            hard_delete: If True, permanently purge (no soft-delete). Default False.

        Returns:
            JSON-encoded ForgetResult.
        """
        plugin = _get_plugin()
        result = await plugin.forget(memory_id=memory_id, hard_delete=hard_delete)
        return json.dumps(
            {
                "memory_id": result.memory_id,
                "deleted": result.deleted,
                "reason": result.reason,
                "errors": result.errors,
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_ask",
        description=(
            "Ask a question and get a synthesised answer grounded in stored memories. "
            "Uses the Dialectic Engine (M14) for single-shot retrieval + LLM synthesis. "
            "Pass history as a JSON-encoded list of {role, content} dicts for follow-up Qs."
        ),
    )
    async def cortex_ask(
        query: str,
        owner_id: Optional[str] = None,
        history: Optional[str] = None,
        mode: str = "fast",
        max_steps: int = 5,
    ) -> str:
        """Ask a question answered from memory.

        Args:
            query:     Natural-language question.
            owner_id:  Memory owner namespace (optional).
            history:   JSON-encoded list of {role, content} dicts for conversation history.
            mode:      'fast' (default) or 'agentic' for multi-step retrieval.
            max_steps: Maximum retrieval steps for agentic mode (default 5).

        Returns:
            JSON-encoded AskResult with answer, sources, confidence, reasoning.
        """
        plugin = _get_plugin()

        # Parse optional history JSON
        parsed_history: Optional[list] = None
        if history:
            try:
                parsed_history = json.loads(history)
                if not isinstance(parsed_history, list):
                    parsed_history = None
            except (json.JSONDecodeError, TypeError):
                parsed_history = None

        result = await plugin.ask(
            query=query,
            owner_id=owner_id,
            history=parsed_history,
            mode=mode,
            max_steps=max(1, max_steps),
        )

        return json.dumps(
            {
                "answer": result.answer,
                "sources": result.sources,
                "confidence": result.confidence,
                "reasoning": result.reasoning,
                "ok": result.ok,
                "errors": result.errors,
            },
            indent=2,
            default=str,
        )

    @mcp.tool(
        name="cortex_cornerstones_list",
        description="List all sealed cornerstone memories (the immutable identity anchors).",
    )
    async def cortex_cornerstones_list() -> str:
        """Return all sealed cornerstones.

        Returns:
            JSON array of cornerstone objects.
        """
        plugin = _get_plugin()
        cornerstones = await plugin.get_cornerstones()

        def _cs_to_dict(cs: Any) -> dict:
            """Convert cornerstone object to a serialisable dict."""
            if isinstance(cs, dict):
                return cs
            # Try common attributes from CornerstoneMemory dataclass
            return {
                "id": getattr(cs, "id", getattr(cs, "cs_id", str(cs))),
                "label": getattr(cs, "label", ""),
                "content": getattr(cs, "content", ""),
                "sealed_at": str(getattr(cs, "sealed_at", "")),
                "hash": getattr(cs, "content_hash", getattr(cs, "hash", "")),
            }

        return json.dumps(
            {
                "ok": True,
                "count": len(cornerstones),
                "cornerstones": [_cs_to_dict(cs) for cs in cornerstones],
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_cornerstones_seal",
        description=(
            "Seal a memory as a cornerstone — an immutable identity anchor that "
            "cannot be modified or deleted without explicit unsealing."
        ),
    )
    async def cortex_cornerstones_seal(
        memory_id: str,
        label: str = "",
        content: str = "",
    ) -> str:
        """Seal *memory_id* as a cornerstone.

        Args:
            memory_id: ID of the memory (or new unique label) to seal.
            label: Human-readable label for the cornerstone.
            content: Content to store (used when creating a new cornerstone).

        Returns:
            JSON-encoded sealed CornerstoneMemory or error.
        """
        plugin = _get_plugin()
        result = await plugin.seal_cornerstone(
            memory_id=memory_id,
            label=label or memory_id,
            content=content,
        )
        if result is None:
            return json.dumps(
                {"ok": False, "error": "CornerstoneGuardian unavailable or seal failed"}
            )
        return json.dumps(
            {
                "ok": True,
                "id": getattr(result, "id", getattr(result, "cs_id", memory_id)),
                "label": getattr(result, "label", label),
                "sealed_at": str(getattr(result, "sealed_at", "")),
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_cornerstones_unseal",
        description=(
            "Unseal a cornerstone, making it eligible for modification or deletion. "
            "Use with caution — unsealing removes immutable protection."
        ),
    )
    async def cortex_cornerstones_unseal(memory_id: str) -> str:
        """Unseal cornerstone *memory_id*.

        Args:
            memory_id: ID of the cornerstone to unseal.

        Returns:
            JSON-encoded result.
        """
        plugin = _get_plugin()
        result = await plugin.unseal_cornerstone(memory_id=memory_id)
        if result is None:
            return json.dumps(
                {"ok": False, "error": "CornerstoneGuardian unavailable or unseal failed"}
            )
        return json.dumps(
            {
                "ok": True,
                "id": getattr(result, "id", getattr(result, "cs_id", memory_id)),
                "label": getattr(result, "label", ""),
                "sealed": getattr(result, "sealed", False),
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_health",
        description="Return Cortex system health status — checks all modules and storage.",
    )
    async def cortex_health() -> str:
        """Run a health check across all Cortex modules.

        Returns:
            JSON-encoded HealthReport with per-module status.
        """
        plugin = _get_plugin()
        report = await plugin.health()
        return json.dumps(
            {
                "ok": report.ok,
                "storage": report.storage,
                "modules": report.modules,
                "errors": report.errors,
                "timestamp": report.timestamp,
            },
            indent=2,
        )

    @mcp.tool(
        name="cortex_feedback",
        description=(
            "Submit retrieval feedback for a memory — mark it as helpful or not helpful "
            "so Cortex can improve future retrievals."
        ),
    )
    async def cortex_feedback(
        memory_id: str,
        helpful: bool,
        context: Optional[str] = None,
        target_type: str = "claim",
    ) -> str:
        """Submit feedback on a retrieved memory.

        Args:
            memory_id: ID of the memory being rated.
            helpful: True = helpful, False = harmful/unhelpful.
            context: Optional free-form context string (why helpful/unhelpful).
            target_type: Memory type — 'claim' | 'cornerstone' | 'procedure'.

        Returns:
            JSON-encoded feedback record or error.
        """
        plugin = _get_plugin()
        result = await plugin.feedback(
            memory_id=memory_id,
            helpful=helpful,
            context=context,
            target_type=target_type,
        )
        if result is None:
            return json.dumps(
                {"ok": False, "error": "FeedbackSystem unavailable or submission failed"}
            )
        return json.dumps(
            {
                "ok": True,
                "memory_id": memory_id,
                "helpful": helpful,
                "feedback_id": getattr(result, "id", getattr(result, "feedback_id", None)),
                "created_at": str(getattr(result, "created_at", "")),
            },
            indent=2,
        )

    # =========================================================================
    # BRAIN GRAPH TOOLS (#66)
    # =========================================================================

    @mcp.tool(
        name="brain_graph_query",
        description=(
            "Query the brain graph for nodes and edges matching a natural-language query. "
            "Returns matching nodes, traversal edges, and a ready-to-inject context block."
        ),
    )
    async def brain_graph_query(
        query: str,
        max_depth: int = 3,
        limit: int = 10,
    ) -> str:
        """Traverse the brain graph and return matching nodes/edges.

        Args:
            query: Natural-language query string.
            max_depth: BFS traversal depth (default 3, max 10).
            limit: Max nodes to return (default 10).

        Returns:
            JSON-encoded dict with nodes, edges, context, and token_count.
        """
        plugin = _get_plugin()
        errors: list[Any] = []
        nodes: list[Any] = []
        edges: list[Any] = []
        context = ""
        token_count = 0

        if not query.strip():
            return json.dumps({"ok": False, "error": "query must be non-empty"})

        try:
            from cortex.brain_graph.query import BrainGraphQueryEngine

            engine = BrainGraphQueryEngine(
                storage=plugin.storage,
                llm=plugin.llm,
                config=plugin.config,
            )
            result = await engine.query(
                query=query,
                owner_id=plugin.config.owner_id,
                max_depth=max_depth,
                token_limit=limit * 50,  # rough token budget
            )

            def _node_to_dict(n: Any) -> dict:
                if isinstance(n, dict):
                    return n
                d = getattr(n, "__dict__", {})
                return {k: str(v) if not isinstance(v, (str, int, float, bool, type(None))) else v
                        for k, v in d.items()}

            nodes = [_node_to_dict(n) for n in getattr(result, "nodes", [])]
            context = getattr(result, "context", "")
            token_count = getattr(result, "token_count", 0)
            traversal = getattr(result, "traversal_path", [])
            edges = [
                {"from": traversal[i], "to": traversal[i + 1]}
                for i in range(len(traversal) - 1)
            ]
        except NotImplementedError as exc:
            errors.append(f"Brain graph query not yet implemented: {exc}")
        except Exception as exc:
            errors.append(f"Brain graph query failed: {exc}")
            logger.error("brain_graph_query error", exc_info=True)

        return json.dumps(
            {
                "ok": len(errors) == 0,
                "query": query,
                "nodes": nodes,
                "edges": edges,
                "context": context,
                "token_count": token_count,
                "errors": errors,
            },
            indent=2,
            default=str,
        )

    @mcp.tool(
        name="brain_graph_build",
        description=(
            "Build or rebuild the brain graph index from a source file/directory or raw text. "
            "Provide either source_path (file or directory path) or text + source_id."
        ),
    )
    async def brain_graph_build(
        source_path: Optional[str] = None,
        text: Optional[str] = None,
        source_id: Optional[str] = None,
    ) -> str:
        """Build a brain graph from source material.

        Args:
            source_path: Path to a file or directory to index.
            text: Raw text to index (used when source_path is absent).
            source_id: Stable identifier for the text source.

        Returns:
            JSON-encoded dict with nodes_created, edges_created, graph_id.
        """
        plugin = _get_plugin()
        errors: list[Any] = []
        nodes_created = 0
        edges_created = 0
        graph_id: Optional[str] = None

        if not source_path and not text:
            return json.dumps(
                {"ok": False, "error": "Provide either 'source_path' or 'text'"},
                indent=2,
            )

        try:
            from cortex.brain_graph.builder import BrainGraphBuilder

            builder = BrainGraphBuilder(
                storage=plugin.storage,
                llm=plugin.llm,
                config=plugin.config,
            )
            if source_path:
                result = await builder.build(
                    source=source_path,
                    owner_id=plugin.config.owner_id,
                )
            else:
                result = await builder.build(
                    source=text,
                    source_type="text",
                    source_id=source_id or "mcp-text",
                    owner_id=plugin.config.owner_id,
                )
            nodes_created = len(getattr(result, "nodes", []))
            edges_created = len(getattr(result, "edges", []))
            graph_id = getattr(result, "graph_id", None) or getattr(result, "id", None)
        except NotImplementedError as exc:
            errors.append(f"Brain graph builder not yet implemented: {exc}")
        except Exception as exc:
            errors.append(f"Brain graph build failed: {exc}")
            logger.error("brain_graph_build error", exc_info=True)

        return json.dumps(
            {
                "ok": len(errors) == 0,
                "nodes_created": nodes_created,
                "edges_created": edges_created,
                "graph_id": graph_id,
                "errors": errors,
            },
            indent=2,
        )

    # =========================================================================
    # SESSION LIFECYCLE TOOLS (#66)
    # =========================================================================

    @mcp.tool(
        name="session_wake",
        description=(
            "Start a new Cortex session. Loads cornerstone memories and initialises "
            "the session record. Returns session info and number of cornerstones loaded."
        ),
    )
    async def session_wake(
        session_id: Optional[str] = None,
    ) -> str:
        """Wake Cortex for a new session.

        Args:
            session_id: Optional custom session ID (auto-generated if omitted).

        Returns:
            JSON-encoded WakeReport with session_id and cornerstones_loaded.
        """
        plugin = _get_plugin()
        try:
            report = await plugin.wake(session_id=session_id)
            return json.dumps(
                {
                    "ok": len(getattr(report, "errors", [])) == 0,
                    "session_id": report.session_id,
                    "cornerstones_loaded": report.cornerstones_loaded,
                    "errors": report.errors,
                },
                indent=2,
            )
        except Exception as exc:
            logger.error("session_wake error", exc_info=True)
            return json.dumps({"ok": False, "error": str(exc)}, indent=2)

    @mcp.tool(
        name="session_sleep",
        description=(
            "End the current Cortex session. Persists the end timestamp, "
            "consolidates session memories, and clears the active session."
        ),
    )
    async def session_sleep(
        session_id: Optional[str] = None,
    ) -> str:
        """Gracefully close a session.

        Args:
            session_id: Session to close (uses current active session if omitted).

        Returns:
            JSON-encoded SleepReport with consolidation results.
        """
        import time as _time

        plugin = _get_plugin()
        try:
            t0 = _time.monotonic()
            report = await plugin.sleep(session_id=session_id)
            duration_ms = int((_time.monotonic() - t0) * 1000)
            claims_consolidated = getattr(report, "memories_summarised", 0) or getattr(
                report, "claims_consolidated", 0
            )
            return json.dumps(
                {
                    "ok": len(getattr(report, "errors", [])) == 0,
                    "session_id": report.session_id,
                    "claims_consolidated": claims_consolidated,
                    "duration_ms": duration_ms,
                    "errors": report.errors,
                },
                indent=2,
            )
        except Exception as exc:
            logger.error("session_sleep error", exc_info=True)
            return json.dumps({"ok": False, "error": str(exc)}, indent=2)

    @mcp.tool(
        name="dream",
        description=(
            "Trigger a Cortex dream cycle (Electric Sheep consolidation): "
            "orphan recovery, claim consolidation, decay, curiosity-seed extraction, "
            "and journal generation."
        ),
    )
    async def dream() -> str:
        """Run a full dream-cycle consolidation.

        Returns:
            JSON-encoded dict with steps_completed, curiosity_seeds,
            claims_consolidated, journal, and duration_ms.
        """
        import time as _time

        plugin = _get_plugin()
        errors: list[Any] = []
        steps_completed = 0
        curiosity_seeds = 0
        claims_consolidated = 0
        journal = ""
        duration_ms = 0

        try:
            from cortex.circadian.engine import CircadianEngine
            from cortex.circadian.sleep import SleepConsolidator

            t0 = _time.monotonic()
            from cortex.identity.drift_calculator import DriftCalculator
            from cortex.deprecation.pipeline import DeprecationPipeline

            drift_calculator = DriftCalculator(
                storage=plugin.storage,
                llm=plugin.llm,
                config=plugin.config,
            )
            deprecation_pipeline = DeprecationPipeline(
                storage=plugin.storage,
                config=plugin.config,
            )
            engine = CircadianEngine(
                storage=plugin.storage,
                config=plugin.config,
                llm=plugin.llm,
                cornerstone_guardian=getattr(plugin, 'cornerstone_guardian', None),
                drift_calculator=drift_calculator,
                deprecation_pipeline=deprecation_pipeline,
            )
            report = await engine.dream(owner_id=plugin.config.owner_id)
            duration_ms = int((_time.monotonic() - t0) * 1000)
            claims_consolidated = getattr(report, "claims_consolidated", 0)
            curiosity_seeds = getattr(report, "curiosity_seeds", 0)
            journal = getattr(report, "journal", "")

            step_fields = [
                "orphans_recovered", "claims_consolidated", "contradictions_resolved",
                "claims_decayed", "claims_archived", "cornerstone_proposals",
                "entity_merge_suggestions", "brain_graphs_refreshed", "feedback_applied",
            ]
            steps_completed = sum(1 for f in step_fields if getattr(report, f, 0))
        except NotImplementedError as exc:
            errors.append(f"Dream cycle not yet implemented: {exc}")
        except Exception as exc:
            errors.append(f"Dream cycle failed: {exc}")
            logger.error("dream error", exc_info=True)

        return json.dumps(
            {
                "ok": len(errors) == 0,
                "steps_completed": steps_completed,
                "curiosity_seeds": curiosity_seeds,
                "claims_consolidated": claims_consolidated,
                "journal": journal,
                "duration_ms": duration_ms,
                "errors": errors,
            },
            indent=2,
        )

    @mcp.tool(
        name="stats",
        description=(
            "Get Cortex system statistics: total claims, sessions, cornerstones, "
            "entity counts, and storage health."
        ),
    )
    async def stats() -> str:
        """Return aggregate memory and system statistics.

        Returns:
            JSON-encoded dict with claim counts, cornerstone status, entity counts.
        """
        plugin = _get_plugin()
        errors: list[Any] = []
        total_claims = 0
        total_sessions = 0
        total_cornerstones = 0
        storage_ok = False

        storage = getattr(plugin, "storage", None)
        if storage is None:
            return json.dumps(
                {"ok": False, "error": "Storage not available"},
                indent=2,
            )

        try:
            if hasattr(storage, "count_claims"):
                total_claims = await storage.count_claims(
                    owner_id=plugin.config.owner_id
                )
            if hasattr(storage, "count_sessions"):
                total_sessions = await storage.count_sessions(
                    owner_id=plugin.config.owner_id
                )
            cs_list = await plugin.get_cornerstones()
            total_cornerstones = len(cs_list)
            storage_ok = True
        except Exception as exc:
            errors.append(f"Stats query failed: {exc}")
            logger.error("stats error", exc_info=True)

        return json.dumps(
            {
                "ok": storage_ok and len(errors) == 0,
                "total_claims": total_claims,
                "total_sessions": total_sessions,
                "total_cornerstones": total_cornerstones,
                "storage_ok": storage_ok,
                "errors": errors,
            },
            indent=2,
        )

    # =========================================================================
    # RESOURCES
    # =========================================================================

    @mcp.resource(
        uri="cortex://memory/stats",
        name="Memory Statistics",
        description=(
            "Cortex memory storage statistics: total memories, tier breakdown, "
            "storage size, and health summary."
        ),
        mime_type="application/json",
    )
    async def memory_stats() -> str:
        """Return memory storage statistics."""
        plugin = _get_plugin()
        report = await plugin.health()

        stats: dict[str, Any] = {
            "health": {
                "ok": report.ok,
                "storage": report.storage,
                "modules": report.modules,
                "timestamp": report.timestamp,
            },
        }

        # Try to get richer stats from storage if available
        storage = getattr(plugin, "storage", None)
        if storage is not None:
            for method_name in ("get_stats", "stats", "count"):
                method = getattr(storage, method_name, None)
                if callable(method):
                    try:
                        raw = method()
                        if asyncio.iscoroutine(raw):
                            raw = await raw
                        if isinstance(raw, dict):
                            stats["storage"] = raw
                        elif raw is not None:
                            stats["storage"] = {"count": raw}
                    except Exception as exc:
                        stats["storage_error"] = str(exc)
                    break

        return json.dumps(stats, indent=2, default=str)

    @mcp.resource(
        uri="cortex://cornerstones/list",
        name="Cornerstone List",
        description=(
            "Current list of sealed cornerstone memories — the immutable identity "
            "anchors that define the agent's persistent identity."
        ),
        mime_type="application/json",
    )
    async def cornerstone_list() -> str:
        """Return the current cornerstone list as a resource."""
        plugin = _get_plugin()
        cornerstones = await plugin.get_cornerstones()

        def _cs_to_dict(cs: Any) -> dict:
            if isinstance(cs, dict):
                return cs
            return {
                "id": getattr(cs, "id", getattr(cs, "cs_id", str(cs))),
                "label": getattr(cs, "label", ""),
                "content": getattr(cs, "content", ""),
                "sealed_at": str(getattr(cs, "sealed_at", "")),
                "hash": getattr(cs, "content_hash", getattr(cs, "hash", "")),
            }

        return json.dumps(
            {
                "count": len(cornerstones),
                "cornerstones": [_cs_to_dict(cs) for cs in cornerstones],
            },
            indent=2,
            default=str,
        )

    return mcp


def main() -> None:
    """Entry point — run Cortex MCP server on stdio transport."""
    logging.basicConfig(
        level=logging.WARNING,
        format="%(asctime)s %(name)s %(levelname)s %(message)s",
    )
    mcp = _build_mcp_server()
    mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
