chore: import upstream snapshot with attribution
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) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:27 +08:00
commit 49b9bb6724
992 changed files with 161690 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
"""Tests for lifespan functionality in both low-level and MCPServer servers."""
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
import anyio
import pytest
from mcp_types import (
CallToolRequestParams,
CallToolResult,
ClientCapabilities,
Implementation,
InitializeRequestParams,
JSONRPCMessage,
JSONRPCNotification,
JSONRPCRequest,
JSONRPCResponse,
TextContent,
)
from pydantic import TypeAdapter
from mcp.server import ServerRequestContext
from mcp.server.lowlevel.server import NotificationOptions, Server
from mcp.server.mcpserver import Context, MCPServer
from mcp.server.models import InitializationOptions
from mcp.shared.message import SessionMessage
@pytest.mark.anyio
async def test_lowlevel_server_lifespan():
"""Test that lifespan works in low-level server."""
@asynccontextmanager
async def test_lifespan(server: Server) -> AsyncIterator[dict[str, bool]]:
"""Test lifespan context that tracks startup/shutdown."""
context = {"started": False, "shutdown": False}
try:
context["started"] = True
yield context
finally:
context["shutdown"] = True
# Create a tool that accesses lifespan context
async def check_lifespan(
ctx: ServerRequestContext[dict[str, bool]], params: CallToolRequestParams
) -> CallToolResult:
assert isinstance(ctx.lifespan_context, dict)
assert ctx.lifespan_context["started"]
assert not ctx.lifespan_context["shutdown"]
return CallToolResult(content=[TextContent(type="text", text="true")])
server = Server[dict[str, bool]]("test", lifespan=test_lifespan, on_call_tool=check_lifespan)
# Create memory streams for testing
send_stream1, receive_stream1 = anyio.create_memory_object_stream[SessionMessage](100)
send_stream2, receive_stream2 = anyio.create_memory_object_stream[SessionMessage](100)
# Run server in background task
async with anyio.create_task_group() as tg, send_stream1, receive_stream1, send_stream2, receive_stream2:
async def run_server():
await server.run(
receive_stream1,
send_stream2,
InitializationOptions(
server_name="test",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
raise_exceptions=True,
)
tg.start_soon(run_server)
# Initialize the server
params = InitializeRequestParams(
protocol_version="2024-11-05",
capabilities=ClientCapabilities(),
client_info=Implementation(name="test-client", version="0.1.0"),
)
await send_stream1.send(
SessionMessage(
JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
)
)
response = await receive_stream2.receive()
response = response.message
# Send initialized notification
await send_stream1.send(SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")))
# Call the tool to verify lifespan context
await send_stream1.send(
SessionMessage(
JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
)
)
# Get response and verify
response = await receive_stream2.receive()
response = response.message
assert isinstance(response, JSONRPCMessage)
assert isinstance(response, JSONRPCResponse)
assert response.result["content"][0]["text"] == "true"
# Cancel server task
tg.cancel_scope.cancel()
@pytest.mark.anyio
async def test_mcpserver_server_lifespan():
"""Test that lifespan works in MCPServer server."""
@asynccontextmanager
async def test_lifespan(server: MCPServer) -> AsyncIterator[dict[str, bool]]:
"""Test lifespan context that tracks startup/shutdown."""
context = {"started": False, "shutdown": False}
try:
context["started"] = True
yield context
finally:
context["shutdown"] = True
server = MCPServer("test", lifespan=test_lifespan)
# Create memory streams for testing
send_stream1, receive_stream1 = anyio.create_memory_object_stream[SessionMessage](100)
send_stream2, receive_stream2 = anyio.create_memory_object_stream[SessionMessage](100)
# Add a tool that checks lifespan context
@server.tool()
def check_lifespan(ctx: Context) -> bool:
"""Tool that checks lifespan context."""
assert isinstance(ctx.request_context.lifespan_context, dict)
assert ctx.request_context.lifespan_context["started"]
assert not ctx.request_context.lifespan_context["shutdown"]
return True
# Run server in background task
async with anyio.create_task_group() as tg, send_stream1, receive_stream1, send_stream2, receive_stream2:
async def run_server():
await server._lowlevel_server.run(
receive_stream1,
send_stream2,
server._lowlevel_server.create_initialization_options(),
raise_exceptions=True,
)
tg.start_soon(run_server)
# Initialize the server
params = InitializeRequestParams(
protocol_version="2024-11-05",
capabilities=ClientCapabilities(),
client_info=Implementation(name="test-client", version="0.1.0"),
)
await send_stream1.send(
SessionMessage(
JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
)
)
response = await receive_stream2.receive()
response = response.message
# Send initialized notification
await send_stream1.send(SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")))
# Call the tool to verify lifespan context
await send_stream1.send(
SessionMessage(
JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
)
)
# Get response and verify
response = await receive_stream2.receive()
response = response.message
assert isinstance(response, JSONRPCMessage)
assert isinstance(response, JSONRPCResponse)
assert response.result["content"][0]["text"] == "true"
# Cancel server task
tg.cancel_scope.cancel()