db-connect-mcp 0.1.0__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.
Potentially problematic release.
This version of db-connect-mcp might be problematic. Click here for more details.
- db_connect_mcp/__init__.py +30 -0
- db_connect_mcp/__main__.py +13 -0
- db_connect_mcp/adapters/__init__.py +72 -0
- db_connect_mcp/adapters/base.py +152 -0
- db_connect_mcp/adapters/clickhouse.py +298 -0
- db_connect_mcp/adapters/mysql.py +288 -0
- db_connect_mcp/adapters/postgresql.py +351 -0
- db_connect_mcp/core/__init__.py +13 -0
- db_connect_mcp/core/analyzer.py +114 -0
- db_connect_mcp/core/connection.py +371 -0
- db_connect_mcp/core/executor.py +239 -0
- db_connect_mcp/core/inspector.py +345 -0
- db_connect_mcp/models/__init__.py +23 -0
- db_connect_mcp/models/capabilities.py +98 -0
- db_connect_mcp/models/config.py +401 -0
- db_connect_mcp/models/database.py +112 -0
- db_connect_mcp/models/query.py +119 -0
- db_connect_mcp/models/statistics.py +176 -0
- db_connect_mcp/models/table.py +230 -0
- db_connect_mcp/server.py +496 -0
- db_connect_mcp-0.1.0.dist-info/METADATA +565 -0
- db_connect_mcp-0.1.0.dist-info/RECORD +25 -0
- db_connect_mcp-0.1.0.dist-info/WHEEL +4 -0
- db_connect_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- db_connect_mcp-0.1.0.dist-info/licenses/LICENSE +21 -0
db_connect_mcp/server.py
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""Multi-database MCP Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server providing database analysis and querying
|
|
4
|
+
capabilities for PostgreSQL, MySQL, and ClickHouse databases.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, Optional
|
|
12
|
+
|
|
13
|
+
from dotenv import load_dotenv
|
|
14
|
+
from mcp.server import Server
|
|
15
|
+
from mcp.types import TextContent, Tool
|
|
16
|
+
|
|
17
|
+
from db_connect_mcp.adapters import create_adapter
|
|
18
|
+
from db_connect_mcp.core import (
|
|
19
|
+
DatabaseConnection,
|
|
20
|
+
MetadataInspector,
|
|
21
|
+
QueryExecutor,
|
|
22
|
+
StatisticsAnalyzer,
|
|
23
|
+
)
|
|
24
|
+
from db_connect_mcp.models.config import DatabaseConfig
|
|
25
|
+
|
|
26
|
+
# Load environment variables
|
|
27
|
+
load_dotenv()
|
|
28
|
+
|
|
29
|
+
# Configure logging
|
|
30
|
+
logging.basicConfig(level=logging.INFO)
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DatabaseMCPServer:
|
|
35
|
+
"""MCP server for multi-database operations."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, config: DatabaseConfig):
|
|
38
|
+
"""
|
|
39
|
+
Initialize database MCP server.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
config: Database configuration
|
|
43
|
+
"""
|
|
44
|
+
self.config = config
|
|
45
|
+
self.connection = DatabaseConnection(config)
|
|
46
|
+
self.adapter = create_adapter(config)
|
|
47
|
+
self.inspector: Optional[MetadataInspector] = None
|
|
48
|
+
self.executor: Optional[QueryExecutor] = None
|
|
49
|
+
self.analyzer: Optional[StatisticsAnalyzer] = None
|
|
50
|
+
self.server = Server("db-mcp")
|
|
51
|
+
|
|
52
|
+
async def initialize(self) -> None:
|
|
53
|
+
"""Initialize all components."""
|
|
54
|
+
await self.connection.initialize()
|
|
55
|
+
|
|
56
|
+
self.inspector = MetadataInspector(self.connection, self.adapter)
|
|
57
|
+
self.executor = QueryExecutor(self.connection, self.adapter)
|
|
58
|
+
self.analyzer = StatisticsAnalyzer(self.connection, self.adapter)
|
|
59
|
+
|
|
60
|
+
# Register MCP tool handlers
|
|
61
|
+
await self._register_tools()
|
|
62
|
+
|
|
63
|
+
logger.info(
|
|
64
|
+
f"Initialized {self.config.dialect} MCP server "
|
|
65
|
+
f"({len(self.adapter.capabilities.get_supported_features())} features)"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def _register_tools(self) -> None:
|
|
69
|
+
"""Register MCP tools based on database capabilities."""
|
|
70
|
+
# Note: Tools are registered via the list_tools decorator, not add_tool
|
|
71
|
+
# This method is kept for initializing any tool-related state
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
def _create_get_database_info_tool(self) -> Tool:
|
|
75
|
+
"""Create get_database_info tool."""
|
|
76
|
+
return Tool(
|
|
77
|
+
name="get_database_info",
|
|
78
|
+
description="Get database information including version, size, and capabilities",
|
|
79
|
+
inputSchema={
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {},
|
|
82
|
+
"required": [],
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def _create_list_schemas_tool(self) -> Tool:
|
|
87
|
+
"""Create list_schemas tool."""
|
|
88
|
+
return Tool(
|
|
89
|
+
name="list_schemas",
|
|
90
|
+
description="List all schemas/databases in the database instance",
|
|
91
|
+
inputSchema={
|
|
92
|
+
"type": "object",
|
|
93
|
+
"properties": {},
|
|
94
|
+
"required": [],
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _create_list_tables_tool(self) -> Tool:
|
|
99
|
+
"""Create list_tables tool."""
|
|
100
|
+
return Tool(
|
|
101
|
+
name="list_tables",
|
|
102
|
+
description="List all tables and views in a schema",
|
|
103
|
+
inputSchema={
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"schema": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "Schema name (optional, uses default if not specified)",
|
|
109
|
+
},
|
|
110
|
+
"include_views": {
|
|
111
|
+
"type": "boolean",
|
|
112
|
+
"description": "Whether to include views (default: true)",
|
|
113
|
+
"default": True,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
"required": [],
|
|
117
|
+
},
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _create_describe_table_tool(self) -> Tool:
|
|
121
|
+
"""Create describe_table tool."""
|
|
122
|
+
return Tool(
|
|
123
|
+
name="describe_table",
|
|
124
|
+
description="Get comprehensive table information including columns, indexes, and constraints",
|
|
125
|
+
inputSchema={
|
|
126
|
+
"type": "object",
|
|
127
|
+
"properties": {
|
|
128
|
+
"table": {"type": "string", "description": "Table name"},
|
|
129
|
+
"schema": {
|
|
130
|
+
"type": "string",
|
|
131
|
+
"description": "Schema name (optional)",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
"required": ["table"],
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _create_execute_query_tool(self) -> Tool:
|
|
139
|
+
"""Create execute_query tool."""
|
|
140
|
+
return Tool(
|
|
141
|
+
name="execute_query",
|
|
142
|
+
description="Execute a read-only SQL query (SELECT, WITH, EXPLAIN)",
|
|
143
|
+
inputSchema={
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"query": {"type": "string", "description": "SQL query to execute"},
|
|
147
|
+
"limit": {
|
|
148
|
+
"type": "integer",
|
|
149
|
+
"description": "Maximum number of rows to return (default: 1000)",
|
|
150
|
+
"default": 1000,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
"required": ["query"],
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def _create_sample_data_tool(self) -> Tool:
|
|
158
|
+
"""Create sample_data tool."""
|
|
159
|
+
return Tool(
|
|
160
|
+
name="sample_data",
|
|
161
|
+
description="Sample data from a table efficiently",
|
|
162
|
+
inputSchema={
|
|
163
|
+
"type": "object",
|
|
164
|
+
"properties": {
|
|
165
|
+
"table": {"type": "string", "description": "Table name"},
|
|
166
|
+
"schema": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "Schema name (optional)",
|
|
169
|
+
},
|
|
170
|
+
"limit": {
|
|
171
|
+
"type": "integer",
|
|
172
|
+
"description": "Number of rows to sample (default: 100)",
|
|
173
|
+
"default": 100,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
"required": ["table"],
|
|
177
|
+
},
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def _create_get_relationships_tool(self) -> Tool:
|
|
181
|
+
"""Create get_table_relationships tool."""
|
|
182
|
+
return Tool(
|
|
183
|
+
name="get_table_relationships",
|
|
184
|
+
description="Get foreign key relationships for a table",
|
|
185
|
+
inputSchema={
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": {
|
|
188
|
+
"table": {"type": "string", "description": "Table name"},
|
|
189
|
+
"schema": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "Schema name (optional)",
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
"required": ["table"],
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _create_analyze_column_tool(self) -> Tool:
|
|
199
|
+
"""Create analyze_column tool."""
|
|
200
|
+
return Tool(
|
|
201
|
+
name="analyze_column",
|
|
202
|
+
description="Get comprehensive column statistics including percentiles and distributions",
|
|
203
|
+
inputSchema={
|
|
204
|
+
"type": "object",
|
|
205
|
+
"properties": {
|
|
206
|
+
"table": {"type": "string", "description": "Table name"},
|
|
207
|
+
"column": {"type": "string", "description": "Column name"},
|
|
208
|
+
"schema": {
|
|
209
|
+
"type": "string",
|
|
210
|
+
"description": "Schema name (optional)",
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
"required": ["table", "column"],
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _create_explain_query_tool(self) -> Tool:
|
|
218
|
+
"""Create explain_query tool."""
|
|
219
|
+
return Tool(
|
|
220
|
+
name="explain_query",
|
|
221
|
+
description="Get query execution plan to analyze performance",
|
|
222
|
+
inputSchema={
|
|
223
|
+
"type": "object",
|
|
224
|
+
"properties": {
|
|
225
|
+
"query": {"type": "string", "description": "SQL query to explain"},
|
|
226
|
+
"analyze": {
|
|
227
|
+
"type": "boolean",
|
|
228
|
+
"description": "Whether to execute the query (EXPLAIN ANALYZE)",
|
|
229
|
+
"default": False,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
"required": ["query"],
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _create_profile_database_tool(self) -> Tool:
|
|
237
|
+
"""Create profile_database tool."""
|
|
238
|
+
return Tool(
|
|
239
|
+
name="profile_database",
|
|
240
|
+
description="Get database-wide profiling information (size, table counts, etc.)",
|
|
241
|
+
inputSchema={
|
|
242
|
+
"type": "object",
|
|
243
|
+
"properties": {},
|
|
244
|
+
"required": [],
|
|
245
|
+
},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Tool handlers
|
|
249
|
+
async def handle_get_database_info(
|
|
250
|
+
self, arguments: dict[str, Any]
|
|
251
|
+
) -> list[TextContent]:
|
|
252
|
+
"""Handle get_database_info request."""
|
|
253
|
+
assert self.inspector is not None
|
|
254
|
+
|
|
255
|
+
version = await self.connection.get_version()
|
|
256
|
+
|
|
257
|
+
# Sanitize connection URL (remove password)
|
|
258
|
+
url_parts = self.config.url.split("@")
|
|
259
|
+
if len(url_parts) > 1:
|
|
260
|
+
sanitized_url = f"<credentials>@{url_parts[-1]}"
|
|
261
|
+
else:
|
|
262
|
+
sanitized_url = self.config.url
|
|
263
|
+
|
|
264
|
+
from db_connect_mcp.models.database import DatabaseInfo
|
|
265
|
+
|
|
266
|
+
db_info = DatabaseInfo(
|
|
267
|
+
name=self.config.url.split("/")[-1], # Extract DB name from URL
|
|
268
|
+
dialect=self.config.dialect,
|
|
269
|
+
version=version,
|
|
270
|
+
size_bytes=None,
|
|
271
|
+
schema_count=None,
|
|
272
|
+
table_count=None,
|
|
273
|
+
capabilities=self.adapter.capabilities,
|
|
274
|
+
server_encoding=None,
|
|
275
|
+
collation=None,
|
|
276
|
+
connection_url=sanitized_url,
|
|
277
|
+
read_only=self.config.read_only,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return [
|
|
281
|
+
TextContent(type="text", text=json.dumps(db_info.model_dump(), indent=2))
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
async def handle_list_schemas(self, arguments: dict[str, Any]) -> list[TextContent]:
|
|
285
|
+
"""Handle list_schemas request."""
|
|
286
|
+
assert self.inspector is not None
|
|
287
|
+
|
|
288
|
+
schemas = await self.inspector.get_schemas()
|
|
289
|
+
schemas_data = [s.model_dump() for s in schemas]
|
|
290
|
+
|
|
291
|
+
return [TextContent(type="text", text=json.dumps(schemas_data, indent=2))]
|
|
292
|
+
|
|
293
|
+
async def handle_list_tables(self, arguments: dict[str, Any]) -> list[TextContent]:
|
|
294
|
+
"""Handle list_tables request."""
|
|
295
|
+
assert self.inspector is not None
|
|
296
|
+
|
|
297
|
+
schema = arguments.get("schema")
|
|
298
|
+
include_views = arguments.get("include_views", True)
|
|
299
|
+
|
|
300
|
+
tables = await self.inspector.get_tables(schema, include_views)
|
|
301
|
+
tables_data = [t.model_dump() for t in tables]
|
|
302
|
+
|
|
303
|
+
return [TextContent(type="text", text=json.dumps(tables_data, indent=2))]
|
|
304
|
+
|
|
305
|
+
async def handle_describe_table(
|
|
306
|
+
self, arguments: dict[str, Any]
|
|
307
|
+
) -> list[TextContent]:
|
|
308
|
+
"""Handle describe_table request."""
|
|
309
|
+
assert self.inspector is not None
|
|
310
|
+
|
|
311
|
+
table = arguments["table"]
|
|
312
|
+
schema = arguments.get("schema")
|
|
313
|
+
|
|
314
|
+
table_info = await self.inspector.describe_table(table, schema)
|
|
315
|
+
|
|
316
|
+
return [
|
|
317
|
+
TextContent(type="text", text=json.dumps(table_info.model_dump(), indent=2))
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
async def handle_execute_query(
|
|
321
|
+
self, arguments: dict[str, Any]
|
|
322
|
+
) -> list[TextContent]:
|
|
323
|
+
"""Handle execute_query request."""
|
|
324
|
+
assert self.executor is not None
|
|
325
|
+
|
|
326
|
+
query = arguments["query"]
|
|
327
|
+
limit = arguments.get("limit", 1000)
|
|
328
|
+
|
|
329
|
+
result = await self.executor.execute_query(query, limit=limit)
|
|
330
|
+
|
|
331
|
+
return [
|
|
332
|
+
TextContent(type="text", text=json.dumps(result.model_dump(), indent=2))
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
async def handle_sample_data(self, arguments: dict[str, Any]) -> list[TextContent]:
|
|
336
|
+
"""Handle sample_data request."""
|
|
337
|
+
assert self.executor is not None
|
|
338
|
+
|
|
339
|
+
table = arguments["table"]
|
|
340
|
+
schema = arguments.get("schema")
|
|
341
|
+
limit = arguments.get("limit", 100)
|
|
342
|
+
|
|
343
|
+
result = await self.executor.sample_data(table, schema, limit)
|
|
344
|
+
|
|
345
|
+
return [
|
|
346
|
+
TextContent(type="text", text=json.dumps(result.model_dump(), indent=2))
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
async def handle_get_relationships(
|
|
350
|
+
self, arguments: dict[str, Any]
|
|
351
|
+
) -> list[TextContent]:
|
|
352
|
+
"""Handle get_table_relationships request."""
|
|
353
|
+
assert self.inspector is not None
|
|
354
|
+
|
|
355
|
+
table = arguments["table"]
|
|
356
|
+
schema = arguments.get("schema")
|
|
357
|
+
|
|
358
|
+
relationships = await self.inspector.get_relationships(table, schema)
|
|
359
|
+
relationships_data = [r.model_dump() for r in relationships]
|
|
360
|
+
|
|
361
|
+
return [TextContent(type="text", text=json.dumps(relationships_data, indent=2))]
|
|
362
|
+
|
|
363
|
+
async def handle_analyze_column(
|
|
364
|
+
self, arguments: dict[str, Any]
|
|
365
|
+
) -> list[TextContent]:
|
|
366
|
+
"""Handle analyze_column request."""
|
|
367
|
+
assert self.analyzer is not None
|
|
368
|
+
|
|
369
|
+
table = arguments["table"]
|
|
370
|
+
column = arguments["column"]
|
|
371
|
+
schema = arguments.get("schema")
|
|
372
|
+
|
|
373
|
+
stats = await self.analyzer.analyze_column(table, column, schema)
|
|
374
|
+
|
|
375
|
+
return [TextContent(type="text", text=json.dumps(stats.model_dump(), indent=2))]
|
|
376
|
+
|
|
377
|
+
async def handle_explain_query(
|
|
378
|
+
self, arguments: dict[str, Any]
|
|
379
|
+
) -> list[TextContent]:
|
|
380
|
+
"""Handle explain_query request."""
|
|
381
|
+
assert self.executor is not None
|
|
382
|
+
|
|
383
|
+
query = arguments["query"]
|
|
384
|
+
analyze = arguments.get("analyze", False)
|
|
385
|
+
|
|
386
|
+
plan = await self.executor.explain_query(query, analyze)
|
|
387
|
+
|
|
388
|
+
return [TextContent(type="text", text=json.dumps(plan.model_dump(), indent=2))]
|
|
389
|
+
|
|
390
|
+
async def handle_profile_database(
|
|
391
|
+
self, arguments: dict[str, Any]
|
|
392
|
+
) -> list[TextContent]:
|
|
393
|
+
"""Handle profile_database request."""
|
|
394
|
+
# This would be implemented with adapter-specific profiling queries
|
|
395
|
+
return [
|
|
396
|
+
TextContent(
|
|
397
|
+
type="text",
|
|
398
|
+
text=json.dumps(
|
|
399
|
+
{"message": "Database profiling not yet implemented"}, indent=2
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
async def cleanup(self) -> None:
|
|
405
|
+
"""Cleanup resources."""
|
|
406
|
+
await self.connection.dispose()
|
|
407
|
+
logger.info("Database MCP server cleaned up")
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
async def main() -> None:
|
|
411
|
+
"""Main entry point for the MCP server."""
|
|
412
|
+
# Get database URL from environment
|
|
413
|
+
database_url = os.getenv("DATABASE_URL")
|
|
414
|
+
if not database_url:
|
|
415
|
+
raise ValueError("DATABASE_URL environment variable must be set")
|
|
416
|
+
|
|
417
|
+
# Create configuration
|
|
418
|
+
config = DatabaseConfig(url=database_url)
|
|
419
|
+
|
|
420
|
+
# Create and initialize server
|
|
421
|
+
mcp_server = DatabaseMCPServer(config)
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
await mcp_server.initialize()
|
|
425
|
+
|
|
426
|
+
# Register list_tools handler
|
|
427
|
+
@mcp_server.server.list_tools()
|
|
428
|
+
async def list_tools() -> list[Tool]:
|
|
429
|
+
"""List available tools based on database capabilities."""
|
|
430
|
+
tools = [
|
|
431
|
+
mcp_server._create_get_database_info_tool(),
|
|
432
|
+
mcp_server._create_list_schemas_tool(),
|
|
433
|
+
mcp_server._create_list_tables_tool(),
|
|
434
|
+
mcp_server._create_describe_table_tool(),
|
|
435
|
+
mcp_server._create_execute_query_tool(),
|
|
436
|
+
mcp_server._create_sample_data_tool(),
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
# Add conditional tools
|
|
440
|
+
if mcp_server.adapter.capabilities.foreign_keys:
|
|
441
|
+
tools.append(mcp_server._create_get_relationships_tool())
|
|
442
|
+
|
|
443
|
+
if mcp_server.adapter.capabilities.advanced_stats:
|
|
444
|
+
tools.append(mcp_server._create_analyze_column_tool())
|
|
445
|
+
|
|
446
|
+
if mcp_server.adapter.capabilities.explain_plans:
|
|
447
|
+
tools.append(mcp_server._create_explain_query_tool())
|
|
448
|
+
|
|
449
|
+
if mcp_server.adapter.capabilities.profiling:
|
|
450
|
+
tools.append(mcp_server._create_profile_database_tool())
|
|
451
|
+
|
|
452
|
+
return tools
|
|
453
|
+
|
|
454
|
+
# Register tool call handlers
|
|
455
|
+
@mcp_server.server.call_tool()
|
|
456
|
+
async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]:
|
|
457
|
+
"""Handle tool calls."""
|
|
458
|
+
handlers = {
|
|
459
|
+
"get_database_info": mcp_server.handle_get_database_info,
|
|
460
|
+
"list_schemas": mcp_server.handle_list_schemas,
|
|
461
|
+
"list_tables": mcp_server.handle_list_tables,
|
|
462
|
+
"describe_table": mcp_server.handle_describe_table,
|
|
463
|
+
"execute_query": mcp_server.handle_execute_query,
|
|
464
|
+
"sample_data": mcp_server.handle_sample_data,
|
|
465
|
+
"get_table_relationships": mcp_server.handle_get_relationships,
|
|
466
|
+
"analyze_column": mcp_server.handle_analyze_column,
|
|
467
|
+
"explain_query": mcp_server.handle_explain_query,
|
|
468
|
+
"profile_database": mcp_server.handle_profile_database,
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
handler = handlers.get(name)
|
|
472
|
+
if handler is None:
|
|
473
|
+
raise ValueError(f"Unknown tool: {name}")
|
|
474
|
+
|
|
475
|
+
return await handler(arguments)
|
|
476
|
+
|
|
477
|
+
# Run the server
|
|
478
|
+
from mcp.server.stdio import stdio_server
|
|
479
|
+
|
|
480
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
481
|
+
await mcp_server.server.run(
|
|
482
|
+
read_stream,
|
|
483
|
+
write_stream,
|
|
484
|
+
mcp_server.server.create_initialization_options(),
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
finally:
|
|
488
|
+
await mcp_server.cleanup()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
if __name__ == "__main__":
|
|
492
|
+
# Windows-specific event loop policy
|
|
493
|
+
if os.name == "nt":
|
|
494
|
+
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) # type: ignore[attr-defined]
|
|
495
|
+
|
|
496
|
+
asyncio.run(main())
|