Skip to content

Conversation

@jlowin
Copy link
Owner

@jlowin jlowin commented Jan 12, 2026

Decorators (@tool, @resource, @prompt) now return the original function unchanged instead of transforming it into a component object. This aligns FastMCP with how decorators work in Flask, FastAPI, and Typer.

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

# v3: greet is still your function
greet("World")  # "Hello, World!"

# v2: greet was a FunctionTool object
# isinstance(greet, FunctionTool)  # was True

Why this matters:

  • Functions stay callable for testing and reuse
  • Instance methods work naturally with mcp.add_tool(obj.method)
  • More intuitive - decorators don't transform what you wrote

Breaking change: Code that treats decorated functions as FunctionTool/FunctionResource/FunctionPrompt objects will break. Set FASTMCP_DECORATOR_MODE=object for v2 behavior.

Closes #2292

Changes decorator behavior so @tool, @resource, and @prompt return
the original function unchanged. Functions are registered with the
server normally but remain callable for testing and reuse.

Adds decorator_mode setting ("function" or "object") for v2 compatibility.
@jlowin jlowin added the feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. label Jan 12, 2026
@marvin-context-protocol marvin-context-protocol bot added breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. v3 Targeted for FastMCP 3 server Related to FastMCP server implementation or server-side functionality. labels Jan 12, 2026
@marvin-context-protocol
Copy link
Contributor

marvin-context-protocol bot commented Jan 12, 2026

Test Failure Analysis

Summary: Static analysis failed due to file size violations - 14 files exceed their configured line limits.

Root Cause: The decorator refactoring changes caused multiple files to exceed their loq (file size enforcement) limits. The failures include both source files, test files, and documentation that were modified or added in this PR.

Suggested Solution: Run loq baseline to automatically update all limits to current file sizes:

uv run loq baseline

This will update loq.toml with the correct line counts for all affected files.

Detailed Analysis

The loq tool enforces file size limits to encourage code modularity. When files exceed configured limits, the pre-commit hook fails.

From CI logs (workflow run 20942110036):

loq (file size limits)...................................................Failed
  ✖    593 > 592    docs/docs.json
  ✖    714 > 713    tests/deprecated/test_import_server.py
  ✖    617 > 616    tests/server/middleware/test_caching.py
  ✖    509 > 508    tests/server/middleware/test_tool_injection.py
  ✖    650 > 648    docs/servers/context.mdx
  ✖    954 > 952    src/fastmcp/tools/tool_transform.py
  ✖  1_748 > 1_741  tests/tools/test_tool_transform.py
  ✖    576 > 565    src/fastmcp/resources/template.py
  ✖  1_554 > 1_538  tests/server/providers/test_local_provider_tools.py
  ✖  2_280 > 2_228  docs/changelog.mdx
  ✖    594 > 500    src/fastmcp/tools/function_tool.py
  ✖    604 > 500    tests/server/auth/test_authorization.py
  ✖  2_799 > 2_682  src/fastmcp/server/server.py
  ✖    997 > 738    src/fastmcp/server/providers/local_provider.py
  14 violations

Analysis:

  • Largest increases: local_provider.py (+259 lines), server.py (+117 lines), changelog.mdx (+52 lines)
  • Two new files need baseline entries: function_tool.py (594 lines), test_authorization.py (604 lines)
  • The increases are expected given the refactoring consolidates function components into dedicated files

This is a straightforward fix - the code changes are legitimate, and the file size limits just need to be updated to reflect the new reality.

Related Files

Files exceeding limits:

  • src/fastmcp/server/providers/local_provider.py (997 > 738) - Largest violation, +259 lines
  • src/fastmcp/server/server.py (2799 > 2682) - +117 lines
  • docs/changelog.mdx (2280 > 2228) - +52 lines
  • tests/server/providers/test_local_provider_tools.py (1554 > 1538) - +16 lines
  • src/fastmcp/resources/template.py (576 > 565) - +11 lines
  • tests/tools/test_tool_transform.py (1748 > 1741) - +7 lines
  • src/fastmcp/tools/tool_transform.py (954 > 952) - +2 lines
  • docs/servers/context.mdx (650 > 648) - +2 lines
  • Plus 6 files with +1 line violations

New files needing baseline entries:

  • src/fastmcp/tools/function_tool.py (594 lines) - New file from refactoring
  • tests/server/auth/test_authorization.py (604 lines) - New file from refactoring

Latest analysis from workflow run 20942110036 (commit a8258f1). Updated by marvin.

@marvin-context-protocol
Copy link
Contributor

marvin-context-protocol bot commented Jan 12, 2026

Test Failure Analysis

Summary: Windows-only test timeout in test_oauth_proxy_storage.py::test_register_and_get_client due to SQLite file locking in the DiskStore backing the OAuth proxy.

Root Cause: The test hangs at sqlite3.connect() when initializing storage for the OAuth proxy. The stack trace shows:

File "src/fastmcp/server/auth/oauth_proxy.py", line 1010, in register_client
File "key_value/aio/adapters/pydantic/base.py", line 207, in put
File "key_value/aio/stores/base.py", line 323, in put
File "key_value/aio/stores/disk/multi_store.py", line 116, in _setup_collection
File "diskcache/core.py", line 591, in __init__
File "diskcache/core.py", line 623, in _con
  con = self._local.con = sqlite3.connect(

This is a Windows-specific issue (tests pass on Linux/macOS). The test uses MultiDiskStore with temporary directories, but on Windows, SQLite database connections aren't being established within the 5-second test timeout. This appears to be a known issue with diskcache on Windows where SQLite connection initialization can block indefinitely.

This is NOT related to the decorator changes in this PR - it's a pre-existing Windows test environment issue with DiskStore/SQLite resource management.

Suggested Solution:

Option 1 (Recommended): Skip disk-based storage tests on Windows and use in-memory storage instead:

@pytest.mark.skipif(sys.platform == 'win32', reason="SQLite disk storage flaky on Windows")
async def test_register_and_get_client(self, jwt_verifier, temp_storage):
    ...

# Or modify the fixture to use MemoryStore on Windows:
@pytest.fixture
async def temp_storage(self) -> AsyncGenerator[AsyncKeyValue, None]:
    if sys.platform == 'win32':
        # Use MemoryStore on Windows to avoid SQLite file locking issues
        yield MemoryStore()
    else:
        # Use disk storage on Unix for more realistic testing
        with tempfile.TemporaryDirectory() as temp_dir:
            disk_store = MultiDiskStore(base_directory=Path(temp_dir))
            yield disk_store
            await disk_store.close()

Option 2: Increase the timeout for this specific test on Windows:

@pytest.mark.timeout(30 if sys.platform == 'win32' else 5)
async def test_register_and_get_client(self, jwt_verifier, temp_storage):
    ...

Option 3: Mark the entire test class as integration tests to exclude from the main test suite:

@pytest.mark.integration
class TestOAuthProxyStorage:
    ...
Detailed Analysis

From workflow run 20941161722 (commit fb4f1e9):

The test test_register_and_get_client times out after 5 seconds when trying to create a SQLite connection on Windows. The issue occurs when:

  1. Test creates a MultiDiskStore with a temporary directory
  2. OAuth proxy tries to register a client
  3. Key-value store attempts to set up a collection
  4. diskcache tries to initialize a SQLite database
  5. sqlite3.connect() blocks and never returns

Known diskcache/SQLite Windows issues:

  • SQLite file locking behaves differently on Windows vs Unix
  • Multiple concurrent connections to the same SQLite database can cause indefinite blocking on Windows
  • The diskcache library has known issues with timeout configuration on Windows (see issue #85 and issue #131)

Why it doesn't affect Linux/macOS: Unix systems handle SQLite file locking more gracefully and have better support for concurrent database access.

Related Files
  • tests/server/auth/test_oauth_proxy_storage.py:72 - The failing test (test_register_and_get_client)
  • tests/server/auth/test_oauth_proxy_storage.py:32-37 - The temp_storage fixture that creates MultiDiskStore
  • src/fastmcp/server/auth/oauth_proxy.py:1010 - OAuthProxy.register_client() calls storage
  • key_value/aio/stores/disk/multi_store.py:116 - MultiDiskStore creates diskcache.Cache

Updated for workflow run 20941161722 - commit fb4f1e9

Move FunctionTool/FunctionResource/FunctionPrompt and their metadata
classes into function_*.py files. Base classes now use @classmethod
with keyword-only args for LSP compatibility. Old import paths work
via __getattr__ with deprecation warnings (respects settings).
# Conflicts:
#	src/fastmcp/prompts/prompt.py
#	src/fastmcp/resources/resource.py
#	src/fastmcp/server/providers/local_provider.py
#	src/fastmcp/server/server.py
#	src/fastmcp/tools/tool.py
- Add auth=meta.auth to filesystem_discovery.py for all component types
- Add tool decorator to __getattr__ deprecation re-exports in tool.py
- Add test for tool decorator deprecated import path
@jlowin jlowin marked this pull request as ready for review January 13, 2026 01:45
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 51 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 1fdf655 and bc2a25c.

📒 Files selected for processing (2)
  • src/fastmcp/prompts/function_prompt.py
  • src/fastmcp/tools/function_tool.py

Walkthrough

This PR changes decorators (@tool, @resource, @prompt) to return the original function by default (settings.decorator_mode = "function") instead of constructing component objects, adds a deprecated "object" mode for legacy behavior, and introduces standalone function-backed component implementations (FunctionTool, FunctionResource, FunctionPrompt) in new modules. It adds utilities for resolving TaskConfig and reading fastmcp metadata, updates discovery and provider code to materialize components from decorated functions at registration/discovery time, and adjusts public exports with compatibility shims and deprecation warnings.

Possibly related PRs

  • PR 2832: Implements standalone @tool/@resource/@prompt and moves function-backed component implementations to dedicated modules (strong overlap in decorator behavior and new Function* types).
  • PR 2680: Refactors decorator/registration logic into LocalProvider and updates server registration paths (overlaps provider/server changes and decorator routing).
  • PR 2823: Extends filesystem discovery to materialize decorator-declared components (direct overlap with extract_components changes).
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Decorators return functions instead of component objects' accurately and concisely summarizes the primary architectural change in the changeset.
Description check ✅ Passed The description covers the main change, provides a concrete code example, explains the rationale, and documents the breaking change with a migration path. All required checklist items are marked, though testing/documentation updates are claimed rather than verifiable from the diff.

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


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
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/fastmcp/tools/tool_transform.py (1)

1-19: Pipeline failure: file size limit exceeded.

Static analysis reports the file exceeds the 952-line limit (currently 954 lines). While the import changes in this PR are minimal, consider whether this file could be split further. The ArgTransform, ArgTransformConfig, ToolTransformConfig, and helper functions could potentially be extracted to a separate module (e.g., tool_transform_config.py) to reduce file size and improve maintainability.

src/fastmcp/server/providers/local_provider.py (1)

1-997: Pipeline failure: File size limit exceeded.

The static analysis reports this file exceeds the line limit (997 > 738). The duplicated patterns identified above contribute significantly to the file size. Refactoring the common patterns into shared utilities would help address this limit while also improving maintainability.

src/fastmcp/tools/function_tool.py (1)

1-604: Pipeline failure: File size limit exceeded.

The static analysis reports this file exceeds the line limit (604 > 500). Consider splitting ParsedFunction into a separate module (e.g., parsed_function.py) since it's a self-contained data class with its own logic.

🧹 Nitpick comments (7)
src/fastmcp/settings.py (1)

352-367: Consider adding a deprecation warning validator for "object" mode.

The description notes that "object" mode is deprecated, but unlike enable_new_openapi_parser (lines 128-139), there's no field_validator to emit a DeprecationWarning when this mode is selected. This would provide consistent behavior and help users migrate.

♻️ Suggested validator
@field_validator("decorator_mode", mode="after")
@classmethod
def _warn_decorator_mode_deprecated(cls, v: str) -> str:
    if v == "object":
        warnings.warn(
            "decorator_mode='object' is deprecated. "
            "Decorators now return the original function by default. "
            "See https://gofastmcp.com/development/upgrade-guide for migration.",
            DeprecationWarning,
            stacklevel=2,
        )
    return v
src/fastmcp/server/server.py (1)

1-2799: Pipeline failure: File size limit exceeded.

The pipeline reports LOQ: file size limit exceeded (2799 > 2682). This is an operational concern—the file has grown beyond the configured limit. Consider refactoring to extract some functionality into separate modules to reduce file size.

Potential candidates for extraction:

  • HTTP transport methods (run_http_async, http_app) → dedicated transport module
  • Mount/import server logic → dedicated composition module
  • Factory methods (from_openapi, from_fastapi, create_proxy) → dedicated factory module
src/fastmcp/resources/function_resource.py (1)

241-282: Redundant ResourceMeta construction in create_resource.

The resource_meta object is created on lines 251-263 but is only used in the else branch (line 282). For the if branch (lines 265-280), all individual parameters are passed directly to ResourceTemplate.from_function. Consider restructuring to avoid redundant object creation when not needed.

src/fastmcp/server/providers/local_provider.py (1)

578-599: Significant code duplication across tool/resource/prompt decorators.

The unbound method detection logic (checking for self/cls as first parameter) is duplicated nearly identically three times:

  • Lines 578-599 (tool)
  • Lines 748-766 (resource)
  • Lines 910-931 (prompt)

Consider extracting this into a shared helper function to reduce duplication and simplify maintenance.

Suggested helper function
def _validate_not_unbound_method(
    fn: Callable[..., Any],
    decorator_name: str,
    import_path: str,
    uri_or_name: str | None = None,
) -> None:
    """Raise TypeError if fn appears to be an unbound method."""
    try:
        params = list(inspect.signature(fn).parameters.keys())
    except (ValueError, TypeError):
        params = []
    if params and params[0] in ("self", "cls"):
        fn_name = getattr(fn, "__name__", "function")
        uri_hint = f"('{uri_or_name}')" if uri_or_name else ""
        raise TypeError(
            f"The function '{fn_name}' has '{params[0]}' as its first parameter. "
            f"Use the standalone @{decorator_name} decorator and register the bound method:\n\n"
            f"    from {import_path} import {decorator_name}\n\n"
            f"    class MyClass:\n"
            f"        @{decorator_name}{uri_hint}\n"
            f"        def {fn_name}(...):\n"
            f"            ...\n\n"
            f"    obj = MyClass()\n"
            f"    mcp.add_{decorator_name}(obj.{fn_name})\n\n"
            f"See https://gofastmcp.com/patterns/decorating-methods"
        )

Also applies to: 748-766, 910-931

src/fastmcp/prompts/function_prompt.py (2)

200-202: Silent exception swallowing during schema generation.

The bare except Exception: pass silently ignores schema generation failures. While the intent is to gracefully degrade when schema enhancement fails, this makes debugging difficult. Consider logging at debug level to aid troubleshooting.

Proposed fix
                         except Exception:
                             # If schema generation fails, skip enhancement
-                            pass
+                            logger.debug(
+                                f"Schema generation failed for parameter '{param_name}', "
+                                "skipping description enhancement"
+                            )

134-143: Comment is misplaced after the lambda check.

The comment # Reject functions with *args or **kwargs on line 136 appears after the lambda ValueError on line 135, making it look like it's documenting the lambda check rather than the subsequent validation loop. This is a minor readability issue.

Proposed fix
         if func_name == "<lambda>":
             raise ValueError("You must provide a name for lambda functions")
-            # Reject functions with *args or **kwargs
+
+        # Reject functions with *args or **kwargs
         sig = inspect.signature(fn)
src/fastmcp/tools/function_tool.py (1)

199-201: Silent exception swallowing during type hint resolution.

Same issue as in function_prompt.py - the bare except Exception: pass silently ignores failures when resolving string type annotations. Consider logging at debug level.

Proposed fix
             except Exception:
                 # If resolution fails, keep the string annotation
-                pass
+                logger.debug(f"Unable to resolve type hint for return annotation: {output_type!r}")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 766641a and fb4f1e9.

⛔ Files ignored due to path filters (15)
  • tests/deprecated/test_function_component_imports.py is excluded by none and included by none
  • tests/deprecated/test_import_server.py is excluded by none and included by none
  • tests/prompts/test_standalone_decorator.py is excluded by none and included by none
  • tests/resources/test_function_resources.py is excluded by none and included by none
  • tests/resources/test_resource_template.py is excluded by none and included by none
  • tests/resources/test_resources.py is excluded by none and included by none
  • tests/resources/test_standalone_decorator.py is excluded by none and included by none
  • tests/server/middleware/test_caching.py is excluded by none and included by none
  • tests/server/middleware/test_tool_injection.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_prompts.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_tools.py is excluded by none and included by none
  • tests/server/tasks/test_sync_function_task_disabled.py is excluded by none and included by none
  • tests/server/test_providers.py is excluded by none and included by none
  • tests/tools/test_standalone_decorator.py is excluded by none and included by none
  • tests/tools/test_tool_transform.py is excluded by none and included by none
📒 Files selected for processing (18)
  • docs/development/upgrade-guide.mdx
  • docs/development/v3-notes/v3-features.mdx
  • src/fastmcp/decorators.py
  • src/fastmcp/prompts/__init__.py
  • src/fastmcp/prompts/function_prompt.py
  • src/fastmcp/prompts/prompt.py
  • src/fastmcp/resources/__init__.py
  • src/fastmcp/resources/function_resource.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/server/providers/filesystem_discovery.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/server/sampling/sampling_tool.py
  • src/fastmcp/server/server.py
  • src/fastmcp/settings.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/tools/function_tool.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/tools/tool_transform.py
🧰 Additional context used
📓 Path-based instructions (3)
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/development/upgrade-guide.mdx
  • docs/development/v3-notes/v3-features.mdx
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase

Files:

  • src/fastmcp/decorators.py
  • src/fastmcp/settings.py
  • src/fastmcp/server/sampling/sampling_tool.py
  • src/fastmcp/tools/tool_transform.py
  • src/fastmcp/server/server.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/resources/function_resource.py
  • src/fastmcp/prompts/function_prompt.py
  • src/fastmcp/server/providers/filesystem_discovery.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/tools/function_tool.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/resources/__init__.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/prompts/__init__.py
  • src/fastmcp/prompts/prompt.py
src/**/__init__.py

📄 CodeRabbit inference engine (AGENTS.md)

Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*

Files:

  • src/fastmcp/resources/__init__.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/prompts/__init__.py
🧠 Learnings (5)
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/prompts/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Prompts (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • docs/development/upgrade-guide.mdx
  • src/fastmcp/prompts/function_prompt.py
  • src/fastmcp/prompts/__init__.py
  • src/fastmcp/prompts/prompt.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/tools/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Tools (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • docs/development/upgrade-guide.mdx
  • src/fastmcp/tools/tool.py
  • src/fastmcp/tools/function_tool.py
  • src/fastmcp/tools/__init__.py
📚 Learning: 2026-01-12T16:25:10.972Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-12T16:25:10.972Z
Learning: Applies to src/**/__init__.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*

Applied to files:

  • src/fastmcp/server/sampling/sampling_tool.py
  • src/fastmcp/tools/tool_transform.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/tools/function_tool.py
  • src/fastmcp/resources/__init__.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/prompts/__init__.py
  • src/fastmcp/prompts/prompt.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/resources/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Resources (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/__init__.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Maintain consistency across all four MCP object types (Tools, Resources, Resource Templates, and Prompts) when implementing similar features

Applied to files:

  • src/fastmcp/server/providers/local_provider.py
🧬 Code graph analysis (10)
src/fastmcp/decorators.py (5)
src/fastmcp/prompts/function_prompt.py (1)
  • PromptMeta (56-67)
src/fastmcp/resources/function_resource.py (1)
  • ResourceMeta (43-57)
src/fastmcp/server/tasks/config.py (1)
  • TaskConfig (42-152)
src/fastmcp/tools/function_tool.py (1)
  • ToolMeta (72-87)
src/fastmcp/utilities/components.py (1)
  • FastMCPMeta (21-22)
src/fastmcp/server/sampling/sampling_tool.py (1)
src/fastmcp/tools/function_tool.py (1)
  • ParsedFunction (117-256)
src/fastmcp/tools/tool.py (2)
src/fastmcp/tools/function_tool.py (3)
  • FunctionTool (259-462)
  • from_function (125-256)
  • from_function (285-417)
src/fastmcp/tools/tool_transform.py (1)
  • ArgTransform (96-207)
src/fastmcp/resources/function_resource.py (6)
src/fastmcp/decorators.py (1)
  • resolve_task_config (17-19)
src/fastmcp/resources/resource.py (3)
  • Resource (209-410)
  • from_function (234-267)
  • read (288-298)
src/fastmcp/server/dependencies.py (1)
  • transform_context_annotations (131-247)
src/fastmcp/server/tasks/config.py (4)
  • TaskConfig (42-152)
  • from_bool (80-89)
  • validate_function (99-152)
  • supports_tasks (91-97)
src/fastmcp/utilities/types.py (1)
  • get_fn_name (34-35)
src/fastmcp/resources/template.py (1)
  • ResourceTemplate (101-316)
src/fastmcp/prompts/function_prompt.py (5)
src/fastmcp/decorators.py (1)
  • resolve_task_config (17-19)
src/fastmcp/prompts/prompt.py (7)
  • Prompt (189-393)
  • PromptArgument (95-104)
  • PromptResult (107-186)
  • from_function (229-261)
  • render (263-274)
  • convert_result (276-310)
  • register_with_docket (366-370)
src/fastmcp/server/dependencies.py (2)
  • transform_context_annotations (131-247)
  • without_injected_parameters (433-491)
src/fastmcp/server/tasks/config.py (2)
  • TaskConfig (42-152)
  • supports_tasks (91-97)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (364-401)
src/fastmcp/resources/resource.py (1)
src/fastmcp/resources/function_resource.py (2)
  • FunctionResource (60-210)
  • from_function (76-184)
src/fastmcp/tools/function_tool.py (10)
src/fastmcp/server/dependencies.py (1)
  • transform_context_annotations (131-247)
src/fastmcp/server/tasks/config.py (4)
  • TaskConfig (42-152)
  • supports_tasks (91-97)
  • from_bool (80-89)
  • validate_function (99-152)
src/fastmcp/server/providers/local_provider.py (4)
  • tool (456-473)
  • tool (476-493)
  • tool (499-682)
  • decorator (747-813)
src/fastmcp/tools/tool.py (6)
  • Tool (123-387)
  • ToolResult (62-120)
  • from_function (188-222)
  • to_mcp_tool (159-185)
  • run (224-234)
  • convert_result (236-274)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (364-401)
src/fastmcp/utilities/types.py (3)
  • Audio (307-362)
  • File (365-448)
  • replace_type (451-485)
src/fastmcp/resources/function_resource.py (2)
  • from_function (76-184)
  • decorator (302-311)
src/fastmcp/prompts/prompt.py (2)
  • from_function (229-261)
  • convert_result (276-310)
src/fastmcp/server/sampling/sampling_tool.py (2)
  • from_function (76-109)
  • run (46-61)
src/fastmcp/server/context.py (1)
  • debug (518-532)
src/fastmcp/server/providers/local_provider.py (5)
src/fastmcp/prompts/function_prompt.py (8)
  • FunctionPrompt (70-333)
  • prompt (337-337)
  • prompt (339-349)
  • prompt (351-362)
  • prompt (365-444)
  • from_function (76-223)
  • PromptMeta (56-67)
  • decorator (419-428)
src/fastmcp/prompts/prompt.py (2)
  • Prompt (189-393)
  • from_function (229-261)
src/fastmcp/resources/resource.py (2)
  • Resource (209-410)
  • from_function (234-267)
src/fastmcp/resources/template.py (3)
  • ResourceTemplate (101-316)
  • from_function (128-155)
  • from_function (451-576)
src/fastmcp/decorators.py (1)
  • get_fastmcp_meta (29-41)
src/fastmcp/resources/__init__.py (3)
src/fastmcp/resources/function_resource.py (3)
  • FunctionResource (60-210)
  • ResourceMeta (43-57)
  • resource (213-313)
src/fastmcp/server/providers/local_provider.py (1)
  • resource (684-815)
src/fastmcp/server/server.py (1)
  • resource (1996-2080)
src/fastmcp/prompts/prompt.py (1)
src/fastmcp/prompts/function_prompt.py (2)
  • FunctionPrompt (70-333)
  • from_function (76-223)
🪛 GitHub Actions: Run static analysis
src/fastmcp/tools/tool_transform.py

[error] 954-954: LOQ: file size limit exceeded (954 > 952).

src/fastmcp/server/server.py

[error] 2799-2799: LOQ: file size limit exceeded (2799 > 2682).

src/fastmcp/tools/function_tool.py

[error] 604-604: LOQ: file size limit exceeded (604 > 500).

src/fastmcp/server/providers/local_provider.py

[error] 997-997: LOQ: file size limit exceeded (997 > 738).

🪛 Ruff (0.14.10)
src/fastmcp/tools/tool.py

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

(TRY003)

src/fastmcp/resources/function_resource.py

125-128: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


236-239: Avoid specifying long messages outside the exception class

(TRY003)

src/fastmcp/prompts/function_prompt.py

112-115: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)


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

(TRY003)


200-202: try-except-pass detected, consider logging the exception

(S110)


200-200: Do not catch blind exception: Exception

(BLE001)


259-262: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)


383-386: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)

src/fastmcp/resources/resource.py

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

(TRY003)

src/fastmcp/tools/function_tool.py

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

(TRY003)


139-141: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


152-154: Avoid specifying long messages outside the exception class

(TRY003)


199-201: try-except-pass detected, consider logging the exception

(S110)


199-199: Do not catch blind exception: Exception

(BLE001)


335-338: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


398-401: Avoid specifying long messages outside the exception class

(TRY003)


527-530: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)

src/fastmcp/server/providers/local_provider.py

272-275: Avoid specifying long messages outside the exception class

(TRY003)


314-317: Avoid specifying long messages outside the exception class

(TRY003)


588-599: Avoid specifying long messages outside the exception class

(TRY003)


739-743: Avoid specifying long messages outside the exception class

(TRY003)


755-766: Avoid specifying long messages outside the exception class

(TRY003)


905-908: Avoid specifying long messages outside the exception class

(TRY003)


920-931: Avoid specifying long messages outside the exception class

(TRY003)


976-979: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)

src/fastmcp/prompts/prompt.py

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

(TRY003)

🔇 Additional comments (26)
src/fastmcp/server/sampling/sampling_tool.py (1)

12-12: LGTM!

The import path update correctly reflects the refactoring of ParsedFunction to its dedicated function_tool module. The usage remains unchanged and consistent with the new module location.

src/fastmcp/tools/tool_transform.py (1)

18-19: Import restructuring is correct.

The split import correctly sources ParsedFunction from its new dedicated module while keeping Tool, ToolResult, and _convert_to_content from the original tool module.

docs/development/upgrade-guide.mdx (1)

17-55: Documentation accurately documents the breaking change and provides correct configuration examples.

The section effectively communicates the decorator behavior change with:

  • Appropriate Warning component for visibility
  • Before/After code examples using CodeGroup showing the behavioral difference
  • Clear rationale explaining why functions remain functions
  • Both configuration methods correctly documented:
    • Programmatic: fastmcp.settings.decorator_mode = "object"
    • Environment variable: FASTMCP_DECORATOR_MODE=object (auto-generated by pydantic_settings from the Settings field)
docs/development/v3-notes/v3-features.mdx (1)

241-271: LGTM! Clear and comprehensive documentation of the decorator behavior change.

The documentation effectively:

  • Explains the new v3 default behavior with a runnable example
  • Provides clear rationale ("Functions stay callable", "Matches Flask, FastAPI, Typer")
  • Documents both migration paths (environment variable and programmatic setting)
  • Appropriately documents the breaking change in both the feature section and breaking changes section

Also applies to: 396-419

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

1-14: LGTM! Intentional and well-organized public API exports.

The exports are appropriately scoped:

  • FunctionTool, ParsedFunction, ToolMeta, tool from the new dedicated module
  • Tool, ToolResult from the base tool module
  • All exports are fundamental types that users need for the metadata-driven decorator patterns

This aligns with the coding guideline to be intentional about re-exports. Based on learnings.

src/fastmcp/decorators.py (1)

29-41: LGTM! Robust metadata extraction with proper edge case handling.

The implementation correctly handles:

  • Direct attribute access for decorated functions
  • Bound methods via __func__ for instance method support
  • Wrapped functions via inspect.unwrap for compatibility with other decorators (e.g., @functools.wraps)
  • The ValueError catch on line 39 correctly handles the edge case where inspect.unwrap encounters an infinite wrapper chain
src/fastmcp/tools/tool.py (2)

187-222: LGTM! Clean delegation pattern with lazy import.

The from_function classmethod correctly:

  • Uses a lazy import to avoid circular dependencies
  • Delegates all parameters to FunctionTool.from_function
  • Maintains the same public API signature for backwards compatibility

460-481: Well-implemented deprecation shim.

The __getattr__ hook provides a clean migration path:

  • Deprecation warnings are controlled by fastmcp.settings.deprecation_warnings
  • Correct stacklevel=2 ensures warnings point to the caller's location
  • Falls back to AttributeError for unknown attributes

The static analysis hint (TRY003) about the long message in the AttributeError is a false positive—this is the standard Python pattern for module-level __getattr__.

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

1-25: LGTM! Consistent with tools module structure.

The import reorganization follows the same pattern as tools/__init__.py:

  • Function-specific types (FunctionResource, ResourceMeta, resource) from the dedicated function_resource module
  • Base types (Resource, ResourceContent, ResourceResult) from the resource module
  • ResourceMeta is appropriately added to public exports for the metadata-driven decorator patterns

Based on learnings, this is intentional about re-exports.

src/fastmcp/server/server.py (3)

69-70: LGTM! Import reorganization aligns with module restructuring.

The imports correctly source types from their new dedicated modules:

  • FunctionPrompt from prompts.function_prompt
  • PromptResult from prompts.prompt
  • FunctionTool from tools.function_tool
  • Other tool types from tools.tool

Also applies to: 89-90


1811-1823: LGTM! API expansion to accept decorated functions.

The add_tool, add_resource, and add_prompt methods now correctly accept both component instances and decorated functions (Callable[..., Any]), enabling the new decorator pattern where functions retain their callable nature.

The docstrings are updated to reflect this dual-input capability.

Also applies to: 1972-1983, 2082-2091


2010-2010: LGTM! Return type correctly reflects decorator mode behavior.

The resource decorator's return type Callable[[AnyFunction], Resource | ResourceTemplate | AnyFunction] correctly indicates that in decorator_mode="function", the original function is returned, while in decorator_mode="object", a component is returned.

Also applies to: 2077-2078

src/fastmcp/prompts/prompt.py (2)

228-261: LGTM! Clean delegation pattern for from_function.

The conversion to a classmethod that delegates to FunctionPrompt.from_function is well-structured. The lazy import inside the method body avoids circular imports while maintaining the public API.


396-425: Well-implemented deprecation shim with controlled warnings.

The __getattr__ implementation correctly:

  • Uses fastmcp.settings.deprecation_warnings to control warning emission
  • Provides clear migration guidance in the warning message
  • Falls through to AttributeError for unknown attributes

One minor observation: the stacklevel=2 is appropriate here since the call originates from the module-level attribute access.

src/fastmcp/resources/function_resource.py (1)

60-73: LGTM! Clean FunctionResource implementation.

The FunctionResource class is well-structured with:

  • Clear docstring explaining lazy loading behavior
  • Proper async handling in read() with recursive Resource support
  • Correct docket registration pattern

Also applies to: 186-200

src/fastmcp/server/providers/local_provider.py (2)

187-216: LGTM! add_tool properly handles both Tool objects and decorated functions.

The implementation correctly:

  • Checks for existing Tool instances first
  • Extracts metadata via get_fastmcp_meta
  • Falls back to Tool.from_function for plain functions without metadata

290-318: LGTM! add_prompt mirrors the add_tool pattern correctly.

Consistent implementation with proper metadata extraction and fallback to Prompt.from_function.

src/fastmcp/resources/resource.py (2)

233-267: LGTM! Consistent delegation pattern for from_function.

The classmethod properly delegates to FunctionResource.from_function with all parameters passed through. This matches the pattern established in prompt.py.


413-443: LGTM! Deprecation shim consistent with other modules.

The __getattr__ implementation follows the same pattern as prompt.py, providing controlled deprecation warnings and proper fallback to the new module location.

src/fastmcp/prompts/function_prompt.py (3)

70-223: Well-structured FunctionPrompt implementation.

The from_function classmethod correctly:

  • Enforces mutual exclusion between metadata and individual parameters
  • Validates function signatures (no *args/**kwargs, named lambdas)
  • Normalizes TaskConfig with proper validation
  • Generates PromptArgument list with JSON schema hints for non-string types
  • Properly handles callable classes and staticmethods

225-298: LGTM! _convert_string_arguments and render implementation.

The type conversion logic properly:

  • Attempts JSON parsing first for complex types
  • Falls back to direct validation
  • Provides informative error messages on failure

The render method correctly validates required arguments and handles async functions.


365-444: LGTM! Standalone prompt decorator with proper mode switching.

The decorator implementation:

  • Supports all invocation patterns (@prompt, @prompt(), @prompt("name"))
  • Guards against classmethod decoration order issues
  • Properly switches between object mode (deprecated) and function mode
  • Attaches metadata to the original function in function mode
src/fastmcp/tools/function_tool.py (4)

116-256: Well-structured ParsedFunction implementation.

The from_function classmethod correctly:

  • Validates function signatures (no *args/**kwargs)
  • Validates exclude_args against function parameters
  • Handles callable classes and staticmethods
  • Generates input/output schemas with proper compression
  • Wraps non-object output schemas to comply with MCP requirements

259-417: FunctionTool.from_function is well-implemented.

The implementation correctly:

  • Enforces mutual exclusion between metadata and individual parameters
  • Emits deprecation warnings for serializer and exclude_args
  • Normalizes TaskConfig with validation
  • Validates output schema is an object type per MCP spec

502-594: LGTM! Standalone tool decorator consistent with other decorators.

The implementation follows the same pattern as prompt and resource decorators with proper mode switching and metadata attachment.


419-427: The current implementation is correct. TypeAdapter.validate_python() on an async function actually invokes the function and returns a coroutine object—it does not merely validate schema and return the input dictionary.

The flow is: (1) validate_python(arguments) calls wrapper_fn(**arguments), which executes the wrapped function and returns a coroutine, (2) inspect.isawaitable(result) detects the coroutine, (3) await result executes it to completion and retrieves the actual return value, (4) convert_result() processes the final result.

The proposed fix would incorrectly attempt to unpack a coroutine object as keyword arguments and invoke the function a second time.

Copy link

@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: fb4f1e9f5a

ℹ️ 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 233 to 237
meta=meta.meta,
task=resolved_task,
exclude_args=meta.exclude_args,
serializer=meta.serializer,
)

Choose a reason for hiding this comment

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

P1 Badge Forward decorator auth in filesystem discovery

When filesystem discovery rebuilds components from __fastmcp__ metadata, it doesn’t pass meta.auth into Tool.from_function (and the same omission occurs for resources/prompts in this function). As a result, any auth checks declared in decorators are silently dropped for modules discovered via FileSystemProvider, allowing unauthenticated access in deployments that rely on decorator-level auth. Please forward meta.auth when constructing Tool/Resource/ResourceTemplate/Prompt objects here.

Useful? React with 👍 / 👎.

Copy link
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/tools/tool.py (1)

50-52: Stale comment: "Re-export from function_tool module"

This comment suggests static re-exports will follow, but the actual re-export mechanism is via __getattr__ at the end of the file. Consider removing or clarifying this comment to avoid confusion.

Suggested fix
-# Re-export from function_tool module
-
 logger = get_logger(__name__)
src/fastmcp/server/providers/filesystem_discovery.py (1)

222-239: Consider using resolve_task_config for consistency.

The inline meta.task if meta.task is not None else False duplicates the logic in resolve_task_config from fastmcp.decorators. Using the utility would improve consistency with other parts of the codebase.

Suggested refactor
+            from fastmcp.decorators import resolve_task_config
+
             if isinstance(meta, ToolMeta):
-                resolved_task = meta.task if meta.task is not None else False
+                resolved_task = resolve_task_config(meta.task)
                 tool = Tool.from_function(

Apply similar changes for ResourceMeta and PromptMeta handling below.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb4f1e9 and a8258f1.

⛔ Files ignored due to path filters (1)
  • tests/deprecated/test_function_component_imports.py is excluded by none and included by none
📒 Files selected for processing (6)
  • src/fastmcp/prompts/__init__.py
  • src/fastmcp/resources/__init__.py
  • src/fastmcp/resources/function_resource.py
  • src/fastmcp/server/providers/filesystem_discovery.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/tools/tool.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/fastmcp/resources/init.py
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase

Files:

  • src/fastmcp/resources/function_resource.py
  • src/fastmcp/tools/__init__.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/server/providers/filesystem_discovery.py
  • src/fastmcp/prompts/__init__.py
src/**/__init__.py

📄 CodeRabbit inference engine (AGENTS.md)

Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*

Files:

  • src/fastmcp/tools/__init__.py
  • src/fastmcp/prompts/__init__.py
🧠 Learnings (3)
📚 Learning: 2026-01-12T16:25:10.972Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-12T16:25:10.972Z
Learning: Applies to src/**/__init__.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*

Applied to files:

  • src/fastmcp/tools/__init__.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/prompts/__init__.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/tools/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Tools (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • src/fastmcp/tools/__init__.py
  • src/fastmcp/tools/tool.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/prompts/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Prompts (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • src/fastmcp/prompts/__init__.py
🧬 Code graph analysis (5)
src/fastmcp/resources/function_resource.py (6)
src/fastmcp/decorators.py (1)
  • resolve_task_config (17-19)
src/fastmcp/resources/resource.py (2)
  • Resource (209-410)
  • from_function (234-267)
src/fastmcp/server/dependencies.py (2)
  • transform_context_annotations (131-247)
  • without_injected_parameters (433-491)
src/fastmcp/server/tasks/config.py (4)
  • TaskConfig (42-152)
  • from_bool (80-89)
  • validate_function (99-152)
  • supports_tasks (91-97)
src/fastmcp/utilities/types.py (1)
  • get_fn_name (34-35)
src/fastmcp/resources/template.py (1)
  • ResourceTemplate (101-316)
src/fastmcp/tools/__init__.py (3)
src/fastmcp/tools/function_tool.py (5)
  • FunctionTool (259-462)
  • tool (466-466)
  • tool (468-482)
  • tool (484-499)
  • tool (502-594)
src/fastmcp/tools/tool.py (2)
  • Tool (123-387)
  • ToolResult (62-120)
src/fastmcp/tools/tool_transform.py (2)
  • forward (39-66)
  • forward_raw (69-92)
src/fastmcp/tools/tool.py (1)
src/fastmcp/tools/function_tool.py (3)
  • FunctionTool (259-462)
  • from_function (125-256)
  • from_function (285-417)
src/fastmcp/server/providers/filesystem_discovery.py (5)
src/fastmcp/decorators.py (1)
  • get_fastmcp_meta (29-41)
src/fastmcp/prompts/function_prompt.py (5)
  • PromptMeta (56-67)
  • prompt (337-337)
  • prompt (339-349)
  • prompt (351-362)
  • prompt (365-444)
src/fastmcp/resources/function_resource.py (2)
  • ResourceMeta (43-57)
  • resource (211-311)
src/fastmcp/server/dependencies.py (1)
  • without_injected_parameters (433-491)
src/fastmcp/tools/function_tool.py (5)
  • ToolMeta (72-87)
  • tool (466-466)
  • tool (468-482)
  • tool (484-499)
  • tool (502-594)
src/fastmcp/prompts/__init__.py (2)
src/fastmcp/prompts/function_prompt.py (5)
  • FunctionPrompt (70-333)
  • prompt (337-337)
  • prompt (339-349)
  • prompt (351-362)
  • prompt (365-444)
src/fastmcp/prompts/prompt.py (4)
  • Message (40-92)
  • Prompt (189-393)
  • PromptArgument (95-104)
  • PromptResult (107-186)
🪛 Ruff (0.14.10)
src/fastmcp/resources/function_resource.py

125-128: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


234-237: Avoid specifying long messages outside the exception class

(TRY003)

src/fastmcp/tools/tool.py

482-482: 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). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (15)
src/fastmcp/prompts/__init__.py (1)

1-12: LGTM!

The re-exports are intentional and well-organized. Moving FunctionPrompt and prompt to the dedicated function_prompt module aligns with the PR's goal of consolidating function-wrapping implementations. Adding PromptArgument to __all__ improves API completeness.

src/fastmcp/tools/tool.py (3)

187-222: LGTM!

The delegation pattern is correct. Using a local import inside from_function avoids circular imports while keeping the base class API intact. This allows users to call Tool.from_function() without knowing about FunctionTool.


460-482: LGTM!

The __getattr__ shim correctly implements the deprecation path for backwards compatibility. The stacklevel=2 ensures warnings point to the caller's import site. This allows existing code importing FunctionTool, ParsedFunction, or tool from this module to continue working while receiving deprecation notices.


457-457: LGTM!

Reducing __all__ to only Tool and ToolResult correctly reflects the intended public API surface. Deprecated names remain accessible via __getattr__ for backwards compatibility but are no longer advertised.

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

1-12: LGTM!

The package cleanly re-exports the fundamental tool types from their new locations. FunctionTool and tool from the new function_tool module, Tool and ToolResult from the base module, and forward/forward_raw utilities from tool_transform. This aligns with the PR's refactoring goals while maintaining a clean public API. Based on learnings, these re-exports are intentional and appropriate for fundamental types.

src/fastmcp/server/providers/filesystem_discovery.py (3)

189-199: LGTM!

The local imports inside extract_components correctly avoid circular import issues while providing access to the metadata types and utilities needed for the new decorator-based component extraction.


240-276: LGTM!

The resource materialization logic correctly mirrors the @resource decorator behavior: checking for URI placeholders and function parameters to decide between ResourceTemplate and Resource. This ensures consistent component creation regardless of whether decorators are used directly or discovered via filesystem.


277-290: LGTM!

Prompt materialization correctly delegates to Prompt.from_function with all metadata fields.

src/fastmcp/resources/function_resource.py (7)

33-39: LGTM!

The DecoratedResource protocol correctly defines the interface for decorated resource functions with __fastmcp__ metadata. Using @runtime_checkable enables isinstance checks at runtime.


42-57: LGTM!

The ResourceMeta dataclass follows the same pattern as ToolMeta and PromptMeta, providing a frozen, discriminated metadata container for decorated functions.


184-198: LGTM!

The read method correctly handles both sync and async functions, and the recursive read for nested Resource results enables composition patterns.


233-237: Good defensive check for common decorator misuse.

Checking if uri is a routine catches the common mistake of using @resource without parentheses, providing a clear error message.


300-309: LGTM!

The decorator correctly switches between legacy "object" mode (with deprecation warning) and the new "function" mode that attaches metadata. The stacklevel=3 ensures the warning points to the user's decorator usage.


282-298: LGTM!

The attach_metadata helper correctly handles both regular functions and bound methods by accessing __func__ when present. This ensures the __fastmcp__ metadata survives method binding.


163-182: No action needed—the code correctly preserves the docstring.

transform_context_annotations returns the same function object (only updates __signature__), so when inspect.getdoc(fn) is called on line 174, it retrieves the original function's docstring. Additionally, without_injected_parameters explicitly preserves __doc__ via assignment, ensuring the docstring is maintained throughout the transformation chain.

Remove standalone decorating-methods.mdx page and add "Using with Methods"
subsection to tools.mdx with brief cross-references in prompts.mdx and
resources.mdx. Update error message URLs to point to new location.
@marvin-context-protocol
Copy link
Contributor

marvin-context-protocol bot commented Jan 13, 2026

Test Failure Analysis

Summary: The static analysis job failed because 15 files exceed their configured line limits in loq.toml.

Root Cause: The decorator refactoring introduced in this PR has increased the size of several files beyond their previously established limits. The loq pre-commit hook enforces file size limits to prevent files from growing too large, and the changes in this PR have pushed multiple files over their thresholds.

Suggested Solution: Run uv run loq baseline to update the loq.toml configuration file with the new line counts for all affected files. This will update the baseline limits to match the current state of the codebase.

uv run loq baseline
git add loq.toml
git commit -m "Update loq baseline for decorator refactoring"
Detailed Analysis

The following 15 files exceed their limits:

✖    714 > 713    tests/deprecated/test_import_server.py
✖    617 > 616    tests/server/middleware/test_caching.py
✖    509 > 508    tests/server/middleware/test_tool_injection.py
✖    650 > 648    docs/servers/context.mdx
✖    954 > 952    src/fastmcp/tools/tool_transform.py
✖    731 > 727    docs/servers/resources.mdx
✖  1,748 > 1,741  tests/tools/test_tool_transform.py
✖    576 > 565    src/fastmcp/resources/template.py
✖  1,554 > 1,538  tests/server/providers/test_local_provider_tools.py
✖  1,040 > 1,019  docs/servers/tools.mdx
✖  2,280 > 2,228  docs/changelog.mdx
✖    594 > 500    src/fastmcp/tools/function_tool.py
✖    604 > 500    tests/server/auth/test_authorization.py
✖  2,799 > 2,682  src/fastmcp/server/server.py
✖    997 > 738    src/fastmcp/server/providers/local_provider.py

The loq tool enforces file size limits to prevent files from becoming too large and difficult to maintain. When legitimate changes cause files to grow (as in this case with the decorator refactoring), the baseline needs to be updated.

Related Files
  • loq.toml - Configuration file that defines line limits for each file
  • .pre-commit-config.yaml - Pre-commit configuration that runs the loq hook

Updated for workflow run 20942889881 - commit bc2a25c. Edited by marvin.

Copy link
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: 1

🧹 Nitpick comments (3)
src/fastmcp/prompts/function_prompt.py (1)

200-202: Silent exception swallowing may hide schema generation failures.

The bare except Exception: pass silently ignores all schema generation failures. While the intent is to gracefully degrade when schema generation fails, this could mask legitimate bugs.

Consider logging at debug level to aid troubleshooting:

♻️ Suggested improvement
                         except Exception:
-                            # If schema generation fails, skip enhancement
-                            pass
+                            # If schema generation fails, skip enhancement
+                            logger.debug(
+                                f"Could not generate schema for parameter '{param_name}'"
+                            )
src/fastmcp/server/providers/local_provider.py (1)

578-599: Helpful error message for unbound methods, but verify consistency across all three decorators.

The error message is excellent—it shows exactly what the user should do. However, I notice the error messages in tool, resource, and prompt decorators all link to the same URL (/servers/tools#using-with-methods). For resources, this may be slightly confusing since there's now a dedicated section in resources.mdx.

Consider updating the URL in the resource decorator (line 765) to point to /servers/resources#using-with-methods for consistency.

src/fastmcp/tools/function_tool.py (1)

193-201: Consider logging type hint resolution failures.

Similar to the prompt module, silently swallowing exceptions during type hint resolution could hide issues. Since this is for output schema generation (which is important for MCP structured output), consider debug logging.

♻️ Suggested improvement
             except Exception:
-                # If resolution fails, keep the string annotation
-                pass
+                # If resolution fails, keep the string annotation
+                logger.debug(f"Could not resolve return type annotation for {fn}")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8258f1 and 1fdf655.

📒 Files selected for processing (8)
  • docs/docs.json
  • docs/patterns/decorating-methods.mdx
  • docs/servers/prompts.mdx
  • docs/servers/resources.mdx
  • docs/servers/tools.mdx
  • src/fastmcp/prompts/function_prompt.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/tools/function_tool.py
💤 Files with no reviewable changes (2)
  • docs/patterns/decorating-methods.mdx
  • docs/docs.json
✅ Files skipped from review due to trivial changes (1)
  • docs/servers/prompts.mdx
🧰 Additional context used
📓 Path-based instructions (2)
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/resources.mdx
  • docs/servers/tools.mdx
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase

Files:

  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/tools/function_tool.py
  • src/fastmcp/prompts/function_prompt.py
🧠 Learnings (3)
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/tools/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Tools (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • docs/servers/tools.mdx
  • src/fastmcp/tools/function_tool.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Maintain consistency across all four MCP object types (Tools, Resources, Resource Templates, and Prompts) when implementing similar features

Applied to files:

  • src/fastmcp/server/providers/local_provider.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/prompts/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Prompts (like adding tags, importing, etc.) must be adopted, applied, and tested consistently

Applied to files:

  • src/fastmcp/prompts/function_prompt.py
🧬 Code graph analysis (3)
src/fastmcp/server/providers/local_provider.py (6)
src/fastmcp/prompts/function_prompt.py (7)
  • FunctionPrompt (70-333)
  • prompt (337-337)
  • prompt (339-349)
  • prompt (351-362)
  • prompt (365-444)
  • from_function (76-223)
  • decorator (419-428)
src/fastmcp/prompts/prompt.py (2)
  • Prompt (189-393)
  • from_function (229-261)
src/fastmcp/resources/resource.py (2)
  • Resource (209-410)
  • from_function (234-267)
src/fastmcp/resources/template.py (3)
  • ResourceTemplate (101-316)
  • from_function (128-155)
  • from_function (451-576)
src/fastmcp/tools/tool.py (2)
  • Tool (123-387)
  • from_function (188-222)
src/fastmcp/decorators.py (1)
  • get_fastmcp_meta (29-41)
src/fastmcp/tools/function_tool.py (6)
src/fastmcp/decorators.py (1)
  • resolve_task_config (17-19)
src/fastmcp/server/dependencies.py (2)
  • transform_context_annotations (131-247)
  • without_injected_parameters (433-491)
src/fastmcp/server/tasks/config.py (4)
  • TaskConfig (42-152)
  • supports_tasks (91-97)
  • from_bool (80-89)
  • validate_function (99-152)
src/fastmcp/tools/tool.py (5)
  • Tool (123-387)
  • ToolResult (62-120)
  • from_function (188-222)
  • to_mcp_tool (159-185)
  • run (224-234)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (364-401)
src/fastmcp/utilities/types.py (3)
  • Audio (307-362)
  • File (365-448)
  • Image (235-304)
src/fastmcp/prompts/function_prompt.py (6)
src/fastmcp/exceptions.py (1)
  • PromptError (22-23)
src/fastmcp/prompts/prompt.py (8)
  • Prompt (189-393)
  • PromptArgument (95-104)
  • PromptResult (107-186)
  • from_function (229-261)
  • render (263-274)
  • convert_result (276-310)
  • register_with_docket (366-370)
  • add_to_docket (372-393)
src/fastmcp/server/dependencies.py (1)
  • transform_context_annotations (131-247)
src/fastmcp/server/tasks/config.py (4)
  • TaskConfig (42-152)
  • from_bool (80-89)
  • validate_function (99-152)
  • supports_tasks (91-97)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (364-401)
src/fastmcp/utilities/types.py (1)
  • get_cached_typeadapter (45-117)
🪛 GitHub Actions: Run static analysis
docs/servers/resources.mdx

[error] 731-731: loq: Line 731 > 727 (violation detected)

docs/servers/tools.mdx

[error] 1040-1040: loq: Line 1040 > 1019 (violation detected)

src/fastmcp/server/providers/local_provider.py

[error] 997-997: loq: Line 997 > 738 (violation detected)

src/fastmcp/tools/function_tool.py

[error] 594-594: loq: Line 594 > 500 (violation detected)

🪛 Ruff (0.14.10)
src/fastmcp/server/providers/local_provider.py

272-275: Avoid specifying long messages outside the exception class

(TRY003)


314-317: Avoid specifying long messages outside the exception class

(TRY003)


588-599: Avoid specifying long messages outside the exception class

(TRY003)


739-743: Avoid specifying long messages outside the exception class

(TRY003)


755-766: Avoid specifying long messages outside the exception class

(TRY003)


905-908: Avoid specifying long messages outside the exception class

(TRY003)


920-931: Avoid specifying long messages outside the exception class

(TRY003)


976-979: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)

src/fastmcp/tools/function_tool.py

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

(TRY003)


139-141: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


152-154: Avoid specifying long messages outside the exception class

(TRY003)


199-201: try-except-pass detected, consider logging the exception

(S110)


199-199: Do not catch blind exception: Exception

(BLE001)


335-338: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


398-401: Avoid specifying long messages outside the exception class

(TRY003)


527-530: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)

src/fastmcp/prompts/function_prompt.py

112-115: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)


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

(TRY003)


200-202: try-except-pass detected, consider logging the exception

(S110)


200-200: Do not catch blind exception: Exception

(BLE001)


259-262: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


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

(TRY003)


383-386: Avoid specifying long messages outside the exception class

(TRY003)


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

(TRY003)


439-439: 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 ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (18)
src/fastmcp/prompts/function_prompt.py (5)

1-44: LGTM! Well-organized imports and type definitions.

The imports are properly organized with TYPE_CHECKING guards for conditional imports (Docket, Execution), and the type variable F is appropriately bounded to Callable[..., Any].


46-68: LGTM! Clean protocol and metadata definitions.

The DecoratedPrompt protocol with @runtime_checkable correctly defines the contract for decorated functions, and the frozen PromptMeta dataclass provides immutable metadata storage with sensible defaults.


269-298: LGTM! Robust render implementation with proper error handling.

The render method correctly validates required arguments, converts string arguments to expected types, handles awaitables, and wraps errors in PromptError with logging. The flow is clear and follows defensive programming patterns.


300-333: LGTM! Clean Docket integration.

The register_with_docket and add_to_docket methods properly check task support before registration and handle the key parameter correctly.


419-428: Stacklevel in deprecation warning may be incorrect for all call paths.

The stacklevel=4 assumes a specific call depth. Depending on how decorator is called (directly via name_or_fn being a routine vs. through the wrapper), the stack depth varies. This could result in the warning pointing to the wrong line.

Consider verifying the warning points to the user's decorator usage in both invocation patterns:

  • @prompt (no parentheses)
  • @prompt() or @prompt(name="x")
docs/servers/resources.mdx (1)

135-138: LGTM! Clear cross-reference for method decoration pattern.

The new subsection appropriately directs users to the Tools documentation for the detailed pattern, maintaining consistency across Tools, Resources, and Prompts documentation. The guidance is concise and actionable.

docs/servers/tools.mdx (1)

119-139: LGTM! Clear and complete example for method decoration.

The documentation:

  • Explains why @mcp.tool doesn't work directly with methods (self/cls appearing as parameters)
  • Provides a complete, runnable example with the Calculator class
  • Shows the correct pattern: decorate with standalone @tool, then register bound method with mcp.add_tool()
  • Comment on line 138 clarifies the schema outcome

This follows the MDX documentation guidelines for including complete, runnable code examples.

src/fastmcp/server/providers/local_provider.py (4)

187-216: LGTM! Clean handling of decorated functions in add_tool.

The method correctly:

  • Checks if input is already a Tool
  • Extracts __fastmcp__ metadata using get_fastmcp_meta
  • Falls back to Tool.from_function(tool) for plain functions without metadata

222-276: LGTM! Comprehensive resource handling with proper template detection.

The logic correctly distinguishes between:

  • Resource (no URI params and no function params)
  • ResourceTemplate (URI params like {param} or function has parameters)

The error message for non-decorated functions is helpful and actionable.


768-813: Consistent decorator mode handling for resources.

The implementation correctly handles both object mode (legacy) and the new function-mode, properly distinguishing between Resource and ResourceTemplate based on URI/function parameters. The metadata attachment follows the same pattern as tools and prompts.


904-969: LGTM! Prompt decorator follows the established pattern.

The implementation is consistent with the tool decorator, including:

  • Classmethod detection with helpful error
  • Unbound method detection
  • Object-mode vs function-mode branching
  • Proper metadata attachment
src/fastmcp/tools/function_tool.py (7)

62-88: LGTM! Clean protocol and metadata definitions.

The DecoratedTool protocol and ToolMeta dataclass mirror the patterns in function_prompt.py, maintaining consistency across the codebase. The ToolMeta correctly includes all tool-specific fields like output_schema, annotations, and exclude_args.


91-114: LGTM! Well-designed schema helpers.

The _WrappedResult generic wrapper elegantly handles MCP's requirement that output schemas be objects. The _is_object_schema function correctly identifies object types including self-referencing schemas with $ref.


203-225: Clever handling of unserializable MCP types.

Using replace_type with _UnserializableType to prevent schema generation for internal types (Image, Audio, File, ToolResult, etc.) is a clean approach that avoids polluting output schemas with FastMCP-specific types.


284-417: LGTM! Comprehensive from_function implementation.

The method:

  • Properly validates mutual exclusion between metadata and individual params
  • Emits deprecation warnings for serializer and exclude_args
  • Validates lambda functions require explicit names
  • Normalizes and validates TaskConfig
  • Validates output schemas are object types per MCP spec

465-499: LGTM! Well-defined overloads for type safety.

The three overloads correctly handle:

  1. @tool (bare decorator on function)
  2. @tool("name", ...) (name as first positional argument)
  3. @tool(name="name", ...) (keyword arguments only)

This provides excellent IDE support and type checking.


569-578: Deprecation warning stacklevel consideration.

Similar to the prompt decorator, the stacklevel=4 may not be correct for all call paths. When decorator is called directly (line 581), the stack is different than when called through wrapper (line 592).

Verify the warning points to the user's code in both scenarios:

@tool  # direct path
def fn1(): ...

@tool()  # wrapper path
def fn2(): ...

419-427: ValidationError handling is properly implemented and documented.

The run() method's ValidationError from type_adapter.validate_python() is correctly handled by the server's call_tool() method (in src/fastmcp/server/server.py, lines 1348-1349), which explicitly catches both ValidationError and PydanticValidationError, logs the exception, and re-raises it. The method's docstring also documents this behavior. No action required.

@jlowin jlowin merged commit 1b723f3 into main Jan 13, 2026
10 of 11 checks passed
@jlowin jlowin deleted the metadata-decorators branch January 13, 2026 02:58
@marvin-context-protocol
Copy link
Contributor

Test Failure Analysis

Summary: The static analysis job failed because 15 files exceed their configured line count limits in the loq (file size enforcement) tool.

Root Cause: This PR adds the entire FastMCP codebase to the repository. While all formatting, linting, and type checking passed successfully, the loq hook enforces file size limits to prevent files from becoming too large. The current loq.toml configuration has baseline limits for these files, but the actual file sizes exceed those limits.

Suggested Solution: Run loq baseline to update the line count limits in loq.toml for all files that exceed their current limits:

uv run loq baseline
git add loq.toml
git commit -m "Update loq baseline for initial codebase commit"

This will update the loq.toml file with the current line counts as the new baseline, allowing future changes to be measured against these sizes.

Detailed Analysis

The loq tool failed with the following violations:

✖    714 > 713    tests/deprecated/test_import_server.py
✖    617 > 616    tests/server/middleware/test_caching.py
✖    509 > 508    tests/server/middleware/test_tool_injection.py
✖    650 > 648    docs/servers/context.mdx
✖    954 > 952    src/fastmcp/tools/tool_transform.py
✖    731 > 727    docs/servers/resources.mdx
✖  1_748 > 1_741  tests/tools/test_tool_transform.py
✖    576 > 565    src/fastmcp/resources/template.py
✖  1_554 > 1_538  tests/server/providers/test_local_provider_tools.py
✖  1_040 > 1_019  docs/servers/tools.mdx
✖  2_280 > 2_228  docs/changelog.mdx
✖    594 > 500    src/fastmcp/tools/function_tool.py
✖    604 > 500    tests/server/auth/test_authorization.py
✖  2_799 > 2_682  src/fastmcp/server/server.py
✖    997 > 738    src/fastmcp/server/providers/local_provider.py

All other pre-commit hooks passed successfully:

  • ✅ prettier
  • ✅ ruff-check
  • ✅ ruff-format
  • ✅ ty (type checking)
  • ✅ codespell

The workflow logs show this is purely a file size limit issue, not a code quality problem.

Related Files
  • loq.toml: Configuration file for the loq tool that enforces file size limits
  • .pre-commit-config.yaml: Includes loq hook with priority 5 that runs after type checking

The loq tool is configured with a default_max_lines = 500 and maintains per-file exceptions in loq.toml. Since this is the initial codebase commit, running loq baseline will establish the correct baseline limits for all files.

@marvin-context-protocol
Copy link
Contributor

Test Failure Analysis

Summary: The Windows Python 3.10 test is timing out during initialization of the AzureProvider when it attempts to create a SQLite-backed disk cache.

Root Cause: PR #2856 introduced a new OAuthProxy base class for the AzureProvider that creates a DiskStore for caching OAuth state during initialization. On Windows, the SQLite connection to settings.home / "oauth-proxy" is hanging and exceeding the 5-second test timeout.

The stack trace shows:

  • AzureProvider.__init__() calls super().__init__() (line 196 in azure.py)
  • OAuthProxy.__init__() creates DiskStore(directory=settings.home / "oauth-proxy") (line 822 in oauth_proxy.py)
  • DiskStore attempts to initialize a SQLite cache using diskcache
  • The SQLite connection hangs on Windows, causing the test to timeout

Suggested Solution:

  1. Provide a test fixture to override storage location: The OAuthProxy should accept an optional client_storage parameter or allow tests to specify a temporary directory for the disk cache.

  2. Make storage initialization lazy: Consider deferring the DiskStore initialization until it's actually needed, or making it async-friendly.

  3. Use in-memory storage for tests: Tests should be able to pass an in-memory key-value store instead of using disk-based storage.

The immediate fix would be to modify test_azure.py to either:

  • Mock/patch the DiskStore initialization
  • Provide a test-specific temporary directory that's guaranteed to work on Windows
  • Pass an in-memory storage adapter to the provider
Detailed Analysis

Failing Test

# tests/server/auth/providers/test_azure.py:542
def test_prepare_scopes_for_upstream_refresh_empty_scopes(self):
    """Test behavior with empty scopes list."""
    provider = AzureProvider(
        client_id="test_client",
        client_secret="test_secret",
        tenant_id="test-tenant",
        base_url="https://myserver.com",
        identifier_uri="api://my-api",
        required_scopes=["read"],
        additional_authorize_scopes=["User.Read", "openid"],
        jwt_signing_key="test-secret",
    )

The timeout occurs during the AzureProvider(...) constructor call.

Stack Trace Excerpt

File "src/fastmcp/server/auth/oauth_proxy.py", line 822, in __init__
    key_value=DiskStore(directory=settings.home / "oauth-proxy"),
File "site-packages/key_value/aio/stores/disk/store.py", line 81, in __init__
    self._cache = Cache(directory=directory, eviction_policy="none")
File "site-packages/diskcache/core.py", line 591, in __init__
    self._sql  # pylint: disable=pointless-statement
File "site-packages/diskcache/core.py", line 648, in _sql
    return self._con.execute
File "site-packages/diskcache/core.py", line 623, in _con
    con = self._local.con = sqlite3.connect(
+++++++++++++++++++++++ Timeout +++++++++++++++++++++++

Why Windows Only?

Windows file system behavior, particularly with SQLite in user_data_dir, can be slower or subject to permission issues that don't occur on Linux/macOS. The 5-second test timeout is insufficient on Windows for this initialization.

Related Files
  • src/fastmcp/server/auth/oauth_proxy.py:822 - Where DiskStore is initialized
  • src/fastmcp/server/auth/providers/azure.py:196 - Where super().__init__() is called
  • tests/server/auth/providers/test_azure.py:542 - The failing test
  • src/fastmcp/settings.py:180 - Definition of settings.home using user_data_dir

@leycec
Copy link

leycec commented Jan 13, 2026

Wondrous. Interestingly, the biggest gain here is interoperability with Python's existing decorator ecosystem. Previously, FastMCP decorators weren't safely chainable with other third-party decorators. Now they are.

This actually matters for @beartype. We explicitly support FastMCP by treating decorated callables as FunctionTool/FunctionResource/FunctionPrompt objects. We should stop doing that. I've made an internal note to myself to stop doing that. My older self will ignore that note, though. I know that guy. He never reads anything. In 2026, who's got the time? 😂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. 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.

Explore Tool Decorator Changes

3 participants