Skip to content

Commit c1c851f

Browse files
committed
test: rebaseline legacy unit tests for the dispatcher swap; rewrite __main__
server/__main__.py: use Server.run() instead of manual ServerSession + incoming_messages. 49 -> 24 lines. tests/server/test_runner.py: connected_runner now drives a JSONRPCDispatcher pair (was DirectDispatcher); ctx is ServerRequestContext; dropped 6 tests of dormant Context features that return in the Context rework. tests/server/test_connection.py: set client_params instead of the now read-only client_info/client_capabilities properties. tests/server/test_stateless_mode.py: ServerSession(dispatcher, connection) fixtures. tests/conftest.py: capfire override resets _otel._tracer to NoOpTracer on teardown so tests after a span-capture test don't see traceparent injected into _meta (pre-existing order-dep, surfaced once both code paths inject). Deleted (covered by tests/interaction/ or test_runner.py): test_session.py, test_session_race_condition.py, test_progress_notifications.py, test_malformed_input.py. test_lowlevel_exception_handling.py: dropped 3 tests of the deleted _handle_message; kept the real-stream Server.run() regression test.
1 parent 8cca623 commit c1c851f

11 files changed

Lines changed: 147 additions & 1377 deletions

‎src/mcp/server/__main__.py‎

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import importlib.metadata
21
import logging
32
import sys
43
import warnings
54

65
import anyio
76

8-
from mcp.server.models import InitializationOptions
9-
from mcp.server.session import ServerSession
7+
from mcp.server.lowlevel.server import Server
108
from mcp.server.stdio import stdio_server
11-
from mcp.types import ServerCapabilities
129

1310
if not sys.warnoptions:
1411
warnings.simplefilter("ignore")
@@ -17,32 +14,10 @@
1714
logger = logging.getLogger("server")
1815

1916

20-
async def receive_loop(session: ServerSession):
21-
logger.info("Starting receive loop")
22-
async for message in session.incoming_messages:
23-
if isinstance(message, Exception):
24-
logger.error("Error: %s", message)
25-
continue
26-
27-
logger.info("Received message from client: %s", message)
28-
29-
30-
async def main():
31-
version = importlib.metadata.version("mcp")
17+
async def main() -> None:
18+
server: Server[dict[str, object]] = Server("mcp")
3219
async with stdio_server() as (read_stream, write_stream):
33-
async with (
34-
ServerSession(
35-
read_stream,
36-
write_stream,
37-
InitializationOptions(
38-
server_name="mcp",
39-
server_version=version,
40-
capabilities=ServerCapabilities(),
41-
),
42-
) as session,
43-
write_stream,
44-
):
45-
await receive_loop(session)
20+
await server.run(read_stream, write_stream, server.create_initialization_options())
4621

4722

4823
if __name__ == "__main__":

‎tests/conftest.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from collections.abc import Iterator
23

34
import pytest
45

@@ -11,7 +12,33 @@
1112
# suite-wide. Set before logfire is imported anywhere.
1213
os.environ.setdefault("LOGFIRE_DISTRIBUTED_TRACING", "true")
1314

15+
import opentelemetry.trace # noqa: E402 (env var must be set before logfire import below)
16+
from logfire.testing import CaptureLogfire # noqa: E402
17+
18+
import mcp.shared._otel # noqa: E402
19+
1420

1521
@pytest.fixture
1622
def anyio_backend():
1723
return "asyncio"
24+
25+
26+
@pytest.fixture(name="capfire")
27+
def _capfire_isolated(capfire: CaptureLogfire) -> Iterator[CaptureLogfire]:
28+
"""Override of logfire's `capfire` that scopes the MCP tracer to the test.
29+
30+
`capfire` installs a real tracer provider, and logfire's proxy machinery
31+
mutates the cached `mcp.shared._otel._tracer` to delegate to it for the
32+
rest of the process. Without isolation, every subsequent test in the same
33+
worker would emit real spans, and `send_raw_request` would inject a real
34+
`traceparent` into outbound `_meta`, breaking the interaction-suite
35+
snapshots that pin `_meta={}` under a no-op tracer.
36+
37+
Setup points `_tracer` at the now-live provider so MCP spans record;
38+
teardown replaces it with a `NoOpTracer`.
39+
"""
40+
mcp.shared._otel._tracer = opentelemetry.trace.get_tracer_provider().get_tracer("mcp-python-sdk")
41+
try:
42+
yield capfire
43+
finally:
44+
mcp.shared._otel._tracer = opentelemetry.trace.NoOpTracer()

‎tests/issues/test_malformed_input.py‎

Lines changed: 0 additions & 151 deletions
This file was deleted.

‎tests/server/conftest.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,13 @@ def finished(self) -> list[ReadableSpan]:
3636
def spans(capfire: CaptureLogfire) -> Iterator[SpanCapture]:
3737
"""In-memory MCP span capture, cleared before and after each test.
3838
39-
Backed by the project-level `capfire` override (see `tests/conftest.py`)
40-
so there is a single global tracer provider for the suite.
39+
Backed by the project-level `capfire` override (see `tests/conftest.py`),
40+
which scopes `mcp.shared._otel._tracer` to the test so the real tracer
41+
doesn't leak into later tests in the same worker.
4142
"""
4243
capture = SpanCapture(capfire.exporter)
4344
capture.clear()
4445
yield capture
4546
capture.clear()
47+
48+

‎tests/server/test_connection.py‎

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
from mcp.shared.dispatcher import CallOptions
1818
from mcp.shared.exceptions import NoBackChannelError
1919
from mcp.types import (
20+
LATEST_PROTOCOL_VERSION,
2021
ClientCapabilities,
2122
ElicitationCapability,
2223
EmptyResult,
24+
Implementation,
25+
InitializeRequestParams,
2326
ListRootsRequest,
2427
ListRootsResult,
2528
PingRequest,
@@ -28,6 +31,14 @@
2831
)
2932

3033

34+
def _client_params(capabilities: ClientCapabilities) -> InitializeRequestParams:
35+
return InitializeRequestParams(
36+
protocol_version=LATEST_PROTOCOL_VERSION,
37+
capabilities=capabilities,
38+
client_info=Implementation(name="t", version="0"),
39+
)
40+
41+
3142
class StubOutbound:
3243
def __init__(
3344
self, *, result: dict[str, Any] | None = None, raise_on_send: type[BaseException] | None = None
@@ -190,14 +201,24 @@ def test_connection_check_capability_false_before_initialized():
190201
)
191202
def test_check_capability_per_field_branches(have: ClientCapabilities, want: ClientCapabilities, expected: bool):
192203
conn = Connection(StubOutbound(), has_standalone_channel=True)
193-
conn.client_capabilities = have
204+
conn.client_params = _client_params(have)
194205
assert conn.check_capability(want) is expected
195206

196207

208+
def test_connection_client_info_and_capabilities_derive_from_client_params():
209+
conn = Connection(StubOutbound(), has_standalone_channel=True)
210+
assert conn.client_info is None
211+
assert conn.client_capabilities is None
212+
caps = ClientCapabilities(sampling=SamplingCapability())
213+
conn.client_params = _client_params(caps)
214+
assert conn.client_info is not None and conn.client_info.name == "t"
215+
assert conn.client_capabilities == caps
216+
217+
197218
def test_connection_check_capability_true_when_client_declares_it():
198219
conn = Connection(StubOutbound(), has_standalone_channel=True)
199-
conn.client_capabilities = ClientCapabilities(
200-
sampling=SamplingCapability(), roots=RootsCapability(list_changed=True)
220+
conn.client_params = _client_params(
221+
ClientCapabilities(sampling=SamplingCapability(), roots=RootsCapability(list_changed=True))
201222
)
202223
conn.initialized.set()
203224
assert conn.check_capability(ClientCapabilities(sampling=SamplingCapability())) is True

0 commit comments

Comments
 (0)