7
7
from mcp .client .stdio import stdio_client
8
8
from mcp .types import CallToolResult , TextContent , Tool
9
9
from pydantic import BaseModel , Field
10
- from termcolor import colored
11
10
12
11
from openhands .core .config .mcp_config import MCPConfig
13
12
from openhands .core .logger import openhands_logger as logger
@@ -76,7 +75,7 @@ class Config:
76
75
77
76
async def connect_sse (self , server_url : str , timeout : float = 30.0 ) -> None :
78
77
"""Connect to an MCP server using SSE transport.
79
-
78
+
80
79
Args:
81
80
server_url: The URL of the SSE server to connect to.
82
81
timeout: Connection timeout in seconds. Default is 30 seconds.
@@ -89,15 +88,19 @@ async def connect_sse(self, server_url: str, timeout: float = 30.0) -> None:
89
88
try :
90
89
import asyncio
91
90
from asyncio import TimeoutError
92
-
91
+
93
92
# Create a task for the connection
94
- connection_task = asyncio .create_task (self ._connect_sse_internal (server_url ))
95
-
93
+ connection_task = asyncio .create_task (
94
+ self ._connect_sse_internal (server_url )
95
+ )
96
+
96
97
# Wait for the connection with timeout
97
98
try :
98
99
await asyncio .wait_for (connection_task , timeout = timeout )
99
100
except TimeoutError :
100
- logger .error (f'Connection to { server_url } timed out after { timeout } seconds' )
101
+ logger .error (
102
+ f'Connection to { server_url } timed out after { timeout } seconds'
103
+ )
101
104
# Cancel the connection task
102
105
connection_task .cancel ()
103
106
try :
@@ -110,7 +113,7 @@ async def connect_sse(self, server_url: str, timeout: float = 30.0) -> None:
110
113
async def _connect_sse_internal (self , server_url : str ) -> None :
111
114
"""Internal method to establish SSE connection."""
112
115
streams_context = sse_client (
113
- url = server_url ,
116
+ url = server_url ,
114
117
)
115
118
streams = await self .exit_stack .enter_async_context (streams_context )
116
119
self .session = await self .exit_stack .enter_async_context (
@@ -119,9 +122,15 @@ async def _connect_sse_internal(self, server_url: str) -> None:
119
122
120
123
await self ._initialize_and_list_tools ()
121
124
122
- async def connect_stdio (self , command : str , args : List [str ], envs : List [tuple [str , str ]], timeout : float = 30.0 ) -> None :
125
+ async def connect_stdio (
126
+ self ,
127
+ command : str ,
128
+ args : List [str ],
129
+ envs : List [tuple [str , str ]],
130
+ timeout : float = 30.0 ,
131
+ ) -> None :
123
132
"""Connect to an MCP server using stdio transport.
124
-
133
+
125
134
Args:
126
135
command: The command to execute.
127
136
args: The arguments to pass to the command.
@@ -136,15 +145,19 @@ async def connect_stdio(self, command: str, args: List[str], envs: List[tuple[st
136
145
try :
137
146
import asyncio
138
147
from asyncio import TimeoutError
139
-
148
+
140
149
# Create a task for the connection
141
- connection_task = asyncio .create_task (self ._connect_stdio_internal (command , args , envs ))
142
-
150
+ connection_task = asyncio .create_task (
151
+ self ._connect_stdio_internal (command , args , envs )
152
+ )
153
+
143
154
# Wait for the connection with timeout
144
155
try :
145
156
await asyncio .wait_for (connection_task , timeout = timeout )
146
157
except TimeoutError :
147
- logger .error (f'Connection to { command } timed out after { timeout } seconds' )
158
+ logger .error (
159
+ f'Connection to { command } timed out after { timeout } seconds'
160
+ )
148
161
# Cancel the connection task
149
162
connection_task .cancel ()
150
163
try :
@@ -154,7 +167,9 @@ async def connect_stdio(self, command: str, args: List[str], envs: List[tuple[st
154
167
except Exception as e :
155
168
logger .error (f'Error connecting to { command } : { str (e )} ' )
156
169
157
- async def _connect_stdio_internal (self , command : str , args : List [str ], envs : List [tuple [str , str ]]) -> None :
170
+ async def _connect_stdio_internal (
171
+ self , command : str , args : List [str ], envs : List [tuple [str , str ]]
172
+ ) -> None :
158
173
"""Internal method to establish stdio connection."""
159
174
envs_dict : dict [str , str ] = {}
160
175
for env in envs :
@@ -249,7 +264,10 @@ def convert_mcp_clients_to_tools(mcp_clients: list[MCPClient] | None) -> list[di
249
264
250
265
251
266
async def create_mcp_clients (
252
- sse_mcp_server : List [str ], commands : List [str ], args : List [List [str ]], envs : List [List [tuple [str , str ]]]
267
+ sse_mcp_server : List [str ],
268
+ commands : List [str ],
269
+ args : List [List [str ]],
270
+ envs : List [List [tuple [str , str ]]],
253
271
) -> List [MCPClient ]:
254
272
mcp_clients : List [MCPClient ] = []
255
273
# Initialize SSE connections
@@ -271,11 +289,15 @@ async def create_mcp_clients(
271
289
try :
272
290
await client .disconnect ()
273
291
except Exception as disconnect_error :
274
- logger .error (f'Error during disconnect after failed connection: { str (disconnect_error )} ' )
292
+ logger .error (
293
+ f'Error during disconnect after failed connection: { str (disconnect_error )} '
294
+ )
275
295
276
296
# Initialize stdio connections
277
297
if commands :
278
- for i , (command , command_args , command_envs ) in enumerate (zip (commands , args , envs )):
298
+ for i , (command , command_args , command_envs ) in enumerate (
299
+ zip (commands , args , envs )
300
+ ):
279
301
logger .info (
280
302
f'Initializing MCP agent for { command } with stdio connection...'
281
303
)
@@ -292,39 +314,45 @@ async def create_mcp_clients(
292
314
try :
293
315
await client .disconnect ()
294
316
except Exception as disconnect_error :
295
- logger .error (f'Error during disconnect after failed connection: { str (disconnect_error )} ' )
317
+ logger .error (
318
+ f'Error during disconnect after failed connection: { str (disconnect_error )} '
319
+ )
296
320
297
321
return mcp_clients
298
322
323
+
299
324
async def fetch_mcp_tools_from_config (mcp_config : MCPConfig ) -> list [dict ]:
300
325
"""
301
326
Retrieves the list of MCP tools from the MCP clients.
302
-
327
+
303
328
Returns:
304
329
A list of tool dictionaries. Returns an empty list if no connections could be established.
305
330
"""
306
331
mcp_clients = []
307
332
mcp_tools = []
308
-
333
+
309
334
try :
310
335
mcp_clients = await create_mcp_clients (
311
- mcp_config .sse .mcp_servers , mcp_config .stdio .commands , mcp_config .stdio .args , mcp_config .stdio .envs
336
+ mcp_config .sse .mcp_servers ,
337
+ mcp_config .stdio .commands ,
338
+ mcp_config .stdio .args ,
339
+ mcp_config .stdio .envs ,
312
340
)
313
-
341
+
314
342
if not mcp_clients :
315
- logger .warning (" No MCP clients were successfully connected" )
343
+ logger .warning (' No MCP clients were successfully connected' )
316
344
return []
317
-
345
+
318
346
mcp_tools = convert_mcp_clients_to_tools (mcp_clients )
319
347
except Exception as e :
320
- logger .error (f" Error fetching MCP tools: { str (e )} " )
348
+ logger .error (f' Error fetching MCP tools: { str (e )} ' )
321
349
return []
322
350
finally :
323
351
# Always disconnect clients to clean up resources
324
352
for mcp_client in mcp_clients :
325
353
try :
326
354
await mcp_client .disconnect ()
327
355
except Exception as disconnect_error :
328
- logger .error (f" Error disconnecting MCP client: { str (disconnect_error )} " )
329
-
356
+ logger .error (f' Error disconnecting MCP client: { str (disconnect_error )} ' )
357
+
330
358
return mcp_tools
0 commit comments