mcp-dbutils 0.4.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (35) hide show
  1. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/CHANGELOG.md +24 -0
  2. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/PKG-INFO +13 -2
  3. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/README.md +12 -1
  4. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/README_CN.md +12 -1
  5. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/config.yaml.example +11 -0
  6. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/pyproject.toml +1 -1
  7. mcp_dbutils-0.5.0/src/mcp_dbutils/sqlite/config.py +150 -0
  8. mcp_dbutils-0.5.0/tests/integration/test_sqlite_config.py +109 -0
  9. mcp_dbutils-0.4.0/src/mcp_dbutils/sqlite/config.py +0 -74
  10. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/.coveragerc +0 -0
  11. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/.github/workflows/release.yml +0 -0
  12. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/.github/workflows/test.yml +0 -0
  13. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/.gitignore +0 -0
  14. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/Dockerfile +0 -0
  15. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/LICENSE +0 -0
  16. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/smithery.yaml +0 -0
  17. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/__init__.py +0 -0
  18. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/base.py +0 -0
  19. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/config.py +0 -0
  20. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/log.py +0 -0
  21. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/postgres/__init__.py +0 -0
  22. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/postgres/config.py +0 -0
  23. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/postgres/handler.py +0 -0
  24. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/postgres/server.py +0 -0
  25. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/sqlite/__init__.py +0 -0
  26. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/sqlite/handler.py +0 -0
  27. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/sqlite/server.py +0 -0
  28. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/src/mcp_dbutils/stats.py +0 -0
  29. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/conftest.py +0 -0
  30. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/integration/test_monitoring.py +0 -0
  31. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/integration/test_postgres.py +0 -0
  32. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/integration/test_postgres_config.py +0 -0
  33. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/integration/test_prompts.py +0 -0
  34. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/integration/test_sqlite.py +0 -0
  35. {mcp_dbutils-0.4.0 → mcp_dbutils-0.5.0}/tests/unit/test_stats.py +0 -0
@@ -1,6 +1,30 @@
1
1
  # CHANGELOG
2
2
 
3
3
 
4
+ ## v0.5.0 (2025-03-02)
5
+
6
+ ### Documentation
7
+
8
+ - Add JDBC URL configuration documentation
9
+ ([`a1b5f4b`](https://github.com/donghao1393/mcp-dbutils/commit/a1b5f4b424cec0df239bed65705aaac7c3e9072a))
10
+
11
+ - Add JDBC URL configuration examples to English and Chinese docs - Document secure credential
12
+ handling approach - Update configuration format descriptions
13
+
14
+ Part of feature #2
15
+
16
+ ### Features
17
+
18
+ - **config**: Add JDBC URL support for SQLite
19
+ ([#5](https://github.com/donghao1393/mcp-dbutils/pull/5),
20
+ [`9feb1e8`](https://github.com/donghao1393/mcp-dbutils/commit/9feb1e8c7e38a8e4e3c0f63c81a72f4a4edd05b5))
21
+
22
+ - Add JDBC URL parsing for SQLite configuration - Support SQLite specific URL format and parameters
23
+ - Keep credentials separate from URL - Complete test coverage for new functionality
24
+
25
+ Part of #4
26
+
27
+
4
28
  ## v0.4.0 (2025-03-01)
5
29
 
6
30
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-dbutils
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: MCP Database Utilities Service
5
5
  Author: Dong Hao
6
6
  License-Expression: MIT
@@ -148,7 +148,7 @@ The project requires a YAML configuration file, specified via the `--config` par
148
148
 
149
149
  ```yaml
150
150
  databases:
151
- # PostgreSQL example (when using Docker)
151
+ # Standard PostgreSQL configuration example
152
152
  my_postgres:
153
153
  type: postgres
154
154
  dbname: test_db
@@ -158,6 +158,13 @@ databases:
158
158
  # host: 172.17.0.1 # For Linux (docker0 IP)
159
159
  port: 5432
160
160
 
161
+ # PostgreSQL with JDBC URL example
162
+ my_postgres_jdbc:
163
+ type: postgres
164
+ jdbc_url: jdbc:postgresql://host.docker.internal:5432/test_db
165
+ user: postgres # Credentials must be provided separately
166
+ password: secret # Not included in JDBC URL for security
167
+
161
168
  # SQLite example (when using Docker)
162
169
  my_sqlite:
163
170
  type: sqlite
@@ -165,6 +172,10 @@ databases:
165
172
  password: optional_password # optional
166
173
  ```
167
174
 
175
+ The configuration supports two formats for PostgreSQL:
176
+ 1. Standard configuration with individual parameters
177
+ 2. JDBC URL configuration with separate credentials (recommended for better compatibility)
178
+
168
179
  ### Debug Mode
169
180
  Set environment variable `MCP_DEBUG=1` to enable debug mode for detailed logging output.
170
181
 
@@ -126,7 +126,7 @@ The project requires a YAML configuration file, specified via the `--config` par
126
126
 
127
127
  ```yaml
128
128
  databases:
129
- # PostgreSQL example (when using Docker)
129
+ # Standard PostgreSQL configuration example
130
130
  my_postgres:
131
131
  type: postgres
132
132
  dbname: test_db
@@ -136,6 +136,13 @@ databases:
136
136
  # host: 172.17.0.1 # For Linux (docker0 IP)
137
137
  port: 5432
138
138
 
139
+ # PostgreSQL with JDBC URL example
140
+ my_postgres_jdbc:
141
+ type: postgres
142
+ jdbc_url: jdbc:postgresql://host.docker.internal:5432/test_db
143
+ user: postgres # Credentials must be provided separately
144
+ password: secret # Not included in JDBC URL for security
145
+
139
146
  # SQLite example (when using Docker)
140
147
  my_sqlite:
141
148
  type: sqlite
@@ -143,6 +150,10 @@ databases:
143
150
  password: optional_password # optional
144
151
  ```
145
152
 
153
+ The configuration supports two formats for PostgreSQL:
154
+ 1. Standard configuration with individual parameters
155
+ 2. JDBC URL configuration with separate credentials (recommended for better compatibility)
156
+
146
157
  ### Debug Mode
147
158
  Set environment variable `MCP_DEBUG=1` to enable debug mode for detailed logging output.
148
159
 
@@ -110,7 +110,7 @@ docker run -i --rm \
110
110
 
111
111
  ```yaml
112
112
  databases:
113
- # PostgreSQL配置示例(使用Docker)
113
+ # PostgreSQL标准配置示例
114
114
  my_postgres:
115
115
  type: postgres
116
116
  dbname: test_db
@@ -120,6 +120,13 @@ databases:
120
120
  # host: 172.17.0.1 # Linux系统使用(docker0网络IP)
121
121
  port: 5432
122
122
 
123
+ # PostgreSQL JDBC URL配置示例
124
+ my_postgres_jdbc:
125
+ type: postgres
126
+ jdbc_url: jdbc:postgresql://host.docker.internal:5432/test_db
127
+ user: postgres # 认证信息必须单独提供
128
+ password: secret # 出于安全考虑,不包含在JDBC URL中
129
+
123
130
  # SQLite配置示例(使用Docker)
124
131
  my_sqlite:
125
132
  type: sqlite
@@ -127,6 +134,10 @@ databases:
127
134
  password: optional_password # 可选
128
135
  ```
129
136
 
137
+ PostgreSQL配置支持两种格式:
138
+ 1. 标准配置:使用独立的参数配置
139
+ 2. JDBC URL配置:使用JDBC URL并单独提供认证信息(推荐,兼容性更好)
140
+
130
141
  ### 调试模式
131
142
  设置环境变量 `MCP_DEBUG=1` 启用调试模式,可以看到详细的日志输出。
132
143
 
@@ -1,10 +1,21 @@
1
1
  databases:
2
2
  # SQLite configuration examples
3
+ # SQLite with standard configuration
3
4
  dev-db:
4
5
  type: sqlite
5
6
  path: /path/to/dev.db
6
7
  password:
7
8
 
9
+ # SQLite with JDBC URL configuration
10
+ # jdbc:sqlite: URL supports query parameters:
11
+ # - mode=ro: Read-only mode
12
+ # - cache=shared: Shared cache mode
13
+ # Note: Password must be provided separately
14
+ prod-sqlite:
15
+ type: sqlite
16
+ jdbc_url: jdbc:sqlite:/path/to/prod.db?mode=ro
17
+ password: optional_password # Provided separately for security
18
+
8
19
  # PostgreSQL configuration examples
9
20
  # Standard configuration
10
21
  test-db:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-dbutils"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "MCP Database Utilities Service"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1,150 @@
1
+ """SQLite configuration module"""
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Dict, Any, Optional, Literal
6
+ from urllib.parse import urlparse, parse_qs
7
+ from ..config import DatabaseConfig
8
+
9
+ def parse_jdbc_url(jdbc_url: str) -> Dict[str, str]:
10
+ """Parse JDBC URL into connection parameters
11
+
12
+ Args:
13
+ jdbc_url: JDBC URL (e.g. jdbc:sqlite:file:/path/to/database.db or jdbc:sqlite:/path/to/database.db)
14
+
15
+ Returns:
16
+ Dictionary of connection parameters
17
+
18
+ Raises:
19
+ ValueError: If URL format is invalid
20
+ """
21
+ if not jdbc_url.startswith('jdbc:sqlite:'):
22
+ raise ValueError("Invalid SQLite JDBC URL format")
23
+
24
+ # Remove jdbc:sqlite: prefix
25
+ url = jdbc_url[12:]
26
+
27
+ # Handle file: prefix
28
+ if url.startswith('file:'):
29
+ url = url[5:]
30
+
31
+ # Parse URL
32
+ parsed = urlparse(url)
33
+ path = parsed.path
34
+
35
+ # Extract query parameters
36
+ params = {}
37
+ if parsed.query:
38
+ query_params = parse_qs(parsed.query)
39
+ for key, values in query_params.items():
40
+ params[key] = values[0]
41
+
42
+ if not path:
43
+ raise ValueError("Database path must be specified in URL")
44
+
45
+ return {
46
+ 'path': path,
47
+ 'parameters': params
48
+ }
49
+
50
+ @dataclass
51
+ class SqliteConfig(DatabaseConfig):
52
+ path: str
53
+ password: Optional[str] = None
54
+ uri: bool = True # Enable URI mode to support parameters like password
55
+ type: Literal['sqlite'] = 'sqlite'
56
+
57
+ @classmethod
58
+ def from_jdbc_url(cls, jdbc_url: str, password: Optional[str] = None) -> 'SqliteConfig':
59
+ """Create configuration from JDBC URL
60
+
61
+ Args:
62
+ jdbc_url: JDBC URL (e.g. jdbc:sqlite:file:/path/to/database.db)
63
+ password: Optional password for database encryption
64
+
65
+ Returns:
66
+ SqliteConfig instance
67
+
68
+ Raises:
69
+ ValueError: If URL format is invalid
70
+ """
71
+ params = parse_jdbc_url(jdbc_url)
72
+
73
+ config = cls(
74
+ path=params['path'],
75
+ password=password,
76
+ uri=True
77
+ )
78
+ config.debug = cls.get_debug_mode()
79
+ return config
80
+
81
+ @property
82
+ def absolute_path(self) -> str:
83
+ """Return absolute path to database file"""
84
+ return str(Path(self.path).expanduser().resolve())
85
+
86
+ def get_connection_params(self) -> Dict[str, Any]:
87
+ """Get sqlite3 connection parameters"""
88
+ if not self.password:
89
+ return {'database': self.absolute_path, 'uri': self.uri}
90
+
91
+ # Use URI format if password is provided
92
+ uri = f"file:{self.absolute_path}?mode=rw"
93
+ if self.password:
94
+ uri += f"&password={self.password}"
95
+
96
+ return {
97
+ 'database': uri,
98
+ 'uri': True
99
+ }
100
+
101
+ def get_masked_connection_info(self) -> Dict[str, Any]:
102
+ """Return connection information for logging"""
103
+ info = {
104
+ 'database': self.absolute_path,
105
+ 'uri': self.uri
106
+ }
107
+ if self.password:
108
+ info['password'] = '******'
109
+ return info
110
+
111
+ @classmethod
112
+ def from_yaml(cls, yaml_path: str, db_name: str, **kwargs) -> 'SqliteConfig':
113
+ """Create SQLite configuration from YAML
114
+
115
+ Args:
116
+ yaml_path: Path to YAML configuration file
117
+ db_name: Database configuration name
118
+ """
119
+ configs = cls.load_yaml_config(yaml_path)
120
+
121
+ if db_name not in configs:
122
+ available_dbs = list(configs.keys())
123
+ raise ValueError(f"Database configuration not found: {db_name}. Available configurations: {available_dbs}")
124
+
125
+ db_config = configs[db_name]
126
+
127
+ if 'type' not in db_config:
128
+ raise ValueError("Database configuration must include 'type' field")
129
+ if db_config['type'] != 'sqlite':
130
+ raise ValueError(f"Configuration is not SQLite type: {db_config['type']}")
131
+
132
+ # Check if using JDBC URL configuration
133
+ if 'jdbc_url' in db_config:
134
+ params = parse_jdbc_url(db_config['jdbc_url'])
135
+ config = cls(
136
+ path=params['path'],
137
+ password=db_config.get('password'),
138
+ uri=True
139
+ )
140
+ else:
141
+ if 'path' not in db_config:
142
+ raise ValueError("SQLite configuration must include 'path' field")
143
+ config = cls(
144
+ path=db_config['path'],
145
+ password=db_config.get('password'),
146
+ uri=True
147
+ )
148
+
149
+ config.debug = cls.get_debug_mode()
150
+ return config
@@ -0,0 +1,109 @@
1
+ """Test SQLite configuration functionality"""
2
+ import pytest
3
+ import tempfile
4
+ import yaml
5
+ from pathlib import Path
6
+ from mcp_dbutils.sqlite.config import SqliteConfig, parse_jdbc_url
7
+
8
+ def test_parse_jdbc_url():
9
+ """Test JDBC URL parsing"""
10
+ # Test basic URL
11
+ url = "jdbc:sqlite:/path/to/test.db"
12
+ params = parse_jdbc_url(url)
13
+ assert params["path"] == "/path/to/test.db"
14
+ assert params["parameters"] == {}
15
+
16
+ # Test URL with file: prefix
17
+ url = "jdbc:sqlite:file:/path/to/test.db"
18
+ params = parse_jdbc_url(url)
19
+ assert params["path"] == "/path/to/test.db"
20
+ assert params["parameters"] == {}
21
+
22
+ # Test URL with parameters
23
+ url = "jdbc:sqlite:/path/to/test.db?mode=ro&cache=shared"
24
+ params = parse_jdbc_url(url)
25
+ assert params["path"] == "/path/to/test.db"
26
+ assert params["parameters"] == {"mode": "ro", "cache": "shared"}
27
+
28
+ # Test invalid format
29
+ with pytest.raises(ValueError, match="Invalid SQLite JDBC URL format"):
30
+ parse_jdbc_url("sqlite:/path/to/test.db")
31
+
32
+ # Test missing path
33
+ with pytest.raises(ValueError, match="Database path must be specified"):
34
+ parse_jdbc_url("jdbc:sqlite:")
35
+
36
+ def test_from_jdbc_url():
37
+ """Test SqliteConfig creation from JDBC URL"""
38
+ url = "jdbc:sqlite:/path/to/test.db"
39
+ config = SqliteConfig.from_jdbc_url(url)
40
+
41
+ assert str(Path(config.path)) == str(Path("/path/to/test.db"))
42
+ assert config.password is None
43
+ assert config.uri is True
44
+ assert config.type == "sqlite"
45
+
46
+ # Test with password
47
+ config = SqliteConfig.from_jdbc_url(url, password="test_pass")
48
+ assert config.password == "test_pass"
49
+ assert config.uri is True
50
+
51
+ def test_from_yaml_with_jdbc_url(tmp_path):
52
+ """Test SqliteConfig creation from YAML with JDBC URL"""
53
+ config_data = {
54
+ "databases": {
55
+ "test_db": {
56
+ "type": "sqlite",
57
+ "jdbc_url": "jdbc:sqlite:/path/to/test.db",
58
+ "password": "test_pass"
59
+ }
60
+ }
61
+ }
62
+
63
+ config_file = tmp_path / "config.yaml"
64
+ with open(config_file, "w") as f:
65
+ yaml.dump(config_data, f)
66
+
67
+ config = SqliteConfig.from_yaml(str(config_file), "test_db")
68
+ assert str(Path(config.path)) == str(Path("/path/to/test.db"))
69
+ assert config.password == "test_pass"
70
+ assert config.uri is True
71
+ assert config.type == "sqlite"
72
+
73
+ def test_required_fields_validation(tmp_path):
74
+ """Test validation of required configuration fields"""
75
+ # Missing type
76
+ config_data = {
77
+ "databases": {
78
+ "test_db": {
79
+ "jdbc_url": "jdbc:sqlite:/path/to/test.db"
80
+ }
81
+ }
82
+ }
83
+
84
+ config_file = tmp_path / "config.yaml"
85
+ with open(config_file, "w") as f:
86
+ yaml.dump(config_data, f)
87
+
88
+ with pytest.raises(ValueError, match="missing required 'type' field"):
89
+ SqliteConfig.from_yaml(str(config_file), "test_db")
90
+
91
+ # Wrong type
92
+ config_data["databases"]["test_db"]["type"] = "postgres"
93
+
94
+ with open(config_file, "w") as f:
95
+ yaml.dump(config_data, f)
96
+
97
+ with pytest.raises(ValueError, match="Configuration is not SQLite type"):
98
+ SqliteConfig.from_yaml(str(config_file), "test_db")
99
+
100
+ # Standard config (non-JDBC) missing path
101
+ config_data["databases"]["test_db"] = {
102
+ "type": "sqlite"
103
+ }
104
+
105
+ with open(config_file, "w") as f:
106
+ yaml.dump(config_data, f)
107
+
108
+ with pytest.raises(ValueError, match="must include 'path' field"):
109
+ SqliteConfig.from_yaml(str(config_file), "test_db")
@@ -1,74 +0,0 @@
1
- """SQLite configuration module"""
2
-
3
- from dataclasses import dataclass
4
- from pathlib import Path
5
- from typing import Dict, Any, Optional, Literal
6
- from ..config import DatabaseConfig
7
-
8
- @dataclass
9
- class SqliteConfig(DatabaseConfig):
10
- path: str
11
- password: Optional[str] = None
12
- uri: bool = True # Enable URI mode to support parameters like password
13
- type: Literal['sqlite'] = 'sqlite'
14
-
15
- @property
16
- def absolute_path(self) -> str:
17
- """Return absolute path to database file"""
18
- return str(Path(self.path).expanduser().resolve())
19
-
20
- def get_connection_params(self) -> Dict[str, Any]:
21
- """Get sqlite3 connection parameters"""
22
- if not self.password:
23
- return {'database': self.absolute_path, 'uri': self.uri}
24
-
25
- # Use URI format if password is provided
26
- uri = f"file:{self.absolute_path}?mode=rw"
27
- if self.password:
28
- uri += f"&password={self.password}"
29
-
30
- return {
31
- 'database': uri,
32
- 'uri': True
33
- }
34
-
35
- def get_masked_connection_info(self) -> Dict[str, Any]:
36
- """Return connection information for logging"""
37
- info = {
38
- 'database': self.absolute_path,
39
- 'uri': self.uri
40
- }
41
- if self.password:
42
- info['password'] = '******'
43
- return info
44
-
45
- @classmethod
46
- def from_yaml(cls, yaml_path: str, db_name: str, **kwargs) -> 'SqliteConfig':
47
- """Create SQLite configuration from YAML
48
-
49
- Args:
50
- yaml_path: Path to YAML configuration file
51
- db_name: Database configuration name
52
- """
53
- configs = cls.load_yaml_config(yaml_path)
54
-
55
- if db_name not in configs:
56
- available_dbs = list(configs.keys())
57
- raise ValueError(f"Database configuration not found: {db_name}. Available configurations: {available_dbs}")
58
-
59
- db_config = configs[db_name]
60
-
61
- if 'type' not in db_config:
62
- raise ValueError("Database configuration must include 'type' field")
63
- if db_config['type'] != 'sqlite':
64
- raise ValueError(f"Configuration is not SQLite type: {db_config['type']}")
65
- if 'path' not in db_config:
66
- raise ValueError("SQLite configuration must include 'path' field")
67
-
68
- config = cls(
69
- path=db_config['path'],
70
- password=db_config.get('password'),
71
- uri=True
72
- )
73
- config.debug = cls.get_debug_mode()
74
- return config
File without changes
File without changes
File without changes
File without changes
File without changes