Skip to content

Fix task capabilities location (issue #2870)#2874

Merged
chrisguidry merged 3 commits intorelease/2.xfrom
fix-task-capabilities-2.x
Jan 14, 2026
Merged

Fix task capabilities location (issue #2870)#2874
chrisguidry merged 3 commits intorelease/2.xfrom
fix-task-capabilities-2.x

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
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 14, 2026

Warning

Rate limit exceeded

@jlowin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 26 minutes and 1 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 ed72c4b and b0b6826.

📒 Files selected for processing (1)
  • src/fastmcp/server/tasks/capabilities.py

Walkthrough

The PR refactors task capabilities handling to expose tasks as a first-class field in ServerCapabilities rather than through experimental_capabilities. It removes experimental_capabilities construction from client and server initialization paths, updates get_task_capabilities() to return a strongly-typed ServerTasksCapability object instead of a plain dictionary, and adds a new get_capabilities() method in LowLevelServer that directly sets the tasks field from the returned capabilities object.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% 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 accurately summarizes the main change: fixing task capabilities location and references the linked issue #2870.
Description check ✅ Passed The PR description clearly explains the problem, solution, and impact. It includes a reference to the linked issue (#2870), explains the technical fix, and notes preserved infrastructure.
Linked Issues check ✅ Passed The PR addresses all coding requirements from #2870: returns ServerTasksCapability-typed values, overrides get_capabilities() in LowLevelServer to set capabilities.tasks as first-class field, removes experimental_capabilities reliance, and maintains existing task infrastructure.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing task capabilities location per #2870. Modifications span multiple files but are unified in purpose: removing experimental_capabilities path and implementing spec-aligned capability placement.

✏️ 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.

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Jan 14, 2026

Test Failure Analysis

Summary: The integration test test_call_tool_list_commits is timing out after 30s due to a GitHub API rate limit (HTTP 429). Additionally, a critical bug exists in the PR's task capabilities implementation.

Root Cause: Two issues:

  1. Test Timeout: The test hits GitHub's Copilot MCP API and receives a 429 Too Many Requests response during teardown, blocking for >30s and triggering pytest's timeout.

  2. Task Capabilities Bug: The PR incorrectly structures the task capabilities dict, which will break task detection by clients.

Critical Bug in PR Code:

# In src/fastmcp/server/low_level.py:186
capabilities.tasks = get_task_capabilities()

The problem: get_task_capabilities() returns:

{"tasks": {"list": {}, "cancel": {}, "requests": {...}}}

But ServerCapabilities.tasks expects either:

  • A ServerTasksCapability object with fields list, cancel, requests
  • A dict that can be coerced to ServerTasksCapability

The current code assigns a dict with a nested "tasks" key, creating the wrong structure.

Suggested Solution:

1. Fix the capabilities bug (CRITICAL - blocks the PR goal):

# In src/fastmcp/server/tasks/capabilities.py
from mcp.types import ServerTasksCapability

def get_task_capabilities() -> ServerTasksCapability | None:
    if not _is_docket_available():
        return None
    
    # Return a ServerTasksCapability object directly
    return ServerTasksCapability(
        list={},
        cancel={},
        requests={
            "tools": {"call": {}},
            "prompts": {"get": {}},
            "resources": {"read": {}},
        },
    )

OR (simpler - remove the outer "tasks" key):

# In src/fastmcp/server/tasks/capabilities.py
def get_task_capabilities() -> dict[str, Any] | None:
    if not _is_docket_available():
        return None
    
    # Return dict WITHOUT outer "tasks" key - Pydantic will coerce it
    return {
        "list": {},
        "cancel": {},
        "requests": {
            "tools": {"call": {}},
            "prompts": {"get": {}},
            "resources": {"read": {}},
        },
    }

2. Address the flaky test (prevents CI from passing):

Option A - Add retry with backoff:

# In tests/integration_tests/test_github_mcp_remote.py
@pytest.mark.integration
@pytest.mark.flaky(reruns=2, reruns_delay=5)
async def test_call_tool_list_commits(...):
    ...

Option B - Skip on rate limit:

import httpx

async def test_call_tool_list_commits(...):
    try:
        async with streamable_http_client:
            ...
    except httpx.HTTPStatusError as e:
        if e.response.status_code == 429:
            pytest.skip("GitHub API rate limit exceeded")
        raise
Detailed Analysis

Error Log Excerpt:

httpx.HTTPStatusError: Client error '429 Too Many Requests' for url 'https://api.githubcopilot.com/mcp/'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429

The 429 error occurs during test teardown: __aexit__ -> _disconnect() -> _session_runner()

The test successfully calls list_commits, but the client cannot cleanly disconnect due to rate limiting.

Type Verification:

# From MCP SDK:
ServerCapabilities.tasks: ServerTasksCapability | None
ServerTasksCapability.model_fields: ['list', 'cancel', 'requests']

The current implementation assigns {"tasks": {...}} to capabilities.tasks, but it should assign {"list": {}, "cancel": {}, "requests": {...}}.

Related Files

Files requiring changes:

  • src/fastmcp/server/tasks/capabilities.py (line 24-34) - CRITICAL: Fix return value to remove outer "tasks" key
  • tests/integration_tests/test_github_mcp_remote.py (line 99-124) - Add retry or skip on 429

Verification needed:

  • src/fastmcp/server/low_level.py (line 186) - Should work correctly once capabilities.py is fixed

Note: This comment has been edited to include type verification and refined solution.

Copy link
Copy Markdown
Collaborator

@chrisguidry chrisguidry left a comment

Choose a reason for hiding this comment

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

A thing of beauty

@chrisguidry chrisguidry merged commit 0e5677c into release/2.x Jan 14, 2026
9 checks passed
@chrisguidry chrisguidry deleted the fix-task-capabilities-2.x branch January 14, 2026 14:18
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.

2 participants