Add FileSystemProvider for filesystem-based component discovery (#2823)

This commit is contained in:
Jeremiah Lowin 2026-01-09 16:31:29 -05:00 committed by GitHub
commit 87f2edd9c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 2419 additions and 4 deletions

View file

@ -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": [

View 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`.