refactor: remove unused mcp.shared.progress module (#2080)
This commit is contained in:
parent
fc57c2c4c5
commit
e82203bfc4
3 changed files with 32 additions and 171 deletions
|
|
@ -371,7 +371,7 @@ async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestPar
|
|||
server = Server("my-server", on_call_tool=handle_call_tool)
|
||||
```
|
||||
|
||||
### `RequestContext` and `ProgressContext` type parameters simplified
|
||||
### `RequestContext` type parameters simplified
|
||||
|
||||
The `RequestContext` class has been split to separate shared fields from server-specific fields. The shared `RequestContext` now only takes 1 type parameter (the session type) instead of 3.
|
||||
|
||||
|
|
@ -380,40 +380,59 @@ The `RequestContext` class has been split to separate shared fields from server-
|
|||
- Type parameters reduced from `RequestContext[SessionT, LifespanContextT, RequestT]` to `RequestContext[SessionT]`
|
||||
- Server-specific fields (`lifespan_context`, `experimental`, `request`, `close_sse_stream`, `close_standalone_sse_stream`) moved to new `ServerRequestContext` class in `mcp.server.context`
|
||||
|
||||
**`ProgressContext` changes:**
|
||||
|
||||
- Type parameters reduced from `ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT]` to `ProgressContext[SessionT]`
|
||||
|
||||
**Before (v1):**
|
||||
|
||||
```python
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.shared.context import RequestContext, LifespanContextT, RequestT
|
||||
from mcp.shared.progress import ProgressContext
|
||||
|
||||
# RequestContext with 3 type parameters
|
||||
ctx: RequestContext[ClientSession, LifespanContextT, RequestT]
|
||||
|
||||
# ProgressContext with 5 type parameters
|
||||
progress_ctx: ProgressContext[SendRequestT, SendNotificationT, SendResultT, ReceiveRequestT, ReceiveNotificationT]
|
||||
```
|
||||
|
||||
**After (v2):**
|
||||
|
||||
```python
|
||||
from mcp.client.context import ClientRequestContext
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.server.context import ServerRequestContext, LifespanContextT, RequestT
|
||||
from mcp.shared.progress import ProgressContext
|
||||
|
||||
# For client-side context (sampling, elicitation, list_roots callbacks)
|
||||
ctx: ClientRequestContext
|
||||
|
||||
# For server-specific context with lifespan and request types
|
||||
server_ctx: ServerRequestContext[LifespanContextT, RequestT]
|
||||
```
|
||||
|
||||
# ProgressContext with 1 type parameter
|
||||
progress_ctx: ProgressContext[ClientSession]
|
||||
### `ProgressContext` and `progress()` context manager removed
|
||||
|
||||
The `mcp.shared.progress` module (`ProgressContext`, `Progress`, and the `progress()` context manager) has been removed. This module had no real-world adoption — all users send progress notifications via `Context.report_progress()` or `session.send_progress_notification()` directly.
|
||||
|
||||
**Before:**
|
||||
|
||||
```python
|
||||
from mcp.shared.progress import progress
|
||||
|
||||
with progress(ctx, total=100) as p:
|
||||
await p.progress(25)
|
||||
```
|
||||
|
||||
**After — use `Context.report_progress()` (recommended):**
|
||||
|
||||
```python
|
||||
@server.tool()
|
||||
async def my_tool(x: int, ctx: Context) -> str:
|
||||
await ctx.report_progress(25, 100)
|
||||
return "done"
|
||||
```
|
||||
|
||||
**After — use `session.send_progress_notification()` (low-level):**
|
||||
|
||||
```python
|
||||
await session.send_progress_notification(
|
||||
progress_token=progress_token,
|
||||
progress=25,
|
||||
total=100,
|
||||
)
|
||||
```
|
||||
|
||||
### Resource URI type changed from `AnyUrl` to `str`
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
from collections.abc import Generator
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Generic
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mcp.shared._context import RequestContext, SessionT
|
||||
from mcp.types import ProgressToken
|
||||
|
||||
|
||||
class Progress(BaseModel):
|
||||
progress: float
|
||||
total: float | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProgressContext(Generic[SessionT]):
|
||||
session: SessionT
|
||||
progress_token: ProgressToken
|
||||
total: float | None
|
||||
current: float = field(default=0.0, init=False)
|
||||
|
||||
async def progress(self, amount: float, message: str | None = None) -> None:
|
||||
self.current += amount
|
||||
|
||||
await self.session.send_progress_notification(
|
||||
self.progress_token, self.current, total=self.total, message=message
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def progress(
|
||||
ctx: RequestContext[SessionT],
|
||||
total: float | None = None,
|
||||
) -> Generator[ProgressContext[SessionT], None]:
|
||||
progress_token = ctx.meta.get("progress_token") if ctx.meta else None
|
||||
if progress_token is None: # pragma: no cover
|
||||
raise ValueError("No progress token provided")
|
||||
|
||||
progress_ctx = ProgressContext(ctx.session, progress_token, total)
|
||||
try:
|
||||
yield progress_ctx
|
||||
finally:
|
||||
pass
|
||||
|
|
@ -10,9 +10,7 @@ from mcp.server import Server, ServerRequestContext
|
|||
from mcp.server.lowlevel import NotificationOptions
|
||||
from mcp.server.models import InitializationOptions
|
||||
from mcp.server.session import ServerSession
|
||||
from mcp.shared._context import RequestContext
|
||||
from mcp.shared.message import SessionMessage
|
||||
from mcp.shared.progress import progress
|
||||
from mcp.shared.session import RequestResponder
|
||||
|
||||
|
||||
|
|
@ -198,117 +196,6 @@ async def test_bidirectional_progress_notifications():
|
|||
assert server_progress_updates[2]["progress"] == 1.0
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_progress_context_manager():
|
||||
"""Test client using progress context manager for sending progress notifications."""
|
||||
# Create memory streams for client/server
|
||||
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[SessionMessage](5)
|
||||
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[SessionMessage](5)
|
||||
|
||||
# Track progress updates
|
||||
server_progress_updates: list[dict[str, Any]] = []
|
||||
|
||||
progress_token = None
|
||||
|
||||
# Register progress handler
|
||||
async def handle_progress(ctx: ServerRequestContext, params: types.ProgressNotificationParams) -> None:
|
||||
server_progress_updates.append(
|
||||
{
|
||||
"token": params.progress_token,
|
||||
"progress": params.progress,
|
||||
"total": params.total,
|
||||
"message": params.message,
|
||||
}
|
||||
)
|
||||
|
||||
server = Server(name="ProgressContextTestServer", on_progress=handle_progress)
|
||||
|
||||
# Run server session to receive progress updates
|
||||
async def run_server():
|
||||
# Create a server session
|
||||
async with ServerSession(
|
||||
client_to_server_receive,
|
||||
server_to_client_send,
|
||||
InitializationOptions(
|
||||
server_name="ProgressContextTestServer",
|
||||
server_version="0.1.0",
|
||||
capabilities=server.get_capabilities(NotificationOptions(), {}),
|
||||
),
|
||||
) as server_session:
|
||||
async for message in server_session.incoming_messages:
|
||||
try:
|
||||
await server._handle_message(message, server_session, {})
|
||||
except Exception as e: # pragma: no cover
|
||||
raise e
|
||||
|
||||
# Client message handler
|
||||
async def handle_client_message(
|
||||
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
|
||||
) -> None:
|
||||
if isinstance(message, Exception): # pragma: no cover
|
||||
raise message
|
||||
|
||||
# run client session
|
||||
async with (
|
||||
ClientSession(
|
||||
server_to_client_receive,
|
||||
client_to_server_send,
|
||||
message_handler=handle_client_message,
|
||||
) as client_session,
|
||||
anyio.create_task_group() as tg,
|
||||
):
|
||||
tg.start_soon(run_server)
|
||||
|
||||
await client_session.initialize()
|
||||
|
||||
progress_token = "client_token_456"
|
||||
|
||||
# Create request context
|
||||
request_context = RequestContext(
|
||||
request_id="test-request",
|
||||
session=client_session,
|
||||
meta={"progress_token": progress_token},
|
||||
)
|
||||
|
||||
# Utilize progress context manager
|
||||
with progress(request_context, total=100) as p:
|
||||
await p.progress(10, message="Loading configuration...")
|
||||
await p.progress(30, message="Connecting to database...")
|
||||
await p.progress(40, message="Fetching data...")
|
||||
await p.progress(20, message="Processing results...")
|
||||
|
||||
# Wait for all messages to be processed
|
||||
await anyio.sleep(0.5)
|
||||
tg.cancel_scope.cancel()
|
||||
|
||||
# Verify progress updates were received by server
|
||||
assert len(server_progress_updates) == 4
|
||||
|
||||
# first update
|
||||
assert server_progress_updates[0]["token"] == progress_token
|
||||
assert server_progress_updates[0]["progress"] == 10
|
||||
assert server_progress_updates[0]["total"] == 100
|
||||
assert server_progress_updates[0]["message"] == "Loading configuration..."
|
||||
|
||||
# second update
|
||||
assert server_progress_updates[1]["token"] == progress_token
|
||||
assert server_progress_updates[1]["progress"] == 40
|
||||
assert server_progress_updates[1]["total"] == 100
|
||||
assert server_progress_updates[1]["message"] == "Connecting to database..."
|
||||
|
||||
# third update
|
||||
assert server_progress_updates[2]["token"] == progress_token
|
||||
assert server_progress_updates[2]["progress"] == 80
|
||||
assert server_progress_updates[2]["total"] == 100
|
||||
assert server_progress_updates[2]["message"] == "Fetching data..."
|
||||
|
||||
# final update
|
||||
assert server_progress_updates[3]["token"] == progress_token
|
||||
assert server_progress_updates[3]["progress"] == 100
|
||||
assert server_progress_updates[3]["total"] == 100
|
||||
assert server_progress_updates[3]["message"] == "Processing results..."
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_progress_callback_exception_logging():
|
||||
"""Test that exceptions in progress callbacks are logged and \
|
||||
|
|
|
|||
Loading…
Reference in a new issue