fastmcp/examples/search/server_regex.py
Jeremiah Lowin c96c0400f3
feat: Search transforms for tool discovery (#3154)
* feat: Add search transforms for tool discovery

RegexSearchTransform and BM25SearchTransform collapse large tool
catalogs into a search interface so LLMs discover tools on demand
instead of receiving the full listing.

* chore: Update SDK documentation

* fix: call_tool recursion guard, atomic BM25 rebuild, hash includes descriptions

* Extract CatalogTransform base class for catalog-aware transforms

Transforms that replace list_tools() with synthetic components (like
search) need to read the real catalog at call time without triggering
their own replacement logic. CatalogTransform handles the re-entrant
bypass via per-instance ContextVar, exposing transform_tools() as the
subclass hook and get_tool_catalog() for catalog access.

* Add search transform examples for regex and BM25

* Add README for search transform examples

* Polish search example clients with rich output

* Remove hardcoded tool counts from search example subtitles

* Clarify that review bot feedback should be evaluated on its merits

* Expand search transform docs with proper hierarchy

---------

Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>
2026-02-26 22:42:38 -05:00

74 lines
1.8 KiB
Python

"""Example: Search transforms with regex pattern matching.
When a server has many tools, listing them all at once can overwhelm an LLM's
context window. Search transforms collapse the full tool catalog behind a
search interface — clients see only `search_tools` and `call_tool`, and
discover the real tools on demand.
This example registers a handful of tools and applies RegexSearchTransform.
Clients use `search_tools` with a regex pattern to find relevant tools, then
`call_tool` to execute them by name.
Run with:
uv run python examples/search/server_regex.py
"""
from fastmcp import FastMCP
from fastmcp.server.transforms.search import RegexSearchTransform
mcp = FastMCP("Regex Search Demo")
# Register a variety of tools across different domains.
# With the search transform active, none of these appear in list_tools —
# they're only discoverable via search.
@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()
# Apply the regex search transform.
# max_results limits how many tools a single search returns.
mcp.add_transform(RegexSearchTransform(max_results=3))
if __name__ == "__main__":
mcp.run()