mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
256 lines
8.7 KiB
Text
256 lines
8.7 KiB
Text
---
|
|
title: Filesystem Provider
|
|
sidebarTitle: Filesystem
|
|
description: Automatic component discovery from Python files
|
|
icon: folder-tree
|
|
tag: NEW
|
|
---
|
|
|
|
import { VersionBadge } from '/snippets/version-badge.mdx'
|
|
|
|
<VersionBadge version="3.0.0" />
|
|
|
|
`FileSystemProvider` scans a directory for Python files and automatically registers functions decorated with `@tool`, `@resource`, or `@prompt`. This enables a file-based organization pattern similar to Next.js routing, where your project structure becomes your component registry.
|
|
|
|
## Why Filesystem Discovery
|
|
|
|
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 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:
|
|
|
|
- **No coordination**: Files don't import the server; server doesn't import files
|
|
- **Predictable naming**: Function names become component names (unless overridden)
|
|
- **Development mode**: Optionally re-scan files on every request for rapid iteration
|
|
|
|
## Quick Start
|
|
|
|
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 pathlib import Path
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.providers import FileSystemProvider
|
|
|
|
mcp = FastMCP("MyServer", providers=[FileSystemProvider(Path(__file__).parent / "components")])
|
|
```
|
|
|
|
In your `components/` directory, create Python files with decorated functions.
|
|
|
|
```python
|
|
# components/tools/greet.py
|
|
from fastmcp.tools import tool
|
|
|
|
@tool
|
|
def greet(name: str) -> str:
|
|
"""Greet someone by name."""
|
|
return f"Hello, {name}!"
|
|
```
|
|
|
|
When the server starts, `FileSystemProvider` scans the directory, imports all Python files, and registers any decorated functions it finds.
|
|
|
|
## Decorators
|
|
|
|
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.tools import tool
|
|
|
|
@tool
|
|
def calculate_sum(a: float, b: float) -> float:
|
|
"""Add two numbers together."""
|
|
return a + b
|
|
```
|
|
|
|
Customize the tool with optional parameters.
|
|
|
|
```python
|
|
from fastmcp.tools import tool
|
|
|
|
@tool(
|
|
name="add-numbers",
|
|
description="Add two numbers together.",
|
|
tags={"math", "arithmetic"},
|
|
)
|
|
def add(a: float, b: float) -> float:
|
|
return a + b
|
|
```
|
|
|
|
The decorator supports all standard tool options: `name`, `title`, `description`, `icons`, `tags`, `output_schema`, `annotations`, and `meta`.
|
|
|
|
### @resource
|
|
|
|
Mark a function as a resource. Unlike `@tool`, the `@resource` decorator requires a URI argument.
|
|
|
|
```python
|
|
from fastmcp.resources import resource
|
|
|
|
@resource("config://app")
|
|
def get_app_config() -> str:
|
|
"""Get application configuration."""
|
|
return '{"version": "1.0"}'
|
|
```
|
|
|
|
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.resources import resource
|
|
|
|
@resource("users://{user_id}/profile")
|
|
def get_user_profile(user_id: str) -> str:
|
|
"""Get a user's profile by ID."""
|
|
return f'{{"id": "{user_id}", "name": "User"}}'
|
|
```
|
|
|
|
The decorator supports: `uri` (required), `name`, `title`, `description`, `icons`, `mime_type`, `tags`, `annotations`, and `meta`.
|
|
|
|
### @prompt
|
|
|
|
Mark a function as a prompt template.
|
|
|
|
```python test="skip"
|
|
from fastmcp.prompts import prompt
|
|
|
|
@prompt
|
|
def code_review(code: str, language: str = "python") -> str:
|
|
"""Generate a code review prompt."""
|
|
return f"Please review this {language} code:\n\n```{language}\n{code}\n```"
|
|
```
|
|
|
|
```python
|
|
from fastmcp.prompts import prompt
|
|
|
|
@prompt(name="explain-concept", tags={"education"})
|
|
def explain(topic: str) -> str:
|
|
"""Generate an explanation prompt."""
|
|
return f"Explain {topic} using clear examples and analogies."
|
|
```
|
|
|
|
The decorator supports: `name`, `title`, `description`, `icons`, `tags`, and `meta`.
|
|
|
|
## Directory Structure
|
|
|
|
The directory structure is purely organizational. The provider recursively scans all `.py` files regardless of which subdirectory they're in. Subdirectories like `tools/`, `resources/`, and `prompts/` are optional conventions that help you organize code.
|
|
|
|
```
|
|
components/
|
|
├── tools/
|
|
│ ├── greeting.py # @tool functions
|
|
│ └── calculator.py # @tool functions
|
|
├── resources/
|
|
│ └── config.py # @resource functions
|
|
└── prompts/
|
|
└── assistant.py # @prompt functions
|
|
```
|
|
|
|
You can also put all components in a single file or organize by feature rather than type.
|
|
|
|
```
|
|
components/
|
|
├── user_management.py # @tool, @resource, @prompt for users
|
|
├── billing.py # @tool, @resource for billing
|
|
└── analytics.py # @tool for analytics
|
|
```
|
|
|
|
## Discovery Rules
|
|
|
|
The provider follows these rules when scanning:
|
|
|
|
| Rule | Behavior |
|
|
|------|----------|
|
|
| File extensions | Only `.py` files are scanned |
|
|
| `__init__.py` | Skipped (used for package structure, not components) |
|
|
| `__pycache__` | Skipped |
|
|
| Private functions | Functions starting with `_` are ignored, even if decorated |
|
|
| No decorators | Files without `@tool`, `@resource`, or `@prompt` are silently skipped |
|
|
| Multiple components | A single file can contain any number of decorated functions |
|
|
|
|
### Package Imports
|
|
|
|
If your directory contains an `__init__.py` file, the provider imports files as proper Python package members. This means relative imports work correctly within your components directory.
|
|
|
|
```python
|
|
# components/__init__.py exists
|
|
|
|
# components/tools/greeting.py
|
|
from ..helpers import format_name # Relative imports work
|
|
|
|
@tool
|
|
def greet(name: str) -> str:
|
|
return f"Hello, {format_name(name)}!"
|
|
```
|
|
|
|
Without `__init__.py`, files are imported directly using `importlib.util.spec_from_file_location`.
|
|
|
|
## Reload Mode
|
|
|
|
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 pathlib import Path
|
|
|
|
from fastmcp.server.providers import FileSystemProvider
|
|
|
|
provider = FileSystemProvider(Path(__file__).parent / "components", reload=True)
|
|
```
|
|
|
|
With `reload=True`, the provider:
|
|
|
|
1. Re-discovers all Python files on each request
|
|
2. Re-imports modules that have changed
|
|
3. Updates the component registry with any new, modified, or removed components
|
|
|
|
<Warning>
|
|
Reload mode adds overhead to every request. Use it only during development, not in production.
|
|
</Warning>
|
|
|
|
## Error Handling
|
|
|
|
When a file fails to import (syntax error, missing dependency, etc.), the provider logs a warning and continues scanning other files. Failed imports don't prevent the server from starting.
|
|
|
|
```
|
|
WARNING - Failed to import /path/to/broken.py: No module named 'missing_dep'
|
|
```
|
|
|
|
The provider tracks which files have failed and only re-logs warnings when the file's modification time changes. This prevents log spam when a broken file is repeatedly scanned in reload mode.
|
|
|
|
## Example Project
|
|
|
|
A complete example is available in the repository at `examples/filesystem-provider/`. The structure demonstrates the recommended organization.
|
|
|
|
```
|
|
examples/filesystem-provider/
|
|
├── server.py # Server entry point
|
|
└── components/
|
|
├── tools/
|
|
│ ├── greeting.py # greet, farewell tools
|
|
│ └── calculator.py # add, multiply tools
|
|
├── resources/
|
|
│ └── config.py # Static and templated resources
|
|
└── prompts/
|
|
└── assistant.py # code_review, explain prompts
|
|
```
|
|
|
|
The server entry point is minimal.
|
|
|
|
```python
|
|
from pathlib import Path
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.server.providers import FileSystemProvider
|
|
|
|
provider = FileSystemProvider(
|
|
root=Path(__file__).parent / "components",
|
|
reload=True,
|
|
)
|
|
|
|
mcp = FastMCP("FilesystemDemo", providers=[provider])
|
|
```
|
|
|
|
Run with `fastmcp run examples/filesystem-provider/server.py` or inspect with `fastmcp inspect examples/filesystem-provider/server.py`.
|