@@ -175,10 +175,6 @@ async def _lifespan_manager(self: FastMCP) -> AsyncIterator[None]:
175175 for provider in self .providers :
176176 await stack .enter_async_context (provider .lifespan ())
177177
178- # After providers are up, adjust MCP handlers to reflect actual
179- # backend capabilities (removes handlers for unsupported methods).
180- self ._sync_proxy_capabilities ()
181-
182178 self ._started .set ()
183179 try :
184180 yield
@@ -195,112 +191,6 @@ async def _lifespan_manager(self: FastMCP) -> AsyncIterator[None]:
195191 self ._lifespan_result_set = False
196192 self ._lifespan_result = None
197193
198- def _sync_proxy_capabilities (self : FastMCP ) -> None :
199- """Remove MCP handlers for capabilities the backend does not support.
200-
201- After provider lifespans have run, any ProxyProvider instances have had a
202- chance to preload their backend's serverCapabilities. If the backend doesn't
203- support a capability (resources, prompts, tools) and there are no local
204- components of that type either, we remove the corresponding request handlers
205- from the low-level MCP server.
206-
207- This has two effects:
208- 1. The ``initialize`` response no longer advertises unsupported capabilities.
209- 2. Clients that try to use an unsupported method receive a proper
210- ``METHOD_NOT_FOUND`` (-32601) JSON-RPC error instead of an empty list.
211-
212- The adjustment is conservative: if there are any providers whose capabilities
213- are not known (i.e. not a LocalProvider or ProxyProvider with loaded caps),
214- we leave the handlers untouched.
215- """
216- import mcp .types
217-
218- from fastmcp .server .providers .local_provider .local_provider import LocalProvider
219- from fastmcp .server .providers .proxy import ProxyProvider
220- from fastmcp .server .providers .wrapped_provider import _WrappedProvider
221-
222- def _unwrap (p : Any ) -> Any :
223- """Recursively unwrap _WrappedProvider to reach the inner provider."""
224- while isinstance (p , _WrappedProvider ):
225- p = p ._inner
226- return p
227-
228- # Restore handlers to the baseline that was saved at construction time
229- # so that a server reused across multiple lifespan cycles starts clean.
230- baseline = getattr (self ._mcp_server , "_baseline_request_handlers" , None )
231- if baseline is not None :
232- self ._mcp_server .request_handlers = dict (baseline )
233- else :
234- self ._mcp_server ._baseline_request_handlers = dict ( # type: ignore[attr-defined] # ty:ignore[unresolved-attribute]
235- self ._mcp_server .request_handlers
236- )
237-
238- # Unwrap all providers so we can inspect the actual provider type,
239- # including namespaced providers wrapped in _WrappedProvider.
240- unwrapped = [_unwrap (p ) for p in self .providers ]
241-
242- all_proxy_providers = [p for p in unwrapped if isinstance (p , ProxyProvider )]
243- if not all_proxy_providers :
244- return
245-
246- # If any ProxyProvider failed to preload capabilities, we can't safely
247- # prune: that backend's capabilities are unknown and removing handlers
248- # could break capabilities it can actually serve.
249- if any (p ._backend_capabilities is None for p in all_proxy_providers ):
250- return
251-
252- # Only adjust when every provider is either a LocalProvider or a
253- # ProxyProvider with known capabilities. Unknown providers may have
254- # components we can't inspect synchronously, so we leave things alone.
255- if any (not isinstance (p , (LocalProvider , ProxyProvider )) for p in unwrapped ):
256- return
257-
258- # Aggregate: a capability is "supported" if ANY proxy backend supports it.
259- backend_caps = [
260- p ._backend_capabilities
261- for p in all_proxy_providers
262- if p ._backend_capabilities is not None
263- ]
264- any_resources = any (bool (c .resources ) for c in backend_caps )
265- any_prompts = any (bool (c .prompts ) for c in backend_caps )
266- any_tools = any (bool (c .tools ) for c in backend_caps )
267-
268- # Check all LocalProvider instances for statically-registered components.
269- # A user may pass additional LocalProvider instances via the providers kwarg,
270- # so we aggregate across every LocalProvider in self.providers, not just
271- # the server's built-in self._local_provider.
272- from fastmcp .prompts .base import Prompt
273- from fastmcp .resources .base import Resource
274- from fastmcp .resources .template import ResourceTemplate
275- from fastmcp .tools .base import Tool
276-
277- local_components = [
278- c
279- for p in unwrapped
280- if isinstance (p , LocalProvider )
281- for c in p ._components .values ()
282- ]
283- local_has_resources = any (
284- isinstance (c , (Resource , ResourceTemplate )) for c in local_components
285- )
286- local_has_prompts = any (isinstance (c , Prompt ) for c in local_components )
287- local_has_tools = any (isinstance (c , Tool ) for c in local_components )
288-
289- if not any_resources and not local_has_resources :
290- self ._mcp_server .request_handlers .pop (mcp .types .ListResourcesRequest , None )
291- self ._mcp_server .request_handlers .pop (
292- mcp .types .ListResourceTemplatesRequest , None
293- )
294- self ._mcp_server .request_handlers .pop (mcp .types .ReadResourceRequest , None )
295-
296- if not any_prompts and not local_has_prompts :
297- self ._mcp_server .request_handlers .pop (mcp .types .ListPromptsRequest , None )
298- self ._mcp_server .request_handlers .pop (mcp .types .GetPromptRequest , None )
299-
300- if not any_tools and not local_has_tools :
301- self ._mcp_server .request_handlers .pop (mcp .types .ListToolsRequest , None )
302- self ._mcp_server .request_handlers .pop (mcp .types .CallToolRequest , None )
303-
304194 def _setup_task_protocol_handlers (self : FastMCP ) -> None :
305195 """Register SEP-1686 task protocol handlers with SDK.
306196
0 commit comments