Refactor MountedProvider into FastMCPProvider + TransformingProvider#2653
Refactor MountedProvider into FastMCPProvider + TransformingProvider#2653
Conversation
Split the monolithic MountedProvider into two focused components: - FastMCPProvider: wraps a FastMCP server as a provider - TransformingProvider: applies namespace/rename transformations to any provider Add with_transforms() method to Provider base class for fluent API. Rename mount() prefix parameter to namespace (deprecate prefix).
WalkthroughThis change removes the MountedProvider and replaces it with two new providers: FastMCPProvider (wraps a FastMCP server and exposes tools, resources, prompts, templates, tasks, and lifespan via the Provider interface) and TransformingProvider (wraps any Provider to apply namespace prefixes and explicit tool renames, including reverse lookups). Provider gains with_transforms() and with_namespace() helpers for composing transforms. Server.mount() is updated to accept a namespace parameter (keeps prefix for backward compatibility with deprecation warnings) and now constructs FastMCPProvider plus optional transforms. Package exports are updated to expose FastMCPProvider and TransformingProvider. Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Organization UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (1)src/fastmcp/**/*.py📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧬 Code graph analysis (1)src/fastmcp/server/server.py (2)
🪛 Ruff (0.14.8)src/fastmcp/server/server.py2700-2700: 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). (4)
🔇 Additional comments (4)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/fastmcp/server/server.py (1)
2774-2780: Consider reusing the existing compiled regex.The
add_resource_prefixfunction usesre.match(r"^([^:]+://)(.*?)$", uri)which duplicates the pattern already compiled at module level asURI_PATTERN(line 119). Consider reusing it:🔎 Suggested optimization
def add_resource_prefix(uri: str, prefix: str) -> str: """Add prefix to resource URI: protocol://path → protocol://prefix/path.""" - match = re.match(r"^([^:]+://)(.*?)$", uri) + match = URI_PATTERN.match(uri) if match: protocol, path = match.groups() return f"{protocol}{prefix}/{path}" return uri
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (5)
tests/server/providers/__init__.pyis excluded by none and included by nonetests/server/providers/test_fastmcp_provider.pyis excluded by none and included by nonetests/server/providers/test_transforming_provider.pyis excluded by none and included by nonetests/server/test_mount.pyis excluded by none and included by nonetests/server/test_server.pyis excluded by none and included by none
📒 Files selected for processing (7)
src/fastmcp/contrib/component_manager/component_service.py(7 hunks)src/fastmcp/server/providers/__init__.py(1 hunks)src/fastmcp/server/providers/base.py(1 hunks)src/fastmcp/server/providers/fastmcp_provider.py(1 hunks)src/fastmcp/server/providers/mounted.py(0 hunks)src/fastmcp/server/providers/transforming.py(1 hunks)src/fastmcp/server/server.py(8 hunks)
💤 Files with no reviewable changes (1)
- src/fastmcp/server/providers/mounted.py
🧰 Additional context used
📓 Path-based instructions (2)
src/fastmcp/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/fastmcp/**/*.py: Write Python code with ≥3.10 type annotations required throughout
Never use bare except - be specific with exception types
Files:
src/fastmcp/server/providers/__init__.pysrc/fastmcp/server/providers/base.pysrc/fastmcp/server/providers/fastmcp_provider.pysrc/fastmcp/contrib/component_manager/component_service.pysrc/fastmcp/server/providers/transforming.pysrc/fastmcp/server/server.py
src/fastmcp/**/__init__.py
📄 CodeRabbit inference engine (AGENTS.md)
Be intentional about re-exports; core types that define a module's purpose should be exported; specialized features can live in submodules; only re-export to fastmcp.* for fundamental types
Files:
src/fastmcp/server/providers/__init__.py
🧠 Learnings (1)
📚 Learning: 2025-12-17T03:06:14.522Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-17T03:06:14.522Z
Learning: Applies to src/fastmcp/**/__init__.py : Be intentional about re-exports; core types that define a module's purpose should be exported; specialized features can live in submodules; only re-export to fastmcp.* for fundamental types
Applied to files:
src/fastmcp/server/providers/__init__.py
🧬 Code graph analysis (2)
src/fastmcp/server/providers/base.py (1)
src/fastmcp/server/providers/transforming.py (1)
TransformingProvider(31-349)
src/fastmcp/contrib/component_manager/component_service.py (1)
src/fastmcp/server/providers/transforming.py (2)
_reverse_resource_uri(154-165)_reverse_tool_name(108-119)
🪛 Ruff (0.14.8)
src/fastmcp/server/providers/transforming.py
86-89: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/server/server.py
2700-2700: 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). (4)
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (22)
src/fastmcp/server/providers/__init__.py (1)
28-36: LGTM!The updated exports correctly reflect the new provider architecture.
FastMCPProviderandTransformingProviderare the core types that define the module's purpose and are appropriately exported.ComponentsandTaskComponentsremain available inbase.pyfor specialized use cases, which aligns with the coding guidelines about being intentional with re-exports.src/fastmcp/contrib/component_manager/component_service.py (2)
18-50: LGTM - Clean recursive resolution logic.The helper function correctly handles the provider chain by recursively unwrapping
TransformingProviderlayers until reaching aFastMCPProvider. The approach of using_reverse_tool_namefor both tools and prompts is correct since they share the same namespace transformation pattern (namespace_name).
79-87: Clean provider iteration pattern.The updated logic correctly iterates over providers and uses the helper to resolve the mounted server and unprefixed key. The delegation to
ComponentService(server)ensures proper recursive handling for deeply nested mounts.src/fastmcp/server/providers/base.py (1)
92-158: LGTM - Well-designed fluent API for transformations.The
with_transforms()andwith_namespace()methods provide a clean, chainable interface for applying transformations. Key observations:
- Lazy import of
TransformingProvidercorrectly avoids circular dependencies.- Return type
Providerenables method chaining (e.g.,.with_namespace("a").with_namespace("b")).- Comprehensive docstrings with practical examples clearly document the stacking behavior.
src/fastmcp/server/server.py (2)
2690-2700: Proper deprecation handling for prefix → namespace migration.The logic correctly handles the three cases:
prefixprovided alone: warn and map tonamespacenamespaceprovided: use directly- Both provided: raise
ValueErrorThe error message is appropriately descriptive for debugging. The static analysis hint (TRY003) about long messages outside exception classes is a minor style preference and acceptable here given the clarity it provides.
2717-2723: Clean provider composition.The provider creation correctly separates concerns:
FastMCPProviderwraps the server to expose components via the Provider interfacewith_transforms()is applied only when namespace or tool_renames are providedThis enables mounting without transformation when neither namespace nor tool_names is specified.
src/fastmcp/server/providers/fastmcp_provider.py (4)
75-88: LGTM - Tool methods correctly delegate to middleware.The implementation properly invokes the mounted server's middleware chain for tool operations.
list_tools()andcall_tool()use the middleware-wrapped methods, ensuring middleware is applied for execution.
103-130: Correct provider semantics for resource access.The
read_resourcemethod correctly catchesNotFoundErrorand returnsNone, adhering to the provider contract whereNonesignals "not found" and allows the server to try other providers. Theread_resource_templatedelegation toread_resourceis appropriate since the server's middleware handles template resolution internally.
155-208: LGTM - Task discovery with correct manager access pattern.The implementation correctly:
- Accesses managers directly (bypassing middleware) for registration efficiency
- Filters by
task_config.mode != "forbidden"consistently with the server's Docket registration- Recursively collects tasks from nested providers to support deeply mounted servers
214-223: Correct lifespan scoping.The lifespan correctly invokes only the user-defined lifespan of the wrapped server, not the full
_lifespan_manager(). This is the right approach because:
- The parent server's Docket handles all background task registration
- Starting a nested Docket/Worker would cause conflicts
The comment clearly documents this intentional design decision.
src/fastmcp/server/providers/transforming.py (12)
1-25: LGTM!Imports are well-organized with appropriate use of
TYPE_CHECKINGto avoid circular imports for type-only references.
27-28: LGTM!The URI pattern correctly captures the protocol prefix and path components for namespace insertion.
61-92: LGTM!The initialization logic correctly validates that
tool_renameshas no duplicate target names (ensuring reversibility) and builds the reverse lookup map efficiently. The detailed error message identifying both conflicting source names is helpful for debugging.
98-119: LGTM!The tool name transformation logic correctly prioritizes explicit renames over namespace prefixing, and the reverse lookup properly handles both cases.
125-138: LGTM!Prompt name transformation is intentionally simpler than tools (namespace-only, no explicit renames), and the implementation is consistent with that design.
144-165: Consider: Asymmetric handling of URIs that don't match the pattern.When a URI doesn't match
protocol://path:
_transform_resource_urireturns it unchanged (line 152)_reverse_resource_urireturnsNone(line 165)This asymmetry could cause issues: a resource with a non-standard URI would be listed (unchanged) but then be unretrievable via
get_resource/read_resourcebecause the reverse lookup returnsNone.If non-standard URIs are expected to be passed through, consider returning
uriin the reverse case as well. If they should be rejected, consider raising or returningNonein the transform case too.
171-196: LGTM!Tool methods correctly apply transformations on listing and reverse them on lookup/call. Using
model_copyensures the original objects aren't mutated.
202-225: LGTM!Resource methods follow the same transformation pattern as tools. The
str(r.uri)conversion handles URI types correctly.
231-260: LGTM!Resource template methods correctly transform
uri_templatefields. The pattern matches other resource handling.
266-291: LGTM!Prompt methods follow the established transformation pattern consistently.
297-339: LGTM!The
get_tasksmethod correctly applies the same transformations as the individuallist_*methods to all component types. The# type: ignore[arg-type]comments are reasonable givenmodel_copy's return type limitations.
345-349: LGTM!Lifecycle correctly delegates to the wrapped provider using async context manager composition.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/fastmcp/server/server.py (1)
638-700: Clean deprecation path for prefix → namespace migration.The deprecation handling is well-implemented with proper warnings and clear migration guidance. The logic correctly maps
prefixtonamespacewhen onlyprefixis provided, and prevents ambiguous usage when both are specified.One minor static analysis concern at line 2700: Consider extracting the long error message to improve readability:
🔎 Optional refactor for error message
+NAMESPACE_PREFIX_CONFLICT_MSG = "Cannot specify both 'prefix' and 'namespace'" + def mount( self, server: FastMCP[LifespanResultT], namespace: str | None = None, as_proxy: bool | None = None, tool_names: dict[str, str] | None = None, prefix: str | None = None, ) -> None: ... if namespace is None: namespace = prefix else: - raise ValueError("Cannot specify both 'prefix' and 'namespace'") + raise ValueError(NAMESPACE_PREFIX_CONFLICT_MSG)
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/fastmcp/server/server.py(8 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/fastmcp/**/*.py: Write Python code with ≥3.10 type annotations required throughout
Never use bare except - be specific with exception types
Files:
src/fastmcp/server/server.py
🧬 Code graph analysis (1)
src/fastmcp/server/server.py (2)
src/fastmcp/server/providers/fastmcp_provider.py (1)
FastMCPProvider(28-223)src/fastmcp/server/providers/base.py (2)
Provider(72-384)with_transforms(92-141)
🪛 Ruff (0.14.8)
src/fastmcp/server/server.py
2700-2700: 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). (4)
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (4)
src/fastmcp/server/server.py (4)
852-852: LGTM! Comment updates align with the provider refactor.The comments correctly reference
FastMCPProviderinstead of the removedMountedProvider, maintaining documentation accuracy.Also applies to: 942-942, 982-982, 1022-1022, 1112-1112
646-685: Excellent documentation of the namespace-based mounting approach.The docstring comprehensively explains how namespaces affect tools, resources, templates, and prompts with clear examples. The migration guidance from
prefixtonamespaceis well-documented.
688-688: Well-structured provider composition using FastMCPProvider + transformations.The implementation correctly:
- Creates a
FastMCPProviderto wrap the mounted server- Applies optional transformations via
with_transforms()for namespace and tool renames- Composes providers cleanly without complex conditional logic
This aligns perfectly with the PR's goal of splitting monolithic
MountedProviderinto focused, composable components.Also applies to: 717-723
2773-2779: Helper function correctly maintains backward compatibility.The local
add_resource_prefixhelper properly transforms resource URIs for the deprecatedimport_serverflow, using the existingURI_PATTERNregex to parse and reconstruct URIs with the prefix as a path segment.
Splits the monolithic
MountedProviderinto two focused, composable components:FastMCPProvider: Wraps a FastMCP server, exposing its components through the Provider interfaceTransformingProvider: Wraps any provider to apply namespace prefixes and tool renamesThis separation enables cleaner composition and makes the transformation logic reusable across any provider type.
New API
Providers now support a fluent
with_transforms()method:Breaking Changes
MountedProvideris removed (replaced byFastMCPProvider+TransformingProvider)mount(prefix=...)is deprecated in favor ofmount(namespace=...)