Skip to content

concurrent_startup param for mcp client to startup all mcp servers parallel async#2597

Closed
geobio wants to merge 1 commit intoPrefectHQ:mainfrom
geobio:g-async-start
Closed

concurrent_startup param for mcp client to startup all mcp servers parallel async#2597
geobio wants to merge 1 commit intoPrefectHQ:mainfrom
geobio:g-async-start

Conversation

@geobio
Copy link
Copy Markdown

@geobio geobio commented Dec 11, 2025

Add concurrent MCP server startup

Adds concurrent_startup parameter to Client for parallel initialization of multiple MCP servers.

Problem

When connecting to many MCP servers via MCPConfig, they currently start sequentially, causing slow initialization (e.g., 30 servers × 5s = 150 seconds (we have experienced this forreals)).

Solution

  • With concurrent_startup all servers start concurrently via asyncio.gather()
  • Startup logs captured per-server and displayed sequentially for readability
  • After they're started up, we re-attach them and logs appear as normal

Changes

  • Added concurrent_startup: bool = False parameter to Client
  • Added warm_up_mcp_config_transports() for concurrent connection
  • Each server's logs captured to temp file during startup, get re-attached after started.
  • Logs displayed in readable format after all servers connect

Example

client = Client(config, concurrent_startup=True)
async with client:
    # Fast startup with readable logs
    tools = await client.list_tools()

@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. client Related to the FastMCP client SDK or client-side functionality. labels Dec 11, 2025
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The static analysis workflow failed because detected formatting issues in tests/client/test_concurrent_startup.py.

Root Cause: The newly added test file doesn't conform to the project's formatting standards enforced by . Specifically:

  • Line 34: The @pytest.mark.skip decorator exceeds line length limits and needs to be broken across multiple lines
  • End of file: Trailing blank line needs to be removed

Suggested Solution: Run the following command locally to fix the formatting:

uv run prek run --all-files

This will auto-format the file to match project standards. Then commit and push the changes.

Detailed Analysis

The workflow shows:

ruff format.............................................................Failed
- hook id: ruff-format
- files were modified by this hook

The specific changes needed in tests/client/test_concurrent_startup.py:

-@pytest.mark.skip(reason="Known issue with proxy keep-alive in tests - works in practice")
+@pytest.mark.skip(
+    reason="Known issue with proxy keep-alive in tests - works in practice"
+)

# And remove trailing blank line at end of file
Related Files
  • tests/client/test_concurrent_startup.py - New test file with formatting issues

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 11, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This pull request adds a concurrent_startup: bool = False parameter to Client.__init__, infer_transport, and MCPConfigTransport.__init__. MCPConfigTransport now tracks mounted server names (_server_names) and a _warmup_done flag. On the first session connect, if concurrent_startup is true, transports are pre-connected concurrently via a new async def warm_up_mcp_config_transports(transports, server_names=None, show_startup_logs=True). That utility runs concurrent connects, captures startup logs for stdio-type transports, displays per-server logs, and aggregates failures using ExceptionGroup.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the problem, solution, changes, and example, but is missing required checklist items from the template (contributors and review checklists are completely absent). Add the Contributors Checklist and Review Checklist sections with all required checkboxes from the template to meet repository requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding a concurrent_startup parameter to enable parallel async startup of multiple MCP servers, which matches the primary objective of the pull request.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f1b5e6 and 6ab5a6c.

⛔ Files ignored due to path filters (1)
  • tests/client/test_concurrent_startup.py is excluded by none and included by none
📒 Files selected for processing (3)
  • src/fastmcp/client/client.py (3 hunks)
  • src/fastmcp/client/transports.py (8 hunks)
  • src/fastmcp/utilities/mcp_config.py (2 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/fastmcp/utilities/mcp_config.py (2)

81-92: Consider logging cleanup failures instead of silent pass.

The bare except Exception catches are flagged by static analysis (BLE001, S110). While acceptable for cleanup code, silently swallowing exceptions can hide debugging information.

             except Exception as e:
                 logs = ""
                 if temp_log_path and temp_log_path.exists():
                     try:
                         logs = temp_log_path.read_text()
                         temp_log_path.unlink()
-                    except Exception:
-                        pass
+                    except OSError:
+                        # Cleanup failure is non-critical; proceed with error reporting
+                        pass
                 return (index, logs, e)

Using OSError (covers file I/O errors) is more specific than bare Exception.


45-48: Redundant import of StdioTransport.

StdioTransport is already imported at the module level (line 9). The local import on line 48 is unnecessary.

     import tempfile
     from pathlib import Path

-    from fastmcp.client.transports import StdioTransport
-
     if not transports:
         return
src/fastmcp/client/transports.py (1)

1019-1028: Consider making show_startup_logs configurable.

Currently show_startup_logs=True is hardcoded. Users may want to suppress startup log output in production or CI environments.

 def __init__(
     self,
     config: MCPConfig | dict,
     name_as_prefix: bool = True,
     concurrent_startup: bool = False,
+    show_startup_logs: bool = True,
 ):
     ...
     self.concurrent_startup = concurrent_startup
+    self._show_startup_logs = show_startup_logs

Then in connect_session:

         await warm_up_mcp_config_transports(
             self._underlying_transports,
             server_names=self._server_names,
-            show_startup_logs=True,
+            show_startup_logs=self._show_startup_logs,
         )

This would also require propagating through infer_transport and Client.__init__.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6fd46 and 7f1b5e6.

⛔ Files ignored due to path filters (1)
  • tests/client/test_concurrent_startup.py is excluded by none and included by none
📒 Files selected for processing (3)
  • src/fastmcp/client/client.py (3 hunks)
  • src/fastmcp/client/transports.py (7 hunks)
  • src/fastmcp/utilities/mcp_config.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bare except - be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code organization and style

Files:

  • src/fastmcp/client/client.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/utilities/mcp_config.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.256Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing using in-memory transport; only use HTTP transport when explicitly testing network features
📚 Learning: 2025-12-04T00:17:41.256Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-04T00:17:41.256Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing using in-memory transport; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/client/client.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/utilities/mcp_config.py
🧬 Code graph analysis (2)
src/fastmcp/client/client.py (1)
src/fastmcp/client/transports.py (9)
  • infer_transport (1042-1042)
  • infer_transport (1046-1046)
  • infer_transport (1050-1050)
  • infer_transport (1054-1054)
  • infer_transport (1058-1058)
  • infer_transport (1062-1064)
  • infer_transport (1068-1072)
  • infer_transport (1076-1076)
  • infer_transport (1079-1160)
src/fastmcp/utilities/mcp_config.py (3)
src/fastmcp/server/server.py (2)
  • name (370-371)
  • FastMCP (173-2970)
src/fastmcp/client/transports.py (2)
  • ClientTransport (76-118)
  • connect (378-412)
src/fastmcp/server/context.py (1)
  • fastmcp (150-155)
🪛 Ruff (0.14.8)
src/fastmcp/utilities/mcp_config.py

79-79: Consider moving this statement to an else block

(TRY300)


81-81: Do not catch blind exception: Exception

(BLE001)


87-88: try-except-pass detected, consider logging the exception

(S110)


87-87: Do not catch blind exception: Exception

(BLE001)


108-108: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (5)
src/fastmcp/client/client.py (1)

175-177: LGTM! Clean parameter propagation.

The concurrent_startup parameter is properly typed, documented, and correctly forwarded to infer_transport. The docstring clearly explains the behavior and trade-offs.

Also applies to: 276-276, 280-284

src/fastmcp/utilities/mcp_config.py (2)

95-99: Verify exception handling with return_exceptions=False.

The inner function connect_with_log_capture catches all exceptions and returns them as tuples. However, asyncio.CancelledError (a BaseException) will escape and cause asyncio.gather to cancel remaining tasks.

If task cancellation should be handled gracefully (e.g., during shutdown), consider catching BaseException or using return_exceptions=True:

-        except Exception as e:
+        except BaseException as e:
+            if isinstance(e, asyncio.CancelledError):
+                raise  # Re-raise cancellation to propagate properly
             logs = ""

Alternatively, if current behavior is intentional (cancellation should abort all servers), this is fine as-is.


111-145: LGTM! Clear startup log formatting.

The _display_startup_logs helper provides readable sequential output with proper status indicators and summary. Using sys.stderr is appropriate for startup diagnostics.

src/fastmcp/client/transports.py (2)

972-987: LGTM! Well-structured concurrent startup integration.

The MCPConfigTransport correctly tracks server names and warm-up state. The concurrent_startup flag is properly stored and defaults to False for backward compatibility.


1088-1096: LGTM! concurrent_startup correctly scoped to MCPConfig.

The parameter is properly documented and only applied when creating MCPConfigTransport. Other transport types correctly ignore it, maintaining backward compatibility.

Also applies to: 1148-1153

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Related to the FastMCP client SDK or client-side functionality. enhancement Improvement to existing functionality. For issues and smaller PR improvements.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant