"""MCP Server for cokodo-agent.

Exposes .agent/ protocol context via Model Context Protocol,
enabling IDE AI clients to programmatically access project data
and update project state.

Requires: pip install cokodo-agent[mcp]
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from mcp.server.fastmcp import FastMCP

from cokodo_agent.context import (
    build_context_content,
    get_context_files,
    list_project_files,
    read_status,
    update_status_section,
)

_RESOURCE_FILES: dict[str, str] = {
    "status": "project/status.md",
    "context": "project/context.md",
    "deploy": "project/deploy.md",
    "tech-stack": "project/tech-stack.md",
    "commands": "project/commands.md",
    "known-issues": "project/known-issues.md",
}


_NO_PROJECT_MSG = (
    "No .agent/ directory found in the current project. "
    "Run 'co init' to initialize the protocol."
)


def create_server(agent_dir: Path | None = None) -> FastMCP:
    """Create and configure the MCP server.

    The server always starts successfully. If no .agent/ directory is found,
    tools return a helpful error message instead of crashing the server process.

    Args:
        agent_dir: Path to .agent/ directory. If None, searches from cwd.
    """
    if agent_dir is None:
        try:
            agent_dir = _find_agent_dir()
        except FileNotFoundError:
            agent_dir = None
    elif not agent_dir.is_dir():
        agent_dir = None

    # Capture as a local variable for closures; may be None if no project found.
    _dir = agent_dir

    mcp = FastMCP(
        "cokodo-agent",
        instructions=(
            "This server provides access to the AI Agent Collaboration Protocol (.agent/ directory). "
            "Use get_project_context as your FIRST action to load all relevant project files. "
            "Use update_status after completing tasks or before git commits."
        ),
    )

    for _name, _rel_path in _RESOURCE_FILES.items():

        def _make_reader(name: str, path: str, d: Path | None) -> Any:
            def reader() -> str:
                if d is None:
                    return _NO_PROJECT_MSG
                full = d / path
                if not full.exists():
                    return f"File not found: {path}"
                return full.read_text(encoding="utf-8")

            reader.__name__ = f"read_{name.replace('-', '_')}"
            reader.__doc__ = f"Current content of .agent/{path}"
            return reader

        mcp.resource(f"agent://{_name}")(
            _make_reader(_name, _rel_path, _dir)
        )

    @mcp.resource("agent://file/{path}")
    def read_agent_file(path: str) -> str:
        """Read any file from the .agent/ directory by relative path."""
        if _dir is None:
            return _NO_PROJECT_MSG
        safe_path = Path(path)
        if ".." in safe_path.parts:
            return "Error: path traversal not allowed"
        full = _dir / safe_path
        if not full.exists():
            return f"File not found: {path}"
        try:
            return full.read_text(encoding="utf-8")
        except OSError as e:
            return f"Error reading {path}: {e}"

    @mcp.tool()
    def get_project_context(
        stack: str | None = None,
        task: str | None = None,
    ) -> str:
        """Get all relevant project context files based on stack and task type.

        Call this as your FIRST action in every conversation to load project context.

        Args:
            stack: Tech stack filter (python/rust/qt/mixed). Optional.
            task: Task type or profile (coding/testing/review/documentation/
                  bug_fix/feature_development/code_review). Optional.

        Returns:
            Concatenated contents of all relevant .agent/ files.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        files = get_context_files(_dir, stack=stack, task=task)
        if not files:
            return "No files found in manifest.json loading_strategy."

        content_map = build_context_content(_dir, files)
        if not content_map:
            return "No context files could be read."

        parts: list[str] = []
        for rel_path, content in content_map.items():
            parts.append(f"# {rel_path}\n\n{content}")
        return "\n---\n\n".join(parts)

    @mcp.tool()
    def update_status(
        completed_tasks: list[str] | None = None,
        new_tasks: list[str] | None = None,
        blockers: list[str] | None = None,
        session_context: str | None = None,
    ) -> str:
        """Update project/status.md at a checkpoint.

        Call this after completing tasks, before git commits,
        when switching tasks, or when blockers change.

        Args:
            completed_tasks: Task descriptions to mark as done.
            new_tasks: New task descriptions to add to Pending.
            blockers: Current blockers (replaces existing). Pass empty list to clear.
            session_context: Key info for next session (replaces Session Context section).

        Returns:
            Updated status.md content, or error message.
        """
        if _dir is None:
            return _NO_PROJECT_MSG
        return update_status_section(
            _dir,
            completed_tasks=completed_tasks,
            new_tasks=new_tasks,
            blockers=blockers,
            session_context=session_context,
        )

    @mcp.tool()
    def lint_protocol() -> str:
        """Run protocol compliance check on the .agent/ directory.

        Returns a summary of lint results (passed/failed rules).
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from collections import defaultdict

        from cokodo_agent.linter import ProtocolLinter

        linter = ProtocolLinter(_dir)
        all_results = linter.lint_all()

        by_rule: dict[str, list] = defaultdict(list)
        for r in all_results:
            by_rule[r.rule].append(r)

        lines: list[str] = []
        total_pass = 0
        total_fail = 0
        for rule_name, rule_results in sorted(by_rule.items()):
            passed = sum(1 for r in rule_results if r.passed)
            failed = len(rule_results) - passed
            total_pass += passed
            total_fail += failed
            status = "OK" if failed == 0 else "FAIL"
            lines.append(f"[{status}] {rule_name}: {passed}/{len(rule_results)} passed")
            if failed > 0:
                for r in rule_results:
                    if not r.passed:
                        lines.append(f"  x {r.file or ''}: {r.message}")

        lines.append(f"\nTotal: {total_pass}/{total_pass + total_fail} passed")
        return "\n".join(lines)

    @mcp.tool()
    def list_files() -> str:
        """List all files in .agent/ directory with their loading layer and description.

        Useful for discovering what project information is available.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        files = list_project_files(_dir)
        if not files:
            return "No files found in .agent/ directory."

        lines: list[str] = []
        current_layer = ""
        for f in sorted(files, key=lambda x: (x["layer"], x["path"])):
            if f["layer"] != current_layer:
                current_layer = f["layer"]
                lines.append(f"\n## {current_layer}")
            lines.append(f"- {f['path']}: {f['description']}")
        return "\n".join(lines)

    # ------------------------------------------------------------------
    # Cross-project reference tools
    # ------------------------------------------------------------------

    @mcp.tool()
    def list_relations() -> str:
        """List all declared project relationships (references and collaborations).

        Returns names, types, source paths, and accessibility status for each
        declared relationship. Use this to discover what cross-project context
        is available.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import (
            check_collaborations,
            check_references,
            load_collaborations,
            load_references,
        )

        project_root = _dir.parent
        refs = load_references(_dir)
        collabs = load_collaborations(_dir)

        if not refs and not collabs:
            return "No cross-project relationships declared in manifest.json."

        lines: list[str] = []

        if refs:
            statuses = check_references(_dir, project_root)
            status_map = {s.name: s for s in statuses}

            lines.append("# References\n")
            for r in refs:
                st = status_map.get(r.name)
                health = "OK" if (st and st.accessible) else "INACCESSIBLE"
                stype = st.source_type if st else "unknown"
                lines.append(f"- **{r.name}** ({stype}, {health})")
                if r.description:
                    lines.append(f"  {r.description}")
                lines.append(f"  source: {r.source}")
                if r.scope:
                    lines.append(f"  scope: {', '.join(r.scope)}")
                lines.append(f"  use_when: {r.use_when}")
                lines.append("")

        if collabs:
            cstatuses = check_collaborations(_dir, project_root)
            cstatus_map = {s.name: s for s in cstatuses}

            lines.append("# Collaborations\n")
            for c in collabs:
                cst = cstatus_map.get(c.name)
                if cst and cst.partner_accessible:
                    health = "IN_SYNC" if cst.shared_files_in_sync else "DRIFTED"
                elif cst:
                    health = "INACCESSIBLE"
                else:
                    health = "UNKNOWN"
                lines.append(f"- **{c.name}** (role: {c.role}, {health})")
                if c.description:
                    lines.append(f"  {c.description}")
                lines.append(f"  partner: {c.partner}")
                if c.shared_scope:
                    lines.append(f"  shared_scope: {', '.join(c.shared_scope)}")
                lines.append("")

        return "\n".join(lines)

    @mcp.tool()
    def get_related_context(
        relation: str,
        file: str | None = None,
    ) -> str:
        """Get context from a related project, document, or directory.

        Use list_relations first to discover available references.

        Args:
            relation: Name of the declared reference (from manifest.json).
            file: Specific file to read (relative path). If omitted, returns
                  all files in scope concatenated.

        Returns:
            Content of the referenced file(s), or an error message.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import (
            get_reference_by_name,
            resolve_reference_content,
        )

        project_root = _dir.parent
        ref = get_reference_by_name(_dir, relation)
        if ref is None:
            return f"Reference '{relation}' not found. Use list_relations to see available references."

        try:
            content_map = resolve_reference_content(ref, project_root, file=file)
        except FileNotFoundError as e:
            return f"Error: {e}"

        if not content_map:
            return f"No matching files found in reference '{relation}'."

        parts: list[str] = []
        for rel_path, content in content_map.items():
            parts.append(f"# [ref:{relation}] {rel_path}\n\n{content}")
        return "\n---\n\n".join(parts)

    @mcp.tool()
    def get_collaboration_context(
        collaboration: str,
        file: str | None = None,
    ) -> str:
        """Get shared content from a collaboration partner.

        Use list_relations first to discover available collaborations.

        Args:
            collaboration: Name of the declared collaboration.
            file: Specific file to read (relative path). If omitted, returns
                  all shared-scope files concatenated.

        Returns:
            Content of the shared file(s), or an error message.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import (
            get_collaboration_by_name,
            resolve_collaboration_content,
        )

        project_root = _dir.parent
        c = get_collaboration_by_name(_dir, collaboration)
        if c is None:
            return (
                f"Collaboration '{collaboration}' not found. "
                "Use list_relations to see available collaborations."
            )

        try:
            content_map = resolve_collaboration_content(c, project_root, file=file)
        except FileNotFoundError as e:
            return f"Error: {e}"

        if not content_map:
            return f"No matching files found in collaboration '{collaboration}'."

        parts: list[str] = []
        for rel_path, content in content_map.items():
            parts.append(f"# [collab:{collaboration}] {rel_path}\n\n{content}")
        return "\n---\n\n".join(parts)

    @mcp.tool()
    def get_collaboration_status(
        collaboration: str | None = None,
    ) -> str:
        """Get sync status and file diff for collaborations.

        Args:
            collaboration: Specific collaboration name. If omitted, checks all.

        Returns:
            Detailed sync status including which files have drifted.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import (
            check_collaborations,
            diff_collaboration,
            get_collaboration_by_name,
            load_collaborations,
        )

        project_root = _dir.parent

        if collaboration:
            c = get_collaboration_by_name(_dir, collaboration)
            if c is None:
                return f"Collaboration '{collaboration}' not found."
            collabs = [c]
        else:
            collabs = load_collaborations(_dir)

        if not collabs:
            return "No collaborations declared."

        statuses = check_collaborations(_dir, project_root)
        status_map = {s.name: s for s in statuses}

        lines: list[str] = []
        for c in collabs:
            st = status_map.get(c.name)
            lines.append(f"## {c.name} (role: {c.role})")

            if not st or not st.partner_accessible:
                lines.append(f"  Status: INACCESSIBLE")
                if st and st.error:
                    lines.append(f"  Error: {st.error}")
                lines.append("")
                continue

            if st.shared_files_in_sync:
                lines.append("  Status: IN_SYNC")
            else:
                lines.append("  Status: DRIFTED")
                try:
                    diffs = diff_collaboration(c, project_root, _dir)
                    for fpath, fstatus in diffs.items():
                        if fstatus != "in_sync":
                            lines.append(f"  - {fpath}: {fstatus}")
                except FileNotFoundError:
                    pass
            lines.append("")

        return "\n".join(lines)

    @mcp.tool()
    def fetch_remote_references() -> str:
        """Fetch or refresh all remote (git:...) references.

        Downloads remote repository content to local cache so that
        get_related_context can serve it. Remote references that are
        already cached and fresh are skipped unless they are stale.

        Returns a summary of fetch results.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import load_references
        from cokodo_agent.remote import (
            fetch_remote_source,
            is_remote_source,
            parse_remote_source,
        )

        refs = load_references(_dir)
        remote_refs = [r for r in refs if is_remote_source(r.source)]

        if not remote_refs:
            return "No remote references declared."

        lines: list[str] = []
        for r in remote_refs:
            try:
                parsed = parse_remote_source(r.source)
                fetch_remote_source(parsed, force=False)
                lines.append(f"[OK] {r.name}: cached ({parsed.owner}/{parsed.repo})")
            except (ConnectionError, FileNotFoundError, ValueError) as e:
                lines.append(f"[FAIL] {r.name}: {e}")

        return "\n".join(lines)

    @mcp.tool()
    def check_relation_health() -> str:
        """Check if all declared relationships are accessible.

        Returns a summary showing which references and collaborations are
        reachable and which have errors. Useful for diagnosing broken
        cross-project links.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        from cokodo_agent.relations import (
            check_collaborations,
            check_references,
            load_collaborations,
            load_references,
        )

        project_root = _dir.parent
        refs = load_references(_dir)
        collabs = load_collaborations(_dir)

        if not refs and not collabs:
            return "No relationships declared. Nothing to check."

        lines: list[str] = []
        ok_count = 0
        fail_count = 0

        if refs:
            lines.append("## References")
            statuses = check_references(_dir, project_root)
            for st in statuses:
                if st.accessible:
                    lines.append(f"[OK]   {st.name} -> {st.source_type} ({st.resolved_path})")
                    ok_count += 1
                else:
                    lines.append(f"[FAIL] {st.name}: {st.error}")
                    fail_count += 1
            lines.append("")

        if collabs:
            lines.append("## Collaborations")
            cstatuses = check_collaborations(_dir, project_root)
            for cst in cstatuses:
                if cst.partner_accessible:
                    sync = "in_sync" if cst.shared_files_in_sync else "drifted"
                    lines.append(f"[OK]   {cst.name} ({cst.role}) -> {sync}")
                    ok_count += 1
                else:
                    lines.append(f"[FAIL] {cst.name} ({cst.role}): {cst.error}")
                    fail_count += 1
            lines.append("")

        lines.append(f"\n{ok_count}/{ok_count + fail_count} relationships healthy.")
        return "\n".join(lines)

    # ------------------------------------------------------------------
    # Prompts
    # ------------------------------------------------------------------

    @mcp.prompt()
    def session_init(
        stack: str | None = None,
        task: str | None = None,
    ) -> str:
        """Build a complete session initialization prompt with all relevant project context.

        Args:
            stack: Tech stack filter (python/rust/qt/mixed). Optional.
            task: Task type or profile. Optional.
        """
        if _dir is None:
            return _NO_PROJECT_MSG

        files = get_context_files(_dir, stack=stack, task=task)
        content_map = build_context_content(_dir, files)

        parts: list[str] = [
            "# Session Context\n",
            "The following project context has been loaded from .agent/ protocol.\n",
        ]

        for rel_path, content in content_map.items():
            parts.append(f"## {rel_path}\n\n{content}\n")

        from cokodo_agent.relations import (
            load_collaborations,
            load_references,
            resolve_collaboration_content,
            resolve_reference_content,
        )

        project_root = _dir.parent

        refs = load_references(_dir)
        always_refs = [r for r in refs if r.use_when == "always"]
        collabs = load_collaborations(_dir)

        if always_refs or collabs:
            parts.append("\n---\n# Cross-Project Context (auto-loaded)\n")

            for ref in always_refs:
                try:
                    ref_content = resolve_reference_content(ref, project_root)
                    for rel_path, content in ref_content.items():
                        parts.append(f"## [ref:{ref.name}] {rel_path}\n\n{content}\n")
                except FileNotFoundError:
                    parts.append(f"## [ref:{ref.name}] (inaccessible)\n")

            for c in collabs:
                try:
                    collab_content = resolve_collaboration_content(c, project_root)
                    for rel_path, content in collab_content.items():
                        parts.append(
                            f"## [collab:{c.name}] {rel_path}\n\n{content}\n"
                        )
                except FileNotFoundError:
                    parts.append(f"## [collab:{c.name}] (inaccessible)\n")

        parts.append(
            "\n---\n"
            "You now have the full project context. "
            "After completing tasks, call update_status to record progress."
        )
        return "\n".join(parts)

    return mcp


def _find_agent_dir() -> Path:
    """Search for .agent/ directory from cwd upward."""
    current = Path.cwd()
    for parent in [current, *current.parents]:
        candidate = parent / ".agent"
        if candidate.is_dir() and (candidate / "manifest.json").exists():
            return candidate
    raise FileNotFoundError(
        "No .agent/ directory found. Run 'co init' to create one."
    )


# ---------------------------------------------------------------------------
# Workspace mode: multi-project discovery
# ---------------------------------------------------------------------------


def discover_agent_dirs(workspace_root: Path, max_depth: int = 2) -> dict[str, Path]:
    """Discover all .agent/ directories under workspace_root.

    Returns a dict mapping project name (directory name) -> .agent/ path.
    Only scans up to max_depth levels deep.
    """
    results: dict[str, Path] = {}

    def _scan(current: Path, depth: int) -> None:
        if depth > max_depth:
            return
        try:
            for child in sorted(current.iterdir()):
                if not child.is_dir():
                    continue
                if child.name.startswith(".") and child.name != ".agent":
                    continue
                if child.name in ("node_modules", "__pycache__", ".git", "venv", ".venv"):
                    continue
                agent_candidate = child / ".agent"
                if (
                    agent_candidate.is_dir()
                    and (agent_candidate / "manifest.json").exists()
                ):
                    project_name = child.name
                    results[project_name] = agent_candidate
                else:
                    _scan(child, depth + 1)
        except PermissionError:
            pass

    # Also check the workspace root itself
    root_agent = workspace_root / ".agent"
    if root_agent.is_dir() and (root_agent / "manifest.json").exists():
        results[workspace_root.name] = root_agent

    _scan(workspace_root, 0)
    return results


def create_workspace_server(
    workspace_root: Path,
    transport: str = "stdio",
) -> FastMCP:
    """Create a workspace-aware MCP server that exposes multiple projects.

    Discovers all .agent/ directories under workspace_root and provides
    tools to list projects and query context from any of them.
    """
    projects = discover_agent_dirs(workspace_root)
    _ws_root = workspace_root

    mcp = FastMCP(
        "cokodo-workspace",
        instructions=(
            "This server provides access to multiple AI Agent Collaboration Protocol "
            "projects in a workspace. Use list_workspace_projects first to discover "
            "available projects, then use workspace_get_context to load context "
            "from a specific project."
        ),
    )

    @mcp.tool()
    def list_workspace_projects() -> str:
        """List all projects discovered in the workspace.

        Returns project names, paths, and basic status.
        """
        if not projects:
            return "No projects with .agent/ found in workspace."

        lines: list[str] = [f"# Workspace: {_ws_root.name}\n"]
        for name, agent_path in sorted(projects.items()):
            project_root = agent_path.parent
            status_file = agent_path / "project" / "status.md"
            has_status = status_file.exists()
            lines.append(f"- **{name}**: {project_root}")
            lines.append(f"  status.md: {'yes' if has_status else 'no'}")

            from cokodo_agent.relations import load_collaborations, load_references

            refs = load_references(agent_path)
            collabs = load_collaborations(agent_path)
            if refs:
                lines.append(f"  references: {len(refs)}")
            if collabs:
                lines.append(f"  collaborations: {len(collabs)}")
            lines.append("")

        return "\n".join(lines)

    @mcp.tool()
    def workspace_get_context(
        project: str,
        stack: str | None = None,
        task: str | None = None,
    ) -> str:
        """Get context files from a specific project in the workspace.

        Args:
            project: Project name (from list_workspace_projects).
            stack: Tech stack filter (python/rust/qt/mixed). Optional.
            task: Task type or profile. Optional.

        Returns:
            Concatenated contents of all relevant .agent/ files.
        """
        if project not in projects:
            available = ", ".join(sorted(projects.keys()))
            return (
                f"Project '{project}' not found. "
                f"Available projects: {available}"
            )

        agent_dir = projects[project]
        files = get_context_files(agent_dir, stack=stack, task=task)
        if not files:
            return f"No context files found for project '{project}'."

        content_map = build_context_content(agent_dir, files)
        if not content_map:
            return f"No context files could be read for project '{project}'."

        parts: list[str] = [f"# Project: {project}\n"]
        for rel_path, content in content_map.items():
            parts.append(f"## {rel_path}\n\n{content}")
        return "\n---\n\n".join(parts)

    @mcp.tool()
    def workspace_get_status(project: str) -> str:
        """Get the current development status of a specific project.

        Args:
            project: Project name (from list_workspace_projects).

        Returns:
            Contents of project/status.md, or error message.
        """
        if project not in projects:
            available = ", ".join(sorted(projects.keys()))
            return f"Project '{project}' not found. Available: {available}"

        status_file = projects[project] / "project" / "status.md"
        if not status_file.exists():
            return f"No status.md found for project '{project}'."

        return status_file.read_text(encoding="utf-8")

    @mcp.tool()
    def workspace_read_file(project: str, path: str) -> str:
        """Read a file from a specific project's .agent/ directory.

        Args:
            project: Project name (from list_workspace_projects).
            path: Relative path within the project's .agent/ directory.

        Returns:
            File contents, or error message.
        """
        if project not in projects:
            available = ", ".join(sorted(projects.keys()))
            return f"Project '{project}' not found. Available: {available}"

        safe_path = Path(path)
        if ".." in safe_path.parts:
            return "Error: path traversal not allowed"

        full = projects[project] / safe_path
        if not full.exists():
            return f"File not found: {path} in project '{project}'"

        try:
            return full.read_text(encoding="utf-8")
        except OSError as e:
            return f"Error reading {path}: {e}"

    @mcp.tool()
    def workspace_list_relations(project: str | None = None) -> str:
        """List cross-project relationships for one or all workspace projects.

        Args:
            project: Specific project name. If omitted, shows all projects.

        Returns:
            Summary of references and collaborations.
        """
        target_projects = (
            {project: projects[project]}
            if project and project in projects
            else projects
        )

        if project and project not in projects:
            available = ", ".join(sorted(projects.keys()))
            return f"Project '{project}' not found. Available: {available}"

        from cokodo_agent.relations import load_collaborations, load_references

        lines: list[str] = []
        for name, agent_dir in sorted(target_projects.items()):
            refs = load_references(agent_dir)
            collabs = load_collaborations(agent_dir)
            if not refs and not collabs:
                continue

            lines.append(f"## {name}")
            for r in refs:
                lines.append(f"  ref: {r.name} -> {r.source}")
            for c in collabs:
                lines.append(f"  collab: {c.name} ({c.role}) -> {c.partner}")
            lines.append("")

        if not lines:
            return "No cross-project relationships found in workspace."

        return "\n".join(lines)

    @mcp.tool()
    def workspace_health_check() -> str:
        """Run health check on all projects and their relationships.

        Returns a workspace-wide summary of reference and collaboration health.
        """
        from cokodo_agent.relations import (
            check_collaborations,
            check_references,
            load_collaborations,
            load_references,
        )

        lines: list[str] = [f"# Workspace Health: {_ws_root.name}\n"]
        total_ok = 0
        total_fail = 0

        for name, agent_dir in sorted(projects.items()):
            project_root = agent_dir.parent
            refs = load_references(agent_dir)
            collabs = load_collaborations(agent_dir)

            if not refs and not collabs:
                lines.append(f"## {name}: no relationships")
                lines.append("")
                continue

            lines.append(f"## {name}")
            if refs:
                for st in check_references(agent_dir, project_root):
                    if st.accessible:
                        lines.append(f"  [OK] ref:{st.name}")
                        total_ok += 1
                    else:
                        lines.append(f"  [FAIL] ref:{st.name}: {st.error}")
                        total_fail += 1

            if collabs:
                for cst in check_collaborations(agent_dir, project_root):
                    if cst.partner_accessible:
                        sync = "in_sync" if cst.shared_files_in_sync else "drifted"
                        lines.append(f"  [OK] collab:{cst.name} ({sync})")
                        total_ok += 1
                    else:
                        lines.append(f"  [FAIL] collab:{cst.name}: {cst.error}")
                        total_fail += 1
            lines.append("")

        lines.append(
            f"\nTotal: {total_ok}/{total_ok + total_fail} relationships healthy "
            f"across {len(projects)} project(s)."
        )
        return "\n".join(lines)

    return mcp


def main(transport: str = "stdio") -> None:
    """Entry point for the MCP server."""
    server = create_server()
    server.run(transport=transport)
