signalpilot-ai-internal 0.10.0__py3-none-any.whl → 0.11.24__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.
- signalpilot_ai_internal/__init__.py +1 -0
- signalpilot_ai_internal/_version.py +1 -1
- signalpilot_ai_internal/cache_service.py +22 -21
- signalpilot_ai_internal/composio_handlers.py +224 -0
- signalpilot_ai_internal/composio_service.py +511 -0
- signalpilot_ai_internal/database_config_handlers.py +182 -0
- signalpilot_ai_internal/database_config_service.py +166 -0
- signalpilot_ai_internal/databricks_schema_service.py +907 -0
- signalpilot_ai_internal/file_scanner_service.py +5 -146
- signalpilot_ai_internal/handlers.py +388 -9
- signalpilot_ai_internal/integrations_config.py +256 -0
- signalpilot_ai_internal/log_utils.py +31 -0
- signalpilot_ai_internal/mcp_handlers.py +532 -0
- signalpilot_ai_internal/mcp_server_manager.py +298 -0
- signalpilot_ai_internal/mcp_service.py +1255 -0
- signalpilot_ai_internal/oauth_token_store.py +141 -0
- signalpilot_ai_internal/schema_search_config.yml +17 -11
- signalpilot_ai_internal/schema_search_service.py +85 -4
- signalpilot_ai_internal/signalpilot_home.py +961 -0
- signalpilot_ai_internal/snowflake_schema_service.py +2 -0
- signalpilot_ai_internal/test_dbt_mcp_server.py +180 -0
- signalpilot_ai_internal/unified_database_schema_service.py +2 -0
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/package.json.orig → signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/package.json +15 -48
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/package.json → signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/package.json.orig +9 -52
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.11.24.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/plugin.json +7 -1
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/122.bab318d6caadb055e29c.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/129.868ca665e6fc225c20a0.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/179.fd45a2e75d471d0aa3b9.js +7 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/220.81105a94aa873fc51a94.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/262.a002dd4630d3b6404a90.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.cc6f6ecacd703bcdb468.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/364.817a883549d55a0e0576.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/384.a4daecd44f1e9364e44a.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/439.667225aab294fb5ed161.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/447.8138af2522716e5a926f.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.925c73e32f3c07448da0.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/477.aaa4cc9e87801fb45f5b.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/481.370056149a59022b700c.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/510.868ca665e6fc225c20a0.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/512.835f97f7ccfc70ff5c93.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/57.6c13335f73de089d6b1e.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/574.ad2709e91ebcac5bbe68.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/635.bddbab8e464fe31f0393.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/713.fda1bcdb10497b0a6ade.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/741.d046701f475fcbf6697d.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.c306dffd4cfe8a613d13.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/801.e39898b6f336539f228c.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/880.77cc0ca10a1860df1b52.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/936.4e2850b2af985ed0d378.js +1 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/956.eeffe67d7781fd63ef4b.js +2 -0
- signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.055f50d20a31f3068c72.js +1 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.11.24.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/third-party-licenses.json +47 -29
- {signalpilot_ai_internal-0.10.0.dist-info → signalpilot_ai_internal-0.11.24.dist-info}/METADATA +14 -31
- signalpilot_ai_internal-0.11.24.dist-info/RECORD +66 -0
- signalpilot_ai_internal-0.11.24.dist-info/licenses/LICENSE +7 -0
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/122.e2dadf63dc64d7b5f1ee.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/220.328403b5545f268b95c6.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/262.726e1da31a50868cb297.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/330.af2e9cb5def5ae2b84d5.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.972abe1d2d66f083f9cc.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/364.dbec4c2dc12e7b050dcc.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/384.fa432bdb7fb6b1c95ad6.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/439.37e271d7a80336daabe2.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.ad22ccddd74ee306fb56.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/481.73c7a9290b7d35a8b9c1.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/512.b58fc0093d080b8ee61c.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js +0 -2
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/57.e9acd2e1f9739037f1ab.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/635.9720593ee20b768da3ca.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/713.8e6edc9a965bdd578ca7.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/741.dc49867fafb03ea2ba4d.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/742.91e7b516c8699eea3373.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.2d75de1a8d2c3131a8db.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/786.770dc7bcab77e14cc135.js +0 -7
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/801.ca9e114a30896b669a3c.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/880.25ddd15aca09421d3765.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/888.34054db17bcf6e87ec95.js +0 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.b05b2f0c9617ba28370d.js +0 -1
- signalpilot_ai_internal-0.10.0.dist-info/RECORD +0 -50
- signalpilot_ai_internal-0.10.0.dist-info/licenses/LICENSE +0 -29
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.11.24.data}/data/etc/jupyter/jupyter_server_config.d/signalpilot_ai.json +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.11.24.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/install.json +0 -0
- /signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js.LICENSE.txt → /signalpilot_ai_internal-0.11.24.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/956.eeffe67d7781fd63ef4b.js.LICENSE.txt +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.11.24.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/style.js +0 -0
- {signalpilot_ai_internal-0.10.0.dist-info → signalpilot_ai_internal-0.11.24.dist-info}/WHEEL +0 -0
|
@@ -8,6 +8,7 @@ except ImportError:
|
|
|
8
8
|
warnings.warn("Importing 'signalpilot_ai_internal' outside a proper installation.")
|
|
9
9
|
__version__ = "dev"
|
|
10
10
|
from .handlers import setup_handlers
|
|
11
|
+
from .mcp_server_manager import autostart_mcp_servers
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def _jupyter_labextension_paths():
|
|
@@ -15,6 +15,9 @@ import uuid
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
from typing import Any, Dict, List, Optional
|
|
17
17
|
|
|
18
|
+
# Import controlled print function
|
|
19
|
+
from .log_utils import print
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class CacheDirectoryManager:
|
|
20
23
|
"""OS-specific cache directory management with fallbacks"""
|
|
@@ -75,7 +78,7 @@ class CacheDirectoryManager:
|
|
|
75
78
|
directories.append(Path(tempfile.gettempdir()) / f"signalpilot-ai-internal-{os.getuid() if hasattr(os, 'getuid') else 'user'}")
|
|
76
79
|
|
|
77
80
|
except Exception as e:
|
|
78
|
-
print(f"
|
|
81
|
+
print(f"ERROR: determining cache directories: {e}")
|
|
79
82
|
# Emergency fallback
|
|
80
83
|
directories.append(Path(tempfile.gettempdir()) / "signalpilot-ai-internal-emergency")
|
|
81
84
|
|
|
@@ -93,7 +96,7 @@ class CacheDirectoryManager:
|
|
|
93
96
|
test_file = cache_dir / f"test_write_{uuid.uuid4().hex[:8]}.tmp"
|
|
94
97
|
test_file.write_text("test")
|
|
95
98
|
test_file.unlink()
|
|
96
|
-
|
|
99
|
+
|
|
97
100
|
print(f"Using cache directory: {cache_dir}")
|
|
98
101
|
return cache_dir
|
|
99
102
|
|
|
@@ -111,14 +114,13 @@ class RobustFileOperations:
|
|
|
111
114
|
@staticmethod
|
|
112
115
|
def safe_write_json(file_path: Path, data: Any, max_retries: int = 3) -> bool:
|
|
113
116
|
"""Safely write JSON data with atomic operations and backups"""
|
|
114
|
-
# print(f"Attempting to write JSON to: {file_path}")
|
|
115
117
|
|
|
116
118
|
if not file_path.parent.exists():
|
|
117
119
|
try:
|
|
118
120
|
print(f"Creating parent directory: {file_path.parent}")
|
|
119
121
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
120
122
|
except Exception as e:
|
|
121
|
-
print(f"Failed to create directory {file_path.parent}: {e}")
|
|
123
|
+
print(f"ERROR: Failed to create directory {file_path.parent}: {e}")
|
|
122
124
|
return False
|
|
123
125
|
|
|
124
126
|
# Create backup if file exists and is valid, but only if last backup is older than 1 hour
|
|
@@ -135,7 +137,6 @@ class RobustFileOperations:
|
|
|
135
137
|
if should_create_backup:
|
|
136
138
|
backup_path = file_path.with_suffix(f".backup.{int(time.time())}")
|
|
137
139
|
shutil.copy2(file_path, backup_path)
|
|
138
|
-
print(f"Created backup: {backup_path}")
|
|
139
140
|
|
|
140
141
|
# Keep only the most recent backup that's at least 1 hour old
|
|
141
142
|
RobustFileOperations._cleanup_backups(file_path)
|
|
@@ -183,7 +184,7 @@ class RobustFileOperations:
|
|
|
183
184
|
shutil.copy2(backup_path, file_path)
|
|
184
185
|
print(f"Restored {file_path} from backup")
|
|
185
186
|
except Exception as restore_error:
|
|
186
|
-
print(f"Failed to restore backup: {restore_error}")
|
|
187
|
+
print(f"ERROR: Failed to restore backup: {restore_error}")
|
|
187
188
|
|
|
188
189
|
return False
|
|
189
190
|
|
|
@@ -203,7 +204,7 @@ class RobustFileOperations:
|
|
|
203
204
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
204
205
|
return json.load(f)
|
|
205
206
|
except Exception as e:
|
|
206
|
-
print(f"Failed to read {file_path}: {e}")
|
|
207
|
+
print(f"ERROR: Failed to read {file_path}: {e}")
|
|
207
208
|
|
|
208
209
|
# Try to recover from backup
|
|
209
210
|
backup_files = sorted(
|
|
@@ -224,7 +225,7 @@ class RobustFileOperations:
|
|
|
224
225
|
shutil.copy2(backup_path, file_path)
|
|
225
226
|
print(f"Restored {file_path} from {backup_path}")
|
|
226
227
|
except Exception as restore_error:
|
|
227
|
-
print(f"Could not restore main file: {restore_error}")
|
|
228
|
+
print(f"ERROR: Could not restore main file: {restore_error}")
|
|
228
229
|
|
|
229
230
|
return data
|
|
230
231
|
|
|
@@ -254,7 +255,7 @@ class RobustFileOperations:
|
|
|
254
255
|
return backup_age > 3600 # 3600 seconds = 1 hour
|
|
255
256
|
|
|
256
257
|
except Exception as e:
|
|
257
|
-
print(f"
|
|
258
|
+
print(f"ERROR: checking backup age: {e}")
|
|
258
259
|
return True # If we can't check, err on the side of creating a backup
|
|
259
260
|
|
|
260
261
|
@staticmethod
|
|
@@ -273,10 +274,10 @@ class RobustFileOperations:
|
|
|
273
274
|
old_backup.unlink()
|
|
274
275
|
print(f"Cleaned up old backup: {old_backup}")
|
|
275
276
|
except Exception as cleanup_error:
|
|
276
|
-
print(f"Failed to cleanup backup {old_backup}: {cleanup_error}")
|
|
277
|
+
print(f"ERROR: Failed to cleanup backup {old_backup}: {cleanup_error}")
|
|
277
278
|
|
|
278
279
|
except Exception as e:
|
|
279
|
-
print(f"
|
|
280
|
+
print(f"ERROR: cleaning up backups: {e}")
|
|
280
281
|
|
|
281
282
|
|
|
282
283
|
class PersistentCacheService:
|
|
@@ -404,21 +405,21 @@ class PersistentCacheService:
|
|
|
404
405
|
if not self.chat_histories_file:
|
|
405
406
|
print(f"ERROR: Cannot save chat history for {chat_id} - no chat histories file configured")
|
|
406
407
|
return False
|
|
407
|
-
|
|
408
|
+
|
|
408
409
|
try:
|
|
409
410
|
print(f"Attempting to save chat history for chat_id: {chat_id}")
|
|
410
411
|
histories = self.get_chat_histories()
|
|
411
412
|
print(f"Current histories count: {len(histories)}")
|
|
412
|
-
|
|
413
|
+
|
|
413
414
|
histories[chat_id] = history
|
|
414
415
|
print(f"Updated histories count: {len(histories)}")
|
|
415
|
-
|
|
416
|
+
|
|
416
417
|
success = RobustFileOperations.safe_write_json(self.chat_histories_file, histories)
|
|
417
418
|
if success:
|
|
418
419
|
print(f"Successfully saved chat history for {chat_id}")
|
|
419
420
|
else:
|
|
420
421
|
print(f"ERROR: Failed to write chat history file for {chat_id}")
|
|
421
|
-
|
|
422
|
+
|
|
422
423
|
return success
|
|
423
424
|
|
|
424
425
|
except Exception as e:
|
|
@@ -584,7 +585,7 @@ class FileScanCacheManager:
|
|
|
584
585
|
cache_path = self._get_file_cache_path(file_path)
|
|
585
586
|
return RobustFileOperations.safe_read_json(cache_path, None)
|
|
586
587
|
except Exception as e:
|
|
587
|
-
print(f"
|
|
588
|
+
print(f"ERROR: reading file cache for {file_path}: {e}")
|
|
588
589
|
return None
|
|
589
590
|
|
|
590
591
|
def set_file_entry(self, file_path: str, entry: Dict[str, Any]) -> bool:
|
|
@@ -596,7 +597,7 @@ class FileScanCacheManager:
|
|
|
596
597
|
cache_path = self._get_file_cache_path(file_path)
|
|
597
598
|
return RobustFileOperations.safe_write_json(cache_path, entry)
|
|
598
599
|
except Exception as e:
|
|
599
|
-
print(f"
|
|
600
|
+
print(f"ERROR: writing file cache for {file_path}: {e}")
|
|
600
601
|
return False
|
|
601
602
|
|
|
602
603
|
def delete_file_entry(self, file_path: str) -> bool:
|
|
@@ -611,7 +612,7 @@ class FileScanCacheManager:
|
|
|
611
612
|
return True
|
|
612
613
|
return True # File doesn't exist, consider it deleted
|
|
613
614
|
except Exception as e:
|
|
614
|
-
print(f"
|
|
615
|
+
print(f"ERROR: deleting file cache for {file_path}: {e}")
|
|
615
616
|
return False
|
|
616
617
|
|
|
617
618
|
def get_scanned_directories(self) -> List[Dict[str, Any]]:
|
|
@@ -622,7 +623,7 @@ class FileScanCacheManager:
|
|
|
622
623
|
try:
|
|
623
624
|
return RobustFileOperations.safe_read_json(self.scanned_directories_file, [])
|
|
624
625
|
except Exception as e:
|
|
625
|
-
print(f"
|
|
626
|
+
print(f"ERROR: reading scanned directories: {e}")
|
|
626
627
|
return []
|
|
627
628
|
|
|
628
629
|
def set_scanned_directories(self, directories: List[Dict[str, Any]]) -> bool:
|
|
@@ -633,7 +634,7 @@ class FileScanCacheManager:
|
|
|
633
634
|
try:
|
|
634
635
|
return RobustFileOperations.safe_write_json(self.scanned_directories_file, directories)
|
|
635
636
|
except Exception as e:
|
|
636
|
-
print(f"
|
|
637
|
+
print(f"ERROR: writing scanned directories: {e}")
|
|
637
638
|
return False
|
|
638
639
|
|
|
639
640
|
def clear_all_file_entries(self) -> bool:
|
|
@@ -648,7 +649,7 @@ class FileScanCacheManager:
|
|
|
648
649
|
cache_file.unlink()
|
|
649
650
|
return True
|
|
650
651
|
except Exception as e:
|
|
651
|
-
print(f"
|
|
652
|
+
print(f"ERROR: clearing file entries: {e}")
|
|
652
653
|
return False
|
|
653
654
|
|
|
654
655
|
def get_cache_stats(self) -> Dict[str, Any]:
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Composio Integration Handlers - Tornado HTTP handlers for OAuth integrations
|
|
3
|
+
Provides REST API for managing Composio OAuth integrations (Notion, Slack, Google)
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import traceback
|
|
8
|
+
import tornado
|
|
9
|
+
from jupyter_server.base.handlers import APIHandler
|
|
10
|
+
from .composio_service import get_composio_service
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Enable debug logging
|
|
15
|
+
logger.setLevel(logging.DEBUG)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IntegrationsHandler(APIHandler):
|
|
19
|
+
"""Handler for listing all integrations and their status"""
|
|
20
|
+
|
|
21
|
+
@tornado.web.authenticated
|
|
22
|
+
async def get(self):
|
|
23
|
+
"""Get all available integrations with their connection status"""
|
|
24
|
+
try:
|
|
25
|
+
service = get_composio_service()
|
|
26
|
+
integrations = service.get_integrations()
|
|
27
|
+
|
|
28
|
+
self.finish(json.dumps({
|
|
29
|
+
'integrations': integrations,
|
|
30
|
+
'configured': service.is_configured(),
|
|
31
|
+
'workerUrl': service.get_worker_url() if service.is_configured() else None,
|
|
32
|
+
}))
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logger.error(f"Error getting integrations: {e}")
|
|
35
|
+
logger.error(traceback.format_exc())
|
|
36
|
+
self.set_status(500)
|
|
37
|
+
self.finish(json.dumps({
|
|
38
|
+
'error': str(e)
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class IntegrationConnectHandler(APIHandler):
|
|
43
|
+
"""Handler for initiating OAuth connection for an integration"""
|
|
44
|
+
|
|
45
|
+
@tornado.web.authenticated
|
|
46
|
+
async def post(self, integration_id):
|
|
47
|
+
"""
|
|
48
|
+
Get worker URL and user ID for initiating OAuth connection.
|
|
49
|
+
Frontend will call the worker directly.
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
service = get_composio_service()
|
|
53
|
+
|
|
54
|
+
if not service.is_configured():
|
|
55
|
+
self.set_status(400)
|
|
56
|
+
self.finish(json.dumps({
|
|
57
|
+
'error': 'Composio worker URL not configured. Set COMPOSIO_WORKER_URL environment variable.',
|
|
58
|
+
'configured': False,
|
|
59
|
+
}))
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
result = service.get_initiate_url(integration_id)
|
|
63
|
+
|
|
64
|
+
self.finish(json.dumps({
|
|
65
|
+
'success': True,
|
|
66
|
+
'workerUrl': result['workerUrl'],
|
|
67
|
+
'userId': result['userId'],
|
|
68
|
+
}))
|
|
69
|
+
except ValueError as e:
|
|
70
|
+
self.set_status(400)
|
|
71
|
+
self.finish(json.dumps({
|
|
72
|
+
'error': str(e)
|
|
73
|
+
}))
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Error initiating connection for {integration_id}: {e}")
|
|
76
|
+
logger.error(traceback.format_exc())
|
|
77
|
+
self.set_status(500)
|
|
78
|
+
self.finish(json.dumps({
|
|
79
|
+
'error': str(e)
|
|
80
|
+
}))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class IntegrationCompleteHandler(APIHandler):
|
|
84
|
+
"""Handler for completing OAuth connection after callback"""
|
|
85
|
+
|
|
86
|
+
@tornado.web.authenticated
|
|
87
|
+
async def post(self, integration_id):
|
|
88
|
+
"""Complete OAuth connection and create MCP server"""
|
|
89
|
+
try:
|
|
90
|
+
service = get_composio_service()
|
|
91
|
+
|
|
92
|
+
if not service.is_configured():
|
|
93
|
+
self.set_status(400)
|
|
94
|
+
self.finish(json.dumps({
|
|
95
|
+
'error': 'Composio worker URL not configured',
|
|
96
|
+
'configured': False,
|
|
97
|
+
}))
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
result = await service.complete_connection(integration_id)
|
|
101
|
+
|
|
102
|
+
self.finish(json.dumps({
|
|
103
|
+
'success': True,
|
|
104
|
+
**result,
|
|
105
|
+
}))
|
|
106
|
+
except ValueError as e:
|
|
107
|
+
self.set_status(400)
|
|
108
|
+
self.finish(json.dumps({
|
|
109
|
+
'error': str(e)
|
|
110
|
+
}))
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Error completing connection for {integration_id}: {e}")
|
|
113
|
+
logger.error(traceback.format_exc())
|
|
114
|
+
self.set_status(500)
|
|
115
|
+
self.finish(json.dumps({
|
|
116
|
+
'error': str(e)
|
|
117
|
+
}))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class IntegrationStatusHandler(APIHandler):
|
|
121
|
+
"""Handler for checking connection status of an integration"""
|
|
122
|
+
|
|
123
|
+
@tornado.web.authenticated
|
|
124
|
+
async def get(self, integration_id):
|
|
125
|
+
"""Check connection status for an integration"""
|
|
126
|
+
try:
|
|
127
|
+
service = get_composio_service()
|
|
128
|
+
|
|
129
|
+
if not service.is_configured():
|
|
130
|
+
self.set_status(400)
|
|
131
|
+
self.finish(json.dumps({
|
|
132
|
+
'error': 'Composio worker URL not configured',
|
|
133
|
+
'configured': False,
|
|
134
|
+
}))
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
result = await service.check_connection_status(integration_id)
|
|
138
|
+
|
|
139
|
+
self.finish(json.dumps({
|
|
140
|
+
'success': True,
|
|
141
|
+
**result,
|
|
142
|
+
}))
|
|
143
|
+
except ValueError as e:
|
|
144
|
+
self.set_status(400)
|
|
145
|
+
self.finish(json.dumps({
|
|
146
|
+
'error': str(e)
|
|
147
|
+
}))
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Error checking status for {integration_id}: {e}")
|
|
150
|
+
logger.error(traceback.format_exc())
|
|
151
|
+
self.set_status(500)
|
|
152
|
+
self.finish(json.dumps({
|
|
153
|
+
'error': str(e)
|
|
154
|
+
}))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class IntegrationDisconnectHandler(APIHandler):
|
|
158
|
+
"""Handler for disconnecting an integration"""
|
|
159
|
+
|
|
160
|
+
@tornado.web.authenticated
|
|
161
|
+
async def delete(self, integration_id):
|
|
162
|
+
"""Disconnect an integration and remove MCP server"""
|
|
163
|
+
try:
|
|
164
|
+
service = get_composio_service()
|
|
165
|
+
|
|
166
|
+
result = await service.disconnect(integration_id)
|
|
167
|
+
|
|
168
|
+
self.finish(json.dumps({
|
|
169
|
+
'success': True,
|
|
170
|
+
**result,
|
|
171
|
+
}))
|
|
172
|
+
except ValueError as e:
|
|
173
|
+
self.set_status(400)
|
|
174
|
+
self.finish(json.dumps({
|
|
175
|
+
'error': str(e)
|
|
176
|
+
}))
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"Error disconnecting {integration_id}: {e}")
|
|
179
|
+
logger.error(traceback.format_exc())
|
|
180
|
+
self.set_status(500)
|
|
181
|
+
self.finish(json.dumps({
|
|
182
|
+
'error': str(e)
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class IntegrationRefreshHandler(APIHandler):
|
|
187
|
+
"""Handler for manually refreshing OAuth tokens for an integration"""
|
|
188
|
+
|
|
189
|
+
@tornado.web.authenticated
|
|
190
|
+
async def post(self, integration_id):
|
|
191
|
+
"""
|
|
192
|
+
Manually trigger token refresh for an integration.
|
|
193
|
+
Fetches fresh credentials from Composio and updates MCP server if tokens changed.
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
service = get_composio_service()
|
|
197
|
+
|
|
198
|
+
if not service.is_configured():
|
|
199
|
+
self.set_status(400)
|
|
200
|
+
self.finish(json.dumps({
|
|
201
|
+
'error': 'Composio worker URL not configured',
|
|
202
|
+
'configured': False,
|
|
203
|
+
}))
|
|
204
|
+
return
|
|
205
|
+
|
|
206
|
+
result = await service.refresh_token(integration_id)
|
|
207
|
+
|
|
208
|
+
self.finish(json.dumps({
|
|
209
|
+
'success': result.get('success', False),
|
|
210
|
+
'tokens_updated': result.get('tokens_updated', False),
|
|
211
|
+
'error': result.get('error'),
|
|
212
|
+
}))
|
|
213
|
+
except ValueError as e:
|
|
214
|
+
self.set_status(400)
|
|
215
|
+
self.finish(json.dumps({
|
|
216
|
+
'error': str(e)
|
|
217
|
+
}))
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"Error refreshing tokens for {integration_id}: {e}")
|
|
220
|
+
logger.error(traceback.format_exc())
|
|
221
|
+
self.set_status(500)
|
|
222
|
+
self.finish(json.dumps({
|
|
223
|
+
'error': str(e)
|
|
224
|
+
}))
|