mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-20 20:44:17 +02:00
Fix task capabilities location (issue #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
This commit is contained in:
parent
bc2f601e52
commit
dec3c6d449
6 changed files with 56 additions and 41 deletions
|
|
@ -36,7 +36,6 @@ from fastmcp.client.auth.oauth import OAuth
|
|||
from fastmcp.mcp_config import MCPConfig, infer_transport_type_from_url
|
||||
from fastmcp.server.dependencies import get_http_headers
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.server.tasks.capabilities import get_task_capabilities
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
|
||||
|
|
@ -900,16 +899,11 @@ class FastMCPTransport(ClientTransport):
|
|||
anyio.create_task_group() as tg,
|
||||
_enter_server_lifespan(server=self.server),
|
||||
):
|
||||
# Build experimental capabilities
|
||||
experimental_capabilities = get_task_capabilities()
|
||||
|
||||
tg.start_soon(
|
||||
lambda: self.server._mcp_server.run(
|
||||
server_read,
|
||||
server_write,
|
||||
self.server._mcp_server.create_initialization_options(
|
||||
experimental_capabilities=experimental_capabilities
|
||||
),
|
||||
self.server._mcp_server.create_initialization_options(),
|
||||
raise_exceptions=self.raise_exceptions,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from starlette.types import Lifespan, Receive, Scope, Send
|
|||
|
||||
from fastmcp.server.auth import AuthProvider
|
||||
from fastmcp.server.auth.middleware import RequireAuthMiddleware
|
||||
from fastmcp.server.tasks.capabilities import get_task_capabilities
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -160,15 +159,10 @@ def create_sse_app(
|
|||
# Create handler for SSE connections
|
||||
async def handle_sse(scope: Scope, receive: Receive, send: Send) -> Response:
|
||||
async with sse.connect_sse(scope, receive, send) as streams:
|
||||
# Build experimental capabilities
|
||||
experimental_capabilities = get_task_capabilities()
|
||||
|
||||
await server._mcp_server.run(
|
||||
streams[0],
|
||||
streams[1],
|
||||
server._mcp_server.create_initialization_options(
|
||||
experimental_capabilities=experimental_capabilities
|
||||
),
|
||||
server._mcp_server.create_initialization_options(),
|
||||
)
|
||||
return Response()
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,31 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|||
**kwargs,
|
||||
)
|
||||
|
||||
def get_capabilities(
|
||||
self,
|
||||
notification_options: NotificationOptions,
|
||||
experimental_capabilities: dict[str, dict[str, Any]],
|
||||
) -> mcp.types.ServerCapabilities:
|
||||
"""Override to set capabilities.tasks as a first-class field per SEP-1686.
|
||||
|
||||
This ensures task capabilities appear in capabilities.tasks instead of
|
||||
capabilities.experimental.tasks, which is required by the MCP spec and
|
||||
enables proper task detection by clients like VS Code Copilot 1.107+.
|
||||
"""
|
||||
from fastmcp.server.tasks.capabilities import get_task_capabilities
|
||||
|
||||
# Get base capabilities from SDK (pass empty dict for experimental)
|
||||
# since we'll set tasks as a first-class field instead
|
||||
capabilities = super().get_capabilities(
|
||||
notification_options,
|
||||
experimental_capabilities or {},
|
||||
)
|
||||
|
||||
# Set tasks as a first-class field (not experimental) per SEP-1686
|
||||
capabilities.tasks = get_task_capabilities()
|
||||
|
||||
return capabilities
|
||||
|
||||
async def run(
|
||||
self,
|
||||
read_stream: MemoryObjectReceiveStream[SessionMessage | Exception],
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ from fastmcp.server.http import (
|
|||
)
|
||||
from fastmcp.server.low_level import LowLevelServer
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
from fastmcp.server.tasks.capabilities import get_task_capabilities
|
||||
from fastmcp.server.tasks.config import TaskConfig
|
||||
from fastmcp.server.tasks.handlers import (
|
||||
handle_prompt_as_task,
|
||||
|
|
@ -2508,9 +2507,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
f"Starting MCP server {self.name!r} with transport 'stdio'"
|
||||
)
|
||||
|
||||
# Build experimental capabilities
|
||||
experimental_capabilities = get_task_capabilities()
|
||||
|
||||
await self._mcp_server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
|
|
@ -2518,7 +2514,6 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
notification_options=NotificationOptions(
|
||||
tools_changed=True
|
||||
),
|
||||
experimental_capabilities=experimental_capabilities,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,29 @@
|
|||
"""SEP-1686 task capabilities declaration."""
|
||||
|
||||
from typing import Any
|
||||
from mcp.types import (
|
||||
ServerTasksCapability,
|
||||
ServerTasksRequestsCapability,
|
||||
TasksCancelCapability,
|
||||
TasksListCapability,
|
||||
TasksToolsCapability,
|
||||
)
|
||||
|
||||
|
||||
def get_task_capabilities() -> dict[str, Any]:
|
||||
"""Return the SEP-1686 task capabilities structure.
|
||||
def get_task_capabilities() -> ServerTasksCapability:
|
||||
"""Return the SEP-1686 task capabilities.
|
||||
|
||||
This is the standard capabilities map advertised to clients,
|
||||
declaring support for list, cancel, and request operations.
|
||||
Returns task capabilities as a first-class ServerCapabilities field,
|
||||
declaring support for list, cancel, and request operations per SEP-1686.
|
||||
|
||||
Note: prompts/resources are passed via extra_data since the SDK types
|
||||
don't include them yet (FastMCP supports them ahead of the spec).
|
||||
"""
|
||||
return {
|
||||
"tasks": {
|
||||
"list": {},
|
||||
"cancel": {},
|
||||
"requests": {
|
||||
"tools": {"call": {}},
|
||||
"prompts": {"get": {}},
|
||||
"resources": {"read": {}},
|
||||
},
|
||||
}
|
||||
}
|
||||
return ServerTasksCapability(
|
||||
list=TasksListCapability(),
|
||||
cancel=TasksCancelCapability(),
|
||||
requests=ServerTasksRequestsCapability(
|
||||
tools=TasksToolsCapability(call={}),
|
||||
prompts={"get": {}},
|
||||
resources={"read": {}},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from fastmcp.server.tasks import get_task_capabilities
|
|||
|
||||
|
||||
async def test_capabilities_include_tasks():
|
||||
"""Server capabilities always include tasks."""
|
||||
"""Server capabilities always include tasks in first-class field (SEP-1686)."""
|
||||
mcp = FastMCP("capability-test")
|
||||
|
||||
@mcp.tool()
|
||||
|
|
@ -22,11 +22,11 @@ async def test_capabilities_include_tasks():
|
|||
# Get server initialization result which includes capabilities
|
||||
init_result = client.initialize_result
|
||||
|
||||
# Verify tasks capability is present
|
||||
assert init_result.capabilities.experimental is not None
|
||||
assert "tasks" in init_result.capabilities.experimental
|
||||
tasks_cap = init_result.capabilities.experimental["tasks"]
|
||||
assert tasks_cap == get_task_capabilities()["tasks"]
|
||||
# Verify tasks capability is present as a first-class field (not experimental)
|
||||
assert init_result.capabilities.tasks is not None
|
||||
assert init_result.capabilities.tasks == get_task_capabilities()
|
||||
# Verify it's NOT in experimental
|
||||
assert "tasks" not in (init_result.capabilities.experimental or {})
|
||||
|
||||
|
||||
async def test_client_uses_task_capable_session():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue