kollabor 0.4.9__py3-none-any.whl → 0.4.15__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 (192) hide show
  1. agents/__init__.py +2 -0
  2. agents/coder/__init__.py +0 -0
  3. agents/coder/agent.json +4 -0
  4. agents/coder/api-integration.md +2150 -0
  5. agents/coder/cli-pretty.md +765 -0
  6. agents/coder/code-review.md +1092 -0
  7. agents/coder/database-design.md +1525 -0
  8. agents/coder/debugging.md +1102 -0
  9. agents/coder/dependency-management.md +1397 -0
  10. agents/coder/git-workflow.md +1099 -0
  11. agents/coder/refactoring.md +1454 -0
  12. agents/coder/security-hardening.md +1732 -0
  13. agents/coder/system_prompt.md +1448 -0
  14. agents/coder/tdd.md +1367 -0
  15. agents/creative-writer/__init__.py +0 -0
  16. agents/creative-writer/agent.json +4 -0
  17. agents/creative-writer/character-development.md +1852 -0
  18. agents/creative-writer/dialogue-craft.md +1122 -0
  19. agents/creative-writer/plot-structure.md +1073 -0
  20. agents/creative-writer/revision-editing.md +1484 -0
  21. agents/creative-writer/system_prompt.md +690 -0
  22. agents/creative-writer/worldbuilding.md +2049 -0
  23. agents/data-analyst/__init__.py +30 -0
  24. agents/data-analyst/agent.json +4 -0
  25. agents/data-analyst/data-visualization.md +992 -0
  26. agents/data-analyst/exploratory-data-analysis.md +1110 -0
  27. agents/data-analyst/pandas-data-manipulation.md +1081 -0
  28. agents/data-analyst/sql-query-optimization.md +881 -0
  29. agents/data-analyst/statistical-analysis.md +1118 -0
  30. agents/data-analyst/system_prompt.md +928 -0
  31. agents/default/__init__.py +0 -0
  32. agents/default/agent.json +4 -0
  33. agents/default/dead-code.md +794 -0
  34. agents/default/explore-agent-system.md +585 -0
  35. agents/default/system_prompt.md +1448 -0
  36. agents/kollabor/__init__.py +0 -0
  37. agents/kollabor/analyze-plugin-lifecycle.md +175 -0
  38. agents/kollabor/analyze-terminal-rendering.md +388 -0
  39. agents/kollabor/code-review.md +1092 -0
  40. agents/kollabor/debug-mcp-integration.md +521 -0
  41. agents/kollabor/debug-plugin-hooks.md +547 -0
  42. agents/kollabor/debugging.md +1102 -0
  43. agents/kollabor/dependency-management.md +1397 -0
  44. agents/kollabor/git-workflow.md +1099 -0
  45. agents/kollabor/inspect-llm-conversation.md +148 -0
  46. agents/kollabor/monitor-event-bus.md +558 -0
  47. agents/kollabor/profile-performance.md +576 -0
  48. agents/kollabor/refactoring.md +1454 -0
  49. agents/kollabor/system_prompt copy.md +1448 -0
  50. agents/kollabor/system_prompt.md +757 -0
  51. agents/kollabor/trace-command-execution.md +178 -0
  52. agents/kollabor/validate-config.md +879 -0
  53. agents/research/__init__.py +0 -0
  54. agents/research/agent.json +4 -0
  55. agents/research/architecture-mapping.md +1099 -0
  56. agents/research/codebase-analysis.md +1077 -0
  57. agents/research/dependency-audit.md +1027 -0
  58. agents/research/performance-profiling.md +1047 -0
  59. agents/research/security-review.md +1359 -0
  60. agents/research/system_prompt.md +492 -0
  61. agents/technical-writer/__init__.py +0 -0
  62. agents/technical-writer/agent.json +4 -0
  63. agents/technical-writer/api-documentation.md +2328 -0
  64. agents/technical-writer/changelog-management.md +1181 -0
  65. agents/technical-writer/readme-writing.md +1360 -0
  66. agents/technical-writer/style-guide.md +1410 -0
  67. agents/technical-writer/system_prompt.md +653 -0
  68. agents/technical-writer/tutorial-creation.md +1448 -0
  69. core/__init__.py +0 -2
  70. core/application.py +343 -88
  71. core/cli.py +229 -10
  72. core/commands/menu_renderer.py +463 -59
  73. core/commands/registry.py +14 -9
  74. core/commands/system_commands.py +2461 -14
  75. core/config/loader.py +151 -37
  76. core/config/service.py +18 -6
  77. core/events/bus.py +29 -9
  78. core/events/executor.py +205 -75
  79. core/events/models.py +27 -8
  80. core/fullscreen/command_integration.py +20 -24
  81. core/fullscreen/components/__init__.py +10 -1
  82. core/fullscreen/components/matrix_components.py +1 -2
  83. core/fullscreen/components/space_shooter_components.py +654 -0
  84. core/fullscreen/plugin.py +5 -0
  85. core/fullscreen/renderer.py +52 -13
  86. core/fullscreen/session.py +52 -15
  87. core/io/__init__.py +29 -5
  88. core/io/buffer_manager.py +6 -1
  89. core/io/config_status_view.py +7 -29
  90. core/io/core_status_views.py +267 -347
  91. core/io/input/__init__.py +25 -0
  92. core/io/input/command_mode_handler.py +711 -0
  93. core/io/input/display_controller.py +128 -0
  94. core/io/input/hook_registrar.py +286 -0
  95. core/io/input/input_loop_manager.py +421 -0
  96. core/io/input/key_press_handler.py +502 -0
  97. core/io/input/modal_controller.py +1011 -0
  98. core/io/input/paste_processor.py +339 -0
  99. core/io/input/status_modal_renderer.py +184 -0
  100. core/io/input_errors.py +5 -1
  101. core/io/input_handler.py +211 -2452
  102. core/io/key_parser.py +7 -0
  103. core/io/layout.py +15 -3
  104. core/io/message_coordinator.py +111 -2
  105. core/io/message_renderer.py +129 -4
  106. core/io/status_renderer.py +147 -607
  107. core/io/terminal_renderer.py +97 -51
  108. core/io/terminal_state.py +21 -4
  109. core/io/visual_effects.py +816 -165
  110. core/llm/agent_manager.py +1063 -0
  111. core/llm/api_adapters/__init__.py +44 -0
  112. core/llm/api_adapters/anthropic_adapter.py +432 -0
  113. core/llm/api_adapters/base.py +241 -0
  114. core/llm/api_adapters/openai_adapter.py +326 -0
  115. core/llm/api_communication_service.py +167 -113
  116. core/llm/conversation_logger.py +322 -16
  117. core/llm/conversation_manager.py +556 -30
  118. core/llm/file_operations_executor.py +84 -32
  119. core/llm/llm_service.py +934 -103
  120. core/llm/mcp_integration.py +541 -57
  121. core/llm/message_display_service.py +135 -18
  122. core/llm/plugin_sdk.py +1 -2
  123. core/llm/profile_manager.py +1183 -0
  124. core/llm/response_parser.py +274 -56
  125. core/llm/response_processor.py +16 -3
  126. core/llm/tool_executor.py +6 -1
  127. core/logging/__init__.py +2 -0
  128. core/logging/setup.py +34 -6
  129. core/models/resume.py +54 -0
  130. core/plugins/__init__.py +4 -2
  131. core/plugins/base.py +127 -0
  132. core/plugins/collector.py +23 -161
  133. core/plugins/discovery.py +37 -3
  134. core/plugins/factory.py +6 -12
  135. core/plugins/registry.py +5 -17
  136. core/ui/config_widgets.py +128 -28
  137. core/ui/live_modal_renderer.py +2 -1
  138. core/ui/modal_actions.py +5 -0
  139. core/ui/modal_overlay_renderer.py +0 -60
  140. core/ui/modal_renderer.py +268 -7
  141. core/ui/modal_state_manager.py +29 -4
  142. core/ui/widgets/base_widget.py +7 -0
  143. core/updates/__init__.py +10 -0
  144. core/updates/version_check_service.py +348 -0
  145. core/updates/version_comparator.py +103 -0
  146. core/utils/config_utils.py +685 -526
  147. core/utils/plugin_utils.py +1 -1
  148. core/utils/session_naming.py +111 -0
  149. fonts/LICENSE +21 -0
  150. fonts/README.md +46 -0
  151. fonts/SymbolsNerdFont-Regular.ttf +0 -0
  152. fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
  153. fonts/__init__.py +44 -0
  154. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
  155. kollabor-0.4.15.dist-info/RECORD +228 -0
  156. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
  157. plugins/agent_orchestrator/__init__.py +39 -0
  158. plugins/agent_orchestrator/activity_monitor.py +181 -0
  159. plugins/agent_orchestrator/file_attacher.py +77 -0
  160. plugins/agent_orchestrator/message_injector.py +135 -0
  161. plugins/agent_orchestrator/models.py +48 -0
  162. plugins/agent_orchestrator/orchestrator.py +403 -0
  163. plugins/agent_orchestrator/plugin.py +976 -0
  164. plugins/agent_orchestrator/xml_parser.py +191 -0
  165. plugins/agent_orchestrator_plugin.py +9 -0
  166. plugins/enhanced_input/box_styles.py +1 -0
  167. plugins/enhanced_input/color_engine.py +19 -4
  168. plugins/enhanced_input/config.py +2 -2
  169. plugins/enhanced_input_plugin.py +61 -11
  170. plugins/fullscreen/__init__.py +6 -2
  171. plugins/fullscreen/example_plugin.py +1035 -222
  172. plugins/fullscreen/setup_wizard_plugin.py +592 -0
  173. plugins/fullscreen/space_shooter_plugin.py +131 -0
  174. plugins/hook_monitoring_plugin.py +436 -78
  175. plugins/query_enhancer_plugin.py +66 -30
  176. plugins/resume_conversation_plugin.py +1494 -0
  177. plugins/save_conversation_plugin.py +98 -32
  178. plugins/system_commands_plugin.py +70 -56
  179. plugins/tmux_plugin.py +154 -78
  180. plugins/workflow_enforcement_plugin.py +94 -92
  181. system_prompt/default.md +952 -886
  182. core/io/input_mode_manager.py +0 -402
  183. core/io/modal_interaction_handler.py +0 -315
  184. core/io/raw_input_processor.py +0 -946
  185. core/storage/__init__.py +0 -5
  186. core/storage/state_manager.py +0 -84
  187. core/ui/widget_integration.py +0 -222
  188. core/utils/key_reader.py +0 -171
  189. kollabor-0.4.9.dist-info/RECORD +0 -128
  190. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
  191. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
  192. {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,11 @@
2
2
 
3
3
  Provides integration with MCP servers for tool discovery and execution,
4
4
  enabling the LLM to interact with external tools and services.
5
+
6
+ Implements MCP JSON-RPC 2.0 protocol over stdio for:
7
+ - Server initialization handshake
8
+ - tools/list for tool discovery
9
+ - tools/call for tool execution
5
10
  """
6
11
 
7
12
  import asyncio
@@ -10,32 +15,208 @@ import logging
10
15
  import re
11
16
  import shlex
12
17
  import subprocess
18
+ import uuid
13
19
  from pathlib import Path
14
- from typing import Any, Dict, List, Optional
20
+ from typing import Any, Dict, List, Optional, Tuple
15
21
 
16
22
  logger = logging.getLogger(__name__)
17
23
 
18
24
 
25
+ class MCPServerConnection:
26
+ """Manages a connection to an MCP server via stdio."""
27
+
28
+ def __init__(self, server_name: str, command: str):
29
+ self.server_name = server_name
30
+ self.command = command
31
+ self.process: Optional[asyncio.subprocess.Process] = None
32
+ self.initialized = False
33
+ self._read_buffer = ""
34
+
35
+ async def connect(self) -> bool:
36
+ """Start the MCP server process."""
37
+ try:
38
+ command_parts = shlex.split(self.command)
39
+ self.process = await asyncio.create_subprocess_exec(
40
+ *command_parts,
41
+ stdin=asyncio.subprocess.PIPE,
42
+ stdout=asyncio.subprocess.PIPE,
43
+ stderr=asyncio.subprocess.PIPE
44
+ )
45
+ logger.info(f"Started MCP server process: {self.server_name}")
46
+ return True
47
+ except Exception as e:
48
+ logger.error(f"Failed to start MCP server {self.server_name}: {e}")
49
+ return False
50
+
51
+ async def initialize(self) -> bool:
52
+ """Send MCP initialize request and wait for response."""
53
+ if not self.process:
54
+ return False
55
+
56
+ request = {
57
+ "jsonrpc": "2.0",
58
+ "id": str(uuid.uuid4()),
59
+ "method": "initialize",
60
+ "params": {
61
+ "protocolVersion": "2024-11-05",
62
+ "capabilities": {},
63
+ "clientInfo": {
64
+ "name": "kollabor-cli",
65
+ "version": "1.0.0"
66
+ }
67
+ }
68
+ }
69
+
70
+ response = await self._send_request(request)
71
+ if response and "result" in response:
72
+ self.initialized = True
73
+ logger.info(f"MCP server {self.server_name} initialized")
74
+
75
+ # Send initialized notification
76
+ notification = {
77
+ "jsonrpc": "2.0",
78
+ "method": "notifications/initialized"
79
+ }
80
+ await self._send_notification(notification)
81
+ return True
82
+
83
+ logger.warning(f"MCP server {self.server_name} initialization failed")
84
+ return False
85
+
86
+ async def list_tools(self) -> List[Dict[str, Any]]:
87
+ """Request tools list from server."""
88
+ if not self.initialized:
89
+ return []
90
+
91
+ request = {
92
+ "jsonrpc": "2.0",
93
+ "id": str(uuid.uuid4()),
94
+ "method": "tools/list",
95
+ "params": {}
96
+ }
97
+
98
+ response = await self._send_request(request)
99
+ if response and "result" in response:
100
+ tools = response["result"].get("tools", [])
101
+ logger.info(f"Got {len(tools)} tools from {self.server_name}")
102
+ return tools
103
+
104
+ return []
105
+
106
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
107
+ """Call a tool on the server."""
108
+ if not self.initialized:
109
+ return {"error": "Server not initialized"}
110
+
111
+ request = {
112
+ "jsonrpc": "2.0",
113
+ "id": str(uuid.uuid4()),
114
+ "method": "tools/call",
115
+ "params": {
116
+ "name": tool_name,
117
+ "arguments": arguments
118
+ }
119
+ }
120
+
121
+ response = await self._send_request(request)
122
+ if response:
123
+ if "result" in response:
124
+ return response["result"]
125
+ elif "error" in response:
126
+ return {"error": response["error"].get("message", "Unknown error")}
127
+
128
+ return {"error": "No response from server"}
129
+
130
+ async def _send_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
131
+ """Send JSON-RPC request and wait for response."""
132
+ if not self.process or not self.process.stdin:
133
+ return None
134
+
135
+ try:
136
+ message = json.dumps(request) + "\n"
137
+ self.process.stdin.write(message.encode())
138
+ await self.process.stdin.drain()
139
+
140
+ # Read response with timeout
141
+ response = await asyncio.wait_for(self._read_response(), timeout=30)
142
+ return response
143
+ except asyncio.TimeoutError:
144
+ logger.warning(f"Timeout waiting for response from {self.server_name}")
145
+ return None
146
+ except Exception as e:
147
+ logger.error(f"Error sending request to {self.server_name}: {e}")
148
+ return None
149
+
150
+ async def _send_notification(self, notification: Dict[str, Any]) -> None:
151
+ """Send JSON-RPC notification (no response expected)."""
152
+ if not self.process or not self.process.stdin:
153
+ return
154
+
155
+ try:
156
+ message = json.dumps(notification) + "\n"
157
+ self.process.stdin.write(message.encode())
158
+ await self.process.stdin.drain()
159
+ except Exception as e:
160
+ logger.error(f"Error sending notification to {self.server_name}: {e}")
161
+
162
+ async def _read_response(self) -> Optional[Dict[str, Any]]:
163
+ """Read a JSON-RPC response from stdout."""
164
+ if not self.process or not self.process.stdout:
165
+ return None
166
+
167
+ while True:
168
+ # Check if we have a complete message in buffer
169
+ if "\n" in self._read_buffer:
170
+ line, self._read_buffer = self._read_buffer.split("\n", 1)
171
+ if line.strip():
172
+ try:
173
+ return json.loads(line)
174
+ except json.JSONDecodeError:
175
+ continue
176
+
177
+ # Read more data
178
+ chunk = await self.process.stdout.read(4096)
179
+ if not chunk:
180
+ return None
181
+ self._read_buffer += chunk.decode()
182
+
183
+ async def close(self) -> None:
184
+ """Close the server connection."""
185
+ if self.process:
186
+ try:
187
+ self.process.terminate()
188
+ await asyncio.wait_for(self.process.wait(), timeout=5)
189
+ except asyncio.TimeoutError:
190
+ self.process.kill()
191
+ except Exception as e:
192
+ logger.warning(f"Error closing MCP server {self.server_name}: {e}")
193
+ self.process = None
194
+ self.initialized = False
195
+ logger.info(f"Closed MCP server connection: {self.server_name}")
196
+
197
+
19
198
  class MCPIntegration:
20
199
  """MCP server and tool integration.
21
-
200
+
22
201
  Manages discovery, registration, and execution of MCP tools,
23
202
  bridging external services with the LLM core service.
203
+
204
+ Uses proper MCP JSON-RPC protocol for server communication.
24
205
  """
25
-
206
+
26
207
  def __init__(self):
27
208
  """Initialize MCP integration."""
28
- self.mcp_servers = {}
29
- self.tool_registry = {}
30
- self.active_connections = {}
31
-
209
+ self.mcp_servers: Dict[str, Dict[str, Any]] = {}
210
+ self.tool_registry: Dict[str, Dict[str, Any]] = {}
211
+ self.server_connections: Dict[str, MCPServerConnection] = {}
212
+
32
213
  # MCP configuration directories (local project first, then global)
33
214
  self.local_mcp_dir = Path.cwd() / ".kollabor-cli" / "mcp"
34
215
  self.global_mcp_dir = Path.home() / ".kollabor-cli" / "mcp"
35
-
216
+
36
217
  # Load from both local and global configs
37
218
  self._load_mcp_config()
38
-
219
+
39
220
  logger.info("MCP Integration initialized")
40
221
 
41
222
  def _load_mcp_config(self):
@@ -50,7 +231,7 @@ class MCPIntegration:
50
231
 
51
232
  def _load_config_from_dir(self, config_dir: Path, config_type: str):
52
233
  """Load MCP config from a specific directory.
53
-
234
+
54
235
  Args:
55
236
  config_dir: Directory to load config from
56
237
  config_type: Type of config (local/global) for logging
@@ -67,28 +248,97 @@ class MCPIntegration:
67
248
  logger.warning(f"Failed to load {config_type} MCP config: {e}")
68
249
 
69
250
  async def discover_mcp_servers(self) -> Dict[str, Any]:
70
- """Auto-discover available MCP servers.
71
-
251
+ """Auto-discover available MCP servers and their tools.
252
+
253
+ Connects to each configured stdio server using MCP protocol,
254
+ initializes it, and queries available tools.
255
+
72
256
  Returns:
73
257
  Dictionary of discovered MCP servers and their capabilities
74
258
  """
75
259
  discovered = {}
76
-
77
- # Check for local MCP servers
260
+
261
+ # Check for local MCP servers (manifest-based)
78
262
  await self._discover_local_servers(discovered)
79
-
80
- # Check for configured servers
263
+
264
+ # Connect to configured stdio servers using MCP protocol
81
265
  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
-
266
+ if not server_config.get("enabled", True):
267
+ logger.debug(f"Skipping disabled MCP server: {server_name}")
268
+ continue
269
+
270
+ if server_config.get("type") == "stdio":
271
+ command = server_config.get("command")
272
+ if command:
273
+ tools = await self._connect_and_list_tools(server_name, command)
274
+ discovered[server_name] = {
275
+ "name": server_name,
276
+ "type": "stdio",
277
+ "tools": [t.get("name") for t in tools],
278
+ "tool_count": len(tools),
279
+ "status": "connected" if tools else "no_tools"
280
+ }
281
+
91
282
  return discovered
283
+
284
+ async def _connect_and_list_tools(self, server_name: str, command: str) -> List[Dict[str, Any]]:
285
+ """Connect to an MCP server and list its tools.
286
+
287
+ Uses proper MCP JSON-RPC protocol:
288
+ 1. Start server process
289
+ 2. Send initialize request
290
+ 3. Send tools/list request
291
+ 4. Register discovered tools
292
+
293
+ Args:
294
+ server_name: Name of the server
295
+ command: Command to start the server
296
+
297
+ Returns:
298
+ List of tool definitions from the server
299
+ """
300
+ # Close existing connection if any
301
+ if server_name in self.server_connections:
302
+ await self.server_connections[server_name].close()
303
+
304
+ # Create new connection
305
+ connection = MCPServerConnection(server_name, command)
306
+
307
+ if not await connection.connect():
308
+ logger.warning(f"Failed to connect to MCP server: {server_name}")
309
+ return []
310
+
311
+ if not await connection.initialize():
312
+ logger.warning(f"Failed to initialize MCP server: {server_name}")
313
+ await connection.close()
314
+ return []
315
+
316
+ # List tools
317
+ tools = await connection.list_tools()
318
+
319
+ # Register tools
320
+ for tool in tools:
321
+ tool_name = tool.get("name")
322
+ if tool_name:
323
+ self.tool_registry[tool_name] = {
324
+ "server": server_name,
325
+ "definition": {
326
+ "name": tool_name,
327
+ "description": tool.get("description", ""),
328
+ "parameters": tool.get("inputSchema", {
329
+ "type": "object",
330
+ "properties": {},
331
+ "required": []
332
+ })
333
+ },
334
+ "enabled": True
335
+ }
336
+ logger.info(f"Registered MCP tool: {tool_name} from {server_name}")
337
+
338
+ # Keep connection open for tool calls
339
+ self.server_connections[server_name] = connection
340
+
341
+ return tools
92
342
 
93
343
  async def _discover_local_servers(self, discovered: Dict):
94
344
  """Discover locally running MCP servers."""
@@ -184,12 +434,12 @@ class MCPIntegration:
184
434
  return False
185
435
 
186
436
  async def call_mcp_tool(self, tool_name: str, params: Dict[str, Any]) -> Dict[str, Any]:
187
- """Execute an MCP tool call.
188
-
437
+ """Execute an MCP tool call using proper MCP protocol.
438
+
189
439
  Args:
190
440
  tool_name: Name of the tool to execute
191
441
  params: Parameters for the tool
192
-
442
+
193
443
  Returns:
194
444
  Tool execution result
195
445
  """
@@ -198,28 +448,30 @@ class MCPIntegration:
198
448
  "error": f"Tool '{tool_name}' not found",
199
449
  "available_tools": list(self.tool_registry.keys())
200
450
  }
201
-
451
+
202
452
  tool_info = self.tool_registry[tool_name]
203
453
  server_name = tool_info["server"]
204
-
454
+
205
455
  if not tool_info["enabled"]:
206
456
  return {"error": f"Tool '{tool_name}' is disabled"}
207
-
208
- try:
209
- # Get server configuration
457
+
458
+ # Get active connection
459
+ connection = self.server_connections.get(server_name)
460
+ if not connection or not connection.initialized:
461
+ # Try to reconnect
210
462
  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
-
463
+ command = server_config.get("command")
464
+ if command:
465
+ await self._connect_and_list_tools(server_name, command)
466
+ connection = self.server_connections.get(server_name)
467
+
468
+ if not connection or not connection.initialized:
469
+ return {"error": f"No active connection to server '{server_name}'"}
470
+
471
+ try:
472
+ result = await connection.call_tool(tool_name, params)
220
473
  logger.info(f"Executed MCP tool: {tool_name}")
221
474
  return result
222
-
223
475
  except Exception as e:
224
476
  logger.error(f"Failed to execute MCP tool {tool_name}: {e}")
225
477
  return {"error": str(e)}
@@ -257,8 +509,9 @@ class MCPIntegration:
257
509
  # Add parameters as JSON input
258
510
  input_json = json.dumps(params)
259
511
 
260
- try:
261
- result = subprocess.run(
512
+ def run_subprocess():
513
+ """Run subprocess in thread to avoid blocking event loop."""
514
+ return subprocess.run(
262
515
  command_parts,
263
516
  shell=False,
264
517
  input=input_json,
@@ -266,7 +519,12 @@ class MCPIntegration:
266
519
  text=True,
267
520
  timeout=30
268
521
  )
269
-
522
+
523
+ try:
524
+ # Run blocking subprocess in executor to not freeze event loop
525
+ loop = asyncio.get_event_loop()
526
+ result = await loop.run_in_executor(None, run_subprocess)
527
+
270
528
  if result.returncode == 0:
271
529
  # Try to parse JSON output
272
530
  try:
@@ -275,7 +533,7 @@ class MCPIntegration:
275
533
  return {"output": result.stdout}
276
534
  else:
277
535
  return {"error": result.stderr or f"Tool exited with code {result.returncode}"}
278
-
536
+
279
537
  except subprocess.TimeoutExpired:
280
538
  return {"error": "Tool execution timed out"}
281
539
  except Exception as e:
@@ -327,7 +585,7 @@ class MCPIntegration:
327
585
 
328
586
  def list_available_tools(self) -> List[Dict[str, Any]]:
329
587
  """List all available MCP tools.
330
-
588
+
331
589
  Returns:
332
590
  List of available tools with their information
333
591
  """
@@ -340,6 +598,233 @@ class MCPIntegration:
340
598
  "definition": tool_info.get("definition", {})
341
599
  })
342
600
  return tools
601
+
602
+ def get_tool_definitions_for_api(self) -> List[Dict[str, Any]]:
603
+ """Convert registered MCP tools to API tool schema format.
604
+
605
+ Returns generic format that adapters (OpenAI/Anthropic) auto-convert:
606
+ - OpenAI wraps in: {type: "function", function: {...}}
607
+ - Anthropic uses: {name, description, input_schema}
608
+
609
+ Returns:
610
+ List of tool definitions in generic API format
611
+ """
612
+ tools = []
613
+
614
+ # Add MCP tools from registry
615
+ for tool_name, tool_info in self.tool_registry.items():
616
+ if not tool_info.get("enabled", True):
617
+ continue
618
+
619
+ definition = tool_info.get("definition", {})
620
+ tools.append({
621
+ "name": tool_name,
622
+ "description": definition.get("description", f"MCP tool: {tool_name}"),
623
+ "parameters": definition.get("parameters", definition.get("inputSchema", {
624
+ "type": "object",
625
+ "properties": {},
626
+ "required": []
627
+ }))
628
+ })
629
+
630
+ # Add built-in file operation tools
631
+ tools.extend(self._get_file_operation_tools())
632
+
633
+ logger.debug(f"Prepared {len(tools)} tools for API ({len(self.tool_registry)} MCP + file ops)")
634
+ return tools
635
+
636
+ def _get_file_operation_tools(self) -> List[Dict[str, Any]]:
637
+ """Get built-in file operation tool definitions.
638
+
639
+ These tools provide file manipulation capabilities that work alongside
640
+ MCP tools. They match the XML-based file operations but as native functions.
641
+
642
+ Returns:
643
+ List of file operation tool definitions
644
+ """
645
+ return [
646
+ {
647
+ "name": "file_read",
648
+ "description": "Read content from a file. Use this to examine existing files before editing.",
649
+ "parameters": {
650
+ "type": "object",
651
+ "properties": {
652
+ "file": {"type": "string", "description": "Relative file path to read"},
653
+ "offset": {"type": "integer", "description": "Line offset to start reading from (0-indexed, optional)"},
654
+ "limit": {"type": "integer", "description": "Number of lines to read (optional)"}
655
+ },
656
+ "required": ["file"]
657
+ }
658
+ },
659
+ {
660
+ "name": "file_create",
661
+ "description": "Create a new file with content. Fails if file already exists.",
662
+ "parameters": {
663
+ "type": "object",
664
+ "properties": {
665
+ "file": {"type": "string", "description": "Relative file path to create"},
666
+ "content": {"type": "string", "description": "Content to write to the file"}
667
+ },
668
+ "required": ["file", "content"]
669
+ }
670
+ },
671
+ {
672
+ "name": "file_create_overwrite",
673
+ "description": "Create or overwrite a file with content. Creates backup if file exists.",
674
+ "parameters": {
675
+ "type": "object",
676
+ "properties": {
677
+ "file": {"type": "string", "description": "Relative file path to create/overwrite"},
678
+ "content": {"type": "string", "description": "Content to write to the file"}
679
+ },
680
+ "required": ["file", "content"]
681
+ }
682
+ },
683
+ {
684
+ "name": "file_edit",
685
+ "description": "Find and replace text in a file. Replaces ALL occurrences of the pattern.",
686
+ "parameters": {
687
+ "type": "object",
688
+ "properties": {
689
+ "file": {"type": "string", "description": "Relative file path to edit"},
690
+ "find": {"type": "string", "description": "Text pattern to find (exact match)"},
691
+ "replace": {"type": "string", "description": "Text to replace with"}
692
+ },
693
+ "required": ["file", "find", "replace"]
694
+ }
695
+ },
696
+ {
697
+ "name": "file_append",
698
+ "description": "Append content to the end of a file.",
699
+ "parameters": {
700
+ "type": "object",
701
+ "properties": {
702
+ "file": {"type": "string", "description": "Relative file path to append to"},
703
+ "content": {"type": "string", "description": "Content to append"}
704
+ },
705
+ "required": ["file", "content"]
706
+ }
707
+ },
708
+ {
709
+ "name": "file_insert_after",
710
+ "description": "Insert content after a pattern in a file. Pattern must match exactly.",
711
+ "parameters": {
712
+ "type": "object",
713
+ "properties": {
714
+ "file": {"type": "string", "description": "Relative file path"},
715
+ "pattern": {"type": "string", "description": "Pattern to find (exact match)"},
716
+ "content": {"type": "string", "description": "Content to insert after pattern"}
717
+ },
718
+ "required": ["file", "pattern", "content"]
719
+ }
720
+ },
721
+ {
722
+ "name": "file_insert_before",
723
+ "description": "Insert content before a pattern in a file. Pattern must match exactly.",
724
+ "parameters": {
725
+ "type": "object",
726
+ "properties": {
727
+ "file": {"type": "string", "description": "Relative file path"},
728
+ "pattern": {"type": "string", "description": "Pattern to find (exact match)"},
729
+ "content": {"type": "string", "description": "Content to insert before pattern"}
730
+ },
731
+ "required": ["file", "pattern", "content"]
732
+ }
733
+ },
734
+ {
735
+ "name": "file_delete",
736
+ "description": "Delete a file. Creates backup before deletion.",
737
+ "parameters": {
738
+ "type": "object",
739
+ "properties": {
740
+ "file": {"type": "string", "description": "Relative file path to delete"}
741
+ },
742
+ "required": ["file"]
743
+ }
744
+ },
745
+ {
746
+ "name": "file_move",
747
+ "description": "Move or rename a file.",
748
+ "parameters": {
749
+ "type": "object",
750
+ "properties": {
751
+ "from": {"type": "string", "description": "Source file path"},
752
+ "to": {"type": "string", "description": "Destination file path"}
753
+ },
754
+ "required": ["from", "to"]
755
+ }
756
+ },
757
+ {
758
+ "name": "file_copy",
759
+ "description": "Copy a file. Fails if destination exists.",
760
+ "parameters": {
761
+ "type": "object",
762
+ "properties": {
763
+ "from": {"type": "string", "description": "Source file path"},
764
+ "to": {"type": "string", "description": "Destination file path"}
765
+ },
766
+ "required": ["from", "to"]
767
+ }
768
+ },
769
+ {
770
+ "name": "file_copy_overwrite",
771
+ "description": "Copy a file, overwriting destination if it exists.",
772
+ "parameters": {
773
+ "type": "object",
774
+ "properties": {
775
+ "from": {"type": "string", "description": "Source file path"},
776
+ "to": {"type": "string", "description": "Destination file path"}
777
+ },
778
+ "required": ["from", "to"]
779
+ }
780
+ },
781
+ {
782
+ "name": "file_mkdir",
783
+ "description": "Create a directory (including parent directories).",
784
+ "parameters": {
785
+ "type": "object",
786
+ "properties": {
787
+ "path": {"type": "string", "description": "Directory path to create"}
788
+ },
789
+ "required": ["path"]
790
+ }
791
+ },
792
+ {
793
+ "name": "file_rmdir",
794
+ "description": "Remove an empty directory.",
795
+ "parameters": {
796
+ "type": "object",
797
+ "properties": {
798
+ "path": {"type": "string", "description": "Directory path to remove"}
799
+ },
800
+ "required": ["path"]
801
+ }
802
+ },
803
+ {
804
+ "name": "file_grep",
805
+ "description": "Search for a pattern in a file and return matching lines.",
806
+ "parameters": {
807
+ "type": "object",
808
+ "properties": {
809
+ "file": {"type": "string", "description": "Relative file path to search"},
810
+ "pattern": {"type": "string", "description": "Text pattern to search for"},
811
+ "case_insensitive": {"type": "boolean", "description": "Whether to ignore case (default: false)"}
812
+ },
813
+ "required": ["file", "pattern"]
814
+ }
815
+ },
816
+ {
817
+ "name": "terminal",
818
+ "description": "Execute a terminal/shell command and return the output.",
819
+ "parameters": {
820
+ "type": "object",
821
+ "properties": {
822
+ "command": {"type": "string", "description": "Shell command to execute"}
823
+ },
824
+ "required": ["command"]
825
+ }
826
+ }
827
+ ]
343
828
 
344
829
  def enable_tool(self, tool_name: str) -> bool:
345
830
  """Enable an MCP tool.
@@ -372,15 +857,14 @@ class MCPIntegration:
372
857
  return False
373
858
 
374
859
  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():
860
+ """Shutdown MCP integration and close all server connections."""
861
+ for server_name, connection in self.server_connections.items():
378
862
  try:
379
- if hasattr(connection, 'close'):
380
- await connection.close()
381
- logger.debug(f"Closed MCP connection: {connection_id}")
863
+ await connection.close()
864
+ logger.debug(f"Closed MCP server connection: {server_name}")
382
865
  except Exception as e:
383
- logger.warning(f"Error closing MCP connection {connection_id}: {e}")
384
-
385
- self.active_connections.clear()
866
+ logger.warning(f"Error closing MCP connection {server_name}: {e}")
867
+
868
+ self.server_connections.clear()
869
+ self.tool_registry.clear()
386
870
  logger.info("MCP Integration shutdown complete")