Skip to content

Fix task capabilities location (issue #2870)#2875

Merged
chrisguidry merged 4 commits intomainfrom
fix-task-capabilities-main
Jan 14, 2026
Merged

Fix task capabilities location (issue #2870)#2875
chrisguidry merged 4 commits intomainfrom
fix-task-capabilities-main

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 14, 2026

Task capabilities were being set in capabilities.experimental.tasks instead of capabilities.tasks, which broke VS Code Copilot 1.107+ integration. Copilot checks capabilities.tasks?.requests?.tools?.call to detect task support—when it doesn't find it, it falls back to synchronous calls with a 5-minute timeout.

The fix overrides get_capabilities() in LowLevelServer to set capabilities.tasks as a first-class field per SEP-1686, rather than passing it through the experimental_capabilities parameter. FastMCP's existing Docket-based task infrastructure remains unchanged.

# Before: capabilities.experimental.tasks = {...}
# After:  capabilities.tasks = {...}

FastMCP continues to support prompts and resources for task execution (ahead of the spec) via the SDK's **extra_data mechanism.

Closes #2870

Tasks belong in capabilities.tasks (first-class field) per SEP-1686,
not capabilities.experimental.tasks. This fixes VS Code Copilot 1.107+
integration which checks capabilities.tasks?.requests?.tools?.call.

Changes:
- Update get_task_capabilities() to return ServerTasksCapability types
- Override get_capabilities() in LowLevelServer to set tasks field
- Remove experimental_capabilities parameter usage
- Update test to verify correct location

Fixes #2870
@jlowin jlowin requested a review from chrisguidry January 14, 2026 03:52
@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. server Related to FastMCP server implementation or server-side functionality. client Related to the FastMCP client SDK or client-side functionality. labels Jan 14, 2026
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3c3a62ee7d

ℹ️ 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 thread src/fastmcp/server/server.py Outdated
Comment on lines +2385 to +2389
await self._mcp_server.run(
read_stream,
write_stream,
self._mcp_server.create_initialization_options(
notification_options=NotificationOptions(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep stdio run inside stream context

In run_stdio_async, the await self._mcp_server.run(...) call is now indented outside the async with stdio_server() block (see the async with stdio_server() a few lines above). Exiting that context closes read_stream/write_stream, so the stdio transport will try to run after its streams are already closed, leading to immediate I/O errors or a dead server. This affects all stdio transport usage; the run call needs to remain inside the stdio_server context.

Useful? React with 👍 / 👎.

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: Type checker () fails because ServerTasksRequestsCapability doesn't define prompts and resources parameters in its type signature, even though the model accepts them at runtime via extra='allow'.

Root Cause: In src/fastmcp/server/tasks/capabilities.py:39-40, the code passes prompts and resources as named arguments to ServerTasksRequestsCapability():

requests=ServerTasksRequestsCapability(
    tools=TasksToolsCapability(call=TasksCallCapability()),
    prompts={"get": {}},      # ❌ Not in type signature
    resources={"read": {}},   # ❌ Not in type signature
),

The MCP SDK's ServerTasksRequestsCapability only defines tools in its type signature, but has model_config = {'extra': 'allow'} which allows additional fields at runtime. The type checker doesn't recognize these extra fields.

Suggested Solution: Pass the extra fields using ** unpacking to satisfy the type checker:

requests=ServerTasksRequestsCapability(
    tools=TasksToolsCapability(call=TasksCallCapability()),
    **{"prompts": {"get": {}}, "resources": {"read": {}}},  # ✅ Extra fields via **kwargs
),

This tells the type checker "we know these aren't in the signature, but the model accepts them" while maintaining the same runtime behavior.

Detailed Analysis

Error log excerpt:

error[unknown-argument]: Argument `prompts` does not match any known parameter
    --> src/fastmcp/server/tasks/capabilities.py:39:13
   |
39 |             prompts={"get": {}},
   |             ^^^^^^^^^^^^^^^^^^^

error[unknown-argument]: Argument `resources` does not match any known parameter
    --> src/fastmcp/server/tasks/capabilities.py:40:13
   |
40 |             resources={"read": {}},
   |             ^^^^^^^^^^^^^^^^^^^^^^

Runtime verification:

>>> from mcp.types import ServerTasksRequestsCapability
>>> ServerTasksRequestsCapability.model_fields.keys()
dict_keys(['tools'])  # Only 'tools' is defined

>>> ServerTasksRequestsCapability.model_config
{'extra': 'allow'}  # But extra fields are allowed!

>>> cap = ServerTasksRequestsCapability(
...     tools=TasksToolsCapability(call=TasksCallCapability()),
...     prompts={'get': {}},
...     resources={'read': {}}
... )
>>> cap.model_dump()
{'tools': {'call': {}}, 'prompts': {'get': {}}, 'resources': {'read': {}}}
# ✅ Works at runtime
Related Files
  • src/fastmcp/server/tasks/capabilities.py:39-40 - Lines causing the type error
  • MCP SDK mcp.types.ServerTasksRequestsCapability - Type definition with extra='allow'

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 14, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR moves task capability handling out of experimental capability plumbing and into the server capability model. It adds a get_capabilities override on LowLevelServer that populates capabilities.tasks using get_task_capabilities(). get_task_capabilities() now returns ServerTasksCapability | None with MCP SDK types. Call sites (transports, HTTP, and STDIO server) no longer construct or pass experimental_capabilities for tasks when creating initialization options or running the MCP server.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: fixing the location of task capabilities from experimental.tasks to tasks, with reference to issue #2870.
Description check ✅ Passed The description follows the template structure with clear explanation of the bug, fix, and includes 'Closes #2870'. All required checklist items are present but unchecked.
Linked Issues check ✅ Passed The PR successfully addresses issue #2870 by moving task capabilities from experimental to first-class field by overriding get_capabilities() in LowLevelServer per SEP-1686.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing task capabilities location: modified get_task_capabilities() return type, added get_capabilities() override, and removed experimental_capabilities usage.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4028b64 and 91df79d.

📒 Files selected for processing (1)
  • src/fastmcp/server/server.py
💤 Files with no reviewable changes (1)
  • src/fastmcp/server/server.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies

✏️ Tip: You can disable this entire section by setting review_details to false in your review 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
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

Caution

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

⚠️ Outside diff range comments (1)
src/fastmcp/server/server.py (1)

2379-2394: Critical: run() call is outside the stdio_server() context manager.

The indentation shows that await self._mcp_server.run() (lines 2385-2394) is executed after the async with stdio_server() context exits. This means read_stream and write_stream will be closed before they're used, causing the server to fail.

The run() call must be indented to be inside the async with stdio_server() block.

🐛 Proposed fix
                 async with self._lifespan_manager():
                     async with stdio_server() as (read_stream, write_stream):
                         mode = " (stateless)" if stateless else ""
                         logger.info(
                             f"Starting MCP server {self.name!r} with transport 'stdio'{mode}"
                         )
 
-                    await self._mcp_server.run(
-                        read_stream,
-                        write_stream,
-                        self._mcp_server.create_initialization_options(
-                            notification_options=NotificationOptions(
-                                tools_changed=True
+                        await self._mcp_server.run(
+                            read_stream,
+                            write_stream,
+                            self._mcp_server.create_initialization_options(
+                                notification_options=NotificationOptions(
+                                    tools_changed=True
+                                ),
                             ),
-                        ),
-                        stateless=stateless,
-                    )
+                            stateless=stateless,
+                        )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d83831 and 3c3a62e.

⛔ 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
  • src/fastmcp/server/http.py
  • src/fastmcp/server/low_level.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/tasks/capabilities.py
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Python ≥3.10 with full type annotations required for all code
Never use bare except - be specific with exception types in Python code

Files:

  • src/fastmcp/server/tasks/capabilities.py
  • src/fastmcp/server/server.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/server/http.py
  • src/fastmcp/server/low_level.py
🧠 Learnings (1)
📚 Learning: 2026-01-13T03:11:40.917Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T03:11:40.917Z
Learning: Applies to **/__init__.py : Be intentional about module re-exports - only re-export fundamental types to fastmcp.*; prefer users importing from specific submodules

Applied to files:

  • src/fastmcp/client/transports.py
🧬 Code graph analysis (4)
src/fastmcp/server/server.py (1)
src/fastmcp/server/low_level.py (2)
  • run (193-227)
  • create_initialization_options (153-166)
src/fastmcp/client/transports.py (1)
src/fastmcp/server/low_level.py (1)
  • create_initialization_options (153-166)
src/fastmcp/server/http.py (1)
src/fastmcp/server/low_level.py (1)
  • create_initialization_options (153-166)
src/fastmcp/server/low_level.py (2)
src/fastmcp/server/context.py (1)
  • fastmcp (186-191)
src/fastmcp/server/tasks/capabilities.py (1)
  • get_task_capabilities (20-42)
🪛 GitHub Actions: Run static analysis
src/fastmcp/server/tasks/capabilities.py

[error] 39-39: ty check: Argument prompts does not match any known parameter.


[error] 40-40: ty check: Argument resources does not match any known parameter.

⏰ 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 ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
🔇 Additional comments (4)
src/fastmcp/client/transports.py (1)

884-891: LGTM! Initialization options simplified correctly.

The removal of experimental_capabilities from create_initialization_options() aligns with the PR objective. Task capabilities are now exposed as a first-class field via the get_capabilities() override in LowLevelServer, so they no longer need to be passed through the experimental path.

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

169-176: LGTM! SSE handler initialization simplified.

The SSE transport path now correctly relies on the get_capabilities() override in LowLevelServer to expose task capabilities as a first-class field, rather than passing them through experimental_capabilities.

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

168-191: LGTM! Core fix for SEP-1686 task capabilities location.

This override correctly places task capabilities in capabilities.tasks as a first-class field instead of routing through experimental_capabilities. The implementation:

  1. Delegates to the parent for base capability construction
  2. Sets capabilities.tasks directly with the typed ServerTasksCapability (or None if docket is unavailable)

This enables proper task detection by clients like VS Code Copilot 1.107+ that check capabilities.tasks?.requests?.tools?.call.

src/fastmcp/server/tasks/capabilities.py (1)

34-42: No action needed. The code at lines 39-40 already has # type: ignore[call-arg] comments in place, which properly suppresses the type checker for these parameters. The docstring (lines 28-29) explicitly documents that prompts and resources are intentionally passed via extra data for forward compatibility since the SDK types don't yet include them. Tests confirm this code works correctly.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

The run() call was outside the async with stdio_server() block, meaning
the streams would be closed before being used.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@chrisguidry chrisguidry merged commit 1a12374 into main Jan 14, 2026
11 checks passed
@chrisguidry chrisguidry deleted the fix-task-capabilities-main branch January 14, 2026 14:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Task capabilities in wrong location (experimental.tasks vs tasks)

2 participants