strands-mcp-server 0.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strands_mcp_server/__init__.py +223 -0
- strands_mcp_server/cli.py +308 -0
- strands_mcp_server/mcp_client.py +543 -0
- strands_mcp_server/mcp_server.py +722 -0
- strands_mcp_server-0.1.2.dist-info/METADATA +306 -0
- strands_mcp_server-0.1.2.dist-info/RECORD +9 -0
- strands_mcp_server-0.1.2.dist-info/WHEEL +4 -0
- strands_mcp_server-0.1.2.dist-info/entry_points.txt +2 -0
- strands_mcp_server-0.1.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
"""MCP Client Tool for Strands Agents.
|
|
2
|
+
|
|
3
|
+
Test and interact with MCP servers from within a Strands Agent. This tool provides
|
|
4
|
+
a complete MCP client implementation that can connect to any MCP server (including
|
|
5
|
+
servers created with the mcp_server tool) and use their exposed tools.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
- **Multiple Transports**: HTTP (streamable), stdio, SSE
|
|
9
|
+
- **Connection Management**: Connect, disconnect, list connections
|
|
10
|
+
- **Tool Discovery**: List available tools from connected servers
|
|
11
|
+
- **Tool Execution**: Call tools on remote servers
|
|
12
|
+
- **Session Persistence**: Maintain connections across multiple operations
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
```python
|
|
16
|
+
from strands import Agent
|
|
17
|
+
from tools.mcp_client import mcp_client
|
|
18
|
+
from tools.mcp_server import mcp_server
|
|
19
|
+
|
|
20
|
+
agent = Agent(tools=[mcp_client, mcp_server])
|
|
21
|
+
|
|
22
|
+
# Start a server
|
|
23
|
+
agent("start mcp server on port 8000")
|
|
24
|
+
|
|
25
|
+
# Connect to it
|
|
26
|
+
agent("connect to mcp server at http://localhost:8000/mcp as test-server")
|
|
27
|
+
|
|
28
|
+
# List its tools
|
|
29
|
+
agent("list tools from test-server")
|
|
30
|
+
|
|
31
|
+
# Call a tool
|
|
32
|
+
agent("call calculator tool on test-server with expression: 2 + 2")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Architecture:
|
|
36
|
+
```
|
|
37
|
+
┌─────────────────────────────────────┐
|
|
38
|
+
│ Strands Agent (Client) │
|
|
39
|
+
│ ┌──────────────────────────────┐ │
|
|
40
|
+
│ │ mcp_client tool │ │
|
|
41
|
+
│ └──────────────────────────────┘ │
|
|
42
|
+
└─────────────────────────────────────┘
|
|
43
|
+
↓
|
|
44
|
+
MCP Client Connection
|
|
45
|
+
↓
|
|
46
|
+
┌─────────────────────────────────────┐
|
|
47
|
+
│ Strands Agent (Server) │
|
|
48
|
+
│ ┌──────────────────────────────┐ │
|
|
49
|
+
│ │ mcp_server tool │ │
|
|
50
|
+
│ └──────────────────────────────┘ │
|
|
51
|
+
└─────────────────────────────────────┘
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
References:
|
|
55
|
+
- MCP Specification: https://spec.modelcontextprotocol.io/
|
|
56
|
+
- MCP Client SDK: python-sdk/src/mcp/client/
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
import logging
|
|
60
|
+
from typing import Any, Optional
|
|
61
|
+
|
|
62
|
+
from strands import tool
|
|
63
|
+
|
|
64
|
+
# Global state for managing MCP connections
|
|
65
|
+
_client_connections: dict[str, Any] = {}
|
|
66
|
+
|
|
67
|
+
logger = logging.getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@tool
|
|
71
|
+
def mcp_client(
|
|
72
|
+
action: str,
|
|
73
|
+
connection_id: Optional[str] = None,
|
|
74
|
+
transport: Optional[str] = None,
|
|
75
|
+
server_url: Optional[str] = None,
|
|
76
|
+
command: Optional[str] = None,
|
|
77
|
+
args: Optional[list[str]] = None,
|
|
78
|
+
tool_name: Optional[str] = None,
|
|
79
|
+
tool_args: Optional[dict[str, Any]] = None,
|
|
80
|
+
) -> dict[str, Any]:
|
|
81
|
+
"""Test and interact with MCP servers.
|
|
82
|
+
|
|
83
|
+
This tool provides a complete MCP client implementation for testing and using
|
|
84
|
+
MCP servers from within a Strands Agent.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
action: Action to perform - "connect", "disconnect", "list_tools", "call_tool", "list_connections"
|
|
88
|
+
connection_id: Unique identifier for this connection
|
|
89
|
+
transport: Transport type - "http", "stdio", or "sse"
|
|
90
|
+
server_url: URL for HTTP/SSE transport (e.g., "http://localhost:8000/mcp")
|
|
91
|
+
command: Command for stdio transport (e.g., "python")
|
|
92
|
+
args: Arguments for stdio command (e.g., ["mcp_server_stdio.py"])
|
|
93
|
+
tool_name: Name of tool to call (for call_tool action)
|
|
94
|
+
tool_args: Arguments to pass to tool (for call_tool action)
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Result dictionary with status and content
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
# Connect to HTTP server
|
|
101
|
+
mcp_client(
|
|
102
|
+
action="connect",
|
|
103
|
+
connection_id="my-server",
|
|
104
|
+
transport="http",
|
|
105
|
+
server_url="http://localhost:8000/mcp"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Connect to stdio server
|
|
109
|
+
mcp_client(
|
|
110
|
+
action="connect",
|
|
111
|
+
connection_id="stdio-server",
|
|
112
|
+
transport="stdio",
|
|
113
|
+
command="python",
|
|
114
|
+
args=["mcp_server_stdio.py"]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# List tools from connection
|
|
118
|
+
mcp_client(action="list_tools", connection_id="my-server")
|
|
119
|
+
|
|
120
|
+
# Call a tool
|
|
121
|
+
mcp_client(
|
|
122
|
+
action="call_tool",
|
|
123
|
+
connection_id="my-server",
|
|
124
|
+
tool_name="calculator",
|
|
125
|
+
tool_args={"expression": "2 + 2"}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# List all connections
|
|
129
|
+
mcp_client(action="list_connections")
|
|
130
|
+
|
|
131
|
+
# Disconnect
|
|
132
|
+
mcp_client(action="disconnect", connection_id="my-server")
|
|
133
|
+
|
|
134
|
+
Notes:
|
|
135
|
+
- stdio transport: Server must be launchable as subprocess
|
|
136
|
+
- HTTP transport: Server must be already running
|
|
137
|
+
- Connections are maintained in global state for reuse
|
|
138
|
+
"""
|
|
139
|
+
if action == "connect":
|
|
140
|
+
return _connect(connection_id, transport, server_url, command, args)
|
|
141
|
+
elif action == "disconnect":
|
|
142
|
+
return _disconnect(connection_id)
|
|
143
|
+
elif action == "list_tools":
|
|
144
|
+
return _list_tools(connection_id)
|
|
145
|
+
elif action == "call_tool":
|
|
146
|
+
return _call_tool(connection_id, tool_name, tool_args)
|
|
147
|
+
elif action == "list_connections":
|
|
148
|
+
return _list_connections()
|
|
149
|
+
else:
|
|
150
|
+
return {
|
|
151
|
+
"status": "error",
|
|
152
|
+
"content": [
|
|
153
|
+
{
|
|
154
|
+
"text": f"❌ Unknown action: {action}\n\n"
|
|
155
|
+
"Available actions: connect, disconnect, list_tools, call_tool, list_connections"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _connect(
|
|
162
|
+
connection_id: Optional[str],
|
|
163
|
+
transport: Optional[str],
|
|
164
|
+
server_url: Optional[str],
|
|
165
|
+
command: Optional[str],
|
|
166
|
+
args: Optional[list[str]],
|
|
167
|
+
) -> dict[str, Any]:
|
|
168
|
+
"""Connect to an MCP server.
|
|
169
|
+
|
|
170
|
+
This function establishes a connection to an MCP server using the specified
|
|
171
|
+
transport type. The connection is stored in global state for reuse.
|
|
172
|
+
|
|
173
|
+
Follows patterns from:
|
|
174
|
+
- Strands MCPClient: sdk-python/src/strands/tools/mcp/mcp_client.py
|
|
175
|
+
- MCP client transports: python-sdk/src/mcp/client/
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
if not connection_id:
|
|
179
|
+
return {
|
|
180
|
+
"status": "error",
|
|
181
|
+
"content": [{"text": "❌ connection_id is required"}],
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if not transport:
|
|
185
|
+
return {
|
|
186
|
+
"status": "error",
|
|
187
|
+
"content": [{"text": "❌ transport is required (http, stdio, or sse)"}],
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if connection_id in _client_connections:
|
|
191
|
+
return {
|
|
192
|
+
"status": "error",
|
|
193
|
+
"content": [{"text": f"❌ Connection '{connection_id}' already exists"}],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Import MCP client components
|
|
197
|
+
from mcp.client.session import ClientSession
|
|
198
|
+
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
199
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
200
|
+
|
|
201
|
+
if transport == "http":
|
|
202
|
+
if not server_url:
|
|
203
|
+
return {
|
|
204
|
+
"status": "error",
|
|
205
|
+
"content": [{"text": "❌ server_url is required for HTTP transport"}],
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
logger.debug(f"Connecting to HTTP server: {server_url}")
|
|
209
|
+
|
|
210
|
+
# Create transport callable following Strands MCPClient pattern
|
|
211
|
+
def transport_callable():
|
|
212
|
+
return streamablehttp_client(server_url)
|
|
213
|
+
|
|
214
|
+
connection_info = {
|
|
215
|
+
"transport": "http",
|
|
216
|
+
"server_url": server_url,
|
|
217
|
+
"transport_callable": transport_callable,
|
|
218
|
+
"session": None, # Will be initialized on first use
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
elif transport == "stdio":
|
|
222
|
+
if not command:
|
|
223
|
+
return {
|
|
224
|
+
"status": "error",
|
|
225
|
+
"content": [{"text": "❌ command is required for stdio transport"}],
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
logger.debug(f"Connecting to stdio server: {command} {args or []}")
|
|
229
|
+
|
|
230
|
+
# Create stdio server parameters
|
|
231
|
+
server_params = StdioServerParameters(command=command, args=args or [], env=None)
|
|
232
|
+
|
|
233
|
+
# Create transport callable
|
|
234
|
+
def transport_callable():
|
|
235
|
+
return stdio_client(server_params)
|
|
236
|
+
|
|
237
|
+
connection_info = {
|
|
238
|
+
"transport": "stdio",
|
|
239
|
+
"command": command,
|
|
240
|
+
"args": args,
|
|
241
|
+
"transport_callable": transport_callable,
|
|
242
|
+
"session": None,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
elif transport == "sse":
|
|
246
|
+
if not server_url:
|
|
247
|
+
return {
|
|
248
|
+
"status": "error",
|
|
249
|
+
"content": [{"text": "❌ server_url is required for SSE transport"}],
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
logger.debug(f"Connecting to SSE server: {server_url}")
|
|
253
|
+
|
|
254
|
+
from mcp.client.sse import sse_client
|
|
255
|
+
|
|
256
|
+
def transport_callable():
|
|
257
|
+
return sse_client(server_url)
|
|
258
|
+
|
|
259
|
+
connection_info = {
|
|
260
|
+
"transport": "sse",
|
|
261
|
+
"server_url": server_url,
|
|
262
|
+
"transport_callable": transport_callable,
|
|
263
|
+
"session": None,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
return {
|
|
268
|
+
"status": "error",
|
|
269
|
+
"content": [{"text": f"❌ Unknown transport: {transport}\n\nSupported: http, stdio, sse"}],
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
# Test the connection by listing tools
|
|
273
|
+
logger.debug(f"Testing connection by listing tools...")
|
|
274
|
+
tools = []
|
|
275
|
+
try:
|
|
276
|
+
# Create a temporary session to test and get tool count
|
|
277
|
+
import asyncio
|
|
278
|
+
|
|
279
|
+
async def test_connection():
|
|
280
|
+
async with connection_info["transport_callable"]() as (
|
|
281
|
+
read_stream,
|
|
282
|
+
write_stream,
|
|
283
|
+
get_session_id, # StreamableHTTP returns 3 values
|
|
284
|
+
):
|
|
285
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
286
|
+
await session.initialize()
|
|
287
|
+
result = await session.list_tools()
|
|
288
|
+
return result.tools
|
|
289
|
+
|
|
290
|
+
tools = asyncio.run(test_connection())
|
|
291
|
+
logger.debug(f"Successfully connected, found {len(tools)} tools")
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
logger.exception("Failed to connect to MCP server")
|
|
295
|
+
return {
|
|
296
|
+
"status": "error",
|
|
297
|
+
"content": [{"text": f"❌ Failed to connect: {str(e)}"}],
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
# Store connection
|
|
301
|
+
_client_connections[connection_id] = connection_info
|
|
302
|
+
|
|
303
|
+
# Build response
|
|
304
|
+
tool_list = "\n".join(f" • {tool.name}" for tool in tools[:10])
|
|
305
|
+
if len(tools) > 10:
|
|
306
|
+
tool_list += f"\n ... and {len(tools) - 10} more"
|
|
307
|
+
|
|
308
|
+
transport_info = (
|
|
309
|
+
f"URL: {server_url}" if transport in ["http", "sse"] else f"Command: {command} {' '.join(args or [])}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
message = (
|
|
313
|
+
f"✅ Connected to MCP server '{connection_id}'\n\n"
|
|
314
|
+
f"📊 Transport: {transport}\n"
|
|
315
|
+
f"🔗 {transport_info}\n"
|
|
316
|
+
f"🔧 Available tools ({len(tools)}):\n"
|
|
317
|
+
f"{tool_list}"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return {"status": "success", "content": [{"text": message}]}
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.exception("Error in connect action")
|
|
324
|
+
return {
|
|
325
|
+
"status": "error",
|
|
326
|
+
"content": [{"text": f"❌ Error: {str(e)}"}],
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def _disconnect(connection_id: Optional[str]) -> dict[str, Any]:
|
|
331
|
+
"""Disconnect from an MCP server.
|
|
332
|
+
|
|
333
|
+
Removes the connection from global state. The actual transport cleanup
|
|
334
|
+
happens automatically via context managers.
|
|
335
|
+
"""
|
|
336
|
+
try:
|
|
337
|
+
if not connection_id:
|
|
338
|
+
return {
|
|
339
|
+
"status": "error",
|
|
340
|
+
"content": [{"text": "❌ connection_id is required"}],
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if connection_id not in _client_connections:
|
|
344
|
+
return {
|
|
345
|
+
"status": "error",
|
|
346
|
+
"content": [{"text": f"❌ Connection '{connection_id}' not found"}],
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
# Remove connection
|
|
350
|
+
del _client_connections[connection_id]
|
|
351
|
+
logger.debug(f"Disconnected from '{connection_id}'")
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
"status": "success",
|
|
355
|
+
"content": [{"text": f"✅ Disconnected from '{connection_id}'"}],
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.exception("Error in disconnect action")
|
|
360
|
+
return {
|
|
361
|
+
"status": "error",
|
|
362
|
+
"content": [{"text": f"❌ Error: {str(e)}"}],
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _list_tools(connection_id: Optional[str]) -> dict[str, Any]:
|
|
367
|
+
"""List tools from a connected MCP server.
|
|
368
|
+
|
|
369
|
+
This creates a temporary session to query the server for its available tools.
|
|
370
|
+
"""
|
|
371
|
+
try:
|
|
372
|
+
if not connection_id:
|
|
373
|
+
return {
|
|
374
|
+
"status": "error",
|
|
375
|
+
"content": [{"text": "❌ connection_id is required"}],
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if connection_id not in _client_connections:
|
|
379
|
+
return {
|
|
380
|
+
"status": "error",
|
|
381
|
+
"content": [{"text": f"❌ Connection '{connection_id}' not found"}],
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
connection_info = _client_connections[connection_id]
|
|
385
|
+
logger.debug(f"Listing tools from '{connection_id}'")
|
|
386
|
+
|
|
387
|
+
# Create session and list tools
|
|
388
|
+
import asyncio
|
|
389
|
+
|
|
390
|
+
from mcp.client.session import ClientSession
|
|
391
|
+
|
|
392
|
+
async def list_tools_async():
|
|
393
|
+
async with connection_info["transport_callable"]() as (
|
|
394
|
+
read_stream,
|
|
395
|
+
write_stream,
|
|
396
|
+
get_session_id, # StreamableHTTP returns 3 values
|
|
397
|
+
):
|
|
398
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
399
|
+
await session.initialize()
|
|
400
|
+
result = await session.list_tools()
|
|
401
|
+
return result.tools
|
|
402
|
+
|
|
403
|
+
tools = asyncio.run(list_tools_async())
|
|
404
|
+
logger.debug(f"Found {len(tools)} tools")
|
|
405
|
+
|
|
406
|
+
# Build detailed tool list
|
|
407
|
+
tool_details = []
|
|
408
|
+
for tool in tools:
|
|
409
|
+
details = f"**{tool.name}**"
|
|
410
|
+
if tool.description:
|
|
411
|
+
details += f"\n Description: {tool.description}"
|
|
412
|
+
if tool.inputSchema:
|
|
413
|
+
# Show required parameters
|
|
414
|
+
schema = tool.inputSchema
|
|
415
|
+
if "required" in schema:
|
|
416
|
+
details += f"\n Required: {', '.join(schema['required'])}"
|
|
417
|
+
if "properties" in schema:
|
|
418
|
+
details += f"\n Parameters: {', '.join(schema['properties'].keys())}"
|
|
419
|
+
tool_details.append(details)
|
|
420
|
+
|
|
421
|
+
tools_text = "\n\n".join(tool_details)
|
|
422
|
+
|
|
423
|
+
message = f"📋 **Tools from '{connection_id}'**\n\n" f"Found {len(tools)} tools:\n\n" f"{tools_text}"
|
|
424
|
+
|
|
425
|
+
return {"status": "success", "content": [{"text": message}]}
|
|
426
|
+
|
|
427
|
+
except Exception as e:
|
|
428
|
+
logger.exception("Error listing tools")
|
|
429
|
+
return {
|
|
430
|
+
"status": "error",
|
|
431
|
+
"content": [{"text": f"❌ Error: {str(e)}"}],
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def _call_tool(
|
|
436
|
+
connection_id: Optional[str],
|
|
437
|
+
tool_name: Optional[str],
|
|
438
|
+
tool_args: Optional[dict[str, Any]],
|
|
439
|
+
) -> dict[str, Any]:
|
|
440
|
+
"""Call a tool on a connected MCP server.
|
|
441
|
+
|
|
442
|
+
This establishes a session, calls the specified tool with provided arguments,
|
|
443
|
+
and returns the result.
|
|
444
|
+
"""
|
|
445
|
+
try:
|
|
446
|
+
if not connection_id:
|
|
447
|
+
return {
|
|
448
|
+
"status": "error",
|
|
449
|
+
"content": [{"text": "❌ connection_id is required"}],
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if not tool_name:
|
|
453
|
+
return {
|
|
454
|
+
"status": "error",
|
|
455
|
+
"content": [{"text": "❌ tool_name is required"}],
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if connection_id not in _client_connections:
|
|
459
|
+
return {
|
|
460
|
+
"status": "error",
|
|
461
|
+
"content": [{"text": f"❌ Connection '{connection_id}' not found"}],
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
connection_info = _client_connections[connection_id]
|
|
465
|
+
logger.debug(f"Calling tool '{tool_name}' on '{connection_id}' with args: {tool_args}")
|
|
466
|
+
|
|
467
|
+
# Call the tool
|
|
468
|
+
import asyncio
|
|
469
|
+
|
|
470
|
+
from mcp.client.session import ClientSession
|
|
471
|
+
|
|
472
|
+
async def call_tool_async():
|
|
473
|
+
async with connection_info["transport_callable"]() as (
|
|
474
|
+
read_stream,
|
|
475
|
+
write_stream,
|
|
476
|
+
get_session_id,
|
|
477
|
+
):
|
|
478
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
479
|
+
await session.initialize()
|
|
480
|
+
result = await session.call_tool(tool_name, tool_args or {})
|
|
481
|
+
return result
|
|
482
|
+
|
|
483
|
+
result = asyncio.run(call_tool_async())
|
|
484
|
+
logger.debug(f"Tool call complete, got {len(result.content)} content items")
|
|
485
|
+
|
|
486
|
+
# Extract result content
|
|
487
|
+
result_text = []
|
|
488
|
+
for content in result.content:
|
|
489
|
+
if hasattr(content, "text"):
|
|
490
|
+
result_text.append(content.text)
|
|
491
|
+
else:
|
|
492
|
+
result_text.append(str(content))
|
|
493
|
+
|
|
494
|
+
combined_result = "\n".join(result_text)
|
|
495
|
+
|
|
496
|
+
message = f"✅ **Tool '{tool_name}' executed on '{connection_id}'**\n\n" f"Result:\n{combined_result}"
|
|
497
|
+
|
|
498
|
+
return {"status": "success", "content": [{"text": message}]}
|
|
499
|
+
|
|
500
|
+
except Exception as e:
|
|
501
|
+
logger.exception(f"Error calling tool '{tool_name}'")
|
|
502
|
+
return {
|
|
503
|
+
"status": "error",
|
|
504
|
+
"content": [{"text": f"❌ Error: {str(e)}"}],
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _list_connections() -> dict[str, Any]:
|
|
509
|
+
"""List all active MCP connections.
|
|
510
|
+
|
|
511
|
+
Shows connection details including transport type and available tools.
|
|
512
|
+
"""
|
|
513
|
+
try:
|
|
514
|
+
if not _client_connections:
|
|
515
|
+
return {
|
|
516
|
+
"status": "success",
|
|
517
|
+
"content": [{"text": "📭 No active MCP connections"}],
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
lines = [f"📡 **Active MCP Connections** ({len(_client_connections)})\n"]
|
|
521
|
+
|
|
522
|
+
for conn_id, conn_info in _client_connections.items():
|
|
523
|
+
lines.append(f"\n**{conn_id}**")
|
|
524
|
+
lines.append(f" • Transport: {conn_info['transport']}")
|
|
525
|
+
|
|
526
|
+
if conn_info["transport"] == "http":
|
|
527
|
+
lines.append(f" • URL: {conn_info['server_url']}")
|
|
528
|
+
elif conn_info["transport"] == "sse":
|
|
529
|
+
lines.append(f" • URL: {conn_info['server_url']}")
|
|
530
|
+
elif conn_info["transport"] == "stdio":
|
|
531
|
+
cmd = f"{conn_info['command']} {' '.join(conn_info.get('args', []))}"
|
|
532
|
+
lines.append(f" • Command: {cmd}")
|
|
533
|
+
|
|
534
|
+
message = "\n".join(lines)
|
|
535
|
+
|
|
536
|
+
return {"status": "success", "content": [{"text": message}]}
|
|
537
|
+
|
|
538
|
+
except Exception as e:
|
|
539
|
+
logger.exception("Error listing connections")
|
|
540
|
+
return {
|
|
541
|
+
"status": "error",
|
|
542
|
+
"content": [{"text": f"❌ Error: {str(e)}"}],
|
|
543
|
+
}
|