Skip to content

Remove enable_tasks setting, enable task protocol by default#2578

Merged
chrisguidry merged 3 commits intomainfrom
remove-enable-tasks
Dec 9, 2025
Merged

Remove enable_tasks setting, enable task protocol by default#2578
chrisguidry merged 3 commits intomainfrom
remove-enable-tasks

Conversation

@chrisguidry
Copy link
Copy Markdown
Collaborator

The task protocol (SEP-1686) was behind a global enable_tasks setting that defaulted to False. 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=True on the server or task=True on individual tools, prompts, and resources.

# Before: needed env var or explicit setting
# FASTMCP_ENABLE_TASKS=true
mcp = FastMCP("example", tasks=True)

# After: just opt in at server/component level
mcp = FastMCP("example", tasks=True)

@mcp.tool(task=True)  # or inherit from server
async def slow_operation() -> str:
    ...

🤖 Generated with Claude Code

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>
@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. breaking change Breaks backward compatibility. Requires minor version bump. Critical for maintainer attention. server Related to FastMCP server implementation or server-side functionality. client Related to the FastMCP client SDK or client-side functionality. labels Dec 9, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 9, 2025

Walkthrough

Background-task opt-in was removed and the settings field enable_tasks deleted. Conditional checks for that flag were eliminated across client initialization, transports, HTTP/SSE handlers, server routing, and stdio readiness; SEP-1686 task handlers and capability advertising are registered/unconditionally used. Documentation and example env/docs referencing FASTMCP_ENABLE_TASKS were removed. New module get_task_capabilities() supplies the reported task capabilities.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.54% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description clearly explains the motivation, change, and impact with code examples, but is missing the required checklist items from the template. Complete the Contributors and Review checklists from the template, including issue reference, testing confirmation, and self-review sign-off.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: removing the enable_tasks setting and enabling task protocol by default.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch remove-enable-tasks

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/fastmcp/client/transports.py (1)

859-866: Align FastMCPTransport task capabilities with HTTP/stdio shape

Here you now always advertise "tasks": {"tools": True, "prompts": True, "resources": True} for in‑process servers, while run_stdio_async and create_sse_app use 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 in FastMCP.run_stdio_async / create_sse_app.

src/fastmcp/server/http.py (1)

163-175: SSE task capabilities match stdio; consider centralizing constant

Declaring the full SEP‑1686 tasks map (list, cancel, and requests.tools/prompts/resources) here matches the stdio path and correctly advertises server task support over SSE. Since the same literal now appears in FastMCP.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_mcp now fetches the underlying tool via _get_tool_with_task_config and inspects its task_config.mode to decide how to handle SEP‑1686 metadata. Because _get_tool_with_task_config intentionally 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 enforcing task_mode or dispatching to handle_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 duplication

The stdio path now advertises full SEP‑1686 task support (list, cancel, and requests.tools/prompts/resources), which is consistent with the SSE HTTP app. To reduce duplication and chance of drift, consider extracting this experimental_capabilities structure into a shared helper or constant used by:

  • FastMCP.run_stdio_async
  • create_sse_app
  • FastMCPTransport’s in‑memory initialization

so all transports expose the same capability map.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 515e8e8 and 350f723.

⛔ Files ignored due to path filters (6)
  • tests/client/tasks/conftest.py is excluded by none and included by none
  • tests/client/test_client.py is excluded by none and included by none
  • tests/server/tasks/conftest.py is excluded by none and included by none
  • tests/server/tasks/test_server_tasks_parameter.py is excluded by none and included by none
  • tests/server/tasks/test_task_capabilities.py is excluded by none and included by none
  • tests/server/tasks/test_task_protocol.py is 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 bare except - 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.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/server/http.py
  • src/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 correct

Switching 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-level tasks default is cleanly plumbed into decorators

self._support_tasks_by_default is derived solely from the constructor’s tasks argument and then reused in the tool, resource, and prompt decorators when task is 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-structured

The GetPrompt handler correctly:

  • Looks up the concrete prompt (including mounted servers).
  • Enforces task_config.mode == "required" by raising McpError(METHOD_NOT_FOUND, ...) when no task metadata is present.
  • Routes to handle_prompt_as_task when task metadata exists and mode is not "forbidden", wrapping the result in ServerResult.
  • Rejects task metadata when mode is "forbidden".

This matches the behavior implemented for resources/tools and the SEP‑1686 semantics.

Comment on lines +690 to +725
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",
)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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).

chrisguidry and others added 2 commits December 9, 2025 10:02
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>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/fastmcp/server/server.py (1)

719-721: Wrap handle_resource_as_task result in ServerResult for consistency.

The handler's return type is mcp.types.ServerResult, but handle_resource_as_task is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 350f723 and 46965d2.

⛔ Files ignored due to path filters (1)
  • tests/server/tasks/test_task_capabilities.py is 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 bare except - 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__.py
  • src/fastmcp/server/tasks/capabilities.py
  • src/fastmcp/server/http.py
  • src/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 False ensures backward compatibility—components won't automatically enable task execution unless explicitly opted in via tasks=True on the server or task=True on individual decorators.


823-859: LGTM! Prompt task routing logic is correct.

The handler properly enforces task modes and correctly wraps the task result in ServerResult at 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.

@chrisguidry chrisguidry merged commit 9ea57f9 into main Dec 9, 2025
13 checks passed
@chrisguidry chrisguidry deleted the remove-enable-tasks branch December 9, 2025 15:16
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. client Related to the FastMCP client SDK or client-side functionality. enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant