kollabor 0.4.9__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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""MCP (Model Context Protocol) integration for LLM core service.
|
|
2
|
+
|
|
3
|
+
Provides integration with MCP servers for tool discovery and execution,
|
|
4
|
+
enabling the LLM to interact with external tools and services.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
import subprocess
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MCPIntegration:
|
|
20
|
+
"""MCP server and tool integration.
|
|
21
|
+
|
|
22
|
+
Manages discovery, registration, and execution of MCP tools,
|
|
23
|
+
bridging external services with the LLM core service.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Initialize MCP integration."""
|
|
28
|
+
self.mcp_servers = {}
|
|
29
|
+
self.tool_registry = {}
|
|
30
|
+
self.active_connections = {}
|
|
31
|
+
|
|
32
|
+
# MCP configuration directories (local project first, then global)
|
|
33
|
+
self.local_mcp_dir = Path.cwd() / ".kollabor-cli" / "mcp"
|
|
34
|
+
self.global_mcp_dir = Path.home() / ".kollabor-cli" / "mcp"
|
|
35
|
+
|
|
36
|
+
# Load from both local and global configs
|
|
37
|
+
self._load_mcp_config()
|
|
38
|
+
|
|
39
|
+
logger.info("MCP Integration initialized")
|
|
40
|
+
|
|
41
|
+
def _load_mcp_config(self):
|
|
42
|
+
"""Load MCP configuration from Kollabor config directories."""
|
|
43
|
+
# Load from global config first (lower priority)
|
|
44
|
+
self._load_config_from_dir(self.global_mcp_dir, "global")
|
|
45
|
+
|
|
46
|
+
# Load from local config second (higher priority, can override)
|
|
47
|
+
self._load_config_from_dir(self.local_mcp_dir, "local")
|
|
48
|
+
|
|
49
|
+
logger.info(f"Loaded {len(self.mcp_servers)} total MCP server configurations")
|
|
50
|
+
|
|
51
|
+
def _load_config_from_dir(self, config_dir: Path, config_type: str):
|
|
52
|
+
"""Load MCP config from a specific directory.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
config_dir: Directory to load config from
|
|
56
|
+
config_type: Type of config (local/global) for logging
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
mcp_settings = config_dir / "mcp_settings.json"
|
|
60
|
+
if mcp_settings.exists():
|
|
61
|
+
with open(mcp_settings, 'r') as f:
|
|
62
|
+
config = json.load(f)
|
|
63
|
+
servers = config.get("servers", {})
|
|
64
|
+
self.mcp_servers.update(servers)
|
|
65
|
+
logger.info(f"Loaded {len(servers)} MCP servers from {config_type} config")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.warning(f"Failed to load {config_type} MCP config: {e}")
|
|
68
|
+
|
|
69
|
+
async def discover_mcp_servers(self) -> Dict[str, Any]:
|
|
70
|
+
"""Auto-discover available MCP servers.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary of discovered MCP servers and their capabilities
|
|
74
|
+
"""
|
|
75
|
+
discovered = {}
|
|
76
|
+
|
|
77
|
+
# Check for local MCP servers
|
|
78
|
+
await self._discover_local_servers(discovered)
|
|
79
|
+
|
|
80
|
+
# Check for configured servers
|
|
81
|
+
for server_name, server_config in self.mcp_servers.items():
|
|
82
|
+
if await self._validate_server(server_config):
|
|
83
|
+
discovered[server_name] = {
|
|
84
|
+
"name": server_name,
|
|
85
|
+
"type": server_config.get("type", "unknown"),
|
|
86
|
+
"capabilities": await self._get_server_capabilities(server_config),
|
|
87
|
+
"status": "available"
|
|
88
|
+
}
|
|
89
|
+
logger.info(f"Discovered MCP server: {server_name}")
|
|
90
|
+
|
|
91
|
+
return discovered
|
|
92
|
+
|
|
93
|
+
async def _discover_local_servers(self, discovered: Dict):
|
|
94
|
+
"""Discover locally running MCP servers."""
|
|
95
|
+
# Check common MCP server locations
|
|
96
|
+
common_paths = [
|
|
97
|
+
Path.home() / ".mcp" / "servers",
|
|
98
|
+
Path("/usr/local/mcp/servers"),
|
|
99
|
+
Path.cwd() / ".mcp" / "servers"
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
for path in common_paths:
|
|
103
|
+
if path.exists():
|
|
104
|
+
for server_dir in path.iterdir():
|
|
105
|
+
if server_dir.is_dir():
|
|
106
|
+
manifest = server_dir / "manifest.json"
|
|
107
|
+
if manifest.exists():
|
|
108
|
+
try:
|
|
109
|
+
with open(manifest, 'r') as f:
|
|
110
|
+
server_info = json.load(f)
|
|
111
|
+
server_name = server_info.get("name", server_dir.name)
|
|
112
|
+
discovered[server_name] = {
|
|
113
|
+
"name": server_name,
|
|
114
|
+
"path": str(server_dir),
|
|
115
|
+
"manifest": server_info,
|
|
116
|
+
"status": "local"
|
|
117
|
+
}
|
|
118
|
+
logger.info(f"Discovered local MCP server: {server_name}")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.warning(f"Failed to load manifest from {server_dir}: {e}")
|
|
121
|
+
|
|
122
|
+
async def _validate_server(self, server_config: Dict) -> bool:
|
|
123
|
+
"""Validate that an MCP server is accessible.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
server_config: Server configuration dictionary
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if server is accessible, False otherwise
|
|
130
|
+
"""
|
|
131
|
+
# Basic validation - can be extended with actual connection test
|
|
132
|
+
required_fields = ["command"] if server_config.get("type") == "stdio" else ["url"]
|
|
133
|
+
return all(field in server_config for field in required_fields)
|
|
134
|
+
|
|
135
|
+
async def _get_server_capabilities(self, server_config: Dict) -> List[str]:
|
|
136
|
+
"""Get capabilities of an MCP server.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
server_config: Server configuration dictionary
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
List of server capabilities
|
|
143
|
+
"""
|
|
144
|
+
capabilities = []
|
|
145
|
+
|
|
146
|
+
# For stdio servers, we can query capabilities
|
|
147
|
+
if server_config.get("type") == "stdio":
|
|
148
|
+
try:
|
|
149
|
+
result = await self._execute_server_command(
|
|
150
|
+
server_config.get("command", ""),
|
|
151
|
+
"--list-tools"
|
|
152
|
+
)
|
|
153
|
+
if result:
|
|
154
|
+
# Parse tool list from output
|
|
155
|
+
tools = result.split("\n")
|
|
156
|
+
capabilities.extend([t.strip() for t in tools if t.strip()])
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.warning(f"Failed to get server capabilities: {e}")
|
|
159
|
+
|
|
160
|
+
return capabilities or ["unknown"]
|
|
161
|
+
|
|
162
|
+
async def register_mcp_tool(self, tool_name: str, server: str,
|
|
163
|
+
tool_definition: Optional[Dict] = None) -> bool:
|
|
164
|
+
"""Register an MCP tool for LLM use.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
tool_name: Name of the tool
|
|
168
|
+
server: Server providing the tool
|
|
169
|
+
tool_definition: Optional tool definition/schema
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
True if registration successful
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
self.tool_registry[tool_name] = {
|
|
176
|
+
"server": server,
|
|
177
|
+
"definition": tool_definition or {},
|
|
178
|
+
"enabled": True
|
|
179
|
+
}
|
|
180
|
+
logger.info(f"Registered MCP tool: {tool_name} from {server}")
|
|
181
|
+
return True
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.error(f"Failed to register MCP tool {tool_name}: {e}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
async def call_mcp_tool(self, tool_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
187
|
+
"""Execute an MCP tool call.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
tool_name: Name of the tool to execute
|
|
191
|
+
params: Parameters for the tool
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Tool execution result
|
|
195
|
+
"""
|
|
196
|
+
if tool_name not in self.tool_registry:
|
|
197
|
+
return {
|
|
198
|
+
"error": f"Tool '{tool_name}' not found",
|
|
199
|
+
"available_tools": list(self.tool_registry.keys())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
tool_info = self.tool_registry[tool_name]
|
|
203
|
+
server_name = tool_info["server"]
|
|
204
|
+
|
|
205
|
+
if not tool_info["enabled"]:
|
|
206
|
+
return {"error": f"Tool '{tool_name}' is disabled"}
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
# Get server configuration
|
|
210
|
+
server_config = self.mcp_servers.get(server_name, {})
|
|
211
|
+
|
|
212
|
+
# Execute tool based on server type
|
|
213
|
+
if server_config.get("type") == "stdio":
|
|
214
|
+
result = await self._execute_stdio_tool(server_config, tool_name, params)
|
|
215
|
+
elif server_config.get("type") == "http":
|
|
216
|
+
result = await self._execute_http_tool(server_config, tool_name, params)
|
|
217
|
+
else:
|
|
218
|
+
result = {"error": f"Unknown server type for {server_name}"}
|
|
219
|
+
|
|
220
|
+
logger.info(f"Executed MCP tool: {tool_name}")
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"Failed to execute MCP tool {tool_name}: {e}")
|
|
225
|
+
return {"error": str(e)}
|
|
226
|
+
|
|
227
|
+
async def _execute_stdio_tool(self, server_config: Dict, tool_name: str,
|
|
228
|
+
params: Dict) -> Dict[str, Any]:
|
|
229
|
+
"""Execute a tool via stdio MCP server.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
server_config: Server configuration
|
|
233
|
+
tool_name: Tool to execute
|
|
234
|
+
params: Tool parameters
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Tool execution result
|
|
238
|
+
"""
|
|
239
|
+
command = server_config.get("command", "")
|
|
240
|
+
if not command:
|
|
241
|
+
return {"error": "No command specified for stdio server"}
|
|
242
|
+
|
|
243
|
+
# Validate tool_name to prevent command injection
|
|
244
|
+
# Only allow alphanumeric, underscore, hyphen, and dot
|
|
245
|
+
if not re.match(r'^[a-zA-Z0-9_\-\.]+$', tool_name):
|
|
246
|
+
return {"error": f"Invalid tool name: {tool_name}"}
|
|
247
|
+
|
|
248
|
+
# Build command as list (safer than shell=True)
|
|
249
|
+
# Parse the command string and add tool arguments
|
|
250
|
+
try:
|
|
251
|
+
command_parts = shlex.split(command)
|
|
252
|
+
except ValueError as e:
|
|
253
|
+
return {"error": f"Invalid command format: {e}"}
|
|
254
|
+
|
|
255
|
+
command_parts.extend(["--tool", tool_name])
|
|
256
|
+
|
|
257
|
+
# Add parameters as JSON input
|
|
258
|
+
input_json = json.dumps(params)
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
result = subprocess.run(
|
|
262
|
+
command_parts,
|
|
263
|
+
shell=False,
|
|
264
|
+
input=input_json,
|
|
265
|
+
capture_output=True,
|
|
266
|
+
text=True,
|
|
267
|
+
timeout=30
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if result.returncode == 0:
|
|
271
|
+
# Try to parse JSON output
|
|
272
|
+
try:
|
|
273
|
+
return json.loads(result.stdout)
|
|
274
|
+
except json.JSONDecodeError:
|
|
275
|
+
return {"output": result.stdout}
|
|
276
|
+
else:
|
|
277
|
+
return {"error": result.stderr or f"Tool exited with code {result.returncode}"}
|
|
278
|
+
|
|
279
|
+
except subprocess.TimeoutExpired:
|
|
280
|
+
return {"error": "Tool execution timed out"}
|
|
281
|
+
except Exception as e:
|
|
282
|
+
return {"error": f"Failed to execute tool: {e}"}
|
|
283
|
+
|
|
284
|
+
async def _execute_http_tool(self, server_config: Dict, tool_name: str,
|
|
285
|
+
params: Dict) -> Dict[str, Any]:
|
|
286
|
+
"""Execute a tool via HTTP MCP server.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
server_config: Server configuration
|
|
290
|
+
tool_name: Tool to execute
|
|
291
|
+
params: Tool parameters
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Tool execution result
|
|
295
|
+
"""
|
|
296
|
+
# This would implement HTTP-based MCP tool calls
|
|
297
|
+
# For now, return a placeholder
|
|
298
|
+
return {
|
|
299
|
+
"status": "not_implemented",
|
|
300
|
+
"message": "HTTP MCP servers not yet implemented"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async def _execute_server_command(self, command: str, *args) -> Optional[str]:
|
|
304
|
+
"""Execute a server command and return output.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
command: Base command to execute
|
|
308
|
+
*args: Additional arguments
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Command output or None if failed
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
full_command = [command] + list(args)
|
|
315
|
+
result = subprocess.run(
|
|
316
|
+
full_command,
|
|
317
|
+
capture_output=True,
|
|
318
|
+
text=True,
|
|
319
|
+
timeout=5
|
|
320
|
+
)
|
|
321
|
+
if result.returncode == 0:
|
|
322
|
+
return result.stdout
|
|
323
|
+
return None
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.warning(f"Failed to execute server command: {e}")
|
|
326
|
+
return None
|
|
327
|
+
|
|
328
|
+
def list_available_tools(self) -> List[Dict[str, Any]]:
|
|
329
|
+
"""List all available MCP tools.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
List of available tools with their information
|
|
333
|
+
"""
|
|
334
|
+
tools = []
|
|
335
|
+
for tool_name, tool_info in self.tool_registry.items():
|
|
336
|
+
tools.append({
|
|
337
|
+
"name": tool_name,
|
|
338
|
+
"server": tool_info["server"],
|
|
339
|
+
"enabled": tool_info["enabled"],
|
|
340
|
+
"definition": tool_info.get("definition", {})
|
|
341
|
+
})
|
|
342
|
+
return tools
|
|
343
|
+
|
|
344
|
+
def enable_tool(self, tool_name: str) -> bool:
|
|
345
|
+
"""Enable an MCP tool.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
tool_name: Name of the tool to enable
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if tool was enabled
|
|
352
|
+
"""
|
|
353
|
+
if tool_name in self.tool_registry:
|
|
354
|
+
self.tool_registry[tool_name]["enabled"] = True
|
|
355
|
+
logger.info(f"Enabled MCP tool: {tool_name}")
|
|
356
|
+
return True
|
|
357
|
+
return False
|
|
358
|
+
|
|
359
|
+
def disable_tool(self, tool_name: str) -> bool:
|
|
360
|
+
"""Disable an MCP tool.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
tool_name: Name of the tool to disable
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
True if tool was disabled
|
|
367
|
+
"""
|
|
368
|
+
if tool_name in self.tool_registry:
|
|
369
|
+
self.tool_registry[tool_name]["enabled"] = False
|
|
370
|
+
logger.info(f"Disabled MCP tool: {tool_name}")
|
|
371
|
+
return True
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
async def shutdown(self):
|
|
375
|
+
"""Shutdown MCP integration and close connections."""
|
|
376
|
+
# Close any active connections
|
|
377
|
+
for connection_id, connection in self.active_connections.items():
|
|
378
|
+
try:
|
|
379
|
+
if hasattr(connection, 'close'):
|
|
380
|
+
await connection.close()
|
|
381
|
+
logger.debug(f"Closed MCP connection: {connection_id}")
|
|
382
|
+
except Exception as e:
|
|
383
|
+
logger.warning(f"Error closing MCP connection {connection_id}: {e}")
|
|
384
|
+
|
|
385
|
+
self.active_connections.clear()
|
|
386
|
+
logger.info("MCP Integration shutdown complete")
|