
`ClientSession` never sends `notifications/cancelled` when `call_tool` is cancelled — server-side coroutines leak
快速结论:该问题发生在客户端通过 ClientSession 调用 call_tool 等请求被取消(超时或外部取消)时,SDK 未向服务端发送 `notifications/cancelled` 通知,导致服务端协程泄漏资源。优先排查是否使用了 MCP Python SDK 1.29.0 版本,并检查服务端是否有长时间运行的协程任务。
问题场景
用户在使用 MCP Python SDK 的 `ClientSession` 调用工具时,当请求因为超时(SDK 内部超时或 `asyncio.wait_for` 外部超时)被取消,服务端对应的协程仍然处于挂起状态,无法释放已占用的资源(如数据库连接、锁、文件句柄)。长时间会话下,每次取消调用都会造成静默资源泄漏。
报错原文
# 问题行为表现(非具体报错信息):
# 服务端工具协程在客户端取消后未收到通知,资源泄漏
# 客户端超时时仅抛出:
McpError(error=ErrorData(code=408, message="Timed out while waiting for response to CallToolRequest. Waited X seconds."))
# 或外部取消时:
asyncio.CancelledError
原因分析
可能原因:
BaseSession.send_request() 方法中存在两条未覆盖的取消路径:
- 路径 A(SDK 内部超时):当
anyio.fail_after触发TimeoutError后,代码直接抛出McpError,未发送notifications/cancelled给服务端。 - 路径 B(外部取消):当调用方使用
asyncio.wait_for或任务组取消时,asyncio.CancelledError在await response_stream_reader.receive()处抛出,但没有对应的异常处理分支,异常直接通过finally块传播,但该块仅清理客户端本地的响应流,未通知服务端。
两种情况下,服务端都持续认为请求仍在处理中,相应协程继续运行但资源无法释放。
环境排查
- MCP Python SDK 版本:1.29.0(已验证包含此问题)
- Python 版本:不特定(但需确认是否使用 asyncio 环境)
- 服务端实现:使用 FastMCP 或自定义工具,且工具中包含长时间运行或阻塞操作(如 `asyncio.sleep(300)`)
- 客户端取消方式:
session.call_tool()配合asyncio.wait_for或 SDK 默认超时
解决步骤
- 确认 Issue 中提出的修复方案:在
src/mcp/shared/session.py的BaseSession.send_request()中:- 新增
_send_cancelled_notification辅助方法,用于尽力发送取消通知(捕获所有异常避免掩盖原始错误)。 - 在
TimeoutError处理分支中,在抛出MCPError之前调用_send_cancelled_notification(request_id, "request timed out")。 - 增加
except anyio.get_cancelled_exc_class()异常处理分支,使用anyio.CancelScope(shield=True)确保取消过程中也能发送通知,然后重新抛出异常。
- 新增
- 可优先尝试:检查是否已应用相关修复(Issue 标记为已于 2025-06-15 在 GitHub 上关闭,可能已有 PR 合并)。
- 如果无法立即更新 SDK,可采用临时规避:在客户端取消操作后手动调用服务端清理逻辑,或添加服务端超时机制。
验证方法
确认问题已解决:
- 使用最小复现脚本(如 Issue 中提供的
probe_a1_leak.py)测试,在取消 50 次请求后,服务端task_count返回的活跃任务数应为 0(表示所有协程已正确清理)。 - 检查服务端日志,确认收到了
notifications/cancelled通知。



