Add SSE polling support (SEP-1699) (#1654)

This commit is contained in:
Felix Weinberger 2025-12-02 11:44:49 +00:00 • committed by GitHub
commit 281fd4765e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1461 additions and 45 deletions

View file

@ -0,0 +1,30 @@
# MCP SSE Polling Demo Client
Demonstrates client-side auto-reconnect for the SSE polling pattern (SEP-1699).
## Features
- Connects to SSE polling demo server
- Automatically reconnects when server closes SSE stream
- Resumes from Last-Event-ID to avoid missing messages
- Respects server-provided retry interval
## Usage
```bash
# First start the server:
uv run mcp-sse-polling-demo --port 3000
# Then run this client:
uv run mcp-sse-polling-client --url http://localhost:3000/mcp
# Custom options:
uv run mcp-sse-polling-client --url http://localhost:3000/mcp --items 20 --checkpoint-every 5
```
## Options
- `--url`: Server URL (default: <http://localhost:3000/mcp>)
- `--items`: Number of items to process (default: 10)
- `--checkpoint-every`: Checkpoint interval (default: 3)
- `--log-level`: Logging level (default: DEBUG)

View file

@ -0,0 +1 @@
"""SSE Polling Demo Client - demonstrates auto-reconnect for long-running tasks."""

View file

@ -0,0 +1,105 @@
"""
SSE Polling Demo Client
Demonstrates the client-side auto-reconnect for SSE polling pattern.
This client connects to the SSE Polling Demo server and calls process_batch,
which triggers periodic server-side stream closes. The client automatically
reconnects using Last-Event-ID and resumes receiving messages.
Run with:
# First start the server:
uv run mcp-sse-polling-demo --port 3000
# Then run this client:
uv run mcp-sse-polling-client --url http://localhost:3000/mcp
"""
import asyncio
import logging
import click
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
logger = logging.getLogger(__name__)
async def run_demo(url: str, items: int, checkpoint_every: int) -> None:
"""Run the SSE polling demo."""
print(f"\n{'=' * 60}")
print("SSE Polling Demo Client")
print(f"{'=' * 60}")
print(f"Server URL: {url}")
print(f"Processing {items} items with checkpoints every {checkpoint_every}")
print(f"{'=' * 60}\n")
async with streamablehttp_client(url) as (read_stream, write_stream, _):
async with ClientSession(read_stream, write_stream) as session:
# Initialize the connection
print("Initializing connection...")
await session.initialize()
print("Connected!\n")
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[t.name for t in tools.tools]}\n")
# Call the process_batch tool
print(f"Calling process_batch(items={items}, checkpoint_every={checkpoint_every})...\n")
print("-" * 40)
result = await session.call_tool(
"process_batch",
{
"items": items,
"checkpoint_every": checkpoint_every,
},
)
print("-" * 40)
if result.content:
content = result.content[0]
text = getattr(content, "text", str(content))
print(f"\nResult: {text}")
else:
print("\nResult: No content")
print(f"{'=' * 60}\n")
@click.command()
@click.option(
"--url",
default="http://localhost:3000/mcp",
help="Server URL",
)
@click.option(
"--items",
default=10,
help="Number of items to process",
)
@click.option(
"--checkpoint-every",
default=3,
help="Checkpoint interval",
)
@click.option(
"--log-level",
default="INFO",
help="Logging level",
)
def main(url: str, items: int, checkpoint_every: int, log_level: str) -> None:
"""Run the SSE Polling Demo client."""
logging.basicConfig(
level=getattr(logging, log_level.upper()),
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
# Suppress noisy HTTP client logging
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
asyncio.run(run_demo(url, items, checkpoint_every))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,36 @@
[project]
name = "mcp-sse-polling-client"
version = "0.1.0"
description = "Demo client for SSE polling with auto-reconnect"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Anthropic, PBC." }]
keywords = ["mcp", "sse", "polling", "client"]
license = { text = "MIT" }
dependencies = ["click>=8.2.0", "mcp"]
[project.scripts]
mcp-sse-polling-client = "mcp_sse_polling_client.main:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["mcp_sse_polling_client"]
[tool.pyright]
include = ["mcp_sse_polling_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", "pytest>=8.3.3", "ruff>=0.6.9"]