[SEP-1686] Raise ValueError when sync functions have task=True#2554
[SEP-1686] Raise ValueError when sync functions have task=True#2554
Conversation
Move validation from server.py decorators to the from_function() class methods on FunctionTool, FunctionPrompt, FunctionResource, and FunctionResourceTemplate. This ensures the check runs regardless of how the objects are created.
WalkthroughThe pull request moves background-task validation from the server registration layer into factory methods. It removes server-side checks that disabled task support for synchronous functions and adds runtime validation in FunctionTool.from_function, FunctionPrompt.from_function, FunctionResource.from_function, and FunctionResourceTemplate.from_function. Each factory now raises a ValueError if Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
src/fastmcp/tools/tool.py (1)
317-323: Consider extracting validation logic to reduce duplication.The same validation pattern (checking
task=Truewith sync functions) appears in four files with minor variations. Consider extracting to a shared utility function.Example shared utility:
# In fastmcp/utilities/validation.py or similar def validate_task_requires_async( fn: Callable[..., Any], task: bool | None, component_type: str, name: str, ) -> None: """Validate that task=True is only used with async functions.""" if not task: return # Handle callable classes and staticmethods fn_to_check = fn if not inspect.isroutine(fn) and hasattr(fn, '__call__'): fn_to_check = fn.__call__ if isinstance(fn_to_check, staticmethod): fn_to_check = fn_to_check.__func__ if not inspect.iscoroutinefunction(fn_to_check): raise ValueError( f"{component_type} '{name}' uses a sync function but has task=True. " "Background tasks require async functions. Set task=False to disable." )Then call it from each
from_functionmethod:validate_task_requires_async(fn, task, "Tool", fn_name)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
tests/server/tasks/test_sync_function_task_disabled.pyis excluded by none and included by nonetests/server/tasks/test_task_capabilities.pyis excluded by none and included by nonetests/server/tasks/test_task_return_types.pyis excluded by none and included by none
📒 Files selected for processing (5)
src/fastmcp/prompts/prompt.py(1 hunks)src/fastmcp/resources/resource.py(1 hunks)src/fastmcp/resources/template.py(1 hunks)src/fastmcp/server/server.py(0 hunks)src/fastmcp/tools/tool.py(1 hunks)
💤 Files with no reviewable changes (1)
- src/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bareexcept- be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style
Files:
src/fastmcp/prompts/prompt.pysrc/fastmcp/resources/template.pysrc/fastmcp/resources/resource.pysrc/fastmcp/tools/tool.py
🧬 Code graph analysis (2)
src/fastmcp/resources/template.py (1)
examples/tasks/client.py (1)
task(83-128)
src/fastmcp/resources/resource.py (2)
examples/tasks/client.py (1)
task(83-128)src/fastmcp/utilities/types.py (1)
get_fn_name(34-35)
🪛 Ruff (0.14.7)
src/fastmcp/prompts/prompt.py
204-207: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/resources/template.py
313-316: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/resources/resource.py
202-205: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/tools/tool.py
320-323: 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: label-issue-or-pr
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
Move the task=True async validation to run AFTER callable classes and staticmethods are unwrapped, preventing false positives for async callable classes with sync-looking signatures.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (4)
src/fastmcp/prompts/prompt.py (1)
211-216: Async-only enforcement for task prompts looks correctThe validation now runs after unwrapping callable classes/staticmethods and cleanly raises on
task=Truewith a non-async function, which matches the PR’s intent and avoids the earlier false positives. The long error message flagged by Ruff (TRY003) is acceptable for clarity; only shorten or centralize it if you want strict lint compliance.src/fastmcp/tools/tool.py (1)
317-329: Task validation for tools correctly unwraps callables before async checkThis block now inspects
fn_to_checkafter unwrapping callable classes and staticmethods, then raises a clearValueErrorwhentask=Trueis used with a non-async function, which is exactly what SEP-1686 requires. The behavior is consistent with resources/prompts and should prevent the previous silent fallback.src/fastmcp/resources/template.py (1)
372-377: Consistent async-only task enforcement for resource templatesThe new guard runs after unwrapping callable instances/staticmethods and rejects
task=Truefor non-async functions with a clear error message, aligning resource templates with tools and prompts in how background-task capability is validated.src/fastmcp/resources/resource.py (1)
200-211: Resource task validation correctly targets the underlying async callableBy unwrapping callable classes and staticmethods into
fn_to_checkbefore callinginspect.iscoroutinefunction, this check reliably raises for sync functions withtask=Truewhile allowing genuine async resources. The error message is clear; you only need to tweak it if you want to satisfy Ruff’s TRY003 lint rule.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
tests/server/tasks/test_sync_function_task_disabled.pyis excluded by none and included by none
📒 Files selected for processing (4)
src/fastmcp/prompts/prompt.py(1 hunks)src/fastmcp/resources/resource.py(1 hunks)src/fastmcp/resources/template.py(1 hunks)src/fastmcp/tools/tool.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bareexcept- be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style
Files:
src/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/prompts/prompt.pysrc/fastmcp/tools/tool.py
🧬 Code graph analysis (3)
src/fastmcp/resources/resource.py (1)
src/fastmcp/utilities/types.py (1)
get_fn_name(34-35)
src/fastmcp/resources/template.py (1)
examples/tasks/client.py (1)
task(83-128)
src/fastmcp/prompts/prompt.py (1)
examples/tasks/client.py (1)
task(83-128)
🪛 Ruff (0.14.7)
src/fastmcp/resources/resource.py
208-211: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/resources/template.py
374-377: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/prompts/prompt.py
213-216: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/tools/tool.py
326-329: 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). (3)
- 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
Test Failure AnalysisSummary: Two integration tests timed out after 30s due to rate limiting (HTTP 429) from GitHub Copilot's MCP API. Root Cause: The failing tests ( Suggested Solution: These integration tests are flaky because they depend on external API rate limits. Consider one of these approaches:
Detailed AnalysisThe tests connect to GitHub's real MCP API endpoint: GITHUB_REMOTE_MCP_URL = "https://api.githubcopilot.com/mcp/"Both failing tests show the same pattern:
The timeout happens because the client hangs waiting for a response that never comes due to rate limiting. The actual error only surfaces during teardown when the client tries to disconnect. Related Files
Note: This failure is unrelated to the changes in this PR (SEP-1686). The PR successfully adds validation for sync functions with |
Background tasks (SEP-1686) require async functions because Docket executes them in a worker process. Previously, sync functions with
task=Truewould log a warning and silently fall back to immediate execution. This was confusing—users wouldn't know their function wasn't actually running as a background task.Now, attempting to use a sync function with
task=Trueraises a clear error at decoration time:The validation is now in the
from_function()class methods (FunctionTool, FunctionPrompt, FunctionResource, FunctionResourceTemplate) rather than the server decorators, ensuring it runs regardless of how objects are created.Follows #2378