Skip to content

Add FileSystemProvider for filesystem-based component discovery#2823

Merged
jlowin merged 4 commits intomainfrom
filesystem-provider
Jan 9, 2026
Merged

Add FileSystemProvider for filesystem-based component discovery#2823
jlowin merged 4 commits intomainfrom
filesystem-provider

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 9, 2026

Adds FileSystemProvider, a new provider that scans a directory for Python files and automatically registers functions decorated with @tool, @resource, or @prompt from fastmcp.fs.

The main benefit is eliminating coordination between files. Traditional servers require either tool files to import the server (to call @server.tool()) or the server to import all tool modules. FileSystemProvider removes this coupling—each file is self-contained with decorators that don't need a server instance.

from fastmcp import FastMCP
from fastmcp.fs import FileSystemProvider

mcp = FastMCP("MyServer", providers=[FileSystemProvider("mcp/")])
# mcp/tools/greet.py
from fastmcp.fs import tool

@tool
def greet(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"

Features:

  • Decorators support full syntax of standard FastMCP decorators
  • reload=True mode for development (re-scans on every request)
  • Package imports with relative import support when __init__.py exists
  • Warning deduplication based on file mtime for broken imports

Closes #2688

@jlowin jlowin added feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. v3 Targeted for FastMCP 3 labels Jan 9, 2026
@marvin-context-protocol marvin-context-protocol Bot added provider Related to the FastMCP Provider class server Related to FastMCP server implementation or server-side functionality. labels Jan 9, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65dbeac71e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +142 to +144
# Import using standard import machinery
try:
return importlib.import_module(module_name)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reload mode skips updated package modules

When a component file lives inside a package (__init__.py present), import_module_from_file uses importlib.import_module, which returns the cached module from sys.modules. In reload mode this means _load_components() will keep seeing the old module contents, so edits/new decorated functions inside a package will not show up until the process restarts. This breaks the documented behavior of reload=True for package-based layouts; consider reloading or invalidating the module entry when re-scanning.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 9, 2026

Walkthrough

Adds a filesystem-based component discovery surface under src/fastmcp/fs/. Introduces three decorators (@tool, @resource, @prompt) with metadata dataclasses and helpers, a package-aware discovery/import utility that extracts decorated callables from Python files, and a FileSystemProvider (subclassing LocalProvider) that registers discovered tools, resources (and templates), and prompts with optional dev reload behavior. Also adds fastmcp.fs package exports, documentation pages for the filesystem provider, and an example project demonstrating discovered components and a reload-enabled server entrypoint.

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR implements a decorator-driven discovery mechanism rather than pure convention-based location-only registration as originally requested in #2688, which differs from the stated issue objective. Clarify whether the decorator-driven approach (fastmcp.fs decorators) satisfies the intent of #2688, or if pure location-based registration without decorators should be prioritized instead.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding FileSystemProvider for filesystem-based component discovery, which aligns with the primary objective of this PR.
Description check ✅ Passed The description includes a clear overview, feature highlights, usage example, and closes the linked issue. It adequately covers the scope of changes and aligns with the repository's description template requirements.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing FileSystemProvider and its supporting components (decorators, discovery, documentation, and examples). No unrelated modifications were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/fastmcp/fs/provider.py (2)

58-73: Consider validating root (exists/is_dir) to fail fast with a clearer error.
Path(root).resolve() + eager _load_components() is good for surfacing import failures early, but if root is missing / not a directory you’ll likely get less actionable downstream errors.

Proposed tweak
     def __init__(
         self,
         root: str | Path = ".",
         reload: bool = False,
     ) -> None:
         super().__init__(on_duplicate="replace")
         self._root = Path(root).resolve()
+        if not self._root.exists():
+            raise FileNotFoundError(f"FileSystemProvider root does not exist: {self._root}")
+        if not self._root.is_dir():
+            raise NotADirectoryError(f"FileSystemProvider root is not a directory: {self._root}")
         self._reload = reload
         self._loaded = False

139-182: Resource vs template classification may be too aggressive for parameterized resources.
Right now, any user-facing function params (even with a fixed URI and no {}) force ResourceTemplate. If the framework intends to allow a fixed-URI resource with query/body params, this will be surprising. Worth confirming expected semantics (and adding a test) for “URI has no params but function has params”.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dbd800a and 65dbeac.

⛔ Files ignored due to path filters (4)
  • FASTMCP_3_MARKETING_SUMMARY.md is excluded by none and included by none
  • tests/fs/test_decorators.py is excluded by none and included by none
  • tests/fs/test_discovery.py is excluded by none and included by none
  • tests/fs/test_provider.py is excluded by none and included by none
📒 Files selected for processing (11)
  • docs/docs.json
  • docs/servers/providers/filesystem.mdx
  • examples/filesystem-provider/mcp/prompts/assistant.py
  • examples/filesystem-provider/mcp/resources/config.py
  • examples/filesystem-provider/mcp/tools/calculator.py
  • examples/filesystem-provider/mcp/tools/greeting.py
  • examples/filesystem-provider/server.py
  • src/fastmcp/fs/__init__.py
  • src/fastmcp/fs/decorators.py
  • src/fastmcp/fs/discovery.py
  • src/fastmcp/fs/provider.py
🧰 Additional context used
📓 Path-based instructions (4)
docs/**/*.mdx

📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)

docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...

Files:

  • docs/servers/providers/filesystem.mdx
docs/**/*.{md,mdx,json}

📄 CodeRabbit inference engine (AGENTS.md)

Documentation uses Mintlify framework. Files must be in docs.json to be included. Never modify docs/python-sdk/** (auto-generated)

Files:

  • docs/servers/providers/filesystem.mdx
  • docs/docs.json
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

docs/**/*.{md,mdx}: Code examples in documentation must explain before showing code and make blocks fully runnable (include imports)
Documentation structure: Headers form navigation guide with logical H2/H3 hierarchy. Content should be user-focused with sections motivating features (why) before mechanics (how). Use prose over code comments for important information
Never use 'This isn't...' or 'not just...' constructions in writing - state what something IS directly. Avoid defensive writing patterns

Files:

  • docs/servers/providers/filesystem.mdx
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/fs/decorators.py
  • src/fastmcp/fs/__init__.py
  • src/fastmcp/fs/provider.py
  • src/fastmcp/fs/discovery.py
🧠 Learnings (2)
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/fs/__init__.py
  • src/fastmcp/fs/discovery.py
  • docs/docs.json
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/fs/__init__.py
  • docs/docs.json
🧬 Code graph analysis (8)
examples/filesystem-provider/mcp/tools/calculator.py (1)
src/fastmcp/fs/decorators.py (4)
  • tool (102-102)
  • tool (106-117)
  • tool (121-131)
  • tool (134-214)
examples/filesystem-provider/mcp/resources/config.py (2)
src/fastmcp/fs/decorators.py (1)
  • resource (222-287)
src/fastmcp/server/server.py (1)
  • name (419-420)
src/fastmcp/fs/__init__.py (2)
src/fastmcp/fs/decorators.py (9)
  • prompt (296-296)
  • prompt (300-309)
  • prompt (313-321)
  • prompt (324-394)
  • resource (222-287)
  • tool (102-102)
  • tool (106-117)
  • tool (121-131)
  • tool (134-214)
src/fastmcp/fs/provider.py (1)
  • FileSystemProvider (34-244)
examples/filesystem-provider/server.py (1)
src/fastmcp/fs/provider.py (1)
  • FileSystemProvider (34-244)
examples/filesystem-provider/mcp/tools/greeting.py (1)
src/fastmcp/fs/decorators.py (4)
  • tool (102-102)
  • tool (106-117)
  • tool (121-131)
  • tool (134-214)
src/fastmcp/fs/provider.py (5)
src/fastmcp/fs/decorators.py (12)
  • PromptMeta (71-80)
  • ResourceMeta (55-67)
  • ToolMeta (40-51)
  • prompt (296-296)
  • prompt (300-309)
  • prompt (313-321)
  • prompt (324-394)
  • resource (222-287)
  • tool (102-102)
  • tool (106-117)
  • tool (121-131)
  • tool (134-214)
src/fastmcp/fs/discovery.py (1)
  • discover_and_import (207-239)
src/fastmcp/resources/resource.py (1)
  • Resource (208-397)
src/fastmcp/resources/template.py (1)
  • ResourceTemplate (97-305)
src/fastmcp/server/dependencies.py (1)
  • without_injected_parameters (86-140)
examples/filesystem-provider/mcp/prompts/assistant.py (1)
src/fastmcp/fs/decorators.py (4)
  • prompt (296-296)
  • prompt (300-309)
  • prompt (313-321)
  • prompt (324-394)
src/fastmcp/fs/discovery.py (2)
src/fastmcp/fs/decorators.py (1)
  • get_fs_meta (86-88)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
🪛 Ruff (0.14.10)
src/fastmcp/fs/decorators.py

264-267: Avoid specifying long messages outside the exception class

(TRY003)

src/fastmcp/fs/provider.py

103-103: Do not catch blind exception: Exception

(BLE001)


114-114: Unused method argument: file_path

(ARG002)

src/fastmcp/fs/discovery.py

146-148: Avoid specifying long messages outside the exception class

(TRY003)


160-160: Avoid specifying long messages outside the exception class

(TRY003)


170-170: Avoid specifying long messages outside the exception class

(TRY003)


231-231: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (36)
examples/filesystem-provider/mcp/tools/calculator.py (3)

1-3: LGTM! Clear module structure.

The module docstring and import statement are correct and follow the filesystem provider pattern.


6-13: LGTM! Well-structured tool with custom metadata.

The decorator correctly demonstrates custom naming, description, and tagging. The implementation is straightforward and correct.


16-24: LGTM! Good example of minimal decorator usage.

This function demonstrates using the decorator with minimal metadata while relying on the docstring for description. The detailed Args section in the docstring is a nice touch.

examples/filesystem-provider/server.py (3)

1-14: LGTM! Comprehensive module documentation.

The docstring provides clear usage instructions with specific commands for running and inspecting the server. The dev mode note is helpful for users.


16-19: LGTM! Correct imports.

All necessary imports are present and appropriate for the filesystem provider example.


21-29: LGTM! Clean provider and server setup.

The provider configuration correctly demonstrates the FileSystemProvider usage with dev mode enabled. The comments clearly explain the behavior, and the path construction is appropriate.

examples/filesystem-provider/mcp/tools/greeting.py (4)

1-3: LGTM! Clear module structure.

The module docstring appropriately highlights that multiple tools can exist in a single file, which is a helpful demonstration.


6-13: LGTM! Clean tool implementation.

The function demonstrates minimal decorator usage while providing good documentation through the docstring.


16-23: LGTM! Consistent implementation.

The farewell function follows the same clean pattern as the greet function, demonstrating consistency in the codebase.


26-28: LGTM! Helpful demonstration of ignored functions.

This is an excellent teaching example showing that undecorated helper functions are not registered by the filesystem provider. The private naming convention (leading underscore) further reinforces that this won't be discovered.

docs/docs.json (2)

112-112: LGTM! Filesystem provider added to navigation.

The new provider page is correctly added to the Providers section navigation. The placement between "local" and "mounting" is reasonable and maintains the documentation flow.


352-360: LGTM! Python SDK group correctly structured.

The new fastmcp.fs group is properly configured with appropriate pages covering the module's main components (init, decorators, discovery, provider). The structure is consistent with other SDK groups.

docs/servers/providers/filesystem.mdx (8)

1-12: LGTM! Proper frontmatter and introduction.

The YAML frontmatter correctly includes required title and description fields. The version badge and opening paragraph establish the component's purpose clearly.


14-25: LGTM! Clear motivation without overselling.

This section effectively explains the rationale for filesystem discovery using direct, user-focused language. The tradeoffs are presented honestly, following the guideline to state what something IS rather than using defensive patterns.


26-50: LGTM! Well-structured quick start with runnable examples.

The quick start section follows documentation guidelines by explaining before showing code, including all necessary imports, and providing complete, runnable examples. The progression from server setup to component creation is logical.


51-133: LGTM! Comprehensive decorator documentation with excellent examples.

The decorator section provides clear, runnable code examples for all three decorator types. Each example includes proper imports and demonstrates both basic and advanced usage patterns. The parameter documentation is clear and complete.


134-157: LGTM! Clear directory structure guidance with flexibility.

This section effectively communicates that directory structure is organizational rather than functional, using progressive disclosure to show conventional patterns before introducing alternatives. The examples are clear and practical.


158-187: LGTM! Comprehensive rules with practical examples.

The discovery rules table clearly documents all scanning behaviors. The package imports section effectively demonstrates when and why relative imports work, with a complete, runnable example.


188-217: LGTM! Clear guidance with appropriate warnings.

Both sections effectively communicate important operational concerns. The reload mode section appropriately warns against production use, and the error handling explanation helps users understand failure behavior without panic.


218-250: LGTM! Complete example with clear next steps.

The example project section provides a comprehensive, realistic example with proper directory structure and fully runnable code. The run instructions at the end give users clear next steps, following documentation best practices.

src/fastmcp/fs/__init__.py (1)

1-45: LGTM! Well-structured module with clear documentation.

The module provides a clean public API with intentional re-exports of core types (FileSystemProvider and the three decorators). The docstring examples effectively demonstrate the filesystem-based component discovery workflow.

examples/filesystem-provider/mcp/resources/config.py (1)

1-55: LGTM! Well-structured resource examples.

The three resource functions demonstrate different patterns effectively:

  • Static resource with fixed URI
  • Templated resource with parameter extraction
  • Custom metadata (name, mime_type, tags)

All functions have proper type annotations and return formatted JSON strings. The defensive fallback in get_env_config (line 35) handles unknown environments gracefully.

examples/filesystem-provider/mcp/prompts/assistant.py (1)

1-39: LGTM! Clear prompt examples demonstrating decorator flexibility.

Both prompt functions showcase different usage patterns:

  • code_review: Simple decorator usage with default parameters
  • explain: Custom name and metadata (description, tags)

Type annotations are complete, and the implementation demonstrates the decorator's flexibility effectively.

src/fastmcp/fs/decorators.py (4)

39-93: LGTM! Well-structured metadata system.

The dataclasses (ToolMeta, ResourceMeta, PromptMeta) provide clear type-safe metadata storage. The FSMeta union type and helper functions (get_fs_meta, has_fs_meta) offer a clean API for metadata access.


101-214: LGTM! Flexible tool decorator with proper overloads.

The decorator supports multiple invocation patterns (@tool, @tool(), @tool("name"), @tool(name="...")) with appropriate type hints. The defensive conversion of dict to ToolAnnotations (lines 180-181) handles both input formats gracefully.


222-287: LGTM! Resource decorator with proper URI validation.

The decorator correctly enforces URI requirements and provides clear error messaging. The defensive conversion of dict to Annotations (lines 269-270) maintains flexibility while ensuring type safety.

Note: The static analysis hint (TRY003) about the error message on lines 264-267 can be safely ignored—the inline message is clear and appropriate for this validation error.


295-394: LGTM! Consistent prompt decorator implementation.

The decorator follows the same flexible invocation patterns as @tool, maintaining API consistency across all three decorators. The implementation is clean and well-documented.

src/fastmcp/fs/discovery.py (4)

35-66: LGTM! Robust file discovery with proper filtering.

The function correctly excludes __init__.py and __pycache__ directories, and returns a sorted list for deterministic ordering. The handling of both directory and file roots (lines 49-53) adds useful flexibility.


74-109: LGTM! Package detection and module naming logic is sound.

The helper functions correctly:

  • Detect package boundaries by walking up the directory tree
  • Compute dotted module names from file paths
  • Handle the package structure appropriately

112-172: LGTM! Comprehensive module import with proper cleanup.

The function handles both package and non-package imports correctly. Key strengths:

  • Package root detection for relative import support
  • sys.path manipulation with proper ordering
  • Cleanup of sys.modules on failure (lines 169-170)
  • Clear error messages for debugging

Note: The TRY003 static analysis hints about error messages can be ignored—the detailed context in ImportError messages aids debugging.


207-239: Broad exception handling is intentional here.

The bare Exception catch on line 231 is appropriate for this use case. The function's purpose is to discover and collect components while tracking failures for later reporting, so catching all exceptions ensures one broken file doesn't stop the entire discovery process. The error is properly recorded in result.failed_files for the caller to handle.

The static analysis hint (BLE001) can be ignored in this context.

src/fastmcp/fs/provider.py (5)

1-31: Docstring + imports look consistent with the feature scope.
No concerns here; the module-level docstring and dependency set are clear.


124-138: Tool registration path is straightforward and matches the meta shape.


183-195: Prompt registration path looks consistent with the meta contract.


243-244: __repr__ is helpful for debugging.


74-112: No action needed; the suggestion misapplies async exception handling to synchronous code.

The _load_components() method is synchronous with no await statements. In Python, asyncio.CancelledError is raised only at await points by the asyncio runtime. Adding except asyncio.CancelledError: raise to synchronous code would be dead code that never executes, regardless of whether the method is called from an async context.

The bare Exception catch here is appropriate for defensive programming—it prevents one malformed component from breaking the entire load. The exception is logged, and execution continues to process remaining components, which is the correct behavior.

Likely an incorrect or invalid review comment.

Comment thread src/fastmcp/fs/provider.py
Comment thread src/fastmcp/fs/provider.py
Comment thread src/fastmcp/fs/provider.py Outdated
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The test test_warning_on_file_change in tests/fs/test_provider.py fails when run with pytest-xdist parallel execution due to Rich console formatting wrapping long file paths across multiple lines.

Root Cause: The test uses a simple substring check (assert "bad.py" in captured.err) to verify that warnings appear in the captured error output. When tests run in parallel with pytest-xdist (using --numprocesses auto --dist worksteal), the Rich logging library formats output for a constrained terminal width. This causes long file paths like /tmp/pytest-of-runner/pytest-6/popen-gw3/test_warning_on_file_change0/bad.py to wrap across lines:

-gw3/test_warning_on_file_change0/ba               
d.py: Failed to execute module

The filename "bad.py" is split as "ba" and "d.py" on separate lines, breaking the substring assertion.

Suggested Solution: Modify the test assertions in tests/fs/test_provider.py to be robust against line wrapping:

Files to modify:

  • tests/fs/test_provider.py (lines 209, 225, 234, 245, 264, 269)

Changes needed:

  1. Replace simple substring checks like assert "bad.py" in captured.err with a check that removes whitespace/newlines first
  2. Alternative: Check for "Failed to import" in the error instead of the specific filename
  3. Or normalize the captured output by removing all whitespace before checking

Example fix:

# Before:
assert "bad.py" in captured.err

# After (option 1 - normalize whitespace):
import re
assert "bad.py" in re.sub(r'\s+', ' ', captured.err)

# Or (option 2 - check for error message instead):
assert "Failed to import" in captured.err and str(bad_file) in re.sub(r'\s+', ' ', captured.err)
Detailed Analysis

Test behavior:

  • ✅ Passes when run in isolation: pytest tests/fs/test_provider.py::TestFileSystemProviderReloadMode::test_warning_on_file_change
  • ❌ Fails when run in parallel: pytest tests/fs/test_provider.py --numprocesses 4 --dist worksteal
  • ❌ Consistently fails in CI across all Python versions and platforms

Actual error output (parallel mode):

AssertionError: assert 'bad.py' in '[01/09/26 15:10:18] WARNING  Failed to import                     provider.py:92
                             /tmp/pytest-of-runner/pytest-6/popen               
                             -gw3/test_warning_on_file_change0/ba               
                             d.py: Failed to execute module                     
                             /tmp/pytest-of-runner/pytest-6/popen               
                             -gw3/test_warning_on_file_change0/ba               
                             d.py: division by zero'

The filename is present but wrapped: ba<newline>d.py

Related tests with same issue:

  • test_warning_deduplication_same_file (line 209) - uses assert "bad.py" in captured.err
  • test_warning_on_file_change (lines 225, 234) - uses assert "bad.py" in captured.err
  • test_warning_cleared_when_fixed (lines 245, 264, 269) - uses assert "tool.py" in captured.err

All of these tests are susceptible to the same issue, though the failure may be intermittent depending on path lengths and terminal width.

Why this happens:

  1. CI runs tests with --numprocesses auto --maxprocesses 4 --dist worksteal
  2. Each pytest-xdist worker has a constrained terminal width
  3. Rich console formatter wraps long lines to fit the terminal
  4. Subprocess paths in /tmp/pytest-of-runner/pytest-N/popen-gwN/ are long enough to trigger wrapping
  5. The wrap can occur mid-filename, breaking substring matches
Related Files
  • tests/fs/test_provider.py: Contains all failing tests
  • src/fastmcp/fs/provider.py:92: Where the warning is logged
  • .github/workflows/run-tests.yml:58: CI configuration using parallel test execution

🤖 This analysis was generated by the FastMCP test failure triage bot

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: Three tests in test_provider.py fail because they check if a filename (e.g., "bad.py") appears in the warning message, but the logger outputs the full absolute path instead of just the filename.

Root Cause: In src/fastmcp/fs/provider.py:96, the warning message logs the full file_path object:

logger.warning(f"Failed to import {file_path}: {error}")

This produces log output like:

  • Linux/macOS: /tmp/pytest-0/test_warning_on_file_change0/bad.py
  • Windows: C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\test_warning_on_file_change0\bad.py

The tests check for the substring "bad.py" (or "tool.py"), which works on Linux/macOS where the path separator is /, but fails on Windows where the full path doesn't contain the simple filename as a distinct substring due to path formatting.

Suggested Solution: Modify line 96 in src/fastmcp/fs/provider.py to log just the filename:

logger.warning(f"Failed to import {file_path.name}: {error}")

Or if you want to keep the full path but make tests more robust, update the test assertions to use Path(file_path).name or check for the filename differently:

assert bad_file.name in captured.err  # Instead of assert "bad.py" in captured.err

The first approach (changing the logger) is cleaner since warning messages with just the filename are more readable for users, while still providing the error context. The full absolute path is less helpful in warning messages and can clutter logs.

Detailed Analysis

Failed Tests:

  1. test_warning_deduplication_same_file - Checks that warnings for broken files don't repeat
  2. test_warning_on_file_change - Checks that warnings reappear when a broken file changes
  3. test_warning_cleared_when_fixed - Checks that warnings clear when files are fixed

All three tests create temporary files and check for the filename in captured.err, but the logger outputs absolute paths.

Log Evidence (Windows failure):

WARNING  Failed to import C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\test_warning_deduplication_sam0\bad.py: Failed to execute module...

The test does assert 'bad.py' in captured.err but the string bad.py doesn't exist as a substring in the Windows path format.

Related Files
  • src/fastmcp/fs/provider.py:96 - Where the warning is logged with full path
  • tests/fs/test_provider.py:200-272 - The three failing test methods
    • Line 209: assert "bad.py" in captured.err
    • Line 225: assert "bad.py" in captured.err
    • Line 234: assert "bad.py" in captured.err
    • Line 245: assert "tool.py" in captured.err
    • Line 262: assert "tool.py" not in captured.err
    • Line 272: assert "tool.py" in captured.err

jlowin added 2 commits January 9, 2026 12:19
Introduces a new provider that scans directories for Python files and
automatically registers @tool, @resource, and @prompt decorated functions.
Eliminates coordination between tool files and server files.
- Reload cached package modules using importlib.reload()
- Add async lock and to_thread for non-blocking reload
- Clear tool transformations on reload
- Remove unused file_path parameter
@jlowin jlowin force-pushed the filesystem-provider branch from 274b617 to 5e1a280 Compare January 9, 2026 17:19
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/fastmcp/fs/discovery.py (1)

137-160: Accumulated sys.path entries may grow unbounded.

Each call to import_module_from_file inserts directories into sys.path without ever removing them. In long-running processes with frequent reloads, this could lead to an ever-growing sys.path. Consider tracking added paths and either reusing existing entries or cleaning up on provider reset.

src/fastmcp/fs/provider.py (1)

143-185: LGTM!

Smart heuristic for distinguishing templates from static resources. The logic correctly identifies templated resources by checking both URI placeholders and function parameters.

The inline import of without_injected_parameters at line 151 is slightly unusual—consider moving it to the top of the file if it doesn't cause circular import issues.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 274b617 and ba9f044.

⛔ Files ignored due to path filters (3)
  • tests/fs/test_decorators.py is excluded by none and included by none
  • tests/fs/test_discovery.py is excluded by none and included by none
  • tests/fs/test_provider.py is excluded by none and included by none
📒 Files selected for processing (11)
  • docs/docs.json
  • docs/servers/providers/filesystem.mdx
  • examples/filesystem-provider/mcp/prompts/assistant.py
  • examples/filesystem-provider/mcp/resources/config.py
  • examples/filesystem-provider/mcp/tools/calculator.py
  • examples/filesystem-provider/mcp/tools/greeting.py
  • examples/filesystem-provider/server.py
  • src/fastmcp/fs/__init__.py
  • src/fastmcp/fs/decorators.py
  • src/fastmcp/fs/discovery.py
  • src/fastmcp/fs/provider.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • docs/docs.json
  • src/fastmcp/fs/init.py
  • examples/filesystem-provider/server.py
  • examples/filesystem-provider/mcp/prompts/assistant.py
  • docs/servers/providers/filesystem.mdx
  • examples/filesystem-provider/mcp/tools/calculator.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/fs/discovery.py
  • src/fastmcp/fs/provider.py
  • src/fastmcp/fs/decorators.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/fs/discovery.py
🧬 Code graph analysis (3)
examples/filesystem-provider/mcp/tools/greeting.py (2)
src/fastmcp/fs/decorators.py (4)
  • tool (102-102)
  • tool (106-117)
  • tool (121-131)
  • tool (134-214)
src/fastmcp/server/server.py (1)
  • name (419-420)
examples/filesystem-provider/mcp/resources/config.py (1)
src/fastmcp/fs/decorators.py (1)
  • resource (222-287)
src/fastmcp/fs/discovery.py (2)
src/fastmcp/fs/decorators.py (1)
  • get_fs_meta (86-88)
src/fastmcp/utilities/logging.py (1)
  • get_logger (14-26)
🪛 Ruff (0.14.10)
src/fastmcp/fs/discovery.py

149-151: Avoid specifying long messages outside the exception class

(TRY003)


163-163: Avoid specifying long messages outside the exception class

(TRY003)


173-173: Avoid specifying long messages outside the exception class

(TRY003)


234-234: Do not catch blind exception: Exception

(BLE001)

src/fastmcp/fs/provider.py

107-107: Do not catch blind exception: Exception

(BLE001)

src/fastmcp/fs/decorators.py

264-267: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (19)
examples/filesystem-provider/mcp/tools/greeting.py (1)

1-28: LGTM!

Clean example demonstrating the filesystem-based tool discovery pattern. The decorated greet and farewell functions have proper type annotations and docstrings, and the undecorated _format_message helper correctly illustrates that private functions are ignored by the discovery mechanism.

examples/filesystem-provider/mcp/resources/config.py (1)

1-55: LGTM!

Excellent example coverage demonstrating static resources, templated resources with URI parameters, and resources with custom metadata. The code is well-documented and correctly uses the @resource decorator API.

src/fastmcp/fs/discovery.py (6)

27-33: LGTM!

Well-structured dataclass for encapsulating discovery results with proper field defaults.


35-66: LGTM!

Solid implementation with good edge-case handling for non-existent paths and single-file inputs. The sorted output ensures deterministic discovery order.


69-109: LGTM!

Package detection logic is well-structured. The upward traversal in _find_package_root correctly handles nested packages, and _compute_module_name properly computes the dotted module name.


168-175: Broad exception catch is acceptable here.

The except Exception is justified since exec_module executes arbitrary user code that can raise any exception type. The cleanup of sys.modules prevents stale partial modules from lingering.


178-207: LGTM!

Clean implementation that correctly filters private attributes and gracefully handles edge cases with the AttributeError catch.


210-241: LGTM!

The main entry point correctly orchestrates discovery, import, and component extraction. Catching both ImportError and broader Exception is reasonable since user module code can fail in arbitrary ways, and failures are properly recorded rather than silently swallowed.

src/fastmcp/fs/decorators.py (5)

39-83: LGTM!

Well-designed discriminated union using literal type fields. The dataclasses provide clean metadata storage for all three component types.


86-93: LGTM!

Clean helper functions for metadata access.


134-214: LGTM!

Comprehensive decorator implementation supporting all common calling patterns. The overloads provide excellent type hints, and the recursive call for the @tool("name") pattern is elegant.


263-267: Good error handling for common misuse.

The explicit TypeError with a helpful message correctly guides users who accidentally use @resource without the required URI argument. The message length is justified for user clarity.


324-394: LGTM!

Consistent implementation following the same patterns as @tool. The simpler metadata structure appropriately reflects prompt requirements.

src/fastmcp/fs/provider.py (6)

59-75: LGTM!

Well-designed initialization with eager loading for early error detection. The lazy lock creation correctly handles the case where no event loop is running during __init__, and mtime-based warning deduplication is a nice touch.


77-115: LGTM!

Robust loading logic with good error handling. The warning deduplication based on file mtime prevents log spam during development, and clearing warnings for successfully fixed files provides good developer experience. The broad exception catch during registration is appropriate for resilience—individual component failures shouldn't prevent other components from loading.


117-126: LGTM!

Clean type-based dispatch to specific registration handlers.


128-141: LGTM!

Clean delegation to Tool.from_function with all metadata fields properly forwarded.


200-216: LGTM!

Correctly implemented double-checked locking pattern with lazy lock creation. Using asyncio.to_thread appropriately offloads the blocking filesystem I/O from the event loop.


220-261: LGTM!

Consistent override pattern ensuring reload mode works for all access methods. The __repr__ provides useful debugging information.

@jlowin jlowin merged commit 87f2edd into main Jan 9, 2026
12 checks passed
@jlowin jlowin deleted the filesystem-provider branch January 9, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. provider Related to the FastMCP Provider class server Related to FastMCP server implementation or server-side functionality. v3 Targeted for FastMCP 3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add FileSystemProvider for convention-based component discovery

1 participant