diff --git a/docs/servers/composition.mdx b/docs/servers/composition.mdx index 1ed826a83..cdbe23186 100644 --- a/docs/servers/composition.mdx +++ b/docs/servers/composition.mdx @@ -26,9 +26,10 @@ The choice of importing or mounting depends on your use case and requirements. | Feature | Importing | Mounting | |---------|----------------|---------| -| **Method** | `FastMCP.import_server()` | `FastMCP.mount()` | +| **Method** | `FastMCP.import_server(server, prefix=None)` | `FastMCP.mount(server, prefix=None)` | | **Composition Type** | One-time copy (static) | Live link (dynamic) | | **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected | +| **Prefix** | Optional - omit for original names | Optional - omit for original names | | **Best For** | Bundling finalized components | Modular runtime composition | ### Proxy Servers @@ -41,7 +42,7 @@ You can also create proxies from configuration dictionaries that follow the MCPC ## Importing (Static Composition) -The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). A `prefix` is added to avoid naming conflicts. +The `import_server()` method copies all components (tools, resources, templates, prompts) from one `FastMCP` instance (the *subserver*) into another (the *main server*). An optional `prefix` can be provided to avoid naming conflicts. If no prefix is provided, components are imported without modification. When multiple servers are imported with the same prefix (or no prefix), the most recently imported server's components take precedence. ```python from fastmcp import FastMCP @@ -65,7 +66,7 @@ main_mcp = FastMCP(name="MainApp") # Import subserver async def setup(): - await main_mcp.import_server("weather", weather_mcp) + await main_mcp.import_server(weather_mcp, prefix="weather") # Result: main_mcp now contains prefixed components: # - Tool: "weather_get_forecast" @@ -78,7 +79,7 @@ if __name__ == "__main__": ### How Importing Works -When you call `await main_mcp.import_server(prefix, subserver)`: +When you call `await main_mcp.import_server(subserver, prefix={whatever})`: 1. **Tools**: All tools from `subserver` are added to `main_mcp` with names prefixed using `{prefix}_`. - `subserver.tool(name="my_tool")` becomes `main_mcp.tool(name="{prefix}_my_tool")`. @@ -91,9 +92,63 @@ When you call `await main_mcp.import_server(prefix, subserver)`: Note that `import_server` performs a **one-time copy** of components. Changes made to the `subserver` *after* importing **will not** be reflected in `main_mcp`. The `subserver`'s `lifespan` context is also **not** executed by the main server. + +The `prefix` parameter is optional. If omitted, components are imported without modification. + + +#### Importing Without Prefixes + + + +You can also import servers without specifying a prefix, which copies components using their original names: + +```python + +from fastmcp import FastMCP +import asyncio + +# Define subservers +weather_mcp = FastMCP(name="WeatherService") + +@weather_mcp.tool +def get_forecast(city: str) -> dict: + """Get weather forecast.""" + return {"city": city, "forecast": "Sunny"} + +@weather_mcp.resource("data://cities/supported") +def list_supported_cities() -> list[str]: + """List cities with weather support.""" + return ["London", "Paris", "Tokyo"] + +# Define main server +main_mcp = FastMCP(name="MainApp") + +# Import subserver +async def setup(): + # Import without prefix - components keep original names + await main_mcp.import_server(weather_mcp) + +# Result: main_mcp now contains: +# - Tool: "get_forecast" (original name preserved) +# - Resource: "data://cities/supported" (original URI preserved) + +if __name__ == "__main__": + asyncio.run(setup()) + main_mcp.run() +``` + +#### Conflict Resolution + + + +When importing multiple servers with the same prefix, or no prefix, components from the **most recently imported** server take precedence. + + + + ## Mounting (Live Linking) -The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the `prefix` are **delegated** to the `subserver` at runtime. +The `mount()` method creates a **live link** between the `main_mcp` server and the `subserver`. Instead of copying components, requests for components matching the optional `prefix` are **delegated** to the `subserver` at runtime. If no prefix is provided, the subserver's components are accessible without prefixing. When multiple servers are mounted with the same prefix (or no prefix), the most recently mounted server takes precedence for conflicting component names. ```python import asyncio @@ -109,7 +164,7 @@ def initial_tool(): # Mount subserver (synchronous operation) main_mcp = FastMCP(name="MainAppLive") -main_mcp.mount("dynamic", dynamic_mcp) +main_mcp.mount(dynamic_mcp, prefix="dynamic") # Add a tool AFTER mounting - it will be accessible through main_mcp @dynamic_mcp.tool @@ -143,6 +198,20 @@ When mounting is configured: The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts. + + The `prefix` parameter is optional. If omitted, components are mounted without modification. + + + +#### Mounting Without Prefixes + + + +You can also mount servers without specifying a prefix, which makes components accessible without prefixing. This works identically to [importing without prefixes](#importing-without-prefixes), including [conflict resolution](#conflict-resolution). + + + + ### Direct vs. Proxy Mounting @@ -161,10 +230,13 @@ FastMCP supports two mounting modes: ```python # Direct mounting (default when no custom lifespan) -main_mcp.mount("api", api_server) +main_mcp.mount(api_server, prefix="api") # Proxy mounting (preserves full client lifecycle) -main_mcp.mount("api", api_server, as_proxy=True) +main_mcp.mount(api_server, prefix="api", as_proxy=True) + +# Mounting without a prefix (components accessible without prefixing) +main_mcp.mount(api_server) ``` FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter. @@ -178,7 +250,7 @@ When using `FastMCP.as_proxy()` to create a proxy server, mounting that server w remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp")) # Mount the proxy (always uses proxy mounting) -main_server.mount("remote", remote_proxy) +main_server.mount(remote_proxy, prefix="remote") ```