mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 14:04:18 +02:00
Add FileSystemProvider for filesystem-based component discovery (#2823)
This commit is contained in:
parent
43811cc16b
commit
87f2edd9c0
14 changed files with 2419 additions and 4 deletions
|
|
@ -20,7 +20,10 @@
|
|||
"primary": "#2d00f7"
|
||||
},
|
||||
"contextual": {
|
||||
"options": ["copy", "view"]
|
||||
"options": [
|
||||
"copy",
|
||||
"view"
|
||||
]
|
||||
},
|
||||
"description": "The fast, Pythonic way to build MCP servers and clients.",
|
||||
"errors": {
|
||||
|
|
@ -106,6 +109,7 @@
|
|||
"pages": [
|
||||
"servers/providers/overview",
|
||||
"servers/providers/local",
|
||||
"servers/providers/filesystem",
|
||||
"servers/providers/mounting",
|
||||
"servers/providers/namespacing",
|
||||
"servers/providers/proxy",
|
||||
|
|
@ -157,7 +161,10 @@
|
|||
{
|
||||
"group": "Essentials",
|
||||
"icon": "cube",
|
||||
"pages": ["clients/client", "clients/transports"]
|
||||
"pages": [
|
||||
"clients/client",
|
||||
"clients/transports"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Core Operations",
|
||||
|
|
@ -184,7 +191,10 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -194,7 +204,10 @@
|
|||
{
|
||||
"group": "Providers",
|
||||
"icon": "globe",
|
||||
"pages": ["integrations/fastapi", "integrations/openapi"]
|
||||
"pages": [
|
||||
"integrations/fastapi",
|
||||
"integrations/openapi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Authentication",
|
||||
|
|
@ -336,6 +349,15 @@
|
|||
"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": [
|
||||
|
|
|
|||
251
docs/servers/providers/filesystem.mdx
Normal file
251
docs/servers/providers/filesystem.mdx
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
---
|
||||
title: Filesystem Provider
|
||||
sidebarTitle: Filesystem
|
||||
description: Automatic component discovery from Python files
|
||||
icon: folder-tree
|
||||
---
|
||||
|
||||
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 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.
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.fs import FileSystemProvider
|
||||
|
||||
mcp = FastMCP("MyServer", providers=[FileSystemProvider("mcp/")])
|
||||
```
|
||||
|
||||
In your `mcp/` directory, create Python files with decorated functions.
|
||||
|
||||
```python
|
||||
# mcp/tools/greet.py
|
||||
from fastmcp.fs 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
|
||||
|
||||
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.
|
||||
|
||||
### @tool
|
||||
|
||||
Mark a function as a tool. The function name becomes the tool name by default.
|
||||
|
||||
```python
|
||||
from fastmcp.fs 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.fs 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.fs 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.fs 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
|
||||
from fastmcp.fs 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.fs 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.
|
||||
|
||||
```
|
||||
mcp/
|
||||
├── 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.
|
||||
|
||||
```
|
||||
mcp/
|
||||
├── 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
|
||||
# mcp/__init__.py exists
|
||||
|
||||
# mcp/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 fastmcp.fs import FileSystemProvider
|
||||
|
||||
provider = FileSystemProvider("mcp/", 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
|
||||
└── mcp/
|
||||
├── 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.fs import FileSystemProvider
|
||||
|
||||
provider = FileSystemProvider(
|
||||
root=Path(__file__).parent / "mcp",
|
||||
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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue