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 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
- pass
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.log = create_logger("db-server", debug)
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 ValueError("Configuration file must contain 'databases' section")
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 ValueError(f"Database configuration not found: {database}. Available configurations: {available_dbs}")
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
- # Create appropriate handler based on configuration
90
- if 'path' in db_config:
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 'dbname' in db_config or 'host' in db_config:
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 ValueError("Cannot determine database type, missing required parameters in configuration")
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 ValueError("Database configuration name must be specified")
170
+ raise ConfigurationError("Database configuration name must be specified")
120
171
 
121
172
  parts = uri.split('/')
122
173
  if len(parts) < 3:
123
- raise ValueError("Invalid resource URI")
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 ValueError(f"Unknown tool: {name}")
208
+ raise ConfigurationError(f"Unknown tool: {name}")
158
209
 
159
210
  if "database" not in arguments:
160
- raise ValueError("Database configuration name must be specified")
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 ValueError("SQL query cannot be empty")
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 ValueError("Only SELECT queries are supported")
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
- self.server.create_initialization_options()
234
+ init_options
182
235
  )
@@ -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.log("error", error_msg)
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.log("error", error_msg)
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 execute_query(self, sql: str) -> str:
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("info", f"Executing query: {sql}")
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("info", f"Query completed, returned {len(results)} rows")
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
- self.log("error", error_msg)
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
- # No special cleanup needed since we're not using connection pool
150
- pass
149
+ # Log final stats before cleanup
150
+ self.log("info", f"Final PostgreSQL handler stats: {self.stats.to_dict()}")
@@ -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 execute_query(self, sql: str) -> str:
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("info", f"Executing query: {sql}")
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("info", f"Query completed, returned {len(results)} rows")
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
- self.log("error", error_msg)
112
- raise
123
+ raise DatabaseError(error_msg)
113
124
 
114
125
  async def cleanup(self):
115
126
  """Cleanup resources"""
116
- # No special cleanup needed for SQLite
117
- pass
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
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
  ![GitHub Repo stars](https://img.shields.io/github/stars/donghao1393/mcp-dbutils)
18
26
  ![PyPI version](https://img.shields.io/pypi/v/mcp-dbutils)
19
- ![Python versions](https://img.shields.io/pypi/pyversions/mcp-dbutils)
27
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/donghao1393/bdd0a63ec2a816539ff8c136ceb41e48/raw/coverage.json)](https://github.com/donghao1393/mcp-dbutils/actions)
28
+ ![Python](https://img.shields.io/badge/Python-3.10%2B-blue)
20
29
  ![License](https://img.shields.io/github/license/donghao1393/mcp-dbutils)
30
+ [![smithery badge](https://smithery.ai/badge/@donghao1393/mcp-dbutils)](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
- "database": {
65
+ "dbutils": {
49
66
  "command": "uvx",
50
- "args": ["mcp-dbutils", "--config", "/path/to/config.yaml"]
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
- "database": {
87
+ "dbutils": {
64
88
  "command": "python",
65
- "args": ["-m", "mcp_dbutils", "--config", "/path/to/config.yaml"]
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
- "database": {
114
+ "dbutils": {
81
115
  "command": "docker",
82
- "args": ["run", "-i", "--rm", "-v", "/path/to/config.yaml:/app/config.yaml",
83
- "mcp/dbutils", "--config", "/app/config.yaml"]
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: localhost
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: /path/to/database.db
111
- password: optional_password # optional
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
+ [![Star History Chart](https://api.star-history.com/svg?repos=donghao1393/mcp-dbutils&type=Date)](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=BQ6lEduhnR0pZnc-YY_OsO-w4XM06dzCJRU1uWnL-5M,6810
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=e-E03JST5t_YBgjRs3EmIEbKzym40WhGyScFerP3ueg,5811
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=v2fFHxepNSUuK65UPo4fwyeBCDG6fit5IuLdBRIKUsY,4220
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.3.dist-info/METADATA,sha256=TqDZw19V4rPsI5zS89McSFqHBSk73KOWTiaS3tE3dzw,6051
14
- mcp_dbutils-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- mcp_dbutils-0.2.3.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
16
- mcp_dbutils-0.2.3.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
17
- mcp_dbutils-0.2.3.dist-info/RECORD,,
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,,