mcp-dbutils 0.8.0__py3-none-any.whl → 0.10.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.
- mcp_dbutils/__init__.py +9 -9
- mcp_dbutils/base.py +383 -58
- mcp_dbutils/config.py +12 -12
- mcp_dbutils/postgres/__init__.py +3 -3
- mcp_dbutils/postgres/config.py +95 -34
- mcp_dbutils/postgres/handler.py +446 -14
- mcp_dbutils/postgres/server.py +16 -16
- mcp_dbutils/sqlite/__init__.py +3 -3
- mcp_dbutils/sqlite/config.py +12 -12
- mcp_dbutils/sqlite/handler.py +361 -77
- mcp_dbutils/sqlite/server.py +21 -21
- mcp_dbutils/stats.py +112 -3
- mcp_dbutils-0.10.0.dist-info/METADATA +227 -0
- mcp_dbutils-0.10.0.dist-info/RECORD +18 -0
- mcp_dbutils-0.8.0.dist-info/METADATA +0 -358
- mcp_dbutils-0.8.0.dist-info/RECORD +0 -18
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.10.0.dist-info}/WHEEL +0 -0
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.10.0.dist-info}/entry_points.txt +0 -0
- {mcp_dbutils-0.8.0.dist-info → mcp_dbutils-0.10.0.dist-info}/licenses/LICENSE +0 -0
mcp_dbutils/config.py
CHANGED
@@ -7,14 +7,14 @@ from dataclasses import dataclass, field
|
|
7
7
|
from typing import Optional, Dict, Any, Literal
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
|
-
# Supported
|
11
|
-
|
10
|
+
# Supported connection types
|
11
|
+
ConnectionType = Literal['sqlite', 'postgres']
|
12
12
|
|
13
|
-
class
|
14
|
-
"""Base class for
|
13
|
+
class ConnectionConfig(ABC):
|
14
|
+
"""Base class for connection configuration"""
|
15
15
|
|
16
16
|
debug: bool = False
|
17
|
-
type:
|
17
|
+
type: ConnectionType # Connection type
|
18
18
|
|
19
19
|
@abstractmethod
|
20
20
|
def get_connection_params(self) -> Dict[str, Any]:
|
@@ -39,19 +39,19 @@ class DatabaseConfig(ABC):
|
|
39
39
|
with open(yaml_path, 'r', encoding='utf-8') as f:
|
40
40
|
config = yaml.safe_load(f)
|
41
41
|
|
42
|
-
if not config or '
|
43
|
-
raise ValueError("Configuration file must contain '
|
42
|
+
if not config or 'connections' not in config:
|
43
|
+
raise ValueError("Configuration file must contain 'connections' section")
|
44
44
|
|
45
45
|
# Validate type field in each database configuration
|
46
|
-
|
47
|
-
for
|
46
|
+
connections = config['connections']
|
47
|
+
for conn_name, db_config in connections.items():
|
48
48
|
if 'type' not in db_config:
|
49
|
-
raise ValueError(f"Database configuration {
|
49
|
+
raise ValueError(f"Database configuration {conn_name} missing required 'type' field")
|
50
50
|
db_type = db_config['type']
|
51
51
|
if db_type not in ('sqlite', 'postgres'):
|
52
|
-
raise ValueError(f"Invalid type value in database configuration {
|
52
|
+
raise ValueError(f"Invalid type value in database configuration {conn_name}: {db_type}")
|
53
53
|
|
54
|
-
return
|
54
|
+
return connections
|
55
55
|
|
56
56
|
@classmethod
|
57
57
|
def get_debug_mode(cls) -> bool:
|
mcp_dbutils/postgres/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
"""PostgreSQL module"""
|
2
2
|
|
3
|
-
from .handler import
|
4
|
-
from .config import
|
3
|
+
from .handler import PostgreSQLHandler
|
4
|
+
from .config import PostgreSQLConfig
|
5
5
|
|
6
|
-
__all__ = ['
|
6
|
+
__all__ = ['PostgreSQLHandler', 'PostgreSQLConfig']
|
mcp_dbutils/postgres/config.py
CHANGED
@@ -1,28 +1,35 @@
|
|
1
1
|
"""PostgreSQL configuration module"""
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from typing import Optional, Dict, Any, Literal
|
4
|
-
from urllib.parse import urlparse
|
5
|
-
from ..config import
|
4
|
+
from urllib.parse import urlparse, parse_qs
|
5
|
+
from ..config import ConnectionConfig
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
@dataclass
|
8
|
+
class SSLConfig:
|
9
|
+
"""SSL configuration for PostgreSQL connection"""
|
10
|
+
mode: Literal['disable', 'require', 'verify-ca', 'verify-full'] = 'disable'
|
11
|
+
cert: Optional[str] = None
|
12
|
+
key: Optional[str] = None
|
13
|
+
root: Optional[str] = None
|
14
|
+
|
15
|
+
def parse_url(url: str) -> Dict[str, Any]:
|
16
|
+
"""Parse PostgreSQL URL into connection parameters
|
9
17
|
|
10
18
|
Args:
|
11
|
-
|
19
|
+
url: URL (e.g. postgresql://host:port/dbname?sslmode=verify-full)
|
12
20
|
|
13
21
|
Returns:
|
14
|
-
Dictionary of connection parameters
|
22
|
+
Dictionary of connection parameters including SSL settings
|
15
23
|
"""
|
16
|
-
if not
|
17
|
-
raise ValueError("Invalid PostgreSQL
|
24
|
+
if not url.startswith('postgresql://'):
|
25
|
+
raise ValueError("Invalid PostgreSQL URL format")
|
18
26
|
|
19
|
-
# Remove jdbc: prefix and ensure no credentials in URL
|
20
|
-
url = jdbc_url[5:]
|
21
27
|
if '@' in url:
|
22
|
-
raise ValueError("
|
28
|
+
raise ValueError("URL should not contain credentials. Please provide username and password separately.")
|
23
29
|
|
24
|
-
# Parse URL
|
30
|
+
# Parse URL and query parameters
|
25
31
|
parsed = urlparse(url)
|
32
|
+
query_params = parse_qs(parsed.query)
|
26
33
|
|
27
34
|
params = {
|
28
35
|
'host': parsed.hostname or 'localhost',
|
@@ -31,12 +38,30 @@ def parse_jdbc_url(jdbc_url: str) -> Dict[str, str]:
|
|
31
38
|
}
|
32
39
|
|
33
40
|
if not params['dbname']:
|
34
|
-
raise ValueError("
|
41
|
+
raise ValueError("PostgreSQL database name must be specified in URL")
|
42
|
+
|
43
|
+
# Parse SSL parameters if present
|
44
|
+
ssl_params = {}
|
45
|
+
if 'sslmode' in query_params:
|
46
|
+
mode = query_params['sslmode'][0]
|
47
|
+
if mode not in ['disable', 'require', 'verify-ca', 'verify-full']:
|
48
|
+
raise ValueError(f"Invalid sslmode: {mode}")
|
49
|
+
ssl_params['mode'] = mode
|
50
|
+
|
51
|
+
if 'sslcert' in query_params:
|
52
|
+
ssl_params['cert'] = query_params['sslcert'][0]
|
53
|
+
if 'sslkey' in query_params:
|
54
|
+
ssl_params['key'] = query_params['sslkey'][0]
|
55
|
+
if 'sslrootcert' in query_params:
|
56
|
+
ssl_params['root'] = query_params['sslrootcert'][0]
|
57
|
+
|
58
|
+
if ssl_params:
|
59
|
+
params['ssl'] = SSLConfig(**ssl_params)
|
35
60
|
|
36
61
|
return params
|
37
62
|
|
38
63
|
@dataclass
|
39
|
-
class
|
64
|
+
class PostgreSQLConfig(ConnectionConfig):
|
40
65
|
dbname: str
|
41
66
|
user: str
|
42
67
|
password: str
|
@@ -44,39 +69,41 @@ class PostgresConfig(DatabaseConfig):
|
|
44
69
|
port: str = '5432'
|
45
70
|
local_host: Optional[str] = None
|
46
71
|
type: Literal['postgres'] = 'postgres'
|
72
|
+
url: Optional[str] = None
|
73
|
+
ssl: Optional[SSLConfig] = None
|
47
74
|
|
48
75
|
@classmethod
|
49
|
-
def from_yaml(cls, yaml_path: str, db_name: str, local_host: Optional[str] = None) -> '
|
76
|
+
def from_yaml(cls, yaml_path: str, db_name: str, local_host: Optional[str] = None) -> 'PostgreSQLConfig':
|
50
77
|
"""Create configuration from YAML file
|
51
78
|
|
52
79
|
Args:
|
53
80
|
yaml_path: Path to YAML configuration file
|
54
|
-
db_name:
|
81
|
+
db_name: Connection configuration name to use
|
55
82
|
local_host: Optional local host address
|
56
83
|
"""
|
57
84
|
configs = cls.load_yaml_config(yaml_path)
|
58
85
|
if not db_name:
|
59
|
-
raise ValueError("
|
86
|
+
raise ValueError("Connection name must be specified")
|
60
87
|
if db_name not in configs:
|
61
88
|
available_dbs = list(configs.keys())
|
62
|
-
raise ValueError(f"
|
89
|
+
raise ValueError(f"Connection configuration not found: {db_name}. Available configurations: {available_dbs}")
|
63
90
|
|
64
91
|
db_config = configs[db_name]
|
65
92
|
if 'type' not in db_config:
|
66
|
-
raise ValueError("
|
93
|
+
raise ValueError("Connection configuration must include 'type' field")
|
67
94
|
if db_config['type'] != 'postgres':
|
68
95
|
raise ValueError(f"Configuration is not PostgreSQL type: {db_config['type']}")
|
69
96
|
|
70
97
|
# Check required credentials
|
71
98
|
if not db_config.get('user'):
|
72
|
-
raise ValueError("User must be specified in
|
99
|
+
raise ValueError("User must be specified in connection configuration")
|
73
100
|
if not db_config.get('password'):
|
74
|
-
raise ValueError("Password must be specified in
|
101
|
+
raise ValueError("Password must be specified in connection configuration")
|
75
102
|
|
76
103
|
# Get connection parameters
|
77
|
-
if '
|
78
|
-
# Parse
|
79
|
-
params =
|
104
|
+
if 'url' in db_config:
|
105
|
+
# Parse URL for connection parameters
|
106
|
+
params = parse_url(db_config['url'])
|
80
107
|
config = cls(
|
81
108
|
dbname=params['dbname'],
|
82
109
|
user=db_config['user'],
|
@@ -84,14 +111,34 @@ class PostgresConfig(DatabaseConfig):
|
|
84
111
|
host=params['host'],
|
85
112
|
port=params['port'],
|
86
113
|
local_host=local_host,
|
114
|
+
url=db_config['url'],
|
115
|
+
ssl=params.get('ssl')
|
87
116
|
)
|
88
117
|
else:
|
89
118
|
if not db_config.get('dbname'):
|
90
|
-
raise ValueError("
|
119
|
+
raise ValueError("PostgreSQL database name must be specified in configuration")
|
91
120
|
if not db_config.get('host'):
|
92
|
-
raise ValueError("Host must be specified in configuration")
|
121
|
+
raise ValueError("Host must be specified in connection configuration")
|
93
122
|
if not db_config.get('port'):
|
94
|
-
raise ValueError("Port must be specified in configuration")
|
123
|
+
raise ValueError("Port must be specified in connection configuration")
|
124
|
+
|
125
|
+
# Parse SSL configuration if present
|
126
|
+
ssl_config = None
|
127
|
+
if 'ssl' in db_config:
|
128
|
+
ssl_params = db_config['ssl']
|
129
|
+
if not isinstance(ssl_params, dict):
|
130
|
+
raise ValueError("SSL configuration must be a dictionary")
|
131
|
+
|
132
|
+
if ssl_params.get('mode') not in [None, 'disable', 'require', 'verify-ca', 'verify-full']:
|
133
|
+
raise ValueError(f"Invalid sslmode: {ssl_params.get('mode')}")
|
134
|
+
|
135
|
+
ssl_config = SSLConfig(
|
136
|
+
mode=ssl_params.get('mode', 'disable'),
|
137
|
+
cert=ssl_params.get('cert'),
|
138
|
+
key=ssl_params.get('key'),
|
139
|
+
root=ssl_params.get('root')
|
140
|
+
)
|
141
|
+
|
95
142
|
config = cls(
|
96
143
|
dbname=db_config['dbname'],
|
97
144
|
user=db_config['user'],
|
@@ -99,25 +146,26 @@ class PostgresConfig(DatabaseConfig):
|
|
99
146
|
host=db_config['host'],
|
100
147
|
port=str(db_config['port']),
|
101
148
|
local_host=local_host,
|
149
|
+
ssl=ssl_config
|
102
150
|
)
|
103
151
|
config.debug = cls.get_debug_mode()
|
104
152
|
return config
|
105
153
|
|
106
154
|
@classmethod
|
107
|
-
def
|
108
|
-
|
109
|
-
"""Create configuration from
|
155
|
+
def from_url(cls, url: str, user: str, password: str,
|
156
|
+
local_host: Optional[str] = None) -> 'PostgreSQLConfig':
|
157
|
+
"""Create configuration from URL and credentials
|
110
158
|
|
111
159
|
Args:
|
112
|
-
|
113
|
-
user: Username for
|
114
|
-
password: Password for
|
160
|
+
url: URL (postgresql://host:port/dbname)
|
161
|
+
user: Username for connection
|
162
|
+
password: Password for connection
|
115
163
|
local_host: Optional local host address
|
116
164
|
|
117
165
|
Raises:
|
118
166
|
ValueError: If URL format is invalid or required parameters are missing
|
119
167
|
"""
|
120
|
-
params =
|
168
|
+
params = parse_url(url)
|
121
169
|
|
122
170
|
config = cls(
|
123
171
|
dbname=params['dbname'],
|
@@ -126,6 +174,8 @@ class PostgresConfig(DatabaseConfig):
|
|
126
174
|
host=params['host'],
|
127
175
|
port=params['port'],
|
128
176
|
local_host=local_host,
|
177
|
+
url=url,
|
178
|
+
ssl=params.get('ssl')
|
129
179
|
)
|
130
180
|
config.debug = cls.get_debug_mode()
|
131
181
|
return config
|
@@ -139,6 +189,17 @@ class PostgresConfig(DatabaseConfig):
|
|
139
189
|
'host': self.local_host or self.host,
|
140
190
|
'port': self.port
|
141
191
|
}
|
192
|
+
|
193
|
+
# Add SSL parameters if configured
|
194
|
+
if self.ssl:
|
195
|
+
params['sslmode'] = self.ssl.mode
|
196
|
+
if self.ssl.cert:
|
197
|
+
params['sslcert'] = self.ssl.cert
|
198
|
+
if self.ssl.key:
|
199
|
+
params['sslkey'] = self.ssl.key
|
200
|
+
if self.ssl.root:
|
201
|
+
params['sslrootcert'] = self.ssl.root
|
202
|
+
|
142
203
|
return {k: v for k, v in params.items() if v}
|
143
204
|
|
144
205
|
def get_masked_connection_info(self) -> Dict[str, Any]:
|