mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
Add ListTools, search limit, and catalog size annotation to CodeMode (#3359)
* Add tests for two-stage pattern, empty full-detail results, empty inputs * Add ListTools, search limit, catalog size annotation; split tests Co-authored-by: Claude <noreply@anthropic.com> * Remove BM25 internal cap so Search.limit is the sole truncation point * Pass default_limit to BM25 instead of arbitrary high cap --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
49534ce91d
commit
404b820144
7 changed files with 704 additions and 264 deletions
|
|
@ -30,7 +30,7 @@ You take a normal server with normally registered tools and add a `CodeMode` tra
|
|||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
|
||||
mcp = FastMCP("Server", transforms=[CodeMode()])
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ This three-stage flow works well for most servers — each step pulls in only th
|
|||
|
||||
## Discovery Tools
|
||||
|
||||
CodeMode ships with three built-in discovery tools: `Search`, `GetSchemas`, and `GetTags`. By default, only `Search` and `GetSchemas` are enabled. Each tool supports a `default_detail` parameter that sets the default verbosity level, and the LLM can override the detail level on any individual call.
|
||||
CodeMode ships with four built-in discovery tools: `Search`, `GetSchemas`, `GetTags`, and `ListTools`. By default, only `Search` and `GetSchemas` are enabled. Each tool supports a `default_detail` parameter that sets the default verbosity level, and the LLM can override the detail level on any individual call.
|
||||
|
||||
### Detail Levels
|
||||
|
||||
|
|
@ -132,6 +132,14 @@ CodeMode ships with three built-in discovery tools: `Search`, `GetSchemas`, and
|
|||
|
||||
`Search` finds tools by natural-language query using BM25 ranking. At its default `"brief"` detail, results include just tool names and descriptions — enough to decide which tools are worth inspecting further. The LLM can request `"detailed"` to get parameter schemas inline, or `"full"` for the complete JSON.
|
||||
|
||||
Search results include an annotation like `"2 of 10 tools:"` when the result set is smaller than the full catalog, so the LLM knows there are more tools to discover with different queries.
|
||||
|
||||
You can cap result count with `default_limit`. The LLM can also override the limit per call. This is useful for large catalogs where you want to keep search results focused:
|
||||
|
||||
```python
|
||||
Search(default_limit=5) # return at most 5 results per search
|
||||
```
|
||||
|
||||
If your tools use [tags](/servers/tools#tags), Search also accepts a `tags` parameter so the LLM can narrow results to specific categories before searching.
|
||||
|
||||
### GetSchemas
|
||||
|
|
@ -150,6 +158,20 @@ If your tools use [tags](/servers/tools#tags), Search also accepts a `tags` para
|
|||
|
||||
`GetTags` isn't included in the defaults — add it when browsing by category would help the LLM orient itself in a large catalog. The LLM can browse tags first, then pass specific tags into Search to narrow results.
|
||||
|
||||
### ListTools
|
||||
|
||||
`ListTools` dumps the entire catalog at whatever detail level the LLM requests. It supports the same three detail levels as `Search` and `GetSchemas`, defaulting to `"brief"`.
|
||||
|
||||
`ListTools` isn't included in the defaults — for large catalogs, search-based discovery is more token-efficient. But for smaller catalogs (under ~20 tools), letting the LLM see everything upfront can be faster than multiple search round-trips:
|
||||
|
||||
```python
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode, ListTools, GetSchemas
|
||||
|
||||
code_mode = CodeMode(
|
||||
discovery_tools=[ListTools(), GetSchemas()],
|
||||
)
|
||||
```
|
||||
|
||||
## Discovery Patterns
|
||||
|
||||
The right discovery configuration depends on your server — how many tools you have and how complex their parameters are. It may be tempting to minimize round-trips by collapsing everything into fewer steps, but for the complex servers that benefit most from CodeMode, our experience is that staged discovery leads to better results. Flooding the LLM with detailed schemas for tools it doesn't end up using can hurt more than the extra round-trip costs. Each pattern below is a complete, copyable configuration.
|
||||
|
|
@ -160,7 +182,7 @@ The default. The LLM searches for candidates, inspects schemas for the ones it w
|
|||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
|
||||
mcp = FastMCP("Server", transforms=[CodeMode()])
|
||||
```
|
||||
|
|
@ -169,7 +191,7 @@ If your tools use [tags](/servers/tools#tags), add `GetTags` so the LLM can brow
|
|||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import GetTags, Search, GetSchemas
|
||||
|
||||
code_mode = CodeMode(
|
||||
|
|
@ -185,7 +207,7 @@ Search returns parameter schemas inline, so the LLM can go straight from search
|
|||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import Search, GetSchemas
|
||||
|
||||
code_mode = CodeMode(
|
||||
|
|
@ -203,7 +225,7 @@ Skip discovery entirely and bake tool instructions into the execute tool's descr
|
|||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
|
||||
code_mode = CodeMode(
|
||||
discovery_tools=[],
|
||||
|
|
@ -225,7 +247,7 @@ Discovery tools are composable — you can mix the built-ins with your own. Each
|
|||
Here's a minimal example:
|
||||
|
||||
```python
|
||||
from fastmcp.experimental.transforms import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import GetToolCatalog, GetSchemas
|
||||
from fastmcp.server.context import Context
|
||||
from fastmcp.tools.tool import Tool
|
||||
|
|
@ -266,7 +288,8 @@ mcp = FastMCP("Server", transforms=[code_mode])
|
|||
The default `MontySandboxProvider` can enforce execution limits — timeouts, memory caps, recursion depth, and more. Without limits, LLM-generated scripts can run indefinitely.
|
||||
|
||||
```python
|
||||
from fastmcp.experimental.transforms import CodeMode, MontySandboxProvider
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import MontySandboxProvider
|
||||
|
||||
sandbox = MontySandboxProvider(
|
||||
limits={"max_duration_secs": 10, "max_memory": 50_000_000},
|
||||
|
|
@ -293,7 +316,8 @@ You can replace the default sandbox with any object implementing the `SandboxPro
|
|||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from fastmcp.experimental.transforms import CodeMode, SandboxProvider
|
||||
from fastmcp.experimental.transforms.code_mode import CodeMode
|
||||
from fastmcp.experimental.transforms.code_mode import SandboxProvider
|
||||
|
||||
class RemoteSandboxProvider:
|
||||
async def run(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue