@@ -135,29 +135,54 @@ async def __connect_to_stdio_server(self, server_script_path: str) -> None:
135135 raise PluginError (error = convert_exception_to_error (e , plugin_name = self .name ))
136136
137137 async def __connect_to_http_server (self , uri : str ) -> None :
138- """Connect to an MCP plugin server via streamable http.
138+ """Connect to an MCP plugin server via streamable http with retry logic .
139139
140140 Args:
141141 uri: the URI of the mcp plugin server.
142142
143143 Raises:
144- PluginError: if there is an external connection error.
144+ PluginError: if there is an external connection error after all retries .
145145 """
146-
147- try :
148- http_transport = await self ._exit_stack .enter_async_context (streamablehttp_client (uri ))
149- self ._http , self ._write , _ = http_transport
150- self ._session = await self ._exit_stack .enter_async_context (ClientSession (self ._http , self ._write ))
151-
152- await self ._session .initialize ()
153-
154- # List available tools
155- response = await self ._session .list_tools ()
156- tools = response .tools
157- logger .info ("\n Connected to plugin MCP (http) server with tools: %s" , " " .join ([tool .name for tool in tools ]))
158- except Exception as e :
159- logger .exception (e )
160- raise PluginError (error = convert_exception_to_error (e , plugin_name = self .name ))
146+ max_retries = 3
147+ base_delay = 1.0
148+
149+ for attempt in range (max_retries ):
150+ logger .info (f"Connecting to external plugin server: { uri } (attempt { attempt + 1 } /{ max_retries } )" )
151+
152+ try :
153+ # Create a fresh exit stack for each attempt
154+ async with AsyncExitStack () as temp_stack :
155+ http_transport = await temp_stack .enter_async_context (streamablehttp_client (uri ))
156+ http_client , write_func , _ = http_transport
157+ session = await temp_stack .enter_async_context (ClientSession (http_client , write_func ))
158+
159+ await session .initialize ()
160+
161+ # List available tools
162+ response = await session .list_tools ()
163+ tools = response .tools
164+ logger .info ("Successfully connected to plugin MCP server with tools: %s" , " " .join ([tool .name for tool in tools ]))
165+
166+ # Success! Now move to the main exit stack
167+ self ._http = await self ._exit_stack .enter_async_context (streamablehttp_client (uri ))
168+ self ._http , self ._write , _ = self ._http
169+ self ._session = await self ._exit_stack .enter_async_context (ClientSession (self ._http , self ._write ))
170+ await self ._session .initialize ()
171+ return
172+
173+ except Exception as e :
174+ logger .warning (f"Connection attempt { attempt + 1 } /{ max_retries } failed: { e } " )
175+
176+ if attempt == max_retries - 1 :
177+ # Final attempt failed
178+ error_msg = f"External plugin '{ self .name } ' connection failed after { max_retries } attempts: { uri } is not reachable. Please ensure the MCP server is running."
179+ logger .error (error_msg )
180+ raise PluginError (error = PluginErrorModel (message = error_msg , plugin_name = self .name ))
181+
182+ # Wait before retry
183+ delay = base_delay * (2 ** attempt )
184+ logger .info (f"Retrying in { delay } s..." )
185+ await asyncio .sleep (delay )
161186
162187 async def __invoke_hook (self , payload_result_model : Type [P ], hook_type : HookType , payload : BaseModel , context : PluginContext ) -> P :
163188 """Invoke an external plugin hook using the MCP protocol.
0 commit comments