SEP-1686: Tasks (#1645)
This commit is contained in:
parent
5983a650cc
commit
c92bb2f7ff
5 changed files with 14230 additions and 120 deletions
37
examples/servers/simple-task/README.md
Normal file
37
examples/servers/simple-task/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# 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
|
||||
```
|
||||
0
examples/servers/simple-task/mcp_simple_task/__init__.py
Normal file
0
examples/servers/simple-task/mcp_simple_task/__init__.py
Normal file
5
examples/servers/simple-task/mcp_simple_task/__main__.py
Normal file
5
examples/servers/simple-task/mcp_simple_task/__main__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import sys
|
||||
|
||||
from .server import main
|
||||
|
||||
sys.exit(main()) # type: ignore[call-arg]
|
||||
84
examples/servers/simple-task/mcp_simple_task/server.py
Normal file
84
examples/servers/simple-task/mcp_simple_task/server.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
"""Simple task server demonstrating MCP tasks over streamable HTTP."""
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Any
|
||||
|
||||
import anyio
|
||||
import click
|
||||
import mcp.types as types
|
||||
import uvicorn
|
||||
from mcp.server.experimental.task_context import ServerTaskContext
|
||||
from mcp.server.lowlevel import Server
|
||||
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount
|
||||
|
||||
server = Server("simple-task-server")
|
||||
|
||||
# One-line setup: auto-registers get_task, get_task_result, list_tasks, cancel_task
|
||||
server.experimental.enable_tasks()
|
||||
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools() -> list[types.Tool]:
|
||||
return [
|
||||
types.Tool(
|
||||
name="long_running_task",
|
||||
description="A task that takes a few seconds to complete with status updates",
|
||||
inputSchema={"type": "object", "properties": {}},
|
||||
execution=types.ToolExecution(taskSupport=types.TASK_REQUIRED),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
async def handle_long_running_task(arguments: dict[str, Any]) -> types.CreateTaskResult:
|
||||
"""Handle the long_running_task tool - demonstrates status updates."""
|
||||
ctx = server.request_context
|
||||
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)
|
||||
|
||||
|
||||
@server.call_tool()
|
||||
async def handle_call_tool(name: str, arguments: dict[str, Any]) -> types.CallToolResult | types.CreateTaskResult:
|
||||
"""Dispatch tool calls to their handlers."""
|
||||
if name == "long_running_task":
|
||||
return await handle_long_running_task(arguments)
|
||||
else:
|
||||
return types.CallToolResult(
|
||||
content=[types.TextContent(type="text", text=f"Unknown tool: {name}")],
|
||||
isError=True,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--port", default=8000, help="Port to listen on")
|
||||
def main(port: int) -> int:
|
||||
session_manager = StreamableHTTPSessionManager(app=server)
|
||||
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(app: Starlette) -> AsyncIterator[None]:
|
||||
async with session_manager.run():
|
||||
yield
|
||||
|
||||
starlette_app = Starlette(
|
||||
routes=[Mount("/mcp", app=session_manager.handle_request)],
|
||||
lifespan=app_lifespan,
|
||||
)
|
||||
|
||||
print(f"Starting server on http://localhost:{port}/mcp")
|
||||
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
|
||||
return 0
|
||||
43
examples/servers/simple-task/pyproject.toml
Normal file
43
examples/servers/simple-task/pyproject.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[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 = "Anthropic, PBC." }]
|
||||
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"]
|
||||
Loading…
Reference in a new issue