4
4
import sys
5
5
from collections .abc import AsyncGenerator
6
6
from contextlib import AbstractAsyncContextManager , asynccontextmanager , suppress
7
+ from time import time
7
8
from typing import Any
8
9
9
10
import httpx
@@ -30,7 +31,6 @@ async def local_http_server( # noqa: C901, PLR0913, PLR0915
30
31
command : str | None = None ,
31
32
args : list [str ] | None = None ,
32
33
env : dict [str , str ] | None = None ,
33
- max_connection_retries : int = 10 ,
34
34
headers : dict [str , Any ] | None = None ,
35
35
) -> AsyncGenerator [tuple [InitializeResult , ListToolsResult , int ], None ]:
36
36
"""Create a local MCP server client.
@@ -65,7 +65,13 @@ def _sse_client(
65
65
value = headers [key ]
66
66
if isinstance (value , SecretStr ):
67
67
headers [key ] = value .get_secret_value ()
68
- return sse_client (url = url , headers = headers )
68
+ logger .debug ("Connecting sse client with timeout: %s" , config .initial_timeout )
69
+ return sse_client (
70
+ url = url ,
71
+ headers = headers ,
72
+ sse_read_timeout = config .initial_timeout ,
73
+ timeout = config .initial_timeout ,
74
+ )
69
75
70
76
get_client = _sse_client if config .transport == "sse" else websocket_client
71
77
logger .debug ("Starting local MCP server %s with command: %s" , config .name , command )
@@ -80,7 +86,6 @@ def _sse_client(
80
86
):
81
87
logger .error ("Failed to start subprocess for %s" , config .name )
82
88
raise RuntimeError ("failed to start subprocess" )
83
- retried = 0
84
89
85
90
# it tooks time to start the server, so we need to retry
86
91
async def _read_stdout (
@@ -114,38 +119,40 @@ async def _read_stderr(
114
119
name = "read-stdout" ,
115
120
)
116
121
122
+ start_time = time ()
117
123
try :
118
- while retried < max_connection_retries :
119
- await asyncio .sleep (0.3 if retried == 0 else 1 )
120
- logger .debug (
121
- "Attempting to connect to server %s (attempt %d/%d)" ,
122
- config .name ,
123
- retried + 1 ,
124
- max_connection_retries ,
125
- )
124
+ logger .debug (
125
+ "Server %s initalizing with timeout: %s" ,
126
+ config .name ,
127
+ config .initial_timeout ,
128
+ )
129
+ while (time () - start_time ) < config .initial_timeout :
126
130
with suppress (TimeoutError , httpx .HTTPError ):
127
131
async with (
128
132
get_client (url = config .url ) as streams ,
129
133
ClientSession (* streams ) as session ,
130
134
):
131
- async with asyncio .timeout (10 ):
135
+ async with asyncio .timeout (config . initial_timeout ):
132
136
initialize_result = await session .initialize ()
133
137
tools = await session .list_tools ()
134
138
logger .info (
135
139
"Successfully connected to server %s, got tools: %s" ,
136
140
config .name ,
137
141
tools ,
138
142
)
139
- break
140
- retried += 1
143
+ logger .info ("Connected to the server %s" , config .name )
144
+ yield initialize_result , tools , subprocess .pid
145
+ break
146
+ await asyncio .sleep (0.3 )
141
147
else :
142
- raise InvalidMcpServerError (config .name )
143
- logger .info (
144
- "Connected to the server %s after %d attempts" , config .name , retried
145
- )
146
- yield initialize_result , tools , subprocess .pid
148
+ logger .warning (
149
+ "Connected to the server %s failed after %s seconds" ,
150
+ config .name ,
151
+ config .initial_timeout ,
152
+ )
153
+ raise InvalidMcpServerError (config .name , reason = "failed to initalize" )
147
154
finally :
148
- with suppress (TimeoutError ):
155
+ with suppress (TimeoutError , ProcessLookupError , asyncio . CancelledError ):
149
156
logger .debug ("Terminating subprocess for %s" , config .name )
150
157
read_stderr_task .cancel ()
151
158
read_stdout_task .cancel ()
@@ -158,7 +165,7 @@ async def _read_stderr(
158
165
subprocess = None
159
166
if subprocess :
160
167
logger .info ("Timeout to terminate mcp-server %s. Kill it." , config .name )
161
- with suppress (TimeoutError ):
168
+ with suppress (TimeoutError , ProcessLookupError , asyncio . CancelledError ):
162
169
read_stderr_task .cancel ()
163
170
read_stdout_task .cancel ()
164
171
subprocess .kill ()
0 commit comments