`ClientSession` never sends `notifications/cancelled` when `call_tool` is cancelled — server-side coroutines leak

用户在使用 MCP Python SDK 的 `ClientSession` 调用工具时,当请求因为超时(SDK 内部超时或 `asyncio.wait_for` 外部超时)被取消,服务端对应的协程仍然处于挂起状态,无法释放已占用的资源(如数据库连接、锁、文件句柄)。长时间会话下,每次取消调用都会造

`ClientSession` never sends `notifications/cancelled` when `call_tool` is cancelled — server-side coroutines leak

`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.CancelledErrorawait response_stream_reader.receive() 处抛出,但没有对应的异常处理分支,异常直接通过 finally 块传播,但该块仅清理客户端本地的响应流,未通知服务端。

两种情况下,服务端都持续认为请求仍在处理中,相应协程继续运行但资源无法释放。

环境排查

  • MCP Python SDK 版本:1.29.0(已验证包含此问题)
  • Python 版本:不特定(但需确认是否使用 asyncio 环境)
  • 服务端实现:使用 FastMCP 或自定义工具,且工具中包含长时间运行或阻塞操作(如 `asyncio.sleep(300)`)
  • 客户端取消方式:session.call_tool() 配合 asyncio.wait_for 或 SDK 默认超时

解决步骤

  1. 确认 Issue 中提出的修复方案:在 src/mcp/shared/session.pyBaseSession.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) 确保取消过程中也能发送通知,然后重新抛出异常。
  2. 可优先尝试:检查是否已应用相关修复(Issue 标记为已于 2025-06-15 在 GitHub 上关闭,可能已有 PR 合并)。
  3. 如果无法立即更新 SDK,可采用临时规避:在客户端取消操作后手动调用服务端清理逻辑,或添加服务端超时机制。

验证方法

确认问题已解决:

  • 使用最小复现脚本(如 Issue 中提供的 probe_a1_leak.py)测试,在取消 50 次请求后,服务端 task_count 返回的活跃任务数应为 0(表示所有协程已正确清理)。
  • 检查服务端日志,确认收到了 notifications/cancelled 通知。

参考来源

modelcontextprotocol/python-sdk #2507

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

celebrityanime
celebrityanime
文章: 8081

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注