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.
Files changed (128) hide show
  1. core/__init__.py +18 -0
  2. core/application.py +578 -0
  3. core/cli.py +193 -0
  4. core/commands/__init__.py +43 -0
  5. core/commands/executor.py +277 -0
  6. core/commands/menu_renderer.py +319 -0
  7. core/commands/parser.py +186 -0
  8. core/commands/registry.py +331 -0
  9. core/commands/system_commands.py +479 -0
  10. core/config/__init__.py +7 -0
  11. core/config/llm_task_config.py +110 -0
  12. core/config/loader.py +501 -0
  13. core/config/manager.py +112 -0
  14. core/config/plugin_config_manager.py +346 -0
  15. core/config/plugin_schema.py +424 -0
  16. core/config/service.py +399 -0
  17. core/effects/__init__.py +1 -0
  18. core/events/__init__.py +12 -0
  19. core/events/bus.py +129 -0
  20. core/events/executor.py +154 -0
  21. core/events/models.py +258 -0
  22. core/events/processor.py +176 -0
  23. core/events/registry.py +289 -0
  24. core/fullscreen/__init__.py +19 -0
  25. core/fullscreen/command_integration.py +290 -0
  26. core/fullscreen/components/__init__.py +12 -0
  27. core/fullscreen/components/animation.py +258 -0
  28. core/fullscreen/components/drawing.py +160 -0
  29. core/fullscreen/components/matrix_components.py +177 -0
  30. core/fullscreen/manager.py +302 -0
  31. core/fullscreen/plugin.py +204 -0
  32. core/fullscreen/renderer.py +282 -0
  33. core/fullscreen/session.py +324 -0
  34. core/io/__init__.py +52 -0
  35. core/io/buffer_manager.py +362 -0
  36. core/io/config_status_view.py +272 -0
  37. core/io/core_status_views.py +410 -0
  38. core/io/input_errors.py +313 -0
  39. core/io/input_handler.py +2655 -0
  40. core/io/input_mode_manager.py +402 -0
  41. core/io/key_parser.py +344 -0
  42. core/io/layout.py +587 -0
  43. core/io/message_coordinator.py +204 -0
  44. core/io/message_renderer.py +601 -0
  45. core/io/modal_interaction_handler.py +315 -0
  46. core/io/raw_input_processor.py +946 -0
  47. core/io/status_renderer.py +845 -0
  48. core/io/terminal_renderer.py +586 -0
  49. core/io/terminal_state.py +551 -0
  50. core/io/visual_effects.py +734 -0
  51. core/llm/__init__.py +26 -0
  52. core/llm/api_communication_service.py +863 -0
  53. core/llm/conversation_logger.py +473 -0
  54. core/llm/conversation_manager.py +414 -0
  55. core/llm/file_operations_executor.py +1401 -0
  56. core/llm/hook_system.py +402 -0
  57. core/llm/llm_service.py +1629 -0
  58. core/llm/mcp_integration.py +386 -0
  59. core/llm/message_display_service.py +450 -0
  60. core/llm/model_router.py +214 -0
  61. core/llm/plugin_sdk.py +396 -0
  62. core/llm/response_parser.py +848 -0
  63. core/llm/response_processor.py +364 -0
  64. core/llm/tool_executor.py +520 -0
  65. core/logging/__init__.py +19 -0
  66. core/logging/setup.py +208 -0
  67. core/models/__init__.py +5 -0
  68. core/models/base.py +23 -0
  69. core/plugins/__init__.py +13 -0
  70. core/plugins/collector.py +212 -0
  71. core/plugins/discovery.py +386 -0
  72. core/plugins/factory.py +263 -0
  73. core/plugins/registry.py +152 -0
  74. core/storage/__init__.py +5 -0
  75. core/storage/state_manager.py +84 -0
  76. core/ui/__init__.py +6 -0
  77. core/ui/config_merger.py +176 -0
  78. core/ui/config_widgets.py +369 -0
  79. core/ui/live_modal_renderer.py +276 -0
  80. core/ui/modal_actions.py +162 -0
  81. core/ui/modal_overlay_renderer.py +373 -0
  82. core/ui/modal_renderer.py +591 -0
  83. core/ui/modal_state_manager.py +443 -0
  84. core/ui/widget_integration.py +222 -0
  85. core/ui/widgets/__init__.py +27 -0
  86. core/ui/widgets/base_widget.py +136 -0
  87. core/ui/widgets/checkbox.py +85 -0
  88. core/ui/widgets/dropdown.py +140 -0
  89. core/ui/widgets/label.py +78 -0
  90. core/ui/widgets/slider.py +185 -0
  91. core/ui/widgets/text_input.py +224 -0
  92. core/utils/__init__.py +11 -0
  93. core/utils/config_utils.py +656 -0
  94. core/utils/dict_utils.py +212 -0
  95. core/utils/error_utils.py +275 -0
  96. core/utils/key_reader.py +171 -0
  97. core/utils/plugin_utils.py +267 -0
  98. core/utils/prompt_renderer.py +151 -0
  99. kollabor-0.4.9.dist-info/METADATA +298 -0
  100. kollabor-0.4.9.dist-info/RECORD +128 -0
  101. kollabor-0.4.9.dist-info/WHEEL +5 -0
  102. kollabor-0.4.9.dist-info/entry_points.txt +2 -0
  103. kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
  104. kollabor-0.4.9.dist-info/top_level.txt +4 -0
  105. kollabor_cli_main.py +20 -0
  106. plugins/__init__.py +1 -0
  107. plugins/enhanced_input/__init__.py +18 -0
  108. plugins/enhanced_input/box_renderer.py +103 -0
  109. plugins/enhanced_input/box_styles.py +142 -0
  110. plugins/enhanced_input/color_engine.py +165 -0
  111. plugins/enhanced_input/config.py +150 -0
  112. plugins/enhanced_input/cursor_manager.py +72 -0
  113. plugins/enhanced_input/geometry.py +81 -0
  114. plugins/enhanced_input/state.py +130 -0
  115. plugins/enhanced_input/text_processor.py +115 -0
  116. plugins/enhanced_input_plugin.py +385 -0
  117. plugins/fullscreen/__init__.py +9 -0
  118. plugins/fullscreen/example_plugin.py +327 -0
  119. plugins/fullscreen/matrix_plugin.py +132 -0
  120. plugins/hook_monitoring_plugin.py +1299 -0
  121. plugins/query_enhancer_plugin.py +350 -0
  122. plugins/save_conversation_plugin.py +502 -0
  123. plugins/system_commands_plugin.py +93 -0
  124. plugins/tmux_plugin.py +795 -0
  125. plugins/workflow_enforcement_plugin.py +629 -0
  126. system_prompt/default.md +1286 -0
  127. system_prompt/default_win.md +265 -0
  128. 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")