Add standalone decorators and eliminate fastmcp.fs module (#2832)

This commit is contained in:
Jeremiah Lowin 2026-01-10 12:16:35 -05:00 committed by GitHub
commit daa2dace2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1280 additions and 1123 deletions

View file

@ -350,15 +350,6 @@
"python-sdk/fastmcp-client-transports"
]
},
{
"group": "fastmcp.fs",
"pages": [
"python-sdk/fastmcp-fs-__init__",
"python-sdk/fastmcp-fs-decorators",
"python-sdk/fastmcp-fs-discovery",
"python-sdk/fastmcp-fs-provider"
]
},
{
"group": "fastmcp.prompts",
"pages": [

View file

@ -28,16 +28,15 @@ Transformation is also powerful for **environment-aware tools**. You can dynamic
The primary way to create a transformed tool is with the `Tool.from_tool()` class method. At its simplest, you can use it to change a tool's top-level metadata like its `name`, `description`, or `tags`.
In the following simple example, we take a generic `search` tool and adjust its name and description to help an LLM client better understand its purpose.
In the following example, we take a generic `search` tool and adjust its name and description to help an LLM client better understand its purpose.
```python {13-21}
```python {1, 6, 11-19, 22}
from fastmcp.tools import tool, Tool
from fastmcp import FastMCP
from fastmcp.tools import Tool
mcp = FastMCP()
# The original, generic tool
@mcp.tool
# Create a tool without registering it using the standalone @tool decorator
# This creates a Tool object that can be transformed before registration
@tool
def search(query: str, category: str = "all") -> list[dict]:
"""Searches for items in the database."""
return database.search(query, category)
@ -47,39 +46,25 @@ product_search_tool = Tool.from_tool(
search,
name="find_products",
description="""
Search for products in the e-commerce catalog.
Use this when customers ask about finding specific items,
Search for products in the e-commerce catalog.
Use this when customers ask about finding specific items,
checking availability, or browsing product categories.
""",
)
# Only register the transformed version
mcp = FastMCP()
mcp.add_tool(product_search_tool)
```
<Tip>
When you transform a tool, the original tool remains registered on the server. To avoid confusing an LLM with two similar tools, you can disable the original one:
```python
from fastmcp import FastMCP
from fastmcp.tools import Tool
mcp = FastMCP()
# The original, generic tool
@mcp.tool
def search(query: str, category: str = "all") -> list[dict]:
...
# Create a more domain-specific version
product_search_tool = Tool.from_tool(search, ...)
mcp.add_tool(product_search_tool)
# Disable the original tool
search.disable()
```
The standalone `@tool` decorator (from `fastmcp.tools`) creates a Tool object without registering it to any server. This is the recommended approach for tool transformation because:
- You only register the tools you want exposed
- No need to disable or remove the original
- Cleaner separation between tool creation and registration
</Tip>
Now, clients see a tool named `find_products` with a clear, domain-specific purpose and relevant tags, even though it still uses the original generic `search` function's logic.
Now, clients see a tool named `find_products` with a clear, domain-specific purpose, even though it still uses the original generic `search` function's logic.
### Parameters

View file

@ -15,7 +15,7 @@ import { VersionBadge } from '/snippets/version-badge.mdx'
Traditional FastMCP servers require coordination between files. Either your tool files import the server to call `@server.tool()`, or your server file imports all the tool modules. Both approaches create coupling that some developers prefer to avoid.
`FileSystemProvider` eliminates this coordination. Each file is self-contained—it uses decorators from `fastmcp.fs` that don't require access to a server instance. The provider discovers these files at startup, so you can add new tools without modifying your server file.
`FileSystemProvider` eliminates this coordination. Each file is self-contained—it uses standalone decorators (`@tool`, `@resource`, `@prompt`) that don't require access to a server instance. The provider discovers these files at startup, so you can add new tools without modifying your server file.
This is a convention some teams prefer, not necessarily better for all projects. The tradeoffs:
@ -25,20 +25,22 @@ This is a convention some teams prefer, not necessarily better for all projects.
## Quick Start
Create a provider pointing to your components directory, then pass it to your server.
Create a provider pointing to your components directory, then pass it to your server. Use `Path(__file__).parent` to make the path relative to your server file.
```python
from fastmcp import FastMCP
from fastmcp.fs import FileSystemProvider
from pathlib import Path
mcp = FastMCP("MyServer", providers=[FileSystemProvider("mcp/")])
from fastmcp import FastMCP
from fastmcp.server.providers import FileSystemProvider
mcp = FastMCP("MyServer", providers=[FileSystemProvider(Path(__file__).parent / "mcp")])
```
In your `mcp/` directory, create Python files with decorated functions.
```python
# mcp/tools/greet.py
from fastmcp.fs import tool
from fastmcp.tools import tool
@tool
def greet(name: str) -> str:
@ -50,14 +52,14 @@ When the server starts, `FileSystemProvider` scans the directory, imports all Py
## Decorators
The `fastmcp.fs` module provides three decorators that mark functions for discovery: `@tool`, `@resource`, and `@prompt`. These support the full syntax of standard FastMCP decorators—all the same parameters work identically.
FastMCP provides standalone decorators that mark functions for discovery: `@tool` from `fastmcp.tools`, `@resource` from `fastmcp.resources`, and `@prompt` from `fastmcp.prompts`. These support the full syntax of server-bound decorators—all the same parameters work identically.
### @tool
Mark a function as a tool. The function name becomes the tool name by default.
```python
from fastmcp.fs import tool
from fastmcp.tools import tool
@tool
def calculate_sum(a: float, b: float) -> float:
@ -68,7 +70,7 @@ def calculate_sum(a: float, b: float) -> float:
Customize the tool with optional parameters.
```python
from fastmcp.fs import tool
from fastmcp.tools import tool
@tool(
name="add-numbers",
@ -86,7 +88,7 @@ The decorator supports all standard tool options: `name`, `title`, `description`
Mark a function as a resource. Unlike `@tool`, the `@resource` decorator requires a URI argument.
```python
from fastmcp.fs import resource
from fastmcp.resources import resource
@resource("config://app")
def get_app_config() -> str:
@ -97,7 +99,7 @@ def get_app_config() -> str:
URIs with template parameters create resource templates. The provider automatically detects whether to register a static resource or a template based on whether the URI contains `{parameters}` or the function has arguments.
```python
from fastmcp.fs import resource
from fastmcp.resources import resource
@resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
@ -112,7 +114,7 @@ The decorator supports: `uri` (required), `name`, `title`, `description`, `icons
Mark a function as a prompt template.
```python
from fastmcp.fs import prompt
from fastmcp.prompts import prompt
@prompt
def code_review(code: str, language: str = "python") -> str:
@ -121,7 +123,7 @@ def code_review(code: str, language: str = "python") -> str:
```
```python
from fastmcp.fs import prompt
from fastmcp.prompts import prompt
@prompt(name="explain-concept", tags={"education"})
def explain(topic: str) -> str:
@ -190,9 +192,11 @@ Without `__init__.py`, files are imported directly using `importlib.util.spec_fr
During development, you may want changes to component files to take effect without restarting the server. Enable reload mode to re-scan the directory on every request.
```python
from fastmcp.fs import FileSystemProvider
from pathlib import Path
provider = FileSystemProvider("mcp/", reload=True)
from fastmcp.server.providers import FileSystemProvider
provider = FileSystemProvider(Path(__file__).parent / "mcp", reload=True)
```
With `reload=True`, the provider:
@ -238,7 +242,7 @@ The server entry point is minimal.
from pathlib import Path
from fastmcp import FastMCP
from fastmcp.fs import FileSystemProvider
from fastmcp.server.providers import FileSystemProvider
provider = FileSystemProvider(
root=Path(__file__).parent / "mcp",