Remove enable_tasks setting, enable task protocol by default#2578
Remove enable_tasks setting, enable task protocol by default#2578chrisguidry merged 3 commits intomainfrom
Conversation
The task protocol (SEP-1686) is now always enabled - server always registers task handlers and advertises task capabilities. Users still opt into background execution at the server level (tasks=True) or component level (task=True on tools, prompts, resources). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
WalkthroughBackground-task opt-in was removed and the settings field Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 1
🧹 Nitpick comments (4)
src/fastmcp/client/transports.py (1)
859-866: Align FastMCPTransport task capabilities with HTTP/stdio shapeHere you now always advertise
"tasks": {"tools": True, "prompts": True, "resources": True}for in‑process servers, whilerun_stdio_asyncandcreate_sse_appuse the richer{list, cancel, requests: {tools.call, prompts.get, resources.read}}map. For consistency (and to avoid capability drift between transports), consider reusing the same experimental_capabilities structure here as inFastMCP.run_stdio_async/create_sse_app.src/fastmcp/server/http.py (1)
163-175: SSE task capabilities match stdio; consider centralizing constantDeclaring the full SEP‑1686 tasks map (
list,cancel, andrequests.tools/prompts/resources) here matches the stdio path and correctly advertises server task support over SSE. Since the same literal now appears inFastMCP.run_stdio_async, consider factoring this into a shared helper/constant to avoid future drift between transports.src/fastmcp/server/server.py (2)
1543-1584: Consider applying enable/tag filtering before tool task routing
_call_tool_mcpnow fetches the underlying tool via_get_tool_with_task_configand inspects itstask_config.modeto decide how to handle SEP‑1686 metadata. Because_get_tool_with_task_configintentionally returns tools “unfiltered” (including from mounted/proxy servers), this logic may consider tools that the parent server would otherwise hide via_should_enable_component(disabled or excluded by tags).If the intent is that disabled/excluded tools should not be invokable even via tasks, consider checking
self._should_enable_component(tool)before enforcingtask_modeor dispatching tohandle_tool_as_task, e.g.:- tool = await self._get_tool_with_task_config(key) - if tool and hasattr(tool, "task_config"): + tool = await self._get_tool_with_task_config(key) + if tool and self._should_enable_component(tool) and hasattr(tool, "task_config"):This keeps task-mode enforcement aligned with the existing visibility rules applied in
_call_tool.
2479-2491: Stdio experimental task capabilities now match HTTP/SSE; avoid duplicationThe stdio path now advertises full SEP‑1686 task support (
list,cancel, andrequests.tools/prompts/resources), which is consistent with the SSE HTTP app. To reduce duplication and chance of drift, consider extracting thisexperimental_capabilitiesstructure into a shared helper or constant used by:
FastMCP.run_stdio_asynccreate_sse_appFastMCPTransport’s in‑memory initializationso all transports expose the same capability map.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
tests/client/tasks/conftest.pyis excluded by none and included by nonetests/client/test_client.pyis excluded by none and included by nonetests/server/tasks/conftest.pyis excluded by none and included by nonetests/server/tasks/test_server_tasks_parameter.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_protocol.pyis excluded by none and included by none
📒 Files selected for processing (8)
docs/servers/tasks.mdx(0 hunks)examples/tasks/.envrc(0 hunks)examples/tasks/README.md(0 hunks)src/fastmcp/client/client.py(1 hunks)src/fastmcp/client/transports.py(1 hunks)src/fastmcp/server/http.py(2 hunks)src/fastmcp/server/server.py(6 hunks)src/fastmcp/settings.py(0 hunks)
💤 Files with no reviewable changes (4)
- examples/tasks/README.md
- examples/tasks/.envrc
- src/fastmcp/settings.py
- docs/servers/tasks.mdx
🧰 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/client/client.pysrc/fastmcp/client/transports.pysrc/fastmcp/server/http.pysrc/fastmcp/server/server.py
🧠 Learnings (1)
📚 Learning: 2025-12-04T00:17:41.256Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.256Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing using in-memory transport; only use HTTP transport when explicitly testing network features
Applied to files:
src/fastmcp/client/transports.py
🧬 Code graph analysis (2)
src/fastmcp/client/client.py (2)
src/fastmcp/client/tasks.py (1)
_task_capable_initialize(37-86)src/fastmcp/server/context.py (1)
session(395-405)
src/fastmcp/server/server.py (6)
src/fastmcp/server/context.py (2)
fastmcp(152-157)Context(100-731)src/fastmcp/server/dependencies.py (1)
message(400-401)src/fastmcp/resources/resource.py (1)
FunctionResource(160-241)src/fastmcp/resources/template.py (1)
FunctionResourceTemplate(231-413)src/fastmcp/server/tasks/handlers.py (3)
handle_resource_as_task(244-356)handle_prompt_as_task(137-241)handle_tool_as_task(27-134)src/fastmcp/tools/tool.py (1)
FunctionTool(275-419)
⏰ 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
🔇 Additional comments (3)
src/fastmcp/client/client.py (1)
461-463: Unified task-capable initialization looks correctSwitching
initialize()to always use_task_capable_initialize(self.session)under the same timeout preserves idempotency and caching while ensuring client task capabilities are always declared. This aligns with the server-side move to unconditional task protocol support.src/fastmcp/server/server.py (2)
213-213: Server-leveltasksdefault is cleanly plumbed into decorators
self._support_tasks_by_defaultis derived solely from the constructor’stasksargument and then reused in thetool,resource, andpromptdecorators whentaskis not explicitly provided. This cleanly decouples background-task support from global settings and matches the PR’s intent that per-server/per-component configuration controls SEP‑1686 behavior.Also applies to: 2005-2008, 2196-2199, 2405-2408
817-849: Prompt task routing (required/forbidden) is well-structuredThe GetPrompt handler correctly:
- Looks up the concrete prompt (including mounted servers).
- Enforces
task_config.mode == "required"by raisingMcpError(METHOD_NOT_FOUND, ...)when no task metadata is present.- Routes to
handle_prompt_as_taskwhen task metadata exists and mode is not"forbidden", wrapping the result inServerResult.- Rejects task metadata when mode is
"forbidden".This matches the behavior implemented for resources/tools and the SEP‑1686 semantics.
| async with fastmcp.server.context.Context(fastmcp=self): | ||
| # Get resource including from mounted servers | ||
| resource = await self._get_resource_with_task_config(str(uri)) | ||
| if resource and hasattr(resource, "task_config"): | ||
| task_mode = resource.task_config.mode # type: ignore[union-attr] | ||
|
|
||
| # Enforce mode="required" - must have task metadata | ||
| if task_mode == "required" and not task_meta: | ||
| raise McpError( | ||
| ErrorData( | ||
| code=METHOD_NOT_FOUND, | ||
| message=f"Resource '{uri}' requires task-augmented execution", | ||
| ) | ||
| ) | ||
|
|
||
| # Route to background if task metadata present and mode allows | ||
| if task_meta and task_mode != "forbidden": | ||
| # For FunctionResource/FunctionResourceTemplate, use Docket | ||
| if isinstance( | ||
| resource, | ||
| FunctionResource | FunctionResourceTemplate, | ||
| ): | ||
| task_meta_dict = task_meta.model_dump(exclude_none=True) | ||
| return await handle_resource_as_task( | ||
| self, str(uri), resource, task_meta_dict | ||
| ) | ||
| # Route to background if task metadata present and mode allows | ||
| if task_meta and task_mode != "forbidden": | ||
| # For FunctionResource/FunctionResourceTemplate, use Docket | ||
| if isinstance( | ||
| resource, | ||
| FunctionResource | FunctionResourceTemplate, | ||
| ): | ||
| task_meta_dict = task_meta.model_dump(exclude_none=True) | ||
| return await handle_resource_as_task( | ||
| self, str(uri), resource, task_meta_dict | ||
| ) | ||
|
|
||
| # Forbidden mode: task requested but mode="forbidden" | ||
| # Raise error since resources don't have isError field | ||
| if task_meta and task_mode == "forbidden": | ||
| raise McpError( | ||
| ErrorData( | ||
| code=METHOD_NOT_FOUND, | ||
| message=f"Resource '{uri}' does not support task-augmented execution", | ||
| ) | ||
| # Forbidden mode: task requested but mode="forbidden" | ||
| # Raise error since resources don't have isError field | ||
| if task_meta and task_mode == "forbidden": | ||
| raise McpError( | ||
| ErrorData( | ||
| code=METHOD_NOT_FOUND, | ||
| message=f"Resource '{uri}' does not support task-augmented execution", | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Wrap resource task responses in ServerResult for consistency
In the ReadResource handler, the synchronous path always returns mcp.types.ServerResult(ReadResourceResult(...)), but the background path currently returns the raw result of handle_resource_as_task(...).
Given how handle_prompt_as_task is used (wrapped in ServerResult) and the surrounding code here, it’s very likely that handle_resource_as_task returns a ReadResourceResult, not a ServerResult. To keep the handler’s contract consistent with what the low-level server expects (and with the synchronous branch), you should wrap the task result as well:
- task_meta_dict = task_meta.model_dump(exclude_none=True)
- return await handle_resource_as_task(
- self, str(uri), resource, task_meta_dict
- )
+ task_meta_dict = task_meta.model_dump(exclude_none=True)
+ result = await handle_resource_as_task(
+ self, str(uri), resource, task_meta_dict
+ )
+ return mcp.types.ServerResult(result)This also mirrors the pattern used in _setup_get_prompt_handler.
🤖 Prompt for AI Agents
In src/fastmcp/server/server.py around lines 690-725, the background/task branch
currently returns the raw result from handle_resource_as_task; change it to
await the task handler into a variable and return it wrapped in a ServerResult
(i.e. result = await handle_resource_as_task(...); return ServerResult(result)
or mcp.types.ServerResult(result) to match the synchronous path and the server
contract).
Addresses code review feedback: - Extract `get_task_capabilities()` to avoid duplicating the SEP-1686 capability structure across transports - Add `_should_enable_component()` check before task routing for tools, resources, and prompts to respect enable/tag filtering - Simplify tasks/__init__.py to avoid circular import issues 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
src/fastmcp/server/server.py (1)
719-721: Wraphandle_resource_as_taskresult inServerResultfor consistency.The handler's return type is
mcp.types.ServerResult, buthandle_resource_as_taskis returned directly. The prompt handler at line 849 correctly wraps its task result:return mcp.types.ServerResult(result). Apply the same pattern here.if isinstance( resource, FunctionResource | FunctionResourceTemplate, ): task_meta_dict = task_meta.model_dump(exclude_none=True) - return await handle_resource_as_task( + result = await handle_resource_as_task( self, str(uri), resource, task_meta_dict ) + return mcp.types.ServerResult(result)
🧹 Nitpick comments (1)
src/fastmcp/server/tasks/capabilities.py (1)
6-22: Consider caching the capabilities dict.The function returns a new dict on every call. Since the capabilities are static, you could define a module-level constant and return it directly to avoid repeated allocations.
+_TASK_CAPABILITIES: dict[str, Any] = { + "tasks": { + "list": {}, + "cancel": {}, + "requests": { + "tools": {"call": {}}, + "prompts": {"get": {}}, + "resources": {"read": {}}, + }, + } +} + + def get_task_capabilities() -> dict[str, Any]: """Return the SEP-1686 task capabilities structure. This is the standard capabilities map advertised to clients, declaring support for list, cancel, and request operations. """ - return { - "tasks": { - "list": {}, - "cancel": {}, - "requests": { - "tools": {"call": {}}, - "prompts": {"get": {}}, - "resources": {"read": {}}, - }, - } - } + return _TASK_CAPABILITIES
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
tests/server/tasks/test_task_capabilities.pyis excluded by none and included by none
📒 Files selected for processing (5)
src/fastmcp/client/transports.py(2 hunks)src/fastmcp/server/http.py(2 hunks)src/fastmcp/server/server.py(6 hunks)src/fastmcp/server/tasks/__init__.py(1 hunks)src/fastmcp/server/tasks/capabilities.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/fastmcp/client/transports.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/server/tasks/__init__.pysrc/fastmcp/server/tasks/capabilities.pysrc/fastmcp/server/http.pysrc/fastmcp/server/server.py
🧬 Code graph analysis (3)
src/fastmcp/server/tasks/__init__.py (2)
src/fastmcp/server/tasks/capabilities.py (1)
get_task_capabilities(6-22)src/fastmcp/server/tasks/config.py (1)
TaskConfig(19-89)
src/fastmcp/server/http.py (1)
src/fastmcp/server/tasks/capabilities.py (1)
get_task_capabilities(6-22)
src/fastmcp/server/server.py (3)
src/fastmcp/server/tasks/capabilities.py (1)
get_task_capabilities(6-22)src/fastmcp/resources/resource.py (2)
FunctionResource(160-241)key(150-157)src/fastmcp/resources/template.py (2)
FunctionResourceTemplate(231-413)key(221-228)
⏰ 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
🔇 Additional comments (6)
src/fastmcp/server/http.py (1)
24-24: LGTM! Clean refactor to unconditional task capabilities.The import and usage of
get_task_capabilities()is correct and aligns with the PR objective to always advertise task capabilities without conditional gating.Also applies to: 164-164
src/fastmcp/server/tasks/__init__.py (1)
6-6: LGTM!The public API export is correctly updated to include
get_task_capabilities.Also applies to: 19-19
src/fastmcp/server/server.py (4)
215-215: LGTM! Clear default behavior.The default of
Falseensures backward compatibility—components won't automatically enable task execution unless explicitly opted in viatasks=Trueon the server ortask=Trueon individual decorators.
823-859: LGTM! Prompt task routing logic is correct.The handler properly enforces task modes and correctly wraps the task result in
ServerResultat line 849.
2494-2494: LGTM!Consistent with the HTTP transport changes—task capabilities are now unconditionally advertised.
1572-1579: > Likely an incorrect or invalid review comment.
The task protocol (SEP-1686) was behind a global
enable_taskssetting that defaulted toFalse. This made sense during development, but now that it's ready for use, the protocol should be enabled by default.With this change, servers always register task handlers and advertise task capabilities. Users still control whether components actually support background execution via
tasks=Trueon the server ortask=Trueon individual tools, prompts, and resources.🤖 Generated with Claude Code