Remove Tasks (SEP-1686) from the SDK (#2714)

This commit is contained in:
Max 2026-06-02 17:27:05 +01:00 • committed by GitHub
commit 8cc187fac0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 63 additions and 12963 deletions

View file

@ -1,74 +0,0 @@
# Simple Interactive Task Server
A minimal MCP server demonstrating interactive tasks with elicitation and sampling.
## Running
```bash
cd examples/servers/simple-task-interactive
uv run mcp-simple-task-interactive
```
The server starts on `http://localhost:8000/mcp` by default. Use `--port` to change.
## What it does
This server exposes two tools:
### `confirm_delete` (demonstrates elicitation)
Asks the user for confirmation before "deleting" a file.
- Uses `task.elicit()` to request user input
- Shows the elicitation flow: task -> input_required -> response -> complete
### `write_haiku` (demonstrates sampling)
Asks the LLM to write a haiku about a topic.
- Uses `task.create_message()` to request LLM completion
- Shows the sampling flow: task -> input_required -> response -> complete
## Usage with the client
In one terminal, start the server:
```bash
cd examples/servers/simple-task-interactive
uv run mcp-simple-task-interactive
```
In another terminal, run the interactive client:
```bash
cd examples/clients/simple-task-interactive-client
uv run mcp-simple-task-interactive-client
```
## Expected server output
When a client connects and calls the tools, you'll see:
```text
Starting server on http://localhost:8000/mcp
[Server] confirm_delete called for 'important.txt'
[Server] Task created: <task-id>
[Server] Sending elicitation request to client...
[Server] Received elicitation response: action=accept, content={'confirm': True}
[Server] Completing task with result: Deleted 'important.txt'
[Server] write_haiku called for topic 'autumn leaves'
[Server] Task created: <task-id>
[Server] Sending sampling request to client...
[Server] Received sampling response: Cherry blossoms fall
Softly on the quiet pon...
[Server] Completing task with haiku
```
## Key concepts
1. **ServerTaskContext**: Provides `elicit()` and `create_message()` for user interaction
2. **run_task()**: Spawns background work, auto-completes/fails, returns immediately
3. **TaskResultHandler**: Delivers queued messages and routes responses
4. **Response routing**: Responses are routed back to waiting resolvers

View file

@ -1,5 +0,0 @@
import sys
from .server import main
sys.exit(main()) # type: ignore[call-arg]

View file

@ -1,138 +0,0 @@
"""Simple interactive task server demonstrating elicitation and sampling.
This example shows the simplified task API where:
- server.experimental.enable_tasks() sets up all infrastructure
- ctx.experimental.run_task() handles task lifecycle automatically
- ServerTaskContext.elicit() and ServerTaskContext.create_message() queue requests properly
"""
from typing import Any
import click
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.experimental.task_context import ServerTaskContext
async def handle_list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(
tools=[
types.Tool(
name="confirm_delete",
description="Asks for confirmation before deleting (demonstrates elicitation)",
input_schema={
"type": "object",
"properties": {"filename": {"type": "string"}},
},
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
),
types.Tool(
name="write_haiku",
description="Asks LLM to write a haiku (demonstrates sampling)",
input_schema={"type": "object", "properties": {"topic": {"type": "string"}}},
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
),
]
)
async def handle_confirm_delete(ctx: ServerRequestContext, arguments: dict[str, Any]) -> types.CreateTaskResult:
"""Handle the confirm_delete tool - demonstrates elicitation."""
ctx.experimental.validate_task_mode(types.TASK_REQUIRED)
filename = arguments.get("filename", "unknown.txt")
print(f"\n[Server] confirm_delete called for '{filename}'")
async def work(task: ServerTaskContext) -> types.CallToolResult:
print(f"[Server] Task {task.task_id} starting elicitation...")
result = await task.elicit(
message=f"Are you sure you want to delete '{filename}'?",
requested_schema={
"type": "object",
"properties": {"confirm": {"type": "boolean"}},
"required": ["confirm"],
},
)
print(f"[Server] Received elicitation response: action={result.action}, content={result.content}")
if result.action == "accept" and result.content:
confirmed = result.content.get("confirm", False)
text = f"Deleted '{filename}'" if confirmed else "Deletion cancelled"
else:
text = "Deletion cancelled"
print(f"[Server] Completing task with result: {text}")
return types.CallToolResult(content=[types.TextContent(type="text", text=text)])
return await ctx.experimental.run_task(work)
async def handle_write_haiku(ctx: ServerRequestContext, arguments: dict[str, Any]) -> types.CreateTaskResult:
"""Handle the write_haiku tool - demonstrates sampling."""
ctx.experimental.validate_task_mode(types.TASK_REQUIRED)
topic = arguments.get("topic", "nature")
print(f"\n[Server] write_haiku called for topic '{topic}'")
async def work(task: ServerTaskContext) -> types.CallToolResult:
print(f"[Server] Task {task.task_id} starting sampling...")
result = await task.create_message(
messages=[
types.SamplingMessage(
role="user",
content=types.TextContent(type="text", text=f"Write a haiku about {topic}"),
)
],
max_tokens=50,
)
haiku = "No response"
if isinstance(result.content, types.TextContent):
haiku = result.content.text
print(f"[Server] Received sampling response: {haiku[:50]}...")
return types.CallToolResult(content=[types.TextContent(type="text", text=f"Haiku:\n{haiku}")])
return await ctx.experimental.run_task(work)
async def handle_call_tool(
ctx: ServerRequestContext, params: types.CallToolRequestParams
) -> types.CallToolResult | types.CreateTaskResult:
"""Dispatch tool calls to their handlers."""
arguments = params.arguments or {}
if params.name == "confirm_delete":
return await handle_confirm_delete(ctx, arguments)
elif params.name == "write_haiku":
return await handle_write_haiku(ctx, arguments)
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Unknown tool: {params.name}")],
is_error=True,
)
server = Server(
"simple-task-interactive",
on_list_tools=handle_list_tools,
on_call_tool=handle_call_tool,
)
# Enable task support - this auto-registers all handlers
server.experimental.enable_tasks()
@click.command()
@click.option("--port", default=8000, help="Port to listen on")
def main(port: int) -> int:
starlette_app = server.streamable_http_app()
print(f"Starting server on http://localhost:{port}/mcp")
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
return 0

View file

@ -1,43 +0,0 @@
[project]
name = "mcp-simple-task-interactive"
version = "0.1.0"
description = "A simple MCP server demonstrating interactive tasks (elicitation & sampling)"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
keywords = ["mcp", "llm", "tasks", "elicitation", "sampling"]
license = { text = "MIT" }
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
dependencies = ["anyio>=4.5", "click>=8.0", "mcp", "starlette", "uvicorn"]
[project.scripts]
mcp-simple-task-interactive = "mcp_simple_task_interactive.server:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["mcp_simple_task_interactive"]
[tool.pyright]
include = ["mcp_simple_task_interactive"]
venvPath = "."
venv = ".venv"
[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = []
[tool.ruff]
line-length = 120
target-version = "py310"
[dependency-groups]
dev = ["pyright>=1.1.378", "ruff>=0.6.9"]

View file

@ -1,37 +0,0 @@
# Simple Task Server
A minimal MCP server demonstrating the experimental tasks feature over streamable HTTP.
## Running
```bash
cd examples/servers/simple-task
uv run mcp-simple-task
```
The server starts on `http://localhost:8000/mcp` by default. Use `--port` to change.
## What it does
This server exposes a single tool `long_running_task` that:
1. Must be called as a task (with `task` metadata in the request)
2. Takes ~3 seconds to complete
3. Sends status updates during execution
4. Returns a result when complete
## Usage with the client
In one terminal, start the server:
```bash
cd examples/servers/simple-task
uv run mcp-simple-task
```
In another terminal, run the client:
```bash
cd examples/clients/simple-task-client
uv run mcp-simple-task-client
```

View file

@ -1,5 +0,0 @@
import sys
from .server import main
sys.exit(main()) # type: ignore[call-arg]

View file

@ -1,70 +0,0 @@
"""Simple task server demonstrating MCP tasks over streamable HTTP."""
import anyio
import click
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.experimental.task_context import ServerTaskContext
async def handle_list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
return types.ListToolsResult(
tools=[
types.Tool(
name="long_running_task",
description="A task that takes a few seconds to complete with status updates",
input_schema={"type": "object", "properties": {}},
execution=types.ToolExecution(task_support=types.TASK_REQUIRED),
)
]
)
async def handle_call_tool(
ctx: ServerRequestContext, params: types.CallToolRequestParams
) -> types.CallToolResult | types.CreateTaskResult:
"""Dispatch tool calls to their handlers."""
if params.name == "long_running_task":
ctx.experimental.validate_task_mode(types.TASK_REQUIRED)
async def work(task: ServerTaskContext) -> types.CallToolResult:
await task.update_status("Starting work...")
await anyio.sleep(1)
await task.update_status("Processing step 1...")
await anyio.sleep(1)
await task.update_status("Processing step 2...")
await anyio.sleep(1)
return types.CallToolResult(content=[types.TextContent(type="text", text="Task completed!")])
return await ctx.experimental.run_task(work)
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Unknown tool: {params.name}")],
is_error=True,
)
server = Server(
"simple-task-server",
on_list_tools=handle_list_tools,
on_call_tool=handle_call_tool,
)
# One-line setup: auto-registers get_task, get_task_result, list_tasks, cancel_task
server.experimental.enable_tasks()
@click.command()
@click.option("--port", default=8000, help="Port to listen on")
def main(port: int) -> int:
starlette_app = server.streamable_http_app()
print(f"Starting server on http://localhost:{port}/mcp")
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
return 0

View file

@ -1,43 +0,0 @@
[project]
name = "mcp-simple-task"
version = "0.1.0"
description = "A simple MCP server demonstrating tasks"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
keywords = ["mcp", "llm", "tasks"]
license = { text = "MIT" }
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
dependencies = ["anyio>=4.5", "click>=8.0", "mcp", "starlette", "uvicorn"]
[project.scripts]
mcp-simple-task = "mcp_simple_task.server:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["mcp_simple_task"]
[tool.pyright]
include = ["mcp_simple_task"]
venvPath = "."
venv = ".venv"
[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = []
[tool.ruff]
line-length = 120
target-version = "py310"
[dependency-groups]
dev = ["pyright>=1.1.378", "ruff>=0.6.9"]