Skip to content

fix(mcp): build the active server list without re-consuming the iterable - #4235

Merged
seratch merged 1 commit into
openai:mainfrom
adityasingh2400:fix-mcp-manager-one-shot-iterable
Aug 6, 2026
Merged

fix(mcp): build the active server list without re-consuming the iterable#4235
seratch merged 1 commit into
openai:mainfrom
adityasingh2400:fix-mcp-manager-one-shot-iterable

Conversation

@adityasingh2400

Copy link
Copy Markdown
Contributor

MCPServerManager.__init__ accepts Iterable[MCPServer] but calls list(servers) twice, so a generator or any other one-shot iterable is already exhausted by the second call and _active_servers starts out empty. _all_servers is populated correctly by the first pass, so the second list should be built from it rather than from the caller's iterable again.

The empty list is observable straight after construction, and it also survives a real operation: when connect_all fails, the drop_failed_servers=False restore path replays the list captured at entry, so active_servers ends up empty where an equivalent list argument leaves it intact. After a successful connect_all the value is masked, because _refresh_active_servers rebuilds it from _all_servers.

Fail-before evidence: with src/agents/mcp/manager.py reverted to main and the new tests kept, test_manager_accepts_one_shot_iterables and test_manager_restores_one_shot_iterable_servers_after_a_failed_connect both fail with assert [] == [<tests.mcp.test_mcp_server_manager.FlakyServer object at 0x...>]. The third new test covers the successful async with path and passes either way, so it is a regression guard rather than a reproducer. With the fix all 62 tests in tests/mcp/test_mcp_server_manager.py pass, and make lint and make typecheck are green.

MCPServerManager.__init__ accepts Iterable[MCPServer] but called list(servers)
twice, so a generator or any other one-shot iterable was already exhausted by
the time the second call ran and _active_servers started out empty. all_servers
was populated correctly from the first pass, which is why the second pass should
read from it rather than from the caller's iterable. The empty list is visible
before connect_all runs, and it also survives a failed connect because
drop_failed_servers=False restores the list captured at that point.
@seratch seratch added this to the 0.20.x milestone Aug 6, 2026
@seratch
seratch merged commit c9153d2 into openai:main Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants