signalpilot-ai-internal 0.7.6__py3-none-any.whl → 0.10.22__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 (56) hide show
  1. signalpilot_ai_internal/__init__.py +1 -0
  2. signalpilot_ai_internal/_version.py +1 -1
  3. signalpilot_ai_internal/databricks_schema_service.py +902 -0
  4. signalpilot_ai_internal/file_scanner_service.py +2 -1
  5. signalpilot_ai_internal/handlers.py +72 -2
  6. signalpilot_ai_internal/mcp_handlers.py +508 -0
  7. signalpilot_ai_internal/mcp_server_manager.py +298 -0
  8. signalpilot_ai_internal/mcp_service.py +1303 -0
  9. signalpilot_ai_internal/schema_search_config.yml +8 -8
  10. signalpilot_ai_internal/schema_search_service.py +62 -1
  11. signalpilot_ai_internal/test_dbt_mcp_server.py +180 -0
  12. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/package.json +5 -3
  13. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/package.json.orig +4 -2
  14. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/plugin.json +7 -1
  15. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/110.224e83db03814fd03955.js +7 -0
  16. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.972abe1d2d66f083f9cc.js +1 -0
  17. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.ad22ccddd74ee306fb56.js +1 -0
  18. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/57.c4232851631fb2e7e59a.js +1 -0
  19. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/726.318e4e791edb63cc788f.js +1 -0
  20. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.2d75de1a8d2c3131a8db.js +1 -0
  21. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/801.ca9e114a30896b669a3c.js +1 -0
  22. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/880.d9914229e4f120e7e9e4.js +1 -0
  23. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/936.d80de1e4da5b520d2f3b.js +1 -0
  24. signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.b63c429ca81e743b403c.js +1 -0
  25. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/third-party-licenses.json +38 -20
  26. {signalpilot_ai_internal-0.7.6.dist-info → signalpilot_ai_internal-0.10.22.dist-info}/METADATA +3 -2
  27. signalpilot_ai_internal-0.10.22.dist-info/RECORD +56 -0
  28. {signalpilot_ai_internal-0.7.6.dist-info → signalpilot_ai_internal-0.10.22.dist-info}/WHEEL +1 -1
  29. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.72484b768a04f89bd3dd.js +0 -1
  30. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.9b4f05a99f5003f82094.js +0 -1
  31. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/490.b4ccb9601c8112407c5d.js +0 -1
  32. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.3aa564fc148b37d1d719.js +0 -1
  33. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/839.ed04fa601a43e8dd24d1.js +0 -1
  34. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.4e9edb7f224152c1dcb4.js +0 -2
  35. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/898.4e9edb7f224152c1dcb4.js.LICENSE.txt +0 -1
  36. signalpilot_ai_internal-0.7.6.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.ee8951353b00c13b8070.js +0 -1
  37. signalpilot_ai_internal-0.7.6.dist-info/RECORD +0 -49
  38. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/etc/jupyter/jupyter_server_config.d/signalpilot_ai.json +0 -0
  39. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/install.json +0 -0
  40. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/122.e2dadf63dc64d7b5f1ee.js +0 -0
  41. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/220.328403b5545f268b95c6.js +0 -0
  42. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/262.726e1da31a50868cb297.js +0 -0
  43. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/364.dbec4c2dc12e7b050dcc.js +0 -0
  44. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/384.fa432bdb7fb6b1c95ad6.js +0 -0
  45. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/439.37e271d7a80336daabe2.js +0 -0
  46. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/481.73c7a9290b7d35a8b9c1.js +0 -0
  47. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/512.b58fc0093d080b8ee61c.js +0 -0
  48. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js +0 -0
  49. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js.LICENSE.txt +0 -0
  50. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/635.9720593ee20b768da3ca.js +0 -0
  51. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/713.8e6edc9a965bdd578ca7.js +0 -0
  52. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/741.dc49867fafb03ea2ba4d.js +0 -0
  53. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/742.91e7b516c8699eea3373.js +0 -0
  54. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/888.34054db17bcf6e87ec95.js +0 -0
  55. {signalpilot_ai_internal-0.7.6.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/style.js +0 -0
  56. {signalpilot_ai_internal-0.7.6.dist-info → signalpilot_ai_internal-0.10.22.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,298 @@
1
+ """
2
+ MCP Server Manager
3
+
4
+ Handles automatic startup and management of MCP servers (like dbt-mcp) when SignalPilot AI starts.
5
+ """
6
+ import subprocess
7
+ import sys
8
+ import os
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional, Dict, List
12
+ import atexit
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class MCPServerManager:
18
+ """Manages MCP server processes"""
19
+
20
+ def __init__(self):
21
+ self._processes: Dict[str, subprocess.Popen] = {}
22
+ self._server_configs: List[Dict] = []
23
+
24
+ # Register cleanup on exit
25
+ atexit.register(self.stop_all_servers)
26
+
27
+ def add_server_config(self, name: str, command: List[str], cwd: Optional[str] = None,
28
+ env: Optional[Dict[str, str]] = None, transport: str = "stdio"):
29
+ """
30
+ Add an MCP server configuration
31
+
32
+ Args:
33
+ name: Server identifier
34
+ command: Command to start the server (as list)
35
+ cwd: Working directory for the server process
36
+ env: Environment variables for the server
37
+ transport: MCP transport type (stdio, sse, etc.)
38
+ """
39
+ config = {
40
+ 'name': name,
41
+ 'command': command,
42
+ 'cwd': cwd,
43
+ 'env': env or {},
44
+ 'transport': transport
45
+ }
46
+ self._server_configs.append(config)
47
+ logger.info(f"Added MCP server config: {name}")
48
+
49
+ def start_server(self, name: str) -> bool:
50
+ """
51
+ Start a specific MCP server
52
+
53
+ Args:
54
+ name: Server identifier
55
+
56
+ Returns:
57
+ True if server started successfully, False otherwise
58
+ """
59
+ # Find the server config
60
+ config = None
61
+ for cfg in self._server_configs:
62
+ if cfg['name'] == name:
63
+ config = cfg
64
+ break
65
+
66
+ if not config:
67
+ logger.error(f"No configuration found for server: {name}")
68
+ return False
69
+
70
+ # Check if already running
71
+ if name in self._processes:
72
+ if self._processes[name].poll() is None:
73
+ logger.info(f"MCP server '{name}' is already running")
74
+ return True
75
+ else:
76
+ # Process ended, remove it
77
+ del self._processes[name]
78
+
79
+ try:
80
+ # Prepare environment
81
+ env = os.environ.copy()
82
+ env.update(config['env'])
83
+ env['MCP_TRANSPORT'] = config['transport']
84
+
85
+ # Start the process
86
+ logger.info(f"Starting MCP server '{name}' with command: {' '.join(config['command'])}")
87
+ process = subprocess.Popen(
88
+ config['command'],
89
+ cwd=config['cwd'],
90
+ env=env,
91
+ stdout=subprocess.PIPE,
92
+ stderr=subprocess.PIPE,
93
+ stdin=subprocess.PIPE,
94
+ bufsize=0 # Unbuffered for stdio transport
95
+ )
96
+
97
+ self._processes[name] = process
98
+ logger.info(f"MCP server '{name}' started with PID: {process.pid}")
99
+ return True
100
+
101
+ except Exception as e:
102
+ logger.error(f"Failed to start MCP server '{name}': {e}")
103
+ return False
104
+
105
+ def start_all_servers(self) -> Dict[str, bool]:
106
+ """
107
+ Start all configured MCP servers
108
+
109
+ Returns:
110
+ Dictionary mapping server names to start success status
111
+ """
112
+ results = {}
113
+ for config in self._server_configs:
114
+ name = config['name']
115
+ results[name] = self.start_server(name)
116
+ return results
117
+
118
+ def stop_server(self, name: str) -> bool:
119
+ """
120
+ Stop a specific MCP server
121
+
122
+ Args:
123
+ name: Server identifier
124
+
125
+ Returns:
126
+ True if server stopped successfully, False otherwise
127
+ """
128
+ if name not in self._processes:
129
+ logger.warning(f"MCP server '{name}' is not running")
130
+ return False
131
+
132
+ try:
133
+ process = self._processes[name]
134
+
135
+ # Try graceful termination first
136
+ process.terminate()
137
+
138
+ try:
139
+ process.wait(timeout=5)
140
+ logger.info(f"MCP server '{name}' terminated gracefully")
141
+ except subprocess.TimeoutExpired:
142
+ # Force kill if it doesn't terminate
143
+ process.kill()
144
+ process.wait()
145
+ logger.warning(f"MCP server '{name}' was force killed")
146
+
147
+ del self._processes[name]
148
+ return True
149
+
150
+ except Exception as e:
151
+ logger.error(f"Failed to stop MCP server '{name}': {e}")
152
+ return False
153
+
154
+ def stop_all_servers(self):
155
+ """Stop all running MCP servers"""
156
+ server_names = list(self._processes.keys())
157
+ for name in server_names:
158
+ self.stop_server(name)
159
+
160
+ def get_server_status(self, name: str) -> Optional[str]:
161
+ """
162
+ Get the status of a specific server
163
+
164
+ Args:
165
+ name: Server identifier
166
+
167
+ Returns:
168
+ 'running', 'stopped', or None if not configured
169
+ """
170
+ # Check if configured
171
+ if not any(cfg['name'] == name for cfg in self._server_configs):
172
+ return None
173
+
174
+ if name not in self._processes:
175
+ return 'stopped'
176
+
177
+ if self._processes[name].poll() is None:
178
+ return 'running'
179
+ else:
180
+ return 'stopped'
181
+
182
+ def get_all_server_status(self) -> Dict[str, str]:
183
+ """
184
+ Get status of all configured servers
185
+
186
+ Returns:
187
+ Dictionary mapping server names to their status
188
+ """
189
+ status = {}
190
+ for config in self._server_configs:
191
+ name = config['name']
192
+ status[name] = self.get_server_status(name)
193
+ return status
194
+
195
+ def get_server_logs(self, name: str, num_lines: int = 50) -> Dict[str, str]:
196
+ """
197
+ Get recent stdout/stderr logs from a server
198
+
199
+ Args:
200
+ name: Server identifier
201
+ num_lines: Number of recent lines to retrieve (approximate)
202
+
203
+ Returns:
204
+ Dictionary with 'stdout' and 'stderr' keys containing log output
205
+ """
206
+ if name not in self._processes:
207
+ return {"stdout": "", "stderr": "", "error": "Server not running"}
208
+
209
+ process = self._processes[name]
210
+ logs = {"stdout": "", "stderr": ""}
211
+
212
+ try:
213
+ # Try to read from stdout (non-blocking)
214
+ import select
215
+ import sys
216
+
217
+ if process.stdout and hasattr(select, 'select'):
218
+ # Unix-like systems
219
+ if select.select([process.stdout], [], [], 0)[0]:
220
+ logs["stdout"] = process.stdout.read(4096).decode('utf-8', errors='ignore')
221
+ elif process.stdout:
222
+ # Windows fallback - attempt read
223
+ try:
224
+ logs["stdout"] = process.stdout.read(4096).decode('utf-8', errors='ignore')
225
+ except:
226
+ logs["stdout"] = "(Unable to read stdout on Windows - pipe may be blocking)"
227
+
228
+ if process.stderr and hasattr(select, 'select'):
229
+ if select.select([process.stderr], [], [], 0)[0]:
230
+ logs["stderr"] = process.stderr.read(4096).decode('utf-8', errors='ignore')
231
+ elif process.stderr:
232
+ try:
233
+ logs["stderr"] = process.stderr.read(4096).decode('utf-8', errors='ignore')
234
+ except:
235
+ logs["stderr"] = "(Unable to read stderr on Windows - pipe may be blocking)"
236
+
237
+ except Exception as e:
238
+ logs["error"] = f"Error reading logs: {e}"
239
+
240
+ return logs
241
+
242
+
243
+ # Global instance
244
+ _mcp_server_manager: Optional[MCPServerManager] = None
245
+
246
+
247
+ def get_mcp_server_manager() -> MCPServerManager:
248
+ """Get the global MCP server manager instance"""
249
+ global _mcp_server_manager
250
+ if _mcp_server_manager is None:
251
+ _mcp_server_manager = MCPServerManager()
252
+ return _mcp_server_manager
253
+
254
+
255
+ def configure_default_servers():
256
+ """Configure default MCP servers for SignalPilot AI"""
257
+ manager = get_mcp_server_manager()
258
+
259
+ # Configure dbt-mcp server
260
+ signalpilot_ai_internal_dir = Path(__file__).parent
261
+ dbt_mcp_dir = signalpilot_ai_internal_dir / "dbt-mcp"
262
+
263
+ if dbt_mcp_dir.exists():
264
+ # Use the Python executable that's running this process
265
+ python_executable = sys.executable
266
+
267
+ # Command to run dbt-mcp using the main module
268
+ dbt_mcp_command = [
269
+ python_executable,
270
+ "-m",
271
+ "dbt_mcp.main"
272
+ ]
273
+
274
+ manager.add_server_config(
275
+ name="dbt-mcp",
276
+ command=dbt_mcp_command,
277
+ cwd=str(dbt_mcp_dir),
278
+ env={},
279
+ transport="stdio"
280
+ )
281
+ logger.info(f"Configured dbt-mcp server at {dbt_mcp_dir}")
282
+ else:
283
+ logger.warning(f"dbt-mcp directory not found at {dbt_mcp_dir}")
284
+
285
+
286
+ def autostart_mcp_servers():
287
+ """Automatically start all configured MCP servers"""
288
+ configure_default_servers()
289
+ manager = get_mcp_server_manager()
290
+ results = manager.start_all_servers()
291
+
292
+ for name, success in results.items():
293
+ if success:
294
+ logger.info(f"✓ MCP server '{name}' started successfully")
295
+ else:
296
+ logger.error(f"✗ MCP server '{name}' failed to start")
297
+
298
+ return results