The MCP library internally uses TaskGroup for async operations, which can
raise ExceptionGroup when cleanup fails. This was causing unhandled errors
to propagate in production.
Changes:
- Update cleanup() method in AsyncBaseMCPClient to catch ExceptionGroup
using except* syntax and log errors at debug level (best-effort cleanup)
- Remove redundant try/except blocks in mcp_manager.py and
mcp_server_manager.py that incorrectly re-raised cleanup exceptions
Fixes#8560🐾 Generated with [Letta Code](https://letta.com)
Co-authored-by: letta-code <248085862+letta-code@users.noreply.github.com>
Co-authored-by: Letta <noreply@letta.com>
Co-authored-by: Kian Jones <11655409+kianjones9@users.noreply.github.com>
- Fix typo "upate" -> "update" in TODO comments (mcp_manager.py, mcp_server_manager.py)
- Improve comments in OAuth callback handler to explain why MCPOAuthSession
is used directly (callback is unauthenticated, manager requires actor)
- Clean up variable naming in callback handler
🐾 Generated with [Letta Code](https://letta.com)
Co-authored-by: Letta <noreply@letta.com>
Problem: When creating an MCP server with many tools, the code used two
asyncio.gather calls - one for tool creation and one for mapping creation.
Each operation involves database INSERT/UPDATE, leading to 2N concurrent
database connections.
Example: An MCP server with 50 tools creates 50 + 50 = 100 simultaneous
database connections (tools + mappings), severely exhausting the pool.
Root cause:
1. asyncio.gather(*[create_mcp_tool_async(...) for tool in tools])
2. asyncio.gather(*[create_mcp_tool_mapping(...) for tool in results])
Both process operations concurrently, each opening a DB session.
Solution: Process tool creation and mapping sequentially in a single loop.
Create each tool, then immediately create its mapping if successful. This:
- Reduces connection count from 2N to 1
- Maintains proper error handling per tool
- Prevents database connection pool exhaustion
Changes:
- apps/core/letta/services/mcp_server_manager.py:
- Replaced two asyncio.gather calls with single sequential loop
- Create mapping immediately after each successful tool creation
- Maintained return_exceptions=True behavior with try/except
- Added explanatory comment about db pool exhaustion prevention
Impact: With 50 MCP tools:
- Before: 100 concurrent DB connections (50 tools + 50 mappings, pool exhaustion)
- After: 1 DB connection at a time (no pool exhaustion)
Note: This follows the same pattern as PR #6617, #6619, #6620, and #6621
which fixed similar issues throughout the codebase.