"""Standalone MCP server entry point — exposes AgntSpace over stdio.

Run it directly:
    python -m agntspace.mcp

Or wire it into Claude Desktop's ``claude_desktop_config.json``:

    {
      "mcpServers": {
        "agntspace": {
          "command": "python",
          "args": ["-m", "agntspace.mcp"]
        }
      }
    }

This launcher boots a **read-focused** subset of the full AgntSpace stack:
vault reader/writer, search (file-based glob fallback when no DB),
entity registry, knowledge graph, task/project/goal/journal services. It
deliberately skips DB-bound services (conversation memory, cost tracker,
work queue, heartbeat, cron) so it can safely run alongside a live
``agntspace start`` server without fighting for the SQLite handle.

Tools whose backing service isn't constructed here return a clear
error via ``ToolExecutor.execute``'s standard handler-missing path — MCP
clients see the error text, not a crash.
"""

from __future__ import annotations

import asyncio
import logging
import sys

from mcp.server.stdio import stdio_server

log = logging.getLogger("agntspace.mcp")


def _build_read_stack():
    """Construct the minimum service graph the MCP server needs.

    Returns ``(tool_executor, vault_reader, vault_path)``.
    """
    from agntspace.agent.tools import ToolExecutor
    from agntspace.coach.service import CoachService
    from agntspace.infra.config import get_settings
    from agntspace.journal.service import JournalService
    from agntspace.memory.entity_registry import EntityRegistry
    from agntspace.memory.graph import KnowledgeGraph
    from agntspace.projects.service import ProjectService
    from agntspace.security.contract import ContractEnforcer
    from agntspace.skills.loader import SkillLoader
    from agntspace.tasks.service import TaskService
    from agntspace.vault.paths import VaultPaths
    from agntspace.vault.reader import VaultReader
    from agntspace.vault.writer import VaultWriter

    settings = get_settings()
    vault_dir = settings.vault_dir
    root_dir = settings.root_dir
    if not vault_dir or not vault_dir.exists():
        print(
            "AgntSpace MCP: no vault configured. Run `agntspace init <folder>` first.",
            file=sys.stderr,
        )
        raise SystemExit(2)

    paths = VaultPaths(root_dir)
    vault = VaultReader(vault_dir, paths=paths)
    writer = VaultWriter(vault_dir, paths=paths)
    contract = ContractEnforcer(vault_dir, paths=paths)
    skills = SkillLoader(paths.resolve("system/skills"))
    try:
        skills.load_all()
    except Exception:
        log.debug("SkillLoader failed; continuing without skill configs", exc_info=True)

    entity_registry = EntityRegistry(vault=vault, writer=writer)
    knowledge_graph = KnowledgeGraph(
        vault, writer=writer, entity_registry=entity_registry, skill_loader=skills,
    )
    try:
        knowledge_graph.build()
    except Exception:
        log.debug("KnowledgeGraph.build() failed; continuing with empty graph", exc_info=True)

    task_service = TaskService(vault, writer)
    project_service = ProjectService(vault, writer)
    coach = CoachService(vault, writer, knowledge_graph=knowledge_graph)
    journal = JournalService(vault, writer, skill_loader=skills)

    tool_executor = ToolExecutor(
        contract=contract,
        vault_reader=vault,
        vault_writer=writer,
        vault_search=None,  # DB-bound; skipped in standalone MCP
        task_service=task_service,
        journal=journal,
        coach=coach,
        project_service=project_service,
        knowledge_graph=knowledge_graph,
    )
    return tool_executor, vault, vault_dir


async def _run() -> None:
    from agntspace.mcp.server import create_mcp_server

    tool_executor, vault, vault_dir = _build_read_stack()
    # Stdio is a trusted local subprocess spawned by the user — no
    # bearer token to check, so we use ``full`` scope. Scope narrowing
    # is an SSE / remote-access concept; locally the user already owns
    # the process and the vault.
    server = create_mcp_server(
        tool_executor,
        vault_path=vault_dir,
        vault_reader=vault,
        scope="full",
    )

    # stdio_server yields (read_stream, write_stream). The mcp SDK owns
    # the framing; we just pipe into server.run with the negotiated
    # initialization options.
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())


def main() -> None:
    # Route logs to stderr so stdout stays exclusively the MCP protocol stream.
    logging.basicConfig(level=logging.WARNING, stream=sys.stderr)
    try:
        asyncio.run(_run())
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
