fix(sdk): close MCP connections from Nanobot facade

The SDK opened MCP connections through AgentLoop.process_direct but
never called close_mcp, leaving stdio MCP generators to be finalized
during asyncio shutdown from a different task, producing a RuntimeError
about exiting a cancel scope in a different task.

Add aclose() that delegates to AgentLoop.close_mcp (which already
drains background tasks and closes MCP stacks), plus __aenter__ and
__aexit__ so the SDK works as an async context manager.

Fixes #4211
This commit is contained in:
axelray-dev
2026-06-06 15:35:28 +08:00
committed by Xubin Ren
parent 6a0368b32f
commit 57fa37dcfe
2 changed files with 46 additions and 0 deletions
+37
View File
@@ -326,3 +326,40 @@ async def test_sdk_capture_prefers_run_level_snapshot():
assert hook.tools_used == ["read_file"]
assert hook.messages == final_messages
@pytest.mark.asyncio
async def test_aclose_delegates_to_loop_close_mcp(tmp_path):
config_path = _write_config(tmp_path)
bot = Nanobot.from_config(config_path, workspace=tmp_path)
bot._loop.close_mcp = AsyncMock()
await bot.aclose()
bot._loop.close_mcp.assert_awaited_once()
@pytest.mark.asyncio
async def test_context_manager_calls_aclose_on_exit(tmp_path):
config_path = _write_config(tmp_path)
bot = Nanobot.from_config(config_path, workspace=tmp_path)
bot._loop.close_mcp = AsyncMock()
async with bot as b:
assert b is bot
bot._loop.close_mcp.assert_awaited_once()
@pytest.mark.asyncio
async def test_context_manager_does_not_swallow_exceptions(tmp_path):
config_path = _write_config(tmp_path)
bot = Nanobot.from_config(config_path, workspace=tmp_path)
bot._loop.close_mcp = AsyncMock()
with pytest.raises(ValueError):
async with bot as b:
assert b is bot
raise ValueError("boom")
bot._loop.close_mcp.assert_awaited_once()