mcp-dbutils 0.15.2__tar.gz → 0.15.4__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 (57) hide show
  1. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.github/workflows/quality-assurance.yml +10 -1
  2. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/CHANGELOG.md +14 -0
  3. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/PKG-INFO +1 -1
  4. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/pyproject.toml +2 -2
  5. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/base.py +33 -28
  6. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/sqlite/server.py +2 -2
  7. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_tools.py +56 -3
  8. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.coveragerc +0 -0
  9. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.github/workflows/code-style.yml +0 -0
  10. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.github/workflows/release.yml +0 -0
  11. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.gitignore +0 -0
  12. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.pre-commit-config.yaml +0 -0
  13. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/.releaserc.json +0 -0
  14. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/Dockerfile +0 -0
  15. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/LICENSE +0 -0
  16. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/README.md +0 -0
  17. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/README_CN.md +0 -0
  18. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/config.yaml.example +0 -0
  19. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/scripts/sonar-ai-fix.fish +0 -0
  20. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/smithery.yaml +0 -0
  21. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/sonar-project.properties +0 -0
  22. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/__init__.py +0 -0
  23. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/config.py +0 -0
  24. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/log.py +0 -0
  25. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/mysql/__init__.py +0 -0
  26. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/mysql/config.py +0 -0
  27. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/mysql/handler.py +0 -0
  28. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/mysql/server.py +0 -0
  29. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/postgres/__init__.py +0 -0
  30. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/postgres/config.py +0 -0
  31. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/postgres/handler.py +0 -0
  32. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/postgres/server.py +0 -0
  33. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/sqlite/__init__.py +0 -0
  34. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/sqlite/config.py +0 -0
  35. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/sqlite/handler.py +0 -0
  36. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/src/mcp_dbutils/stats.py +0 -0
  37. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/conftest.py +0 -0
  38. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/__init__.py +0 -0
  39. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/conftest.py +0 -0
  40. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/fixtures.py +0 -0
  41. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_logging.py +0 -0
  42. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_monitoring.py +0 -0
  43. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_monitoring_enhanced.py +0 -0
  44. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_mysql.py +0 -0
  45. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_mysql_config.py +0 -0
  46. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_postgres.py +0 -0
  47. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_postgres_config.py +0 -0
  48. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_prompts.py +0 -0
  49. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_sqlite.py +0 -0
  50. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_sqlite_config.py +0 -0
  51. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/integration/test_tools_advanced.py +0 -0
  52. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_base.py +0 -0
  53. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_log.py +0 -0
  54. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_mysql_server.py +0 -0
  55. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_postgres_server.py +0 -0
  56. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_sqlite_server.py +0 -0
  57. {mcp_dbutils-0.15.2 → mcp_dbutils-0.15.4}/tests/unit/test_stats.py +0 -0
@@ -283,9 +283,18 @@ jobs:
283
283
  const projectKey = process.env.SONAR_PROJECT_KEY;
284
284
  console.log(`获取项目 ${projectKey} 的SonarCloud问题...`);
285
285
 
286
+ // 获取PR号
287
+ let prNumberParam = '';
288
+ if (context.issue.number) {
289
+ prNumberParam = `&pullRequest=${context.issue.number}`;
290
+ console.log(`处理PR #${context.issue.number}的SonarCloud问题`);
291
+ } else {
292
+ console.log('未检测到PR号,将获取所有未解决的问题');
293
+ }
294
+
286
295
  // 获取未解决的问题
287
296
  const issuesResponse = await fetch(
288
- `https://sonarcloud.io/api/issues/search?componentKeys=${projectKey}&resolved=false&ps=500`,
297
+ `https://sonarcloud.io/api/issues/search?componentKeys=${projectKey}${prNumberParam}&resolved=false&ps=500`,
289
298
  { headers: { Authorization: `Bearer ${process.env.SONAR_TOKEN}` } }
290
299
  ).then(res => res.json());
291
300
 
@@ -1,3 +1,17 @@
1
+ ## [0.15.4](https://github.com/donghao1393/mcp-dbutils/compare/v0.15.3...v0.15.4) (2025-03-15)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * 修复并启用test_list_tables_tool_errors测试 ([#47](https://github.com/donghao1393/mcp-dbutils/issues/47)) ([2dd707e](https://github.com/donghao1393/mcp-dbutils/commit/2dd707ecbca0e7cdf02721a5a3caf243537874fe)), closes [#46](https://github.com/donghao1393/mcp-dbutils/issues/46)
7
+
8
+ ## [0.15.3](https://github.com/donghao1393/mcp-dbutils/compare/v0.15.2...v0.15.3) (2025-03-15)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * 修复SonarCloud问题提取逻辑,仅获取PR相关问题 ([#43](https://github.com/donghao1393/mcp-dbutils/issues/43)) ([8ca874c](https://github.com/donghao1393/mcp-dbutils/commit/8ca874c61a0f1d02ed98c79ea18ab7f66eb41659)), closes [#42](https://github.com/donghao1393/mcp-dbutils/issues/42)
14
+
1
15
  ## [0.15.2](https://github.com/donghao1393/mcp-dbutils/compare/v0.15.1...v0.15.2) (2025-03-14)
2
16
 
3
17
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-dbutils
3
- Version: 0.15.2
3
+ Version: 0.15.4
4
4
  Summary: MCP Database Utilities Service
5
5
  Author: Dong Hao
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-dbutils"
3
- version = "0.15.2"
3
+ version = "0.15.4"
4
4
  description = "MCP Database Utilities Service"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -62,7 +62,7 @@ filterwarnings = [
62
62
  # Ruff配置
63
63
  [tool.ruff]
64
64
  # 目标Python版本
65
- target-version = "0.15.2"
65
+ target-version = "0.15.4"
66
66
  # 行长度限制
67
67
  line-length = 88
68
68
  # 排除的文件和目录
@@ -1,24 +1,11 @@
1
1
  """Connection server base class"""
2
2
 
3
- class ConnectionHandlerError(Exception):
4
- """Base exception for connection errors"""
5
- pass
6
-
7
- class ConfigurationError(ConnectionHandlerError):
8
- """Configuration related errors"""
9
- pass
10
-
11
- class ConnectionError(ConnectionHandlerError):
12
- """Connection related errors"""
13
- pass
14
-
15
3
  import json
16
4
  from abc import ABC, abstractmethod
17
5
  from contextlib import asynccontextmanager
18
6
  from datetime import datetime
19
7
  from importlib.metadata import metadata
20
- from typing import AsyncContextManager
21
- from unittest.mock import MagicMock
8
+ from typing import Any, AsyncContextManager, Dict
22
9
 
23
10
  import mcp.server.stdio
24
11
  import mcp.types as types
@@ -28,6 +15,19 @@ from mcp.server import Server
28
15
  from .log import create_logger
29
16
  from .stats import ResourceStats
30
17
 
18
+
19
+ class ConnectionHandlerError(Exception):
20
+ """Base exception for connection errors"""
21
+ pass
22
+
23
+ class ConfigurationError(ConnectionHandlerError):
24
+ """Configuration related errors"""
25
+ pass
26
+
27
+ class ConnectionError(ConnectionHandlerError):
28
+ """Connection related errors"""
29
+ pass
30
+
31
31
  # 常量定义
32
32
  DATABASE_CONNECTION_NAME = "Database connection name"
33
33
  EMPTY_QUERY_ERROR = "SQL query cannot be empty"
@@ -347,14 +347,16 @@ class ConnectionServer:
347
347
 
348
348
  handler.stats.record_connection_start()
349
349
  self.send_log(LOG_LEVEL_DEBUG, f"Handler created successfully for {connection}")
350
- # 处理MagicMock对象,避免JSON序列化错误
350
+ # 使用通用的方式处理统计信息序列化
351
351
  try:
352
- stats_dict = handler.stats.to_dict()
353
- stats_json = json.dumps(stats_dict)
354
- self.send_log(LOG_LEVEL_INFO, f"Resource stats: {stats_json}")
352
+ if hasattr(handler.stats, 'to_dict') and callable(handler.stats.to_dict):
353
+ stats_dict = handler.stats.to_dict()
354
+ stats_json = json.dumps(stats_dict)
355
+ self.send_log(LOG_LEVEL_INFO, f"Resource stats: {stats_json}")
356
+ else:
357
+ self.send_log(LOG_LEVEL_INFO, "Resource stats not available")
355
358
  except TypeError:
356
- # 在测试环境中,stats可能是MagicMock对象
357
- self.send_log(LOG_LEVEL_INFO, "Resource stats: [Mock object in test environment]")
359
+ self.send_log(LOG_LEVEL_INFO, "Resource stats: [Could not serialize stats object]")
358
360
  yield handler
359
361
  except yaml.YAMLError as e:
360
362
  raise ConfigurationError(f"Invalid YAML configuration: {str(e)}")
@@ -364,16 +366,19 @@ class ConnectionServer:
364
366
  if handler:
365
367
  self.send_log(LOG_LEVEL_DEBUG, f"Cleaning up handler for {connection}")
366
368
  handler.stats.record_connection_end()
367
- # 处理MagicMock对象,避免JSON序列化错误
369
+ # 使用通用的方式处理统计信息序列化
368
370
  try:
369
- stats_dict = handler.stats.to_dict()
370
- stats_json = json.dumps(stats_dict)
371
- self.send_log(LOG_LEVEL_INFO, f"Final resource stats: {stats_json}")
371
+ if hasattr(handler.stats, 'to_dict') and callable(handler.stats.to_dict):
372
+ stats_dict = handler.stats.to_dict()
373
+ stats_json = json.dumps(stats_dict)
374
+ self.send_log(LOG_LEVEL_INFO, f"Final resource stats: {stats_json}")
375
+ else:
376
+ self.send_log(LOG_LEVEL_INFO, "Final resource stats not available")
372
377
  except TypeError:
373
- # 在测试环境中,stats可能是MagicMock对象
374
- self.send_log(LOG_LEVEL_INFO, "Final resource stats: [Mock object in test environment]")
375
- # 在测试环境中,handler可能是MagicMock对象,但cleanup可能是AsyncMock
376
- await handler.cleanup()
378
+ self.send_log(LOG_LEVEL_INFO, "Final resource stats: [Could not serialize stats object]")
379
+ # 清理资源
380
+ if hasattr(handler, 'cleanup') and callable(handler.cleanup):
381
+ await handler.cleanup()
377
382
 
378
383
  def _setup_handlers(self):
379
384
  """Setup MCP handlers"""
@@ -163,7 +163,7 @@ class SQLiteServer(ConnectionServer):
163
163
  columns = [desc[0] for desc in cursor.description]
164
164
  formatted_results = [dict(zip(columns, row)) for row in results]
165
165
 
166
- # 在测试环境中,connection可能是MagicMock对象,不能序列化为JSON
166
+ # 使用更通用的方法确定配置名称
167
167
  config_name = connection if isinstance(connection, str) else 'default'
168
168
  result_text = json.dumps({
169
169
  'type': 'sqlite',
@@ -179,7 +179,7 @@ class SQLiteServer(ConnectionServer):
179
179
  return [types.TextContent(type="text", text=result_text)]
180
180
 
181
181
  except sqlite3.Error as e:
182
- # 在测试环境中,connection可能是MagicMock对象,不能序列化为JSON
182
+ # 使用更通用的方法确定配置名称
183
183
  config_name = connection if isinstance(connection, str) else 'default'
184
184
  error_msg = json.dumps({
185
185
  'type': 'sqlite',
@@ -266,9 +266,62 @@ async def test_list_indexes_tool(postgres_db, sqlite_db, mcp_config):
266
266
  await server_to_client_send.aclose()
267
267
  await server_to_client_recv.aclose()
268
268
 
269
- @pytest.mark.skip(reason="Error testing is unstable, will be fixed in a future PR")
270
269
  @pytest.mark.asyncio
271
270
  async def test_list_tables_tool_errors(postgres_db, mcp_config):
272
271
  """Test error cases for list_tables tool"""
273
- # This test is skipped for now
274
- pass
272
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as tmp:
273
+ # 写入有效的配置
274
+ yaml.dump(mcp_config, tmp)
275
+ tmp.flush()
276
+ server = ConnectionServer(config_path=tmp.name)
277
+
278
+ # 创建双向流
279
+ client_to_server_send, client_to_server_recv = anyio.create_memory_object_stream[types.JSONRPCMessage | Exception](10)
280
+ server_to_client_send, server_to_client_recv = anyio.create_memory_object_stream[types.JSONRPCMessage](10)
281
+
282
+ # 在后台启动服务器
283
+ server_task = asyncio.create_task(
284
+ server.server.run(
285
+ client_to_server_recv,
286
+ server_to_client_send,
287
+ server.server.create_initialization_options(),
288
+ raise_exceptions=False # 不抛出异常,让客户端接收错误响应
289
+ )
290
+ )
291
+
292
+ try:
293
+ # 初始化客户端会话
294
+ client = ClientSession(server_to_client_recv, client_to_server_send)
295
+ async with client:
296
+ await client.initialize()
297
+
298
+ # 测试场景1:不存在的连接名
299
+ result = await client.call_tool("dbutils-list-tables", {"connection": "non_existent_connection"})
300
+ print(f"收到响应: {result}")
301
+ assert result.isError, "应该返回错误状态"
302
+ assert len(result.content) == 1
303
+ assert result.content[0].type == "text"
304
+ assert "Connection not found" in result.content[0].text
305
+ assert "non_existent_connection" in result.content[0].text
306
+
307
+ # 测试场景2:缺少必需的连接参数
308
+ result = await client.call_tool("dbutils-list-tables", {})
309
+ print(f"收到响应: {result}")
310
+ assert result.isError, "应该返回错误状态"
311
+ assert len(result.content) == 1
312
+ assert result.content[0].type == "text"
313
+ assert "Connection name must be specified" in result.content[0].text
314
+
315
+ finally:
316
+ # 清理
317
+ server_task.cancel()
318
+ try:
319
+ await server_task
320
+ except asyncio.CancelledError:
321
+ pass
322
+
323
+ # 关闭流
324
+ await client_to_server_send.aclose()
325
+ await client_to_server_recv.aclose()
326
+ await server_to_client_send.aclose()
327
+ await server_to_client_recv.aclose()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes