Skip to content

Close the transport a rejected connect_to_server opened - #3523

Closed
pujitha24 wants to merge 1 commit into
modelcontextprotocol:mainfrom
pujitha24:auto/issue-3490
Closed

pujitha24 wants to merge 1 commit into
modelcontextprotocol:mainfrom
pujitha24:auto/issue-3490

Conversation

@pujitha24

Copy link
Copy Markdown

Fixes #3490

Motivation and Context

ClientSessionGroup.connect_to_server opens a transport (subprocess or HTTP
session) and initializes a ClientSession before validating the server's
prompts/resources/tools against names already registered in the group. When
that validation rejects the server for a duplicate name, connect_with_session
raises MCPError before the session is recorded in self._sessions, so the
caller has no session object to close and the group's own tracking never lists
it. The already-opened transport stays alive until the whole group tears down:
a leaked child process for stdio, or an initialized session counted against a
server's max_sessions for streamable HTTP. A long-lived host that retries a
failed connect leaks one more resource per attempt.

How Has This Been Tested?

Added test_client_session_group_connect_to_server_closes_transport_on_duplicate
to tests/client/test_session_group.py, which mocks _establish_session to
register a mock transport stack the way the real implementation does, triggers
the existing duplicate-tool-name rejection path, and asserts the mock stack's
aclose is awaited and the session is removed from _session_exit_stacks.
Confirmed the test fails on the pre-fix code with "Expected aclose to have
been awaited once. Awaited 0 times." and passes after the fix.

Ran the full suite: uv run --frozen pytest and ./scripts/test both pass
(5969 passed, 10 skipped, 1 xfailed), coverage stays at 100.00% with no new
pragma: no cover, and ruff format --check, ruff check, and pyright are
clean on both changed files.

Breaking Changes

None. connect_to_server's signature, return type, and raised exception are
unchanged; the only observable difference is that the transport it opened is
now closed before the exception propagates, instead of leaking until the
whole group is torn down.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Two prior PRs for this same issue (#3491, #3502) were auto-closed by this
repo's require-issue-link bot because their authors weren't assigned to
#3490 — neither was rejected on technical grounds. This PR reimplements the
same fix (close the transport in connect_to_server, the only caller that
owns it, so connect_with_session's "the group never closes a session it
didn't open" contract for its own direct callers is preserved).


AI assistance: this change was drafted with Claude Code.

Motivation:

ClientSessionGroup.connect_to_server opens a transport (subprocess or
HTTP session) and initializes a ClientSession before validating the
server's prompts/resources/tools against names already registered in
the group. When that validation rejects the server for a duplicate
name, connect_with_session raises MCPError before the session is
recorded in self._sessions, so the caller has no session object to
close and the group's own tracking never lists it. The already-opened
transport stays alive until the whole group tears down: a leaked
child process for stdio, or an initialized session counted against a
server's max_sessions for streamable HTTP. A long-lived host that
retries a failed connect leaks one more resource per attempt.

Approach:

connect_to_server is the only caller of connect_with_session that
owns the transport it opened (via _establish_session); the other
caller, connect_with_session used directly, is handed an
already-owned session by its caller and must not have it closed out
from under them. So the fix is scoped to connect_to_server: wrap the
call to connect_with_session in try/except, and on any exception pop
the session's entry out of self._session_exit_stacks and close it
before re-raising, mirroring the cleanup disconnect_from_server
already does for a session the group decides to drop.

Validation:

Added test_client_session_group_connect_to_server_closes_transport_on_duplicate
to tests/client/test_session_group.py, which mocks _establish_session
to register a mock transport stack the way the real implementation
does, triggers the existing duplicate-tool-name rejection path, and
asserts the mock stack's aclose is awaited and the session is removed
from _session_exit_stacks. Confirmed the test fails on the pre-fix
code with "Expected aclose to have been awaited once. Awaited 0
times." and passes after the fix.

Ran the full suite: `uv run --frozen pytest` and `./scripts/test`
both pass (5969 passed, 10 skipped, 1 xfailed), coverage stays at
100.00% with no new pragma: no cover, and `ruff format --check`,
`ruff check`, and `pyright` are clean on both changed files.

Report: modelcontextprotocol#3490
Signed-off-by: Pujitha Paladugu <10557236+pujitha24@users.noreply.github.com>
Assisted-by: claude-sonnet-5 (via Claude Code)
@github-actions github-actions Bot added the missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md) label Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3490.

If a maintainer assigns you to #3490, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take.

You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way.

CONTRIBUTING.md has the full reasoning, but in short:

  • We're a small team with very little capacity to review community PRs right now.
  • Many recent PRs are AI-generated with little human review, and reviewing one carefully still costs a maintainer as much time as it ever did. A well-described issue is usually more useful to us than the code.

Maintainers: reopen, remove missing-issue-link, or add bypass-issue-check to override.

@github-actions github-actions Bot closed this Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

missing-issue-link Auto-closed: PR needs a linked issue assigned to its author (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClientSessionGroup: a rejected connect_to_server leaves its transport running — the session is established before its components are validated

1 participant