signalpilot-ai-internal 0.10.0__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.
- signalpilot_ai_internal/__init__.py +1 -0
- signalpilot_ai_internal/_version.py +1 -1
- signalpilot_ai_internal/databricks_schema_service.py +902 -0
- signalpilot_ai_internal/handlers.py +72 -2
- signalpilot_ai_internal/mcp_handlers.py +508 -0
- signalpilot_ai_internal/mcp_server_manager.py +298 -0
- signalpilot_ai_internal/mcp_service.py +1303 -0
- signalpilot_ai_internal/schema_search_service.py +62 -1
- signalpilot_ai_internal/test_dbt_mcp_server.py +180 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/package.json +2 -2
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/package.json.orig +1 -1
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/schemas/signalpilot-ai-internal/plugin.json +7 -1
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/786.770dc7bcab77e14cc135.js → signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/110.224e83db03814fd03955.js +2 -2
- signalpilot_ai_internal-0.10.0.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/57.e9acd2e1f9739037f1ab.js → signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/57.c4232851631fb2e7e59a.js +1 -1
- signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/726.318e4e791edb63cc788f.js +1 -0
- signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/880.d9914229e4f120e7e9e4.js +1 -0
- signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/936.d80de1e4da5b520d2f3b.js +1 -0
- signalpilot_ai_internal-0.10.22.data/data/share/jupyter/labextensions/signalpilot-ai-internal/static/remoteEntry.b63c429ca81e743b403c.js +1 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/third-party-licenses.json +18 -0
- {signalpilot_ai_internal-0.10.0.dist-info → signalpilot_ai_internal-0.10.22.dist-info}/METADATA +3 -2
- signalpilot_ai_internal-0.10.22.dist-info/RECORD +56 -0
- 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/880.25ddd15aca09421d3765.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.data → signalpilot_ai_internal-0.10.22.data}/data/etc/jupyter/jupyter_server_config.d/signalpilot_ai.json +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/install.json +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/122.e2dadf63dc64d7b5f1ee.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/220.328403b5545f268b95c6.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/262.726e1da31a50868cb297.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/353.972abe1d2d66f083f9cc.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/364.dbec4c2dc12e7b050dcc.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/384.fa432bdb7fb6b1c95ad6.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/439.37e271d7a80336daabe2.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/476.ad22ccddd74ee306fb56.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/481.73c7a9290b7d35a8b9c1.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/512.b58fc0093d080b8ee61c.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/553.b4042a795c91d9ff71ef.js.LICENSE.txt +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/635.9720593ee20b768da3ca.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/713.8e6edc9a965bdd578ca7.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/741.dc49867fafb03ea2ba4d.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/742.91e7b516c8699eea3373.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/785.2d75de1a8d2c3131a8db.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/801.ca9e114a30896b669a3c.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.data}/data/share/jupyter/labextensions/signalpilot-ai-internal/static/888.34054db17bcf6e87ec95.js +0 -0
- {signalpilot_ai_internal-0.10.0.data → signalpilot_ai_internal-0.10.22.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.10.22.dist-info}/WHEEL +0 -0
- {signalpilot_ai_internal-0.10.0.dist-info → signalpilot_ai_internal-0.10.22.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,8 +12,22 @@ from .cache_service import get_cache_service
|
|
|
12
12
|
from .cache_handlers import ChatHistoriesHandler, AppValuesHandler, CacheInfoHandler
|
|
13
13
|
from .unified_database_schema_service import UnifiedDatabaseSchemaHandler, UnifiedDatabaseQueryHandler
|
|
14
14
|
from .snowflake_schema_service import SnowflakeSchemaHandler, SnowflakeQueryHandler
|
|
15
|
+
from .databricks_schema_service import DatabricksSchemaHandler, DatabricksQueryHandler, DatabricksTestHandler
|
|
15
16
|
from .file_scanner_service import get_file_scanner_service
|
|
16
17
|
from .schema_search_service import SchemaSearchHandler
|
|
18
|
+
from .mcp_handlers import (
|
|
19
|
+
MCPServersHandler,
|
|
20
|
+
MCPServerHandler,
|
|
21
|
+
MCPConnectHandler,
|
|
22
|
+
MCPDisconnectHandler,
|
|
23
|
+
MCPToolsHandler,
|
|
24
|
+
MCPAllToolsHandler,
|
|
25
|
+
MCPToolCallHandler,
|
|
26
|
+
MCPServerEnableHandler,
|
|
27
|
+
MCPServerDisableHandler,
|
|
28
|
+
MCPToolEnableHandler,
|
|
29
|
+
MCPConfigFileHandler
|
|
30
|
+
)
|
|
17
31
|
|
|
18
32
|
|
|
19
33
|
class HelloWorldHandler(APIHandler):
|
|
@@ -741,12 +755,30 @@ def setup_handlers(web_app):
|
|
|
741
755
|
# Snowflake service endpoints
|
|
742
756
|
snowflake_schema_route = url_path_join(base_url, "signalpilot-ai-internal", "snowflake", "schema")
|
|
743
757
|
snowflake_query_route = url_path_join(base_url, "signalpilot-ai-internal", "snowflake", "query")
|
|
744
|
-
|
|
758
|
+
|
|
759
|
+
# Databricks service endpoints
|
|
760
|
+
databricks_schema_route = url_path_join(base_url, "signalpilot-ai-internal", "databricks", "schema")
|
|
761
|
+
databricks_query_route = url_path_join(base_url, "signalpilot-ai-internal", "databricks", "query")
|
|
762
|
+
databricks_test_route = url_path_join(base_url, "signalpilot-ai-internal", "databricks", "test")
|
|
763
|
+
|
|
745
764
|
# Notebook HTML export endpoint
|
|
746
765
|
notebook_html_route = url_path_join(base_url, "signalpilot-ai-internal", "notebook", "to-html")
|
|
747
766
|
|
|
748
767
|
# Terminal endpoint
|
|
749
768
|
terminal_execute_route = url_path_join(base_url, "signalpilot-ai-internal", "terminal", "execute")
|
|
769
|
+
|
|
770
|
+
# MCP service endpoints
|
|
771
|
+
mcp_servers_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers")
|
|
772
|
+
mcp_server_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)")
|
|
773
|
+
mcp_connect_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "connect")
|
|
774
|
+
mcp_disconnect_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)", "disconnect")
|
|
775
|
+
mcp_tools_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)", "tools")
|
|
776
|
+
mcp_all_tools_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "tools")
|
|
777
|
+
mcp_tool_call_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "call-tool")
|
|
778
|
+
mcp_tool_enable_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)", "tools", "([^/]+)")
|
|
779
|
+
mcp_server_enable_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)", "enable")
|
|
780
|
+
mcp_server_disable_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "servers", "([^/]+)", "disable")
|
|
781
|
+
mcp_config_file_route = url_path_join(base_url, "signalpilot-ai-internal", "mcp", "config-file")
|
|
750
782
|
|
|
751
783
|
handlers = [
|
|
752
784
|
# Original endpoint
|
|
@@ -791,9 +823,28 @@ def setup_handlers(web_app):
|
|
|
791
823
|
# Snowflake service endpoints
|
|
792
824
|
(snowflake_schema_route, SnowflakeSchemaHandler),
|
|
793
825
|
(snowflake_query_route, SnowflakeQueryHandler),
|
|
794
|
-
|
|
826
|
+
|
|
827
|
+
# Databricks service endpoints
|
|
828
|
+
(databricks_schema_route, DatabricksSchemaHandler),
|
|
829
|
+
(databricks_query_route, DatabricksQueryHandler),
|
|
830
|
+
(databricks_test_route, DatabricksTestHandler),
|
|
831
|
+
|
|
795
832
|
# Notebook HTML export endpoint
|
|
796
833
|
(notebook_html_route, NotebookToHTMLHandler),
|
|
834
|
+
|
|
835
|
+
# MCP service endpoints
|
|
836
|
+
# Note: More specific routes should come before parameterized routes
|
|
837
|
+
(mcp_config_file_route, MCPConfigFileHandler),
|
|
838
|
+
(mcp_servers_route, MCPServersHandler),
|
|
839
|
+
(mcp_server_route, MCPServerHandler),
|
|
840
|
+
(mcp_connect_route, MCPConnectHandler),
|
|
841
|
+
(mcp_disconnect_route, MCPDisconnectHandler),
|
|
842
|
+
(mcp_tools_route, MCPToolsHandler),
|
|
843
|
+
(mcp_all_tools_route, MCPAllToolsHandler),
|
|
844
|
+
(mcp_tool_call_route, MCPToolCallHandler),
|
|
845
|
+
(mcp_tool_enable_route, MCPToolEnableHandler),
|
|
846
|
+
(mcp_server_enable_route, MCPServerEnableHandler),
|
|
847
|
+
(mcp_server_disable_route, MCPServerDisableHandler),
|
|
797
848
|
]
|
|
798
849
|
|
|
799
850
|
web_app.add_handlers(host_pattern, handlers)
|
|
@@ -806,6 +857,18 @@ def setup_handlers(web_app):
|
|
|
806
857
|
else:
|
|
807
858
|
print("WARNING: SignalPilot AI cache service failed to initialize!")
|
|
808
859
|
|
|
860
|
+
# Register cleanup handler for MCP servers on shutdown
|
|
861
|
+
from .mcp_server_manager import get_mcp_server_manager
|
|
862
|
+
|
|
863
|
+
def cleanup_mcp_servers():
|
|
864
|
+
"""Stop all MCP servers on shutdown"""
|
|
865
|
+
manager = get_mcp_server_manager()
|
|
866
|
+
manager.stop_all_servers()
|
|
867
|
+
|
|
868
|
+
# Register cleanup with web app
|
|
869
|
+
import atexit
|
|
870
|
+
atexit.register(cleanup_mcp_servers)
|
|
871
|
+
|
|
809
872
|
print("SignalPilot AI backend handlers registered:")
|
|
810
873
|
print(f" - Hello World: {hello_route}")
|
|
811
874
|
print(f" - Read All Files: {read_all_files_route}")
|
|
@@ -821,5 +884,12 @@ def setup_handlers(web_app):
|
|
|
821
884
|
print(f" - MySQL Query: {mysql_query_route}")
|
|
822
885
|
print(f" - Snowflake Schema: {snowflake_schema_route}")
|
|
823
886
|
print(f" - Snowflake Query: {snowflake_query_route}")
|
|
887
|
+
print(f" - Databricks Schema: {databricks_schema_route}")
|
|
888
|
+
print(f" - Databricks Query: {databricks_query_route}")
|
|
889
|
+
print(f" - Databricks Test: {databricks_test_route}")
|
|
824
890
|
print(f" - Notebook Cells: {notebook_cells_route}")
|
|
825
891
|
print(f" - Notebook to HTML: {notebook_html_route}")
|
|
892
|
+
print(f" - MCP Servers: {mcp_servers_route}")
|
|
893
|
+
print(f" - MCP Connect: {mcp_connect_route}")
|
|
894
|
+
print(f" - MCP Tools: {mcp_all_tools_route}")
|
|
895
|
+
print(f" - MCP Tool Call: {mcp_tool_call_route}")
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Handlers - Tornado HTTP handlers for MCP API endpoints
|
|
3
|
+
Provides REST API for managing MCP server connections and tool calls
|
|
4
|
+
"""
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import traceback
|
|
8
|
+
import tornado
|
|
9
|
+
from jupyter_server.base.handlers import APIHandler
|
|
10
|
+
from .mcp_service import get_mcp_service
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Enable debug logging
|
|
15
|
+
logger.setLevel(logging.DEBUG)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MCPServersHandler(APIHandler):
|
|
19
|
+
"""Handler for managing MCP server configurations"""
|
|
20
|
+
|
|
21
|
+
@tornado.web.authenticated
|
|
22
|
+
async def get(self):
|
|
23
|
+
"""Get all configured MCP servers"""
|
|
24
|
+
try:
|
|
25
|
+
mcp_service = get_mcp_service()
|
|
26
|
+
configs = mcp_service.load_all_configs()
|
|
27
|
+
|
|
28
|
+
# Add connection status to each server
|
|
29
|
+
servers = []
|
|
30
|
+
for server_id, config in configs.items():
|
|
31
|
+
server_info = {
|
|
32
|
+
**config,
|
|
33
|
+
'status': mcp_service.get_connection_status(server_id),
|
|
34
|
+
'enabled': config.get('enabled', True)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# Add tool count if connected
|
|
38
|
+
if server_id in mcp_service.tools_cache:
|
|
39
|
+
server_info['toolCount'] = len(mcp_service.tools_cache[server_id])
|
|
40
|
+
|
|
41
|
+
servers.append(server_info)
|
|
42
|
+
|
|
43
|
+
self.finish(json.dumps({
|
|
44
|
+
'servers': servers
|
|
45
|
+
}))
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Error getting MCP servers: {e}")
|
|
48
|
+
self.set_status(500)
|
|
49
|
+
self.finish(json.dumps({
|
|
50
|
+
'error': str(e)
|
|
51
|
+
}))
|
|
52
|
+
|
|
53
|
+
@tornado.web.authenticated
|
|
54
|
+
async def post(self):
|
|
55
|
+
"""Save a new MCP server configuration"""
|
|
56
|
+
try:
|
|
57
|
+
data = json.loads(self.request.body.decode('utf-8'))
|
|
58
|
+
|
|
59
|
+
# Validate required fields
|
|
60
|
+
if 'name' not in data:
|
|
61
|
+
self.set_status(400)
|
|
62
|
+
self.finish(json.dumps({
|
|
63
|
+
'error': 'Server name is required'
|
|
64
|
+
}))
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# Determine connection type (default to 'command' if not specified)
|
|
68
|
+
connection_type = data.get('type', 'command')
|
|
69
|
+
data['type'] = connection_type # Ensure type is set in the data
|
|
70
|
+
|
|
71
|
+
if connection_type == 'command':
|
|
72
|
+
if 'command' not in data:
|
|
73
|
+
self.set_status(400)
|
|
74
|
+
self.finish(json.dumps({
|
|
75
|
+
'error': 'Command is required for command-based MCP'
|
|
76
|
+
}))
|
|
77
|
+
return
|
|
78
|
+
elif connection_type in ['http', 'sse']:
|
|
79
|
+
if 'url' not in data:
|
|
80
|
+
self.set_status(400)
|
|
81
|
+
self.finish(json.dumps({
|
|
82
|
+
'error': 'URL is required for HTTP/SSE MCP'
|
|
83
|
+
}))
|
|
84
|
+
return
|
|
85
|
+
else:
|
|
86
|
+
self.set_status(400)
|
|
87
|
+
self.finish(json.dumps({
|
|
88
|
+
'error': f'Invalid connection type: {connection_type}'
|
|
89
|
+
}))
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Save configuration
|
|
93
|
+
mcp_service = get_mcp_service()
|
|
94
|
+
saved_config = mcp_service.save_server_config(data)
|
|
95
|
+
|
|
96
|
+
self.finish(json.dumps({
|
|
97
|
+
'success': True,
|
|
98
|
+
'server': saved_config
|
|
99
|
+
}))
|
|
100
|
+
except json.JSONDecodeError:
|
|
101
|
+
self.set_status(400)
|
|
102
|
+
self.finish(json.dumps({
|
|
103
|
+
'error': 'Invalid JSON in request body'
|
|
104
|
+
}))
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Error saving MCP server: {e}")
|
|
107
|
+
self.set_status(500)
|
|
108
|
+
self.finish(json.dumps({
|
|
109
|
+
'error': str(e)
|
|
110
|
+
}))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class MCPServerHandler(APIHandler):
|
|
114
|
+
"""Handler for individual MCP server operations"""
|
|
115
|
+
|
|
116
|
+
@tornado.web.authenticated
|
|
117
|
+
async def delete(self, server_id):
|
|
118
|
+
"""Delete an MCP server configuration"""
|
|
119
|
+
try:
|
|
120
|
+
mcp_service = get_mcp_service()
|
|
121
|
+
success = mcp_service.delete_server_config(server_id)
|
|
122
|
+
|
|
123
|
+
if success:
|
|
124
|
+
self.finish(json.dumps({
|
|
125
|
+
'success': True,
|
|
126
|
+
'message': f'Server {server_id} deleted'
|
|
127
|
+
}))
|
|
128
|
+
else:
|
|
129
|
+
self.set_status(404)
|
|
130
|
+
self.finish(json.dumps({
|
|
131
|
+
'error': 'Server not found'
|
|
132
|
+
}))
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Error deleting MCP server: {e}")
|
|
135
|
+
self.set_status(500)
|
|
136
|
+
self.finish(json.dumps({
|
|
137
|
+
'error': str(e)
|
|
138
|
+
}))
|
|
139
|
+
|
|
140
|
+
@tornado.web.authenticated
|
|
141
|
+
async def put(self, server_id):
|
|
142
|
+
"""Update an MCP server configuration"""
|
|
143
|
+
try:
|
|
144
|
+
data = json.loads(self.request.body.decode('utf-8'))
|
|
145
|
+
mcp_service = get_mcp_service()
|
|
146
|
+
|
|
147
|
+
# Get existing config
|
|
148
|
+
config = mcp_service.get_server_config(server_id)
|
|
149
|
+
if not config:
|
|
150
|
+
self.set_status(404)
|
|
151
|
+
self.finish(json.dumps({
|
|
152
|
+
'error': 'Server not found'
|
|
153
|
+
}))
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Update config with new data
|
|
157
|
+
config.update(data)
|
|
158
|
+
config['id'] = server_id # Ensure ID is preserved
|
|
159
|
+
|
|
160
|
+
# Save updated config
|
|
161
|
+
saved_config = mcp_service.save_server_config(config)
|
|
162
|
+
|
|
163
|
+
self.finish(json.dumps({
|
|
164
|
+
'success': True,
|
|
165
|
+
'server': saved_config
|
|
166
|
+
}))
|
|
167
|
+
except json.JSONDecodeError:
|
|
168
|
+
self.set_status(400)
|
|
169
|
+
self.finish(json.dumps({
|
|
170
|
+
'error': 'Invalid JSON in request body'
|
|
171
|
+
}))
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"Error updating MCP server: {e}")
|
|
174
|
+
self.set_status(500)
|
|
175
|
+
self.finish(json.dumps({
|
|
176
|
+
'error': str(e)
|
|
177
|
+
}))
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class MCPConnectHandler(APIHandler):
|
|
181
|
+
"""Handler for connecting to MCP servers"""
|
|
182
|
+
|
|
183
|
+
@tornado.web.authenticated
|
|
184
|
+
async def post(self):
|
|
185
|
+
"""Connect to a specific MCP server"""
|
|
186
|
+
server_id = None
|
|
187
|
+
try:
|
|
188
|
+
logger.debug(f"[MCP Handler] Received connect request")
|
|
189
|
+
data = json.loads(self.request.body.decode('utf-8'))
|
|
190
|
+
server_id = data.get('server_id')
|
|
191
|
+
|
|
192
|
+
logger.debug(f"[MCP Handler] Request data: {data}")
|
|
193
|
+
|
|
194
|
+
if not server_id:
|
|
195
|
+
logger.warning(f"[MCP Handler] Missing server_id in request")
|
|
196
|
+
self.set_status(400)
|
|
197
|
+
self.finish(json.dumps({
|
|
198
|
+
'error': 'server_id is required'
|
|
199
|
+
}))
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
logger.info(f"[MCP Handler] Attempting to connect to server: {server_id}")
|
|
203
|
+
mcp_service = get_mcp_service()
|
|
204
|
+
server_info = await mcp_service.connect(server_id)
|
|
205
|
+
|
|
206
|
+
logger.info(f"[MCP Handler] Successfully connected to {server_id}")
|
|
207
|
+
self.finish(json.dumps({
|
|
208
|
+
'success': True,
|
|
209
|
+
'server': server_info
|
|
210
|
+
}))
|
|
211
|
+
except json.JSONDecodeError as e:
|
|
212
|
+
logger.error(f"[MCP Handler] Invalid JSON in request body: {e}")
|
|
213
|
+
logger.error(f"[MCP Handler] Stack trace:\n{traceback.format_exc()}")
|
|
214
|
+
self.set_status(400)
|
|
215
|
+
self.finish(json.dumps({
|
|
216
|
+
'error': f'Invalid JSON in request body: {str(e)}'
|
|
217
|
+
}))
|
|
218
|
+
except ValueError as e:
|
|
219
|
+
logger.error(f"[MCP Handler] ValueError for server {server_id}: {e}")
|
|
220
|
+
logger.error(f"[MCP Handler] Stack trace:\n{traceback.format_exc()}")
|
|
221
|
+
self.set_status(404)
|
|
222
|
+
self.finish(json.dumps({
|
|
223
|
+
'error': str(e),
|
|
224
|
+
'errorType': 'ValueError',
|
|
225
|
+
'serverId': server_id
|
|
226
|
+
}))
|
|
227
|
+
except RuntimeError as e:
|
|
228
|
+
logger.error(f"[MCP Handler] RuntimeError connecting to {server_id}: {e}")
|
|
229
|
+
logger.error(f"[MCP Handler] Stack trace:\n{traceback.format_exc()}")
|
|
230
|
+
self.set_status(500)
|
|
231
|
+
self.finish(json.dumps({
|
|
232
|
+
'error': str(e),
|
|
233
|
+
'errorType': 'RuntimeError',
|
|
234
|
+
'serverId': server_id,
|
|
235
|
+
'details': 'Check server logs for detailed error information'
|
|
236
|
+
}))
|
|
237
|
+
except Exception as e:
|
|
238
|
+
error_type = type(e).__name__
|
|
239
|
+
error_msg = str(e)
|
|
240
|
+
logger.error(f"[MCP Handler] Unexpected error ({error_type}) connecting to {server_id}: {error_msg}")
|
|
241
|
+
logger.error(f"[MCP Handler] Full stack trace:\n{traceback.format_exc()}")
|
|
242
|
+
self.set_status(500)
|
|
243
|
+
self.finish(json.dumps({
|
|
244
|
+
'error': error_msg,
|
|
245
|
+
'errorType': error_type,
|
|
246
|
+
'serverId': server_id,
|
|
247
|
+
'stackTrace': traceback.format_exc(),
|
|
248
|
+
'details': 'An unexpected error occurred. Check server logs for more information.'
|
|
249
|
+
}))
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class MCPDisconnectHandler(APIHandler):
|
|
253
|
+
"""Handler for disconnecting from MCP servers"""
|
|
254
|
+
|
|
255
|
+
@tornado.web.authenticated
|
|
256
|
+
async def post(self, server_id):
|
|
257
|
+
"""Disconnect from a specific MCP server"""
|
|
258
|
+
try:
|
|
259
|
+
mcp_service = get_mcp_service()
|
|
260
|
+
success = await mcp_service.disconnect(server_id)
|
|
261
|
+
|
|
262
|
+
if success:
|
|
263
|
+
self.finish(json.dumps({
|
|
264
|
+
'success': True,
|
|
265
|
+
'message': f'Disconnected from server {server_id}'
|
|
266
|
+
}))
|
|
267
|
+
else:
|
|
268
|
+
self.set_status(404)
|
|
269
|
+
self.finish(json.dumps({
|
|
270
|
+
'error': 'Server not found or not connected'
|
|
271
|
+
}))
|
|
272
|
+
except Exception as e:
|
|
273
|
+
logger.error(f"Error disconnecting from MCP server: {e}")
|
|
274
|
+
self.set_status(500)
|
|
275
|
+
self.finish(json.dumps({
|
|
276
|
+
'error': str(e)
|
|
277
|
+
}))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class MCPToolsHandler(APIHandler):
|
|
281
|
+
"""Handler for listing MCP tools"""
|
|
282
|
+
|
|
283
|
+
@tornado.web.authenticated
|
|
284
|
+
async def get(self, server_id):
|
|
285
|
+
"""Get available tools from a connected MCP server"""
|
|
286
|
+
try:
|
|
287
|
+
mcp_service = get_mcp_service()
|
|
288
|
+
tools = await mcp_service.list_tools(server_id)
|
|
289
|
+
|
|
290
|
+
self.finish(json.dumps({
|
|
291
|
+
'tools': tools
|
|
292
|
+
}))
|
|
293
|
+
except ValueError as e:
|
|
294
|
+
self.set_status(404)
|
|
295
|
+
self.finish(json.dumps({
|
|
296
|
+
'error': str(e)
|
|
297
|
+
}))
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Error listing MCP tools: {e}")
|
|
300
|
+
self.set_status(500)
|
|
301
|
+
self.finish(json.dumps({
|
|
302
|
+
'error': str(e)
|
|
303
|
+
}))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class MCPAllToolsHandler(APIHandler):
|
|
307
|
+
"""Handler for getting all tools from all connected servers"""
|
|
308
|
+
|
|
309
|
+
@tornado.web.authenticated
|
|
310
|
+
async def get(self):
|
|
311
|
+
"""Get all tools from all connected MCP servers"""
|
|
312
|
+
try:
|
|
313
|
+
mcp_service = get_mcp_service()
|
|
314
|
+
tools = await mcp_service.get_all_tools()
|
|
315
|
+
|
|
316
|
+
self.finish(json.dumps({
|
|
317
|
+
'tools': tools
|
|
318
|
+
}))
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.error(f"Error getting all MCP tools: {e}")
|
|
321
|
+
self.set_status(500)
|
|
322
|
+
self.finish(json.dumps({
|
|
323
|
+
'error': str(e)
|
|
324
|
+
}))
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class MCPToolCallHandler(APIHandler):
|
|
328
|
+
"""Handler for calling MCP tools"""
|
|
329
|
+
|
|
330
|
+
@tornado.web.authenticated
|
|
331
|
+
async def post(self):
|
|
332
|
+
"""Call a tool on an MCP server"""
|
|
333
|
+
try:
|
|
334
|
+
data = json.loads(self.request.body.decode('utf-8'))
|
|
335
|
+
|
|
336
|
+
server_id = data.get('server_id')
|
|
337
|
+
tool_name = data.get('tool_name')
|
|
338
|
+
arguments = data.get('arguments', {})
|
|
339
|
+
|
|
340
|
+
if not server_id or not tool_name:
|
|
341
|
+
self.set_status(400)
|
|
342
|
+
self.finish(json.dumps({
|
|
343
|
+
'error': 'server_id and tool_name are required'
|
|
344
|
+
}))
|
|
345
|
+
return
|
|
346
|
+
|
|
347
|
+
mcp_service = get_mcp_service()
|
|
348
|
+
result = await mcp_service.call_tool(server_id, tool_name, arguments)
|
|
349
|
+
|
|
350
|
+
self.finish(json.dumps({
|
|
351
|
+
'success': True,
|
|
352
|
+
'result': result
|
|
353
|
+
}))
|
|
354
|
+
except ValueError as e:
|
|
355
|
+
self.set_status(404)
|
|
356
|
+
self.finish(json.dumps({
|
|
357
|
+
'error': str(e)
|
|
358
|
+
}))
|
|
359
|
+
except Exception as e:
|
|
360
|
+
logger.error(f"Error calling MCP tool: {e}")
|
|
361
|
+
self.set_status(500)
|
|
362
|
+
self.finish(json.dumps({
|
|
363
|
+
'error': str(e)
|
|
364
|
+
}))
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class MCPServerEnableHandler(APIHandler):
|
|
368
|
+
"""Handler for enabling MCP servers"""
|
|
369
|
+
|
|
370
|
+
@tornado.web.authenticated
|
|
371
|
+
async def post(self, server_id):
|
|
372
|
+
"""Enable an MCP server"""
|
|
373
|
+
try:
|
|
374
|
+
mcp_service = get_mcp_service()
|
|
375
|
+
success = mcp_service.enable_server(server_id)
|
|
376
|
+
|
|
377
|
+
if success:
|
|
378
|
+
self.finish(json.dumps({
|
|
379
|
+
'success': True,
|
|
380
|
+
'message': f'Server {server_id} enabled'
|
|
381
|
+
}))
|
|
382
|
+
else:
|
|
383
|
+
self.set_status(404)
|
|
384
|
+
self.finish(json.dumps({
|
|
385
|
+
'error': 'Server not found'
|
|
386
|
+
}))
|
|
387
|
+
except Exception as e:
|
|
388
|
+
logger.error(f"Error enabling MCP server: {e}")
|
|
389
|
+
self.set_status(500)
|
|
390
|
+
self.finish(json.dumps({
|
|
391
|
+
'error': str(e)
|
|
392
|
+
}))
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class MCPServerDisableHandler(APIHandler):
|
|
396
|
+
"""Handler for disabling MCP servers"""
|
|
397
|
+
|
|
398
|
+
@tornado.web.authenticated
|
|
399
|
+
async def post(self, server_id):
|
|
400
|
+
"""Disable an MCP server"""
|
|
401
|
+
try:
|
|
402
|
+
mcp_service = get_mcp_service()
|
|
403
|
+
success = mcp_service.disable_server(server_id)
|
|
404
|
+
|
|
405
|
+
if success:
|
|
406
|
+
self.finish(json.dumps({
|
|
407
|
+
'success': True,
|
|
408
|
+
'message': f'Server {server_id} disabled'
|
|
409
|
+
}))
|
|
410
|
+
else:
|
|
411
|
+
self.set_status(404)
|
|
412
|
+
self.finish(json.dumps({
|
|
413
|
+
'error': 'Server not found'
|
|
414
|
+
}))
|
|
415
|
+
except Exception as e:
|
|
416
|
+
logger.error(f"Error disabling MCP server: {e}")
|
|
417
|
+
self.set_status(500)
|
|
418
|
+
self.finish(json.dumps({
|
|
419
|
+
'error': str(e)
|
|
420
|
+
}))
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class MCPToolEnableHandler(APIHandler):
|
|
424
|
+
"""Handler for enabling/disabling individual MCP tools"""
|
|
425
|
+
|
|
426
|
+
@tornado.web.authenticated
|
|
427
|
+
async def put(self, server_id, tool_name):
|
|
428
|
+
"""Update enabled/disabled state for a specific tool"""
|
|
429
|
+
try:
|
|
430
|
+
data = json.loads(self.request.body.decode('utf-8'))
|
|
431
|
+
enabled = data.get('enabled', True)
|
|
432
|
+
|
|
433
|
+
mcp_service = get_mcp_service()
|
|
434
|
+
success = mcp_service.update_tool_enabled(server_id, tool_name, enabled)
|
|
435
|
+
|
|
436
|
+
if success:
|
|
437
|
+
self.finish(json.dumps({
|
|
438
|
+
'success': True,
|
|
439
|
+
'message': f'Tool {tool_name} {"enabled" if enabled else "disabled"}'
|
|
440
|
+
}))
|
|
441
|
+
else:
|
|
442
|
+
self.set_status(404)
|
|
443
|
+
self.finish(json.dumps({
|
|
444
|
+
'error': 'Server not found'
|
|
445
|
+
}))
|
|
446
|
+
except Exception as e:
|
|
447
|
+
logger.error(f"Error updating tool enabled state: {e}")
|
|
448
|
+
self.set_status(500)
|
|
449
|
+
self.finish(json.dumps({
|
|
450
|
+
'error': str(e)
|
|
451
|
+
}))
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class MCPConfigFileHandler(APIHandler):
|
|
455
|
+
"""Handler for managing the entire MCP config file"""
|
|
456
|
+
|
|
457
|
+
@tornado.web.authenticated
|
|
458
|
+
async def get(self):
|
|
459
|
+
"""Get the raw JSON config file content"""
|
|
460
|
+
try:
|
|
461
|
+
logger.debug(f"[MCP ConfigFile Handler] GET request received")
|
|
462
|
+
mcp_service = get_mcp_service()
|
|
463
|
+
content = mcp_service.get_config_file_content()
|
|
464
|
+
|
|
465
|
+
# Ensure content is valid JSON string
|
|
466
|
+
if not content:
|
|
467
|
+
content = json.dumps({'mcpServers': {}}, indent=2)
|
|
468
|
+
|
|
469
|
+
# Validate it's valid JSON
|
|
470
|
+
try:
|
|
471
|
+
json.loads(content)
|
|
472
|
+
except json.JSONDecodeError as e:
|
|
473
|
+
logger.error(f"Config file content is not valid JSON: {e}")
|
|
474
|
+
content = json.dumps({'mcpServers': {}}, indent=2)
|
|
475
|
+
|
|
476
|
+
logger.debug(f"[MCP ConfigFile Handler] Returning config file content ({len(content)} chars)")
|
|
477
|
+
self.set_header('Content-Type', 'application/json; charset=utf-8')
|
|
478
|
+
self.finish(content)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.error(f"Error reading config file: {e}")
|
|
481
|
+
logger.error(f"Stack trace:\n{traceback.format_exc()}")
|
|
482
|
+
self.set_status(500)
|
|
483
|
+
self.set_header('Content-Type', 'application/json')
|
|
484
|
+
self.finish(json.dumps({
|
|
485
|
+
'error': str(e)
|
|
486
|
+
}))
|
|
487
|
+
|
|
488
|
+
@tornado.web.authenticated
|
|
489
|
+
async def put(self):
|
|
490
|
+
"""Update the entire config file with diff detection"""
|
|
491
|
+
try:
|
|
492
|
+
content = self.request.body.decode('utf-8')
|
|
493
|
+
mcp_service = get_mcp_service()
|
|
494
|
+
|
|
495
|
+
result = mcp_service.update_config_file(content)
|
|
496
|
+
|
|
497
|
+
self.finish(json.dumps(result))
|
|
498
|
+
except ValueError as e:
|
|
499
|
+
self.set_status(400)
|
|
500
|
+
self.finish(json.dumps({
|
|
501
|
+
'error': str(e)
|
|
502
|
+
}))
|
|
503
|
+
except Exception as e:
|
|
504
|
+
logger.error(f"Error updating config file: {e}")
|
|
505
|
+
self.set_status(500)
|
|
506
|
+
self.finish(json.dumps({
|
|
507
|
+
'error': str(e)
|
|
508
|
+
}))
|