From 93b77d322f54f38d0e0f38c936ed68a81b9017b7 Mon Sep 17 00:00:00 2001 From: Aryan Date: Sun, 4 Jan 2026 03:14:53 +0530 Subject: [PATCH 1/3] Fix MCP plugin hang when initialization fails --- python/semantic_kernel/connectors/mcp.py | 62 +++++++++++++----------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/python/semantic_kernel/connectors/mcp.py b/python/semantic_kernel/connectors/mcp.py index c473347c7b62..d391540d8b83 100644 --- a/python/semantic_kernel/connectors/mcp.py +++ b/python/semantic_kernel/connectors/mcp.py @@ -237,15 +237,15 @@ async def __aexit__( async def connect(self) -> None: """Connect to the MCP server.""" - ready_event = asyncio.Event() + loop = asyncio.get_running_loop() + ready_future: asyncio.Future[None] = loop.create_future() + try: - self._current_task = asyncio.create_task(self._inner_connect(ready_event)) - await ready_event.wait() + self._current_task = asyncio.create_task(self._inner_connect(ready_future)) + await ready_future except KernelPluginInvalidConfigurationError: - ready_event.clear() raise except Exception as ex: - ready_event.clear() await self.close() raise FunctionExecutionException("Failed to enter context manager.") from ex @@ -261,16 +261,20 @@ async def close(self) -> None: self._current_task = None self.session = None - async def _inner_connect(self, ready_event: asyncio.Event) -> None: + async def _inner_connect(self, ready_future: asyncio.Future) -> None: if not self.session: try: transport = await self._exit_stack.enter_async_context(self.get_mcp_client()) except Exception as ex: await self._exit_stack.aclose() - ready_event.set() - raise KernelPluginInvalidConfigurationError( - "Failed to connect to the MCP server. Please check your configuration." - ) from ex + if not ready_future.done(): + exc = KernelPluginInvalidConfigurationError( + "Failed to connect to the MCP server. Please check your configuration." + ) + exc.__cause__ = ex + ready_future.set_exception(exc) + return + try: session = await self._exit_stack.enter_async_context( ClientSession( @@ -284,16 +288,24 @@ async def _inner_connect(self, ready_event: asyncio.Event) -> None: ) except Exception as ex: await self._exit_stack.aclose() - raise KernelPluginInvalidConfigurationError( - "Failed to create a session. Please check your configuration." - ) from ex + if not ready_future.done(): + exc = KernelPluginInvalidConfigurationError( + "Failed to create a session. Please check your configuration." + ) + exc.__cause__ = ex + ready_future.set_exception(exc) + return try: await session.initialize() except Exception as ex: await self._exit_stack.aclose() - raise KernelPluginInvalidConfigurationError( - "Failed to initialize session. Please check your configuration." - ) from ex + if not ready_future.done(): + exc = KernelPluginInvalidConfigurationError( + "Failed to initialize session. Please check your configuration." + ) + exc.__cause__ = ex + ready_future.set_exception(exc) + return self.session = session elif self.session._request_id == 0: # If the session is not initialized, we need to reinitialize it @@ -312,7 +324,8 @@ async def _inner_connect(self, ready_event: asyncio.Event) -> None: except Exception: logger.warning("Failed to set log level to %s", logger.level) # Setting up is complete, will now signal the main loop that we are ready - ready_event.set() + if not ready_future.done(): + ready_future.set_result(None) # Create a stop event to signal the exit stack to close self._stop_event = asyncio.Event() await self._stop_event.wait() @@ -434,11 +447,8 @@ async def message_handler( async def load_prompts(self): """Load prompts from the MCP server.""" - try: - prompt_list = await self.session.list_prompts() - except Exception: - prompt_list = None - for prompt in prompt_list.prompts if prompt_list else []: + prompt_list = await self.session.list_prompts() + for prompt in prompt_list.prompts: local_name = _normalize_mcp_name(prompt.name) func = kernel_function(name=local_name, description=prompt.description)( partial(self.get_prompt, prompt.name) @@ -448,12 +458,8 @@ async def load_prompts(self): async def load_tools(self): """Load tools from the MCP server.""" - try: - tool_list = await self.session.list_tools() - except Exception: - tool_list = None - # Create methods with the kernel_function decorator for each tool - for tool in tool_list.tools if tool_list else []: + tool_list = await self.session.list_tools() + for tool in tool_list.tools: local_name = _normalize_mcp_name(tool.name) func = kernel_function(name=local_name, description=tool.description)(partial(self.call_tool, tool.name)) func.__kernel_function_parameters__ = _get_parameter_dicts_from_mcp_tool(tool) From b51f34ff6be656bfad3edfc469d693301966cdf0 Mon Sep 17 00:00:00 2001 From: Aryan Date: Thu, 30 Apr 2026 18:57:27 +0530 Subject: [PATCH 2/3] Fix MCP initialization hang: ensure future resolution and improve error handling --- python/semantic_kernel/connectors/mcp.py | 47 +++++++++++++++--------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/python/semantic_kernel/connectors/mcp.py b/python/semantic_kernel/connectors/mcp.py index d391540d8b83..e521b4e220ed 100644 --- a/python/semantic_kernel/connectors/mcp.py +++ b/python/semantic_kernel/connectors/mcp.py @@ -272,6 +272,7 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: "Failed to connect to the MCP server. Please check your configuration." ) exc.__cause__ = ex + exc.__suppress_context__ = True ready_future.set_exception(exc) return @@ -293,6 +294,7 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: "Failed to create a session. Please check your configuration." ) exc.__cause__ = ex + exc.__suppress_context__ = True ready_future.set_exception(exc) return try: @@ -304,6 +306,7 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: "Failed to initialize session. Please check your configuration." ) exc.__cause__ = ex + exc.__suppress_context__ = True ready_future.set_exception(exc) return self.session = session @@ -311,21 +314,25 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: # If the session is not initialized, we need to reinitialize it await self.session.initialize() logger.debug("Connected to MCP server: %s", self.session) - if self.load_tools_flag: - await self.load_tools() - if self.load_prompts_flag: - await self.load_prompts() - - if logger.level != logging.NOTSET: - try: - await self.session.set_logging_level( - next(level for level, value in LOG_LEVEL_MAPPING.items() if value == logger.level) - ) - except Exception: - logger.warning("Failed to set log level to %s", logger.level) - # Setting up is complete, will now signal the main loop that we are ready - if not ready_future.done(): - ready_future.set_result(None) + try: + if self.load_tools_flag: + await self.load_tools() + if self.load_prompts_flag: + await self.load_prompts() + if logger.level != logging.NOTSET: + try: + await self.session.set_logging_level( + next(level for level, value in LOG_LEVEL_MAPPING.items() if value == logger.level) + ) + except Exception: + logger.warning("Failed to set log level to %s", logger.level) + if not ready_future.done(): + ready_future.set_result(None) + except Exception as ex: + await self._exit_stack.aclose() + if not ready_future.done(): + ready_future.set_exception(ex) + return # Create a stop event to signal the exit stack to close self._stop_event = asyncio.Event() await self._stop_event.wait() @@ -441,9 +448,15 @@ async def message_handler( if isinstance(message, types.ServerNotification): match message.root.method: case "notifications/tools/list_changed": - await self.load_tools() + try: + await self.load_tools() + except Exception as ex: + logger.warning("Failed to reload tools on notification: %s", ex) case "notifications/prompts/list_changed": - await self.load_prompts() + try: + await self.load_prompts() + except Exception as ex: + logger.warning("Failed to reload prompts on notification: %s", ex) async def load_prompts(self): """Load prompts from the MCP server.""" From 1ec87f010933590078146b1fd893725f230ec179 Mon Sep 17 00:00:00 2001 From: Aryan Date: Thu, 30 Apr 2026 20:18:17 +0530 Subject: [PATCH 3/3] Ruff formatting --- python/semantic_kernel/connectors/mcp.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/python/semantic_kernel/connectors/mcp.py b/python/semantic_kernel/connectors/mcp.py index e521b4e220ed..fdc186f21da8 100644 --- a/python/semantic_kernel/connectors/mcp.py +++ b/python/semantic_kernel/connectors/mcp.py @@ -266,7 +266,6 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: try: transport = await self._exit_stack.enter_async_context(self.get_mcp_client()) except Exception as ex: - await self._exit_stack.aclose() if not ready_future.done(): exc = KernelPluginInvalidConfigurationError( "Failed to connect to the MCP server. Please check your configuration." @@ -274,6 +273,10 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: exc.__cause__ = ex exc.__suppress_context__ = True ready_future.set_exception(exc) + try: + await self._exit_stack.aclose() + except Exception: + logger.warning("Failed to close exit stack during error handling") return try: @@ -288,19 +291,21 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: ) ) except Exception as ex: - await self._exit_stack.aclose() if not ready_future.done(): exc = KernelPluginInvalidConfigurationError( - "Failed to create a session. Please check your configuration." + "Failed to connect to the MCP server. Please check your configuration." ) exc.__cause__ = ex exc.__suppress_context__ = True ready_future.set_exception(exc) + try: + await self._exit_stack.aclose() + except Exception: + logger.warning("Failed to close exit stack during error handling") return try: await session.initialize() except Exception as ex: - await self._exit_stack.aclose() if not ready_future.done(): exc = KernelPluginInvalidConfigurationError( "Failed to initialize session. Please check your configuration." @@ -308,6 +313,10 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: exc.__cause__ = ex exc.__suppress_context__ = True ready_future.set_exception(exc) + try: + await self._exit_stack.aclose() + except Exception: + logger.warning("Failed to close exit stack during error handling") return self.session = session elif self.session._request_id == 0: @@ -329,9 +338,12 @@ async def _inner_connect(self, ready_future: asyncio.Future) -> None: if not ready_future.done(): ready_future.set_result(None) except Exception as ex: - await self._exit_stack.aclose() if not ready_future.done(): ready_future.set_exception(ex) + try: + await self._exit_stack.aclose() + except Exception: + logger.warning("Failed to close exit stack during error handling") return # Create a stop event to signal the exit stack to close self._stop_event = asyncio.Event()