Skip to content

Commit 831f1ee

Browse files
author
RJ Lopez
committed
Auto-sync VERITAS session updates
1 parent 3da79fe commit 831f1ee

7 files changed

Lines changed: 119 additions & 0 deletions

File tree

output.txt

16.4 KB
Binary file not shown.

output_b.txt

Whitespace-only changes.

pr_915_body.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Fixes #915 by sequentially closing `ClientSessionGroup` session stacks during teardown, preventing `anyio` cancellation scope violations that previously masked underlying connection errors.
2+
3+
## Motivation and Context
4+
When `ClientSessionGroup` encounters a connection failure (e.g., when a remote server is unavailable), it triggers `__aexit__` due to the exiting context manager. Prior to this fix, `__aexit__` attempted to concurrently close all previously established session exit stacks using an `anyio` task group (`async with anyio.create_task_group()`).
5+
6+
Because this teardown occurred while unwinding an exception, AnyIO correctly threw `RuntimeError: Attempted to exit cancel scope in a different task`. This completely masked the original connection error (like `ConnectError`) and confused consumers. By iterating through `_session_exit_stacks` and sequentially awaiting `aclose()`, we preserve AnyIO task contexts and allow the true exception to propagate cleanly.
7+
8+
## How Has This Been Tested?
9+
- Tested using a standalone script that forces an immediate connection failure. Verified that `ConnectError` and arbitrary exceptions are now successfully caught by the user instead of throwing `RuntimeError: Attempted to exit cancel scope in a different task`.
10+
- Ran the existing `pytest tests/shared/test_session_group.py` test suite locally to ensure no regressions in default scope teardown.
11+
12+
## Breaking Changes
13+
None. Users will now properly receive `ExceptionGroup` or `ConnectError` on failure rather than cryptic AnyIO scope errors.
14+
15+
## Types of changes
16+
- [x] Bug fix (non-breaking change which fixes an issue)
17+
- [ ] New feature (non-breaking change which adds functionality)
18+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
19+
- [ ] Documentation update
20+
21+
## Checklist
22+
- [x] I have read the [MCP Documentation](https://modelcontextprotocol.io)
23+
- [x] My code follows the repository's style guidelines
24+
- [x] New and existing tests pass locally
25+
- [x] I have added appropriate error handling
26+
- [ ] I have added or updated documentation as needed
27+
28+
## Additional context
29+
Fixes #915
30+
This maintains scope integrity for asynchronous connections since the sessions were already created sequentially in the main task; they can safely be brought down sequentially.

pr_body.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Added wildcard pattern matching to the Accept header parser. The server now correctly handles `*/*`, `type/*`, and exact media type matches in order of specificity per the RFC.
2+
3+
## Motivation and Context
4+
The Streamable HTTP transport rejects requests with `Accept: */*` or `Accept: application/*` headers, returning an error instead of matching against supported media types. This violates RFC 7231 §5.3.2 which requires servers to honor wildcard Accept values.
5+
6+
Fixes #1641
7+
8+
## How Has This Been Tested?
9+
- Added tests for `*/*` wildcard acceptance
10+
- Added tests for `application/*` partial wildcard
11+
- Added tests for exact match still working
12+
- All 62 existing tests pass locally
13+
14+
## Breaking Changes
15+
None.
16+
17+
## Types of changes
18+
- [x] Bug fix (non-breaking change which fixes an issue)
19+
- [ ] New feature (non-breaking change which adds functionality)
20+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
21+
- [ ] Documentation update
22+
23+
## Checklist
24+
- [x] I have read the [MCP Documentation](https://modelcontextprotocol.io)
25+
- [x] My code follows the repository's style guidelines
26+
- [x] New and existing tests pass locally
27+
- [x] I have added appropriate error handling
28+
- [x] I have added or updated documentation as needed
29+
30+
## Additional context
31+
N/A

repro_915.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import anyio
2+
import mcp
3+
from mcp.client.session_group import ClientSessionGroup, SseServerParameters
4+
import traceback
5+
6+
async def main():
7+
try:
8+
async with ClientSessionGroup() as group:
9+
print("Connecting to unavailable server...")
10+
await group.connect_to_server(
11+
SseServerParameters(url="http://localhost:54321/sse")
12+
)
13+
except Exception as e:
14+
print(f"Caught exception: {type(e).__name__}: {e}")
15+
traceback.print_exc()
16+
17+
anyio.run(main)

repro_915_b.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import anyio
2+
import mcp
3+
from mcp.client.session_group import ClientSessionGroup, SseServerParameters
4+
from mcp.client.stdio import StdioServerParameters
5+
import httpx
6+
import traceback
7+
import sys
8+
9+
async def main():
10+
try:
11+
print("Starting test...")
12+
# Create a working server parameter
13+
# We can use StdioServerParameters with a command that just sleeps or something to simulate a working server.
14+
working_server = StdioServerParameters(command="python", args=["-c", "import time; time.sleep(100)"])
15+
broken_server = SseServerParameters(url="http://localhost:54321/sse")
16+
17+
async with ClientSessionGroup() as group:
18+
print("Connecting to working server...")
19+
await group.connect_to_server(working_server)
20+
print("Connecting to broken server...")
21+
await group.connect_to_server(broken_server)
22+
23+
except Exception as e:
24+
print(f"Caught exception: {type(e).__name__}: {e}")
25+
traceback.print_exc()
26+
27+
anyio.run(main)

test_fix.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import anyio
2+
import mcp.client.session_group
3+
from mcp.client.stdio import stdio_client, StdioServerParameters
4+
5+
async def main():
6+
sg = mcp.client.session_group.ClientSessionGroup()
7+
try:
8+
async with sg:
9+
await sg.connect_to_server('broken', stdio_client(StdioServerParameters(command='python', args=['-c', 'import sys; sys.exit(1)'])))
10+
except Exception as e:
11+
print(f"FAILED CLEANLY WITH: {type(e).__name__}")
12+
13+
if __name__ == "__main__":
14+
anyio.run(main)

0 commit comments

Comments
 (0)