--- title: Server Composition sidebarTitle: Composition description: Combine multiple FastMCP servers into a single, larger application using mounting and importing. icon: puzzle-piece --- import { VersionBadge } from '/snippets/version-badge.mdx' As your MCP applications grow, you might want to organize your tools, resources, and prompts into logical modules or reuse existing server components. FastMCP supports composition through two methods: - **`import_server`**: For a one-time copy of components with prefixing (static composition). - **`mount`**: For creating a live link where the main server delegates requests to the subserver (dynamic composition). ## Why Compose Servers? - **Modularity**: Break down large applications into smaller, focused servers (e.g., a `WeatherServer`, a `DatabaseServer`, a `CalendarServer`). - **Reusability**: Create common utility servers (e.g., a `TextProcessingServer`) and mount them wherever needed. - **Teamwork**: Different teams can work on separate FastMCP servers that are later combined. - **Organization**: Keep related functionality grouped together logically. ### Importing vs Mounting The choice of importing or mounting depends on your use case and requirements. | Feature | Importing | Mounting | |---------|----------------|---------| | **Method** | `FastMCP.import_server()` | `FastMCP.mount()` | | **Composition Type** | One-time copy (static) | Live link (dynamic) | | **Updates** | Changes to subserver NOT reflected | Changes to subserver immediately reflected | | **Best For** | Bundling finalized components | Modular runtime composition | ### Proxy Servers FastMCP supports [MCP proxying](/patterns/proxy), which allows you to mirror a local or remote server in a local FastMCP instance. Proxies are fully compatible with both importing and mounting. ## 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. ```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(): await main_mcp.import_server("weather", weather_mcp) # Result: main_mcp now contains prefixed components: # - Tool: "weather_get_forecast" # - Resource: "weather+data://cities/supported" if __name__ == "__main__": asyncio.run(setup()) main_mcp.run() ``` ### How Importing Works When you call `await main_mcp.import_server(prefix, subserver)`: 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")`. 2. **Resources**: All resources are added with URIs prefixed using `{prefix}+`. - `subserver.resource(uri="data://info")` becomes `main_mcp.resource(uri="{prefix}+data://info")`. 3. **Resource Templates**: Templates are prefixed similarly to resources. - `subserver.resource(uri="data://{id}")` becomes `main_mcp.resource(uri="{prefix}+data://{id}")`. 4. **Prompts**: All prompts are added with names prefixed like tools. - `subserver.prompt(name="my_prompt")` becomes `main_mcp.prompt(name="{prefix}_my_prompt")`. 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. ## 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. ```python import asyncio from fastmcp import FastMCP, Client # Define subserver dynamic_mcp = FastMCP(name="DynamicService") @dynamic_mcp.tool() def initial_tool(): """Initial tool demonstration.""" return "Initial Tool Exists" # Mount subserver (synchronous operation) main_mcp = FastMCP(name="MainAppLive") main_mcp.mount("dynamic", dynamic_mcp) # Add a tool AFTER mounting - it will be accessible through main_mcp @dynamic_mcp.tool() def added_later(): """Tool added after mounting.""" return "Tool Added Dynamically!" # Testing access to mounted tools async def test_dynamic_mount(): tools = await main_mcp.get_tools() print("Available tools:", list(tools.keys())) # Shows: ['dynamic_initial_tool', 'dynamic_added_later'] async with Client(main_mcp) as client: result = await client.call_tool("dynamic_added_later") print("Result:", result[0].text) # Shows: "Tool Added Dynamically!" if __name__ == "__main__": asyncio.run(test_dynamic_mount()) ``` ### How Mounting Works When mounting is configured: 1. **Live Link**: The parent server establishes a connection to the mounted server. 2. **Dynamic Updates**: Changes to the mounted server are immediately reflected when accessed through the parent. 3. **Prefixed Access**: The parent server uses prefixes to route requests to the mounted server. 4. **Delegation**: Requests for components matching the prefix are delegated to the mounted server at runtime. The same prefixing rules apply as with `import_server` for naming tools, resources, templates, and prompts. ### Direct vs. Proxy Mounting FastMCP supports two mounting modes: 1. **Direct Mounting** (default): The parent server directly accesses the mounted server's objects in memory. - No client lifecycle events occur on the mounted server - The mounted server's lifespan context is not executed - Communication is handled through direct method calls 2. **Proxy Mounting**: The parent server treats the mounted server as a separate entity and communicates with it through a client interface. - Full client lifecycle events occur on the mounted server - The mounted server's lifespan is executed when a client connects - Communication happens via an in-memory Client transport ```python # Direct mounting (default when no custom lifespan) main_mcp.mount("api", api_server) # Proxy mounting (preserves full client lifecycle) main_mcp.mount("api", api_server, as_proxy=True) ``` FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan, but you can override this behavior with the `as_proxy` parameter. #### Interaction with Proxy Servers When using `FastMCP.as_proxy()` to create a proxy server, mounting that server will always use proxy mounting: ```python # Create a proxy for a remote server remote_proxy = FastMCP.as_proxy(Client("http://example.com/mcp")) # Mount the proxy (always uses proxy mounting) main_server.mount("remote", remote_proxy) ``` ## Customizing Separators Both `import_server()` and `mount()` allow you to customize the separators used for prefixing components. The defaults are `_` for tools and prompts, and `+` for resources. ```python import_server await main_mcp.import_server( prefix="api", app=some_subserver, tool_separator="_", # Tool name becomes: "api_sub_tool_name" resource_separator="+", # Resource URI becomes: "api+data://sub_resource" prompt_separator="_" # Prompt name becomes: "api_sub_prompt_name" ) ``` ```python mount main_mcp.mount( prefix="api", app=some_subserver, tool_separator="_", # Tool name becomes: "api_sub_tool_name" resource_separator="+", # Resource URI becomes: "api+data://sub_resource" prompt_separator="_" # Prompt name becomes: "api_sub_prompt_name" ) ``` Be cautious when choosing separators. Some MCP clients (like Claude Desktop) might have restrictions on characters allowed in tool names (e.g., `/` might not be supported). The defaults (`_` for names, `+` for URIs) are generally safe. To "cleanly" import or mount a server, set the prefix and all separators to `""` (empty string). This is generally unecessary but could save a couple tokens at the risk of a name collision!