"""Example: Search transforms with BM25 relevance ranking.

BM25SearchTransform uses term-frequency/inverse-document-frequency scoring
to rank tools by relevance to a natural language query. Unlike regex search
(which requires the user to construct a pattern), BM25 handles queries like
"work with text" or "do math" and returns the most relevant matches.

The index is built lazily and rebuilt automatically when the tool catalog
changes (e.g. tools added or removed between requests).

Run with:
    uv run python examples/search/server_bm25.py
"""

import os

from fastmcp import FastMCP
from fastmcp.server.transforms.search import BM25SearchTransform

mcp = FastMCP("BM25 Search Demo")


@mcp.tool
def add(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b


@mcp.tool
def multiply(x: float, y: float) -> float:
    """Multiply two numbers."""
    return x * y


@mcp.tool
def fibonacci(n: int) -> list[int]:
    """Generate the first n Fibonacci numbers."""
    if n <= 0:
        return []
    seq = [0, 1]
    while len(seq) < n:
        seq.append(seq[-1] + seq[-2])
    return seq[:n]


@mcp.tool
def reverse_string(text: str) -> str:
    """Reverse a string."""
    return text[::-1]


@mcp.tool
def word_count(text: str) -> int:
    """Count the number of words in a text."""
    return len(text.split())


@mcp.tool
def to_uppercase(text: str) -> str:
    """Convert text to uppercase."""
    return text.upper()


@mcp.tool
def list_files(directory: str) -> list[str]:
    """List files in a directory."""
    return os.listdir(directory)


@mcp.tool
def read_file(path: str) -> str:
    """Read the contents of a file."""
    with open(path) as f:
        return f.read()


# BM25 search with a higher result limit for this larger catalog.
# The `always_visible` option keeps specific tools in list_tools output
# alongside the search/call tools — useful for tools the LLM should
# always know about.
mcp.add_transform(BM25SearchTransform(max_results=5, always_visible=["list_files"]))


if __name__ == "__main__":
    mcp.run()
