mcp-dbutils 0.2.4__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.4
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)
27
+ [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/donghao1393/bdd0a63ec2a816539ff8c136ceb41e48/raw/coverage.json)](https://github.com/donghao1393/mcp-dbutils/actions)
19
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`:
@@ -86,6 +103,7 @@ Add to Claude configuration:
86
103
  ```bash
87
104
  docker run -i --rm \
88
105
  -v /path/to/config.yaml:/app/config.yaml \
106
+ -v /path/to/sqlite.db:/app/sqlite.db \ # Optional: for SQLite database
89
107
  -e MCP_DEBUG=1 \ # Optional: Enable debug mode
90
108
  mcp/dbutils --config /app/config.yaml
91
109
  ```
@@ -101,6 +119,8 @@ Add to Claude configuration:
101
119
  "--rm",
102
120
  "-v",
103
121
  "/path/to/config.yaml:/app/config.yaml",
122
+ "-v",
123
+ "/path/to/sqlite.db:/app/sqlite.db", // Optional: for SQLite database
104
124
  "mcp/dbutils",
105
125
  "--config",
106
126
  "/app/config.yaml"
@@ -112,6 +132,12 @@ Add to Claude configuration:
112
132
  }
113
133
  ```
114
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
+
115
141
  ### Requirements
116
142
  - Python 3.10+
117
143
  - PostgreSQL (optional)
@@ -122,20 +148,21 @@ The project requires a YAML configuration file, specified via the `--config` par
122
148
 
123
149
  ```yaml
124
150
  databases:
125
- # PostgreSQL example
151
+ # PostgreSQL example (when using Docker)
126
152
  my_postgres:
127
153
  type: postgres
128
154
  dbname: test_db
129
155
  user: postgres
130
156
  password: secret
131
- host: localhost
157
+ host: host.docker.internal # For Mac/Windows
158
+ # host: 172.17.0.1 # For Linux (docker0 IP)
132
159
  port: 5432
133
160
 
134
- # SQLite example
161
+ # SQLite example (when using Docker)
135
162
  my_sqlite:
136
163
  type: sqlite
137
- path: /path/to/database.db
138
- password: optional_password # optional
164
+ path: /app/sqlite.db # Mapped path inside container
165
+ password: optional_password # optional
139
166
  ```
140
167
 
141
168
  ### Debug Mode
@@ -283,3 +310,7 @@ For detailed guidelines, see [CONTRIBUTING.md](.github/CONTRIBUTING.md)
283
310
  * [5ire](https://5ire.app/)
284
311
  * [Cline](https://cline.bot)
285
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.4.dist-info/METADATA,sha256=Eq2rC77Wrf6UAUSiJVhWNU0N-nQKZw7rSUadOD5Bm4Y,7718
14
- mcp_dbutils-0.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- mcp_dbutils-0.2.4.dist-info/entry_points.txt,sha256=XTjt0QmYRgKOJQT6skR9bp1EMUfIrgpHeZJPZ3CJffs,49
16
- mcp_dbutils-0.2.4.dist-info/licenses/LICENSE,sha256=1A_CwpWVlbjrKdVEYO77vYfnXlW7oxcilZ8FpA_BzCI,1065
17
- mcp_dbutils-0.2.4.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,,