Remove Tasks (SEP-1686) from the SDK (#2714)
This commit is contained in:
parent
a5b2ebb660
commit
8cc187fac0
20 changed files with 63 additions and 12963 deletions
|
|
@ -1,43 +0,0 @@
|
|||
# Simple Task Client
|
||||
|
||||
A minimal MCP client demonstrating polling for task results over streamable HTTP.
|
||||
|
||||
## Running
|
||||
|
||||
First, start the simple-task server in another terminal:
|
||||
|
||||
```bash
|
||||
cd examples/servers/simple-task
|
||||
uv run mcp-simple-task
|
||||
```
|
||||
|
||||
Then run the client:
|
||||
|
||||
```bash
|
||||
cd examples/clients/simple-task-client
|
||||
uv run mcp-simple-task-client
|
||||
```
|
||||
|
||||
Use `--url` to connect to a different server.
|
||||
|
||||
## What it does
|
||||
|
||||
1. Connects to the server via streamable HTTP
|
||||
2. Calls the `long_running_task` tool as a task
|
||||
3. Polls the task status until completion
|
||||
4. Retrieves and prints the result
|
||||
|
||||
## Expected output
|
||||
|
||||
```text
|
||||
Available tools: ['long_running_task']
|
||||
|
||||
Calling tool as a task...
|
||||
Task created: <task-id>
|
||||
Status: working - Starting work...
|
||||
Status: working - Processing step 1...
|
||||
Status: working - Processing step 2...
|
||||
Status: completed -
|
||||
|
||||
Result: Task completed!
|
||||
```
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import sys
|
||||
|
||||
from .main import main
|
||||
|
||||
sys.exit(main()) # type: ignore[call-arg]
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
"""Simple task client demonstrating MCP tasks polling over streamable HTTP."""
|
||||
|
||||
import asyncio
|
||||
|
||||
import click
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.types import CallToolResult, TextContent
|
||||
|
||||
|
||||
async def run(url: str) -> None:
|
||||
async with streamable_http_client(url) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
|
||||
# List tools
|
||||
tools = await session.list_tools()
|
||||
print(f"Available tools: {[t.name for t in tools.tools]}")
|
||||
|
||||
# Call the tool as a task
|
||||
print("\nCalling tool as a task...")
|
||||
|
||||
result = await session.experimental.call_tool_as_task(
|
||||
"long_running_task",
|
||||
arguments={},
|
||||
ttl=60000,
|
||||
)
|
||||
task_id = result.task.task_id
|
||||
print(f"Task created: {task_id}")
|
||||
|
||||
status = None
|
||||
# Poll until done (respects server's pollInterval hint)
|
||||
async for status in session.experimental.poll_task(task_id):
|
||||
print(f" Status: {status.status} - {status.status_message or ''}")
|
||||
|
||||
# Check final status
|
||||
if status and status.status != "completed":
|
||||
print(f"Task ended with status: {status.status}")
|
||||
return
|
||||
|
||||
# Get the result
|
||||
task_result = await session.experimental.get_task_result(task_id, CallToolResult)
|
||||
content = task_result.content[0]
|
||||
if isinstance(content, TextContent):
|
||||
print(f"\nResult: {content.text}")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--url", default="http://localhost:8000/mcp", help="Server URL")
|
||||
def main(url: str) -> int:
|
||||
asyncio.run(run(url))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[project]
|
||||
name = "mcp-simple-task-client"
|
||||
version = "0.1.0"
|
||||
description = "A simple MCP client demonstrating task polling"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
|
||||
keywords = ["mcp", "llm", "tasks", "client"]
|
||||
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 = ["click>=8.0", "mcp"]
|
||||
|
||||
[project.scripts]
|
||||
mcp-simple-task-client = "mcp_simple_task_client.main:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["mcp_simple_task_client"]
|
||||
|
||||
[tool.pyright]
|
||||
include = ["mcp_simple_task_client"]
|
||||
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"]
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
# Simple Interactive Task Client
|
||||
|
||||
A minimal MCP client demonstrating responses to interactive tasks (elicitation and sampling).
|
||||
|
||||
## Running
|
||||
|
||||
First, start the interactive task server in another terminal:
|
||||
|
||||
```bash
|
||||
cd examples/servers/simple-task-interactive
|
||||
uv run mcp-simple-task-interactive
|
||||
```
|
||||
|
||||
Then run the client:
|
||||
|
||||
```bash
|
||||
cd examples/clients/simple-task-interactive-client
|
||||
uv run mcp-simple-task-interactive-client
|
||||
```
|
||||
|
||||
Use `--url` to connect to a different server.
|
||||
|
||||
## What it does
|
||||
|
||||
1. Connects to the server via streamable HTTP
|
||||
2. Calls `confirm_delete` - server asks for confirmation, client responds via terminal
|
||||
3. Calls `write_haiku` - server requests LLM completion, client returns a hardcoded haiku
|
||||
|
||||
## Key concepts
|
||||
|
||||
### Elicitation callback
|
||||
|
||||
```python
|
||||
async def elicitation_callback(context, params) -> ElicitResult:
|
||||
# Handle user input request from server
|
||||
return ElicitResult(action="accept", content={"confirm": True})
|
||||
```
|
||||
|
||||
### Sampling callback
|
||||
|
||||
```python
|
||||
async def sampling_callback(context, params) -> CreateMessageResult:
|
||||
# Handle LLM completion request from server
|
||||
return CreateMessageResult(model="...", role="assistant", content=...)
|
||||
```
|
||||
|
||||
### Using call_tool_as_task
|
||||
|
||||
```python
|
||||
# Call a tool as a task (returns immediately with task reference)
|
||||
result = await session.experimental.call_tool_as_task("tool_name", {"arg": "value"})
|
||||
task_id = result.task.task_id
|
||||
|
||||
# Get result - this delivers elicitation/sampling requests and blocks until complete
|
||||
final = await session.experimental.get_task_result(task_id, CallToolResult)
|
||||
```
|
||||
|
||||
**Important**: The `get_task_result()` call is what triggers the delivery of elicitation
|
||||
and sampling requests to your callbacks. It blocks until the task completes and returns
|
||||
the final result.
|
||||
|
||||
## Expected output
|
||||
|
||||
```text
|
||||
Available tools: ['confirm_delete', 'write_haiku']
|
||||
|
||||
--- Demo 1: Elicitation ---
|
||||
Calling confirm_delete tool...
|
||||
Task created: <task-id>
|
||||
|
||||
[Elicitation] Server asks: Are you sure you want to delete 'important.txt'?
|
||||
Your response (y/n): y
|
||||
[Elicitation] Responding with: confirm=True
|
||||
Result: Deleted 'important.txt'
|
||||
|
||||
--- Demo 2: Sampling ---
|
||||
Calling write_haiku tool...
|
||||
Task created: <task-id>
|
||||
|
||||
[Sampling] Server requests LLM completion for: Write a haiku about autumn leaves
|
||||
[Sampling] Responding with haiku
|
||||
Result:
|
||||
Haiku:
|
||||
Cherry blossoms fall
|
||||
Softly on the quiet pond
|
||||
Spring whispers goodbye
|
||||
```
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import sys
|
||||
|
||||
from .main import main
|
||||
|
||||
sys.exit(main()) # type: ignore[call-arg]
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
"""Simple interactive task client demonstrating elicitation and sampling responses.
|
||||
|
||||
This example demonstrates the spec-compliant polling pattern:
|
||||
1. Poll tasks/get watching for status changes
|
||||
2. On input_required, call tasks/result to receive elicitation/sampling requests
|
||||
3. Continue until terminal status, then retrieve final result
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import click
|
||||
from mcp import ClientSession
|
||||
from mcp.client.context import ClientRequestContext
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.types import (
|
||||
CallToolResult,
|
||||
CreateMessageRequestParams,
|
||||
CreateMessageResult,
|
||||
ElicitRequestParams,
|
||||
ElicitResult,
|
||||
TextContent,
|
||||
)
|
||||
|
||||
|
||||
async def elicitation_callback(
|
||||
context: ClientRequestContext,
|
||||
params: ElicitRequestParams,
|
||||
) -> ElicitResult:
|
||||
"""Handle elicitation requests from the server."""
|
||||
print(f"\n[Elicitation] Server asks: {params.message}")
|
||||
|
||||
# Simple terminal prompt
|
||||
response = input("Your response (y/n): ").strip().lower()
|
||||
confirmed = response in ("y", "yes", "true", "1")
|
||||
|
||||
print(f"[Elicitation] Responding with: confirm={confirmed}")
|
||||
return ElicitResult(action="accept", content={"confirm": confirmed})
|
||||
|
||||
|
||||
async def sampling_callback(
|
||||
context: ClientRequestContext,
|
||||
params: CreateMessageRequestParams,
|
||||
) -> CreateMessageResult:
|
||||
"""Handle sampling requests from the server."""
|
||||
# Get the prompt from the first message
|
||||
prompt = "unknown"
|
||||
if params.messages:
|
||||
content = params.messages[0].content
|
||||
if isinstance(content, TextContent):
|
||||
prompt = content.text
|
||||
|
||||
print(f"\n[Sampling] Server requests LLM completion for: {prompt}")
|
||||
|
||||
# Return a hardcoded haiku (in real use, call your LLM here)
|
||||
haiku = """Cherry blossoms fall
|
||||
Softly on the quiet pond
|
||||
Spring whispers goodbye"""
|
||||
|
||||
print("[Sampling] Responding with haiku")
|
||||
return CreateMessageResult(
|
||||
model="mock-haiku-model",
|
||||
role="assistant",
|
||||
content=TextContent(type="text", text=haiku),
|
||||
)
|
||||
|
||||
|
||||
def get_text(result: CallToolResult) -> str:
|
||||
"""Extract text from a CallToolResult."""
|
||||
if result.content and isinstance(result.content[0], TextContent):
|
||||
return result.content[0].text
|
||||
return "(no text)"
|
||||
|
||||
|
||||
async def run(url: str) -> None:
|
||||
async with streamable_http_client(url) as (read, write):
|
||||
async with ClientSession(
|
||||
read,
|
||||
write,
|
||||
elicitation_callback=elicitation_callback,
|
||||
sampling_callback=sampling_callback,
|
||||
) as session:
|
||||
await session.initialize()
|
||||
|
||||
# List tools
|
||||
tools = await session.list_tools()
|
||||
print(f"Available tools: {[t.name for t in tools.tools]}")
|
||||
|
||||
# Demo 1: Elicitation (confirm_delete)
|
||||
print("\n--- Demo 1: Elicitation ---")
|
||||
print("Calling confirm_delete tool...")
|
||||
|
||||
elicit_task = await session.experimental.call_tool_as_task("confirm_delete", {"filename": "important.txt"})
|
||||
elicit_task_id = elicit_task.task.task_id
|
||||
print(f"Task created: {elicit_task_id}")
|
||||
|
||||
# Poll until terminal, calling tasks/result on input_required
|
||||
async for status in session.experimental.poll_task(elicit_task_id):
|
||||
print(f"[Poll] Status: {status.status}")
|
||||
if status.status == "input_required":
|
||||
# Server needs input - tasks/result delivers the elicitation request
|
||||
elicit_result = await session.experimental.get_task_result(elicit_task_id, CallToolResult)
|
||||
break
|
||||
else:
|
||||
# poll_task exited due to terminal status
|
||||
elicit_result = await session.experimental.get_task_result(elicit_task_id, CallToolResult)
|
||||
|
||||
print(f"Result: {get_text(elicit_result)}")
|
||||
|
||||
# Demo 2: Sampling (write_haiku)
|
||||
print("\n--- Demo 2: Sampling ---")
|
||||
print("Calling write_haiku tool...")
|
||||
|
||||
sampling_task = await session.experimental.call_tool_as_task("write_haiku", {"topic": "autumn leaves"})
|
||||
sampling_task_id = sampling_task.task.task_id
|
||||
print(f"Task created: {sampling_task_id}")
|
||||
|
||||
# Poll until terminal, calling tasks/result on input_required
|
||||
async for status in session.experimental.poll_task(sampling_task_id):
|
||||
print(f"[Poll] Status: {status.status}")
|
||||
if status.status == "input_required":
|
||||
sampling_result = await session.experimental.get_task_result(sampling_task_id, CallToolResult)
|
||||
break
|
||||
else:
|
||||
sampling_result = await session.experimental.get_task_result(sampling_task_id, CallToolResult)
|
||||
|
||||
print(f"Result:\n{get_text(sampling_result)}")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("--url", default="http://localhost:8000/mcp", help="Server URL")
|
||||
def main(url: str) -> int:
|
||||
asyncio.run(run(url))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[project]
|
||||
name = "mcp-simple-task-interactive-client"
|
||||
version = "0.1.0"
|
||||
description = "A simple MCP client demonstrating interactive task responses"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
|
||||
keywords = ["mcp", "llm", "tasks", "client", "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 = ["click>=8.0", "mcp"]
|
||||
|
||||
[project.scripts]
|
||||
mcp-simple-task-interactive-client = "mcp_simple_task_interactive_client.main:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["mcp_simple_task_interactive_client"]
|
||||
|
||||
[tool.pyright]
|
||||
include = ["mcp_simple_task_interactive_client"]
|
||||
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"]
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import sys
|
||||
|
||||
from .server import main
|
||||
|
||||
sys.exit(main()) # type: ignore[call-arg]
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import sys
|
||||
|
||||
from .server import main
|
||||
|
||||
sys.exit(main()) # type: ignore[call-arg]
|
||||
|
|
@ -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
|
||||
|
|
@ -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"]
|
||||
Loading…
Reference in a new issue