Files
modelcontextprotocol--pytho…/tests/interaction/mcpserver/test_subscriptions.py
T
wehub-resource-sync 49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:10:27 +08:00

63 lines
2.6 KiB
Python

"""Client.listen against MCPServer over the connect matrix (2026-07-28)."""
import anyio
import pytest
from mcp.client.subscriptions import ListenNotSupportedError, ResourceUpdated, ToolsListChanged
from mcp.server.mcpserver import Context, MCPServer
from tests.interaction._connect import Connect
from tests.interaction._requirements import requirement
pytestmark = pytest.mark.anyio
def _notebook() -> MCPServer:
mcp = MCPServer("notebook")
@mcp.tool()
async def touch_tools(ctx: Context) -> str:
await ctx.notify_tools_changed()
return "ok"
@mcp.tool()
async def edit_note(name: str, ctx: Context) -> str:
await ctx.notify_resource_updated(f"note://{name}")
return "saved"
return mcp
@requirement("subscriptions:listen:client:honored-surfacing")
@requirement("subscriptions:listen:client:iteration")
async def test_listen_surfaces_the_ack_and_iterates_typed_events(connect: Connect) -> None:
"""Entering waits for the ack (honored is set before any event); iteration yields
only the typed event kinds this stream opted in to."""
mcp = _notebook()
async with connect(mcp) as client:
with anyio.fail_after(10):
async with client.listen( # pragma: no branch
tools_list_changed=True, resource_subscriptions=["note://todo"]
) as sub:
assert sub.honored.tools_list_changed is True
assert sub.honored.resource_subscriptions == ["note://todo"]
await client.call_tool("edit_note", {"name": "journal"}) # unsubscribed URI: silent
await client.call_tool("edit_note", {"name": "todo"})
assert await anext(sub) == ResourceUpdated(uri="note://todo")
await client.call_tool("touch_tools", {})
assert await anext(sub) == ToolsListChanged()
@requirement("subscriptions:listen:client:era-guard")
async def test_listen_on_a_pre_2026_connection_raises_the_typed_steer(connect: Connect) -> None:
"""On 2025-era connections the guard fires before anything touches the wire, steering to the legacy verbs."""
mcp = _notebook()
async with connect(mcp) as client:
with anyio.fail_after(10):
# Entering is where the guard fires; __aenter__ directly avoids an unreachable with-body.
with pytest.raises(ListenNotSupportedError) as exc_info:
await client.listen(tools_list_changed=True).__aenter__()
assert exc_info.value.negotiated_version == client.session.protocol_version
assert "subscribe_resource" in str(exc_info.value)