mcp-dbutils 0.2.3__py3-none-any.whl → 0.2.8__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.
- mcp_dbutils/base.py +68 -15
- mcp_dbutils/postgres/handler.py +12 -12
- mcp_dbutils/sqlite/handler.py +19 -8
- mcp_dbutils/stats.py +85 -0
- {mcp_dbutils-0.2.3.dist-info → mcp_dbutils-0.2.8.dist-info}/METADATA +112 -14
- {mcp_dbutils-0.2.3.dist-info → mcp_dbutils-0.2.8.dist-info}/RECORD +9 -8
- {mcp_dbutils-0.2.3.dist-info → mcp_dbutils-0.2.8.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.2.3.dist-info → mcp_dbutils-0.2.8.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.2.3.dist-info → mcp_dbutils-0.2.8.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/base.py
CHANGED
@@ -1,13 +1,27 @@
|
|
1
1
|
"""Database server base class"""
|
2
2
|
|
3
|
+
class DatabaseError(Exception):
|
4
|
+
"""Base exception for database errors"""
|
5
|
+
pass
|
6
|
+
|
7
|
+
class ConfigurationError(DatabaseError):
|
8
|
+
"""Configuration related errors"""
|
9
|
+
pass
|
10
|
+
|
11
|
+
class ConnectionError(DatabaseError):
|
12
|
+
"""Connection related errors"""
|
13
|
+
pass
|
14
|
+
|
3
15
|
from abc import ABC, abstractmethod
|
4
16
|
from typing import Any, List, Optional, AsyncContextManager
|
5
17
|
from contextlib import asynccontextmanager
|
18
|
+
import json
|
6
19
|
import yaml
|
7
20
|
from mcp.server import Server, NotificationOptions
|
8
21
|
import mcp.server.stdio
|
9
22
|
import mcp.types as types
|
10
23
|
from .log import create_logger
|
24
|
+
from .stats import ResourceStats
|
11
25
|
|
12
26
|
class DatabaseHandler(ABC):
|
13
27
|
"""Abstract base class defining common interface for database handlers"""
|
@@ -24,6 +38,7 @@ class DatabaseHandler(ABC):
|
|
24
38
|
self.database = database
|
25
39
|
self.debug = debug
|
26
40
|
self.log = create_logger(f"db-handler-{database}", debug)
|
41
|
+
self.stats = ResourceStats()
|
27
42
|
|
28
43
|
@abstractmethod
|
29
44
|
async def get_tables(self) -> list[types.Resource]:
|
@@ -36,9 +51,22 @@ class DatabaseHandler(ABC):
|
|
36
51
|
pass
|
37
52
|
|
38
53
|
@abstractmethod
|
54
|
+
async def _execute_query(self, sql: str) -> str:
|
55
|
+
"""Internal query execution method to be implemented by subclasses"""
|
56
|
+
pass
|
57
|
+
|
39
58
|
async def execute_query(self, sql: str) -> str:
|
40
59
|
"""Execute SQL query"""
|
41
|
-
|
60
|
+
try:
|
61
|
+
self.stats.record_query()
|
62
|
+
result = await self._execute_query(sql)
|
63
|
+
self.stats.update_memory_usage(result)
|
64
|
+
self.log("info", f"Resource stats: {json.dumps(self.stats.to_dict())}")
|
65
|
+
return result
|
66
|
+
except Exception as e:
|
67
|
+
self.stats.record_error(e.__class__.__name__)
|
68
|
+
self.log("error", f"Query error - {str(e)}\nResource stats: {json.dumps(self.stats.to_dict())}")
|
69
|
+
raise
|
42
70
|
|
43
71
|
@abstractmethod
|
44
72
|
async def cleanup(self):
|
@@ -57,9 +85,18 @@ class DatabaseServer:
|
|
57
85
|
"""
|
58
86
|
self.config_path = config_path
|
59
87
|
self.debug = debug
|
60
|
-
self.
|
88
|
+
self.logger = create_logger("db-server", debug)
|
61
89
|
self.server = Server("database-server")
|
62
90
|
self._setup_handlers()
|
91
|
+
self._setup_prompts()
|
92
|
+
|
93
|
+
def _setup_prompts(self):
|
94
|
+
"""Setup prompts handlers"""
|
95
|
+
async def handle_list_prompts() -> list[types.Prompt]:
|
96
|
+
"""Return empty list of prompts for now"""
|
97
|
+
return []
|
98
|
+
|
99
|
+
self.server.request_handlers["prompts/list"] = handle_list_prompts
|
63
100
|
|
64
101
|
@asynccontextmanager
|
65
102
|
async def get_handler(self, database: str) -> AsyncContextManager[DatabaseHandler]:
|
@@ -77,28 +114,42 @@ class DatabaseServer:
|
|
77
114
|
with open(self.config_path, 'r') as f:
|
78
115
|
config = yaml.safe_load(f)
|
79
116
|
if not config or 'databases' not in config:
|
80
|
-
raise
|
117
|
+
raise ConfigurationError("Configuration file must contain 'databases' section")
|
81
118
|
if database not in config['databases']:
|
82
119
|
available_dbs = list(config['databases'].keys())
|
83
|
-
raise
|
120
|
+
raise ConfigurationError(f"Database configuration not found: {database}. Available configurations: {available_dbs}")
|
84
121
|
|
85
122
|
db_config = config['databases'][database]
|
86
123
|
|
87
124
|
handler = None
|
88
125
|
try:
|
89
|
-
|
90
|
-
|
126
|
+
if 'type' not in db_config:
|
127
|
+
raise ConfigurationError("Database configuration must include 'type' field")
|
128
|
+
|
129
|
+
db_type = db_config['type']
|
130
|
+
self.logger("debug", f"Creating handler for database type: {db_type}")
|
131
|
+
if db_type == 'sqlite':
|
91
132
|
from .sqlite.handler import SqliteHandler
|
92
133
|
handler = SqliteHandler(self.config_path, database, self.debug)
|
93
|
-
elif
|
134
|
+
elif db_type == 'postgres':
|
94
135
|
from .postgres.handler import PostgresHandler
|
95
136
|
handler = PostgresHandler(self.config_path, database, self.debug)
|
96
137
|
else:
|
97
|
-
raise
|
138
|
+
raise ConfigurationError(f"Unsupported database type: {db_type}")
|
98
139
|
|
140
|
+
handler.stats.record_connection_start()
|
141
|
+
self.logger("debug", f"Handler created successfully for {database}")
|
142
|
+
self.logger("info", f"Resource stats: {json.dumps(handler.stats.to_dict())}")
|
99
143
|
yield handler
|
144
|
+
except yaml.YAMLError as e:
|
145
|
+
raise ConfigurationError(f"Invalid YAML configuration: {str(e)}")
|
146
|
+
except ImportError as e:
|
147
|
+
raise ConfigurationError(f"Failed to import handler for {db_type}: {str(e)}")
|
100
148
|
finally:
|
101
149
|
if handler:
|
150
|
+
self.logger("debug", f"Cleaning up handler for {database}")
|
151
|
+
handler.stats.record_connection_end()
|
152
|
+
self.logger("info", f"Final resource stats: {json.dumps(handler.stats.to_dict())}")
|
102
153
|
await handler.cleanup()
|
103
154
|
|
104
155
|
def _setup_handlers(self):
|
@@ -116,11 +167,11 @@ class DatabaseServer:
|
|
116
167
|
@self.server.read_resource()
|
117
168
|
async def handle_read_resource(uri: str, arguments: dict | None = None) -> str:
|
118
169
|
if not arguments or 'database' not in arguments:
|
119
|
-
raise
|
170
|
+
raise ConfigurationError("Database configuration name must be specified")
|
120
171
|
|
121
172
|
parts = uri.split('/')
|
122
173
|
if len(parts) < 3:
|
123
|
-
raise
|
174
|
+
raise ConfigurationError("Invalid resource URI format")
|
124
175
|
|
125
176
|
database = arguments['database']
|
126
177
|
table_name = parts[-2] # URI format: xxx/table_name/schema
|
@@ -154,18 +205,18 @@ class DatabaseServer:
|
|
154
205
|
@self.server.call_tool()
|
155
206
|
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
|
156
207
|
if name != "query":
|
157
|
-
raise
|
208
|
+
raise ConfigurationError(f"Unknown tool: {name}")
|
158
209
|
|
159
210
|
if "database" not in arguments:
|
160
|
-
raise
|
211
|
+
raise ConfigurationError("Database configuration name must be specified")
|
161
212
|
|
162
213
|
sql = arguments.get("sql", "").strip()
|
163
214
|
if not sql:
|
164
|
-
raise
|
215
|
+
raise ConfigurationError("SQL query cannot be empty")
|
165
216
|
|
166
217
|
# Only allow SELECT statements
|
167
218
|
if not sql.lower().startswith("select"):
|
168
|
-
raise
|
219
|
+
raise ConfigurationError("Only SELECT queries are supported for security reasons")
|
169
220
|
|
170
221
|
database = arguments["database"]
|
171
222
|
async with self.get_handler(database) as handler:
|
@@ -175,8 +226,10 @@ class DatabaseServer:
|
|
175
226
|
async def run(self):
|
176
227
|
"""Run server"""
|
177
228
|
async with mcp.server.stdio.stdio_server() as streams:
|
229
|
+
init_options = self.server.create_initialization_options()
|
230
|
+
init_options.capabilities.prompts = types.PromptsCapability(list=True)
|
178
231
|
await self.server.run(
|
179
232
|
streams[0],
|
180
233
|
streams[1],
|
181
|
-
|
234
|
+
init_options
|
182
235
|
)
|
mcp_dbutils/postgres/handler.py
CHANGED
@@ -4,7 +4,7 @@ import psycopg2
|
|
4
4
|
from psycopg2.pool import SimpleConnectionPool
|
5
5
|
import mcp.types as types
|
6
6
|
|
7
|
-
from ..base import DatabaseHandler
|
7
|
+
from ..base import DatabaseHandler, DatabaseError
|
8
8
|
from .config import PostgresConfig
|
9
9
|
|
10
10
|
class PostgresHandler(DatabaseHandler):
|
@@ -51,8 +51,8 @@ class PostgresHandler(DatabaseHandler):
|
|
51
51
|
]
|
52
52
|
except psycopg2.Error as e:
|
53
53
|
error_msg = f"Failed to get table list: [Code: {e.pgcode}] {e.pgerror or str(e)}"
|
54
|
-
self.
|
55
|
-
raise
|
54
|
+
self.stats.record_error(e.__class__.__name__)
|
55
|
+
raise DatabaseError(error_msg)
|
56
56
|
finally:
|
57
57
|
if conn:
|
58
58
|
conn.close()
|
@@ -104,18 +104,19 @@ class PostgresHandler(DatabaseHandler):
|
|
104
104
|
})
|
105
105
|
except psycopg2.Error as e:
|
106
106
|
error_msg = f"Failed to read table schema: [Code: {e.pgcode}] {e.pgerror or str(e)}"
|
107
|
-
self.
|
108
|
-
raise
|
107
|
+
self.stats.record_error(e.__class__.__name__)
|
108
|
+
raise DatabaseError(error_msg)
|
109
109
|
finally:
|
110
110
|
if conn:
|
111
111
|
conn.close()
|
112
112
|
|
113
|
-
async def
|
113
|
+
async def _execute_query(self, sql: str) -> str:
|
114
114
|
"""Execute SQL query"""
|
115
|
+
conn = None
|
115
116
|
try:
|
116
117
|
conn_params = self.config.get_connection_params()
|
117
118
|
conn = psycopg2.connect(**conn_params)
|
118
|
-
self.log("
|
119
|
+
self.log("debug", f"Executing query: {sql}")
|
119
120
|
|
120
121
|
with conn.cursor() as cur:
|
121
122
|
# Start read-only transaction
|
@@ -132,19 +133,18 @@ class PostgresHandler(DatabaseHandler):
|
|
132
133
|
'row_count': len(results)
|
133
134
|
})
|
134
135
|
|
135
|
-
self.log("
|
136
|
+
self.log("debug", f"Query completed, returned {len(results)} rows")
|
136
137
|
return result_text
|
137
138
|
finally:
|
138
139
|
cur.execute("ROLLBACK")
|
139
140
|
except psycopg2.Error as e:
|
140
141
|
error_msg = f"Query execution failed: [Code: {e.pgcode}] {e.pgerror or str(e)}"
|
141
|
-
|
142
|
-
raise
|
142
|
+
raise DatabaseError(error_msg)
|
143
143
|
finally:
|
144
144
|
if conn:
|
145
145
|
conn.close()
|
146
146
|
|
147
147
|
async def cleanup(self):
|
148
148
|
"""Cleanup resources"""
|
149
|
-
#
|
150
|
-
|
149
|
+
# Log final stats before cleanup
|
150
|
+
self.log("info", f"Final PostgreSQL handler stats: {self.stats.to_dict()}")
|
mcp_dbutils/sqlite/handler.py
CHANGED
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
5
5
|
from contextlib import closing
|
6
6
|
import mcp.types as types
|
7
7
|
|
8
|
-
from ..base import DatabaseHandler
|
8
|
+
from ..base import DatabaseHandler, DatabaseError
|
9
9
|
from .config import SqliteConfig
|
10
10
|
|
11
11
|
class SqliteHandler(DatabaseHandler):
|
@@ -86,11 +86,23 @@ class SqliteHandler(DatabaseHandler):
|
|
86
86
|
self.log("error", error_msg)
|
87
87
|
raise
|
88
88
|
|
89
|
-
async def
|
89
|
+
async def _execute_query(self, sql: str) -> str:
|
90
90
|
"""Execute SQL query"""
|
91
|
+
# Check for non-SELECT queries
|
92
|
+
sql_lower = sql.lower().strip()
|
93
|
+
if not sql_lower.startswith('select'):
|
94
|
+
error_msg = "cannot execute DELETE statement"
|
95
|
+
if sql_lower.startswith('delete'):
|
96
|
+
error_msg = "cannot execute DELETE statement"
|
97
|
+
elif sql_lower.startswith('update'):
|
98
|
+
error_msg = "cannot execute UPDATE statement"
|
99
|
+
elif sql_lower.startswith('insert'):
|
100
|
+
error_msg = "cannot execute INSERT statement"
|
101
|
+
raise DatabaseError(error_msg)
|
102
|
+
|
91
103
|
try:
|
92
104
|
with closing(self._get_connection()) as conn:
|
93
|
-
self.log("
|
105
|
+
self.log("debug", f"Executing query: {sql}")
|
94
106
|
cursor = conn.execute(sql)
|
95
107
|
results = cursor.fetchall()
|
96
108
|
|
@@ -103,15 +115,14 @@ class SqliteHandler(DatabaseHandler):
|
|
103
115
|
'row_count': len(results)
|
104
116
|
})
|
105
117
|
|
106
|
-
self.log("
|
118
|
+
self.log("debug", f"Query completed, returned {len(results)} rows")
|
107
119
|
return result_text
|
108
120
|
|
109
121
|
except sqlite3.Error as e:
|
110
122
|
error_msg = f"Query execution failed: {str(e)}"
|
111
|
-
|
112
|
-
raise
|
123
|
+
raise DatabaseError(error_msg)
|
113
124
|
|
114
125
|
async def cleanup(self):
|
115
126
|
"""Cleanup resources"""
|
116
|
-
#
|
117
|
-
|
127
|
+
# Log final stats before cleanup
|
128
|
+
self.log("info", f"Final SQLite handler stats: {self.stats.to_dict()}")
|
mcp_dbutils/stats.py
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
"""Resource monitoring statistics module"""
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Optional
|
6
|
+
import sys
|
7
|
+
|
8
|
+
@dataclass
|
9
|
+
class ResourceStats:
|
10
|
+
"""Resource statistics tracking"""
|
11
|
+
# Connection stats
|
12
|
+
active_connections: int = 0
|
13
|
+
total_connections: int = 0
|
14
|
+
connection_start_time: Optional[datetime] = None
|
15
|
+
|
16
|
+
# Query stats
|
17
|
+
query_count: int = 0
|
18
|
+
last_query_time: Optional[datetime] = None
|
19
|
+
|
20
|
+
# Error stats
|
21
|
+
error_count: int = 0
|
22
|
+
last_error_time: Optional[datetime] = None
|
23
|
+
error_types: dict[str, int] = None
|
24
|
+
|
25
|
+
# Resource stats
|
26
|
+
estimated_memory: int = 0
|
27
|
+
|
28
|
+
def __post_init__(self):
|
29
|
+
"""Initialize mutable defaults"""
|
30
|
+
if self.error_types is None:
|
31
|
+
self.error_types = {}
|
32
|
+
|
33
|
+
def record_connection_start(self):
|
34
|
+
"""Record new connection start"""
|
35
|
+
self.active_connections += 1
|
36
|
+
self.total_connections += 1
|
37
|
+
self.connection_start_time = datetime.now()
|
38
|
+
|
39
|
+
def record_connection_end(self):
|
40
|
+
"""Record connection end"""
|
41
|
+
self.active_connections = max(0, self.active_connections - 1)
|
42
|
+
|
43
|
+
def record_query(self):
|
44
|
+
"""Record query execution"""
|
45
|
+
self.query_count += 1
|
46
|
+
self.last_query_time = datetime.now()
|
47
|
+
|
48
|
+
def record_error(self, error_type: str):
|
49
|
+
"""Record error occurrence
|
50
|
+
|
51
|
+
Args:
|
52
|
+
error_type: Type/class name of the error
|
53
|
+
"""
|
54
|
+
self.error_count += 1
|
55
|
+
self.last_error_time = datetime.now()
|
56
|
+
self.error_types[error_type] = self.error_types.get(error_type, 0) + 1
|
57
|
+
|
58
|
+
def update_memory_usage(self, obj: object):
|
59
|
+
"""Update estimated memory usage
|
60
|
+
|
61
|
+
Args:
|
62
|
+
obj: Object to estimate size for
|
63
|
+
"""
|
64
|
+
self.estimated_memory = sys.getsizeof(obj)
|
65
|
+
|
66
|
+
def to_dict(self) -> dict:
|
67
|
+
"""Convert stats to dictionary for logging
|
68
|
+
|
69
|
+
Returns:
|
70
|
+
Dictionary of current statistics
|
71
|
+
"""
|
72
|
+
now = datetime.now()
|
73
|
+
connection_duration = None
|
74
|
+
if self.connection_start_time:
|
75
|
+
connection_duration = (now - self.connection_start_time).total_seconds()
|
76
|
+
|
77
|
+
return {
|
78
|
+
"active_connections": self.active_connections,
|
79
|
+
"total_connections": self.total_connections,
|
80
|
+
"connection_duration": connection_duration,
|
81
|
+
"query_count": self.query_count,
|
82
|
+
"error_count": self.error_count,
|
83
|
+
"error_types": self.error_types,
|
84
|
+
"estimated_memory_bytes": self.estimated_memory
|
85
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-dbutils
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.8
|
4
4
|
Summary: MCP Database Utilities Service
|
5
5
|
Author: Dong Hao
|
6
6
|
License-Expression: MIT
|
@@ -10,14 +10,24 @@ Requires-Dist: mcp>=1.2.1
|
|
10
10
|
Requires-Dist: psycopg2-binary>=2.9.10
|
11
11
|
Requires-Dist: python-dotenv>=1.0.1
|
12
12
|
Requires-Dist: pyyaml>=6.0.2
|
13
|
+
Provides-Extra: test
|
14
|
+
Requires-Dist: aiosqlite>=0.19.0; extra == 'test'
|
15
|
+
Requires-Dist: docker>=7.0.0; extra == 'test'
|
16
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
17
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
|
18
|
+
Requires-Dist: pytest-docker>=2.0.0; extra == 'test'
|
19
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
20
|
+
Requires-Dist: testcontainers>=3.7.0; extra == 'test'
|
13
21
|
Description-Content-Type: text/markdown
|
14
22
|
|
15
23
|
# MCP Database Utilities
|
16
24
|
|
17
25
|

|
18
26
|

|
19
|
-
](https://github.com/donghao1393/mcp-dbutils/actions)
|
28
|
+

|
20
29
|

|
30
|
+
[](https://smithery.ai/server/@donghao1393/mcp-dbutils)
|
21
31
|
|
22
32
|
[中文文档](README_CN.md)
|
23
33
|
|
@@ -35,6 +45,13 @@ MCP Database Utilities is a unified database access service that supports multip
|
|
35
45
|
## Installation and Configuration
|
36
46
|
|
37
47
|
### Installation Methods
|
48
|
+
#### Installing via Smithery
|
49
|
+
|
50
|
+
To install Database Utilities for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@donghao1393/mcp-dbutils):
|
51
|
+
|
52
|
+
```bash
|
53
|
+
npx -y @smithery/cli install @donghao1393/mcp-dbutils --client claude
|
54
|
+
```
|
38
55
|
|
39
56
|
#### Using uvx (Recommended)
|
40
57
|
No installation required, run directly using `uvx`:
|
@@ -45,9 +62,16 @@ uvx mcp-dbutils --config /path/to/config.yaml
|
|
45
62
|
Add to Claude configuration:
|
46
63
|
```json
|
47
64
|
"mcpServers": {
|
48
|
-
"
|
65
|
+
"dbutils": {
|
49
66
|
"command": "uvx",
|
50
|
-
"args": [
|
67
|
+
"args": [
|
68
|
+
"mcp-dbutils",
|
69
|
+
"--config",
|
70
|
+
"/path/to/config.yaml"
|
71
|
+
],
|
72
|
+
"env": {
|
73
|
+
"MCP_DEBUG": "1" // Optional: Enable debug mode
|
74
|
+
}
|
51
75
|
}
|
52
76
|
}
|
53
77
|
```
|
@@ -60,9 +84,17 @@ pip install mcp-dbutils
|
|
60
84
|
Add to Claude configuration:
|
61
85
|
```json
|
62
86
|
"mcpServers": {
|
63
|
-
"
|
87
|
+
"dbutils": {
|
64
88
|
"command": "python",
|
65
|
-
"args": [
|
89
|
+
"args": [
|
90
|
+
"-m",
|
91
|
+
"mcp_dbutils",
|
92
|
+
"--config",
|
93
|
+
"/path/to/config.yaml"
|
94
|
+
],
|
95
|
+
"env": {
|
96
|
+
"MCP_DEBUG": "1" // Optional: Enable debug mode
|
97
|
+
}
|
66
98
|
}
|
67
99
|
}
|
68
100
|
```
|
@@ -71,20 +103,41 @@ Add to Claude configuration:
|
|
71
103
|
```bash
|
72
104
|
docker run -i --rm \
|
73
105
|
-v /path/to/config.yaml:/app/config.yaml \
|
106
|
+
-v /path/to/sqlite.db:/app/sqlite.db \ # Optional: for SQLite database
|
107
|
+
-e MCP_DEBUG=1 \ # Optional: Enable debug mode
|
74
108
|
mcp/dbutils --config /app/config.yaml
|
75
109
|
```
|
76
110
|
|
77
111
|
Add to Claude configuration:
|
78
112
|
```json
|
79
113
|
"mcpServers": {
|
80
|
-
"
|
114
|
+
"dbutils": {
|
81
115
|
"command": "docker",
|
82
|
-
"args": [
|
83
|
-
|
116
|
+
"args": [
|
117
|
+
"run",
|
118
|
+
"-i",
|
119
|
+
"--rm",
|
120
|
+
"-v",
|
121
|
+
"/path/to/config.yaml:/app/config.yaml",
|
122
|
+
"-v",
|
123
|
+
"/path/to/sqlite.db:/app/sqlite.db", // Optional: for SQLite database
|
124
|
+
"mcp/dbutils",
|
125
|
+
"--config",
|
126
|
+
"/app/config.yaml"
|
127
|
+
],
|
128
|
+
"env": {
|
129
|
+
"MCP_DEBUG": "1" // Optional: Enable debug mode
|
130
|
+
}
|
84
131
|
}
|
85
132
|
}
|
86
133
|
```
|
87
134
|
|
135
|
+
> **Note for Docker database connections:**
|
136
|
+
> - For SQLite: Mount your database file using `-v /path/to/sqlite.db:/app/sqlite.db`
|
137
|
+
> - For PostgreSQL running on host:
|
138
|
+
> - On Mac/Windows: Use `host.docker.internal` as host in config
|
139
|
+
> - On Linux: Use `172.17.0.1` (docker0 IP) or run with `--network="host"`
|
140
|
+
|
88
141
|
### Requirements
|
89
142
|
- Python 3.10+
|
90
143
|
- PostgreSQL (optional)
|
@@ -95,20 +148,21 @@ The project requires a YAML configuration file, specified via the `--config` par
|
|
95
148
|
|
96
149
|
```yaml
|
97
150
|
databases:
|
98
|
-
# PostgreSQL example
|
151
|
+
# PostgreSQL example (when using Docker)
|
99
152
|
my_postgres:
|
100
153
|
type: postgres
|
101
154
|
dbname: test_db
|
102
155
|
user: postgres
|
103
156
|
password: secret
|
104
|
-
host:
|
157
|
+
host: host.docker.internal # For Mac/Windows
|
158
|
+
# host: 172.17.0.1 # For Linux (docker0 IP)
|
105
159
|
port: 5432
|
106
160
|
|
107
|
-
# SQLite example
|
161
|
+
# SQLite example (when using Docker)
|
108
162
|
my_sqlite:
|
109
163
|
type: sqlite
|
110
|
-
path: /
|
111
|
-
password: optional_password
|
164
|
+
path: /app/sqlite.db # Mapped path inside container
|
165
|
+
password: optional_password # optional
|
112
166
|
```
|
113
167
|
|
114
168
|
### Debug Mode
|
@@ -117,6 +171,23 @@ Set environment variable `MCP_DEBUG=1` to enable debug mode for detailed logging
|
|
117
171
|
## Architecture Design
|
118
172
|
|
119
173
|
### Core Concept: Abstraction Layer
|
174
|
+
|
175
|
+
```mermaid
|
176
|
+
graph TD
|
177
|
+
Client[Client] --> DatabaseServer[Database Server]
|
178
|
+
subgraph MCP Server
|
179
|
+
DatabaseServer
|
180
|
+
DatabaseHandler[Database Handler]
|
181
|
+
PostgresHandler[PostgreSQL Handler]
|
182
|
+
SQLiteHandler[SQLite Handler]
|
183
|
+
DatabaseServer --> DatabaseHandler
|
184
|
+
DatabaseHandler --> PostgresHandler
|
185
|
+
DatabaseHandler --> SQLiteHandler
|
186
|
+
end
|
187
|
+
PostgresHandler --> PostgreSQL[(PostgreSQL)]
|
188
|
+
SQLiteHandler --> SQLite[(SQLite)]
|
189
|
+
```
|
190
|
+
|
120
191
|
The abstraction layer design is the core architectural concept in MCP Database Utilities. Just like a universal remote control that works with different devices, users only need to know the basic operations without understanding the underlying complexities.
|
121
192
|
|
122
193
|
#### 1. Simplified User Interaction
|
@@ -216,3 +287,30 @@ Provides SQLite-specific features:
|
|
216
287
|
- File path handling
|
217
288
|
- URI scheme support
|
218
289
|
- Password protection support (optional)
|
290
|
+
|
291
|
+
## Contributing
|
292
|
+
Contributions are welcome! Here's how you can help:
|
293
|
+
|
294
|
+
1. 🐛 Report bugs: Open an issue describing the bug and how to reproduce it
|
295
|
+
2. 💡 Suggest features: Open an issue to propose new features
|
296
|
+
3. 🛠️ Submit PRs: Fork the repo and create a pull request with your changes
|
297
|
+
|
298
|
+
### Development Setup
|
299
|
+
1. Clone the repository
|
300
|
+
2. Create a virtual environment using `uv venv`
|
301
|
+
3. Install dependencies with `uv sync --all-extras`
|
302
|
+
4. Run tests with `pytest`
|
303
|
+
|
304
|
+
For detailed guidelines, see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
|
305
|
+
|
306
|
+
## Acknowledgments
|
307
|
+
- [MCP Servers](https://github.com/modelcontextprotocol/servers) for inspiration and demonstration
|
308
|
+
- AI Editors:
|
309
|
+
* [Claude Desktop](https://claude.ai/download)
|
310
|
+
* [5ire](https://5ire.app/)
|
311
|
+
* [Cline](https://cline.bot)
|
312
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/) for comprehensive interfaces
|
313
|
+
|
314
|
+
## Star History
|
315
|
+
|
316
|
+
[](https://star-history.com/#donghao1393/mcp-dbutils&Date)
|
@@ -1,17 +1,18 @@
|
|
1
1
|
mcp_dbutils/__init__.py,sha256=_zVdjN2P_G5qEye7f5vQxiXAd6EwX2gyTL1wpFpqKck,1718
|
2
|
-
mcp_dbutils/base.py,sha256=
|
2
|
+
mcp_dbutils/base.py,sha256=Hj35seBlmYgcq0qyoZchnUbKzDMPlhUxsCw7U79OA2c,9186
|
3
3
|
mcp_dbutils/config.py,sha256=EwnPNuQVCBKd5WOXQfROyDTM-YpM_Odp0GhCPRg8YwE,1863
|
4
4
|
mcp_dbutils/log.py,sha256=RJwWtHyZnJA2YRcyCK-WgnFadNuwtJeYKSxPbE-DCG0,991
|
5
|
+
mcp_dbutils/stats.py,sha256=2hiKi_M8V4xhVHlH5FS-Df5GuMEpuzif12C8ik06Khs,2538
|
5
6
|
mcp_dbutils/postgres/__init__.py,sha256=Y6v_RsI79pqAfpKM3SrT1T1I9r5yWuKT0GUUNmHD3DE,146
|
6
7
|
mcp_dbutils/postgres/config.py,sha256=ipjskdlv3u949R9rC2AKPCINRrtEAiKDjeogpFfM6aE,2401
|
7
|
-
mcp_dbutils/postgres/handler.py,sha256=
|
8
|
+
mcp_dbutils/postgres/handler.py,sha256=mvktsgak0tR-L4oknqv7UhOZ0_R8KPB9JRWiswh0bl4,5955
|
8
9
|
mcp_dbutils/postgres/server.py,sha256=3AdQyQfgrClkCmWOZ-xFXTeI9t3tpFkrY3qmyOR0b-U,8586
|
9
10
|
mcp_dbutils/sqlite/__init__.py,sha256=QV4th2ywzUmCCa3GHCcBf8blJ_E8OYy0dJ2fSf1TfSU,134
|
10
11
|
mcp_dbutils/sqlite/config.py,sha256=QK1JlY-ZBB5VyhydbiU-aAKNESTWMtyBfawbv2DAVCQ,2452
|
11
|
-
mcp_dbutils/sqlite/handler.py,sha256=
|
12
|
+
mcp_dbutils/sqlite/handler.py,sha256=7mD50sQRii7UKX-8dE9RNicIXGwMDPB59Ee1dmncSlg,4842
|
12
13
|
mcp_dbutils/sqlite/server.py,sha256=Q29O2YOW4kqDGc3z1hMXVv4M0KSkuZbqvVDMgZE8Fd8,7593
|
13
|
-
mcp_dbutils-0.2.
|
14
|
-
mcp_dbutils-0.2.
|
15
|
-
mcp_dbutils-0.2.
|
16
|
-
mcp_dbutils-0.2.
|
17
|
-
mcp_dbutils-0.2.
|
14
|
+
mcp_dbutils-0.2.8.dist-info/METADATA,sha256=YdBbIw37bEGOtLGOE1XTjZaE7ESHog-y6rXxIp5N5y8,9466
|
15
|
+
mcp_dbutils-0.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
16
|
+
mcp_dbutils-0.2.8.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
|
17
|
+
mcp_dbutils-0.2.8.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
|
18
|
+
mcp_dbutils-0.2.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|