chore: import upstream snapshot with attribution
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:39:59 +08:00
commit 60e0ffc959
1282 changed files with 294901 additions and 0 deletions
+577
View File
@@ -0,0 +1,577 @@
---
title: FastMCP CLI
sidebarTitle: CLI
description: Learn how to use the FastMCP command-line interface
icon: terminal
---
import { VersionBadge } from "/snippets/version-badge.mdx"
FastMCP provides a command-line interface (CLI) that makes it easy to run, develop, and install your MCP servers. The CLI is automatically installed when you install FastMCP.
```bash
fastmcp --help
```
## Commands Overview
| Command | Purpose | Dependency Management |
| ------- | ------- | --------------------- |
| `run` | Run a FastMCP server directly | **Supports:** Local files, factory functions, URLs, fastmcp.json configs, MCP configs. **Deps:** Uses your local environment directly. With `--python`, `--with`, `--project`, or `--with-requirements`: Runs via `uv run` subprocess. With fastmcp.json: Automatically manages dependencies based on configuration |
| `dev` | Run a server with the MCP Inspector for testing | **Supports:** Local files and fastmcp.json configs. **Deps:** Always runs via `uv run` subprocess (never uses your local environment); dependencies must be specified or available in a uv-managed project. With fastmcp.json: Uses configured dependencies |
| `install` | Install a server in MCP client applications | **Supports:** Local files and fastmcp.json configs. **Deps:** Creates an isolated environment; dependencies must be explicitly specified with `--with` and/or `--with-editable`. With fastmcp.json: Uses configured dependencies |
| `inspect` | Generate a JSON report about a FastMCP server | **Supports:** Local files and fastmcp.json configs. **Deps:** Uses your current environment; you are responsible for ensuring all dependencies are available |
| `project prepare` | Create a persistent uv project from fastmcp.json environment config | **Supports:** fastmcp.json configs only. **Deps:** Creates a uv project directory with all dependencies pre-installed for reuse with `--project` flag |
| `version` | Display version information | N/A |
## `fastmcp run`
Run a FastMCP server directly or proxy a remote server.
```bash
fastmcp run server.py
```
<Tip>
By default, this command runs the server directly in your current Python environment. You are responsible for ensuring all dependencies are available. When using `--python`, `--with`, `--project`, or `--with-requirements` options, it runs the server via `uv run` subprocess instead.
</Tip>
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Transport | `--transport`, `-t` | Transport protocol to use (`stdio`, `http`, or `sse`) |
| Host | `--host` | Host to bind to when using http transport (default: 127.0.0.1) |
| Port | `--port`, `-p` | Port to bind to when using http transport (default: 8000) |
| Path | `--path` | Path to bind to when using http transport (default: `/mcp/` or `/sse/` for SSE) |
| Log Level | `--log-level`, `-l` | Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| No Banner | `--no-banner` | Disable the startup banner display |
| No Environment | `--skip-env` | Skip environment setup with uv (use when already in a uv environment) |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
### Entrypoints
<VersionBadge version="2.3.5" />
The `fastmcp run` command supports the following entrypoints:
1. **[Inferred server instance](#inferred-server-instance)**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **[Explicit server entrypoint](#explicit-server-entrypoint)**: `server.py:custom_name` - imports and uses the specified server entrypoint
3. **[Factory function](#factory-function)**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
4. **[Remote server proxy](#remote-server-proxy)**: `https://example.com/mcp-server` - connects to a remote server and creates a **local proxy server**
5. **[FastMCP configuration file](#fastmcp-configuration)**: `fastmcp.json` - runs servers using FastMCP's declarative configuration format (auto-detects files in current directory)
6. **MCP configuration file**: `mcp.json` - runs servers defined in a standard MCP configuration file
<Warning>
Note: When using `fastmcp run` with a local file, it **completely ignores** the `if __name__ == "__main__"` block. This means:
- Any setup code in `__main__` will NOT run
- Server configuration in `__main__` is bypassed
- `fastmcp run` finds your server entrypoint/factory and runs it with its own transport settings
If you need setup code to run, use the **factory pattern** instead.
</Warning>
#### Inferred Server Instance
If you provide a path to a file, `fastmcp run` will load the file and look for a FastMCP server instance stored as a variable named `mcp`, `server`, or `app`. If no such object is found, it will raise an error.
For example, if you have a file called `server.py` with the following content:
```python server.py
from fastmcp import FastMCP
mcp = FastMCP("MyServer")
```
You can run it with:
```bash
fastmcp run server.py
```
#### Explicit Server Entrypoint
If your server is stored as a variable with a custom name, or you want to be explicit about which server to run, you can use the following syntax to load a specific server entrypoint:
```bash
fastmcp run server.py:custom_name
```
For example, if you have a file called `server.py` with the following content:
```python
from fastmcp import FastMCP
my_server = FastMCP("CustomServer")
@my_server.tool
def hello() -> str:
return "Hello from custom server!"
```
You can run it with:
```bash
fastmcp run server.py:my_server
```
#### Factory Function
<VersionBadge version="2.11.2" />
Since `fastmcp run` ignores the `if __name__ == "__main__"` block, you can use a factory function to run setup code before your server starts. Factory functions are called without any arguments and must return a FastMCP server instance. Both sync and async factory functions are supported.
The syntax for using a factory function is the same as for an explicit server entrypoint: `fastmcp run server.py:factory_fn`. FastMCP will automatically detect that you have identified a function rather than a server Instance
For example, if you have a file called `server.py` with the following content:
```python
from fastmcp import FastMCP
async def create_server() -> FastMCP:
mcp = FastMCP("MyServer")
@mcp.tool
def add(x: int, y: int) -> int:
return x + y
# Setup that runs with fastmcp run
tool = await mcp.get_tool("add")
tool.disable()
return mcp
```
You can run it with:
```bash
fastmcp run server.py:create_server
```
#### Remote Server Proxy
FastMCP run can also start a local proxy server that connects to a remote server. This is useful when you want to run a remote server locally for testing or development purposes, or to use with a client that doesn't support direct connections to remote servers.
To start a local proxy, you can use the following syntax:
```bash
fastmcp run https://example.com/mcp
```
#### FastMCP Configuration
<VersionBadge version="2.11.4" />
FastMCP supports declarative configuration through `fastmcp.json` files. When you run `fastmcp run` without arguments, it automatically looks for a `fastmcp.json` file in the current directory:
```bash
# Auto-detect fastmcp.json in current directory
fastmcp run
# Or explicitly specify a configuration file
fastmcp run my-config.fastmcp.json
```
The configuration file handles dependencies, environment variables, and transport settings. Command-line arguments override configuration file values:
```bash
# Override port from config file
fastmcp run fastmcp.json --port 8080
# Skip environment setup when already in a uv environment
fastmcp run fastmcp.json --skip-env
```
<Note>
The `--skip-env` flag is useful when:
- You're already in an activated virtual environment
- You're inside a Docker container with pre-installed dependencies
- You're in a uv-managed environment (prevents infinite recursion)
- You want to test the server without environment setup
</Note>
See [Server Configuration](/v2/deployment/server-configuration) for detailed documentation on fastmcp.json.
#### MCP Configuration
FastMCP can also run servers defined in a standard MCP configuration file. This is useful when you want to run multiple servers from a single file, or when you want to use a client that doesn't support direct connections to remote servers.
To run a MCP configuration file, you can use the following syntax:
```bash
fastmcp run mcp.json
```
This will run all the servers defined in the file.
## `fastmcp dev`
Run a MCP server with the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) for testing.
```bash
fastmcp dev server.py
```
<Tip>
This command always runs your server via `uv run` subprocess (never your local environment) to work with the MCP Inspector. Dependencies can be:
- Specified using `--with` and/or `--with-editable` options
- Defined in a `fastmcp.json` configuration file
- Available in a uv-managed project
When using `fastmcp.json`, the dev command automatically uses the configured dependencies.
</Tip>
<Warning>
The `dev` command is a shortcut for testing a server over STDIO only. When the Inspector launches, you may need to:
1. Select "STDIO" from the transport dropdown
2. Connect manually
This command does not support HTTP testing. To test a server over Streamable HTTP or SSE:
1. Start your server manually with the appropriate transport using either the command line:
```bash
fastmcp run server.py --transport http
```
or by setting the transport in your code:
```bash
python server.py # Assuming your __main__ block sets Streamable HTTP transport
```
2. Open the MCP Inspector separately and connect to your running server
</Warning>
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Editable Package | `--with-editable`, `-e` | Directory containing pyproject.toml to install in editable mode |
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Inspector Version | `--inspector-version` | Version of the MCP Inspector to use |
| UI Port | `--ui-port` | Port for the MCP Inspector UI |
| Server Port | `--server-port` | Port for the MCP Inspector Proxy server |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
### Entrypoints
The `dev` command supports local FastMCP server files and configuration:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration (auto-detects in current directory)
<Warning>
The `dev` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files.
</Warning>
**Examples**
```bash
# Run dev server with editable mode and additional packages
fastmcp dev server.py -e . --with pandas --with matplotlib
# Run dev server with fastmcp.json configuration (auto-detects)
fastmcp dev
# Run dev server with explicit fastmcp.json file
fastmcp dev dev.fastmcp.json
# Run dev server with specific Python version
fastmcp dev server.py --python 3.11
# Run dev server with requirements file
fastmcp dev server.py --with-requirements requirements.txt
# Run dev server within a specific project directory
fastmcp dev server.py --project /path/to/project
```
## `fastmcp install`
<VersionBadge version="2.10.3" />
Install a MCP server in MCP client applications. FastMCP currently supports the following clients:
- **Claude Code** - Installs via Claude Code's built-in MCP management system
- **Claude Desktop** - Installs via direct configuration file modification
- **Cursor** - Installs via deeplink that opens Cursor for user confirmation
- **MCP JSON** - Generates standard MCP JSON configuration for manual use
```bash
fastmcp install claude-code server.py
fastmcp install claude-desktop server.py
fastmcp install cursor server.py
fastmcp install mcp-json server.py
```
Note that for security reasons, MCP clients usually run every server in a completely isolated environment. Therefore, all dependencies must be explicitly specified using the `--with` and/or `--with-editable` options (following `uv` conventions) or by attaching them to your server in code via the `dependencies` parameter. You should not assume that the MCP server will have access to your local environment.
<Warning>
**`uv` must be installed and available in your system PATH**. Both Claude Desktop and Cursor run in isolated environments and need `uv` to manage dependencies. On macOS, install `uv` globally with Homebrew for Claude Desktop compatibility: `brew install uv`.
</Warning>
<Note>
**Python Version Considerations**: The install commands now support the `--python` option to specify a Python version directly. You can also use `--project` to run within a specific project directory or `--with-requirements` to install dependencies from a requirements file.
</Note>
<Tip>
**FastMCP `install` commands focus on local server files with STDIO transport.** For remote servers running with HTTP or SSE transport, use your client's native configuration - FastMCP's value is simplifying the complex local setup with dependencies and `uv` commands.
</Tip>
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Server Name | `--server-name`, `-n` | Custom name for the server (defaults to server's name attribute or file name) |
| Editable Package | `--with-editable`, `-e` | Directory containing pyproject.toml to install in editable mode |
| Additional Packages | `--with` | Additional packages to install (can be used multiple times) |
| Environment Variables | `--env` | Environment variables in KEY=VALUE format (can be used multiple times) |
| Environment File | `--env-file`, `-f` | Load environment variables from a .env file |
| Python Version | `--python` | Python version to use (e.g., 3.10, 3.11) |
| Project Directory | `--project` | Run the command within the given project directory |
| Requirements File | `--with-requirements` | Requirements file to install dependencies from |
### Entrypoints
The `install` command supports local FastMCP server files and configuration:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
4. **FastMCP configuration**: `fastmcp.json` - uses FastMCP's declarative configuration with dependencies and settings
<Note>
Factory functions are particularly useful for install commands since they allow setup code to run that would otherwise be ignored when the MCP client runs your server. When using fastmcp.json, dependencies are automatically handled.
</Note>
<Warning>
The `install` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files. For remote servers, use your MCP client's native configuration.
</Warning>
**Examples**
```bash
# Auto-detects server entrypoint (looks for 'mcp', 'server', or 'app')
fastmcp install claude-desktop server.py
# Install with fastmcp.json configuration (auto-detects)
fastmcp install claude-desktop
# Install with explicit fastmcp.json file
fastmcp install claude-desktop my-config.fastmcp.json
# Uses specific server entrypoint
fastmcp install claude-desktop server.py:my_server
# With custom name and dependencies
fastmcp install claude-desktop server.py:my_server --server-name "My Analysis Server" --with pandas
# Install in Claude Code with environment variables
fastmcp install claude-code server.py --env API_KEY=secret --env DEBUG=true
# Install in Cursor with environment variables
fastmcp install cursor server.py --env API_KEY=secret --env DEBUG=true
# Install with environment file
fastmcp install cursor server.py --env-file .env
# Install with specific Python version
fastmcp install claude-desktop server.py --python 3.11
# Install with requirements file
fastmcp install claude-code server.py --with-requirements requirements.txt
# Install within a project directory
fastmcp install cursor server.py --project /path/to/project
# Generate MCP JSON configuration
fastmcp install mcp-json server.py --name "My Server" --with pandas
# Copy JSON configuration to clipboard
fastmcp install mcp-json server.py --copy
```
### MCP JSON Generation
The `mcp-json` subcommand generates standard MCP JSON configuration that can be used with any MCP-compatible client. This is useful when:
- Working with MCP clients not directly supported by FastMCP
- Creating configuration for CI/CD environments
- Sharing server configurations with others
- Integration with custom tooling
The generated JSON follows the standard MCP server configuration format used by Claude Desktop, VS Code, Cursor, and other MCP clients, with the server name as the root key:
```json
{
"server-name": {
"command": "uv",
"args": [
"run",
"--with",
"fastmcp",
"fastmcp",
"run",
"/path/to/server.py"
],
"env": {
"API_KEY": "value"
}
}
}
```
<Note>
To use this configuration with your MCP client, you'll typically need to add it to the client's `mcpServers` object. Consult your client's documentation for any specific configuration requirements or formatting needs.
</Note>
**Options specific to mcp-json:**
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Copy to Clipboard | `--copy` | Copy configuration to clipboard instead of printing to stdout |
## `fastmcp inspect`
<VersionBadge version="2.9.0" />
Inspect a FastMCP server to view summary information or generate a detailed JSON report.
```bash
# Show text summary
fastmcp inspect server.py
# Output FastMCP JSON to stdout
fastmcp inspect server.py --format fastmcp
# Save MCP JSON to file (format required with -o)
fastmcp inspect server.py --format mcp -o manifest.json
```
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Format | `--format`, `-f` | Output format: `fastmcp` (FastMCP-specific) or `mcp` (MCP protocol). Required when using `-o` |
| Output File | `--output`, `-o` | Save JSON report to file instead of stdout. Requires `--format` |
### Output Formats
#### FastMCP Format (`--format fastmcp`)
The default and most comprehensive format, includes all FastMCP-specific metadata:
- Server name, instructions, and version
- FastMCP version and MCP version
- Tool tags and enabled status
- Output schemas for tools
- Annotations and custom metadata
- Uses snake_case field names
- **Use this for**: Complete server introspection and debugging FastMCP servers
#### MCP Protocol Format (`--format mcp`)
Shows exactly what MCP clients will see via the protocol:
- Only includes standard MCP protocol fields
- Matches output from `client.list_tools()`, `client.list_prompts()`, etc.
- Uses camelCase field names (e.g., `inputSchema`)
- Excludes FastMCP-specific fields like tags and enabled status
- **Use this for**: Debugging client visibility and ensuring MCP compatibility
### Entrypoints
The `inspect` command supports local FastMCP server files and configuration:
1. **Inferred server instance**: `server.py` - imports the module and looks for a FastMCP server instance named `mcp`, `server`, or `app`. Errors if no such object is found.
2. **Explicit server entrypoint**: `server.py:custom_name` - imports and uses the specified server entrypoint
3. **Factory function**: `server.py:create_server` - calls the specified function (sync or async) to create a server instance
4. **FastMCP configuration**: `fastmcp.json` - inspects servers defined with FastMCP's declarative configuration
<Warning>
The `inspect` command **only supports local files and fastmcp.json** - no URLs, remote servers, or standard MCP configuration files.
</Warning>
### Examples
```bash
# Show text summary (no JSON output)
fastmcp inspect server.py
# Output:
# Server: MyServer
# Instructions: A helpful MCP server
# Version: 1.0.0
#
# Components:
# Tools: 5
# Prompts: 2
# Resources: 3
# Templates: 1
#
# Environment:
# FastMCP: 2.0.0
# MCP: 1.0.0
#
# Use --format [fastmcp|mcp] for complete JSON output
# Output FastMCP format to stdout
fastmcp inspect server.py --format fastmcp
# Specify server entrypoint
fastmcp inspect server.py:my_server
# Output MCP protocol format to stdout
fastmcp inspect server.py --format mcp
# Save to file (format required)
fastmcp inspect server.py --format fastmcp -o server-manifest.json
# Save MCP format with custom server object
fastmcp inspect server.py:my_server --format mcp -o mcp-manifest.json
# Error: format required with output file
fastmcp inspect server.py -o output.json
# Error: --format is required when using -o/--output
```
## `fastmcp project prepare`
Create a persistent uv project directory from a fastmcp.json file's environment configuration. This allows you to pre-install all dependencies once and reuse them with the `--project` flag.
```bash
fastmcp project prepare fastmcp.json --output-dir ./env
```
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Output Directory | `--output-dir` | **Required.** Directory where the persistent uv project will be created |
### Usage Pattern
```bash
# Step 1: Prepare the environment (installs dependencies)
fastmcp project prepare fastmcp.json --output-dir ./my-env
# Step 2: Run using the prepared environment (fast, no dependency installation)
fastmcp run fastmcp.json --project ./my-env
```
The prepare command creates a uv project with:
- A `pyproject.toml` containing all dependencies from the fastmcp.json
- A `.venv` with all packages pre-installed
- A `uv.lock` file for reproducible environments
This is useful when you want to separate environment setup from server execution, such as in deployment scenarios where dependencies are installed once and the server is run multiple times.
## `fastmcp version`
Display version information about FastMCP and related components.
```bash
fastmcp version
```
### Options
| Option | Flag | Description |
| ------ | ---- | ----------- |
| Copy to Clipboard | `--copy` | Copy version information to clipboard |
+45
View File
@@ -0,0 +1,45 @@
---
title: "Contrib Modules"
description: "Community-contributed modules extending FastMCP"
icon: "cubes"
---
import { VersionBadge } from "/snippets/version-badge.mdx"
<VersionBadge version="2.2.1" />
FastMCP includes a `contrib` package that holds community-contributed modules. These modules extend FastMCP's functionality but aren't officially maintained by the core team.
Contrib modules provide additional features, integrations, or patterns that complement the core FastMCP library. They offer a way for the community to share useful extensions while keeping the core library focused and maintainable.
The available modules can be viewed in the [contrib directory](https://github.com/PrefectHQ/fastmcp/tree/main/fastmcp_slim/fastmcp/contrib).
## Usage
To use a contrib module, import it from the `fastmcp.contrib` package:
```python test="skip"
from fastmcp.contrib import my_module
```
## Important Considerations
- **Stability**: Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
- **Compatibility**: Changes to core FastMCP might break modules in `contrib` without explicit warnings in the main changelog.
- **Dependencies**: Contrib modules may have additional dependencies not required by the core library. These dependencies are typically documented in the module's README or separate requirements files.
## Contributing
We welcome contributions to the `contrib` package! If you have a module that extends FastMCP in a useful way, consider contributing it:
1. Create a new directory in `fastmcp_slim/fastmcp/contrib/` for your module
3. Add proper tests for your module in `tests/contrib/`
2. Include comprehensive documentation in a README.md file, including usage and examples, as well as any additional dependencies or installation instructions
5. Submit a pull request
The ideal contrib module:
- Solves a specific use case or integration need
- Follows FastMCP coding standards
- Includes thorough documentation and examples
- Has comprehensive tests
- Specifies any additional dependencies
+225
View File
@@ -0,0 +1,225 @@
---
title: Decorating Methods
sidebarTitle: Decorating Methods
description: Properly use instance methods, class methods, and static methods with FastMCP decorators.
icon: at
---
FastMCP's decorator system is designed to work with functions, but you may see unexpected behavior if you try to decorate an instance or class method. This guide explains the correct approach for using methods with all FastMCP decorators (`@tool`, `@resource`, and `@prompt`).
## Why Are Methods Hard?
When you apply a FastMCP decorator like `@tool`, `@resource`, or `@prompt` to a method, the decorator captures the function at decoration time. For instance methods and class methods, this poses a challenge because:
1. For instance methods: The decorator gets the unbound method before any instance exists
2. For class methods: The decorator gets the function before it's bound to the class
This means directly decorating these methods doesn't work as expected. In practice, the LLM would see parameters like `self` or `cls` that it cannot provide values for.
Additionally, **FastMCP decorators return objects (Tool, Resource, or Prompt instances) rather than the original function**. This means that when you decorate a method directly, the method becomes the returned object and is no longer callable by your code:
<Warning>
**Don't do this!**
The method will no longer be callable from Python, and the tool won't be callable by LLMs.
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool
def my_method(self, x: int) -> int:
return x * 2
obj = MyClass()
obj.my_method(5) # Fails - my_method is a Tool, not a function
```
</Warning>
This is another important reason to register methods functionally after defining the class.
## Recommended Patterns
### Instance Methods
<Warning>
**Don't do this!**
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool # This won't work correctly
def add(self, x, y):
return x + y
```
</Warning>
When the decorator is applied this way, it captures the unbound method. When the LLM later tries to use this component, it will see `self` as a required parameter, but it won't know what to provide for it, causing errors or unexpected behavior.
<Check>
**Do this instead**:
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
def add(self, x, y):
return x + y
# Create an instance first, then register the bound methods
obj = MyClass()
mcp.tool(obj.add)
# Now you can call it without 'self' showing up as a parameter
await mcp._mcp_call_tool('add', {'x': 1, 'y': 2}) # Returns 3
```
</Check>
This approach works because:
1. You first create an instance of the class (`obj`)
2. When you access the method through the instance (`obj.add`), Python creates a bound method where `self` is already set to that instance
3. When you register this bound method, the system sees a callable that only expects the appropriate parameters, not `self`
### Class Methods
The behavior of decorating class methods depends on the order of decorators:
<Warning>
**Don't do this** (decorator order matters):
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@classmethod
@mcp.tool # This won't work but won't raise an error
def from_string_v1(cls, s):
return cls(s)
@mcp.tool
@classmethod # This will raise a helpful ValueError
def from_string_v2(cls, s):
return cls(s)
```
</Warning>
- If `@classmethod` comes first, then `@mcp.tool`: No error is raised, but it won't work correctly
- If `@mcp.tool` comes first, then `@classmethod`: FastMCP will detect this and raise a helpful `ValueError` with guidance
<Check>
**Do this instead**:
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@classmethod
def from_string(cls, s):
return cls(s)
# Register the class method after the class is defined
mcp.tool(MyClass.from_string)
```
</Check>
This works because:
1. The `@classmethod` decorator is applied properly during class definition
2. When you access `MyClass.from_string`, Python provides a special method object that automatically binds the class to the `cls` parameter
3. When registered, only the appropriate parameters are exposed to the LLM, hiding the implementation detail of the `cls` parameter
### Static Methods
Static methods "work" with FastMCP decorators, but this is not recommended because the FastMCP decorator will not return a callable method. Therefore, you should register static methods the same way as other methods.
<Warning>
**This is not recommended, though it will work.**
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@mcp.tool
@staticmethod
def utility(x, y):
return x + y
```
</Warning>
This works because `@staticmethod` converts the method to a regular function, which the FastMCP decorator can then properly process. However, this is not recommended because the FastMCP decorator will not return a callable staticmethod. Therefore, you should register static methods the same way as other methods.
<Check>
**Prefer this pattern:**
```python
from fastmcp import FastMCP
mcp = FastMCP()
class MyClass:
@staticmethod
def utility(x, y):
return x + y
# This also works
mcp.tool(MyClass.utility)
```
</Check>
## Additional Patterns
### Creating Components at Class Initialization
You can automatically register instance methods when creating an object:
```python
from fastmcp import FastMCP
mcp = FastMCP()
class ComponentProvider:
def __init__(self, mcp_instance):
# Register methods
mcp_instance.tool(self.tool_method)
mcp_instance.resource("resource://data")(self.resource_method)
def tool_method(self, x):
return x * 2
def resource_method(self):
return "Resource data"
# The methods are automatically registered when creating the instance
provider = ComponentProvider(mcp)
```
This pattern is useful when:
- You want to encapsulate registration logic within the class itself
- You have multiple related components that should be registered together
- You want to ensure that methods are always properly registered when creating an instance
The class automatically registers its methods during initialization, ensuring they're properly bound to the instance before registration.
## Summary
The current behavior of FastMCP decorators with methods is:
- **Static methods**: Can be decorated directly and work perfectly with all FastMCP decorators
- **Class methods**: Cannot be decorated directly and will raise a helpful `ValueError` with guidance
- **Instance methods**: Should be registered after creating an instance using the decorator calls
For class and instance methods, you should register them after creating the instance or class to ensure proper method binding. This ensures that the methods are properly bound before being registered.
Understanding these patterns allows you to effectively organize your components into classes while maintaining proper method binding, giving you the benefits of object-oriented design without sacrificing the simplicity of FastMCP's decorator system.
+104
View File
@@ -0,0 +1,104 @@
---
title: Testing your FastMCP Server
sidebarTitle: Testing
description: How to test your FastMCP server.
icon: vial
---
The best way to ensure a reliable and maintainable FastMCP Server is to test it! The FastMCP Client combined with Pytest provides a simple and powerful way to test your FastMCP servers.
## Prerequisites
Testing FastMCP servers requires `pytest-asyncio` to handle async test functions and fixtures. Install it as a development dependency:
```bash
pip install pytest-asyncio
```
We recommend configuring pytest to automatically handle async tests by setting the asyncio mode to `auto` in your `pyproject.toml`:
```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
```
This eliminates the need to decorate every async test with `@pytest.mark.asyncio`.
## Testing with Pytest Fixtures
Using Pytest Fixtures, you can wrap your FastMCP Server in a Client instance that makes interacting with your server fast and easy. This is especially useful when building your own MCP Servers and enables a tight development loop by allowing you to avoid using a separate tool like MCP Inspector during development:
```python
import pytest
from fastmcp.client import Client
from fastmcp.client.transports import FastMCPTransport
from my_project.main import mcp
@pytest.fixture
async def main_mcp_client():
async with Client(transport=mcp) as mcp_client:
yield mcp_client
async def test_list_tools(main_mcp_client: Client[FastMCPTransport]):
list_tools = await main_mcp_client.list_tools()
assert len(list_tools) == 5
```
We recommend the [inline-snapshot library](https://github.com/15r10nk/inline-snapshot) for asserting complex data structures coming from your MCP Server. This library allows you to write tests that are easy to read and understand, and are also easy to update when the data structure changes.
```python
from inline_snapshot import snapshot
async def test_list_tools(main_mcp_client: Client[FastMCPTransport]):
list_tools = await main_mcp_client.list_tools()
assert list_tools == snapshot()
```
Simply run `pytest --inline-snapshot=fix,create` to fill in the `snapshot()` with actual data.
<Tip>
For values that change you can leverage the [dirty-equals](https://github.com/samuelcolvin/dirty-equals) library to perform flexible equality assertions on dynamic or non-deterministic values.
</Tip>
Using the pytest `parametrize` decorator, you can easily test your tools with a wide variety of inputs.
```python
import pytest
from my_project.main import mcp
from fastmcp.client import Client
from fastmcp.client.transports import FastMCPTransport
@pytest.fixture
async def main_mcp_client():
async with Client(mcp) as client:
yield client
@pytest.mark.parametrize(
"first_number, second_number, expected",
[
(1, 2, 3),
(2, 3, 5),
(3, 4, 7),
],
)
async def test_add(
first_number: int,
second_number: int,
expected: int,
main_mcp_client: Client[FastMCPTransport],
):
result = await main_mcp_client.call_tool(
name="add", arguments={"x": first_number, "y": second_number}
)
assert result.data is not None
assert isinstance(result.data, int)
assert result.data == expected
```
<Tip>
The [FastMCP Repository contains thousands of tests](https://github.com/PrefectHQ/fastmcp/tree/main/tests) for the FastMCP Client and Server. Everything from connecting to remote MCP servers, to testing tools, resources, and prompts is covered, take a look for inspiration!
</Tip>
+739
View File
@@ -0,0 +1,739 @@
---
title: Tool Transformation
sidebarTitle: Tool Transformation
description: Create enhanced tool variants with modified schemas, argument mappings, and custom behavior.
icon: wand-magic-sparkles
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="2.8.0" />
Tool transformation allows you to create new, enhanced tools from existing ones. This powerful feature enables you to adapt tools for different contexts, simplify complex interfaces, or add custom logic without duplicating code.
## Why Transform Tools?
Often, an existing tool is *almost* perfect for your use case, but it might have:
- A confusing description (or no description at all).
- Argument names or descriptions that are not intuitive for an LLM (e.g., `q` instead of `query`).
- Unnecessary parameters that you want to hide from the LLM.
- A need for input validation before the original tool is called.
- A need to modify or format the tool's output.
Instead of rewriting the tool from scratch, you can **transform** it to fit your needs.
## Basic Transformation
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.
```python {13-21}
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]:
"""Searches for items in the database."""
return database.search(query, category)
# Create a more domain-specific version by changing its metadata
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,
checking availability, or browsing product categories.
""",
)
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()
```
</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.
### Parameters
The `Tool.from_tool()` class method is the primary way to create a transformed tool. It takes the following parameters:
- `tool`: The tool to transform. This is the only required argument.
- `name`: An optional name for the new tool.
- `description`: An optional description for the new tool.
- `transform_args`: A dictionary of `ArgTransform` objects, one for each argument you want to modify.
- `transform_fn`: An optional function that will be called instead of the parent tool's logic.
- `output_schema`: Control output schema and structured outputs (see [Output Schema Control](#output-schema-control)).
- `tags`: An optional set of tags for the new tool.
- `annotations`: An optional set of `ToolAnnotations` for the new tool.
- `serializer`: An optional function that will be called to serialize the result of the new tool.
- `meta`: Control meta information for the tool. Use `None` to remove meta, any dict to set meta, or leave unset to inherit from parent.
The result is a new `TransformedTool` object that wraps the parent tool and applies the transformations you specify. You can add this tool to your MCP server using its `add_tool()` method.
## Modifying Arguments
To modify a tool's parameters, provide a dictionary of `ArgTransform` objects to the `transform_args` parameter of `Tool.from_tool()`. Each key is the name of the *original* argument you want to modify.
<Tip>
You only need to provide a `transform_args` entry for arguments you want to modify. All other arguments will be passed through unchanged.
</Tip>
### The ArgTransform Class
To modify an argument, you need to create an `ArgTransform` object. This object has the following parameters:
- `name`: The new name for the argument.
- `description`: The new description for the argument.
- `default`: The new default value for the argument.
- `default_factory`: A function that will be called to generate a default value for the argument. This is useful for arguments that need to be generated for each tool call, such as timestamps or unique IDs.
- `hide`: Whether to hide the argument from the LLM.
- `required`: Whether the argument is required, usually used to make an optional argument be required instead.
- `type`: The new type for the argument.
<Tip>
Certain combinations of parameters are not allowed. For example, you can only use `default_factory` with `hide=True`, because dynamic defaults cannot be represented in a JSON schema for the client. You can only set required=True for arguments that do not declare a default value.
</Tip>
### Descriptions
By far the most common reason to transform a tool, after its own description, is to improve its argument descriptions. A good description is crucial for helping an LLM understand how to use a parameter correctly. This is especially important when wrapping tools from external APIs, whose argument descriptions may be missing or written for developers, not LLMs.
In this example, we add a helpful description to the `user_id` argument:
```python {16-19}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP()
@mcp.tool
def find_user(user_id: str):
"""Finds a user by their ID."""
...
new_tool = Tool.from_tool(
find_user,
transform_args={
"user_id": ArgTransform(
description=(
"The unique identifier for the user, "
"usually in the format 'usr-xxxxxxxx'."
)
)
}
)
```
### Names
At times, you may want to rename an argument to make it more intuitive for an LLM.
For example, in the following example, we take a generic `q` argument and expand it to `search_query`:
```python {15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP()
@mcp.tool
def search(q: str):
"""Searches for items in the database."""
return database.search(q)
new_tool = Tool.from_tool(
search,
transform_args={
"q": ArgTransform(name="search_query")
}
)
```
### Default Values
You can update the default value for any argument using the `default` parameter. Here, we change the default value of the `y` argument to 10:
```python{15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP()
@mcp.tool
def add(x: int, y: int) -> int:
"""Adds two numbers."""
return x + y
new_tool = Tool.from_tool(
add,
transform_args={
"y": ArgTransform(default=10)
}
)
```
Default values are especially useful in combination with hidden arguments.
### Hiding Arguments
Sometimes a tool requires arguments that shouldn't be exposed to the LLM, such as API keys, configuration flags, or internal IDs. You can hide these parameters using `hide=True`. Note that you can only hide arguments that have a default value (or for which you provide a new default), because the LLM can't provide a value at call time.
<Tip>
To pass a constant value to the parent tool, combine `hide=True` with `default=<value>`.
</Tip>
```python {19-20}
import os
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP()
@mcp.tool
def send_email(to: str, subject: str, body: str, api_key: str):
"""Sends an email."""
...
# Create a simplified version that hides the API key
new_tool = Tool.from_tool(
send_email,
name="send_notification",
transform_args={
"api_key": ArgTransform(
hide=True,
default=os.environ.get("EMAIL_API_KEY"),
)
}
)
```
The LLM now only sees the `to`, `subject`, and `body` parameters. The `api_key` is supplied automatically from an environment variable.
For values that must be generated for each tool call (like timestamps or unique IDs), use `default_factory`, which is called with no arguments every time the tool is called. For example,
```python {3-4}
transform_args = {
'timestamp': ArgTransform(
hide=True,
default_factory=lambda: datetime.now(),
)
}
```
<Warning>
`default_factory` can only be used with `hide=True`. This is because visible parameters need static defaults that can be represented in a JSON schema for the client.
</Warning>
### Meta Information
<VersionBadge version="2.11.0" />
You can control meta information on transformed tools using the `meta` parameter. Meta information is additional data about the tool that doesn't affect its functionality but can be used by clients for categorization, routing, or other purposes.
```python {15-17}
from fastmcp import FastMCP
from fastmcp.tools import Tool
mcp = FastMCP()
@mcp.tool
def analyze_data(data: str) -> dict:
"""Analyzes the provided data."""
return {"result": f"Analysis of {data}"}
# Add custom meta information
enhanced_tool = Tool.from_tool(
analyze_data,
name="enhanced_analyzer",
meta={
"category": "analytics",
"priority": "high",
"requires_auth": True
}
)
mcp.add_tool(enhanced_tool)
```
You can also remove meta information entirely:
```python {6}
# Remove meta information from parent tool
simplified_tool = Tool.from_tool(
analyze_data,
name="simple_analyzer",
meta=None # Removes any meta information
)
```
If you don't specify the `meta` parameter, the transformed tool inherits the parent tool's meta information.
### Required Values
In rare cases where you want to make an optional argument required, you can set `required=True`. This has no effect if the argument was already required.
```python {3}
transform_args = {
'user_id': ArgTransform(
required=True,
)
}
```
## Modifying Tool Behavior
<Warning>
With great power comes great responsibility. Modifying tool behavior is a very advanced feature.
</Warning>
In addition to changing a tool's schema, advanced users can also modify its behavior. This is useful for adding validation logic, or for post-processing the tool's output.
The `from_tool()` method takes a `transform_fn` parameter, which is an async function that replaces the parent tool's logic and gives you complete control over the tool's execution.
### The Transform Function
The `transform_fn` is an async function that **completely replaces** the parent tool's logic.
Critically, the transform function's arguments are used to determine the new tool's final schema. Any arguments that are not already present in the parent tool schema OR the `transform_args` will be added to the new tool's schema. Note that when `transform_args` and your function have the same argument name, the `transform_args` metadata will take precedence, if provided.
```python
async def my_custom_logic(user_input: str, max_length: int = 100) -> str:
# Your custom logic here - this completely replaces the parent tool
return f"Custom result for: {user_input[:max_length]}"
Tool.from_tool(transform_fn=my_custom_logic)
```
<Tip>
The name / docstring of the `transform_fn` are ignored. Only its arguments are used to determine the final schema.
</Tip>
### Calling the Parent Tool
Most of the time, you don't want to completely replace the parent tool's behavior. Instead, you want to add validation, modify inputs, or post-process outputs while still leveraging the parent tool's core functionality. For this, FastMCP provides the special `forward()` and `forward_raw()` functions.
Both `forward()` and `forward_raw()` are async functions that let you call the parent tool from within your `transform_fn`:
- **`forward()`** (recommended): Automatically handles argument mapping based on your `ArgTransform` configurations. Call it with the transformed argument names.
- **`forward_raw()`**: Bypasses all transformation and calls the parent tool directly with its original argument names. This is rarely needed unless you're doing complex argument manipulation, perhaps without `arg_transforms`.
The most common transformation pattern is to validate (potentially renamed) arguments before calling the parent tool. Here's an example that validates that `x` and `y` are positive before calling the parent tool:
<Tabs>
<Tab title="Using forward()">
In the simplest case, your parent tool and your transform function have the same arguments. You can call `forward()` with the same argument names as the parent tool:
```python {15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward
mcp = FastMCP()
@mcp.tool
def add(x: int, y: int) -> int:
"""Adds two numbers."""
return x + y
async def ensure_positive(x: int, y: int) -> int:
if x <= 0 or y <= 0:
raise ValueError("x and y must be positive")
return await forward(x=x, y=y)
new_tool = Tool.from_tool(
add,
transform_fn=ensure_positive,
)
mcp.add_tool(new_tool)
```
</Tab>
<Tab title="Using forward() with renamed args">
When your transformed tool has different argument names than the parent tool, you can call `forward()` with the renamed arguments and it will automatically map the arguments to the parent tool's arguments:
```python {15, 20-23}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward
mcp = FastMCP()
@mcp.tool
def add(x: int, y: int) -> int:
"""Adds two numbers."""
return x + y
async def ensure_positive(a: int, b: int) -> int:
if a <= 0 or b <= 0:
raise ValueError("a and b must be positive")
return await forward(a=a, b=b)
new_tool = Tool.from_tool(
add,
transform_fn=ensure_positive,
transform_args={
"x": ArgTransform(name="a"),
"y": ArgTransform(name="b"),
}
)
mcp.add_tool(new_tool)
```
</Tab>
<Tab title="Using forward_raw()">
Finally, you can use `forward_raw()` to bypass all argument mapping and call the parent tool directly with its original argument names.
```python {15, 20-23}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward
mcp = FastMCP()
@mcp.tool
def add(x: int, y: int) -> int:
"""Adds two numbers."""
return x + y
async def ensure_positive(a: int, b: int) -> int:
if a <= 0 or b <= 0:
raise ValueError("a and b must be positive")
return await forward_raw(x=a, y=b)
new_tool = Tool.from_tool(
add,
transform_fn=ensure_positive,
transform_args={
"x": ArgTransform(name="a"),
"y": ArgTransform(name="b"),
}
)
mcp.add_tool(new_tool)
```
</Tab>
</Tabs>
### Passing Arguments with **kwargs
If your `transform_fn` includes `**kwargs` in its signature, it will receive **all arguments from the parent tool after `ArgTransform` configurations have been applied**. This is powerful for creating flexible validation functions that don't require you to add every argument to the function signature.
In the following example, we wrap a parent tool that accepts two arguments `x` and `y`. These are renamed to `a` and `b` in the transformed tool, and the transform only validates `a`, passing the other argument through as `**kwargs`.
```python {12, 15}
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import forward, ArgTransform
mcp = FastMCP()
@mcp.tool
def add(x: int, y: int) -> int:
"""Adds two numbers."""
return x + y
async def ensure_a_positive(a: int, **kwargs) -> int:
if a <= 0:
raise ValueError("a must be positive")
return await forward(a=a, **kwargs)
new_tool = Tool.from_tool(
add,
transform_fn=ensure_a_positive,
transform_args={
"x": ArgTransform(name="a"),
"y": ArgTransform(name="b"),
}
)
mcp.add_tool(new_tool)
```
<Tip>
In the above example, `**kwargs` receives the renamed argument `b`, not the original argument `y`. It is therefore recommended to use with `forward()`, not `forward_raw()`.
</Tip>
## Modifying MCP Tools with MCPConfig
When running MCP Servers under FastMCP with `MCPConfig`, you can also apply a subset of tool transformations
directly in the MCPConfig json file.
```json
{
"mcpServers": {
"weather": {
"url": "https://weather.example.com/mcp",
"transport": "http",
"tools": {
"weather_get_forecast": {
"name": "miami_weather",
"description": "Get the weather for Miami",
"meta": {
"category": "weather",
"location": "miami"
},
"arguments": {
"city": {
"name": "city",
"default": "Miami",
"hide": True,
}
}
}
}
}
}
}
```
The `tools` section is a dictionary of tool names to tool configurations. Each tool configuration is a
dictionary of tool properties.
See the [MCPConfigTransport](/v2/clients/transports#tool-transformation-with-fastmcp-and-mcpconfig) documentation for more details.
## Output Schema Control
<VersionBadge version="2.10.0" />
Transformed tools inherit output schemas from their parent by default, but you can control this behavior:
**Inherit from Parent (Default)**
```python
Tool.from_tool(parent_tool, name="renamed_tool")
```
The transformed tool automatically uses the parent tool's output schema and structured output behavior.
**Custom Output Schema**
```python
Tool.from_tool(parent_tool, output_schema={
"type": "object",
"properties": {"status": {"type": "string"}}
})
```
Provide your own schema that differs from the parent. The tool must return data matching this schema.
**Remove Output Schema**
```python
Tool.from_tool(parent_tool, output_schema=None)
```
Removes the output schema declaration. Automatic structured content still works for object-like returns (dict, dataclass, Pydantic models) but primitive types won't be structured.
**Full Control with Transform Functions**
```python
async def custom_output(**kwargs) -> ToolResult:
result = await forward(**kwargs)
return ToolResult(content=[...], structured_content={...})
Tool.from_tool(parent_tool, transform_fn=custom_output)
```
Use a transform function returning `ToolResult` for complete control over both content blocks and structured outputs.
## Common Patterns
Tool transformation is a flexible feature that supports many powerful patterns. Here are a few common use cases to give you ideas.
### Exposing Client Methods as Tools
A powerful use case for tool transformation is exposing methods from existing Python clients (GitHub clients, API clients, database clients, etc.) directly as MCP tools. This pattern eliminates boilerplate wrapper functions and treats tools as annotations around client methods.
**Without Tool Transformation**, you typically create wrapper functions that duplicate annotations:
```python
async def get_repository(
owner: Annotated[str, "The owner of the repository."],
repo: Annotated[str, "The name of the repository."],
) -> Repository:
"""Get basic information about a GitHub repository."""
return await github_client.get_repository(owner=owner, repo=repo)
```
**With Tool Transformation**, you can wrap the client method directly:
```python
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP("GitHub Tools")
# Wrap a client method directly as a tool
get_repo_tool = Tool.from_tool(
tool=Tool.from_function(fn=github_client.get_repository),
description="Get basic information about a GitHub repository.",
transform_args={
"owner": ArgTransform(description="The owner of the repository."),
"repo": ArgTransform(description="The name of the repository."),
}
)
mcp.add_tool(get_repo_tool)
```
This pattern keeps the implementation in your client and treats the tool as an annotation layer, avoiding duplicate code.
#### Hiding Client-Specific Arguments
Client methods often have internal parameters (debug flags, auth tokens, rate limit settings) that shouldn't be exposed to LLMs. Use `hide=True` with a default value to handle these automatically:
```python
get_issues_tool = Tool.from_tool(
tool=Tool.from_function(fn=github_client.get_issues),
description="Get issues from a GitHub repository.",
transform_args={
"owner": ArgTransform(description="The owner of the repository."),
"repo": ArgTransform(description="The name of the repository."),
"limit": ArgTransform(description="Maximum number of issues to return."),
# Hide internal parameters
"include_debug_info": ArgTransform(hide=True, default=False),
"error_on_not_found": ArgTransform(hide=True, default=True),
}
)
mcp.add_tool(get_issues_tool)
```
The LLM only sees `owner`, `repo`, and `limit`. Internal parameters are supplied automatically.
#### Reusable Argument Patterns
When wrapping multiple client methods, you can define reusable argument transformations. This scales well for larger tool sets and keeps annotations consistent:
```python
from fastmcp import FastMCP
from fastmcp.tools import Tool
from fastmcp.tools.tool_transform import ArgTransform
mcp = FastMCP("GitHub Tools")
# Define reusable argument patterns
OWNER_ARG = ArgTransform(description="The repository owner.")
REPO_ARG = ArgTransform(description="The repository name.")
LIMIT_ARG = ArgTransform(description="Maximum number of items to return.")
HIDE_ERROR = ArgTransform(hide=True, default=True)
def create_github_tools(client):
"""Create tools from GitHub client methods with shared argument patterns."""
owner_repo_args = {
"owner": OWNER_ARG,
"repo": REPO_ARG,
}
error_args = {
"error_on_not_found": HIDE_ERROR,
}
return [
Tool.from_tool(
tool=Tool.from_function(fn=client.get_repository),
description="Get basic information about a GitHub repository.",
transform_args={**owner_repo_args, **error_args}
),
Tool.from_tool(
tool=Tool.from_function(fn=client.get_issue),
description="Get a specific issue from a repository.",
transform_args={
**owner_repo_args,
"issue_number": ArgTransform(description="The issue number."),
"limit_comments": LIMIT_ARG,
**error_args,
}
),
Tool.from_tool(
tool=Tool.from_function(fn=client.get_pull_request),
description="Get a specific pull request from a repository.",
transform_args={
**owner_repo_args,
"pull_request_number": ArgTransform(description="The PR number."),
"limit_comments": LIMIT_ARG,
**error_args,
}
),
]
# Add all tools to the server
for tool in create_github_tools(github_client):
mcp.add_tool(tool)
```
This pattern provides several benefits:
- **No duplicate implementation**: Logic stays in the client
- **Consistent annotations**: Reusable argument patterns ensure consistency
- **Easy maintenance**: Update the client, not wrapper functions
- **Scalable**: Easily add new tools by wrapping additional client methods
### Adapting Remote or Generated Tools
This is one of the most common reasons to use tool transformation. Tools from remote MCP servers (via a [proxy](/v2/servers/proxy)) or generated from an [OpenAPI spec](/v2/integrations/openapi) are often too generic for direct use by an LLM. You can use transformation to create a simpler, more intuitive version for your specific needs.
### Chaining Transformations
You can chain transformations by using an already transformed tool as the parent for a new transformation. This lets you build up complex behaviors in layers, for example, first renaming arguments, and then adding validation logic to the renamed tool.
### Context-Aware Tool Factories
You can write functions that act as "factories," generating specialized versions of a tool for different contexts. For example, you could create a `get_my_data` tool that is specific to the currently logged-in user by hiding the `user_id` parameter and providing it automatically.
```python
from fastmcp import FastMCP
from fastmcp.tools import Tool, tool
from fastmcp.tools.tool_transform import ArgTransform
# A generic tool that requires a user_id
@tool
def get_user_data(user_id: str, query: str) -> str:
"""Fetch data for a specific user."""
return f"Data for user {user_id}: {query}"
def create_user_tool(user_id: str) -> Tool:
"""Factory that creates a user-specific version of get_user_data."""
return Tool.from_tool(
get_user_data,
name="get_my_data",
description="Fetch your data. No need to specify a user ID.",
transform_args={
"user_id": ArgTransform(hide=True, default=user_id),
},
)
# Create a server with a tool customized for the current user
mcp = FastMCP("User Server")
current_user_id = "user-123" # e.g., from auth context
mcp.add_tool(create_user_tool(current_user_id))
# Clients see "get_my_data(query: str)" — user_id is injected automatically
```