---
schema: rendered-html/v1
title: "Tool Search - FastMCP"
source_url: https://gofastmcp.com/servers/transforms/tool-search
generated_at: 2026-06-11T19:36:17.038563+00:00
---




[FastMCP home page![light logo](images/gofastmcp__com__servers__transforms__tool-search__wordmark.avif)![dark logo](images/gofastmcp__com__servers__transforms__tool-search__wordmark-white.avif)](gofastmcp__com.md)







Working with Tools

Tool Search









  - [Overview](gofastmcp__com__servers__transforms__transforms.md)
  - [Tool Transformation](gofastmcp__com__servers__transforms__tool-transformation.md)
  - [Code Mode

    NEW](gofastmcp__com__servers__transforms__code-mode.md)
  - [Tool Search

    NEW](/servers/transforms/tool-search)
  - [Namespace](gofastmcp__com__servers__transforms__namespace.md)
  - [Visibility

    NEW](gofastmcp__com__servers__visibility.md)
  - [Resources as Tools

    NEW](gofastmcp__com__servers__transforms__resources-as-tools.md)
  - [Prompts as Tools

    NEW](gofastmcp__com__servers__transforms__prompts-as-tools.md)
  - [Tool Fingerprinting](gofastmcp__com__servers__tool-fingerprinting.md)














- [How It Works](#how-it-works)
- [Search Strategies](#search-strategies)
  - [Regex Search](#regex-search)
  - [BM25 Search](#bm25-search)
  - [Which to Choose](#which-to-choose)
- [Configuration](#configuration)
  - [Limiting Results](#limiting-results)
  - [Pinning Tools](#pinning-tools)
  - [Custom Tool Names](#custom-tool-names)
- [The call\_tool Proxy](#the-call_tool-proxy)
- [Auth and Visibility](#auth-and-visibility)

Working with Tools

# Tool Search


Replace large tool catalogs with on-demand search


New in version `3.1.0`
When a server exposes hundreds or thousands of tools, sending the full catalog to an LLM wastes tokens and degrades tool selection accuracy. Search transforms solve this by replacing the tool listing with a search interface — the LLM discovers tools on demand instead of receiving everything upfront.

## [​](#how-it-works) How It Works

When you add a search transform, `list_tools()` returns just two synthetic tools instead of the full catalog:

- **`search_tools`** finds tools matching a query and returns their full definitions
- **`call_tool`** executes a discovered tool by name

The original tools are still callable. They’re hidden from the listing but remain fully functional — the search transform controls *discovery*, not *access*.
Both synthetic tools search across tool names, descriptions, parameter names, and parameter descriptions. A search for `"email"` would match a tool named `send_email`, a tool with “email” in its description, or a tool with an `email_address` parameter.
Search results are returned in the same JSON format as `list_tools`, including the full input schema, so the LLM can construct valid calls immediately without a second round-trip.

## [​](#search-strategies) Search Strategies

FastMCP provides two search transforms. They share the same interface — two synthetic tools, same configuration options — but differ in how they match queries to tools.

### [​](#regex-search) Regex Search

`RegexSearchTransform` matches tools against a regex pattern using case-insensitive `re.search`. It has zero overhead and no index to build, making it a good default when the LLM knows roughly what it’s looking for.

from fastmcp.server.transforms.search import RegexSearchTransform

mcp = FastMCP("My Server", transforms=[RegexSearchTransform()])

def search_database(query: str, limit: int = 10) -> list[dict]:
    """Search the database for records matching the query."""
    ...

def delete_record(record_id: str) -> bool:
    """Delete a record from the database by its ID."""
    ...

def send_email(to: str, subject: str, body: str) -> bool:
    """Send an email to the given recipient."""
    ...

The LLM’s `search_tools` call takes a `pattern` parameter — a regex string:

# Exact substring match
result = await client.call_tool("search_tools", {"pattern": "database"})
# Returns: search_database, delete_record

# Regex pattern
result = await client.call_tool("search_tools", {"pattern": "send.*email|notify"})
# Returns: send_email

Results are returned in catalog order. If the pattern is invalid regex, the search returns an empty list rather than raising an error.

### [​](#bm25-search) BM25 Search

`BM25SearchTransform` ranks tools by relevance using the [BM25 Okapi](https://en.wikipedia.org/wiki/Okapi_BM25) algorithm. It’s better for natural language queries because it scores each tool based on term frequency and document rarity, returning results ranked by relevance rather than filtering by match/no-match.

from fastmcp.server.transforms.search import BM25SearchTransform

mcp = FastMCP("My Server", transforms=[BM25SearchTransform()])

# ... define tools ...

The LLM’s `search_tools` call takes a `query` parameter — natural language:

result = await client.call_tool("search_tools", {
    "query": "tools for deleting things from the database"
})
# Returns: delete_record ranked first, search_database second

BM25 builds an in-memory index from the searchable text of all tools. The index is created lazily on the first search and automatically rebuilt whenever the tool catalog changes — for example, when tools are added, removed, or have their descriptions updated. The staleness check is based on a hash of all searchable text, so description changes are detected even when tool names stay the same.

### [​](#which-to-choose) Which to Choose

Use **regex** when your LLM is good at constructing targeted patterns and you want deterministic, predictable results. Regex is also simpler to debug — you can see exactly what pattern was sent.
Use **BM25** when your LLM tends to describe what it needs in natural language, or when your tool catalog has nuanced descriptions where relevance ranking adds value. BM25 handles partial matches and synonyms better because it scores on individual terms rather than requiring a single pattern to match.

## [​](#configuration) Configuration

Both search transforms accept the same configuration options.

### [​](#limiting-results) Limiting Results

By default, search returns at most 5 tools. Adjust `max_results` based on your catalog size and how much context you want the LLM to receive per search:

mcp.add_transform(RegexSearchTransform(max_results=10))
mcp.add_transform(BM25SearchTransform(max_results=3))

With regex, results stop as soon as the limit is reached (first N matches in catalog order). With BM25, all tools are scored and the top N by relevance are returned.

### [​](#pinning-tools) Pinning Tools

Some tools should always be visible regardless of search. Use `always_visible` to pin them in the listing alongside the synthetic tools:

mcp.add_transform(RegexSearchTransform(
    always_visible=["help", "status"],
))

# list_tools returns: help, status, search_tools, call_tool

Pinned tools appear directly in `list_tools` so the LLM can call them without searching. They’re excluded from search results to avoid duplication.

### [​](#custom-tool-names) Custom Tool Names

The default names `search_tools` and `call_tool` can be changed to avoid conflicts with real tools:

mcp.add_transform(RegexSearchTransform(
    search_tool_name="find_tools",
    call_tool_name="run_tool",
))

## [​](#the-call_tool-proxy) The `call_tool` Proxy

The `call_tool` proxy forwards calls to the real tool. When a client calls `call_tool(name="search_database", arguments={...})`, the proxy resolves `search_database` through the server’s normal tool pipeline — including transforms and middleware — and executes it.
The proxy rejects attempts to call the synthetic tools themselves. `call_tool(name="call_tool")` raises an error rather than recursing.

Tools discovered through search can also be called directly via `client.call_tool("search_database", {...})` without going through the proxy. The proxy exists for LLMs that only know about the tools returned by `list_tools` and need a way to invoke discovered tools through a tool they can see.

## [​](#auth-and-visibility) Auth and Visibility

Search results respect the full authorization pipeline. Tools filtered by middleware, visibility transforms, or component-level auth checks won’t appear in search results.
The search tool queries `list_tools()` through the complete pipeline at search time, so the same filtering that controls what a client sees in the listing also controls what they can discover through search.

from fastmcp.server.transforms import Visibility
from fastmcp.server.transforms.search import RegexSearchTransform

mcp = FastMCP("My Server")

# ... define tools ...

# Disable admin tools globally
mcp.add_transform(Visibility(False, tags={"admin"}))

# Add search — admin tools won't appear in results
mcp.add_transform(RegexSearchTransform())

Session-level visibility changes (via `ctx.disable_components()`) are also reflected immediately in search results.

[Code Mode

Previous](gofastmcp__com__servers__transforms__code-mode.md)[Namespace Transform

Next](gofastmcp__com__servers__transforms__namespace.md)






