Skip to content

Fix timeout not propagating to proxy clients in multi-server MCPConfig#2809

Merged
jlowin merged 7 commits intomainfrom
fix/mcp-config-timeout-propagation
Jan 10, 2026
Merged

Fix timeout not propagating to proxy clients in multi-server MCPConfig#2809
jlowin merged 7 commits intomainfrom
fix/mcp-config-timeout-propagation

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 7, 2026

When using MCPConfig with multiple servers, the timeout wasn't propagating to the proxy clients because they were being created in __init__ before the timeout was known. The solution was to defer proxy client creation to connect_session() when session_kwargs are available.

This also simplifies the codebase by moving all logic into MCPConfigTransport and deleting the helper utilities in utilities/mcp_config.py.

async with Client(config, timeout=timedelta(seconds=30)) as client:
    # timeout now correctly flows to all backend servers
    await client.call_tool("server1_tool", {})

Closes #2802

@marvin-context-protocol marvin-context-protocol Bot added bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality. labels Jan 7, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 7, 2026

Walkthrough

MCPConfigTransport was refactored to support multi-server configurations: it now tracks underlying transports in _transports, adds name_as_prefix, and delays proxy creation until connect_session. A new internal _create_proxy builds per-server transports and ProxyClient-backed proxies (including Transforming* server types). connect_session short-circuits single-server usage or constructs a FastMCPRouter mounting per-server proxies and opens a session via FastMCPTransport. close closes all tracked transports. Separately, two utility functions in src/fastmcp/utilities/mcp_config.py that converted MCP configs into server/transport/proxy tuples were removed.

Possibly related PRs

  • jlowin/fastmcp PR 2520: Touches ProxyClient creation and forwarding of client kwargs/meta, closely related to proxy instantiation and request forwarding changes.
  • jlowin/fastmcp PR 2591: Modifies connect_session cleanup and exception propagation logic, overlapping changes to session connection flow.
  • jlowin/fastmcp PR 2669: Alters proxy construction/mounting behavior and namespace/prefix handling, directly intersecting MCPConfigTransport's mounting logic.
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description lacks required checklist items and documentation updates information specified in the template. Add the Contributors Checklist with checkmarks for issue reference, workflow compliance, testing, and documentation. Include the Review Checklist confirming self-review and readiness.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: timeout propagation to proxy clients in multi-server MCPConfig, matching the core problem addressed in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 (1)
src/fastmcp/utilities/json_schema.py (1)

9-51: Well-designed fallback for circular schema handling.

The dereference_refs function properly:

  1. Uses jsonref to inline all $ref references
  2. Removes $defs after successful resolution
  3. Falls back to resolve_root_ref for self-referencing/circular schemas that can't be fully dereferenced

Consider the static analysis suggestion (TRY300) to move the success return into an else block for clearer control flow, though this is a minor style preference.

♻️ Optional: Move return to else block per TRY300
     try:
         # Use jsonref to resolve all $ref references
         # proxies=False returns plain dicts (not proxy objects)
         # lazy_load=False resolves immediately
         dereferenced = replace_refs(schema, proxies=False, lazy_load=False)
 
         # Remove $defs since all references have been resolved
         if isinstance(dereferenced, dict) and "$defs" in dereferenced:
             dereferenced = {k: v for k, v in dereferenced.items() if k != "$defs"}
 
-        return dereferenced
- 
     except JsonRefError:
         # Self-referencing/circular schemas can't be fully dereferenced
         # Fall back to resolving only root-level $ref (for MCP spec compliance)
         return resolve_root_ref(schema)
+    else:
+        return dereferenced
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a117316 and 84ed861.

⛔ Files ignored due to path filters (7)
  • pyproject.toml is excluded by none and included by none
  • tests/test_mcp_config.py is excluded by none and included by none
  • tests/tools/test_tool.py is excluded by none and included by none
  • tests/tools/test_tool_transform.py is excluded by none and included by none
  • tests/utilities/openapi/test_schemas.py is excluded by none and included by none
  • tests/utilities/test_json_schema.py is excluded by none and included by none
  • uv.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (5)
  • src/fastmcp/client/transports.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/tools/tool_transform.py
  • src/fastmcp/utilities/json_schema.py
  • src/fastmcp/utilities/mcp_config.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/client/transports.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/utilities/json_schema.py
  • src/fastmcp/tools/tool_transform.py
🧠 Learnings (3)
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/tools/tool.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/tools/tool_transform.py
🧬 Code graph analysis (4)
src/fastmcp/utilities/mcp_config.py (3)
src/fastmcp/server/server.py (3)
  • FastMCP (194-2576)
  • name (419-420)
  • as_proxy (2509-2567)
src/fastmcp/client/transports.py (3)
  • ClientTransport (77-119)
  • StreamableHttpTransport (231-332)
  • SSETransport (161-228)
src/fastmcp/server/providers/proxy.py (1)
  • ProxyClient (703-737)
src/fastmcp/client/transports.py (1)
src/fastmcp/utilities/mcp_config.py (1)
  • mcp_config_to_servers_and_transports (18-31)
src/fastmcp/tools/tool.py (1)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (286-323)
src/fastmcp/tools/tool_transform.py (1)
src/fastmcp/utilities/json_schema.py (1)
  • compress_schema (286-323)
🪛 Ruff (0.14.10)
src/fastmcp/utilities/json_schema.py

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

(TRY300)

⏰ 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). (4)
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
🔇 Additional comments (9)
src/fastmcp/utilities/mcp_config.py (2)

18-31: LGTM! Clean API extension for proxy client tracking.

The return type update to include Client[Any] | None as a fourth tuple element is well-documented and properly typed. This enables the timeout propagation fix for multi-server configurations.


53-72: LGTM! Correct proxy client handling for different server types.

The logic correctly distinguishes between:

  • Transforming servers that manage their own clients (returning None)
  • Non-transforming servers where the ProxyClient is captured and returned for timeout propagation
src/fastmcp/tools/tool.py (1)

37-37: LGTM! Import cleanup aligns with schema refactoring.

The removal of resolve_root_ref import is consistent with the changes in json_schema.py where compress_schema now internally handles reference resolution via dereference_refs, which falls back to resolve_root_ref for circular schemas.

src/fastmcp/tools/tool_transform.py (2)

683-683: LGTM! Consistent with updated compress_schema signature.

The removal of prune_defs=True aligns with the refactored compress_schema in json_schema.py, which now handles reference resolution via dereference_refs at the start of the function.


866-866: LGTM! Same pattern as line 683.

Consistent removal of the deprecated prune_defs parameter from the compress_schema call.

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

1000-1001: LGTM! Proxy client tracking for timeout propagation.

The _proxy_clients list enables collecting proxy clients during initialization so their timeouts can be updated in connect_session().


1017-1028: LGTM! Correct unpacking and conditional collection of proxy clients.

The code correctly:

  1. Unpacks the new 4-tuple return value
  2. Only appends non-None proxy clients to the tracking list

1036-1041: Core fix: Timeout propagation to proxy clients.

This correctly propagates read_timeout_seconds to all tracked proxy clients before establishing the session. The comment accurately explains why this works with Client.new()'s shallow copy behavior.

One observation: this sets read_timeout_seconds even when timeout is None, which will override any existing value. This behavior seems intentional—if the user doesn't provide a timeout, the proxy clients shouldn't use a stale one.

src/fastmcp/utilities/json_schema.py (1)

306-321: LGTM! Correct schema compression flow.

The refactored compress_schema:

  1. Dereferences all $ref entries first via dereference_refs
  2. Applies parameter pruning
  3. Runs single-pass optimization with prune_defs=False (since $defs are already removed by dereference_refs)

This ensures MCP client compatibility while maintaining schema optimization.

When MCPConfigTransport has multiple servers, it creates proxy clients
for each server. The timeout from the top-level Client wasn't being
propagated to these proxy clients, causing HTTP calls to ignore the
timeout setting.

Store proxy client references and propagate read_timeout_seconds to
them in connect_session().

Closes #2802
@jlowin jlowin force-pushed the fix/mcp-config-timeout-propagation branch from 84ed861 to 6b11026 Compare January 7, 2026 23:25
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84ed8618d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/utilities/mcp_config.py Outdated
Comment on lines +18 to +21
def mcp_config_to_servers_and_transports(
config: MCPConfig,
) -> list[tuple[str, FastMCP[Any], ClientTransport]]:
"""A utility function to convert each entry of an MCP Config into a transport and server."""
) -> list[tuple[str, FastMCP[Any], ClientTransport, Client[Any] | None]]:
"""A utility function to convert each entry of an MCP Config into a transport, server, and proxy client.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve 3-tuple return for mcp_config helpers

Changing mcp_config_to_servers_and_transports to return a 4‑tuple will raise ValueError: too many values to unpack for any existing callers that do for name, server, transport in .... This helper is part of the documented SDK surface, so the new return shape is a breaking change introduced here. Consider keeping the original 3‑tuple and exposing proxy clients via a new helper or an opt‑in flag to avoid breaking external consumers.

Useful? React with 👍 / 👎.

Comment thread tests/test_mcp_config.py Outdated
This is a regression test for https://github.com/jlowin/fastmcp/issues/2802
where timeout was ignored in multi-server configurations.
"""
import datetime
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move test import to module top

AGENTS.md (Testing Standards) requires imports to be at the top of the file, not inside test bodies. The inline import datetime violates that rule and will be flagged in review for this repo. Move the import to the module header to align with the project’s mandated testing conventions.

Useful? React with 👍 / 👎.

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 (1)
src/fastmcp/utilities/mcp_config.py (1)

3-3: Consider using TYPE_CHECKING for the Client import.

Since Client is only used in type annotations in this file and not at runtime, consider wrapping this import in TYPE_CHECKING to avoid potential circular import issues and reduce import overhead:

+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from fastmcp.client.client import Client
-from fastmcp.client.client import Client

Then use string annotation: "Client[Any] | None" in the return types.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84ed861 and 6b11026.

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

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/client/transports.py
🧠 Learnings (3)
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/utilities/mcp_config.py
🧬 Code graph analysis (2)
src/fastmcp/utilities/mcp_config.py (3)
src/fastmcp/client/transports.py (2)
  • ClientTransport (77-119)
  • StreamableHttpTransport (231-332)
src/fastmcp/mcp_config.py (3)
  • to_transport (119-123)
  • to_transport (159-168)
  • to_transport (211-233)
src/fastmcp/server/providers/proxy.py (1)
  • ProxyClient (703-737)
src/fastmcp/client/transports.py (1)
src/fastmcp/utilities/mcp_config.py (1)
  • mcp_config_to_servers_and_transports (18-31)
⏰ 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). (4)
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • 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/transports.py (3)

992-1001: LGTM on proxy client tracking initialization.

The local import of Client avoids circular dependency issues, and the _proxy_clients list is properly typed. The initialization alongside _underlying_transports is consistent with the existing pattern.


1017-1028: LGTM on proxy client collection during multi-server setup.

The conditional append correctly filters out None values from transforming servers that manage their own clients. The unpacking aligns with the updated 4-tuple return type from mcp_config_to_servers_and_transports.


1036-1044: Timeout propagation approach is correct and properly documented.

The code correctly propagates read_timeout_seconds to all proxy clients via direct mutation of their _session_kwargs. This works because Client.new() uses copy.copy(self), creating a shallow copy where all new clients share the same _session_kwargs dictionary reference. The existing comment accurately documents this coupling, so the implementation is clear to future maintainers.

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

18-31: LGTM on updated function signature and documentation.

The return type correctly includes Client[Any] | None as the fourth tuple element, and the docstring clearly explains that proxy_client may be None for transforming server types.


51-72: LGTM on proxy client handling logic.

The implementation correctly:

  • Initializes proxy_client to None (line 53)
  • Leaves it as None for transforming servers that manage their own clients (line 58-62)
  • Creates and assigns a ProxyClient for the non-transforming path (lines 65-68)
  • Returns the 4-tuple consistently (line 72)

The type annotation Client[Any] | None matches the actual runtime values.

@jlowin jlowin marked this pull request as draft January 9, 2026 02:36
jlowin added 3 commits January 9, 2026 17:18
Refactor MCPConfigTransport to handle all logic inline rather than through
helper functions in utilities/mcp_config.py. Proxy clients are now created
in connect_session() when session_kwargs (including timeout) are available,
fixing timeout propagation to backend servers.
@jlowin jlowin marked this pull request as ready for review January 10, 2026 00:37
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 (1)
src/fastmcp/client/transports.py (1)

1023-1037: Consider adding error handling for partial transport creation.

While the current implementation is functional, if _create_proxy raises an exception during the loop (lines 1029-1032), some transports may be left in _transports without being properly initialized. Consider wrapping the loop in a try-except block to ensure cleanup on failure:

♻️ Optional improvement for robustness
 # Multiple servers - create composite with mounted proxies
 # Clear any previous transports from prior connections
 self._transports = []
 timeout = session_kwargs.get("read_timeout_seconds")
 composite = FastMCP[Any](name="MCPRouter")

+try:
     for name, server_config in self.config.mcpServers.items():
         transport, proxy = self._create_proxy(name, server_config, timeout)
         self._transports.append(transport)
         composite.mount(proxy, namespace=name if self.name_as_prefix else None)
+except Exception:
+    # Clean up any transports created before the failure
+    for transport in self._transports:
+        await transport.close()
+    self._transports = []
+    raise

 async with FastMCPTransport(mcp=composite).connect_session(
     **session_kwargs
 ) as session:
     yield session

Note: This is optional since users should call close() on exception anyway, but it would make the behavior more defensive.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b11026 and 9caaff7.

⛔ Files ignored due to path filters (2)
  • tests/client/test_client.py is excluded by none and included by none
  • tests/test_mcp_config.py is excluded by none and included by none
📒 Files selected for processing (2)
  • src/fastmcp/client/transports.py
  • src/fastmcp/utilities/mcp_config.py
💤 Files with no reviewable changes (1)
  • src/fastmcp/utilities/mcp_config.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/client/transports.py
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Follow existing patterns and maintain consistency in code implementation

Applied to files:

  • src/fastmcp/client/transports.py
🧬 Code graph analysis (1)
src/fastmcp/client/transports.py (1)
src/fastmcp/mcp_config.py (9)
  • MCPConfig (247-298)
  • RemoteMCPServer (175-233)
  • StdioMCPServer (126-168)
  • TransformingRemoteMCPServer (236-237)
  • TransformingStdioMCPServer (171-172)
  • infer_transport_type_from_url (56-73)
  • to_transport (119-123)
  • to_transport (159-168)
  • to_transport (211-233)
🪛 Ruff (0.14.10)
src/fastmcp/client/transports.py

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

(TRY003)

🔇 Additional comments (5)
src/fastmcp/client/transports.py (5)

36-44: LGTM! Imports support multi-server configuration.

The new imports enable handling of different MCP server types including transforming servers, which is essential for the multi-server proxy functionality.


1002-1011: Well-structured initialization for single and multi-server support.

The name_as_prefix flag and _transports list provide good state management. The single-server optimization (creating transport eagerly in __init__) allows the transport to be inspected before connection, which is a nice ergonomic improvement.


1017-1037: Excellent refactoring that correctly propagates timeout to proxy clients.

The key improvement here is extracting timeout from session_kwargs (line 1026) and passing it to _create_proxy (line 1030), which ensures that the timeout flows through to all backend servers. The single-server optimization (lines 1017-1021) avoids unnecessary overhead, while the multi-server composite approach (lines 1023-1037) elegantly handles multiple backends through mounted proxies.


1039-1076: Core fix correctly implemented - timeout now propagates to proxy clients.

This method is the heart of the timeout propagation fix. Line 1067 creates ProxyClient(transport=transport, timeout=timeout), ensuring that the timeout from session-level configuration flows through to each backend server's proxy client. The handling of transforming servers (lines 1054-1065) is also well-structured, correctly extracting transformation configuration while delegating to base class to_transport() for the underlying transport.


1077-1079: LGTM! Proper cleanup of all underlying transports.

The close method correctly iterates through all tracked transports and ensures cleanup, handling both single-server and multi-server configurations appropriately.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9caaff7cac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1023 to +1026
# Multiple servers - create composite with mounted proxies
# Clear any previous transports from prior connections
self._transports = []
timeout = session_kwargs.get("read_timeout_seconds")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Close or reuse prior transports before clearing list

When the same Client instance is used across multiple async with client: cycles, connect_session is called again after the previous session ends. In the multi-server path you reset self._transports to an empty list without closing the existing transports, so any prior StdioTransport instances (with keep_alive=True by default) keep their subprocesses running and become unreachable. This leaks processes/connections over repeated connect/disconnect cycles. Consider closing existing transports (or reusing them) before overwriting the list so prior sessions are cleaned up.

Useful? React with 👍 / 👎.

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: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9caaff7 and 2e9ba67.

📒 Files selected for processing (1)
  • src/fastmcp/client/transports.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/client/transports.py
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to tests/**/*.py : Pass FastMCP servers directly to clients for testing without network complexity; only use HTTP transport when explicitly testing network features

Applied to files:

  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types

Applied to files:

  • src/fastmcp/client/transports.py
📚 Learning: 2025-12-25T15:53:07.656Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.656Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/client/transports.py
🧬 Code graph analysis (1)
src/fastmcp/client/transports.py (3)
src/fastmcp/server/context.py (1)
  • fastmcp (169-174)
src/fastmcp/mcp_config.py (9)
  • MCPConfig (247-298)
  • RemoteMCPServer (175-233)
  • StdioMCPServer (126-168)
  • TransformingRemoteMCPServer (236-237)
  • TransformingStdioMCPServer (171-172)
  • infer_transport_type_from_url (56-73)
  • to_transport (119-123)
  • to_transport (159-168)
  • to_transport (211-233)
src/fastmcp/server/providers/proxy.py (1)
  • ProxyClient (703-737)
🪛 Ruff (0.14.10)
src/fastmcp/client/transports.py

1006-1006: 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). (1)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
🔇 Additional comments (1)
src/fastmcp/client/transports.py (1)

1046-1082: Excellent implementation of timeout propagation fix.

This method correctly addresses the core issue described in the PR: timeout now flows from session_kwargs through to the ProxyClient (line 1074), ensuring all backend servers respect the Client-provided timeout.

The logic properly handles:

  • Transforming servers by calling base class to_transport() to get the raw transport, then applying transformations via tool_transformations parameter
  • Regular servers via direct to_transport() call
  • Tag filtering via include_tags and exclude_tags
  • Circular import prevention by importing ProxyClient locally

if isinstance(config, dict):
config = MCPConfig.from_dict(config)
self.config = config
self.name_as_prefix = name_as_prefix
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add type annotation for name_as_prefix parameter.

The parameter is missing a type annotation. Per coding guidelines, Python ≥ 3.10 requires full type annotations.

🔧 Proposed fix
-    def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True):
+    def __init__(self, config: MCPConfig | dict, name_as_prefix: bool = True) -> None:

Also consider adding the bool type annotation if it's not already inferred:

-        self.name_as_prefix = name_as_prefix
+        self.name_as_prefix: bool = name_as_prefix

Based on coding guidelines requiring full type annotations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.name_as_prefix = name_as_prefix
self.name_as_prefix: bool = name_as_prefix

Comment thread src/fastmcp/client/transports.py Outdated
@jlowin jlowin merged commit 401b315 into main Jan 10, 2026
15 checks passed
@jlowin jlowin deleted the fix/mcp-config-timeout-propagation branch January 10, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCPConfigTransport Timeout Not Applied When Multiple Servers Are Configured

1 participant