feat(lzwcai-agile-db): 更新AgileDB技能至v0.4.2版本并扩展工具集

- 将技能版本从0.2.0升级至0.4.2
- 工具数量从33个扩展至57个,新增数据源管理、AI训练、库表关联配置等功能
- 新增MQTT字段关联同步模块(8个工具)和库表关联配置(3个工具)
- 添加重要的契约提示和安全确认原则,包括target默认值、alter_table操作限制等
- 修正工具参数说明,如execute_sql的executableSql改为sql,参数结构优化
- 增强安全机制,明确危险操作的用户确认流程和目标资源选择规则
- 更新README.md中的工具数量统计和功能描述
This commit is contained in:
2026-06-17 14:40:43 +08:00
parent 557361632c
commit ba5cd4bbe1
115 changed files with 7587 additions and 575 deletions

View File

@@ -1,6 +1,6 @@
# lzwcai-mcp-agile-db
数据库管理平台 MCP Server提供 33 个工具用于数据库管理、表操作、数据 CRUD、API 密钥管理、技能与工具管理等。
数据库管理平台 MCP Server提供 34 个工具用于数据库管理、表操作、数据 CRUD、API 密钥管理、技能与工具管理等。
## 环境变量
@@ -58,6 +58,7 @@ lzwcai-mcp-agile-db
- `delete_api_key` - 删除密钥
- `get_api_key_permissions` - 查看密钥权限
- `grant_api_key_permissions` - 授予权限
- `revoke_api_key_permissions` - 撤销/删除已授予权限
### 技能与工具管理
- `get_skill_by_datasource` - 获取技能信息

View File

@@ -1,202 +1,10 @@
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
2026-06-17 11:19:35 - 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-17 11:19:35 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
2026-06-17 11:19:35 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
2026-06-17 11:19:35 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=http://x
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:78] - [API响应] HTTP 400
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:98] - [API错误] HTTP 400, 参数不合法
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:78] - [API响应] HTTP 200
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:78] - [API响应] HTTP 404
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:107] - [API错误] HTTP 404, {"path": "/x"}

View File

@@ -1,15 +1,2 @@
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-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:98] - [API错误] HTTP 400, 参数不合法
2026-06-17 11:19:35 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:107] - [API错误] HTTP 404, {"path": "/x"}

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
lzwcai-mcp-agile-db MCP Server
数据库管理平台 MCP 工具服务,提供 33 个工具用于数据库管理、表操作、API 密钥管理等
数据库管理平台 MCP 工具服务,提供数据库管理、表/数据操作、技能、API 密钥、MQTT 同步、AI 训练等工具
"""
import json
@@ -104,19 +104,24 @@ async def handle_call_tool(
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={},
try:
async with stdio_server() as streams:
await server.run(
streams[0],
streams[1],
InitializationOptions(
server_name="lzwcai_mcp_agile_db",
server_version="0.1.12",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
),
)
)
finally:
# 释放全局 HTTP 客户端
if _api_client is not None:
await _api_client.close()
def main():

View File

@@ -0,0 +1,68 @@
"""
AI 补全训练工具
含 generate_table_by_description 的轮询配套 get_ai_training_detail。
训练状态 trainingStatus: 0 待训练 / 1 训练中 / 2 已完成 / 3 失败
"""
from ._base import register_tool, ToolDef
@register_tool("list_ai_trainings")
class ListAiTrainingsTool(ToolDef):
name = "list_ai_trainings"
description = "获取 AI 补全训练任务列表"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID可选过滤"},
"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 await self.client.get("/ai/training/list", params=params)
@register_tool("create_ai_training_by_selected")
class CreateAiTrainingBySelectedTool(ToolDef):
name = "create_ai_training_by_selected"
description = "按选中的表创建 AI 补全训练任务"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"tableIds": {
"type": "array",
"description": "要训练的表 ID 数组",
"items": {"type": "string"},
},
},
"required": ["tableIds"],
}
async def execute(self, args: dict) -> dict:
return await self.client.post("/ai/training/createBySelected", json_data=args)
@register_tool("get_ai_training_detail")
class GetAiTrainingDetailTool(ToolDef):
name = "get_ai_training_detail"
description = (
"查询 AI 训练任务详情(轮询用)。"
"trainingStatus: 0 待训练 / 1 训练中 / 2 已完成 / 3 失败。"
"generate_table_by_description 返回 taskId 后,用本工具轮询直到 status=2 取结果"
)
input_schema = {
"type": "object",
"properties": {
"taskId": {"type": "string", "description": "训练任务 IDgenerate_table 返回的 taskId"},
},
"required": ["taskId"],
}
async def execute(self, args: dict) -> dict:
return await self.client.get(f"/ai/training/{args['taskId']}")

View File

@@ -1,5 +1,5 @@
"""
API 密钥管理工具 (工具 18-23)
API 密钥管理工具(含创建、状态切换、删除、权限查询/授予/撤销)
"""
from ._base import register_tool, ToolDef
@@ -119,3 +119,34 @@ class GrantApiKeyPermissionsTool(ToolDef):
async def execute(self, args: dict) -> dict:
return await self.client.post("/datasource/api_key/permission/grant_batch", json_data=args)
@register_tool("revoke_api_key_permissions")
class RevokeApiKeyPermissionsTool(ToolDef):
name = "revoke_api_key_permissions"
description = "撤销/删除 API 密钥已授予的权限(按权限记录 ID"
input_schema = {
"type": "object",
"properties": {
"permissionIds": {
"type": "array",
"items": {"type": "string"},
"description": (
"权限记录 ID 列表。"
"先从 get_api_key_permissions 获取,"
"取 connectionPermissions / databasePermissions / tablePermissions 中每项的 id 字段"
),
},
},
"required": ["permissionIds"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
permission_ids = args.pop("permissionIds", None) or []
# 过滤掉空字符串/None防止拼接出类似 "1,,2" 的非法 ID
permission_ids = [pid for pid in permission_ids if pid is not None and str(pid).strip()]
if not permission_ids:
raise ValueError("permissionIds 不能为空")
ids = ",".join(str(pid).strip() for pid in permission_ids)
return await self.client.delete(f"/datasource/api_key/permission/{ids}")

View File

@@ -0,0 +1,104 @@
"""
库表关联配置工具(外置/内置通用)
对应文档 §2.2 / §4.2
- postConnectionConfig 建库表关联配置(外置源建完连接后选库选表的最后一步)
- putConnectionConfig 改库表关联配置
- deleteConnectionConfig 删配置(同时也是「删除库」的实现)
"""
from ._base import register_tool, ToolDef
@register_tool("create_connection_config")
class CreateConnectionConfigTool(ToolDef):
name = "create_connection_config"
description = (
"创建「库表关联」配置:外置数据源建完连接后,选择要纳管的库与表的最后一步。"
"完成后数据源才能用于智能问数"
)
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源连接ID"},
"syncTables": {"type": "boolean", "default": True, "description": "是否同步表结构,默认 true"},
"databases": {
"type": "array",
"description": "要纳管的库及其表",
"items": {
"type": "object",
"properties": {
"databaseName": {"type": "string", "description": "库名"},
"tableNames": {
"type": "array",
"description": "选中的表名数组",
"items": {"type": "string"},
},
},
"required": ["databaseName", "tableNames"],
},
},
},
"required": ["connectionId", "databases"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
args.setdefault("syncTables", True)
return await self.client.post("/datasource/config", json_data=args)
@register_tool("update_connection_config")
class UpdateConnectionConfigTool(ToolDef):
name = "update_connection_config"
description = "更新「库表关联」配置(编辑已纳管的库表范围)"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "配置 ID编辑时必传"},
"connectionId": {"type": "string", "description": "数据源连接ID"},
"syncTables": {"type": "boolean", "default": True, "description": "是否同步表结构,默认 true"},
"databases": {
"type": "array",
"description": "要纳管的库及其表",
"items": {
"type": "object",
"properties": {
"databaseName": {"type": "string", "description": "库名"},
"tableNames": {
"type": "array",
"description": "选中的表名数组",
"items": {"type": "string"},
},
},
"required": ["databaseName", "tableNames"],
},
},
},
"required": ["id", "connectionId", "databases"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
args.setdefault("syncTables", True)
return await self.client.put("/datasource/config", json_data=args)
@register_tool("delete_connection_config")
class DeleteConnectionConfigTool(ToolDef):
name = "delete_connection_config"
description = "删除库表关联配置(批量)。这也是「删除内置库」的实现:传入对应库的配置 ID"
input_schema = {
"type": "object",
"properties": {
"ids": {
"type": "array",
"description": "配置 ID 数组",
"items": {"type": "string"},
},
},
"required": ["ids"],
}
async def execute(self, args: dict) -> dict:
return await self.client.post("/datasource/config/deletes", json_data={"ids": args["ids"]})

View File

@@ -2,8 +2,10 @@
数据导入工具 (工具 30-31)
"""
import base64
import io
import mimetypes
import os
from urllib.parse import urlparse, unquote
from ._base import register_tool, ToolDef
@@ -11,62 +13,186 @@ from ._base import register_tool, ToolDef
@register_tool("preview_import_data")
class PreviewImportDataTool(ToolDef):
name = "preview_import_data"
description = "上传 Excel 文件AI 智能识别并预览表结构/数据"
description = "从 URL 下载 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"},
"file_url": {"type": "string", "description": "Excel 文件下载地址.xlsx/.xls, <500KB"},
},
"required": ["connectionId", "file_base64"],
"required": ["connectionId", "file_url"],
}
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)
# 构建文件上传
file_url = args.pop("file_url")
# 0. 基础 URL 校验
parsed_url = urlparse(file_url)
if parsed_url.scheme not in ("http", "https"):
raise ValueError("仅支持 http/https 协议的下载地址")
# 1. 下载文件
download_resp = await self.client.client.get(file_url, follow_redirects=True, timeout=30.0)
download_resp.raise_for_status()
# 1.1 预先根据 Content-Length 拦截超大文件,避免无意义下载
max_size = 500 * 1024
content_length = download_resp.headers.get("content-length")
if content_length:
try:
if int(content_length) > max_size:
raise ValueError(
f"文件 Content-Length {content_length} bytes 超过平台限制 {max_size} bytes (500KB)"
)
except ValueError as e:
if "超过平台限制" in str(e):
raise
# 非数字时忽略
file_content = download_resp.content
# 2. 文件大小校验(平台限制 500KB
if len(file_content) > max_size:
raise ValueError(
f"文件大小 {len(file_content)} bytes 超过平台限制 {max_size} bytes (500KB)"
"请先压缩或拆分后重试"
)
# 3. 解析文件名并校验扩展名
file_name = self._extract_file_name(download_resp, file_url)
allowed_exts = (".xlsx", ".xls")
if not file_name.lower().endswith(allowed_exts):
raise ValueError(
f"不支持的文件类型: {file_name},仅支持 {', '.join(allowed_exts)}"
)
# 4. 推断 MIME 类型
content_type, _ = mimetypes.guess_type(file_name)
if not content_type:
content_type = (
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
if file_name.lower().endswith(".xlsx")
else "application/vnd.ms-excel"
)
# 5. 构建文件上传
files = {
"file": (file_name, io.BytesIO(file_content), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
"file": (file_name, io.BytesIO(file_content), content_type),
}
return await self.client.upload(
f"/datasource/connection/{connection_id}/import_document/preview",
files=files,
params={"target": target},
)
@staticmethod
def _extract_file_name(response, file_url: str) -> str:
"""从响应头或 URL 中提取文件名。"""
# 优先从 Content-Disposition 解析
content_disposition = response.headers.get("content-disposition")
if content_disposition:
# 简单解析 filename="xxx" 或 filename*=UTF-8''xxx
# 注意filename* 也包含 filename= 前缀,必须先检查 filename*
for part in content_disposition.split(";"):
part = part.strip()
if part.lower().startswith("filename*="):
encoded = part.split("=", 1)[1]
# 形如 UTF-8''xxx
if "''" in encoded:
_, _, file_name = encoded.partition("''")
return os.path.basename(unquote(file_name, encoding="utf-8"))
return os.path.basename(unquote(encoded, encoding="utf-8"))
if part.lower().startswith("filename="):
file_name = part.split("=", 1)[1].strip('"')
return os.path.basename(unquote(file_name))
# fallback 从 URL 路径获取
parsed = urlparse(file_url)
path = unquote(parsed.path)
file_name = os.path.basename(path) if path else ""
if file_name and "." in file_name:
return file_name
return "import.xlsx"
@register_tool("confirm_import_data")
class ConfirmImportDataTool(ToolDef):
name = "confirm_import_data"
description = "确认导入 AI 识别后的数据"
description = (
"确认导入 AI 识别后的数据(建表+插数据)。"
"传入 preview_import_data 返回的 data 原文 + databaseName 即可,"
"工具内部会自动组装成后端要求的 {tableStructure(含databaseName), allData} 结构"
)
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "description": "环境"},
"data": {"type": "object", "description": "导入数据(含 tableStructure + allData"},
"connectionId": {"type": "string", "description": "数据源连接 ID"},
"databaseName": {"type": "string", "description": "落库的数据库名(导入目标库)"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "环境,默认 test"},
"data": {"type": "object", "description": "preview_import_data 返回的 data含 tableStructure/allData或已组装好的最终结构"},
},
"required": ["connectionId", "data"],
}
@staticmethod
def _build_body(data: dict, database_name) -> dict:
"""把 preview 返回的 data 组装成 confirm 要求的 body。
后端真实结构(已真机探测确认):
{ "tableStructure": <单表对象,含 columns 且需带 databaseName>, "allData": [...] }
preview 返回的 data.tableStructure 是 {success,message,data:{tables:[...]}} 的包装,
需取出 data.tables[0] 作为单表对象。
"""
if not isinstance(data, dict):
return data
# 若传入的是 preview 工具的完整返回信封 {code, msg, data:{...}},先剥一层
if "tableStructure" not in data and isinstance(data.get("data"), dict):
inner_env = data["data"]
if "tableStructure" in inner_env or "allData" in inner_env:
data = inner_env
ts = data.get("tableStructure")
single_table = None
if isinstance(ts, dict):
if "columns" in ts:
# 已是单表对象(调用方自行组装过)
single_table = dict(ts)
else:
# preview 包装tableStructure.data.tables[0]
inner = ts.get("data") if isinstance(ts.get("data"), dict) else {}
tables = inner.get("tables") if isinstance(inner, dict) else None
if isinstance(tables, list) and tables:
single_table = dict(tables[0])
if single_table is None:
# 无法识别结构,原样透传(兼容调用方已构造好完整 body 的情况)
return data
if database_name and not single_table.get("databaseName"):
single_table["databaseName"] = database_name
all_data = data.get("allData")
if all_data is None:
all_data = data.get("data") or []
return {"tableStructure": single_table, "allData": all_data}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
target = args.pop("target", "test")
database_name = args.pop("databaseName", None)
data = args.pop("data")
body = self._build_body(data, database_name)
return await self.client.post(
f"/datasource/connection/{connection_id}/import_document/confirm",
json_data=data,
json_data=body,
params={"target": target},
)

View File

@@ -45,6 +45,28 @@ class ListTablesTool(ToolDef):
return await self.client.get("/datasource/table/list", params=params)
@register_tool("list_tables_with_ai")
class ListTablesWithAiTool(ToolDef):
name = "list_tables_with_ai"
description = (
"获取表元数据列表(含 AI 训练描述),内置库管理的主列表。"
"注意datasourceId 实为「配置/库」ID即 create_connection_config 落库后的 config id"
)
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "配置/库 ID非数据源连接 ID"},
"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 await self.client.get("/datasource/table/ailist", params=params)
@register_tool("get_table_detail")
class GetTableDetailTool(ToolDef):
name = "get_table_detail"
@@ -117,8 +139,8 @@ class AlterTableTool(ToolDef):
"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 不同包含不同字段)"},
"operation": {"type": "string", "enum": ["ADD_COLUMN", "MODIFY_COLUMN", "DROP_COLUMN"], "description": "变更类型:新增/修改/删除字段(后端实测仅支持这三种)"},
"column": {"type": "object", "description": "列定义。ADD/MODIFY 需 columnName+columnType(+columnLength/columnComment 等)DROP 仅需 columnName"},
},
"required": ["operation", "column"],
},
@@ -137,7 +159,7 @@ class AlterTableTool(ToolDef):
@register_tool("generate_table_by_description")
class GenerateTableByDescriptionTool(ToolDef):
name = "generate_table_by_description"
description = "通过自然语言描述让 AI 生成表结构(异步任务"
description = "通过自然语言描述让 AI 生成表结构(异步任务,返回 taskId。需配合 get_ai_training_detail 轮询 taskId 获取生成结果"
input_schema = {
"type": "object",
"properties": {
@@ -149,3 +171,116 @@ class GenerateTableByDescriptionTool(ToolDef):
async def execute(self, args: dict) -> dict:
return await self.client.post("/datasource/connection/generate_table", json_data=args)
@register_tool("create_database")
class CreateDatabaseTool(ToolDef):
name = "create_database"
description = "在内置数据源下创建一个数据库(建表前置步骤)"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源连接 ID"},
"databaseName": {"type": "string", "description": "数据库名(仅字母数字下划线)"},
},
"required": ["connectionId", "databaseName"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
return await self.client.post(f"/datasource/connection/{connection_id}/create_database", json_data=args)
@register_tool("create_database_table")
class CreateDatabaseTableTool(ToolDef):
name = "create_database_table"
description = (
"一次性创建数据库 + 表(合并接口,免去先建库再建表)。"
"支持两种用法:①单表便捷参数 tableName/tableComment/columns"
"②批量传 tables 数组(一次建多张表)。二者任选其一"
)
# 单字段columnName/columnType/...)的 schema单表和批量两种用法共用
_COLUMN_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"],
}
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": "字段定义数组(用法①单表,结构同 create_table",
"items": _COLUMN_ITEMS,
},
# —— 用法②:批量 tables 数组 ——
"tables": {
"type": "array",
"description": "表定义数组(用法②批量建多表),每项含 tableName/tableComment/columns",
"items": {
"type": "object",
"properties": {
"tableName": {"type": "string", "description": "表名"},
"tableComment": {"type": "string", "description": "表注释"},
"columns": {"type": "array", "description": "字段定义数组", "items": _COLUMN_ITEMS},
},
"required": ["tableName", "columns"],
},
},
},
"required": ["connectionId", "databaseName"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
# 后端真实 body 结构:{databaseName, tables:[{tableName,tableComment,columns}]}(已真机探测确认)
if not args.get("tables"):
table = {"tableName": args.pop("tableName", None), "columns": args.pop("columns", [])}
if args.get("tableComment") is not None:
table["tableComment"] = args.pop("tableComment")
args["tables"] = [table]
else:
# 批量用法下,清掉可能并存的单表字段,避免歧义
args.pop("tableName", None)
args.pop("tableComment", None)
args.pop("columns", None)
return await self.client.post(f"/datasource/connection/{connection_id}/create_database_table", json_data=args)
@register_tool("alter_database")
class AlterDatabaseTool(ToolDef):
name = "alter_database"
description = "修改数据库(如改名)"
input_schema = {
"type": "object",
"properties": {
"connectionId": {"type": "string", "description": "数据源连接 ID"},
"databaseName": {"type": "string", "description": "当前数据库名"},
"newName": {"type": "string", "description": "新数据库名"},
},
"required": ["connectionId", "databaseName", "newName"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
connection_id = args.pop("connectionId")
# 后端改名字段名为 newName已真机探测确认兼容历史传参 newDatabaseName
if "newName" not in args and "newDatabaseName" in args:
args["newName"] = args.pop("newDatabaseName")
return await self.client.put(f"/datasource/connection/{connection_id}/alter_database", json_data=args)

View File

@@ -71,7 +71,7 @@ class CreateDatasourceTool(ToolDef):
"type": "object",
"properties": {
"datasourceName": {"type": "string", "description": "数据源名称3-20字"},
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng"], "description": "数据库类型"},
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng", "kingbase", "sqlite", "mariadb"], "description": "数据库类型"},
"host": {"type": "string", "description": "数据库地址"},
"port": {"type": "integer", "description": "端口号"},
"databaseName": {"type": "string", "description": "要连接的数据库名"},
@@ -88,7 +88,7 @@ class CreateDatasourceTool(ToolDef):
args = dict(args)
test_first = args.pop("test_first", True)
# 如果需要先测试连接
# 如果需要先测试连接(测试失败时 client 会直接抛异常,由上层统一捕获)
if test_first:
test_data = {
"datasourceName": args.get("datasourceName"),
@@ -100,10 +100,8 @@ class CreateDatasourceTool(ToolDef):
"password": args.get("password"),
"connectionType": args.get("connectionType", "user_password"),
}
test_result = await self.client.post("/datasource/connection/test", json_data=test_data)
if test_result.get("code") != 200:
return {"success": False, "error": f"连接测试失败: {test_result.get('msg', '未知错误')}"}
await self.client.post("/datasource/connection/test", json_data=test_data)
# 创建数据源
return await self.client.post("/datasource/connection", json_data=args)
@@ -117,11 +115,13 @@ class UpdateDatasourceTool(ToolDef):
"properties": {
"id": {"type": "string", "description": "数据源 ID"},
"datasourceName": {"type": "string", "description": "更新名称"},
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng", "kingbase", "sqlite", "mariadb"], "description": "数据库类型"},
"host": {"type": "string", "description": "更新地址"},
"port": {"type": "integer", "description": "更新端口"},
"databaseName": {"type": "string", "description": "更新数据库名"},
"username": {"type": "string", "description": "更新用户名"},
"password": {"type": "string", "description": "新密码(不传则不变)"},
"connectionType": {"type": "string", "enum": ["user_password", "ssl"], "description": "连接类型"},
"remark": {"type": "string", "description": "更新描述"},
},
"required": ["id"],
@@ -168,6 +168,82 @@ class DeleteDatasourceTool(ToolDef):
except Exception as e:
# 记录日志但继续删除
logger.debug(f"停用数据源失败(可能已停用): {e}")
# 删除数据源
return await self.client.delete(f"/datasource/connection/{ds_id}")
@register_tool("test_connection")
class TestConnectionTool(ToolDef):
name = "test_connection"
description = "测试外部数据库连接是否可用(不创建数据源)"
input_schema = {
"type": "object",
"properties": {
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng", "kingbase", "sqlite", "mariadb"], "description": "数据库类型"},
"host": {"type": "string", "description": "数据库地址"},
"port": {"type": "integer", "description": "端口号"},
"databaseName": {"type": "string", "description": "要连接的数据库名"},
"username": {"type": "string", "description": "数据库用户名"},
"password": {"type": "string", "description": "密码"},
"connectionType": {"type": "string", "enum": ["user_password", "ssl"], "description": "连接类型,默认 user_password"},
},
"required": ["datasourceType", "host", "port", "databaseName", "username"],
}
async def execute(self, args: dict) -> dict:
args = dict(args)
args.setdefault("connectionType", "user_password")
return await self.client.post("/datasource/connection/test", json_data=args)
@register_tool("get_realtime_structure")
class GetRealtimeStructureTool(ToolDef):
name = "get_realtime_structure"
description = "拉取数据源真实的库表结构(实时探测远端数据库)"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
},
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
ds_id = args["datasourceId"]
return await self.client.get(f"/datasource/connection/realtime/structure/{ds_id}")
@register_tool("create_builtin_datasource")
class CreateBuiltinDatasourceTool(ToolDef):
name = "create_builtin_datasource"
description = "创建内置 PostgreSQL 数据源连接,返回 connectionId内置源建库建表链路第一步"
input_schema = {
"type": "object",
"properties": {
"datasourceName": {"type": "string", "description": "数据源名称3-20字"},
"remark": {"type": "string", "description": "数据源描述"},
},
"required": ["datasourceName"],
}
async def execute(self, args: dict) -> dict:
return await self.client.post("/datasource/connection/create_builtin_postgresql", json_data=args)
@register_tool("update_builtin_datasource")
class UpdateBuiltinDatasourceTool(ToolDef):
name = "update_builtin_datasource"
description = "修改内置 PostgreSQL 数据源连接信息"
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "数据源 ID"},
"datasourceName": {"type": "string", "description": "更新名称"},
"remark": {"type": "string", "description": "更新描述"},
},
"required": ["id"],
}
async def execute(self, args: dict) -> dict:
return await self.client.put("/datasource/connection/update_builtin_database", json_data=args)

View File

@@ -0,0 +1,151 @@
"""
MQTT 字段关联同步工具(内置源专属,文档 §8
接口前缀为 /system/mqtt注意与 /datasource 不同)。
关于 sourceTable / targetTable 的方向(已真机验证):
直接调后端 API 时按字面透传即可——create 传什么detail 回显就是什么,
字段自洽、后端不做调换。例create 传 sourceTable=sys_user/targetTable=users
detail 读回仍是 sourceTable=sys_user/targetTable=users。
文档 §8 提到的「前端提交时调换」是前端 Vue 组件为 UI 展示所做的转换,
与后端 API 契约无关,本工具(直连 API无需调换。
"""
from ._base import register_tool, ToolDef
@register_tool("list_mqtt_configs")
class ListMqttConfigsTool(ToolDef):
name = "list_mqtt_configs"
description = "获取 MQTT 字段关联同步配置列表"
input_schema = {
"type": "object",
"properties": {
"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 await self.client.get("/system/mqtt/config/list", params=params)
@register_tool("get_mqtt_config_detail")
class GetMqttConfigDetailTool(ToolDef):
name = "get_mqtt_config_detail"
description = "获取 MQTT 同步配置详情"
input_schema = {
"type": "object",
"properties": {
"configId": {"type": "string", "description": "配置 ID"},
},
"required": ["configId"],
}
async def execute(self, args: dict) -> dict:
return await self.client.get(f"/system/mqtt/config/{args['configId']}")
@register_tool("list_mqtt_target_tables")
class ListMqttTargetTablesTool(ToolDef):
name = "list_mqtt_target_tables"
description = "获取 MQTT 可同步的目标表列表"
input_schema = {"type": "object", "properties": {}, "required": []}
async def execute(self, args: dict) -> dict:
return await self.client.get("/system/mqtt/config/tableList")
@register_tool("list_mqtt_target_table_columns")
class ListMqttTargetTableColumnsTool(ToolDef):
name = "list_mqtt_target_table_columns"
description = "获取 MQTT 目标表的字段列表"
input_schema = {
"type": "object",
"properties": {
"tableName": {"type": "string", "description": "目标表名"},
},
"required": ["tableName"],
}
async def execute(self, args: dict) -> dict:
return await self.client.get(f"/system/mqtt/config/tableColumns/{args['tableName']}")
@register_tool("create_mqtt_config")
class CreateMqttConfigTool(ToolDef):
name = "create_mqtt_config"
description = (
"新增 MQTT 字段关联同步配置。"
"sourceTable/targetTable 按字面透传即可(实测确认 create 存入与 detail 回显一致,"
"无需调换文档§8 的调换是前端 UI 层行为,与本 API 无关)"
)
input_schema = {
"type": "object",
"properties": {
"sourceTable": {"type": "string", "description": "源表名"},
"targetTable": {"type": "string", "description": "目标表名"},
"config": {"type": "object", "description": "完整配置体(字段映射等),按后端结构透传"},
},
"required": [],
}
async def execute(self, args: dict) -> dict:
# config 若存在则以其为主体,否则直接用 args去掉 config 包裹键)
body = args.get("config") if isinstance(args.get("config"), dict) else dict(args)
return await self.client.post("/system/mqtt/config", json_data=body)
@register_tool("update_mqtt_config")
class UpdateMqttConfigTool(ToolDef):
name = "update_mqtt_config"
description = (
"修改 MQTT 字段关联同步配置。"
"sourceTable/targetTable 按字面透传即可(实测确认无需调换,详见 create_mqtt_config"
)
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "配置 ID"},
"sourceTable": {"type": "string", "description": "源表名"},
"targetTable": {"type": "string", "description": "目标表名"},
"config": {"type": "object", "description": "完整配置体,按后端结构透传"},
},
"required": ["id"],
}
async def execute(self, args: dict) -> dict:
if isinstance(args.get("config"), dict):
body = dict(args["config"])
body.setdefault("id", args.get("id"))
else:
body = dict(args)
return await self.client.put("/system/mqtt/config", json_data=body)
@register_tool("delete_mqtt_config")
class DeleteMqttConfigTool(ToolDef):
name = "delete_mqtt_config"
description = "删除 MQTT 同步配置(支持多个 id逗号拼接"
input_schema = {
"type": "object",
"properties": {
"ids": {"type": "string", "description": "配置 ID多个用逗号拼接"},
},
"required": ["ids"],
}
async def execute(self, args: dict) -> dict:
return await self.client.delete(f"/system/mqtt/config/{args['ids']}")
@register_tool("refresh_mqtt_config_cache")
class RefreshMqttConfigCacheTool(ToolDef):
name = "refresh_mqtt_config_cache"
description = "刷新 MQTT 同步配置缓存"
input_schema = {"type": "object", "properties": {}, "required": []}
async def execute(self, args: dict) -> dict:
return await self.client.get("/system/mqtt/config/refreshCache")

View File

@@ -119,14 +119,17 @@ class DeleteSkillToolTool(ToolDef):
@register_tool("update_skill_config")
class UpdateSkillConfigTool(ToolDef):
name = "update_skill_config"
description = "更新技能配置(如 MCP Server 配置模板)"
description = "更新技能(名称/描述/MCP 配置模板等)。对应后端 skill/updateOrGet"
input_schema = {
"type": "object",
"properties": {
"datasourceId": {"type": "string", "description": "数据源 ID"},
"configTemplate": {"type": "string", "description": "配置模板 JSON 字符串"},
"skillId": {"type": "string", "description": "技能 ID可选"},
"name": {"type": "string", "description": "技能名称(可选)"},
"description": {"type": "string", "description": "技能描述(可选)"},
"configTemplate": {"type": "string", "description": "配置模板 JSON 字符串(可选)"},
},
"required": ["datasourceId", "configTemplate"],
"required": ["datasourceId"],
}
async def execute(self, args: dict) -> dict:
@@ -135,3 +138,40 @@ class UpdateSkillConfigTool(ToolDef):
if "configTemplate" in args and isinstance(args["configTemplate"], dict):
args["configTemplate"] = json.dumps(args["configTemplate"])
return await self.client.post("/datasource/skill/updateOrGet", json_data=args)
@register_tool("update_skill_tool")
class UpdateSkillToolTool(ToolDef):
name = "update_skill_tool"
description = "修改技能下某个工具的名称/描述/SQL等对应后端 tskilltool/updateOrGetupsert 语义)"
# 真实工具实体字段(已真机探测确认):主键 id、展示名 uniqueName、描述 description、
# SQL 模板 sqlTemplate、结果类型 resultType。后端不认 skillToolId/businessDescription/businessScenario。
input_schema = {
"type": "object",
"properties": {
"id": {"type": "string", "description": "技能工具 IDget_skill_tools 返回的 id"},
"uniqueName": {"type": "string", "description": "工具展示名(可选)"},
"description": {"type": "string", "description": "工具描述(可选)"},
"sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"},
"resultType": {"type": "string", "enum": ["single", "list"], "description": "结果类型(可选)"},
},
"required": ["id"],
}
# 旧参数名 -> 真实字段名(向后兼容此前错误实现的调用方)
_LEGACY_MAP = {
"skillToolId": "id",
"name": "uniqueName",
"businessDescription": "description",
}
async def execute(self, args: dict) -> dict:
args = dict(args)
for old, new in self._LEGACY_MAP.items():
if old in args and new not in args:
args[new] = args.pop(old)
else:
args.pop(old, None)
# businessScenario 后端实体无此字段,丢弃避免干扰
args.pop("businessScenario", None)
return await self.client.post("/datasource/skill/tskilltool/updateOrGet", json_data=args)

View File

@@ -35,4 +35,11 @@ class ExecuteSqlTool(ToolDef):
body["businessName"] = args["businessName"]
if "params" in args:
body["parameters"] = args["params"]
return await self.client.post("/datasource/sqlExecutionLog/testSqlWithSchema", json_data=body)
# target 通过 query 参数下发prod/test 双环境),默认 prod
target = args.get("target", "prod")
params = {"target": target} if target else None
return await self.client.post(
"/datasource/sqlExecutionLog/testSqlWithSchema",
json_data=body,
params=params,
)

View File

@@ -8,22 +8,24 @@ from ._base import register_tool, ToolDef
@register_tool("toggle_table_subscription")
class ToggleTableSubscriptionTool(ToolDef):
name = "toggle_table_subscription"
description = "切换表的订阅状态"
description = "切换表的订阅状态(订阅依赖该表已配置 MQTT 字段关联,否则后端会报操作失败)"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"datasourceId": {"type": "string", "description": "数据源 ID"},
"configId": {"type": "string", "description": "库/配置 ID即 list_databases 返回的 config id"},
"tableName": {"type": "string", "description": "表名(注意:后端按表名而非 tableId 识别)"},
"subscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"},
},
"required": ["tableId", "datasourceId", "subscribe"],
"required": ["configId", "tableName", "subscribe"],
}
async def execute(self, args: dict) -> dict:
# 映射参数名为后端 API 期望的格式
# 后端真实字段已真机探测确认configId + tableName + isSubscribe(bool)
# 兼容旧参数名 datasourceId->configId
config_id = args.get("configId") or args.get("datasourceId")
body = {
"tableId": args["tableId"],
"datasourceId": args["datasourceId"],
"subscribe": args["subscribe"],
"configId": config_id,
"tableName": args.get("tableName"),
"isSubscribe": args.get("subscribe"),
}
return await self.client.post("/datasource/subscription/toggle", json_data=body)

View File

@@ -10,12 +10,12 @@ from ._base import register_tool, ToolDef
@register_tool("query_table_data")
class QueryTableDataTool(ToolDef):
name = "query_table_data"
description = "查询内置表数据(分页)"
description = "查询内置表数据(分页)。target 默认 test调试环境查询线上数据须显式传 target=prod"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "环境,默认 test调试线上数据传 prod"},
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
"pageSize": {"type": "integer", "default": 10, "description": "每页数量"},
},
@@ -25,6 +25,7 @@ class QueryTableDataTool(ToolDef):
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
args.setdefault("target", "test")
params = {k: v for k, v in args.items() if v is not None}
return await self.client.get(f"/datasource/connection/builtin/table/{table_id}", params=params)
@@ -32,12 +33,12 @@ class QueryTableDataTool(ToolDef):
@register_tool("insert_table_row")
class InsertTableRowTool(ToolDef):
name = "insert_table_row"
description = "向内置表插入一行数据"
description = "向内置表插入一行数据。⚠️ 写操作 target 默认 test调试环境写入线上库须显式传 target=prod"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "⚠️ 环境,默认 test调试写入线上库必须显式传 prod"},
"data": {"type": "object", "description": "行数据(键值对,键为字段名)"},
},
"required": ["tableId", "data"],
@@ -46,21 +47,22 @@ class InsertTableRowTool(ToolDef):
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
target = args.pop("target", "test")
data = args.pop("data", {})
params = {"target": target} if target else {}
return await self.client.post(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params)
body = {"data": data}
return await self.client.post(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
@register_tool("update_table_row")
class UpdateTableRowTool(ToolDef):
name = "update_table_row"
description = "更新内置表的指定行"
description = "更新内置表的指定行。⚠️ 写操作 target 默认 test调试环境修改线上库须显式传 target=prod"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "⚠️ 环境,默认 test调试修改线上库必须显式传 prod"},
"primaryKey": {"type": "object", "description": "主键值(如 {\"id\": 1}"},
"data": {"type": "object", "description": "要更新的字段值"},
},
@@ -70,7 +72,7 @@ class UpdateTableRowTool(ToolDef):
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
target = args.pop("target", "test")
primary_key = args.pop("primaryKey")
data = args.pop("data", {})
params = {"target": target} if target else {}
@@ -81,12 +83,12 @@ class UpdateTableRowTool(ToolDef):
@register_tool("delete_table_rows")
class DeleteTableRowsTool(ToolDef):
name = "delete_table_rows"
description = "删除内置表的指定行(根据主键批量删除)"
description = "删除内置表的指定行(根据主键批量删除)。⚠️ 写操作 target 默认 test调试环境删除线上库数据须显式传 target=prod"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "⚠️ 环境,默认 test调试删除线上库数据必须显式传 prod"},
"primaryKeys": {
"type": "array",
"description": "主键数组(如 [{\"id\": 1}, {\"id\": 2}]",
@@ -99,7 +101,7 @@ class DeleteTableRowsTool(ToolDef):
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
target = args.pop("target", "test")
primary_keys = args.pop("primaryKeys")
params = {"target": target} if target else {}
body = {"primaryKeys": primary_keys}
@@ -109,12 +111,12 @@ class DeleteTableRowsTool(ToolDef):
@register_tool("export_table_excel")
class ExportTableExcelTool(ToolDef):
name = "export_table_excel"
description = "导出表数据为 Excel 文件(返回 base64 编码)"
description = "导出表数据为 Excel 文件(返回 base64 编码)。target 默认 test调试环境导出线上数据须显式传 target=prod"
input_schema = {
"type": "object",
"properties": {
"tableId": {"type": "string", "description": "表 ID"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "环境,默认 test调试线上数据传 prod"},
},
"required": ["tableId"],
}
@@ -122,7 +124,7 @@ class ExportTableExcelTool(ToolDef):
async def execute(self, args: dict) -> dict:
args = dict(args)
table_id = args.pop("tableId")
target = args.pop("target", "prod")
target = args.pop("target", "test")
params = {"target": target} if target else {}
result = await self.client.get(f"/datasource/connection/builtin/table/{table_id}/export/excel", params=params)

View File

@@ -64,37 +64,49 @@ class AgileDBAPIClient:
return headers
def _build_url(self, path: str) -> str:
"""构建完整 URL,自动去掉路径中多余的 /api 前缀"""
"""构建完整 URL
约定base_url 已包含完整地址(如含 /api 前缀则配在环境变量里),
各工具传入的 path 不带 /api 前缀,此处直接拼接。
"""
if path.startswith('http://') or path.startswith('https://'):
return path
# 去掉 /api 前缀,因为 base_url 已经包含完整地址
clean_path = path
if clean_path.startswith('/api'):
clean_path = clean_path[4:]
return f"{self.base_url}{clean_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()
# 先尝试解析 body再判断状态码。
# 平台会用非 2xx 状态码 + body 里的 {code, msg} 返回业务错误,
# 若先 raise_for_status() 会在解析 body 前抛异常,导致真正的 msg 全部丢失。
try:
data = response.json()
except json.JSONDecodeError:
# 非 JSON 响应(如文件下载
except (json.JSONDecodeError, UnicodeDecodeError):
# 非 JSON 响应(如 Excel/文件下载,二进制内容 .json() 会抛 UnicodeDecodeError
response.raise_for_status()
return {"success": True, "data": response.content, "raw": True}
# 检查平台 API 的 {code, msg} 格式
# 平台约定code 为 200 或 0 均表示成功(见数据库模块文档 §1.1
if isinstance(data, dict) and 'code' in data:
if data['code'] != 200:
if data['code'] not in (0, 200):
error_msg = data.get('msg', '未知错误')
logger.error(f"[API错误] {error_msg}")
logger.error(f"[API错误] HTTP {response.status_code}, {error_msg}")
raise Exception(error_msg)
# code 合法即视为业务成功,不再用 HTTP 状态码二次否决
return data
# 无 {code} 信封的响应:回落到 HTTP 状态码判断,
# 非 2xx 时把 body 文本带进异常,避免错误细节丢失。
if response.status_code >= 400:
detail = data if isinstance(data, str) else json.dumps(data, ensure_ascii=False)
logger.error(f"[API错误] HTTP {response.status_code}, {detail}")
raise Exception(f"HTTP {response.status_code}: {detail}")
return data
async def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
@@ -147,9 +159,13 @@ class AgileDBAPIClient:
try:
logger.info(f"[API请求] DELETE {url}")
headers = self._get_headers()
# 注意httpx 的 client.delete() 便捷方法不接受 json 参数(仅 post/put/patch 有)。
# 需要带 body 的 DELETE 必须走通用 request(),否则会抛 TypeError。
if json_data is not None:
headers['Content-Type'] = 'application/json'
response = await self.client.delete(url, headers=headers, params=params, json=json_data)
response = await self.client.request("DELETE", url, headers=headers, params=params, json=json_data)
else:
response = await self.client.delete(url, headers=headers, params=params)
return self._handle_response(response, url)
except httpx.TimeoutException:
raise Exception(f"API 请求超时: {url}")

View File

@@ -44,7 +44,7 @@ def get_env_config() -> dict:
dict: 包含所有配置的字典
"""
return {
"api_key": get_api_key(""),
"api_key": os.environ.get("API_KEY", ""),
"base_url": get_base_url(),
}

View File

@@ -0,0 +1,454 @@
# 数据库模块 —— 功能、接口与业务链路说明
> 适用范围:`src/components/databasePage/` 全部组件 + `src/server/database.ts` API 层
> 数据源分两类:**外置数据源external** 与 **内置数据源builtin / 内置 PostgreSQL**
> 所有接口前缀均为 `/api/datasource`MQTT 同步相关为 `/api/system/mqtt`
> 大部分接口超时设置为 `300000ms`5 分钟,因含 AI 处理)
---
## 0. 名词与基础概念
| 名词 | 说明 |
| --- | --- |
| 连接 / 数据源connection | 一条数据库连接记录,`sourceType``external``builtin` |
| 配置config | 数据源下「数据库 + 选中表」的关联配置,一条 config 对应一个库 |
| 表元数据table metadata | 平台侧记录的表结构字段、注释、AI 训练描述等) |
| 技能skill | 绑定到某数据源的「智能问数技能」 |
| 工具tool | 技能下的一条可复用 SQL 查询能力(沉淀为 MCP 工具) |
| 环境target | 内置数据源数据分 `prod`(线上)/ `test`(调试)两套 |
| status | 数据源状态:`0` 运行中,`1` 已停止 |
### 两类数据源核心差异
| 维度 | 外置数据源 external | 内置数据源 builtin |
| --- | --- | --- |
| 来源 | 连接用户已有的远程数据库 | 平台内置 PostgreSQL平台直接建库建表 |
| 默认数字员工 | `1001`SQL处理助手 | `1002`SQL处理助手 |
| 建表能力 | ❌ 只读连接,不能建表 | ✅ AI 智能建表 / Excel 导入建表 / 手动建表 |
| 数据增删改 | ❌ 不提供行级 CRUD | ✅ 行级增删改查 + Excel 导入导出 |
| 环境切换 | ❌ 无 | ✅ prod / test 双环境 |
| 表订阅同步 | ❌ | ✅ MQTT 数据变更订阅 |
| 创建入口 | `CreateDataSource.vue` | `CreateBuiltinDataSource.vue` |
---
## 1. 数据源(连接)的增删改查 —— 内外置通用
入口:[DataSourcePageMain.vue](../src/components/databasePage/DataSourcePageMain.vue) → [DataSourceList.vue](../src/components/databasePage/DataSourceList.vue)
### 1.1 查询(列表 / 详情)
| 功能 | 组件方法 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 数据源列表(分页/搜索/状态筛选) | `fetchDataSources()` | `getConnectionList(params)` | GET | `/api/datasource/connection/list` |
| 数据源详情 | `fetchDataSourceDetail()` | `getConnectionDetail(id)` | GET | `/api/datasource/connection/{id}` |
| 连接实例详情(含库表结构) | — | `getConnectionInstanceDetail(id)` | GET | `/api/datasource/connection/{id}` |
| 查看数据源实时库表结构 | `loadDatabaseList()` | `getConnectionRealtimeStructure(id)` | GET | `/api/datasource/connection/realtime/structure/{id}` |
- 列表查询参数:`{ datasourceName?, status?, pageNum, pageSize }`,前端用手动 `IntersectionObserver` 做无限滚动。
- 响应判定统一:`code === 200 || code === 0` 视为成功,列表数据在 `response.rows`,总数在 `response.total`
### 1.2 启用 / 停用
| 功能 | 组件方法 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 切换运行状态 | `handleChangeStatus()` | `putConnectionChangeStatus({ id, status })` | PUT | `/api/datasource/connection/changeStatus` |
- `status` 切换:`1->0` 启用,`0->1` 停用。
### 1.3 删除
| 功能 | 组件方法 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 删除数据源 | `handleDelete()` | `deleteConnection(id)` | DELETE | `/api/datasource/connection/{id}` |
**业务链路(删除)**
```
用户点删除 → Modal 二次确认
→ 若 status===0运行中先 putConnectionChangeStatus({id, status:1}) 停用
→ 等待 500ms 确保状态更新
→ deleteConnection(id)
→ 成功后 fetchDataSources() 刷新列表
```
---
## 2. 外置数据源external的创建与编辑
入口组件:[CreateDataSource.vue](../src/components/databasePage/CreateDataSource.vue)4 步向导)
### 2.1 步骤与接口
```
步骤0 选择数据库类型mysql/postgresql/oracle/sqlserver/dameng/kingbase/sqlite/mariadb
步骤1 配置连接信息host/port/库名/用户名/密码/认证方式)
→ 点「下一步」自动触发 testConnection() 测试连接
→ 测试通过 → postConnectionDetail() / putConnectionDetail() 创建/更新数据源
→ emit('refresh') 通知列表刷新
→ getConnectionRealtimeStructure() 拉取真实库表结构
步骤2 选择数据库和数据表大数据量shallowRef + 分批渲染 + 防抖搜索)
步骤3 确认信息
→ postConnectionConfig() / putConnectionConfig() 落库「库表关联」配置
→ emit('success')
```
### 2.2 接口清单
| 功能 | 接口函数 | 方法 | 路径 | 关键入参 |
| --- | --- | --- | --- | --- |
| 测试连接 | `testConnection(data)` | POST | `/api/datasource/connection/test` | host/port/databaseName/datasourceType/username/password/connectionType |
| 创建数据源 | `postConnectionDetail(data)` | POST | `/api/datasource/connection` | 同上 + datasourceName/remark |
| 更新数据源 | `putConnectionDetail(data)` | PUT | `/api/datasource/connection` | 同上 + id编辑时密码可空表示不改 |
| 拉取实时库表结构 | `getConnectionRealtimeStructure(id)` | GET | `/api/datasource/connection/realtime/structure/{id}` | 数据源 id |
| 创建库表关联配置 | `postConnectionConfig(data)` | POST | `/api/datasource/config` | connectionId / syncTables / databases[] |
| 更新库表关联配置 | `putConnectionConfig(data)` | PUT | `/api/datasource/config` | id + 同上 |
| 删除配置 | `deleteConnectionConfig(ids[])` | POST | `/api/datasource/config/deletes` | `{ ids: [] }` |
- `postConnectionConfig` 请求体结构:
```json
{
"id": "配置ID编辑传",
"connectionId": "数据源ID",
"syncTables": true,
"databases": [
{ "databaseName": "库名", "tableNames": ["表1", "表2"] }
]
}
```
- 编辑模式不回显密码;密码留空 = 不修改密码。
---
## 3. 内置数据源builtin / 内置 PostgreSQL的创建与编辑
入口组件:[CreateBuiltinDataSource.vue](../src/components/databasePage/CreateBuiltinDataSource.vue)
支持三种模式:`create`(新建数据源)/ `edit`(编辑数据源)/ `addTable`(为已有库新增表)。
### 3.1 步骤与接口create 模式)
```
步骤0 填写数据源信息datasourceName / remark
→ postCreateBuiltinPostgreSQLConnection() 创建内置 PG 连接,返回 connectionId
步骤1 填写业务场景描述 + 数据库名(仅字母数字下划线,自动加 _数据源id 后缀)
→ AI 生成表结构postGenerateTable() 返回 taskId
→ 轮询 getAiTrainingDetail(taskId)trainingStatus: 0待训/1训练中/2完成/3失败最多 60 次 × 2s
→ 解析 createTableData.data.tables 填入表列表
→ postCreateDatabase(connectionId, { databaseName }) 创建数据库
步骤2 预览 / 编辑表结构TableDetailEditor
→ handleSubmit() 遍历表逐张 postCreateTable(connectionId, requestData)
→ emit('refresh') + emit('success')
```
### 3.2 接口清单
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 创建内置 PG 连接 | `postCreateBuiltinPostgreSQLConnection(data)` | POST | `/api/datasource/connection/create_builtin_postgresql` |
| 修改内置 PG 连接 | `putUpdateBuiltinDatabase(data)` | PUT | `/api/datasource/connection/update_builtin_database` |
| AI 生成表结构 | `postGenerateTable(data)` | POST | `/api/datasource/connection/generate_table` |
| 查询 AI 训练任务详情(轮询) | `getAiTrainingDetail(taskId)` | GET | `/api/ai/training/{taskId}` |
| 创建数据库 | `postCreateDatabase(connectionId, data)` | POST | `/api/datasource/connection/{connectionId}/create_database` |
| 创建数据表 | `postCreateTable(connectionId, data)` | POST | `/api/datasource/connection/{connectionId}/create_table` |
| 创建库+表(合并) | `postCreateDatabaseTable(connectionId, data)` | POST | `/api/datasource/connection/{connectionId}/create_database_table` |
- `addTable` 模式不直接调建表接口,而是 `emit('submitTables', tables)` 交回父组件(如 DatabaseDetail提交。
- AI 业务描述润色走 `getAgentGeneric` + `AGENT_GENERIC_CODES.SQL_SCENARIO_DESCRIPTION``server/employee`)。
---
## 4. 数据库 / 表结构的增删改查(主要面向内置源)
入口组件:[DatabaseDetail.vue](../src/components/databasePage/DatabaseDetail.vue)(左侧表列表 + 右侧四视图)
### 4.1 表 / 字段的查询
| 功能 | 组件方法 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 表元数据列表(分页/无限滚动) | `getTablesList()` | `getTableAiList(params)` | GET | `/api/datasource/table/ailist` |
| 表详情(字段列表) | `executeSelectTable()` | `getTableDetail(id)` | GET | `/api/datasource/table/{id}/detail` |
| 表列表(基础) | — | `getTableList(data)` | GET | `/api/datasource/table/list` |
- `getTableAiList` 参数:`{ datasourceId, pageNum, pageSize }`(这里 `datasourceId` 实为「配置/库」id
- 切表有 Race Condition 防护:对比 `currentTableId` 拦截过期的详情请求。
### 4.2 库 / 表的增删改
| 功能 | 组件方法 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 修改数据库(改名) | `handleDatabaseSettingsConfirm()` | `putAlterDatabase(connectionId, data)` | PUT | `/api/datasource/connection/{connectionId}/alter_database` |
| 删除库(配置) | `handleDeleteDatabase()` | `deleteConnectionConfig(ids[])` | POST | `/api/datasource/config/deletes` |
| AI 智能建表(入口) | `createNewTable()` | 仅打开弹窗(内嵌 `CreateBuiltinDataSource` 的 addTable 模式) | — | — |
| 批量建表(提交) | `handleSubmitTables(tables)` | 循环 `postCreateTable(connectionId, data)` | POST | `/api/datasource/connection/{connectionId}/create_table` |
| 修改表结构 | `handleSaveTable()` | `putAlterTable(connectionId, data)` | PUT | `/api/datasource/connection/{connectionId}/alter_table` |
- 修改库后会重新 `getConnectionDetail(id)` 并 `emit('refresh', updatedDataSource)` 同步父级。
- 改表用增量 operation 标记字段:`ADD_COLUMN` / `MODIFY_COLUMN` / `DROP_COLUMN`(来自 TableDetailEditor
### 4.3 智能导入建表Excel
入口组件:[TableRecognition.vue](../src/components/databasePage/TableRecognition.vue)(两步向导)
```
步骤0 上传 Excel≤500KB+ 选环境
→ postImportDocumentPreview(connectionId, file, target) AI 识别前10条 → 表结构预览
步骤1 编辑确认表结构与数据
→ postImportDocumentConfirm(connectionId, data, {target}) 建表 + 插入数据
→ emit('complete')
```
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 文档导入预览AI识别 | `postImportDocumentPreview(connectionId, file, target)` | POST | `/api/datasource/connection/{connectionId}/import_document/preview` |
| 文档导入确认(建表+插数据) | `postImportDocumentConfirm(connectionId, data, params)` | POST | `/api/datasource/connection/{connectionId}/import_document/confirm` |
---
## 5. 内置表「数据行」的增删改查builtin 专属prod/test 双环境)
入口组件:[CustomizeDbTable.vue](../src/components/databasePage/CustomizeDbTable.vue)(包裹通用 `CommonDbTable`
所有接口都带 `target` 参数区分线上 / 调试环境。
| 功能 | 组件回调 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- | --- |
| 查(分页拉数据) | `onFetch` | `getBuiltinTableData(tableId, {pageNum,pageSize,target})` | GET | `/api/datasource/connection/builtin/table/{tableId}` |
| 增(新增行) | `onAdd` | `postBuiltinTableRows(tableId, data, {target})` | POST | `/api/datasource/connection/builtin/table/{tableId}/rows` |
| 改(更新行) | `onEdit` | `putBuiltinTableRows(tableId, data, {target})` | PUT | `/api/datasource/connection/builtin/table/{tableId}/rows` |
| 删(删除行) | `onDelete` | `deleteBuiltinTableRows(tableId, {primaryKeys}, {target})` | DELETE | `/api/datasource/connection/builtin/table/{tableId}/rows` |
| 导出 Excel | `onExport` | `getBuiltinTableExportExcel(tableId, {target})` | GET | `/api/datasource/connection/builtin/table/{tableId}/export/excel` |
| 导入数据 | `onImport` → `handleImportComplete` | `postImportDocumentConfirm(...)` | POST | 见上 |
- `onFetch` 把后端二维数组 `content` 按列名映射成对象数组。
- `onEdit/onDelete` 用动态主键(`rowKey`,默认 `id`)拼装 `primaryKey` / `primaryKeys`。
- 删除入参结构:`{ primaryKeys: [ { 主键字段: 值 }, ... ] }`。
- 导出接口为 `responseType: 'blob'`含完整错误兜底blob 是 JSON 时解析出 msg
---
## 6. 智能问数技能skill与工具tool的增删改查
入口组件:[ChatDebugging.vue](../src/components/databasePage/ChatDebugging.vue) + [SqlControllerMsg.vue](../src/components/databasePage/SqlControllerMsg.vue)
### 6.1 接口清单
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 查询数据源配置(含 skillBool | `getConnectionConfig(id)` | GET | `/api/datasource/config/{id}` |
| 按数据源查技能 | `getSkillByDatasource(id)` | GET | `/api/datasource/skill/getByDatasource/{id}` |
| 按技能 ID 查工具列表 | `getSkillBySkillId(id)` | GET | `/api/datasource/skill/getBySkillId/{id}` |
| 创建技能 | `postSkillCreateOrGet(data)` | POST | `/api/datasource/skill/createOrGet` |
| 修改技能 | `putSkillUpdateOrGet(data)` | POST | `/api/datasource/skill/updateOrGet` |
| 确认/创建技能工具 | `postSqlSkillConfirmTools(data)` | POST | `/api/datasource/skill/confirmTools` |
| 修改工具名/描述 | `postSkillToolUpdateOrGet(data)` | POST | `/api/datasource/skill/tskilltool/updateOrGet` |
| 删除技能工具 | `postDeleteSkillTool(skillToolId)` | DELETE | `/api/datasource/skill/tskilltool/{skillToolId}` |
> 说明:`executeSql`POST `/api/datasource/sqlExecutionLog/testSqlWithSchema`)虽在 `server/database.ts` 中定义,但在 databasePage 模块内**未被任何组件调用**——它仅在工作流编辑器 [workflowPage/EditorMain.vue](../src/components/workflowPage/EditorMain.vue) 里使用。智能问数场景下SQL 由后端 AI 通过 SSE 流执行并直接返回结果,前端不再单独调用执行接口。
### 6.2 「把查询沉淀为工具」业务链路SqlControllerMsg 核心)
```
AI 返回 msgType=5 的 SQL 结果 → SqlControllerMsg 解析多视图
用户点「添加到工具」
→ 校验环境test 禁止)、是否选库、重复检测
→ 分两条路径:
┌ skillBool=true技能已存在
│ → postSqlSkillConfirmTools() 直接确认工具
└ skillBool=false技能不存在
→ postSkillCreateOrGet() 创建技能
→ putSkillUpdateOrGet() 写入 MCP 配置模板 lzwcai-mcp-sqlexecutor
→ postSqlSkillConfirmTools() 确认工具
→ eventBus.emit(RELOAD_SKILL_DATA, datasourceId)
→ ChatDebugging 监听到事件 → loadSkillData() 刷新左侧技能/工具列表
```
- 重复检测链路:`getConnectionConfig → getSkillByDatasource → getSkillBySkillId`,比对 `sqlTemplate`。
---
## 7. AI 补全训练
入口:[DatabaseDetail.vue](../src/components/databasePage/DatabaseDetail.vue)「AI补全管理」视图
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 训练任务列表 | `getAiTrainingList(data)` | GET | `/api/ai/training/list` |
| 按选中表创建训练 | `postAiTrainingCreateBySelected(data)` | POST | `/api/ai/training/createBySelected` |
| 训练任务详情 | `getAiTrainingDetail(id)` | GET | `/api/ai/training/{id}` |
- 训练状态:`0` 待训练 / `1` 训练中 / `2` 已完成 / `3` 失败。
---
## 8. 字段关联同步MQTTbuiltin 专属)
入口:[DatabaseDetail.vue](../src/components/databasePage/DatabaseDetail.vue)「字段关联管理」视图
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 同步配置列表 | `getMqttConfigList(params)` | GET | `/api/system/mqtt/config/list` |
| 配置详情 | `getMqttConfigDetail(configId)` | GET | `/api/system/mqtt/config/{configId}` |
| 目标表列表 | `getMqttConfigTableList()` | GET | `/api/system/mqtt/config/tableList` |
| 目标表字段列表 | `getMqttConfigTableColumns(tableName)` | GET | `/api/system/mqtt/config/tableColumns/{tableName}` |
| 新增配置 | `postMqttConfig(data)` | POST | `/api/system/mqtt/config` |
| 修改配置 | `putMqttConfig(data)` | PUT | `/api/system/mqtt/config` |
| 删除配置 | `deleteMqttConfig(ids)` | DELETE | `/api/system/mqtt/config/{ids}` |
| 刷新缓存 | `getMqttConfigRefreshCache()` | GET | `/api/system/mqtt/config/refreshCache` |
| 切换表订阅同步 | `postDatasourceSubscriptionToggle(data)` | POST | `/api/datasource/subscription/toggle` |
> ⚠️ 注意:提交配置时前端故意调换 `sourceTable` / `targetTable`,回显时再换回来(见 DatabaseDetail 的 `applyRelationConfigByTargetTable` 与 `loadRelationConfigByTableId`)。
---
## 9. API 密钥与权限的增删改查
入口组件:[DataSourceKeys.vue](../src/components/databasePage/DataSourceKeys.vue) + [DataSourceKeySetting.vue](../src/components/databasePage/DataSourceKeySetting.vue)
### 9.1 密钥 CRUD
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 密钥列表 | `getApiKeyList(data)` | GET | `/api/datasource/api_key/list` |
| 新增密钥 | `postApiKey(data)` | POST | `/api/datasource/api_key` |
| 修改密钥(含启停) | `putApiKey(data)` | PUT | `/api/datasource/api_key` |
| 删除密钥 | `deleteApiKey(ids)` | DELETE | `/api/datasource/api_key/{ids}` |
### 9.2 权限配置
| 功能 | 接口函数 | 方法 | 路径 |
| --- | --- | --- | --- |
| 查询密钥权限 | `getApiKeyPermission(apiKeyId)` | GET | `/api/datasource/api_key/permission/{apiKeyId}` |
| 批量授予权限 | `postApiKeyPermissionGrantBatch(data)` | POST | `/api/datasource/api_key/permission/grant_batch` |
权限配置走 `DataSourceKeySetting` 四步向导(数据源 → 数据库 → 数据表分三级权限connection / database / table。其中加载列表复用
| 步骤 | 接口函数 | 路径 |
| --- | --- | --- |
| 选数据源 | `getConnectionList` | `/api/datasource/connection/list` |
| 选数据库 | `getConnectionConfigList` | `/api/datasource/config/list` |
| 选数据表 | `getTableList` | `/api/datasource/table/list` |
`postApiKeyPermissionGrantBatch` 请求体batchDatas每项
```json
{
"apiKeyId": "密钥ID",
"batchDatas": [
{
"connectionId": "数据源ID",
"permissionLevel": "connection|database|table",
"permissionType": "read,write逗号拼接",
"databaseName": "库名database/table 级)",
"tableName": "表名table 级)"
}
]
}
```
---
## 10. 端到端业务链路总览
### 链路 A外置数据源「从创建到智能问数」
```
DataSourcePageMain → 添加远程数据源
→ CreateDataSource 4步:
testConnection → postConnectionDetail → getConnectionRealtimeStructure → postConnectionConfig
→ 列表出现卡片 → 点「业务执行」
→ ChatDebugging员工1001三栏 → DataSourcePlugIn 选库 → ChatBusiness 提问
→ 后端 AI 经 SSE 流执行 SQL 并返回结果 (msgType=5) → SqlControllerMsg 多视图渲染
→ 可「添加到工具」沉淀为技能
```
### 链路 B内置数据源「从建库建表到数据管理」
```
DataSourcePageMain → 添加内置数据源
→ CreateBuiltinDataSource:
postCreateBuiltinPostgreSQLConnection
→ postGenerateTable + 轮询 getAiTrainingDetailAI 建表结构)
→ postCreateDatabase → postCreateTable
→ DatabaseDetail 管理:
getTableAiList表列表→ getTableDetail字段
→ CustomizeDbTable 行级 CRUDprod/test
→ 智能导入 TableRecognitionpreview→confirm
→ 字段关联 MQTT / AI 补全训练 / 订阅
→ 业务执行员工1002智能问数同链路 A
```
### 链路 C对外开放API 密钥)
```
DataSourceKeys → postApiKey 建密钥
→ DataSourceKeySetting 配权限(连接→库→表三级)
→ postApiKeyPermissionGrantBatch 批量授权
→ ApiCallDocument 查看调用文档(/API_DOCUMENTATION.md
→ 外部系统凭密钥调用数据查询
```
---
## 附录:完整接口索引(按 server/database.ts 顺序)
| # | 函数 | 方法 | 路径 | 用途 |
| --- | --- | --- | --- | --- |
| 1 | `getTableDetail` | GET | `/api/datasource/table/{id}/detail` | 表详情 |
| 2 | `getConnectionList` | GET | `/api/datasource/connection/list` | 连接列表 |
| 3 | `getConnectionDetail` | GET | `/api/datasource/connection/{id}` | 连接详情 |
| 4 | `getConnectionInstanceDetail` | GET | `/api/datasource/connection/{id}` | 连接实例详情 |
| 5 | `getTableList` | GET | `/api/datasource/table/list` | 表列表 |
| 6 | `getMqttConfigList` | GET | `/api/system/mqtt/config/list` | MQTT配置列表 |
| 7 | `getMqttConfigDetail` | GET | `/api/system/mqtt/config/{configId}` | MQTT配置详情 |
| 8 | `getMqttConfigTableList` | GET | `/api/system/mqtt/config/tableList` | MQTT目标表 |
| 9 | `getMqttConfigTableColumns` | GET | `/api/system/mqtt/config/tableColumns/{tableName}` | MQTT表字段 |
| 10 | `postMqttConfig` | POST | `/api/system/mqtt/config` | 新增MQTT配置 |
| 11 | `putMqttConfig` | PUT | `/api/system/mqtt/config` | 改MQTT配置 |
| 12 | `deleteMqttConfig` | DELETE | `/api/system/mqtt/config/{ids}` | 删MQTT配置 |
| 13 | `getMqttConfigRefreshCache` | GET | `/api/system/mqtt/config/refreshCache` | 刷新缓存 |
| 14 | `getTableAiList` | GET | `/api/datasource/table/ailist` | 表元数据列表 |
| 15 | `testConnection` | POST | `/api/datasource/connection/test` | 测试连接 |
| 16 | `postConnectionDetail` | POST | `/api/datasource/connection` | 创建连接 |
| 17 | `postCreateBuiltinPostgreSQLConnection` | POST | `/api/datasource/connection/create_builtin_postgresql` | 创建内置PG |
| 18 | `postCreateDatabaseTable` | POST | `/api/datasource/connection/{id}/create_database_table` | 创建库+表 |
| 19 | `postCreateTable` | POST | `/api/datasource/connection/{id}/create_table` | 创建表 |
| 20 | `postCreateDatabase` | POST | `/api/datasource/connection/{id}/create_database` | 创建库 |
| 21 | `putAlterDatabase` | PUT | `/api/datasource/connection/{id}/alter_database` | 改库 |
| 22 | `putAlterTable` | PUT | `/api/datasource/connection/{id}/alter_table` | 改表 |
| 23 | `postGenerateTable` | POST | `/api/datasource/connection/generate_table` | AI生成表结构 |
| 24 | `putUpdateBuiltinDatabase` | PUT | `/api/datasource/connection/update_builtin_database` | 改内置PG |
| 25 | `putConnectionDetail` | PUT | `/api/datasource/connection` | 更新连接 |
| 26 | `deleteConnection` | DELETE | `/api/datasource/connection/{id}` | 删连接 |
| 27 | `deleteConnectionConfig` | POST | `/api/datasource/config/deletes` | 删配置 |
| 28 | `putConnectionConfig` | PUT | `/api/datasource/config` | 改库表配置 |
| 29 | `postConnectionConfig` | POST | `/api/datasource/config` | 建库表配置 |
| 30 | `getConnectionRealtimeStructure` | GET | `/api/datasource/connection/realtime/structure/{id}` | 实时库表结构 |
| 31 | `getSkillByDatasource` | GET | `/api/datasource/skill/getByDatasource/{id}` | 按源查技能 |
| 32 | `getSkillBySkillId` | GET | `/api/datasource/skill/getBySkillId/{id}` | 查工具列表 |
| 33 | `postSkillCreateOrGet` | POST | `/api/datasource/skill/createOrGet` | 建技能 |
| 34 | `putSkillUpdateOrGet` | POST | `/api/datasource/skill/updateOrGet` | 改技能 |
| 35 | `postDeleteSkillTool` | DELETE | `/api/datasource/skill/tskilltool/{id}` | 删工具 |
| 36 | `postSkillToolUpdateOrGet` | POST | `/api/datasource/skill/tskilltool/updateOrGet` | 改工具 |
| 37 | `getAiTrainingList` | GET | `/api/ai/training/list` | 训练列表 |
| 38 | `postAiTrainingCreateBySelected` | POST | `/api/ai/training/createBySelected` | 建训练 |
| 39 | `getAiTrainingDetail` | GET | `/api/ai/training/{id}` | 训练详情 |
| 40 | `putConnectionChangeStatus` | PUT | `/api/datasource/connection/changeStatus` | 启停连接 |
| 41 | `getConnectionConfig` | GET | `/api/datasource/config/{id}` | 查源配置 |
| 42 | `getConnectionConfigList` | GET | `/api/datasource/config/list` | 配置列表 |
| 43 | `postSqlSkillConfirmTools` | POST | `/api/datasource/skill/confirmTools` | 确认工具 |
| 44 | `getApiKeyList` | GET | `/api/datasource/api_key/list` | 密钥列表 |
| 45 | `postApiKey` | POST | `/api/datasource/api_key` | 建密钥 |
| 46 | `putApiKey` | PUT | `/api/datasource/api_key` | 改密钥 |
| 47 | `deleteApiKey` | DELETE | `/api/datasource/api_key/{ids}` | 删密钥 |
| 48 | `getApiKeyPermission` | GET | `/api/datasource/api_key/permission/{apiKeyId}` | 查权限 |
| 49 | `postApiKeyPermissionGrantBatch` | POST | `/api/datasource/api_key/permission/grant_batch` | 批量授权 |
| 50 | `postDatasourceSubscriptionToggle` | POST | `/api/datasource/subscription/toggle` | 订阅开关 |
| 51 | `getBuiltinTableData` | GET | `/api/datasource/connection/builtin/table/{tableId}` | 查内置表数据 |
| 52 | `postBuiltinTableRows` | POST | `/api/datasource/connection/builtin/table/{tableId}/rows` | 增行 |
| 53 | `putBuiltinTableRows` | PUT | `/api/datasource/connection/builtin/table/{tableId}/rows` | 改行 |
| 54 | `deleteBuiltinTableRows` | DELETE | `/api/datasource/connection/builtin/table/{tableId}/rows` | 删行 |
| 55 | `getBuiltinTableExportExcel` | GET | `/api/datasource/connection/builtin/table/{tableId}/export/excel` | 导出Excel |
| 56 | `postImportDocumentPreview` | POST | `/api/datasource/connection/{id}/import_document/preview` | 导入预览 |
| 57 | `postImportDocumentConfirm` | POST | `/api/datasource/connection/{id}/import_document/confirm` | 导入确认 |
| 58 | `executeSql` | POST | `/api/datasource/sqlExecutionLog/testSqlWithSchema` | 执行SQL |

View File

@@ -174,7 +174,7 @@
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| datasourceId | string | 是 | 数据源 ID |
| connectionId | string | 是 | 数据源连接 ID(路径参数) |
| databaseName | string | 是 | 目标数据库名 |
| tableName | string | 是 | 表名(小写字母+数字+下划线) |
| tableComment | string | 否 | 表注释 |
@@ -198,33 +198,29 @@
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 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 | 否 | 新表注释 |
| connectionId | string | 是 | 数据源连接 ID路径参数 |
| databaseName | string | 是 | 数据库名 |
| tableName | string | 是 | 表名 |
| operations | array | 是 | 表结构变更操作数组 |
| operations[].operation | string | | ADD_COLUMN / DROP_COLUMN / RENAME_COLUMN / ALTER_COLUMN_TYPE / SET_NOT_NULL / DROP_NOT_NULL / SET_DEFAULT / DROP_DEFAULT |
| operations[].column | object | | 列定义(根据 operation 不同包含不同字段,如 columnName/columnType/newColumnName 等) |
| tableComment | string | 否 | 新表注释 |
**返回**:修改结果
---
#### 12. `generate_table_by_description`
- **用途**:通过自然语言描述让 AI 生成表结构
- **用途**:通过自然语言描述让 AI 生成表结构(异步任务)
- **对应前端**CreateBuiltinDataSource.vue AI 生成表结构
- **对应 API**`postGenerateTable(data)` ✅ 已实现 — `POST /api/datasource/connection/generate_table`
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| description | string | 是 | 业务场景描述至少6个字符 |
| requirement | string | 是 | 业务需求描述 |
| databaseId | int | 否 | 关联的数据库 ID |
**返回**AI 生成的表结构(表名、表注释、字段列表含类型/主键/注释等)
**返回**异步任务信息taskId、status需轮询任务状态获取最终生成的表结构
> **场景示例**:用户说"我需要一个商城系统,管理商品、分类和用户评价"AI 返回完整的表结构设计。
@@ -389,6 +385,18 @@
---
#### 23.5 `revoke_api_key_permissions`
- **用途**:撤销/删除 API 密钥已授予的权限(按权限记录 ID
- **对应 API**:按现有 delete 风格推测为 `DELETE /api/datasource/api_key/permission/{ids}`,需后端真机验证
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| permissionIds | array[string] | 是 | 权限记录 ID 列表。先从 `get_api_key_permissions` 获取,取 `connectionPermissions` / `databasePermissions` / `tablePermissions` 中每项的 `id` |
**返回**:撤销结果
---
### 🤖 技能与工具管理 (内置数据源 AI 能力)
#### 24. `get_skill_by_datasource`
@@ -437,12 +445,14 @@
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| skillId | string | 是 | 技能 ID |
| name | string | | 工具名称 |
| businessDescription | string | 是 | 业务描述 |
| sqlTemplate | string | 是 | SQL 模板(支持 #{param} 参数占位) |
| sqlParams | string | | 参数 JSON Schema默认空对象 |
| resultType | string | | single/list默认 list |
| businessScenario | string | 否 | 业务场景描述 |
| tableIds | array | | 关联的表 ID 数组 |
| suggestions | array | 是 | SQL 工具建议数组(支持批量) |
| suggestions[].name | string | 是 | 工具名称 |
| suggestions[].businessDescription | string | | 业务描述 |
| suggestions[].sqlTemplate | string | | SQL 模板(支持 #{param} 参数占位) |
| suggestions[].sqlParams | string/object | 否 | 参数 JSON Schema对象会自动序列化为字符串 |
| suggestions[].resultType | string | 否 | single/list默认 list |
| suggestions[].businessScenario | string | 否 | 业务场景描述 |
**返回**:工具创建结果

View File

@@ -7,6 +7,6 @@ from lzwcai_mcp_agile_db.server import main
import os
if __name__ == "__main__":
os.environ["API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg"
os.environ["backendBaseUrl"] = "https://dempdemo.lzwcai.com"
os.environ["API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6ImNlMDAwYjA4LWU0YTYtNGM2MS1hNzJiLWI3NTlmNmY1N2Q4NCJ9.jiNmGQZfL4-nSIFrLuaCt7mT5zj0FOojAVkLeHwPOroI5jBxodrCe1PSwGO1OHq5Ztb0tLEVZw2FFVj0OlTceQ"
os.environ["backendBaseUrl"] = "http://192.168.2.236:8088"
main()

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "lzwcai-mcp-agile-db"
version = "0.1.3"
version = "0.1.7"
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"