Skip to content

Commit 3b8f612

Browse files
Update test to show cleanup issue exists on all platforms
The test now demonstrates that: 1. The cleanup issue affects all platforms, not just Windows 2. Closing stdin and waiting for graceful exit allows cleanup to run This confirms that PR #1044's approach of implementing the MCP spec's stdio shutdown sequence (close stdin first, then wait, then terminate) could address issue #1027. Github-Issue:#1027
1 parent 9f50d20 commit 3b8f612

File tree

1 file changed

+29
-52
lines changed

1 file changed

+29
-52
lines changed

tests/issues/test_1027_win_unreachable_cleanup.py

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ async def test_lifespan_cleanup_executed():
4343
Path(startup_marker).unlink()
4444
Path(cleanup_marker).unlink()
4545

46-
# Create a minimal MCP server that tracks lifecycle
46+
# Create a minimal MCP server using FastMCP that tracks lifecycle
4747
server_code = textwrap.dedent(f"""
4848
import asyncio
4949
import sys
5050
from pathlib import Path
5151
from contextlib import asynccontextmanager
52-
from mcp.server.lowlevel import Server
53-
from mcp.server.stdio import stdio_server
52+
from mcp.server.fastmcp import FastMCP
5453
5554
STARTUP_MARKER = {repr(startup_marker)}
5655
CLEANUP_MARKER = {repr(cleanup_marker)}
@@ -60,31 +59,19 @@ async def lifespan(server):
6059
# Write startup marker
6160
Path(STARTUP_MARKER).write_text("started")
6261
try:
63-
yield
62+
yield {{"started": True}}
6463
finally:
6564
# This cleanup code should run when server shuts down
6665
Path(CLEANUP_MARKER).write_text("cleaned up")
6766
68-
async def main():
69-
async with stdio_server() as (read, write):
70-
server = Server("test-server", version="1.0.0")
71-
server.set_lifespan(lifespan)
72-
73-
async def handle_initialize(params):
74-
return {{"protocolVersion": "1.0.0", "capabilities": {{}}}}
75-
76-
server.set_request_handler("initialize", handle_initialize)
77-
78-
async def handle_echo(params):
79-
return {{"text": params.get("text", "")}}
80-
81-
server.set_request_handler("test/echo", handle_echo)
82-
83-
await server.run(read, write)
67+
mcp = FastMCP("test-server", lifespan=lifespan)
68+
69+
@mcp.tool()
70+
def echo(text: str) -> str:
71+
return text
8472
8573
if __name__ == "__main__":
86-
import asyncio
87-
asyncio.run(main())
74+
mcp.run()
8875
""")
8976

9077
# Write the server script to a temporary file
@@ -103,33 +90,30 @@ async def handle_echo(params):
10390
async with ClientSession(read, write) as session:
10491
# Initialize the session
10592
result = await session.initialize()
106-
assert result.protocolVersion == "1.0.0"
93+
assert result.protocolVersion in ["2024-11-05", "2025-06-18"]
10794

10895
# Verify startup marker was created
10996
assert Path(startup_marker).exists(), "Server startup marker not created"
11097
assert Path(startup_marker).read_text() == "started"
11198

11299
# Make a test request to ensure server is working
113-
response = await session.send_request("test/echo", {"text": "hello"})
114-
assert response.text == "hello"
100+
response = await session.call_tool("echo", {"text": "hello"})
101+
assert response.content[0].text == "hello"
115102

116103
# Session will be closed when exiting the context manager
117104

118105
# Give server a moment to run cleanup (if it can)
119106
await asyncio.sleep(0.5)
120107

121108
# Check if cleanup marker was created
122-
if sys.platform == "win32":
123-
# On Windows, this assertion currently fails because cleanup doesn't run
124-
# When issue #1027 is fixed, this should pass
109+
# This currently fails on all platforms because process.terminate()
110+
# doesn't allow cleanup code to run
111+
if not Path(cleanup_marker).exists():
125112
pytest.xfail(
126-
"Cleanup code after yield is not executed on Windows (issue #1027)"
127-
if not Path(cleanup_marker).exists()
128-
else "Cleanup unexpectedly succeeded - issue may be fixed!"
113+
"Cleanup code after yield is not executed when process is terminated (issue #1027)"
129114
)
130115
else:
131-
# On Unix systems, cleanup should work
132-
assert Path(cleanup_marker).exists(), "Server cleanup marker not created"
116+
# If cleanup succeeded, the issue may be fixed
133117
assert Path(cleanup_marker).read_text() == "cleaned up"
134118

135119
finally:
@@ -166,8 +150,7 @@ async def test_stdin_close_triggers_cleanup():
166150
import sys
167151
from pathlib import Path
168152
from contextlib import asynccontextmanager
169-
from mcp.server.lowlevel import Server
170-
from mcp.server.stdio import stdio_server
153+
from mcp.server.fastmcp import FastMCP
171154
172155
STARTUP_MARKER = {repr(startup_marker)}
173156
CLEANUP_MARKER = {repr(cleanup_marker)}
@@ -177,30 +160,24 @@ async def lifespan(server):
177160
# Write startup marker
178161
Path(STARTUP_MARKER).write_text("started")
179162
try:
180-
yield
163+
yield {{"started": True}}
181164
finally:
182165
# This cleanup code should run when stdin closes
183166
Path(CLEANUP_MARKER).write_text("cleaned up")
184167
185-
async def main():
168+
mcp = FastMCP("test-server", lifespan=lifespan)
169+
170+
@mcp.tool()
171+
def echo(text: str) -> str:
172+
return text
173+
174+
if __name__ == "__main__":
175+
# The server should exit gracefully when stdin closes
186176
try:
187-
async with stdio_server() as (read, write):
188-
server = Server("test-server", version="1.0.0")
189-
server.set_lifespan(lifespan)
190-
191-
async def handle_initialize(params):
192-
return {{"protocolVersion": "1.0.0", "capabilities": {{}}}}
193-
194-
server.set_request_handler("initialize", handle_initialize)
195-
196-
# The server should exit gracefully when stdin closes
197-
await server.run(read, write)
177+
mcp.run()
198178
except Exception:
199179
# Server might get EOF or other errors when stdin closes
200180
pass
201-
202-
if __name__ == "__main__":
203-
asyncio.run(main())
204181
""")
205182

206183
# Write the server script to a temporary file
@@ -218,7 +195,7 @@ async def handle_initialize(params):
218195
command=sys.executable,
219196
args=[server_script],
220197
env=None,
221-
stderr=None,
198+
errlog=sys.stderr,
222199
cwd=None
223200
)
224201

0 commit comments

Comments
 (0)