# File: src/mcp/server/fastmcp.py
# Purpose: Provide a lightweight MCP server facade for tests and local stubs.
# Context: Used by integration tests that expect decorator-based tool
#          registration without requiring the full production server stack.

"""Lightweight MCP server facade for tests and local experimentation.

Purpose:
    Offer a minimal implementation of the MCP server interface that supports
    tool registration and stubbed stdio execution during testing.
Context:
    Imported by test helpers and local scripts when the production MCP server
    infrastructure is either unavailable or unnecessary.
Side Effects:
    None.
"""

from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass
from typing import Any

ToolCallable = Callable[..., Any]
"""Callable type accepted as MCP tool handler (sync or async)."""


@dataclass(slots=True)
class RegisteredTool:
    """Capture metadata for a tool registered with FastMCP.

    Purpose:
        Persist handler information for lookup during tool invocation.

    Args:
        name: Public name exposed through the MCP API.
        fn: Callable implementing the tool behaviour.
        description: Optional summary presented to clients.

    Returns:
        None.
    Side Effects:
        None.
    """

    name: str
    fn: ToolCallable
    description: str | None = None


class FastMCP:  # pragma: no cover - minimal test stub
    """Store tool handlers and expose a stubbed stdio runner.

    Purpose:
        Manage an in-memory registry of MCP tools for tests.

    Args:
        name: Identifier for the MCP server instance.
        instructions: Optional server instructions advertised to clients.
    Side Effects:
        None.
    """

    def __init__(self, name: str, instructions: str | None = None) -> None:
        """Initialise the in-memory registry for MCP tools.

        Purpose:
            Capture server metadata and prepare the tool mapping.

        Args:
            name: Identifier for the MCP server instance.
            instructions: Optional descriptive text for the server.
        Side Effects:
            None.
        """

        self.name = name
        self.instructions = instructions or ""
        self._tools: dict[str, RegisteredTool] = {}

    def tool(
        self, name: str, description: str | None = None
    ) -> Callable[[ToolCallable], ToolCallable]:
        """Register a callable as an MCP tool decorator-style.

        Purpose:
            Allow tests to register tool handlers using the production API.

        Args:
            name: Public name exposed to MCP clients.
            description: Optional human-readable description for the tool.

        Returns:
            Callable: A decorator that stores the tool in the registry.
        Side Effects:
            Mutates the internal `_tools` dictionary.
        """

        def deco(fn: ToolCallable) -> ToolCallable:
            """Store the provided function as the tool handler.

            Purpose:
                Capture the decorated function in the tool registry.

            Args:
                fn: Function implementing the tool behaviour.

            Returns:
                Callable: The original function, unchanged.
            Side Effects:
                Updates the `_tools` dictionary with the provided handler.
            """

            self._tools[name] = RegisteredTool(name=name, fn=fn, description=description)
            return fn

        return deco

    async def run_stdio_async(self) -> None:
        """Simulate async stdio handling without performing I/O.

        Purpose:
            Provide an awaitable stub compatible with production call sites.

        Returns:
            None.
        Side Effects:
            None.
        """

        return
