feat: 添加数据库管理平台MCP Server

新增lzwcai-mcp-agile-db项目,提供数据库管理、表操作、数据CRUD、
API密钥管理、技能与工具管理等功能。

包含33个工具:
- 数据源管理:创建、更新、删除数据源
- 数据库与表管理:表结构操作、数据查询等
- API密钥管理:密钥创建、权限管理等
- 技能与工具管理:SQL工具创建、配置更新等
- 数据导入和SQL执行功能

添加了完整的README文档说明安装使用方法,
以及Python 3.12版本支持和基本项目结构。
This commit is contained in:
2026-06-11 09:53:40 +08:00
parent a1012e61bf
commit 9c597c9b0d
57 changed files with 3688 additions and 2103 deletions

View File

@@ -0,0 +1,85 @@
# lzwcai-mcp-agile-db
数据库管理平台 MCP Server提供 33 个工具用于数据库管理、表操作、数据 CRUD、API 密钥管理、技能与工具管理等。
## 环境变量
| 变量名 | 必填 | 说明 |
|--------|------|------|
| `AGILE_DB_API_KEY` | 是 | 数据库管理平台的 API 密钥 |
| `AGILE_DB_BASE_URL` | 否 | 数据库管理平台后端地址(默认 `http://localhost:8080` |
## 安装
```bash
pip install -e .
```
## 运行
```bash
# 设置环境变量
export AGILE_DB_API_KEY="your-api-key"
export AGILE_DB_BASE_URL="http://localhost:8080" # 可选
# 运行 MCP Server
lzwcai-mcp-agile-db
```
## 工具列表
### 数据源管理
- `list_datasources` - 获取数据源列表
- `get_datasource_detail` - 获取数据源详情
- `create_datasource` - 创建数据源
- `update_datasource` - 更新数据源
- `toggle_datasource_status` - 启用/停用数据源
- `delete_datasource` - 删除数据源
### 数据库与表管理
- `list_databases` - 获取数据库列表
- `list_tables` - 获取表列表
- `get_table_detail` - 获取表详情
- `create_table` - 创建表
- `alter_table` - 修改表结构
- `generate_table_by_description` - 通过自然语言生成表结构
### 表数据 CRUD
- `query_table_data` - 查询表数据
- `insert_table_row` - 插入行数据
- `update_table_row` - 更新行数据
- `delete_table_rows` - 删除行数据
- `export_table_excel` - 导出 Excel
### API 密钥管理
- `list_api_keys` - 获取密钥列表
- `create_api_key` - 创建密钥
- `toggle_api_key_status` - 启用/禁用密钥
- `delete_api_key` - 删除密钥
- `get_api_key_permissions` - 查看密钥权限
- `grant_api_key_permissions` - 授予权限
### 技能与工具管理
- `get_skill_by_datasource` - 获取技能信息
- `get_skill_tools` - 获取技能工具列表
- `create_skill` - 创建技能
- `create_sql_tool` - 创建 SQL 工具
- `delete_skill_tool` - 删除技能工具
- `update_skill_config` - 更新技能配置
### 数据导入
- `preview_import_data` - 预览导入数据
- `confirm_import_data` - 确认导入数据
### 表订阅与 SQL 执行
- `toggle_table_subscription` - 切换表订阅
- `execute_sql` - 执行 SQL 查询
## 架构
- `tools/_base.py` - 工具注册装饰器和基类
- `tools/*.py` - 工具实现文件
- `utils/api_client.py` - 统一 HTTP 客户端
- `utils/env_config.py` - 环境变量配置
- `utils/logger_config.py` - 日志配置
- `server.py` - MCP Server 注册和启动逻辑

View File

@@ -0,0 +1 @@
3.12

View File

@@ -0,0 +1,5 @@
"""lzwcai-mcp-agile-db MCP Server 包"""
from .server import main
__all__ = ["main"]

View File

@@ -0,0 +1,202 @@
2026-06-11 09:30:49 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
2026-06-11 09:30:49 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000025426747BF0>
2026-06-11 09:30:50 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
2026-06-11 09:30:50 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
2026-06-11 09:33:24 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
2026-06-11 09:33:24 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000020155C7D190>
2026-06-11 09:33:25 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.server - INFO - [server.py:56] - ListTools 响应: 返回 33 个工具
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
2026-06-11 09:33:45 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000020155844410>
2026-06-11 09:33:45 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-06-11 09:33:45 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
2026-06-11 09:33:45 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_datasources
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
2026-06-11 09:33:46 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
2026-06-11 09:33:46 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='D:\\anaconda3\\Library\\ssl\\cacert.pem'
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000020155E9D8E0>
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x0000020155E740D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000020155D4CFE0>
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:33:40 GMT'), (b'Content-Type', b'application/json;charset=utf-8'), (b'Content-Length', b'51'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
2026-06-11 09:33:46 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:91] - [API错误] 登录过期,请重新登录
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.server - ERROR - [server.py:96] - 工具执行失败: list_datasources, 错误: 登录过期,请重新登录
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\server.py", line 84, in handle_call_tool
result = await tool_instance.execute(arguments or {})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\tools\datasources.py", line 30, in execute
return self.client.get("/api/datasource/connection/list", params=params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 102, in get
return self._handle_response(response, url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 92, in _handle_response
raise Exception(error_msg)
Exception: 登录过期,请重新登录
2026-06-11 09:33:46 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
2026-06-11 09:39:21 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
2026-06-11 09:39:21 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
2026-06-11 09:39:21 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='C:\\Users\\HiWin10\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\cacert.pem'
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000027729C00710>
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x0000027729DFE9D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000027729C23170>
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:39:15 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
2026-06-11 09:39:21 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list "HTTP/1.1 200 "
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
2026-06-11 09:39:52 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
2026-06-11 09:39:52 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D06C860>
2026-06-11 09:39:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.server - INFO - [server.py:56] - ListTools 响应: 返回 33 个工具
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D188A40>
2026-06-11 09:40:08 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_datasources
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
2026-06-11 09:40:08 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
2026-06-11 09:40:08 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='D:\\anaconda3\\Library\\ssl\\cacert.pem'
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D28C560>
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x000002B57D2642D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57CF80410>
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:40:02 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
2026-06-11 09:40:08 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - INFO - [server.py:86] - 工具执行成功: list_datasources
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - DEBUG - [server.py:87] - 工具返回结果: {
"total": 14,
"rows": [
{
"createBy": "",
"createTime": "2026-06-10 16:47:43",
"updateBy": "",
"updateTime": "2026-06-10 16:47:43",
"remark": "设备报价管理系统包含设备基础信息、预设方案模板、报价单主表和明细表四个核心数据对象,支持从设备参数管理到整套产线方案配置的完整报价流程,为销售部门提供标准化报价服务,实现快速方案生成和精准成本核算。",
"id": "58",
"enterpriseId": "1937166012193443842",
"deptId": "1171",
"userId": "292",
"host": "host.docker.internal",
"port": 5432,
"datasourceName": "HMD产品",
"database...
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
2026-06-11 09:40:55 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D0617C0>
2026-06-11 09:40:55 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-06-11 09:40:55 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
2026-06-11 09:40:55 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_api_keys
2026-06-11 09:40:55 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/api_key/list
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - close.started
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - close.complete
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D2623C0>
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x000002B57D2642D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D2626F0>
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:40:49 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
2026-06-11 09:40:56 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/api_key/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.server - INFO - [server.py:86] - 工具执行成功: list_api_keys
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.server - DEBUG - [server.py:87] - 工具返回结果: {
"total": 6,
"rows": [
{
"createBy": "",
"createTime": "2026-06-06 15:10:31",
"updateBy": "",
"updateTime": "2026-06-06 15:10:31",
"remark": null,
"id": "7",
"apiKey": "Lb8LgEJ7eBUU8QMifKUJvo9w6YLAotbKJ-w1DKU8ZrU",
"apiKeyName": "AWINBEXT",
"enterpriseId": "1937166012193443842",
"status": 0,
"expireTime": "2027-06-06T15:10:32.000+08:00"
},
{
"createBy": "",
"createTime": "2026-05-25 14:47:11",
"u...
2026-06-11 09:40:56 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent

View File

@@ -0,0 +1,15 @@
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:91] - [API错误] 登录过期,请重新登录
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.server - ERROR - [server.py:96] - 工具执行失败: list_datasources, 错误: 登录过期,请重新登录
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\server.py", line 84, in handle_call_tool
result = await tool_instance.execute(arguments or {})
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\tools\datasources.py", line 30, in execute
return self.client.get("/api/datasource/connection/list", params=params)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 102, in get
return self._handle_response(response, url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 92, in _handle_response
raise Exception(error_msg)
Exception: 登录过期,请重新登录

View File

@@ -0,0 +1,6 @@
def main():
print("Hello from lzwcai-mcp-agile-db!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
[project]
name = "lzwcai-mcp-agile-db"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = []

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""
lzwcai-mcp-agile-db MCP Server
数据库管理平台 MCP 工具服务,提供 33 个工具用于数据库管理、表操作、API 密钥管理等
"""
import json
import logging
import anyio
import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from .utils.logger_config import setup_system_logging, get_logger
from .utils.api_client import AgileDBAPIClient
from .tools._base import get_registered_tools
# 初始化日志系统
setup_system_logging(app_name="lzwcai_mcp_agile_db", log_level=logging.DEBUG)
logger = get_logger(__name__)
# 初始化 MCP Server
server = Server("lzwcai_mcp_agile_db")
# 全局 API 客户端
_api_client: AgileDBAPIClient = None
def get_api_client() -> AgileDBAPIClient:
"""获取或创建 API 客户端"""
global _api_client
if _api_client is None:
_api_client = AgileDBAPIClient()
return _api_client
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""列出所有可用工具"""
logger.info("收到 ListTools 请求")
tools = []
for tool_cls in get_registered_tools():
instance = tool_cls(get_api_client())
tool_def = instance.to_tool_def()
tools.append(
types.Tool(
name=tool_def["name"],
description=tool_def["description"],
inputSchema=tool_def["inputSchema"],
)
)
logger.info(f"ListTools 响应: 返回 {len(tools)} 个工具")
return tools
@server.call_tool()
async def handle_call_tool(
name: str,
arguments: dict | None,
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""调用工具"""
logger.info(f"收到 CallTool 请求: name={name}")
# 查找对应的工具类
tool_cls = None
for cls in get_registered_tools():
if cls.name == name:
tool_cls = cls
break
if tool_cls is None:
logger.error(f"未找到工具: {name}")
raise ValueError(f"未知工具: {name}")
# 创建工具实例并执行
client = get_api_client()
tool_instance = tool_cls(client)
try:
result = await tool_instance.execute(arguments or {})
logger.info(f"工具执行成功: {name}")
logger.debug(f"工具返回结果: {json.dumps(result, ensure_ascii=False, indent=2)[:500]}...")
return [
types.TextContent(
type="text",
text=json.dumps(result, ensure_ascii=False, indent=2),
)
]
except Exception as e:
logger.error(f"工具执行失败: {name}, 错误: {e}", exc_info=True)
return [
types.TextContent(
type="text",
text=json.dumps({"error": str(e), "tool_name": name}, ensure_ascii=False, indent=2),
)
]
async def run_server():
"""运行 MCP Server (stdio 模式)"""
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
InitializationOptions(
server_name="lzwcai_mcp_agile_db",
server_version="0.1.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
def main():
"""主入口"""
logger.info("=" * 50)
logger.info("lzwcai-mcp-agile-db MCP Server 启动")
# 导入所有工具模块(触发装饰器注册)
from . import tools # noqa: F401
logger.info(f"已注册工具数量: {len(get_registered_tools())}")
logger.info("=" * 50)
logger.info("开始运行 MCP Server (stdio 模式)")
anyio.run(run_server)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,18 @@
"""
工具自动发现模块
自动导入 tools/ 目录下所有工具模块,触发 @register_tool 装饰器注册
"""
import importlib
import pkgutil
from pathlib import Path
# 获取当前包路径
_package_path = Path(__file__).parent
# 遍历所有 Python 文件(排除 __init__.py 和 _base.py
for _, module_name, _ in pkgutil.iter_modules([str(_package_path)]):
if module_name.startswith("_"):
continue
# 动态导入模块,触发装饰器
importlib.import_module(f".{module_name}", __package__)

View File

@@ -0,0 +1,116 @@
"""
工具注册框架
提供 ToolDef 基类和 @register_tool 装饰器,用于声明式定义 MCP 工具
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, TYPE_CHECKING
if TYPE_CHECKING:
from ..utils.api_client import AgileDBAPIClient
# 全局工具注册表
_registered_tools = []
def register_tool(name: str):
"""
工具注册装饰器
使用方式:
@register_tool("list_datasources")
class ListDatasourcesTool(ToolDef):
name = "list_datasources"
description = "获取数据源列表"
input_schema = {...}
async def execute(self, args):
return await self.client.get("/api/...", params=args)
Args:
name: 工具名称(唯一标识)
"""
def decorator(cls):
# 确保类有正确的 name 属性
if not hasattr(cls, 'name') or cls.name != name:
cls.name = name
# 注册到全局列表,避免重复注册
if cls not in _registered_tools:
_registered_tools.append(cls)
return cls
return decorator
class ToolDef(ABC):
"""
工具定义基类
所有工具都应继承此类并使用 @register_tool 装饰器注册
类属性:
name: 工具名称(唯一标识)
description: 工具描述
input_schema: JSON Schema 格式的工具输入参数定义
实例属性:
client: AgileDBAPIClient 实例(由 server 注入)
"""
name: str = ""
description: str = ""
input_schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
def __init__(self, client: "AgileDBAPIClient"):
"""
初始化工具实例
Args:
client: API 客户端实例(由 server 注入)
"""
self.client = client
@abstractmethod
async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
"""
执行工具逻辑
Args:
args: 工具输入参数(已校验)
Returns:
Dict[str, Any]: 执行结果,将作为 MCP 工具返回值
"""
pass
def to_tool_def(self) -> Dict[str, Any]:
"""
转换为 MCP 工具定义格式
Returns:
dict: MCP types.Tool 所需的参数
"""
return {
"name": self.name,
"description": self.description,
"inputSchema": self.input_schema,
}
def get_registered_tools() -> list:
"""
获取所有已注册的工具类列表
Returns:
list: 所有被 @register_tool 装饰的类
"""
return list(_registered_tools)
def clear_registered_tools():
"""
清空所有已注册的工具(主要用于测试)
"""
_registered_tools.clear()

View File

@@ -0,0 +1,121 @@
"""
API 密钥管理工具 (工具 18-23)
"""
from ._base import register_tool, ToolDef
@register_tool("list_api_keys")
class ListApiKeysTool(ToolDef):
name = "list_api_keys"
description = "获取 API 密钥列表"
input_schema = {
"type": "object",
"properties": {
"apiKeyName": {"type": "string", "description": "密钥名称模糊搜索"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
},
"required": [],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
params = {k: v for k, v in args.items() if v is not None}
return self.client.get("/api/datasource/api_key/list", params=params)
@register_tool("create_api_key")
class CreateApiKeyTool(ToolDef):
name = "create_api_key"
description = "创建新的 API 密钥"
input_schema = {
"type": "object",
"properties": {
"apiKeyName": {"type": "string", "description": "密钥名称最多50字"},
},
"required": ["apiKeyName"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/api_key", json_data=args)
@register_tool("toggle_api_key_status")
class ToggleApiKeyStatusTool(ToolDef):
name = "toggle_api_key_status"
description = "启用/禁用 API 密钥"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "密钥 ID"},
"status": {"type": "integer", "enum": [0, 1], "description": "0=启用, 1=禁用"},
},
"required": ["id", "status"],
}
async def execute(self, args: dict) -> dict:
return self.client.put("/api/datasource/api_key", json_data=args)
@register_tool("delete_api_key")
class DeleteApiKeyTool(ToolDef):
name = "delete_api_key"
description = "删除 API 密钥"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "密钥 ID"},
},
"required": ["id"],
}
async def execute(self, args: dict) -> dict:
return self.client.delete(f"/api/datasource/api_key/{args['id']}")
@register_tool("get_api_key_permissions")
class GetApiKeyPermissionsTool(ToolDef):
name = "get_api_key_permissions"
description = "查看指定密钥的权限配置"
input_schema = {
"type": "object",
"properties": {
"apiKeyId": {"type": "string", "description": "密钥 ID"},
},
"required": ["apiKeyId"],
}
async def execute(self, args: dict) -> dict:
return self.client.get(f"/api/datasource/api_key/permission/{args['apiKeyId']}")
@register_tool("grant_api_key_permissions")
class GrantApiKeyPermissionsTool(ToolDef):
name = "grant_api_key_permissions"
description = "批量为 API 密钥授予权限"
input_schema = {
"type": "object",
"properties": {
"apiKeyId": {"type": "string", "description": "密钥 ID"},
"batchDatas": {
"type": "array",
"description": "权限批量数据数组",
"items": {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源 ID"},
"permissionLevel": {"type": "string", "enum": ["connection", "database", "table"], "description": "权限级别"},
"permissionType": {"type": "string", "description": "权限类型(逗号分隔)"},
"databaseName": {"type": "string", "description": "数据库名level=database/table 时)"},
"tableName": {"type": "string", "description": "表名level=table 时)"},
},
"required": ["connectionId", "permissionLevel", "permissionType"],
},
},
},
"required": ["apiKeyId", "batchDatas"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/api_key/permission/grant_batch", json_data=args)

View File

@@ -0,0 +1,72 @@
"""
数据导入工具 (工具 30-31)
"""
import base64
import io
from ._base import register_tool, ToolDef
@register_tool("preview_import_data")
class PreviewImportDataTool(ToolDef):
name = "preview_import_data"
description = "上传 Excel 文件AI 智能识别并预览表结构/数据"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "环境,默认 test"},
"file_base64": {"type": "string", "description": "Excel 文件 base64 编码(.xlsx/.xls, <500KB"},
"file_name": {"type": "string", "description": "文件名(如 data.xlsx"},
},
"required": ["connectionId", "file_base64"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
target = args.pop("target", "test")
file_base64 = args.pop("file_base64")
file_name = args.pop("file_name", "import.xlsx")
# 解码 base64 文件
file_content = base64.b64decode(file_base64)
# 构建文件上传
files = {
"file": (file_name, io.BytesIO(file_content), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
}
return self.client.upload(
f"/api/datasource/connection/{connection_id}/import_document/preview",
files=files,
params={"target": target},
)
@register_tool("confirm_import_data")
class ConfirmImportDataTool(ToolDef):
name = "confirm_import_data"
description = "确认导入 AI 识别后的数据"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "description": "环境"},
"data": {"type": "object", "description": "导入数据(含 tableStructure + allData"},
},
"required": ["connectionId", "data"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
target = args.pop("target", "test")
data = args.pop("data")
return self.client.post(
f"/api/datasource/connection/{connection_id}/import_document/confirm",
json_data=data,
params={"target": target},
)

View File

@@ -0,0 +1,151 @@
"""
数据库与表管理工具 (工具 7-12)
"""
from ._base import register_tool, ToolDef
@register_tool("list_databases")
class ListDatabasesTool(ToolDef):
name = "list_databases"
description = "获取指定数据源下的数据库列表"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
params = {k: v for k, v in args.items() if v is not None}
return self.client.get("/api/datasource/config/list", params=params)
@register_tool("list_tables")
class ListTablesTool(ToolDef):
name = "list_tables"
description = "获取指定数据源下的表列表"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
params = {k: v for k, v in args.items() if v is not None}
return self.client.get("/api/datasource/table/list", params=params)
@register_tool("get_table_detail")
class GetTableDetailTool(ToolDef):
name = "get_table_detail"
description = "获取表的完整结构信息(字段列表、主键、类型等)"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
},
"required": ["tableId"],
}
async def execute(self, args: dict) -> dict:
table_id = args["tableId"]
return self.client.get(f"/api/datasource/table/{table_id}/detail")
@register_tool("create_table")
class CreateTableTool(ToolDef):
name = "create_table"
description = "在指定数据库创建新表"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源连接 ID"},
"databaseName": {"type": "string", "description": "目标数据库名"},
"tableName": {"type": "string", "description": "表名(小写字母+数字+下划线)"},
"tableComment": {"type": "string", "description": "表注释"},
"columns": {
"type": "array",
"description": "字段定义数组",
"items": {
"type": "object",
"properties": {
"columnName": {"type": "string", "description": "字段名"},
"columnType": {"type": "string", "description": "字段类型VARCHAR/INTEGER/SERIAL等"},
"columnLength": {"type": "integer", "description": "字段长度"},
"isPrimaryKey": {"type": "boolean", "description": "是否主键"},
"isNullable": {"type": "boolean", "description": "是否可空"},
"isAutoIncrement": {"type": "boolean", "description": "是否自增"},
"columnComment": {"type": "string", "description": "字段注释"},
"defaultValue": {"type": "string", "description": "默认值"},
},
"required": ["columnName", "columnType"],
},
},
},
"required": ["connectionId", "databaseName", "tableName", "columns"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
return self.client.post(f"/api/datasource/connection/{connection_id}/create_table", json_data=args)
@register_tool("alter_table")
class AlterTableTool(ToolDef):
name = "alter_table"
description = "修改已有表结构(增/改/删字段)"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源连接 ID"},
"databaseName": {"type": "string", "description": "数据库名"},
"tableName": {"type": "string", "description": "表名"},
"operations": {
"type": "array",
"description": "表结构变更操作数组",
"items": {
"type": "object",
"properties": {
"operation": {"type": "string", "enum": ["ADD_COLUMN", "DROP_COLUMN", "RENAME_COLUMN", "ALTER_COLUMN_TYPE", "SET_NOT_NULL", "DROP_NOT_NULL", "SET_DEFAULT", "DROP_DEFAULT"], "description": "变更类型"},
"column": {"type": "object", "description": "列定义(根据 operation 不同包含不同字段)"},
},
"required": ["operation", "column"],
},
},
"tableComment": {"type": "string", "description": "表注释"},
},
"required": ["connectionId", "databaseName", "tableName", "operations"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
return self.client.put(f"/api/datasource/connection/{connection_id}/alter_table", json_data=args)
@register_tool("generate_table_by_description")
class GenerateTableByDescriptionTool(ToolDef):
name = "generate_table_by_description"
description = "通过自然语言描述让 AI 生成表结构(异步任务)"
input_schema = {
"type": "object",
"properties": {
"requirement": {"type": "string", "description": "业务需求描述"},
"databaseId": {"type": "integer", "description": "关联的数据库 ID可选"},
},
"required": ["requirement"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/connection/generate_table", json_data=args)

View File

@@ -0,0 +1,173 @@
"""
数据源管理工具 (工具 1-6)
"""
import logging
from ._base import register_tool, ToolDef
logger = logging.getLogger(__name__)
@register_tool("list_datasources")
class ListDatasourcesTool(ToolDef):
name = "list_datasources"
description = "获取数据源列表,支持搜索和状态筛选"
input_schema = {
"type": "object",
"properties": {
"datasourceName": {"type": "string", "description": "数据源名称模糊搜索"},
"status": {"type": "integer", "description": "0=运行中, 1=已停止, 不传=全部"},
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
},
"required": [],
}
async def execute(self, args: dict) -> dict:
params = {k: v for k, v in args.items() if v is not None}
return self.client.get("/api/datasource/connection/list", params=params)
@register_tool("get_datasource_detail")
class GetDatasourceDetailTool(ToolDef):
name = "get_datasource_detail"
description = "获取单个数据源的完整详情(含配置和实时结构)"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
ds_id = args["datasourceId"]
result = {}
# 获取基本信息
try:
result["detail"] = self.client.get(f"/api/datasource/connection/{ds_id}")
except Exception as e:
result["detail"] = {"error": str(e)}
# 获取配置
try:
result["config"] = self.client.get(f"/api/datasource/config/{ds_id}")
except Exception as e:
result["config"] = {"error": str(e)}
# 获取实时结构
try:
result["structure"] = self.client.get(f"/api/datasource/connection/realtime/structure/{ds_id}")
except Exception as e:
result["structure"] = {"error": str(e)}
return result
@register_tool("create_datasource")
class CreateDatasourceTool(ToolDef):
name = "create_datasource"
description = "创建外部数据源连接(可选先测试连接)"
input_schema = {
"type": "object",
"properties": {
"datasourceName": {"type": "string", "description": "数据源名称3-20字"},
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng"], "description": "数据库类型"},
"host": {"type": "string", "description": "数据库地址"},
"port": {"type": "integer", "description": "端口号"},
"databaseName": {"type": "string", "description": "要连接的数据库名"},
"username": {"type": "string", "description": "数据库用户名"},
"password": {"type": "string", "description": "密码"},
"remark": {"type": "string", "description": "数据源描述"},
"connectionType": {"type": "string", "enum": ["user_password", "ssl"], "description": "连接类型,默认 user_password"},
"test_first": {"type": "boolean", "default": True, "description": "是否先测试连接,默认 true"},
},
"required": ["datasourceName", "datasourceType", "host", "port", "databaseName", "username"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
test_first = args.pop("test_first", True)
# 如果需要先测试连接
if test_first:
test_data = {
"datasourceName": args.get("datasourceName"),
"datasourceType": args.get("datasourceType"),
"host": args.get("host"),
"port": args.get("port"),
"databaseName": args.get("databaseName"),
"username": args.get("username"),
"password": args.get("password"),
"connectionType": args.get("connectionType", "user_password"),
}
test_result = self.client.post("/api/datasource/connection/test", json_data=test_data)
if test_result.get("code") != 200:
return {"success": False, "error": f"连接测试失败: {test_result.get('msg', '未知错误')}"}
# 创建数据源
return self.client.post("/api/datasource/connection", json_data=args)
@register_tool("update_datasource")
class UpdateDatasourceTool(ToolDef):
name = "update_datasource"
description = "更新数据源连接信息"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "数据源 ID"},
"datasourceName": {"type": "string", "description": "更新名称"},
"host": {"type": "string", "description": "更新地址"},
"port": {"type": "integer", "description": "更新端口"},
"databaseName": {"type": "string", "description": "更新数据库名"},
"username": {"type": "string", "description": "更新用户名"},
"password": {"type": "string", "description": "新密码(不传则不变)"},
"remark": {"type": "string", "description": "更新描述"},
},
"required": ["id"],
}
async def execute(self, args: dict) -> dict:
return self.client.put("/api/datasource/connection", json_data=args)
@register_tool("toggle_datasource_status")
class ToggleDatasourceStatusTool(ToolDef):
name = "toggle_datasource_status"
description = "启用/停用数据源"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "数据源 ID"},
"status": {"type": "integer", "enum": [0, 1], "description": "0=启用, 1=停用"},
},
"required": ["id", "status"],
}
async def execute(self, args: dict) -> dict:
return self.client.put("/api/datasource/connection/changeStatus", json_data=args)
@register_tool("delete_datasource")
class DeleteDatasourceTool(ToolDef):
name = "delete_datasource"
description = "删除数据源(运行中会自动先停用)"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "数据源 ID"},
},
"required": ["id"],
}
async def execute(self, args: dict) -> dict:
ds_id = args["id"]
# 先尝试停用(仅忽略已停用等预期错误)
try:
self.client.put("/api/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1})
except Exception as e:
# 记录日志但继续删除
logger.debug(f"停用数据源失败(可能已停用): {e}")
# 删除数据源
return self.client.delete(f"/api/datasource/connection/{ds_id}")

View File

@@ -0,0 +1,137 @@
"""
技能与工具管理工具 (工具 24-29)
"""
import json
from ._base import register_tool, ToolDef
@register_tool("get_skill_by_datasource")
class GetSkillByDatasourceTool(ToolDef):
name = "get_skill_by_datasource"
description = "根据数据源获取技能信息"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
return self.client.get(f"/api/datasource/skill/getByDatasource/{args['datasourceId']}")
@register_tool("get_skill_tools")
class GetSkillToolsTool(ToolDef):
name = "get_skill_tools"
description = "获取技能下的工具列表"
input_schema = {
"type": "object",
"properties": {
"skillId": {"type": "string", "description": "技能 ID"},
},
"required": ["skillId"],
}
async def execute(self, args: dict) -> dict:
return self.client.get(f"/api/datasource/skill/getBySkillId/{args['skillId']}")
@register_tool("create_skill")
class CreateSkillTool(ToolDef):
name = "create_skill"
description = "为数据源创建技能"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"name": {"type": "string", "description": "技能名称(不传则自动生成)"},
"description": {"type": "string", "description": "技能描述"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/skill/createOrGet", json_data=args)
@register_tool("create_sql_tool")
class CreateSqlToolTool(ToolDef):
name = "create_sql_tool"
description = "将 SQL 查询创建为可复用工具(支持批量)"
input_schema = {
"type": "object",
"properties": {
"skillId": {"type": "string", "description": "技能 ID"},
"tableIds": {
"type": "array",
"description": "关联的表 ID 数组",
"items": {"type": "string"},
},
"suggestions": {
"type": "array",
"description": "SQL 工具建议数组",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "工具名称"},
"businessDescription": {"type": "string", "description": "业务描述"},
"sqlTemplate": {"type": "string", "description": "SQL 模板(支持 #{param} 参数占位)"},
"sqlParams": {"type": "string", "description": "参数 JSON SchemaJSON 字符串或对象)"},
"resultType": {"type": "string", "enum": ["single", "list"], "default": "list", "description": "结果类型,默认 list"},
"businessScenario": {"type": "string", "description": "业务场景描述"},
},
"required": ["name", "businessDescription", "sqlTemplate"],
},
},
},
"required": ["skillId", "suggestions"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
# 处理 suggestions 中的 sqlParams
if "suggestions" in args and isinstance(args["suggestions"], list):
for suggestion in args["suggestions"]:
if "sqlParams" in suggestion and isinstance(suggestion["sqlParams"], dict):
suggestion["sqlParams"] = json.dumps(suggestion["sqlParams"])
return self.client.post("/api/datasource/skill/confirmTools", json_data=args)
@register_tool("delete_skill_tool")
class DeleteSkillToolTool(ToolDef):
name = "delete_skill_tool"
description = "删除技能下的工具"
input_schema = {
"type": "object",
"properties": {
"skillToolId": {"type": "string", "description": "工具 ID"},
},
"required": ["skillToolId"],
}
async def execute(self, args: dict) -> dict:
return self.client.delete(f"/api/datasource/skill/tskilltool/{args['skillToolId']}")
@register_tool("update_skill_config")
class UpdateSkillConfigTool(ToolDef):
name = "update_skill_config"
description = "更新技能配置(如 MCP Server 配置模板)"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"configTemplate": {"type": "string", "description": "配置模板 JSON 字符串"},
},
"required": ["datasourceId", "configTemplate"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
# 如果 configTemplate 是 dict转为 JSON 字符串
if "configTemplate" in args and isinstance(args["configTemplate"], dict):
args["configTemplate"] = json.dumps(args["configTemplate"])
return self.client.post("/api/datasource/skill/updateOrGet", json_data=args)

View File

@@ -0,0 +1,25 @@
"""
SQL 执行工具 (工具 33)
"""
from ._base import register_tool, ToolDef
@register_tool("execute_sql")
class ExecuteSqlTool(ToolDef):
name = "execute_sql"
description = "执行原生 SQL 查询"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"executableSql": {"type": "string", "description": "SQL 语句"},
"sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"},
"businessName": {"type": "string", "description": "业务名称(可选)"},
"parameters": {"type": "object", "description": "参数定义(可选)"},
},
"required": ["datasourceId", "executableSql"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/sqlExecutionLog/testSqlWithSchema", json_data=args)

View File

@@ -0,0 +1,23 @@
"""
表订阅工具 (工具 32)
"""
from ._base import register_tool, ToolDef
@register_tool("toggle_table_subscription")
class ToggleTableSubscriptionTool(ToolDef):
name = "toggle_table_subscription"
description = "切换表的订阅状态"
input_schema = {
"type": "object",
"properties": {
"configId": {"type": "string", "description": "数据库配置 ID"},
"tableName": {"type": "string", "description": "表名"},
"isSubscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"},
},
"required": ["configId", "tableName", "isSubscribe"],
}
async def execute(self, args: dict) -> dict:
return self.client.post("/api/datasource/subscription/toggle", json_data=args)

View File

@@ -0,0 +1,137 @@
"""
表数据 CRUD 工具 (工具 13-17)
"""
import base64
from ._base import register_tool, ToolDef
@register_tool("query_table_data")
class QueryTableDataTool(ToolDef):
name = "query_table_data"
description = "查询内置表数据(分页)"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 10, "description": "每页数量"},
},
"required": ["tableId"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
params = {k: v for k, v in args.items() if v is not None}
return self.client.get(f"/api/datasource/connection/builtin/table/{table_id}", params=params)
@register_tool("insert_table_row")
class InsertTableRowTool(ToolDef):
name = "insert_table_row"
description = "向内置表插入一行数据"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"data": {"type": "object", "description": "行数据(键值对,键为字段名)"},
},
"required": ["tableId", "data"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
data = args.pop("data", {})
params = {"target": target} if target else {}
return self.client.post(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params)
@register_tool("update_table_row")
class UpdateTableRowTool(ToolDef):
name = "update_table_row"
description = "更新内置表的指定行"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"primaryKey": {"type": "object", "description": "主键值(如 {\"id\": 1}"},
"data": {"type": "object", "description": "要更新的字段值"},
},
"required": ["tableId", "primaryKey", "data"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
primary_key = args.pop("primaryKey")
data = args.pop("data", {})
params = {"target": target} if target else {}
body = {"primaryKey": primary_key, "data": data}
return self.client.put(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
@register_tool("delete_table_rows")
class DeleteTableRowsTool(ToolDef):
name = "delete_table_rows"
description = "删除内置表的指定行(根据主键批量删除)"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"primaryKeys": {
"type": "array",
"description": "主键数组(如 [{\"id\": 1}, {\"id\": 2}]",
"items": {"type": "object"},
},
},
"required": ["tableId", "primaryKeys"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
primary_keys = args.pop("primaryKeys")
params = {"target": target} if target else {}
body = {"primaryKeys": primary_keys}
return self.client.delete(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
@register_tool("export_table_excel")
class ExportTableExcelTool(ToolDef):
name = "export_table_excel"
description = "导出表数据为 Excel 文件(返回 base64 编码)"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
},
"required": ["tableId"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
params = {"target": target} if target else {}
result = self.client.get(f"/api/datasource/connection/builtin/table/{table_id}/export/excel", params=params)
# 处理二进制响应
if result.get("raw"):
content = result["data"]
return {
"success": True,
"file_base64": base64.b64encode(content).decode("utf-8"),
"message": "Excel 文件已导出,请解码 base64 内容获取文件",
}
return result

View File

@@ -0,0 +1,13 @@
from .env_config import get_api_key, get_base_url, get_env_config
from .logger_config import setup_system_logging, get_logger
from .api_client import AgileDBAPIClient, get_default_client
__all__ = [
'get_api_key',
'get_base_url',
'get_env_config',
'setup_system_logging',
'get_logger',
'AgileDBAPIClient',
'get_default_client',
]

View File

@@ -0,0 +1,196 @@
"""
数据库管理平台 API 调用客户端
用于调用数据库管理平台的所有 API 接口
"""
import httpx
import json
import os
from typing import Dict, Any, Optional
from .env_config import get_api_key, get_base_url
from .logger_config import get_logger
logger = get_logger(__name__)
# 默认超时配置(秒)
DEFAULT_TIMEOUT = 30.0
class AgileDBAPIClient:
"""数据库管理平台 API 客户端"""
def __init__(
self,
base_url: Optional[str] = None,
api_key: Optional[str] = None,
default_timeout: float = DEFAULT_TIMEOUT,
):
"""
初始化 API 客户端
Args:
base_url: API 基础 URL默认从环境变量 AGILE_DB_BASE_URL 读取)
api_key: API 密钥(默认从环境变量 AGILE_DB_API_KEY 读取)
default_timeout: 请求超时时间(秒),默认 30 秒
"""
if base_url is None:
base_url = get_base_url()
if api_key is None:
api_key = get_api_key()
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.default_timeout = default_timeout
self._client: Optional[httpx.Client] = None
logger.info(f"[客户端初始化] base_url={self.base_url}")
@property
def client(self) -> httpx.Client:
"""懒加载 HTTP 客户端"""
if self._client is None:
self._client = httpx.Client(timeout=self.default_timeout)
return self._client
def _get_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
"""获取请求头"""
headers = {
'Authorization': self.api_key if self.api_key.startswith('Bearer ') else f'Bearer {self.api_key}',
}
if extra_headers:
headers.update(extra_headers)
return headers
def _build_url(self, path: str) -> str:
"""构建完整 URL"""
if path.startswith('http://') or path.startswith('https://'):
return path
return f"{self.base_url}{path}"
def _handle_response(self, response: httpx.Response, url: str) -> Dict[str, Any]:
"""统一处理 API 响应"""
logger.info(f"[API响应] HTTP {response.status_code}")
if response.status_code == 204:
return {"success": True, "data": None}
response.raise_for_status()
try:
data = response.json()
except json.JSONDecodeError:
# 非 JSON 响应(如文件下载)
return {"success": True, "data": response.content, "raw": True}
# 检查平台 API 的 {code, msg} 格式
if isinstance(data, dict) and 'code' in data:
if data['code'] != 200:
error_msg = data.get('msg', '未知错误')
logger.error(f"[API错误] {error_msg}")
raise Exception(error_msg)
return data
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发送 GET 请求"""
url = self._build_url(path)
try:
logger.info(f"[API请求] GET {url}")
response = self.client.get(url, headers=self._get_headers(), params=params)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")
except httpx.HTTPStatusError as e:
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
except httpx.RequestError as e:
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
def post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发送 POST 请求"""
url = self._build_url(path)
try:
logger.info(f"[API请求] POST {url}")
headers = self._get_headers({'Content-Type': 'application/json'})
response = self.client.post(url, headers=headers, json=json_data, params=params)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")
except httpx.HTTPStatusError as e:
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
except httpx.RequestError as e:
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
def put(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发送 PUT 请求"""
url = self._build_url(path)
try:
logger.info(f"[API请求] PUT {url}")
headers = self._get_headers({'Content-Type': 'application/json'})
response = self.client.put(url, headers=headers, json=json_data, params=params)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")
except httpx.HTTPStatusError as e:
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
except httpx.RequestError as e:
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
def delete(self, path: str, params: Optional[Dict[str, Any]] = None, json_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发送 DELETE 请求"""
url = self._build_url(path)
try:
logger.info(f"[API请求] DELETE {url}")
headers = self._get_headers()
if json_data is not None:
headers['Content-Type'] = 'application/json'
response = self.client.delete(url, headers=headers, params=params, json=json_data)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")
except httpx.HTTPStatusError as e:
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
except httpx.RequestError as e:
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
def upload(self, path: str, files: Dict[str, Any], params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发送文件上传请求multipart/form-data"""
url = self._build_url(path)
try:
logger.info(f"[API请求] UPLOAD {url}")
# 文件上传不需要 Content-Typehttpx 会自动设置 multipart/form-data
headers = self._get_headers()
response = self.client.post(url, headers=headers, files=files, params=params)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")
except httpx.HTTPStatusError as e:
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
except httpx.RequestError as e:
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
def close(self):
"""关闭 HTTP 客户端"""
if self._client is not None:
self._client.close()
self._client = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
return False
# 懒加载的默认客户端
_default_client: Optional[AgileDBAPIClient] = None
def get_default_client() -> AgileDBAPIClient:
"""获取默认客户端(懒加载)"""
global _default_client
if _default_client is None:
_default_client = AgileDBAPIClient()
return _default_client

View File

@@ -0,0 +1,60 @@
"""环境变量配置模块 - 数据库管理平台 MCP Server"""
import os
from typing import Optional
def get_api_key(default: Optional[str] = None) -> str:
"""
获取数据库管理平台 API 密钥
Args:
default: 默认值(可选)
Returns:
str: API 密钥
Raises:
ValueError: 当 AGILE_DB_API_KEY 未设置且无默认值时
"""
value = os.environ.get("AGILE_DB_API_KEY", default or "")
if not value:
raise ValueError("环境变量 AGILE_DB_API_KEY 未设置")
return value
def get_base_url(default: str = "http://localhost:8080") -> str:
"""
获取数据库管理平台后端地址
Args:
default: 默认值(默认 http://localhost:8080
Returns:
str: 后端 API 基础 URL
"""
return os.environ.get("AGILE_DB_BASE_URL", default)
def get_env_config() -> dict:
"""
获取所有环境配置
Returns:
dict: 包含所有配置的字典
"""
return {
"api_key": get_api_key(""),
"base_url": get_base_url(),
}
def set_env_variable(key: str, value: str) -> None:
"""
设置环境变量(仅在当前进程中有效)
Args:
key: 环境变量名
value: 环境变量值
"""
os.environ[key] = value

View File

@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*-
"""
统一日志配置模块
提供系统级别的日志配置和管理
"""
import os
import sys
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
class LoggerConfig:
"""日志配置管理类"""
def __init__(self, logs_dir: str = None):
"""初始化日志配置
Args:
logs_dir: 日志目录路径默认为项目根目录下的logs文件夹
"""
if logs_dir:
self.logs_dir = Path(logs_dir)
else:
project_root = Path(__file__).parent.parent
self.logs_dir = project_root / "logs"
self.logs_dir.mkdir(exist_ok=True)
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
self.date_format = '%Y-%m-%d %H:%M:%S'
self.log_level = self._get_log_level_from_env()
self._initialized = False
def _get_log_level_from_env(self) -> int:
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
level_mapping = {
'DEBUG': logging.DEBUG,
'INFO': logging.INFO,
'WARNING': logging.WARNING,
'WARN': logging.WARNING,
'ERROR': logging.ERROR,
'CRITICAL': logging.CRITICAL,
'FATAL': logging.CRITICAL
}
return level_mapping.get(log_level_str, logging.INFO)
def setup_logging(self,
app_name: str = "lzwcai_mcp_agile_db",
log_level: int = logging.INFO,
max_file_size: int = 10 * 1024 * 1024,
backup_count: int = 5,
console_output: bool = True) -> logging.Logger:
if self._initialized:
return logging.getLogger()
root_logger = logging.getLogger()
root_logger.setLevel(log_level)
for handler in root_logger.handlers[:]:
root_logger.removeHandler(handler)
formatter = logging.Formatter(self.log_format, self.date_format)
# 1. 主日志文件 - 按大小滚动
main_log_file = self.logs_dir / f"{app_name}.log"
file_handler = RotatingFileHandler(
main_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
file_handler.setLevel(log_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
# 2. 错误日志文件
error_log_file = self.logs_dir / f"{app_name}_error.log"
error_handler = RotatingFileHandler(
error_log_file,
maxBytes=max_file_size,
backupCount=backup_count,
encoding='utf-8'
)
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(formatter)
root_logger.addHandler(error_handler)
# 3. 控制台输出 (MCP协议使用stdio时必须将日志输出到stderr)
if console_output:
console_handler = logging.StreamHandler(sys.stderr)
console_handler.setLevel(log_level)
console_formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
self.date_format
)
console_handler.setFormatter(console_formatter)
root_logger.addHandler(console_handler)
self._initialized = True
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
return root_logger
def get_module_logger(self, name: str) -> logging.Logger:
"""获取模块级别的 logger继承根配置"""
return logging.getLogger(name)
# 全局日志配置实例
logger_config = LoggerConfig()
def setup_system_logging(app_name: str = "lzwcai_mcp_agile_db",
log_level: int = logging.INFO) -> logging.Logger:
return logger_config.setup_logging(app_name, log_level)
def get_logger(name: str) -> logging.Logger:
return logger_config.get_module_logger(name)

View File

@@ -0,0 +1,621 @@
# 数据库管理平台 - MCP 工具设计方案
> 基于平台现有功能,设计可供外部 AI Agent 通过 MCP 协议调用的工具集。
> 目标:**用户脱离平台界面,通过 MCP 调用即可使用数据库管理平台的核心功能。**
---
## 一、设计原则
1. **按用户场景分组**:不是简单映射 API而是围绕用户真实工作流组织工具
2. **最小化调用链**:复杂操作尽量合并为一个 tool减少多轮调用
3. **读写分离**:查询类工具可安全暴露,写操作需明确提示
4. **环境感知**:所有操作默认携带环境参数(prod/test),内置数据源特有
5. **权限前置**:调用方需提供 apiKeyId 或等效凭证,工具内部自动鉴权
---
## 二、工具清单
### 🗄️ 数据源管理
#### 1. `list_datasources`
- **用途**:获取数据源列表,支持搜索和状态筛选
- **对应前端**DataSourceList.vue
- **对应 API**`getConnectionList` ✅ 已实现
- **端点**`GET /api/datasource/connection/list`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceName | string | 否 | 数据源名称模糊搜索 |
| status | int | 否 | 0=运行中, 1=已停止, 不传=全部 |
| sourceType | string | 否 | builtin/external |
| pageNum | int | 否 | 默认 1 |
| pageSize | int | 否 | 默认 20 |
**返回**:数据源列表(含名称、类型、状态、库数、表数、创建时间等)
---
#### 2. `get_datasource_detail`
- **用途**:获取单个数据源的完整详情
- **对应前端**DatabaseDetail.vue 头部
- **对应 API**
- `getConnectionDetail(id)` ✅ 已实现 — `GET /api/datasource/connection/{id}`
- `getConnectionConfig(id)` ✅ 已实现 — `GET /api/datasource/config/{id}`
- `getConnectionRealtimeStructure(id)` ✅ 已实现 — `GET /api/datasource/connection/realtime/structure/{id}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
**返回**:数据源详情 + 数据库配置列表 + 实时结构(数据库列表、表列表、字段信息)
---
#### 3. `create_datasource`
- **用途**:创建外部数据源连接
- **对应前端**CreateDataSource.vue
- **对应 API**
- `testConnection(data)` ✅ 已实现 — `POST /api/datasource/connection/test`
- `postConnectionDetail(data)` ✅ 已实现 — `POST /api/datasource/connection`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceName | string | 是 | 数据源名称3-20字 |
| datasourceType | string | 是 | mysql/postgresql/oracle/sqlserver/dameng |
| host | string | 是 | 数据库地址 |
| port | int | 是 | 端口号 |
| databaseName | string | 是 | 要连接的数据库名 |
| username | string | 是 | 数据库用户名 |
| password | string | 否 | 密码 |
| remark | string | 否 | 数据源描述 |
| connectionType | string | 否 | user_password/ssl默认 user_password |
| test_first | bool | 否 | 是否先测试连接,默认 true |
**返回**:创建结果,含数据源 ID
---
#### 4. `update_datasource`
- **用途**:更新数据源连接信息
- **对应 API**`putConnectionDetail(data)` ✅ 已实现 — `PUT /api/datasource/connection`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | 是 | 数据源 ID |
| datasourceName | string | 否 | 更新名称 |
| host | string | 否 | 更新地址 |
| port | int | 否 | 更新端口 |
| databaseName | string | 否 | 更新数据库名 |
| username | string | 否 | 更新用户名 |
| password | string | 否 | 新密码(不传则不变) |
| remark | string | 否 | 更新描述 |
**返回**:更新结果
---
#### 5. `toggle_datasource_status`
- **用途**:启用/停用数据源
- **对应 API**`putConnectionChangeStatus(data)` ✅ 已实现 — `PUT /api/datasource/connection/changeStatus`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | 是 | 数据源 ID |
| status | int | 是 | 0=启用, 1=停用 |
**返回**:操作结果
---
#### 6. `delete_datasource`
- **用途**:删除数据源(运行中会自动先停用)
- **对应 API**
- `putConnectionChangeStatus(data)` ✅ 已实现 — `PUT /api/datasource/connection/changeStatus`
- `deleteConnection(id)` ✅ 已实现 — `DELETE /api/datasource/connection/{id}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | 是 | 数据源 ID |
**返回**:删除结果
---
### 📊 数据库与表管理
#### 7. `list_databases`
- **用途**:获取指定数据源下的数据库列表
- **对应 API**`getConnectionConfigList(data)` ✅ 已实现 — `GET /api/datasource/config/list`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
**返回**数据库列表ID、名称、类型、状态、表数
---
#### 8. `list_tables`
- **用途**:获取指定数据库下的表列表
- **对应前端**DatabaseDetail.vue 左侧表列表
- **对应 API**`getTableList(data)` ✅ 已实现 — `GET /api/datasource/table/list`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| databaseName | string | 否 | 数据库名过滤 |
| keyword | string | 否 | 表名模糊搜索 |
| pageNum | int | 否 | 默认 1 |
| pageSize | int | 否 | 默认 20 |
**返回**:表列表(表名、注释、字段数、创建时间等)
---
#### 9. `get_table_detail`
- **用途**:获取表的完整结构信息
- **对应前端**DatabaseDetail.vue 右侧字段列表
- **对应 API**`getTableDetail(id)` ✅ 已实现 — `GET /api/datasource/table/{id}/detail`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
**返回**表名、表注释、字段列表字段名、类型、长度、主键、可空、默认值、注释、AI训练状态
---
#### 10. `create_table`
- **用途**:在指定数据库创建新表
- **对应前端**CreateBuiltinDataSource.vue 表结构编辑器
- **对应 API**`postCreateTable(connectionId, data)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/create_table`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| databaseName | string | 是 | 目标数据库名 |
| tableName | string | 是 | 表名(小写字母+数字+下划线) |
| tableComment | string | 否 | 表注释 |
| columns | array | 是 | 字段定义数组 |
| columns[].columnName | string | 是 | 字段名 |
| columns[].columnType | string | 是 | 字段类型VARCHAR/INTEGER/SERIAL等 |
| columns[].columnLength | int | 否 | 字段长度 |
| columns[].isPrimaryKey | bool | 否 | 是否主键 |
| columns[].isNullable | bool | 否 | 是否可空 |
| columns[].isAutoIncrement | bool | 否 | 是否自增 |
| columns[].columnComment | string | 否 | 字段注释 |
| columns[].defaultValue | string | 否 | 默认值 |
**返回**:创建结果,含表 ID
---
#### 11. `alter_table`
- **用途**:修改已有表结构(增/改/删字段)
- **对应 API**`putAlterTable(connectionId, data)` ✅ 已实现 — `PUT /api/datasource/connection/{connectionId}/alter_table`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| columns | array | 是 | 字段变更数组 |
| columns[].operation | string | 是 | ADD_COLUMN / MODIFY_COLUMN / DROP_COLUMN |
| columns[].columnName | string | 是 | 字段名 |
| columns[].columnType | string | 否 | 字段类型ADD/MODIFY |
| columns[].columnLength | int | 否 | 字段长度 |
| columns[].isPrimaryKey | bool | 否 | 是否主键 |
| columns[].isNullable | bool | 否 | 是否可空 |
| columns[].columnComment | string | 否 | 字段注释 |
| columns[].defaultValue | string | 否 | 默认值 |
| newTableName | string | 否 | 新表名(重命名) |
| newTableComment | string | 否 | 新表注释 |
**返回**:修改结果
---
#### 12. `generate_table_by_description`
- **用途**:通过自然语言描述让 AI 生成表结构
- **对应前端**CreateBuiltinDataSource.vue AI 生成表结构
- **对应 API**`postGenerateTable(data)` ✅ 已实现 — `POST /api/datasource/connection/generate_table`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| description | string | 是 | 业务场景描述至少6个字符 |
**返回**AI 生成的表结构(表名、表注释、字段列表含类型/主键/注释等)
> **场景示例**:用户说"我需要一个商城系统,管理商品、分类和用户评价"AI 返回完整的表结构设计。
---
### 📝 表数据操作 (内置数据源 CRUD)
#### 13. `query_table_data`
- **用途**:查询内置表数据(分页)
- **对应前端**CustomizeDbTable.vue 线上/调试数据视图
- **对应 API**`getBuiltinTableData(tableId, params)` ✅ 已实现 — `GET /api/datasource/connection/builtin/table/{tableId}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| target | string | 否 | prod/test默认 prod |
| pageNum | int | 否 | 默认 1 |
| pageSize | int | 否 | 默认 10 |
**返回**:表结构信息 + 数据行(二维数组转换后的对象数组) + 总数
---
#### 14. `insert_table_row`
- **用途**:向内置表插入一行数据
- **对应 API**`postBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `POST /api/datasource/connection/builtin/table/{tableId}/rows`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| target | string | 否 | prod/test默认 prod |
| data | object | 是 | 行数据(键值对,键为字段名) |
**返回**:插入结果
---
#### 15. `update_table_row`
- **用途**:更新内置表的指定行
- **对应 API**`putBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `PUT /api/datasource/connection/builtin/table/{tableId}/rows`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| target | string | 否 | prod/test默认 prod |
| primaryKey | object | 是 | 主键值(如 {"id": 1} |
| data | object | 是 | 要更新的字段值 |
**返回**:更新结果
---
#### 16. `delete_table_rows`
- **用途**:删除内置表的指定行
- **对应 API**`deleteBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `DELETE /api/datasource/connection/builtin/table/{tableId}/rows`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| target | string | 否 | prod/test默认 prod |
| primaryKeys | array | 是 | 主键数组(如 [{"id": 1}, {"id": 2}] |
**返回**:删除结果
---
#### 17. `export_table_excel`
- **用途**:导出表数据为 Excel 文件
- **对应 API**`getBuiltinTableExportExcel(tableId, params)` ✅ 已实现 — `GET /api/datasource/connection/builtin/table/{tableId}/export/excel`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| target | string | 否 | prod/test默认 prod |
**返回**Excel 文件(二进制 blob+ 文件名(从 Content-Disposition 解析)
---
### 🔑 API 密钥与权限管理
#### 18. `list_api_keys`
- **用途**:获取 API 密钥列表
- **对应前端**DataSourceKeys.vue
- **对应 API**`getApiKeyList(data)` ✅ 已实现 — `GET /api/datasource/api_key/list`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiKeyName | string | 否 | 密钥名称模糊搜索 |
| pageNum | int | 否 | 默认 1 |
| pageSize | int | 否 | 默认 20 |
**返回**密钥列表名称、Key、状态、创建时间
---
#### 19. `create_api_key`
- **用途**:创建新的 API 密钥
- **对应 API**`postApiKey(data)` ✅ 已实现 — `POST /api/datasource/api_key`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiKeyName | string | 是 | 密钥名称最多50字 |
**返回**:密钥信息(含 API Key 明文、ID
---
#### 20. `toggle_api_key_status`
- **用途**:启用/禁用 API 密钥
- **对应 API**`putApiKey(data)` ✅ 已实现 — `PUT /api/datasource/api_key`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | 是 | 密钥 ID |
| status | int | 是 | 0=启用, 1=禁用 |
**返回**:操作结果
---
#### 21. `delete_api_key`
- **用途**:删除 API 密钥
- **对应 API**`deleteApiKey(ids)` ✅ 已实现 — `DELETE /api/datasource/api_key/{ids}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | 是 | 密钥 ID |
**返回**:删除结果
---
#### 22. `get_api_key_permissions`
- **用途**:查看指定密钥的权限配置
- **对应 API**`getApiKeyPermission(apiKeyId)` ✅ 已实现 — `GET /api/datasource/api_key/permission/{apiKeyId}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiKeyId | string | 是 | 密钥 ID |
**返回**三级权限connectionPermissions / databasePermissions / tablePermissions
---
#### 23. `grant_api_key_permissions`
- **用途**:批量为 API 密钥授予权限
- **对应前端**DataSourceKeySetting.vue
- **对应 API**`postApiKeyPermissionGrantBatch(data)` ✅ 已实现 — `POST /api/datasource/api_key/permission/grant_batch`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| apiKeyId | string | 是 | 密钥 ID |
| batchDatas | array | 是 | 权限批量数据数组 |
| batchDatas[].connectionId | string | 是 | 数据源 ID |
| batchDatas[].permissionLevel | string | 是 | connection/database/table |
| batchDatas[].permissionType | string | 是 | 权限类型(逗号分隔) |
| batchDatas[].databaseName | string | 否 | 数据库名level=database/table 时) |
| batchDatas[].tableName | string | 否 | 表名level=table 时) |
**返回**:授权结果
---
### 🤖 技能与工具管理 (内置数据源 AI 能力)
#### 24. `get_skill_by_datasource`
- **用途**:根据数据源获取技能信息
- **对应 API**`getSkillByDatasource(id)` ✅ 已实现 — `GET /api/datasource/skill/getByDatasource/{id}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
**返回**:技能信息(含 skillBool 标识)
---
#### 25. `get_skill_tools`
- **用途**:获取技能下的工具列表
- **对应 API**`getSkillBySkillId(id)` ✅ 已实现 — `GET /api/datasource/skill/getBySkillId/{id}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| skillId | string | 是 | 技能 ID |
**返回**工具列表名称、描述、SQL模板、参数定义、业务场景
---
#### 26. `create_skill`
- **用途**:为数据源创建技能
- **对应 API**`postSkillCreateOrGet(data)` ✅ 已实现 — `POST /api/datasource/skill/createOrGet`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| name | string | 否 | 技能名称(不传则自动生成) |
| description | string | 否 | 技能描述 |
**返回**:技能 ID
---
#### 27. `create_sql_tool`
- **用途**:将 SQL 查询创建为可复用工具
- **对应前端**SqlControllerMsg.vue 添加到工具功能
- **对应 API**`postSqlSkillConfirmTools(data)` ✅ 已实现 — `POST /api/datasource/skill/confirmTools`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| skillId | string | 是 | 技能 ID |
| name | string | 是 | 工具名称 |
| businessDescription | string | 是 | 业务描述 |
| sqlTemplate | string | 是 | SQL 模板(支持 #{param} 参数占位) |
| sqlParams | string | 否 | 参数 JSON Schema默认空对象 |
| resultType | string | 否 | single/list默认 list |
| businessScenario | string | 否 | 业务场景描述 |
**返回**:工具创建结果
---
#### 28. `delete_skill_tool`
- **用途**:删除技能下的工具
- **对应 API**`postDeleteSkillTool(skillToolId)` ✅ 已实现 — `DELETE /api/datasource/skill/tskilltool/{skillToolId}`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| skillToolId | string | 是 | 工具 ID |
**返回**:删除结果
---
#### 29. `update_skill_config`
- **用途**:更新技能配置(如 MCP Server 配置模板)
- **对应 API**`putSkillUpdateOrGet(data)` ✅ 已实现 — `POST /api/datasource/skill/updateOrGet`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| configTemplate | string | 是 | 配置模板 JSON 字符串 |
**返回**:更新结果
---
### 📥 数据导入
#### 30. `preview_import_data`
- **用途**:上传 Excel 文件AI 智能识别并预览表结构/数据
- **对应前端**TableRecognition.vue
- **对应 API**`postImportDocumentPreview(connectionId, file, target)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/import_document/preview`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| connectionId | int | 是 | 数据源 ID |
| target | string | 否 | prod/test默认 test |
| file | binary | 是 | Excel 文件 (.xlsx/.xls, <500KB) |
**返回**:识别结果(表结构 + 数据预览)
---
#### 31. `confirm_import_data`
- **用途**:确认导入 AI 识别后的数据
- **对应 API**`postImportDocumentConfirm(connectionId, data, params)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/import_document/confirm`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| connectionId | int | 是 | 数据源 ID |
| target | string | 否 | prod/test |
| data | object | 是 | 导入数据(含 tableStructure + allData |
**返回**:导入结果
---
### 🏷️ 表订阅
#### 32. `toggle_table_subscription`
- **用途**:切换表的订阅状态
- **对应前端**DatabaseDetail.vue 订阅按钮
- **对应 API**`postDatasourceSubscriptionToggle(data)` ✅ 已实现 — `POST /api/datasource/subscription/toggle`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| tableId | string | 是 | 表 ID |
| datasourceId | string | 是 | 数据源 ID |
| subscribe | bool | 是 | true=订阅, false=取消订阅 |
**返回**:操作结果
---
### 🔧 SQL 执行
#### 33. `execute_sql`
- **用途**:执行原生 SQL 查询
- **对应 API**`executeSql(data)` ✅ 已实现 — `POST /api/datasource/sqlExecutionLog/testSqlWithSchema`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| sql | string | 是 | SQL 语句 |
| target | string | 否 | prod/test默认 prod |
| params | object | 否 | 参数对象 |
**返回**:查询结果(表头、数据行、业务名称、描述等)
---
## 三、推荐使用场景
### 场景 1外部 AI Agent 管理数据库
```
用户: "帮我查一下有哪些数据源"
Agent: 调用 list_datasources()
用户: "看看 mall_db 有哪些表"
Agent: 调用 list_tables(datasourceId="xx", databaseName="mall_db")
用户: "users 表结构给我看一下"
Agent: 调用 get_table_detail(tableId="xx")
```
### 场景 2通过 AI 描述自动生成表结构
```
用户: "我需要一个订单系统,包含订单、订单明细、支付方式"
Agent: 调用 generate_table_by_description(description="我需要一个订单系统...")
Agent: 返回 AI 生成的表结构,用户确认后调用 create_table() 批量创建
```
### 场景 3管理 API 密钥和权限
```
用户: "帮我创建一个叫'第三方报表系统'的 API Key"
Agent: 调用 create_api_key(apiKeyName="第三方报表系统")
用户: "给它开通 mall_db 数据库的读取权限"
Agent: 调用 grant_api_key_permissions(apiKeyId="xx", batchDatas=[{...}])
```
### 场景 4管理表数据
```
用户: "查一下 users 表前 10 条数据"
Agent: 调用 query_table_data(tableId="xx", pageNum=1, pageSize=10)
用户: "新增一个用户,用户名是 test_user"
Agent: 调用 insert_table_row(tableId="xx", data={"user_name": "test_user", ...})
```
### 场景 5创建 SQL 工具
```
用户: "把这个查询保存为工具,叫'按地区统计订单'"
Agent: 调用 create_sql_tool(skillId="xx", name="按地区统计订单",
sqlTemplate="SELECT region, COUNT(*) FROM orders GROUP BY region", ...)
```
### 场景 6导入 Excel 数据
```
用户: "帮我导入这份 Excel 到测试环境"
Agent: 调用 preview_import_data(connectionId=xx, target="test", file=...)
Agent: 展示识别结果,用户确认后调用 confirm_import_data(...)
```
---
## 四、工具优先级建议
| 优先级 | 工具 | 理由 |
|--------|------|------|
| **P0 核心** | list_datasources, list_tables, get_table_detail, execute_sql, query_table_data | 覆盖 80% 查询场景 |
| **P1 常用** | create_datasource, create_table, generate_table_by_description, create_api_key, grant_api_key_permissions | 管理核心操作 |
| **P2 扩展** | insert/update/delete_table_row, export_table_excel, create_sql_tool, toggle_table_subscription | 数据操作与 AI 能力 |
| **P3 完整** | preview/confirm_import_data, update_skill_config, alter_table, delete_skill_tool | 高级功能 |
---
## 五、实现建议
1. **MCP Server 实现**:建议用 Python + `mcp` 库或 Node.js + `@modelcontextprotocol/sdk`
2. **鉴权方式**:工具内部复用平台现有的 API Key 鉴权机制,调用方传入 apiKeyId
3. **错误处理**:统一错误格式 `{ success: false, error: "描述" }`,与平台 API 拦截器保持一致
4. **环境默认值**:所有 target 参数默认 prod调用方显式指定 test 才能操作测试数据
5. **批量操作**:对于 create_table 等可能涉及多表的场景,支持批量参数或循环调用
6. **AI 工具调用**generate_table_by_description 等 AI 工具需要调用平台的 AI 服务接口agent generic / aiTextGenerator
7. **前端 API 映射**:所有 33 个 MCP 工具均已在前端 `src/server/database.ts` 中实现对应函数,可直接复用

View File

@@ -0,0 +1,303 @@
# 数据库管理平台 - 功能总览
> 本文档梳理 `databasePage` 组件集群与 `DatabaseDetailPage` 的整体功能架构。
---
## 一、系统定位
这是一个面向业务用户的**数据库管理与智能问数一体化平台**,支持两种数据源模式:
| 模式 | 说明 | 核心差异 |
|------|------|----------|
| **外部数据源** | 远程 MySQL/PostgreSQL/Oracle/SQL Server/达梦 等 | 标准数据库连接,提供只读/CRUD 管理 |
| **内置数据源** | 平台内建 PostgreSQL 实例 | 支持 AI 技能、工具、环境切换(prod/test)、AI 训练 |
---
## 二、功能模块全景
### 1. 数据源管理
#### 1.1 数据源列表 (`DataSourceList.vue`)
- **卡片式展示**名称、ID、类型徽章(内置/外部)、数据库类型、运行状态、库数/表数统计
- **无限滚动分页**IntersectionObserver + useInfiniteScroll每页 20 条
- **搜索与筛选**:按名称模糊搜索、按状态筛选(运行中/已停止)
- **数据源操作**
- 编辑(外部→CreateDataSource, 内置→CreateBuiltinDataSource)
- 启用/停用(切换 status 0/1)
- 删除(运行中自动先停用再删除)
- **业务执行入口**:点击打开智能问数聊天界面 (ChatDebugging)
- **查看详情入口**:右侧 Drawer 打开 DatabaseDetail (95% 宽度)
#### 1.2 外部数据源创建/编辑 (`CreateDataSource.vue`) - 4步向导
| 步骤 | 内容 | 核心功能 |
|------|------|----------|
| Step 1 | 选择数据库类型 | MySQL/PostgreSQL/Oracle/SQL Server/达梦,自动填充默认端口 |
| Step 2 | 配置连接信息 | 名称、描述(AI润色)、主机、端口、认证(基础认证/SSL)、用户名密码(AI生成名称)、测试连接 |
| Step 3 | 选择数据库和表 | 树形展开、多选、搜索过滤、分页加载表列表、全选/清空 |
| Step 4 | 确认信息 | 汇总展示已选数据库和表标签 |
#### 1.3 内置数据源创建/编辑/添加表 (`CreateBuiltinDataSource.vue`)
| 模式 | 步骤 | 说明 |
|------|------|------|
| create | 名称描述→业务场景→表结构 | AI 生成表结构,批量创建数据库和表 |
| edit | 名称描述→表结构 | 更新已有内置数据源 |
| addTable | 业务场景→表结构 | 在已有内置数据源下新增表 |
- **AI 能力**AI 优化描述、AI 生成数据库名、AI 生成表名/表描述/字段描述
- **表结构编辑器**:左右分栏(左侧表列表, 右侧 TableDetailEditor 预览/编辑)
- **提交流程**:创建数据源 → 创建 PostgreSQL 连接 → 创建数据库 → 批量建表 → 检查 AI 训练状态
### 2. 数据库详情 (`DatabaseDetail.vue`)
左侧面板 + 右侧面板的经典布局:
#### 2.1 左侧 - 数据库与表管理
- **数据库选择器**:下拉切换当前数据库,悬浮显示数据库详情(ID/名称/数据源/类型)
- **数据表列表**
- 搜索过滤
- 多选/全选 (Checkbox)
- 无限滚动加载
- AI 补全(选中多个表批量触发)
- 更多操作: AI智能建表、智能导入表、删除库
- **底部功能按钮**字段关联管理、AI补全管理
#### 2.2 右侧 - 详情视图(三种视图)
**视图 A: 表详情 (`fieldViewMode = fields`)**
- 字段列表表格: 字段名、类型、长度、可空、主键、默认值、注释、描述
- AI 训练状态徽章(已训练/未训练)
- 操作: 修改表、字段关联管理、表订阅(已订阅/未订阅)、刷新
- 用户 ID 字段关联提示(user_id/createById 等自动关联系统用户)
**视图 B: 线上数据 (`fieldViewMode = online`)**
- 通过 `CustomizeDbTable` 组件查看/编辑生产环境真实数据
- 支持增删改查、导出 Excel、智能导入数据
**视图 C: 调试数据 (`fieldViewMode = debug`)**
- 同上,但操作测试环境数据
#### 2.3 右侧 - 字段关联管理视图
- 为当前表的字段建立与目标表字段的映射关系
- 目标表数据更新时自动同步到当前表
- 支持保存配置、添加/删除关联
#### 2.4 右侧 - AI 补全管理视图
- AI 补全任务列表: 任务名、类型、状态(待执行/运行中/成功/失败)、进度
- 任务详情弹窗: 训练结果(格式化代码展示)、耗时、场景描述
- 支持重新执行、删除任务
### 3. 内建表数据管理 (`CustomizeDbTable.vue`)
- 基于 `CommonDbTable` 的完整 CRUD 表格
- 动态列配置: 根据表结构自动生成字段类型、宽度、可编辑性
- **操作能力**: 新增行、编辑行、删除行、导出 Excel、导入数据、刷新
- **导入流程**: 下载模板 → 上传 Excel → AI 识别预览 → 确认导入(调用 postImportDocumentConfirm)
- 环境切换: `target` prop 控制 prod/test
### 4. 智能导入识别 (`TableRecognition.vue`)
- **Step 1: 上传文件**: 选择目标环境(prod/test),上传 Excel (.xlsx/.xls, <500KB)
- **Step 2: 智能识别**: AI 解析 Excel预览表结构/数据,支持编辑
- **两种模式**:
- `table` 模式: 根据 Excel 内容自动生成数据表结构
- `data` 模式: 根据规范模板识别并导入数据
- 空行过滤、字段标记、模板下载
### 5. 智能问数 / 业务执行 (`ChatDebugging.vue`)
- **三栏布局**: 左侧面板(可折叠) + 中间聊天区 + 右侧信息
- **左侧面板**:
- 数字员工列表(可收起)
- 新建会话按钮
- 会话列表(点击切换、删除)
- 技能列表(展开/折叠)
- 技能名称 + 编辑按钮
- 工具列表(名称、别名、编辑、删除)
- 至少保留一个工具
- **中间聊天区** (`ChatBusiness` 组件):
- 数据源插件嵌入输入区 (`DataSourcePlugIn`)
- 数据库下拉选择
- 环境切换(生产/测试,仅内置数据源)
- SQL 消息控制器 (`SqlControllerMsg`)
- 默认数据库查询提示词(常用查询/数据统计/新手指引)
#### 5.1 SQL 查询结果展示 (`SqlControllerMsg.vue`)
| 视图类型 | 说明 |
|----------|------|
| SQL 代码 | 展示可执行 SQL 语句,支持复制 |
| 图表可视化 | ChartGallery 组件渲染图表 |
| 查询数据 | JSON 格式原始数据,支持复制 |
| 文本说明 | 业务名称、描述、AI 解释 |
| 表格视图 | Ant Design Table 展示查询结果 |
| 添加到工具 | 将 SQL 查询保存为可复用工具 |
- **添加工具功能**:
- 重复检测(按 sqlTemplate 归一化比对)
- 仅生产环境可用
- 技能创建/更新流程(无技能时自动创建 skill + MCP 配置)
- 通过 EventBus 触发技能刷新
#### 5.2 数据源选择插件 (`DataSourcePlugIn.vue`)
- 紧凑的内联选择器: 数据源名称 → 数据库下拉 → 环境(prod/test)
- 监听 props 变化自动重置无效选择
- 暴露 getSelection/clearSelection/setEnvironment 方法
### 6. API 密钥管理 (`DataSourceKeys.vue` + `DataSourceKeySetting.vue`)
- **密钥列表**: 名称、API Key(脱敏显示/可切换显示)、状态开关(启用/禁用)、创建时间、操作(详情/编辑/删除)
- **两步创建流程**:
1. 输入密钥名称 → 调用 postApiKey 创建
2. 弹窗询问是否配置权限 → 打开权限配置
- **权限配置** (`DataSourceKeySetting.vue`) - 3步向导:
- Step 1: 选择数据源(单选) + 配置数据源级权限
- Step 2: 选择数据库(单选, 非必填) + 配置数据库级权限
- Step 3: 选择数据表(多选, 非必填) + 行内配置表级权限
- Step 4: 完成页(ApiKeyPermissionPreview 汇总) → 批量提交权限
- **权限选项**: 按层级不同(connection/database/table),每种有对应的读/写/管理等权限
- **API 调用文档**: 切换到文档视图查看调用说明
### 7. 表字段编辑器 (`TableDetailEditor.vue`)
- **表信息编辑**: 表名(AI生成)、表描述(AI优化)
- **字段列表表格**:
- 序号、字段名、字段类型(分组下拉)、长度、主键、自增、可空、默认值、注释
- AI 生成字段描述(逐字段)
- 添加字段(首个字段自动设为主键+自增)
- 删除字段(有id的字段记录到 deletedColumns)
- 主键设置后不可取消(已有字段)
- 自增字段自动切换为整数类型
- **两种模式**: `edit` (编辑) / `preview` (预览)
- **校验功能**: validateFields 供外部调用
- **字段类型** (面向 PostgreSQL): VARCHAR/TEXT/SERIAL/INTEGER/BIGINT/NUMERIC/TIMESTAMP/BOOLEAN/JSONB/INT2/INT4/INT8/SMALLINT/SMALLSERIAL/BIGSERIAL/BOOL/BIGSERIAL
### 8. 其他辅助组件
| 组件 | 功能 |
|------|------|
| `ChartGallery.vue` | 图表可视化渲染 |
| `AddToolModal.vue` | 添加工具弹窗(工具名、描述编辑+AI辅助) |
| `ApiKeyPermissionPreview.vue` | 权限汇总预览组件 |
| `ApiCallDocument.vue` | API 调用文档展示 |
| `DatabaseMessageList.vue` | 数据库消息列表 |
| `field-types-dictionary.json` | 字段类型字典 |
---
## 三、API 接口层 (server/database.ts)
### 数据源管理
| 函数 | 方法 | 用途 |
|------|------|------|
| `getConnectionList` | GET | 数据源列表(分页+搜索+筛选) |
| `getConnectionDetail` | GET | 数据源详情 |
| `postConnectionDetail` | POST | 创建数据源 |
| `putConnectionDetail` | PUT | 更新数据源 |
| `deleteConnection` | DELETE | 删除数据源 |
| `putConnectionChangeStatus` | PUT | 切换数据源状态(启用/停用) |
| `testConnection` | POST | 测试连接 |
### 内置数据库管理
| 函数 | 方法 | 用途 |
|------|------|------|
| `postCreateBuiltinPostgreSQLConnection` | POST | 创建内置 PostgreSQL 连接 |
| `postCreateDatabase` | POST | 创建数据库 |
| `putAlterDatabase` | PUT | 修改数据库 |
| `postCreateTable` | POST | 创建表 |
| `putAlterTable` | PUT | 修改表 |
| `postGenerateTable` | POST | AI 生成表结构 |
| `putUpdateBuiltinDatabase` | PUT | 更新内置数据库 |
### 数据库配置
| 函数 | 方法 | 用途 |
|------|------|------|
| `getConnectionConfig` | GET | 获取数据库配置(含 skillBool) |
| `getConnectionConfigList` | GET | 数据库配置列表 |
| `postConnectionConfig` | POST | 创建数据库配置 |
| `putConnectionConfig` | PUT | 更新数据库配置 |
| `deleteConnectionConfig` | DELETE | 删除数据库配置 |
| `getConnectionRealtimeStructure` | GET | 获取实时数据库结构 |
### 表数据管理
| 函数 | 方法 | 用途 |
|------|------|------|
| `getTableList` | GET | 表列表 |
| `getTableDetail` | GET | 表详情 |
| `getBuiltinTableData` | GET | 获取内置表数据(分页) |
| `postBuiltinTableRows` | POST | 新增表数据行 |
| `putBuiltinTableRows` | PUT | 更新表数据行 |
| `deleteBuiltinTableRows` | DELETE | 删除表数据行 |
| `getBuiltinTableExportExcel` | GET | 导出表数据为 Excel |
### 数据导入
| 函数 | 方法 | 用途 |
|------|------|------|
| `postImportDocumentPreview` | POST | 上传 Excel 预览识别结果 |
| `postImportDocumentConfirm` | POST | 确认导入数据 |
### 技能与工具
| 函数 | 方法 | 用途 |
|------|------|------|
| `getSkillByDatasource` | GET | 根据数据源获取技能 |
| `getSkillBySkillId` | GET | 根据技能ID获取工具列表 |
| `postSkillCreateOrGet` | POST | 创建或获取技能 |
| `putSkillUpdateOrGet` | PUT | 更新技能配置 |
| `postSkillToolUpdateOrGet` | POST | 创建/更新技能工具 |
| `postDeleteSkillTool` | POST | 删除技能工具 |
| `postSqlSkillConfirmTools` | POST | 确认并创建 SQL 工具 |
### AI 训练
| 函数 | 方法 | 用途 |
|------|------|------|
| `getAiTrainingList` | GET | AI 训练任务列表 |
| `postAiTrainingCreateBySelected` | POST | 创建 AI 训练任务 |
| `getAiTrainingDetail` | GET | 训练任务详情 |
### API 密钥
| 函数 | 方法 | 用途 |
|------|------|------|
| `getApiKeyList` | GET | 密钥列表 |
| `postApiKey` | POST | 创建密钥 |
| `putApiKey` | PUT | 更新密钥 |
| `deleteApiKey` | DELETE | 删除密钥 |
| `getApiKeyPermission` | GET | 获取密钥权限 |
| `postApiKeyPermissionGrantBatch` | POST | 批量授予权限 |
### 订阅
| 函数 | 方法 | 用途 |
|------|------|------|
| `postDatasourceSubscriptionToggle` | POST | 切换表订阅状态 |
### SQL 执行
| 函数 | 方法 | 用途 |
|------|------|------|
| `executeSql` | POST | 执行 SQL 查询 |
---
## 四、状态管理与通信
- **Pinia Store**: `useDatabaseChatStore` 管理选中数据库、环境、技能状态
- **EventBus**: `EVENTS.RELOAD_SKILL_DATA` 用于工具添加后刷新技能数据
- **权限控制**: `hasPermission()` 函数按权限标识控制按钮可见性
## 五、权限标识体系
| 权限标识 | 控制范围 |
|----------|----------|
| `database:create` | 创建数据源 |
| `database:edit` | 编辑数据源 |
| `database:delete` | 删除数据源 |
| `database:import` | 导入表/数据 |
| `database:export` | 导出表 |
| `database:table:create` | AI 智能建表 |
| `database:table:ai:complete` | AI 补全功能 |
| `database:apikey:view` | 查看密钥管理 |
| `database:apikey:create` | 创建密钥 |
---
## 六、关键技术特点
1. **AI 深度集成**: 描述润色、名称生成、表结构自动生成、字段描述生成、AI 补全、AI 训练
2. **双环境隔离**: 生产/测试环境切换,测试环境限制添加工具等敏感操作
3. **无限滚动**: IntersectionObserver + 哨兵元素实现列表分页加载
4. **三级权限体系**: 数据源 → 数据库 → 数据表,通过 API Key 控制访问粒度
5. **MCP 工具集成**: 内置数据源自动配置 MCP Server (`lzwcai-mcp-sqlexecutor`),支持 AI 工具注册
6. **Excel 智能导入**: AI 识别 Excel 内容,自动匹配表结构,支持表结构和数据两种导入模式

View File

@@ -0,0 +1,12 @@
"""
Entry point for lzwcai-mcp-agile-db
Runs the MCP server for database management platform
"""
from lzwcai_mcp_agile_db.server import main
import os
if __name__ == "__main__":
os.environ["AGILE_DB_API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg"
os.environ["AGILE_DB_BASE_URL"] = "https://dempdemo.lzwcai.com"
main()

View File

@@ -0,0 +1,34 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "lzwcai-mcp-agile-db"
version = "0.1.0"
description = "MCP server for database management platform with 33 tools for datasource, table, data, API key, and skill management"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
authors = [
{name = "lzwcai", email = "your-email@example.com"},
]
keywords = ["mcp", "database", "agile", "server"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.10.1",
]
[project.scripts]
lzwcai-mcp-agile-db = "lzwcai_mcp_agile_db.server:main"
[tool.hatch.build.targets.wheel]
packages = ["lzwcai_mcp_agile_db"]

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +1,289 @@
2026-05-25 14:32:22 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs 2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 14:32:22 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5 2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:304] - ============================================================ 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:307] - ============================================================ 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 162 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2008360664955854850 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:314] - ============================================================ 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接... 2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
2026-05-25 14:32:35 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest 2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:156] - 收到列出工具请求 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api... 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:283] - 调用第三方APIskill_id: 2008360664955854850 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方APIskill_id: 2058819964077572098
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850 2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 14:32:35 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850 "HTTP/1.1 200 " 2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850 2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2008360824029028354', 'createBy': 'wxl06', 'createTime': '2026-01-06 10:11:49', 'updateBy': 'wxl06', 'updateTime': '2026-01-06 10:11:59', 'serviceId': '2008360664960049153', 'uniqueName': '查询订单信息', 'name': 'chaxundingdanxinxi_3acda9b4', 'description': 'chaxundingdanxinxi_3acda9b4: 查询订单表中的订单信息,包括订单号、产品名称、数量、购买客户、备注和状态等关键信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 4ms', 'toolType': 'sql', 'datasourceId': '162', 'sqlTemplate': 'SELECT order_id, product_name, quantity, customer, remarks, status FROM orders WHERE 1=1', 'sqlParams': '{"type":"object","required":[],"properties":{}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["employeeId"],"properties":{"employeeId":{"type":"number","description":"员工ID用于标识员工的唯一数字标识符","examples":[1001,2002]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}]} 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据 2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:128] - API配置: 1 条 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2008360824029028354', 'businessName': 'chaxundingdanxinxi_3acda9b4', 'businessDescription': 'chaxundingdanxinxi_3acda9b4: 查询订单表中的订单信息,包括订单号、产品名称、数量、购买客户、备注和状态等关键信息', 'sqlTemplate': 'SELECT order_id, product_name, quantity, customer, remarks, status FROM orders WHERE 1=1', 'parameters': {'type': 'object', 'required': [], 'properties': {}}, 'datasourceId': '162'}] 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具 2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: {
"datasourceId": "240",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"parameters": {},
"testParams": {}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema
2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 "
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据:
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: <class 'dict'>
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: {
"msg": "操作成功",
"code": 200,
"data": {
"resultCount": 6,
"data": [
{
"记录ID": 80001,
"任务ID": "3320260418001",
"原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:19.000+08:00"
},
{
"记录ID": 80002,
"任务ID": "3320260418002",
"原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:34.000+08:00"
},
{
"记录ID": 80003,
"任务ID": "3320260418003",
"原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}",
"处理结果": "导入失败",
"错误原因": "字段校验失败mobile格式不正确",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:19:49.000+08:00"
},
{
"记录ID": 80004,
"任务ID": "3320260418004",
"原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:04.000+08:00"
},
{
"记录ID": 80005,
"任务ID": "3320260418005",
"原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}",
"处理结果": "导入失败",
"错误原因": "目标表写入失败:唯一键冲突",
"处理状态": "FAILED",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:19.000+08:00"
},
{
"记录ID": 80006,
"任务ID": "3320260418006",
"原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}",
"处理结果": "导入成功",
"错误原因": null,
"处理状态": "SUCCESS",
"关联目标表": "employee_import",
"创建时间": "2026-04-18T15:20:34.000+08:00"
}
],
"databaseName": "import_log_db_168",
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。",
"originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"executionStatus": "success",
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
"testParams": {},
"errorMessage": null,
"executionTime": 8,
"datasourceId": "240",
"logId": "2058821884796174336",
"executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
"datasourceName": "import_log_db_168"
}
}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'}
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id:
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map):
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg)
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 APIskill_id: 2058819964077572098
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录包含原始数据、处理状态、错误原因等关键信息用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具

View File

@@ -1,223 +0,0 @@
2025-12-31 12:57:27 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2025-12-31 12:57:27 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:299] - ============================================================
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:302] - ============================================================
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:309] - ============================================================
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
2025-12-31 12:57:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:278] - 调用第三方APIskill_id:
2025-12-31 12:57:28 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/
2025-12-31 12:57:29 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/ "HTTP/1.1 404 "
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"}
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id
response.raise_for_status()
File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status
raise HTTPStatusError(message, request=request, response=self)
httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id
raise Exception(error_msg)
Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
2025-12-31 12:57:29 - mcp_services - WARNING - [main.py:131] - API获取失败降级使用本地配置: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
2025-12-31 12:57:29 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 12:57:29 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:54 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2025-12-31 12:57:54 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:299] - ============================================================
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:302] - ============================================================
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:309] - ============================================================
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
2025-12-31 12:57:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local...
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:123] - 本地配置: 3 条
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:31 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 15:00:31 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭
2025-12-31 15:00:53 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
2025-12-31 15:00:53 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:299] - ============================================================
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:302] - ============================================================
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 29
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:309] - ============================================================
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
2025-12-31 15:00:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api...
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:278] - 调用第三方APIskill_id:
2025-12-31 15:00:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request
raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request
response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request
raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request
stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect
stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp
with map_exceptions(exc_map):
^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
raise to_exc(exc) from exc
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id
response = self.client.get(
^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request(
^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
return self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
response = self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
response = self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
response = self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
response = transport.handle_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions():
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id
raise Exception(error_msg)
Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 15:00:56 - mcp_services - WARNING - [main.py:131] - API获取失败降级使用本地配置: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:55] - 成功加载 0 个业务查询配置
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具
2025-12-31 15:00:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具

View File

@@ -1,110 +1,26 @@
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/ 2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"} 2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id
response.raise_for_status()
File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status
raise HTTPStatusError(message, request=request, response=self)
httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id
raise Exception(error_msg)
Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
tool = generate_tool_schema_from_query(query)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
tool_name = query['businessName']
~~~~~^^^^^^^^^^^^^^^^
KeyError: 'businessName'
2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
Traceback (most recent call last): Traceback (most recent call last):
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
yield yield
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
resp = self._pool.handle_request(req) resp = self._pool.handle_request(req)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
raise exc from None raise exc from None
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
response = connection.handle_request( response = connection.handle_request(
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
raise exc raise exc
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
stream = self._connect(request) stream = self._connect(request)
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
stream = self._network_backend.connect_tcp(**kwargs) stream = self._network_backend.connect_tcp(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
with map_exceptions(exc_map): with map_exceptions(exc_map):
^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value) self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
@@ -114,9 +30,9 @@ httpcore.ConnectError: [Errno 11001] getaddrinfo failed
The above exception was the direct cause of the following exception: The above exception was the direct cause of the following exception:
Traceback (most recent call last): Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
response = self.client.get( response = self.client.get(url, headers=self._get_headers())
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
return self.request( return self.request(
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
@@ -137,7 +53,6 @@ Traceback (most recent call last):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
with map_httpcore_exceptions(): with map_httpcore_exceptions():
^^^^^^^^^^^^^^^^^^^^^^^^^
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__ File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
self.gen.throw(value) self.gen.throw(value)
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
@@ -147,12 +62,12 @@ httpx.ConnectError: [Errno 11001] getaddrinfo failed
During handling of the above exception, another exception occurred: During handling of the above exception, another exception occurred:
Traceback (most recent call last): Traceback (most recent call last):
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
raw_result = get_skill_by_id(skill_id) raw_result = get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
return default_client.get_skill_by_id(skill_id) return default_client.get_skill_by_id(skill_id)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
raise Exception(error_msg) raise Exception(error_msg)
Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,13 @@
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import asyncio import asyncio
import json
import logging import logging
# 支持直接运行和模块导入两种方式
try: try:
from .utils import load_json, generate_tool_name, generate_input_schema from .utils import load_json, generate_input_schema
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema from .utils import get_skill_by_id, process_skill_response, test_sql_with_schema
from .utils import get_database_id, get_skill_id, get_env_config from .utils import get_skill_id, get_env_config
from .utils.logger_config import logger_config from .utils.logger_config import logger_config
except ImportError: except ImportError:
from utils import load_json, generate_input_schema from utils import load_json, generate_input_schema
@@ -19,41 +19,43 @@ from mcp.server.models import InitializationOptions
from mcp.server import NotificationOptions, Server from mcp.server import NotificationOptions, Server
import mcp.types as types import mcp.types as types
# 初始化 MCP 专用日志器
mcp_logger = logger_config.setup_mcp_logging() mcp_logger = logger_config.setup_mcp_logging()
# ========== 数据源配置 ========== DATA_SOURCE_API = "api"
# 数据源类型常量 DATA_SOURCE_LOCAL = "local"
DATA_SOURCE_API = "api" # 仅使用API数据 DATA_SOURCE_BOTH = "both"
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
# 默认数据源(可修改)
DEFAULT_DATA_SOURCE = DATA_SOURCE_API DEFAULT_DATA_SOURCE = DATA_SOURCE_API
# ================================
def _text_response(payload: dict[str, Any]) -> list[types.TextContent]:
"""Build a JSON text response."""
return [
types.TextContent(
type="text",
text=json.dumps(payload, ensure_ascii=False, indent=2)
)
]
def _extract_user_id(arguments: dict[str, Any]) -> Any:
"""Allow numeric 0, reject None and blank strings."""
user_id = arguments.get("userId")
if user_id is None:
return None
if isinstance(user_id, str) and not user_id.strip():
return None
return user_id
def get_queries(): def get_queries():
""" """Read local business query config."""
获取业务查询配置
Returns:
list: 包含所有业务查询配置的列表
"""
try: try:
# 获取当前文件所在目录
current_dir = Path(__file__).parent current_dir = Path(__file__).parent
# 构建 businessQueries.json 的路径
json_path = current_dir / "businessQueries.json" json_path = current_dir / "businessQueries.json"
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}") mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
# 使用 load_json 方法读取 JSON 文件
queries = load_json(json_path) queries = load_json(json_path)
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置") mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
return queries return queries
except Exception as e: except Exception as e:
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True) mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
@@ -61,110 +63,72 @@ def get_queries():
def generate_tool_schema_from_query(query: dict) -> types.Tool: def generate_tool_schema_from_query(query: dict) -> types.Tool:
""" """Generate an MCP tool definition from one query config."""
根据查询配置生成 MCP 工具模式
Args:
query: 单个查询配置字典
Returns:
types.Tool: MCP 工具对象
"""
try: try:
# 获取参数定义并生成 inputSchema parameters = query.get("parameters", {})
parameters = query.get('parameters', {})
input_schema = generate_input_schema(parameters) input_schema = generate_input_schema(parameters)
tool_name = query["businessName"]
# 生成工具名称(格式: tool_拼音_id
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName']
# 构建工具描述,包含业务名称和业务描述
description = f"{query['businessName']}: {query['businessDescription']}" description = f"{query['businessName']}: {query['businessDescription']}"
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}") mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
return types.Tool( return types.Tool(
name=tool_name, name=tool_name,
description=description, description=description,
inputSchema=input_schema inputSchema=input_schema
) )
except Exception as e: except Exception as e:
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True) mcp_logger.error(
f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}",
exc_info=True
)
raise raise
# 创建 MCP 服务器实例
server = Server("lzwcai-mcp-sqlexecutor") server = Server("lzwcai-mcp-sqlexecutor")
# 缓存查询配置,避免重复加载
_queries_cache = None _queries_cache = None
async def get_queries_cache(source: str = None): async def get_queries_cache(source: str = None):
""" """Get or initialize query config cache."""
获取或初始化查询配置缓存
Args:
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE
- "api": 仅使用API数据
- "local": 仅使用本地JSON数据
- "both": 合并本地和API数据
Returns:
查询配置列表
"""
global _queries_cache global _queries_cache
if _queries_cache is None: if _queries_cache is None:
source = source or DEFAULT_DATA_SOURCE source = source or DEFAULT_DATA_SOURCE
mcp_logger.info(f"初始化查询配置数据源: {source}...") mcp_logger.info(f"初始化查询配置数据源: {source}")
if source == DATA_SOURCE_LOCAL: if source == DATA_SOURCE_LOCAL:
_queries_cache = get_queries() _queries_cache = get_queries()
mcp_logger.info(f"本地配置: {len(_queries_cache)}") mcp_logger.info(f"本地配置: {len(_queries_cache)}")
elif source == DATA_SOURCE_API: elif source == DATA_SOURCE_API:
try: try:
_queries_cache = await call_third_party_api() _queries_cache = await call_third_party_api()
mcp_logger.info(f"API配置: {len(_queries_cache)}") mcp_logger.info(f"API 配置: {len(_queries_cache)}")
mcp_logger.info(f"API配置数组: {_queries_cache}") mcp_logger.info(f"API 配置数组: {_queries_cache}")
except Exception as e: except Exception as e:
mcp_logger.warning(f"API获取失败降级使用本地配置: {e}") mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}")
_queries_cache = get_queries() _queries_cache = get_queries()
else:
else: # DATA_SOURCE_BOTH
local = get_queries() local = get_queries()
try: try:
api = await call_third_party_api() api = await call_third_party_api()
except Exception as e: except Exception as e:
mcp_logger.warning(f"API获取失败: {e}") mcp_logger.warning(f"API 获取失败: {e}")
api = [] api = []
_queries_cache = local + api _queries_cache = local + api
mcp_logger.info(f"配置总数: {len(_queries_cache)}本地{len(local)}+API{len(api)}") mcp_logger.info(f"配置总数: {len(_queries_cache)}本地 {len(local)} + API {len(api)}")
return _queries_cache return _queries_cache
@server.list_tools() @server.list_tools()
async def handle_list_tools() -> list[types.Tool]: async def handle_list_tools() -> list[types.Tool]:
""" """List all dynamically generated MCP tools."""
列出所有动态生成的 MCP 工具
Returns:
list[types.Tool]: 所有可用的工具列表
"""
try: try:
mcp_logger.info("收到列出工具请求") mcp_logger.info("收到列出工具请求")
queries = await get_queries_cache() queries = await get_queries_cache()
tools = [] tools = [generate_tool_schema_from_query(query) for query in queries]
for query in queries:
tool = generate_tool_schema_from_query(query)
tools.append(tool)
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具") mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}") mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
return tools return tools
except Exception as e: except Exception as e:
mcp_logger.error(f"列出工具失败: {e}", exc_info=True) mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
@@ -173,151 +137,97 @@ async def handle_list_tools() -> list[types.Tool]:
@server.call_tool() @server.call_tool()
async def handle_call_tool( async def handle_call_tool(
name: str, name: str,
arguments: dict[str, Any] | None arguments: dict[str, Any] | None
) -> list[types.TextContent]: ) -> list[types.TextContent]:
""" """Handle MCP tool invocation."""
处理工具调用请求
Args:
name: 工具名称
arguments: 工具参数
Returns:
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
"""
try: try:
mcp_logger.info(f"收到工具调用请求: {name}") mcp_logger.info(f"收到工具调用请求: {name}")
mcp_logger.debug(f"工具参数: {arguments}") mcp_logger.debug(f"工具参数: {arguments}")
# 获取查询配置缓存
queries = await get_queries_cache() queries = await get_queries_cache()
tool_item = next((query for query in queries if query["businessName"] == name), None)
# 根据工具名称查找对应的 item接口配置
tool_item = None if not tool_item:
for query in queries: return _text_response({"error": f"未找到工具 {name} 对应的配置"})
# tool_name = generate_tool_name(query['businessName'], query['id'])
tool_name = query['businessName'] normalized_parameters = generate_input_schema(tool_item.get("parameters"))
if tool_name == name: normalized_arguments = arguments or {}
tool_item = query user_id = _extract_user_id(normalized_arguments)
break
if user_id is None:
# 构建返回结果 error_msg = f"工具 {name} 缺少必填参数 userId"
import json mcp_logger.warning(error_msg)
return _text_response({"error": error_msg, "required": ["userId"]})
if tool_item:
request_data = { request_data = {
"datasourceId": tool_item.get("datasourceId"), "datasourceId": tool_item.get("datasourceId"),
"businessName": tool_item.get("businessName"), "businessName": tool_item.get("businessName"),
"businessDescription": tool_item.get("businessDescription"), "businessDescription": tool_item.get("businessDescription"),
"sqlTemplate": tool_item.get("sqlTemplate"), "sqlTemplate": tool_item.get("sqlTemplate"),
"parameters": tool_item.get("parameters"), "parameters": normalized_parameters,
"testParams": arguments or {} "testParams": normalized_arguments,
} "userId": user_id
}
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
if arguments and arguments.get("targetDatabaseName"): target_database_name = normalized_arguments.get("targetDatabaseName")
request_data["targetDatabaseName"] = arguments["targetDatabaseName"] if target_database_name:
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}") request_data["targetDatabaseName"] = target_database_name
mcp_logger.debug(f"添加目标数据库名称: {target_database_name}")
# 如果 arguments 中有 userId 且有值,添加到 request_data
if arguments and arguments.get("userId"): try:
request_data["userId"] = arguments["userId"] mcp_logger.info("正在调用测试 SQL API...")
mcp_logger.debug(f"添加用户ID: {arguments['userId']}") result_payload = test_sql_with_schema(request_data)
mcp_logger.info("测试 SQL API 调用成功")
# 调用测试SQL API except Exception as e:
try: error_msg = f"调用测试 SQL API 失败: {str(e)}"
mcp_logger.info("正在调用测试SQL API...") mcp_logger.error(error_msg, exc_info=True)
api_response = test_sql_with_schema(request_data) result_payload = {"error": error_msg}
mcp_logger.info("测试SQL API调用成功")
mcp_logger.debug(f"工具调用结果: {result_payload}")
# 只返回 API 响应结果 return _text_response(result_payload)
result_text = json.dumps(api_response, ensure_ascii=False, indent=2)
except Exception as e:
error_msg = f"调用测试SQL API失败: {str(e)}"
mcp_logger.error(error_msg, exc_info=True)
result_text = json.dumps({"error": error_msg}, ensure_ascii=False, indent=2)
else:
error_msg = f"未找到工具 {name} 对应的配置"
result_text = json.dumps({"error": error_msg}, ensure_ascii=False, indent=2)
mcp_logger.debug(f"工具调用结果: {result_text}")
return [
types.TextContent(
type="text",
text=result_text
)
]
except Exception as e: except Exception as e:
error_msg = f"工具调用失败: {name}, 错误: {e}" error_msg = f"工具调用失败: {name}, 错误: {e}"
mcp_logger.error(error_msg, exc_info=True) mcp_logger.error(error_msg, exc_info=True)
return [ return [types.TextContent(type="text", text=f"错误: {error_msg}")]
types.TextContent(
type="text",
text=f"错误: {error_msg}"
)
]
async def call_third_party_api(skill_id: str = None) -> list: async def call_third_party_api(skill_id: str = None) -> list:
""" """Call backend API and map the response into query configs."""
调用第三方API获取技能信息并返回处理后的数据
Args:
skill_id: 技能ID默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178
Returns:
处理后的查询配置列表businessQueries格式
Example:
queries = await call_third_party_api()
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
"""
try: try:
# 如果没有传入 skill_id则从环境变量读取
if skill_id is None: if skill_id is None:
skill_id = get_skill_id() skill_id = get_skill_id()
mcp_logger.info(f"调用第三方APIskill_id: {skill_id}")
# 获取原始数据
raw_result = get_skill_by_id(skill_id)
mcp_logger.info(f"成功{raw_result}") mcp_logger.info(f"调用第三方 APIskill_id: {skill_id}")
raw_result = get_skill_by_id(skill_id)
# 处理并返回 mcp_logger.info(f"成功获取原始响应: {raw_result}")
processed_queries = process_skill_response(raw_result) processed_queries = process_skill_response(raw_result)
mcp_logger.info(f"成功处理 {len(processed_queries)} 条数据")
mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据")
return processed_queries return processed_queries
except Exception as e: except Exception as e:
mcp_logger.error(f"API调用失败: {e}", exc_info=True) mcp_logger.error(f"API 调用失败: {e}", exc_info=True)
raise raise
async def async_main(): async def async_main():
"""MCP 服务器异步主函数""" """Async entry for the MCP server."""
try: try:
mcp_logger.info("=" * 60) mcp_logger.info("=" * 60)
mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor") mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor")
mcp_logger.info("版本: 0.1.0") mcp_logger.info("版本: 0.1.0")
mcp_logger.info("=" * 60) mcp_logger.info("=" * 60)
# 输出环境配置信息
env_config = get_env_config() env_config = get_env_config()
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}") mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}") mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}") mcp_logger.info(f"环境配置 - Backend Base URL: {env_config['backend_base_url']}")
mcp_logger.info("=" * 60) mcp_logger.info("=" * 60)
from mcp.server.stdio import stdio_server from mcp.server.stdio import stdio_server
async with stdio_server() as (read_stream, write_stream): async with stdio_server() as (read_stream, write_stream):
mcp_logger.info("MCP 服务已启动,等待客户端连接...") mcp_logger.info("MCP 服务已启动,等待客户端连接...")
await server.run( await server.run(
read_stream, read_stream,
write_stream, write_stream,
@@ -330,30 +240,25 @@ async def async_main():
), ),
), ),
) )
mcp_logger.info("MCP 服务已关闭") mcp_logger.info("MCP 服务已关闭")
except Exception as e: except Exception as e:
mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True) mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True)
raise raise
def main(): def main():
"""入口点函数(用于 console_scripts""" """Console entrypoint."""
try: try:
# 初始化系统日志
# MCP协议使用stdio通信必须禁用控制台输出以避免干扰JSON-RPC通信
logger_config.setup_logging( logger_config.setup_logging(
app_name="lzwcai_mcp_sqlexecutor", app_name="lzwcai_mcp_sqlexecutor",
log_level=logging.INFO, log_level=logging.INFO,
console_output=False # 禁用控制台输出 console_output=False
) )
mcp_logger.info("开始运行 MCP SQL Executor 服务")
mcp_logger.info("开始运行 MCP SQL Executor 服务器")
asyncio.run(async_main()) asyncio.run(async_main())
except KeyboardInterrupt: except KeyboardInterrupt:
mcp_logger.info("收到中断信号,正在关闭服务...") mcp_logger.info("收到中断信号,正在关闭服务...")
except Exception as e: except Exception as e:
mcp_logger.error(f"程序运行失败: {e}", exc_info=True) mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
raise raise

View File

@@ -1,330 +1,209 @@
""" """Backend API client helpers."""
第三方API调用客户端
用于调用外部数据源接口 import json
""" import logging
from typing import Any, Dict, List, Optional
import httpx import httpx
import logging
import json
from typing import Dict, Any, Optional, List
# 支持直接运行和模块导入两种方式
try: try:
from .env_config import get_backend_base_url from .env_config import get_backend_base_url
except ImportError: except ImportError:
from env_config import get_backend_base_url from env_config import get_backend_base_url
# 获取日志记录器
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class DataSourceAPIClient: class DataSourceAPIClient:
"""数据源API客户端""" """HTTP client for backend datasource APIs."""
def __init__( def __init__(
self, self,
base_url: Optional[str] = None, base_url: Optional[str] = None,
token: Optional[str] = None token: Optional[str] = None
): ):
"""
初始化API客户端
Args:
base_url: API基础URL默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088
token: 认证令牌Bearer Token
"""
# 如果没有传入 base_url则从环境变量读取
if base_url is None: if base_url is None:
base_url = get_backend_base_url() base_url = get_backend_base_url()
self.base_url = base_url.rstrip('/') self.base_url = base_url.rstrip("/")
self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw" self.token = token or (
"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1i"
"ZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dn"
"vqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
)
self.client = httpx.Client(timeout=30.0) self.client = httpx.Client(timeout=30.0)
def _get_headers(self) -> Dict[str, str]: def _get_headers(self) -> Dict[str, str]:
"""
获取请求头
Returns:
请求头字典
"""
return { return {
'Authorization': f'Bearer {self.token}', "Authorization": f"Bearer {self.token}",
} }
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]: def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
""" """Fetch skill details by skill id."""
根据技能ID获取技能信息
Args:
skill_id: 技能ID
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try: try:
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}" url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
logger.info(f"正在调用API: {url}") logger.info(f"正在调用 API: {url}")
logger.debug(f"请求参数 - skill_id: {skill_id}") logger.debug(f"请求参数 - skill_id: {skill_id}")
response = self.client.get( response = self.client.get(url, headers=self._get_headers())
url,
headers=self._get_headers()
)
# 检查HTTP状态码
response.raise_for_status() response.raise_for_status()
# 解析JSON响应
data = response.json() data = response.json()
logger.info(f"API调用成功: {url}") logger.info(f"API 调用成功: {url}")
logger.debug(f"响应数据: {data}") logger.debug(f"响应数据: {data}")
return data return data
except httpx.TimeoutException: except httpx.TimeoutException:
error_msg = f"API请求超时: {url}" error_msg = f"API 请求超时: {url}"
logger.error(error_msg) logger.error(error_msg)
raise Exception(error_msg) raise Exception(error_msg)
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
error_msg = f"API请求失败 (HTTP {e.response.status_code}): {url}" error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg) logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}") logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg) raise Exception(error_msg)
except httpx.RequestError as e: except httpx.RequestError as e:
error_msg = f"API请求异常: {url}, 错误: {str(e)}" error_msg = f"API 请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg) logger.error(error_msg)
raise Exception(error_msg) raise Exception(error_msg)
except Exception as e: except Exception as e:
error_msg = f"处理API响应时出错: {str(e)}" error_msg = f"处理 API 响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
raise Exception(error_msg) raise Exception(error_msg)
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]: def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
""" """Call backend SQL test endpoint."""
测试SQL语句并返回执行结果
Args:
request_data: 请求数据,包含以下字段:
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
Returns:
API响应数据
Raises:
Exception: 请求失败时抛出
"""
try: try:
# 详细记录传入的数据
logger.info("=" * 80) logger.info("=" * 80)
logger.info("test_sql_with_schema 接口接收到的数据:") logger.info("test_sql_with_schema 接口接收到的数据:")
logger.info(f"数据类型: {type(request_data)}") logger.info(f"数据类型: {type(request_data)}")
logger.info(f"数据内容: {json.dumps(request_data, ensure_ascii=False, indent=2)}") logger.info(f"数据内容: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
logger.info(f"数据源ID: {request_data.get('datasourceId')}") logger.info(f"数据源 ID: {request_data.get('datasourceId')}")
logger.info(f"业务名称: {request_data.get('businessName')}") logger.info(f"业务名称: {request_data.get('businessName')}")
logger.info(f"业务描述: {request_data.get('businessDescription')}") logger.info(f"业务描述: {request_data.get('businessDescription')}")
logger.info(f"SQL模板: {request_data.get('sqlTemplate')}") logger.info(f"SQL 模板: {request_data.get('sqlTemplate')}")
logger.info(f"参数定义: {request_data.get('parameters')}") logger.info(f"参数定义: {request_data.get('parameters')}")
logger.info(f"测试参数: {request_data.get('testParams')}") logger.info(f"测试参数: {request_data.get('testParams')}")
if 'userId' in request_data: if "userId" in request_data:
logger.info(f"用户ID: {request_data.get('userId')}") logger.info(f"用户 ID: {request_data.get('userId')}")
logger.info("=" * 80) logger.info("=" * 80)
url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema" url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema"
# 构建请求头包含Content-Type
headers = self._get_headers() headers = self._get_headers()
headers['Content-Type'] = 'application/json' headers["Content-Type"] = "application/json"
headers['Accept'] = '*/*' headers["Accept"] = "*/*"
logger.info(f"正在调用测试SQL API: {url}") logger.info(f"正在调用测试 SQL API: {url}")
logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}") logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
# 发送POST请求 response = self.client.post(url, headers=headers, json=request_data)
response = self.client.post(
url,
headers=headers,
json=request_data
)
# 检查HTTP状态码
response.raise_for_status() response.raise_for_status()
# 解析JSON响应
data = response.json() data = response.json()
# 详细记录返回的数据
logger.info("=" * 80) logger.info("=" * 80)
logger.info("test_sql_with_schema 接口返回的数据:") logger.info("test_sql_with_schema 接口返回的数据:")
logger.info(f"HTTP状态码: {response.status_code}") logger.info(f"HTTP 状态码: {response.status_code}")
logger.info(f"响应数据类型: {type(data)}") logger.info(f"响应数据类型: {type(data)}")
logger.info(f"响应数据内容: {json.dumps(data, ensure_ascii=False, indent=2)}") logger.info(f"响应数据内容: {json.dumps(data, ensure_ascii=False, indent=2)}")
if isinstance(data, dict): if isinstance(data, dict):
logger.info(f"响应code: {data.get('code')}") logger.info(f"响应 code: {data.get('code')}")
logger.info(f"响应msg: {data.get('msg')}") logger.info(f"响应 msg: {data.get('msg')}")
logger.info(f"响应data: {data.get('data')}") logger.info(f"响应 data: {data.get('data')}")
logger.info("=" * 80) logger.info("=" * 80)
logger.info("测试SQL API调用成功") logger.info("测试 SQL API 调用成功")
return data return data
except httpx.TimeoutException: except httpx.TimeoutException:
error_msg = f"测试SQL API请求超时: {url}" error_msg = f"测试 SQL API 请求超时: {url}"
logger.error(error_msg) logger.error(error_msg)
raise Exception(error_msg) raise Exception(error_msg)
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
error_msg = f"测试SQL API请求失败 (HTTP {e.response.status_code}): {url}" error_msg = f"测试 SQL API 请求失败 (HTTP {e.response.status_code}): {url}"
logger.error(error_msg) logger.error(error_msg)
logger.error(f"错误响应: {e.response.text}") logger.error(f"错误响应: {e.response.text}")
raise Exception(error_msg) raise Exception(error_msg)
except httpx.RequestError as e: except httpx.RequestError as e:
error_msg = f"测试SQL API请求异常: {url}, 错误: {str(e)}" error_msg = f"测试 SQL API 请求异常: {url}, 错误: {str(e)}"
logger.error(error_msg) logger.error(error_msg)
raise Exception(error_msg) raise Exception(error_msg)
except Exception as e: except Exception as e:
error_msg = f"处理测试SQL API响应时出错: {str(e)}" error_msg = f"处理测试 SQL API 响应时出错: {str(e)}"
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
raise Exception(error_msg) raise Exception(error_msg)
def close(self): def close(self):
"""关闭HTTP客户端""" """Close the underlying HTTP client."""
self.client.close() self.client.close()
# 创建默认客户端实例
default_client = DataSourceAPIClient() default_client = DataSourceAPIClient()
def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: def get_skill_by_id(
""" skill_id: str,
便捷函数根据技能ID获取技能信息 base_url: Optional[str] = None,
token: Optional[str] = None
Args: ) -> Dict[str, Any]:
skill_id: 技能ID """Convenience wrapper for skill lookup."""
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token: if base_url or token:
client = DataSourceAPIClient( client = DataSourceAPIClient(base_url=base_url, token=token)
base_url=base_url,
token=token
)
return client.get_skill_by_id(skill_id) return client.get_skill_by_id(skill_id)
else: return default_client.get_skill_by_id(skill_id)
return default_client.get_skill_by_id(skill_id)
def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]: def test_sql_with_schema(
""" request_data: Dict[str, Any],
便捷函数测试SQL语句并返回执行结果 base_url: Optional[str] = None,
token: Optional[str] = None
Args: ) -> Dict[str, Any]:
request_data: 请求数据,包含以下字段: """Convenience wrapper for SQL test endpoint."""
- datasourceId: 数据源ID
- businessName: 业务名称
- businessDescription: 业务描述
- sqlTemplate: SQL模板
- parameters: 参数定义
- testParams: 测试参数
base_url: API基础URL可选默认从环境变量 BACKEND_BASE_URL 读取)
token: 认证令牌(可选,使用默认值)
Returns:
API响应数据
"""
if base_url or token: if base_url or token:
client = DataSourceAPIClient( client = DataSourceAPIClient(base_url=base_url, token=token)
base_url=base_url,
token=token
)
return client.test_sql_with_schema(request_data) return client.test_sql_with_schema(request_data)
else: return default_client.test_sql_with_schema(request_data)
return default_client.test_sql_with_schema(request_data)
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]: def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
""" """Map backend skill response into business query configs."""
处理API响应数据映射为businessQueries格式
Args:
response: API原始响应数据
Returns:
处理后的查询配置列表
"""
try: try:
# 提取data数组
data_list = response.get("data", []) data_list = response.get("data", [])
# 默认的员工ID参数schema
default_employee_schema = {
"type": "object",
"required": ["employeeId"],
"properties": {
"employeeId": {
"type": "number",
"description": "员工ID用于标识员工的唯一数字标识符",
"examples": [1001, 2002]
}
}
}
# 映射每个skill为businessQuery格式
queries = [] queries = []
for skill in data_list: for skill in data_list:
# 解析sqlParams字符串为JSON对象 sql_params_raw = skill.get("sqlParams")
sql_params_str = skill.get("sqlParams") or "{}" sql_params: Dict[str, Any] = {}
sql_params = json.loads(sql_params_str)
if isinstance(sql_params_raw, dict):
# 判断sqlParams是否为空对象 sql_params = sql_params_raw
is_empty_params = ( elif isinstance(sql_params_raw, str) and sql_params_raw.strip():
not sql_params.get("properties") or try:
len(sql_params.get("properties", {})) == 0 parsed_sql_params = json.loads(sql_params_raw)
) and ( if isinstance(parsed_sql_params, dict):
not sql_params.get("required") or sql_params = parsed_sql_params
len(sql_params.get("required", [])) == 0 else:
) logger.warning(
f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是对象,已回退为空对象"
# # 如果是空对象使用默认的员工ID参数 )
# if is_empty_params: except json.JSONDecodeError:
# logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空使用默认员工ID参数") logger.warning(
# sql_params = default_employee_schema f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是合法 JSON已回退为空对象"
)
# 映射字段
query = { query = {
"id": skill.get("id"), "id": skill.get("id"),
"businessName": skill.get("name"), "businessName": skill.get("name"),
"businessDescription": skill.get("description"), "businessDescription": skill.get("description"),
"sqlTemplate": skill.get("sqlTemplate"), "sqlTemplate": skill.get("sqlTemplate"),
"parameters": sql_params, "parameters": sql_params,
"datasourceId": skill.get("datasourceId") "datasourceId": skill.get("datasourceId"),
} }
queries.append(query) queries.append(query)
logger.info(f"成功处理 {len(queries)} 条技能数据") logger.info(f"成功处理 {len(queries)} 条技能数据")
return queries return queries
except Exception as e: except Exception as e:
logger.error(f"处理API响应数据失败: {e}", exc_info=True) logger.error(f"处理 API 响应数据失败: {e}", exc_info=True)
raise raise

View File

@@ -1,149 +1,93 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
Schema 生成工具模块 Schema 生成工具模块
""" """
from copy import deepcopy
from typing import Any, Dict from typing import Any, Dict
def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]: def _normalize_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
""" """将任意参数定义归一化为 object schema。"""
从查询配置的参数定义生成 MCP 工具的 inputSchema if not isinstance(parameters, dict) or not parameters:
此函数会保留完整的 JSON Schema 信息,包括:
- type: Schema 类型(通常是 "object"
- required: 必填字段列表
- properties: 属性定义(包括每个属性的 type, description, format, examples 等)
- description: Schema 的整体描述(如果有)
- 以及其他任何 JSON Schema 标准字段
此函数还会自动添加以下字段(如果原始 parameters 中未定义):
- targetDatabaseName: 目标数据库名称(非必填,默认为空字符串)
Args:
parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象
Returns:
Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象
Example:
>>> params = {
... "type": "object",
... "required": ["userId", "startTime"],
... "properties": {
... "userId": {
... "type": "integer",
... "description": "用户的唯一标识符",
... "examples": [10086]
... },
... "startTime": {
... "type": "string",
... "format": "date-time",
... "description": "查询的起始时间",
... "examples": ["2023-01-01 00:00:00"]
... }
... }
... }
>>> schema = generate_input_schema(params)
>>> # schema 将包含所有原始信息,包括 format 和 examples
>>> # 同时会自动添加 targetDatabaseName 字段
"""
# 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用
# 但确保至少包含 type 和 properties
if not parameters:
# 如果 parameters 为空,返回一个空的 object schema
return { return {
"type": "object", "type": "object",
"properties": {}, "properties": {},
"required": [] "required": []
} }
# 深拷贝 parameters 以避免修改原始数据 input_schema = deepcopy(parameters)
input_schema = dict(parameters) input_schema["type"] = "object"
# 确保必需的字段存在 if not isinstance(input_schema.get("properties"), dict):
if "type" not in input_schema:
input_schema["type"] = "object"
if "properties" not in input_schema:
input_schema["properties"] = {} input_schema["properties"] = {}
if "required" not in input_schema: if not isinstance(input_schema.get("required"), list):
input_schema["required"] = [] input_schema["required"] = []
# 添加 targetDatabaseName 字段(如果不存在) return input_schema
def generate_input_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
"""
从查询配置的参数定义生成 MCP 工具的 inputSchema。
会统一补齐:
- `targetDatabaseName`:可选
- `userId`:必填
"""
input_schema = _normalize_schema(parameters)
if "targetDatabaseName" not in input_schema["properties"]: if "targetDatabaseName" not in input_schema["properties"]:
input_schema["properties"]["targetDatabaseName"] = { input_schema["properties"]["targetDatabaseName"] = {
"type": "string", "type": "string",
"description": "目标数据库名称", "description": "目标数据库名称",
"default": "" "default": ""
} }
# 添加 userId 字段(如果不存在)
if "userId" not in input_schema["properties"]: if "userId" not in input_schema["properties"]:
input_schema["properties"]["userId"] = { input_schema["properties"]["userId"] = {
"type": "string", "type": "string",
"description": "当前ai平台用户id" "description": "当前 AI 平台用户 ID"
} }
# 保留所有其他字段,如 description, examples, format 等 if "userId" not in input_schema["required"]:
# JSON Schema 标准支持的字段都会被保留: input_schema["required"].append("userId")
# - additionalProperties
# - patternProperties
# - minProperties / maxProperties
# - dependencies
# - 等等
return input_schema return input_schema
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]: def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
""" """
验证 inputSchema 是否符合基本的 JSON Schema 规范 验证 inputSchema 是否符合基本的 JSON Schema 规范
Args:
schema: 要验证的 schema 对象
Returns:
tuple[bool, str]: (是否有效, 错误消息或成功消息)
Example:
>>> schema = {"type": "object", "properties": {"id": {"type": "string"}}}
>>> is_valid, msg = validate_input_schema(schema)
>>> print(is_valid, msg)
True, "Schema 验证通过"
""" """
if not isinstance(schema, dict): if not isinstance(schema, dict):
return False, "Schema 必须是一个字典对象" return False, "Schema 必须是一个字典对象"
if schema.get("type") != "object": if schema.get("type") != "object":
return False, "Schema 的 type 字段必须是 'object'" return False, "Schema 的 type 字段必须是 'object'"
if "properties" not in schema: if "properties" not in schema:
return False, "Schema 必须包含 properties 字段" return False, "Schema 必须包含 properties 字段"
if not isinstance(schema.get("properties"), dict): if not isinstance(schema.get("properties"), dict):
return False, "Schema 的 properties 字段必须是一个字典对象" return False, "Schema 的 properties 字段必须是一个字典对象"
# 验证 required 字段(如果存在)
if "required" in schema: if "required" in schema:
required = schema["required"] required = schema["required"]
if not isinstance(required, list): if not isinstance(required, list):
return False, "Schema 的 required 字段必须是一个列表" return False, "Schema 的 required 字段必须是一个列表"
# 验证所有 required 的字段都在 properties 中定义
properties = schema["properties"] properties = schema["properties"]
for field in required: for field in required:
if field not in properties: if field not in properties:
return False, f"必填字段 '{field}' 未在 properties 中定义" return False, f"必填字段 '{field}' 未在 properties 中定义"
# 验证 properties 中每个字段的定义
for prop_name, prop_def in schema["properties"].items(): for prop_name, prop_def in schema["properties"].items():
if not isinstance(prop_def, dict): if not isinstance(prop_def, dict):
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象" return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
if "type" not in prop_def: if "type" not in prop_def:
return False, f"属性 '{prop_name}' 必须包含 type 字段" return False, f"属性 '{prop_name}' 必须包含 type 字段"
return True, "Schema 验证通过"
return True, "Schema 验证通过"

View File

@@ -1,14 +1,20 @@
""" """
Entry point for lzwcai-mcp-sqlexecutor Repository-local launcher for lzwcai-mcp-sqlexecutor.
Runs the MCP server for SQL query execution
""" """
import os import os
os.environ["databaseId"] = "162"
os.environ["skillId"] = "2008360664955854850"
os.environ["backendBaseUrl"] = "http://192.168.2.236:8088"
if __name__ == "__main__":
# Import and run the actual MCP server
from lzwcai_mcp_sqlexecutor.main import main
def main():
# Keep local developer defaults without overriding explicit environment settings.
os.environ.setdefault("databaseId", "240")
os.environ.setdefault("skillId", "2058819964077572098")
os.environ.setdefault("backendBaseUrl", "http://192.168.2.236:8088")
from lzwcai_mcp_sqlexecutor.main import main as package_main
package_main()
if __name__ == "__main__":
main() main()

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "lzwcai-mcp-sqlexecutor" name = "lzwcai-mcp-sqlexecutor"
version = "0.1.12" version = "0.1.13"
description = "MCP server for executing business SQL queries with dynamic tool generation" description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@@ -24,7 +24,7 @@ WORKFLOW_EXECUTE_TIMEOUT = 600.0 # 工作流执行超时10分钟参考 wo
# 异步轮询默认配置(参考 workflow-execution-api.md # 异步轮询默认配置(参考 workflow-execution-api.md
DEFAULT_POLLING_INTERVAL = 1.0 DEFAULT_POLLING_INTERVAL = 1.0
DEFAULT_MAX_POLL_COUNT = 120 DEFAULT_MAX_POLL_COUNT = 800
DEFAULT_MAX_ERROR_COUNT = 3 DEFAULT_MAX_ERROR_COUNT = 3

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "lzwcai-workflow-to-mcp" name = "lzwcai-workflow-to-mcp"
version = "0.1.8" version = "0.1.9"
description = "MCP server for executing business SQL queries with dynamic tool generation" description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"