Initial Checks
Description
Dear developpers,
I am developing with MCP SDK Python on Windows. I noticed that the resource cleanup process of my local process defined inside the lifespan is not executed at all. To investigate the issue, I tested with the minimal code below and found that the code after the yield statement is never executed, regardless of whether an error occurs or not.
I haven’t been able to test this on platforms other than Windows or with the SSE transport.
"""agent_runner.py
A minimum code to use MCP server.
Please run this file.
"""
import os
import sys
import asyncio
import logging
from dotenv import load_dotenv
from openai import AsyncOpenAI
from agents import Agent, Runner, OpenAIChatCompletionsModel
from agents.mcp import MCPServerStdio, MCPServerStdioParams
load_dotenv() # loading OPENAI_API_KEY
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("agent_runner")
# disable library loggers
logging.getLogger('mcp').setLevel(logging.ERROR)
logging.getLogger('httpx').setLevel(logging.ERROR)
async def main():
# Launch MCP server
params = MCPServerStdioParams(
command=sys.executable,
args=[os.path.join(os.path.dirname(__file__), "srv_stdio.py")],
env={},
encoding="utf-8"
)
async with MCPServerStdio(params=params, name="MCP tool") as mcp_server:
logger.info("4. MCP stdio server running")
# Create agent
agent = Agent(
name="AgentWithMCP",
instructions="Answer with using tools.",
model=OpenAIChatCompletionsModel(model="gpt-4", openai_client=AsyncOpenAI()),
mcp_servers=[mcp_server]
)
prompt = "Please reverse the word 'hello'."
logger.info(f" PROMPT: {prompt}")
result = await Runner.run(agent, input=prompt)
logger.info(f" FINAL OUTPUT: {result.final_output}")
if __name__ == "__main__":
logger.info("1. Starting program...")
asyncio.run(main())
logger.info("8. Program terminated.")
"""srv_stdio.py
A minimum code to show ignoring after "yield" in lifetime.
Please put this file on the same folder as "agent_runner.py".
"""
from __future__ import annotations as _annotations
import os
import sys
import logging
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from mcp.server.fastmcp import FastMCP
from mcp.server.lowlevel.server import Server, LifespanResultT, RequestT
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("stdio server")
# disable library loggers
logging.getLogger('mcp').setLevel(logging.ERROR)
logging.getLogger('httpx').setLevel(logging.ERROR)
if sys.platform == "win32" and os.environ.get('PYTHONIOENCODING') is None:
sys.stdin.reconfigure(encoding="utf-8")
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
@asynccontextmanager
async def lifespan(server: Server[LifespanResultT, RequestT]) -> AsyncIterator[object]:
logger.info('3. Launching Server...')
try:
yield {}
finally:
logger.info('6. Terminating Server '
'(Want to release my resources here).') # not shown
mcp = FastMCP(
"EchoMCPServer",
lifespan=lifespan,
)
@mcp.tool()
def echo(msg: str) -> str:
"""Make the passed string reversed."""
logger.info(f"5. [echo] called with msg='{msg}'")
return msg[::-1]
if __name__ == "__main__":
logger.info("2. Starting MCP server...")
mcp.run()
logger.info("7. MCP server terminated.") # not shown
The outputs missing "6. Terminating Server ~" and "7. MCP server terminated.".
INFO:agent_runner:1. Starting program...
INFO:stdio server:2. Starting MCP server...
INFO:stdio server:3. Launching Server...
INFO:agent_runner:4. MCP stdio server running
INFO:agent_runner: PROMPT: Please reverse the word 'hello'.
INFO:stdio server:5. [echo] called with msg='olleh'
INFO:agent_runner: FINAL OUTPUT: The reverse of 'hello' is 'olleh'.
INFO:agent_runner:8. Program terminated.
I suspected that this issue might be due to the process being forcibly terminated when exiting the with block in MCPServerStdio. Upon investigation, I found that modifying the following section allows the sample code to correctly display "6. Terminating Server ~" and "7. MCP server terminated.", and the cleanup process is executed as intended.
# mcp/client/stdio/win32.py
102 - process.terminate()
102 + os.kill(process.pid, signal.CTRL_C_EVENT)
This change seems to resolve the issue on my end, but do you think it could be helpful for improving the MCP SDK overall?
Example Code
Python & MCP Python SDK
Python 3.10
mcp 1.9.4
openai-agents 0.0.17
Initial Checks
Description
Dear developpers,
I am developing with MCP SDK Python on Windows. I noticed that the resource cleanup process of my local process defined inside the
lifespanis not executed at all. To investigate the issue, I tested with the minimal code below and found that the code after theyieldstatement is never executed, regardless of whether an error occurs or not.I haven’t been able to test this on platforms other than Windows or with the SSE transport.
The outputs missing "6. Terminating Server ~" and "7. MCP server terminated.".
I suspected that this issue might be due to the process being forcibly terminated when exiting the
withblock inMCPServerStdio. Upon investigation, I found that modifying the following section allows the sample code to correctly display "6. Terminating Server ~" and "7. MCP server terminated.", and the cleanup process is executed as intended.This change seems to resolve the issue on my end, but do you think it could be helpful for improving the MCP SDK overall?
Example Code
Python & MCP Python SDK