Jeremiah Lowin Jeremiah Lowin marvin-context-protocol[bot] commited on
Commit
125f50b
·
unverified ·
1 Parent(s): b8a5dcf

Support importing custom_route endpoints when mounting servers (#1470)

Browse files

Co-authored-by: Jeremiah Lowin <jlowin@users.noreply.github.com>
Co-authored-by: marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>

docs/python-sdk/fastmcp-server-server.mdx CHANGED
@@ -26,7 +26,7 @@ Default lifespan context manager that does nothing.
26
  - An empty context object
27
 
28
 
29
- ### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2162" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
30
 
31
  ```python
32
  add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
@@ -64,7 +64,7 @@ add_resource_prefix("resource:///absolute/path", "prefix")
64
  - `ValueError`: If the URI doesn't match the expected protocol\://path format
65
 
66
 
67
- ### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2222" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
68
 
69
  ```python
70
  remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
@@ -103,7 +103,7 @@ remove_resource_prefix("resource://prefix//absolute/path", "prefix")
103
  - `ValueError`: If the URI doesn't match the expected protocol\://path format
104
 
105
 
106
- ### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
107
 
108
  ```python
109
  has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
@@ -147,31 +147,31 @@ False
147
 
148
  **Methods:**
149
 
150
- #### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L288" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
151
 
152
  ```python
153
  settings(self) -> Settings
154
  ```
155
 
156
- #### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L299" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
157
 
158
  ```python
159
  name(self) -> str
160
  ```
161
 
162
- #### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L303" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
163
 
164
  ```python
165
  instructions(self) -> str | None
166
  ```
167
 
168
- #### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L307" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
169
 
170
  ```python
171
  version(self) -> str | None
172
  ```
173
 
174
- #### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L310" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
175
 
176
  ```python
177
  run_async(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
@@ -183,7 +183,7 @@ Run the FastMCP server asynchronously.
183
  - `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
184
 
185
 
186
- #### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L340" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
187
 
188
  ```python
189
  run(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
@@ -195,13 +195,13 @@ Run the FastMCP server. Note this is a synchronous function.
195
  - `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
196
 
197
 
198
- #### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L382" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
199
 
200
  ```python
201
  add_middleware(self, middleware: Middleware) -> None
202
  ```
203
 
204
- #### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L385" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
205
 
206
  ```python
207
  get_tools(self) -> dict[str, Tool]
@@ -210,13 +210,13 @@ get_tools(self) -> dict[str, Tool]
210
  Get all registered tools, indexed by registered key.
211
 
212
 
213
- #### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L389" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
214
 
215
  ```python
216
  get_tool(self, key: str) -> Tool
217
  ```
218
 
219
- #### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L395" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
220
 
221
  ```python
222
  get_resources(self) -> dict[str, Resource]
@@ -225,13 +225,13 @@ get_resources(self) -> dict[str, Resource]
225
  Get all registered resources, indexed by registered key.
226
 
227
 
228
- #### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L399" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
229
 
230
  ```python
231
  get_resource(self, key: str) -> Resource
232
  ```
233
 
234
- #### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L405" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
235
 
236
  ```python
237
  get_resource_templates(self) -> dict[str, ResourceTemplate]
@@ -240,7 +240,7 @@ get_resource_templates(self) -> dict[str, ResourceTemplate]
240
  Get all registered resource templates, indexed by registered key.
241
 
242
 
243
- #### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L409" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
244
 
245
  ```python
246
  get_resource_template(self, key: str) -> ResourceTemplate
@@ -249,7 +249,7 @@ get_resource_template(self, key: str) -> ResourceTemplate
249
  Get a registered resource template by key.
250
 
251
 
252
- #### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L416" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
253
 
254
  ```python
255
  get_prompts(self) -> dict[str, Prompt]
@@ -258,13 +258,13 @@ get_prompts(self) -> dict[str, Prompt]
258
  List all available prompts.
259
 
260
 
261
- #### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L422" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
262
 
263
  ```python
264
  get_prompt(self, key: str) -> Prompt
265
  ```
266
 
267
- #### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L428" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
268
 
269
  ```python
270
  custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) -> Callable[[Callable[[Request], Awaitable[Response]]], Callable[[Request], Awaitable[Response]]]
@@ -285,7 +285,7 @@ Starlette's reverse URL lookup feature)
285
  - `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
286
 
287
 
288
- #### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L808" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
289
 
290
  ```python
291
  add_tool(self, tool: Tool) -> Tool
@@ -303,7 +303,7 @@ with the Context type annotation. See the @tool decorator for examples.
303
  - The tool instance that was added to the server.
304
 
305
 
306
- #### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L833" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
307
 
308
  ```python
309
  remove_tool(self, name: str) -> None
@@ -318,7 +318,7 @@ Remove a tool from the server.
318
  - `NotFoundError`: If the tool is not found
319
 
320
 
321
- #### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L853" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
322
 
323
  ```python
324
  add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
@@ -327,7 +327,7 @@ add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfi
327
  Add a tool transformation.
328
 
329
 
330
- #### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L859" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
331
 
332
  ```python
333
  remove_tool_transformation(self, tool_name: str) -> None
@@ -336,19 +336,19 @@ remove_tool_transformation(self, tool_name: str) -> None
336
  Remove a tool transformation.
337
 
338
 
339
- #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L864" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
340
 
341
  ```python
342
  tool(self, name_or_fn: AnyFunction) -> FunctionTool
343
  ```
344
 
345
- #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L880" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
346
 
347
  ```python
348
  tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
349
  ```
350
 
351
- #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L895" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
352
 
353
  ```python
354
  tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
@@ -404,7 +404,7 @@ server.tool(my_function, name="custom_name")
404
  ```
405
 
406
 
407
- #### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1026" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
408
 
409
  ```python
410
  add_resource(self, resource: Resource) -> Resource
@@ -419,7 +419,7 @@ Add a resource to the server.
419
  - The resource instance that was added to the server.
420
 
421
 
422
- #### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1048" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
423
 
424
  ```python
425
  add_template(self, template: ResourceTemplate) -> ResourceTemplate
@@ -434,7 +434,7 @@ Add a resource template to the server.
434
  - The template instance that was added to the server.
435
 
436
 
437
- #### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1070" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
438
 
439
  ```python
440
  add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
@@ -454,7 +454,7 @@ has parameters, it will be registered as a template resource.
454
  - `tags`: Optional set of tags for categorizing the resource
455
 
456
 
457
- #### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1108" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
458
 
459
  ```python
460
  resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
@@ -514,7 +514,7 @@ async def get_weather(city: str) -> str:
514
  ```
515
 
516
 
517
- #### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1245" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
518
 
519
  ```python
520
  add_prompt(self, prompt: Prompt) -> Prompt
@@ -529,19 +529,19 @@ Add a prompt to the server.
529
  - The prompt instance that was added to the server.
530
 
531
 
532
- #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1268" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
533
 
534
  ```python
535
  prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
536
  ```
537
 
538
- #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1281" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
539
 
540
  ```python
541
  prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
542
  ```
543
 
544
- #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1293" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
545
 
546
  ```python
547
  prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
@@ -619,7 +619,7 @@ Decorator to register a prompt.
619
  ```
620
 
621
 
622
- #### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1434" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
623
 
624
  ```python
625
  run_stdio_async(self, show_banner: bool = True) -> None
@@ -628,7 +628,7 @@ run_stdio_async(self, show_banner: bool = True) -> None
628
  Run the server using stdio transport.
629
 
630
 
631
- #### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1454" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
632
 
633
  ```python
634
  run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, stateless_http: bool | None = None) -> None
@@ -647,7 +647,7 @@ Run the server using HTTP transport.
647
  - `stateless_http`: Whether to use stateless HTTP (defaults to settings.stateless_http)
648
 
649
 
650
- #### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1528" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
651
 
652
  ```python
653
  run_sse_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
@@ -656,7 +656,7 @@ run_sse_async(self, host: str | None = None, port: int | None = None, log_level:
656
  Run the server using SSE transport.
657
 
658
 
659
- #### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1556" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
660
 
661
  ```python
662
  sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
@@ -670,7 +670,7 @@ Create a Starlette app for the SSE server.
670
  - `middleware`: A list of middleware to apply to the app
671
 
672
 
673
- #### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1587" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
674
 
675
  ```python
676
  streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
@@ -683,7 +683,7 @@ Create a Starlette app for the StreamableHTTP server.
683
  - `middleware`: A list of middleware to apply to the app
684
 
685
 
686
- #### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1608" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
687
 
688
  ```python
689
  http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
@@ -700,13 +700,13 @@ Create a Starlette app using the specified HTTP transport.
700
  - A Starlette application configured with the specified transport
701
 
702
 
703
- #### `run_streamable_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1657" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
704
 
705
  ```python
706
  run_streamable_http_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
707
  ```
708
 
709
- #### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1682" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
710
 
711
  ```python
712
  mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
@@ -760,7 +760,7 @@ automatically determined based on whether the server has a custom lifespan
760
  - `prompt_separator`: Deprecated. Separator character for prompt names.
761
 
762
 
763
- #### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1803" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
764
 
765
  ```python
766
  import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None, tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None) -> None
@@ -801,7 +801,7 @@ applied using the protocol\://prefix/path format
801
  - `prompt_separator`: Deprecated. Separator for prompt names.
802
 
803
 
804
- #### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1930" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
805
 
806
  ```python
807
  from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
@@ -810,7 +810,7 @@ from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route
810
  Create a FastMCP server from an OpenAPI specification.
811
 
812
 
813
- #### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1979" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
814
 
815
  ```python
816
  from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
@@ -819,7 +819,7 @@ from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap]
819
  Create a FastMCP server from a FastAPI application.
820
 
821
 
822
- #### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2042" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
823
 
824
  ```python
825
  as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
@@ -833,7 +833,7 @@ instance or any value accepted as the `transport` argument of
833
  `fastmcp.client.Client` constructor.
834
 
835
 
836
- #### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
837
 
838
  ```python
839
  from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
@@ -842,4 +842,4 @@ from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPPr
842
  Create a FastMCP proxy server from a FastMCP client.
843
 
844
 
845
- ### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2156" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
 
26
  - An empty context object
27
 
28
 
29
+ ### `add_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2182" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
30
 
31
  ```python
32
  add_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
 
64
  - `ValueError`: If the URI doesn't match the expected protocol\://path format
65
 
66
 
67
+ ### `remove_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2242" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
68
 
69
  ```python
70
  remove_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> str
 
103
  - `ValueError`: If the URI doesn't match the expected protocol\://path format
104
 
105
 
106
+ ### `has_resource_prefix` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2309" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
107
 
108
  ```python
109
  has_resource_prefix(uri: str, prefix: str, prefix_format: Literal['protocol', 'path'] | None = None) -> bool
 
147
 
148
  **Methods:**
149
 
150
+ #### `settings` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L289" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
151
 
152
  ```python
153
  settings(self) -> Settings
154
  ```
155
 
156
+ #### `name` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
157
 
158
  ```python
159
  name(self) -> str
160
  ```
161
 
162
+ #### `instructions` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L304" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
163
 
164
  ```python
165
  instructions(self) -> str | None
166
  ```
167
 
168
+ #### `version` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L308" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
169
 
170
  ```python
171
  version(self) -> str | None
172
  ```
173
 
174
+ #### `run_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L311" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
175
 
176
  ```python
177
  run_async(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
 
183
  - `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
184
 
185
 
186
+ #### `run` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L341" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
187
 
188
  ```python
189
  run(self, transport: Transport | None = None, show_banner: bool = True, **transport_kwargs: Any) -> None
 
195
  - `transport`: Transport protocol to use ("stdio", "sse", or "streamable-http")
196
 
197
 
198
+ #### `add_middleware` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L383" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
199
 
200
  ```python
201
  add_middleware(self, middleware: Middleware) -> None
202
  ```
203
 
204
+ #### `get_tools` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L386" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
205
 
206
  ```python
207
  get_tools(self) -> dict[str, Tool]
 
210
  Get all registered tools, indexed by registered key.
211
 
212
 
213
+ #### `get_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L390" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
214
 
215
  ```python
216
  get_tool(self, key: str) -> Tool
217
  ```
218
 
219
+ #### `get_resources` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L396" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
220
 
221
  ```python
222
  get_resources(self) -> dict[str, Resource]
 
225
  Get all registered resources, indexed by registered key.
226
 
227
 
228
+ #### `get_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L400" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
229
 
230
  ```python
231
  get_resource(self, key: str) -> Resource
232
  ```
233
 
234
+ #### `get_resource_templates` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L406" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
235
 
236
  ```python
237
  get_resource_templates(self) -> dict[str, ResourceTemplate]
 
240
  Get all registered resource templates, indexed by registered key.
241
 
242
 
243
+ #### `get_resource_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L410" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
244
 
245
  ```python
246
  get_resource_template(self, key: str) -> ResourceTemplate
 
249
  Get a registered resource template by key.
250
 
251
 
252
+ #### `get_prompts` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L417" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
253
 
254
  ```python
255
  get_prompts(self) -> dict[str, Prompt]
 
258
  List all available prompts.
259
 
260
 
261
+ #### `get_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L423" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
262
 
263
  ```python
264
  get_prompt(self, key: str) -> Prompt
265
  ```
266
 
267
+ #### `custom_route` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L429" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
268
 
269
  ```python
270
  custom_route(self, path: str, methods: list[str], name: str | None = None, include_in_schema: bool = True) -> Callable[[Callable[[Request], Awaitable[Response]]], Callable[[Request], Awaitable[Response]]]
 
285
  - `include_in_schema`: Whether to include in OpenAPI schema, defaults to True
286
 
287
 
288
+ #### `add_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L827" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
289
 
290
  ```python
291
  add_tool(self, tool: Tool) -> Tool
 
303
  - The tool instance that was added to the server.
304
 
305
 
306
+ #### `remove_tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L852" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
307
 
308
  ```python
309
  remove_tool(self, name: str) -> None
 
318
  - `NotFoundError`: If the tool is not found
319
 
320
 
321
+ #### `add_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L872" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
322
 
323
  ```python
324
  add_tool_transformation(self, tool_name: str, transformation: ToolTransformConfig) -> None
 
327
  Add a tool transformation.
328
 
329
 
330
+ #### `remove_tool_transformation` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L878" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
331
 
332
  ```python
333
  remove_tool_transformation(self, tool_name: str) -> None
 
336
  Remove a tool transformation.
337
 
338
 
339
+ #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L883" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
340
 
341
  ```python
342
  tool(self, name_or_fn: AnyFunction) -> FunctionTool
343
  ```
344
 
345
+ #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L899" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
346
 
347
  ```python
348
  tool(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionTool]
349
  ```
350
 
351
+ #### `tool` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L914" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
352
 
353
  ```python
354
  tool(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionTool] | FunctionTool
 
404
  ```
405
 
406
 
407
+ #### `add_resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1045" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
408
 
409
  ```python
410
  add_resource(self, resource: Resource) -> Resource
 
419
  - The resource instance that was added to the server.
420
 
421
 
422
+ #### `add_template` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1067" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
423
 
424
  ```python
425
  add_template(self, template: ResourceTemplate) -> ResourceTemplate
 
434
  - The template instance that was added to the server.
435
 
436
 
437
+ #### `add_resource_fn` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1089" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
438
 
439
  ```python
440
  add_resource_fn(self, fn: AnyFunction, uri: str, name: str | None = None, description: str | None = None, mime_type: str | None = None, tags: set[str] | None = None) -> None
 
454
  - `tags`: Optional set of tags for categorizing the resource
455
 
456
 
457
+ #### `resource` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1127" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
458
 
459
  ```python
460
  resource(self, uri: str) -> Callable[[AnyFunction], Resource | ResourceTemplate]
 
514
  ```
515
 
516
 
517
+ #### `add_prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1264" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
518
 
519
  ```python
520
  add_prompt(self, prompt: Prompt) -> Prompt
 
529
  - The prompt instance that was added to the server.
530
 
531
 
532
+ #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1287" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
533
 
534
  ```python
535
  prompt(self, name_or_fn: AnyFunction) -> FunctionPrompt
536
  ```
537
 
538
+ #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1300" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
539
 
540
  ```python
541
  prompt(self, name_or_fn: str | None = None) -> Callable[[AnyFunction], FunctionPrompt]
542
  ```
543
 
544
+ #### `prompt` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1312" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
545
 
546
  ```python
547
  prompt(self, name_or_fn: str | AnyFunction | None = None) -> Callable[[AnyFunction], FunctionPrompt] | FunctionPrompt
 
619
  ```
620
 
621
 
622
+ #### `run_stdio_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1453" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
623
 
624
  ```python
625
  run_stdio_async(self, show_banner: bool = True) -> None
 
628
  Run the server using stdio transport.
629
 
630
 
631
+ #### `run_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1473" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
632
 
633
  ```python
634
  run_http_async(self, show_banner: bool = True, transport: Literal['http', 'streamable-http', 'sse'] = 'http', host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None, middleware: list[ASGIMiddleware] | None = None, stateless_http: bool | None = None) -> None
 
647
  - `stateless_http`: Whether to use stateless HTTP (defaults to settings.stateless_http)
648
 
649
 
650
+ #### `run_sse_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1547" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
651
 
652
  ```python
653
  run_sse_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
 
656
  Run the server using SSE transport.
657
 
658
 
659
+ #### `sse_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1575" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
660
 
661
  ```python
662
  sse_app(self, path: str | None = None, message_path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
 
670
  - `middleware`: A list of middleware to apply to the app
671
 
672
 
673
+ #### `streamable_http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1606" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
674
 
675
  ```python
676
  streamable_http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None) -> StarletteWithLifespan
 
683
  - `middleware`: A list of middleware to apply to the app
684
 
685
 
686
+ #### `http_app` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1627" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
687
 
688
  ```python
689
  http_app(self, path: str | None = None, middleware: list[ASGIMiddleware] | None = None, json_response: bool | None = None, stateless_http: bool | None = None, transport: Literal['http', 'streamable-http', 'sse'] = 'http') -> StarletteWithLifespan
 
700
  - A Starlette application configured with the specified transport
701
 
702
 
703
+ #### `run_streamable_http_async` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1676" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
704
 
705
  ```python
706
  run_streamable_http_async(self, host: str | None = None, port: int | None = None, log_level: str | None = None, path: str | None = None, uvicorn_config: dict[str, Any] | None = None) -> None
707
  ```
708
 
709
+ #### `mount` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1701" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
710
 
711
  ```python
712
  mount(self, server: FastMCP[LifespanResultT], prefix: str | None = None, as_proxy: bool | None = None) -> None
 
760
  - `prompt_separator`: Deprecated. Separator character for prompt names.
761
 
762
 
763
+ #### `import_server` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1823" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
764
 
765
  ```python
766
  import_server(self, server: FastMCP[LifespanResultT], prefix: str | None = None, tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None) -> None
 
801
  - `prompt_separator`: Deprecated. Separator for prompt names.
802
 
803
 
804
+ #### `from_openapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1950" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
805
 
806
  ```python
807
  from_openapi(cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
 
810
  Create a FastMCP server from an OpenAPI specification.
811
 
812
 
813
+ #### `from_fastapi` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L1999" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
814
 
815
  ```python
816
  from_fastapi(cls, app: Any, name: str | None = None, route_maps: list[RouteMap] | list[RouteMapNew] | None = None, route_map_fn: OpenAPIRouteMapFn | OpenAPIRouteMapFnNew | None = None, mcp_component_fn: OpenAPIComponentFn | OpenAPIComponentFnNew | None = None, mcp_names: dict[str, str] | None = None, httpx_client_kwargs: dict[str, Any] | None = None, tags: set[str] | None = None, **settings: Any) -> FastMCPOpenAPI | FastMCPOpenAPINew
 
819
  Create a FastMCP server from a FastAPI application.
820
 
821
 
822
+ #### `as_proxy` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2062" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
823
 
824
  ```python
825
  as_proxy(cls, backend: Client[ClientTransportT] | ClientTransport | FastMCP[Any] | AnyUrl | Path | MCPConfig | dict[str, Any] | str, **settings: Any) -> FastMCPProxy
 
833
  `fastmcp.client.Client` constructor.
834
 
835
 
836
+ #### `from_client` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2123" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
837
 
838
  ```python
839
  from_client(cls, client: Client[ClientTransportT], **settings: Any) -> FastMCPProxy
 
842
  Create a FastMCP proxy server from a FastMCP client.
843
 
844
 
845
+ ### `MountedServer` <sup><a href="https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/server.py#L2176" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>
docs/servers/composition.mdx CHANGED
@@ -310,4 +310,8 @@ server = FastMCP("LegacyServer", resource_prefix_format="protocol")
310
  server = FastMCP("NewServer", resource_prefix_format="path")
311
  ```
312
 
313
- When mounting or importing servers, the prefix format of the parent server is used.
 
 
 
 
 
310
  server = FastMCP("NewServer", resource_prefix_format="path")
311
  ```
312
 
313
+ When mounting or importing servers, the prefix format of the parent server is used.
314
+
315
+ <Note>
316
+ When mounting servers, custom HTTP routes defined with `@server.custom_route()` are also forwarded to the parent server, making them accessible through the parent's HTTP application.
317
+ </Note>
src/fastmcp/server/http.py CHANGED
@@ -229,7 +229,7 @@ def create_sse_app(
229
  # Add custom routes with lowest precedence
230
  if routes:
231
  server_routes.extend(routes)
232
- server_routes.extend(server._additional_http_routes)
233
 
234
  # Add middleware
235
  if middleware:
@@ -331,7 +331,7 @@ def create_streamable_http_app(
331
  # Add custom routes with lowest precedence
332
  if routes:
333
  server_routes.extend(routes)
334
- server_routes.extend(server._additional_http_routes)
335
 
336
  # Add middleware
337
  if middleware:
 
229
  # Add custom routes with lowest precedence
230
  if routes:
231
  server_routes.extend(routes)
232
+ server_routes.extend(server._get_additional_http_routes())
233
 
234
  # Add middleware
235
  if middleware:
 
331
  # Add custom routes with lowest precedence
332
  if routes:
333
  server_routes.extend(routes)
334
+ server_routes.extend(server._get_additional_http_routes())
335
 
336
  # Add middleware
337
  if middleware:
src/fastmcp/server/server.py CHANGED
@@ -172,6 +172,7 @@ class FastMCP(Generic[LifespanResultT]):
172
  )
173
 
174
  self._additional_http_routes: list[BaseRoute] = []
 
175
  self._tool_manager = ToolManager(
176
  duplicate_behavior=on_duplicate_tools,
177
  mask_error_details=mask_error_details,
@@ -475,6 +476,24 @@ class FastMCP(Generic[LifespanResultT]):
475
 
476
  return decorator
477
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
  async def _mcp_list_tools(self) -> list[MCPTool]:
479
  logger.debug("Handler called: list_tools")
480
 
@@ -1796,6 +1815,7 @@ class FastMCP(Generic[LifespanResultT]):
1796
  server=server,
1797
  resource_prefix_format=self.resource_prefix_format,
1798
  )
 
1799
  self._tool_manager.mount(mounted_server)
1800
  self._resource_manager.mount(mounted_server)
1801
  self._prompt_manager.mount(mounted_server)
 
172
  )
173
 
174
  self._additional_http_routes: list[BaseRoute] = []
175
+ self._mounted_servers: list[MountedServer] = []
176
  self._tool_manager = ToolManager(
177
  duplicate_behavior=on_duplicate_tools,
178
  mask_error_details=mask_error_details,
 
476
 
477
  return decorator
478
 
479
+ def _get_additional_http_routes(self) -> list[BaseRoute]:
480
+ """Get all additional HTTP routes including from mounted servers.
481
+
482
+ Returns a list of all custom HTTP routes from this server and
483
+ recursively from all mounted servers.
484
+
485
+ Returns:
486
+ List of Starlette BaseRoute objects
487
+ """
488
+ routes = list(self._additional_http_routes)
489
+
490
+ # Recursively get routes from mounted servers
491
+ for mounted_server in self._mounted_servers:
492
+ mounted_routes = mounted_server.server._get_additional_http_routes()
493
+ routes.extend(mounted_routes)
494
+
495
+ return routes
496
+
497
  async def _mcp_list_tools(self) -> list[MCPTool]:
498
  logger.debug("Handler called: list_tools")
499
 
 
1815
  server=server,
1816
  resource_prefix_format=self.resource_prefix_format,
1817
  )
1818
+ self._mounted_servers.append(mounted_server)
1819
  self._tool_manager.mount(mounted_server)
1820
  self._resource_manager.mount(mounted_server)
1821
  self._prompt_manager.mount(mounted_server)
tests/server/test_mount.py CHANGED
@@ -1019,3 +1019,125 @@ class TestResourceNamePrefixing:
1019
  # The template name should also be prefixed
1020
  template = templates["resource://prefix/user/{user_id}"]
1021
  assert template.name == "prefix_user_template"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1019
  # The template name should also be prefixed
1020
  template = templates["resource://prefix/user/{user_id}"]
1021
  assert template.name == "prefix_user_template"
1022
+
1023
+
1024
+ class TestCustomRouteForwarding:
1025
+ """Test that custom HTTP routes from mounted servers are forwarded."""
1026
+
1027
+ async def test_get_additional_http_routes_empty(self):
1028
+ """Test _get_additional_http_routes returns empty list for server with no routes."""
1029
+ server = FastMCP("TestServer")
1030
+ routes = server._get_additional_http_routes()
1031
+ assert routes == []
1032
+
1033
+ async def test_get_additional_http_routes_with_custom_route(self):
1034
+ """Test _get_additional_http_routes returns server's own routes."""
1035
+ server = FastMCP("TestServer")
1036
+
1037
+ @server.custom_route("/test", methods=["GET"])
1038
+ async def test_route(request):
1039
+ from starlette.responses import JSONResponse
1040
+
1041
+ return JSONResponse({"message": "test"})
1042
+
1043
+ routes = server._get_additional_http_routes()
1044
+ assert len(routes) == 1
1045
+ assert routes[0].path == "/test" # type: ignore[attr-defined]
1046
+
1047
+ async def test_get_additional_http_routes_with_mounted_server(self):
1048
+ """Test _get_additional_http_routes includes routes from mounted servers."""
1049
+ main_server = FastMCP("MainServer")
1050
+ sub_server = FastMCP("SubServer")
1051
+
1052
+ @sub_server.custom_route("/sub-route", methods=["GET"])
1053
+ async def sub_route(request):
1054
+ from starlette.responses import JSONResponse
1055
+
1056
+ return JSONResponse({"message": "from sub"})
1057
+
1058
+ # Mount the sub server
1059
+ main_server.mount(sub_server, "sub")
1060
+
1061
+ routes = main_server._get_additional_http_routes()
1062
+ assert len(routes) == 1
1063
+ assert routes[0].path == "/sub-route" # type: ignore[attr-defined]
1064
+
1065
+ async def test_get_additional_http_routes_recursive(self):
1066
+ """Test _get_additional_http_routes works recursively with nested mounts."""
1067
+ main_server = FastMCP("MainServer")
1068
+ sub_server = FastMCP("SubServer")
1069
+ nested_server = FastMCP("NestedServer")
1070
+
1071
+ @main_server.custom_route("/main-route", methods=["GET"])
1072
+ async def main_route(request):
1073
+ from starlette.responses import JSONResponse
1074
+
1075
+ return JSONResponse({"message": "from main"})
1076
+
1077
+ @sub_server.custom_route("/sub-route", methods=["GET"])
1078
+ async def sub_route(request):
1079
+ from starlette.responses import JSONResponse
1080
+
1081
+ return JSONResponse({"message": "from sub"})
1082
+
1083
+ @nested_server.custom_route("/nested-route", methods=["GET"])
1084
+ async def nested_route(request):
1085
+ from starlette.responses import JSONResponse
1086
+
1087
+ return JSONResponse({"message": "from nested"})
1088
+
1089
+ # Create nested mounting: main -> sub -> nested
1090
+ sub_server.mount(nested_server, "nested")
1091
+ main_server.mount(sub_server, "sub")
1092
+
1093
+ routes = main_server._get_additional_http_routes()
1094
+
1095
+ # Should include all routes
1096
+ assert len(routes) == 3
1097
+ route_paths = [route.path for route in routes] # type: ignore[attr-defined]
1098
+ assert "/main-route" in route_paths
1099
+ assert "/sub-route" in route_paths
1100
+ assert "/nested-route" in route_paths
1101
+
1102
+ async def test_mounted_servers_tracking(self):
1103
+ """Test that _mounted_servers list tracks mounted servers correctly."""
1104
+ main_server = FastMCP("MainServer")
1105
+ sub_server1 = FastMCP("SubServer1")
1106
+ sub_server2 = FastMCP("SubServer2")
1107
+
1108
+ # Initially no mounted servers
1109
+ assert len(main_server._mounted_servers) == 0
1110
+
1111
+ # Mount first server
1112
+ main_server.mount(sub_server1, "sub1")
1113
+ assert len(main_server._mounted_servers) == 1
1114
+ assert main_server._mounted_servers[0].server == sub_server1
1115
+ assert main_server._mounted_servers[0].prefix == "sub1"
1116
+
1117
+ # Mount second server
1118
+ main_server.mount(sub_server2, "sub2")
1119
+ assert len(main_server._mounted_servers) == 2
1120
+ assert main_server._mounted_servers[1].server == sub_server2
1121
+ assert main_server._mounted_servers[1].prefix == "sub2"
1122
+
1123
+ async def test_multiple_routes_same_server(self):
1124
+ """Test that multiple custom routes from same server are all included."""
1125
+ server = FastMCP("TestServer")
1126
+
1127
+ @server.custom_route("/route1", methods=["GET"])
1128
+ async def route1(request):
1129
+ from starlette.responses import JSONResponse
1130
+
1131
+ return JSONResponse({"message": "route1"})
1132
+
1133
+ @server.custom_route("/route2", methods=["POST"])
1134
+ async def route2(request):
1135
+ from starlette.responses import JSONResponse
1136
+
1137
+ return JSONResponse({"message": "route2"})
1138
+
1139
+ routes = server._get_additional_http_routes()
1140
+ assert len(routes) == 2
1141
+ route_paths = [route.path for route in routes] # type: ignore[attr-defined]
1142
+ assert "/route1" in route_paths
1143
+ assert "/route2" in route_paths