"""Neo Cortex MCP — dual mode memory tools.

Modes (controlled by ENV vars):
    - Proxy (default): forwards to neo-cortex HTTP API on port 5074
    - Embedded: CORTEX_DATA_DIR set → local CortexService + ChromaDB
    - Embedded + subscriber: CORTEX_DATA_DIR + MEMORY_HUB_URL set
      → also runs SignalR subscriber in daemon thread

Usage:
    python -m neo_cortex.mcp          # stdio transport (for Claude Code)
    python -m neo_cortex.mcp --http   # HTTP transport (for testing)
"""

import os
import sys

try:
    from importlib.metadata import version as _pkg_version
    __version__ = _pkg_version("neo-cortex-mcp")
except Exception:
    __version__ = "5.1.5"

_data_dir = os.environ.get("CORTEX_DATA_DIR")
_hub_url = os.environ.get("MEMORY_HUB_URL")
_embedded = _data_dir is not None

from functools import partial
from neo_cortex._log import boot_log, LOG_PATH as _log_path
_bl = partial(boot_log, "BOOT")


_bl(f"{'='*60}")
_bl(f"neo-cortex-mcp v{__version__} STARTING")
_bl(f"python: {sys.version}")
_bl(f"executable: {sys.executable}")
_bl(f"platform: {sys.platform}")
_bl(f"pid: {os.getpid()}")
_bl(f"cwd: {os.getcwd()}")
_bl(f"CORTEX_DATA_DIR: {_data_dir}")
_bl(f"MEMORY_HUB_URL: {_hub_url}")
_bl(f"JINA_API_KEY: {'SET' if os.environ.get('JINA_API_KEY') else 'NOT SET'}")
_bl(f"CLASSIFIER_PROVIDER: {os.environ.get('CLASSIFIER_PROVIDER', 'gemini')}")
_bl(f"embedded mode: {_embedded}")

# ============================================================
# OPENSSL FIX — must run BEFORE any ssl/httpx imports
# uv-managed Python on Windows has broken OpenSSL (OPENSSL_Applink crash).
# Root cause: SSLKEYLOGFILE env var (set by security software) + DLL mismatch.
# See: github.com/astral-sh/python-build-standalone/issues/640
# See: github.com/astral-sh/uv/issues/14333
# ============================================================
if sys.platform == "win32":
    _had_keylog = "SSLKEYLOGFILE" in os.environ
    if _had_keylog:
        del os.environ["SSLKEYLOGFILE"]
        _bl(f"OPENSSL FIX: removed SSLKEYLOGFILE env var")
    else:
        _bl("OPENSSL FIX: SSLKEYLOGFILE not set (good)")

    try:
        import ssl as _ssl_mod
        import _ssl
        _openssl_ver = _ssl_mod.OPENSSL_VERSION
        _bl(f"OPENSSL FIX: ssl force-init OK → {_openssl_ver}")
    except Exception as _ssl_err:
        _bl(f"OPENSSL FIX: ssl force-init FAILED: {_ssl_err}")
else:
    _bl("OPENSSL FIX: not Windows, skipping")

# ============================================================
# IMPORTS — logged one by one to find crash point
# ============================================================
_bl("import: logging")
import logging

_bl("import: threading")
import threading

_bl("import: pathlib.Path")
from pathlib import Path

_bl("import: typing.Optional")
from typing import Optional

_bl("import: httpx")
try:
    import httpx
    _bl("import: httpx OK")
except Exception as e:
    _bl(f"import: httpx FAILED: {e}")
    raise

_bl("import: fastmcp.FastMCP")
try:
    from fastmcp import FastMCP
    _bl("import: fastmcp OK")
except Exception as e:
    _bl(f"import: fastmcp FAILED: {e}")
    raise

# ============================================================
# LOGGER setup (now that logging is imported)
# ============================================================
logger = logging.getLogger("neo-cortex-mcp")

# ============================================================
# MODE INIT — embedded or proxy
# ============================================================
_cortex = None
_digestor = None
_init_error = None

if _embedded:
    _bl("embedded: importing subscriber.configure_paths")
    from neo_cortex.subscriber import configure_paths
    _bl("embedded: configure_paths()")
    configure_paths(_data_dir)
    Path(_data_dir).mkdir(parents=True, exist_ok=True)

    try:
        _bl("embedded: importing neo_cortex.config")
        from neo_cortex import config
        _bl(f"embedded: CORTEX_DB_PATH={config.CORTEX_DB_PATH}")
        _bl(f"embedded: MEMORY_INDEX_DB_PATH={config.MEMORY_INDEX_DB_PATH}")
        _bl(f"embedded: CONVERSATION_DB_PATH={config.CONVERSATION_DB_PATH}")

        _bl("embedded: importing Classifier + LLM")
        from neo_cortex.classifier import Classifier
        from neo_cortex.llm import create_client

        _bl("embedded: importing CortexService")
        from neo_cortex.cortex import CortexService

        _bl("embedded: importing JinaEmbedder")
        from neo_cortex.embedder import JinaEmbedder

        _bl("embedded: importing MemoryIndex")
        from neo_cortex.memory_index import MemoryIndex

        _bl("embedded: importing MemoryStore")
        from neo_cortex.store import MemoryStore

        _bl("embedded: importing v5 components")
        from neo_cortex.conversation_log import ConversationLog
        from neo_cortex.dedup import DedupStore
        from neo_cortex.digestor import EpisodeDigestor
        from neo_cortex.graph import ConceptGraph

        _bl(f"embedded: creating MemoryStore(db_path={config.CORTEX_DB_PATH})")
        store = MemoryStore(config.CORTEX_DB_PATH, config.COLLECTION_NAME)
        _bl(f"embedded: MemoryStore OK, count={store.count()}")

        _bl("embedded: load_api_key('jina')")
        jina_key = config.load_api_key("jina")
        _bl(f"embedded: jina key loaded ({len(jina_key)} chars)")

        _bl(f"embedded: platform={sys.platform}, verify_ssl={sys.platform != 'win32'}")

        _bl("embedded: creating JinaEmbedder")
        embedder = JinaEmbedder(jina_key)
        _bl("embedded: JinaEmbedder OK")

        _bl(f"embedded: creating LLM client (provider={config.CLASSIFIER_PROVIDER})")
        llm_client = create_client()
        _bl(f"embedded: LLM client OK → {llm_client.name}")

        _bl("embedded: creating Classifier")
        classifier = Classifier(llm_client)
        _bl("embedded: Classifier OK")

        _bl("embedded: creating MemoryIndex")
        index = MemoryIndex(config.MEMORY_INDEX_DB_PATH)
        rd = index.register_runtime_day()
        _bl(f"embedded: MemoryIndex OK, count={index.count()}, runtime_day={rd}")

        # v5 components — dedup + graph share memory_index connection
        _bl("embedded: creating DedupStore (shared conn)")
        dedup = DedupStore(index.conn)
        _bl("embedded: DedupStore OK")

        _bl("embedded: creating ConceptGraph (shared conn)")
        graph = ConceptGraph(index.conn)
        _bl(f"embedded: ConceptGraph OK, nodes={graph.node_count()}")

        _bl("embedded: creating CortexService (v5: dedup+graph)")
        _cortex = CortexService(store, embedder, classifier, index=index, dedup=dedup, graph=graph)
        _bl("embedded: CortexService v5 READY")

        # Consolidator (phase 2 — merge similar memories during idle)
        from neo_cortex.merge_consolidator import MemoryConsolidator
        _bl("embedded: creating MemoryConsolidator")
        _consolidator = MemoryConsolidator(
            store=store, index=index, embedder=embedder,
            llm=llm_client, dedup=dedup, graph=graph,
        )
        _bl("embedded: MemoryConsolidator READY")

        # Distiller (Gemini — for digestor distill mode)
        distiller = None
        if config.DIGESTOR_MODE == "distill":
            try:
                from neo_cortex.distiller import GeminiDistiller
                _bl("embedded: load_api_key('gemini')")
                gemini_key = config.load_api_key("gemini")
                _bl(f"embedded: gemini key loaded ({len(gemini_key)} chars)")
                distiller = GeminiDistiller(gemini_key)
                _bl(f"embedded: GeminiDistiller OK (model={config.GEMINI_MODEL})")
            except KeyError:
                _bl("embedded: no gemini key found, falling back to segment mode")
            except Exception as e:
                _bl(f"embedded: GeminiDistiller FAILED: {e}, falling back to segment mode")

        # Digestor
        _bl(f"embedded: creating ConversationLog({config.CONVERSATION_DB_PATH})")
        _conv_log = ConversationLog(config.CONVERSATION_DB_PATH)
        _conv_count = _conv_log.count()
        _bl(f"embedded: ConversationLog OK, entries={_conv_count}")

        _mode = "distill" if distiller else "segment"
        _bl(f"embedded: creating EpisodeDigestor (mode={_mode})")
        _digestor = EpisodeDigestor(log=_conv_log, cortex=_cortex, classifier=classifier, distiller=distiller, consolidator=_consolidator)
        _bl("embedded: EpisodeDigestor READY — will start background thread in main()")

    except Exception as e:
        _init_error = str(e)
        _bl(f"embedded: INIT FAILED: {e}")
        import traceback
        _bl(traceback.format_exc())
        _cortex = None


def _check_embedded():
    """Raise clear error if embedded mode but cortex init failed."""
    if _embedded and _cortex is None:
        raise RuntimeError(f"CortexService init failed: {_init_error}. Check JINA_API_KEY and GEMINI_API_KEY env vars.")


# --- Subscriber thread (embedded + hub_url) ---
def _start_subscriber(hub_url: str, cortex) -> threading.Thread:
    """Start SignalR subscriber in a daemon thread with its own asyncio loop.

    Feeds BOTH paths:
      - Strada 1: cortex.ingest() per turn (noise filter)
      - Strada 2: conversation_log.append() per event (digestor, no noise filter)
    """
    import asyncio as _asyncio

    from neo_cortex.models import ConversationAppendRequest
    from neo_cortex.subscriber import TurnAccumulator

    async def _run():
        from pysignalr.client import SignalRClient

        client = SignalRClient(hub_url)
        accumulator = TurnAccumulator()

        def _append_log(session_id: str, role: str, content: str) -> None:
            """Feed conversation_log (Strada 2) — safe, never throws."""
            if not _conv_log or not content:
                return
            try:
                _conv_log.append(ConversationAppendRequest(
                    session_id=session_id, role=role, content=content,
                ))
            except Exception as e:
                _bl(f"SUBSCRIBER: conv_log append failed: {e}")

        async def on_user_message(args):
            data = args[0] if args else {}
            sid = data.get("sessionId", "")
            text = data.get("text", "")
            accumulator.on_user_message(sid, text)
            _append_log(sid, "user", text)

        async def on_assistant_message(args):
            data = args[0] if args else {}
            sid = data.get("sessionId", "")
            text = data.get("text", "")
            accumulator.on_assistant_message(sid, text, data.get("model", "unknown"))
            _append_log(sid, "assistant", text)

        async def on_tool_use(args):
            data = args[0] if args else {}
            sid = data.get("sessionId", "")
            tool_name = data.get("toolName", "")
            accumulator.on_tool_use(sid, tool_name)
            _append_log(sid, "tool_use", tool_name)

        async def on_tool_result(args):
            data = args[0] if args else {}
            sid = data.get("sessionId", "")
            output = data.get("output", "")
            # Truncate large tool results for conversation_log
            if len(output) > 2000:
                output = output[:2000] + "\n[truncated]"
            _append_log(sid, "tool_result", output)

        async def on_turn_complete(args):
            if not accumulator.is_complete:
                accumulator.reset()
                return
            from neo_cortex.models import IngestRequest
            req = IngestRequest(
                session_id=accumulator.session_id,
                question=accumulator.question,
                answer=accumulator.answer,
                model=accumulator.model,
                tools_used=accumulator.tools_used,
                turn_number=accumulator.turn_number,
            )
            try:
                memory_id = await cortex.ingest(req)
                if memory_id:
                    logger.info("Subscriber ingested: %s (turn %d)", memory_id, accumulator.turn_number)
            except Exception:
                logger.exception("Subscriber ingest failed for turn %d", accumulator.turn_number)
            accumulator.reset()

        client.on("MemoryUserMessage", on_user_message)
        client.on("MemoryAssistantMessage", on_assistant_message)
        client.on("MemoryToolUse", on_tool_use)
        client.on("MemoryToolResult", on_tool_result)
        client.on("MemoryTurnComplete", on_turn_complete)

        _bl(f"SUBSCRIBER: connecting to {hub_url}")
        await client.run()

    def _thread_target():
        try:
            _asyncio.run(_run())
        except Exception as e:
            # CRITICAL: catch ALL exceptions in subscriber thread.
            # An unhandled exception here can crash the entire process.
            _bl(f"SUBSCRIBER: CRASHED: {e}")
            import traceback
            _bl(f"SUBSCRIBER: {traceback.format_exc()}")

    t = threading.Thread(target=_thread_target, daemon=True, name="cortex-subscriber")
    t.start()
    _bl("SUBSCRIBER: thread started")
    return t


# --- MCP server ---
_bl("creating FastMCP server")
mcp = FastMCP(
    "neo-cortex",
    instructions="Neo's memory cortex. Semantic memory search, ingestion, dream cycles, and timeline.",
)
_bl("FastMCP server created")

# ============================================================
# AUTO-BOOTSTRAP — install hooks into .claude/settings.json
# ============================================================
try:
    from neo_cortex.bootstrap import ensure_hooks
    _boot_res = ensure_hooks()
    if _boot_res.error:
        _bl(f"bootstrap: skipped — {_boot_res.error}")
    elif _boot_res.already_present:
        _bl("bootstrap: hooks already installed")
    else:
        _parts = []
        if _boot_res.session_start_installed:
            _parts.append("SessionStart")
        if _boot_res.stop_installed:
            _parts.append("Stop")
        if _boot_res.created_dir:
            _parts.append("created .claude/")
        if _boot_res.created_file:
            _parts.append("created settings.json")
        _bl(f"bootstrap: installed — {', '.join(_parts)} → {_boot_res.settings_path}")
        _bl("bootstrap: RESTART Claude Code to activate hooks")
except Exception as _boot_err:
    _bl(f"bootstrap: FAILED (non-fatal): {_boot_err}")


async def _run_tool(name: str, fn) -> dict | str:
    """Guard + error handler for all MCP tool calls."""
    _bl(f"TOOL {name}")
    try:
        if not _cortex:
            return {"error": "no cortex available"}
        result = await fn()
        _bl(f"TOOL {name}: OK")
        return result
    except Exception as e:
        import traceback
        _bl(f"TOOL {name}: FAILED: {e}")
        _bl(traceback.format_exc())
        return {"error": str(e)}


@mcp.tool()
async def memory_query(query: str, n: int = 5, detail: str = "compact") -> dict | str:
    """Search past conversation memories by semantic similarity.

    Args:
        query: What to search for (natural language)
        n: Number of results to return (default 5)
        detail: "compact" (MBEL + refs) or "full" (raw JSON for debug)
    """
    async def _do():
        result = await _cortex.recall(query, n=n, smart=True)
        if detail == "full":
            return result.model_dump()
        mbel = result.mbel or "(no results)"
        ref_lines = []
        for m in result.memories:
            title = m.record.topic or m.record.question[:80]
            if _cortex.index:
                sf = _cortex.index.get_structured(m.record.id)
                if sf and sf.title:
                    title = sf.title
            ref_lines.append(f"  {m.record.id}::{title}{{sim={m.similarity:.2f}}}")
        if ref_lines:
            mbel += "\n§refs\n" + "\n".join(ref_lines)
        return mbel
    return await _run_tool("memory_query", _do)


@mcp.tool()
async def memory_stats(detail: str = "compact") -> dict:
    """Get cortex statistics: total memories, sessions, energy levels, dream count."""
    async def _do():
        if detail == "full":
            return _cortex.stats().model_dump()
        if _cortex.index:
            return _cortex.index.stats(_cortex.dream_count)
        return _cortex.stats().model_dump()
    return await _run_tool("memory_stats", _do)


@mcp.tool()
async def memory_dream(detail: str = "compact") -> dict:
    """Run one dream cycle: boost high-energy memories, decay others."""
    async def _do():
        result = await _cortex.dream()
        if detail == "full":
            return result.model_dump()
        return {
            "status": result.status,
            "dream_count": result.dream_count,
            "high_energy_count": len(result.cluster),
        }
    return await _run_tool("memory_dream", _do)


@mcp.tool()
async def memory_spontaneous(n: int = 3, detail: str = "compact") -> dict | str:
    """Surface important memories not accessed recently (spontaneous recall).

    Returns high-energy memories that haven't been accessed in 7+ days.
    Useful for rediscovering forgotten but important knowledge.

    Args:
        n: Number of memories to surface (default 3)
        detail: "compact" (MBEL refs) or "full" (raw JSON)
    """
    async def _do():
        memories = await _cortex.spontaneous(n=n)
        if not memories:
            return {"status": "no spontaneous memories", "count": 0}
        if detail == "full":
            return {"memories": [m.model_dump() for m in memories], "count": len(memories)}
        lines = [f"§spontaneous @count::{len(memories)}"]
        for m in memories:
            title = m.topic or m.question[:80]
            if _cortex.index:
                sf = _cortex.index.get_structured(m.id)
                if sf and sf.title:
                    title = sf.title
            lines.append(f"  {m.id}::{title}{{project={m.project}}}")
        return "\n".join(lines)
    return await _run_tool("memory_spontaneous", _do)


@mcp.tool()
async def memory_ingest(turn: dict) -> dict:
    """Manually ingest a conversation turn into memory.

    Args:
        turn: JSON with session_id, question, answer, model, stats
    """
    async def _do():
        from neo_cortex.models import IngestRequest
        req = IngestRequest(**turn)
        memory_id = await _cortex.ingest(req)
        return {"status": "ok", "id": memory_id}
    return await _run_tool("memory_ingest", _do)


@mcp.tool()
async def memory_timeline(n: int = 10, project: Optional[str] = None, detail: str = "compact") -> dict | str:
    """Get most recent memories by timestamp (newest first).

    Use this at session start to load recent context, or to see what happened lately.

    Args:
        n: Number of recent memories to return (default 10)
        project: Optional filter by project name
        detail: "compact" (titles only) or "full" (raw JSON for debug)
    """
    async def _do():
        if detail == "full":
            return (await _cortex.timeline(n=n, project=project)).model_dump()
        result = await _cortex.timeline(n=n, project=project, smart=True)
        return result.mbel or "(no memories)"
    return await _run_tool("memory_timeline", _do)


@mcp.tool()
async def memory_search(query: str = "", project: Optional[str] = None,
                        activity: Optional[str] = None, n: int = 10) -> dict:
    """Search memory index. Returns compact results (title/project/date).
    Use memory_get for full details of specific memories.

    Args:
        query: Search text (or empty for recent)
        project: Filter by project name
        activity: Filter by activity type
        n: Max results
    """
    async def _do():
        if _cortex.index:
            results = _cortex.index.search_fts(query=query, project=project, activity=activity, n=n)
            return {"results": [r.model_dump() for r in results]}
        result = await _cortex.recall(query or "recent", n=n)
        return result.model_dump()
    return await _run_tool("memory_search", _do)


@mcp.tool()
async def memory_get(ids: str, detail: str = "compact") -> dict:
    """Get full memory details by IDs (comma-separated).

    Args:
        ids: Comma-separated memory IDs (e.g. "v3_abc_1_12345,v3_def_2_67890")
        detail: "compact" (structured fields) or "full" (raw record for debug)
    """
    async def _do():
        if not _cortex.index:
            return {"error": "memory_get requires SQLite index in embedded mode"}
        id_list = [i.strip() for i in ids.split(",")]
        if detail == "full":
            records = _cortex.index.get_by_ids(id_list)
            return {"memories": [r.model_dump() for r in records]}
        results = []
        for mid in id_list:
            sf = _cortex.index.get_structured(mid)
            if sf:
                row = _cortex.index.get_by_id(mid)
                results.append({
                    "id": mid,
                    "title": sf.title,
                    "summary": sf.summary,
                    "facts": sf.facts,
                    "concepts": sf.concepts,
                    "files_touched": sf.files_touched,
                    "project": row.project if row else "general",
                    "activity": row.activity.value if row and hasattr(row.activity, "value") else "discussion",
                    "energy": row.energy if row else 1.0,
                })
        return {"memories": results}
    return await _run_tool("memory_get", _do)


@mcp.tool()
async def memory_rebuild() -> dict:
    """Wipe all memories and rebuild from conversation log.

    Clears: ChromaDB, memory_index, dedup hashes, concept graph.
    Keeps: conversation_log (all turns marked as undigested).
    The digestor will automatically re-process everything.
    """
    async def _do():
        if _digestor:
            _digestor.pause()
        try:
            cleared = {}
            cleared["chromadb"] = _cortex._store.clear()
            if _cortex._index:
                cleared["memory_index"] = _cortex._index.clear()
            if _cortex._dedup:
                cleared["dedup"] = _cortex._dedup.clear()
            if _cortex._graph:
                cleared["concept_graph"] = _cortex._graph.clear()
            if _cortex._index:
                cleared["pending_merges"] = _cortex._index.clear_pending_merges()
            if _conv_log:
                cleared["turns_reset"] = _conv_log.reset_digested()
                cleared["total_turns"] = _conv_log.count()
            _cortex._dream_count = 0
            return {"status": "rebuilt", "cleared": cleared}
        finally:
            if _digestor:
                _digestor.resume()
    return await _run_tool("memory_rebuild", _do)


@mcp.tool()
async def memory_identity_review() -> dict | str:
    """Review mature metacognitive insights and propose CLAUDE.md amendments.

    Clusters self-reflective memories that have high energy and stability,
    generates amendment proposals, and returns them with evidence trail.
    Only returns proposals that pass coherence check.
    """
    async def _do():
        from neo_cortex.reflection import identity_review
        from neo_cortex.llm import create_client
        llm = create_client()
        proposals = await identity_review(_cortex, llm)
        if not proposals:
            return {"status": "no_proposals", "message": "No mature insights ready for graduation."}
        return {"status": "proposals_ready", "count": len(proposals), "proposals": proposals}
    return await _run_tool("memory_identity_review", _do)


# --- Dashboard ---

_dashboard_port: int = 0


@mcp.tool()
async def memory_dashboard() -> dict:
    """Get the URL of the cortex dashboard web UI.

    Opens in browser to browse memories, view concept graph,
    check energy distribution, and trigger maintenance operations.
    Each neo-cortex instance gets its own dynamic port.
    """
    if _dashboard_port:
        return {"url": f"http://localhost:{_dashboard_port}", "status": "running"}
    return {"error": "dashboard not available", "status": "not_started"}


# --- Entrypoint ---


def _start_digestor(digestor) -> threading.Thread:
    """Start EpisodeDigestor in a daemon thread.

    Each tick: grab undigested turns, segment by topic, ingest complete segments.
    Sleeps 30s between ticks.
    """
    import asyncio as _asyncio

    async def _run():
        await digestor.run()

    def _thread_target():
        try:
            _asyncio.run(_run())
        except Exception as e:
            _bl(f"DIGESTOR: CRASHED: {e}")
            import traceback
            _bl(f"DIGESTOR: {traceback.format_exc()}")

    t = threading.Thread(target=_thread_target, daemon=True, name="cortex-digestor")
    t.start()
    _bl("DIGESTOR: thread started")
    return t


def main():
    _bl("main() called")

    # Configure structured logging (FileHandler + StreamHandler)
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
        handlers=[
            logging.FileHandler(_log_path, encoding="utf-8"),
            logging.StreamHandler(),
        ],
    )

    # Diagnostic summary
    import platform
    try:
        import chromadb as _chromadb
        chroma_ver = _chromadb.__version__
    except Exception:
        chroma_ver = "N/A"

    logger.info(
        "neo-cortex-mcp v%s | python %s | chromadb %s | os=%s | embedded=%s | data_dir=%s",
        __version__, platform.python_version(), chroma_ver,
        platform.system(), _embedded, _data_dir,
    )
    if _cortex:
        logger.info("CortexService v5 OK (dedup+graph+digestor)")
    else:
        logger.error("CortexService FAILED: %s", _init_error)

    # Start subscriber if both env vars set
    if _hub_url and _cortex:
        _start_subscriber(_hub_url, _cortex)

    # Start digestor background thread
    if _digestor and _cortex:
        _start_digestor(_digestor)

    # Start dashboard web UI
    if _cortex:
        from neo_cortex.dashboard import start_dashboard
        global _dashboard_port
        _dashboard_port = start_dashboard(_cortex)
        _bl(f"DASHBOARD: http://localhost:{_dashboard_port}")

    _bl("starting MCP transport")
    if "--http" in sys.argv:
        mcp.run(transport="streamable-http", host="127.0.0.1", port=5075)
    else:
        mcp.run(transport="stdio")


if __name__ == "__main__":
    main()
