feat: 添加数据库管理平台MCP Server
新增lzwcai-mcp-agile-db项目,提供数据库管理、表操作、数据CRUD、 API密钥管理、技能与工具管理等功能。 包含33个工具: - 数据源管理:创建、更新、删除数据源 - 数据库与表管理:表结构操作、数据查询等 - API密钥管理:密钥创建、权限管理等 - 技能与工具管理:SQL工具创建、配置更新等 - 数据导入和SQL执行功能 添加了完整的README文档说明安装使用方法, 以及Python 3.12版本支持和基本项目结构。
This commit is contained in:
85
lzwcai_mcp_agile_db/README.md
Normal file
85
lzwcai_mcp_agile_db/README.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# lzwcai-mcp-agile-db
|
||||||
|
|
||||||
|
数据库管理平台 MCP Server,提供 33 个工具用于数据库管理、表操作、数据 CRUD、API 密钥管理、技能与工具管理等。
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量名 | 必填 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `AGILE_DB_API_KEY` | 是 | 数据库管理平台的 API 密钥 |
|
||||||
|
| `AGILE_DB_BASE_URL` | 否 | 数据库管理平台后端地址(默认 `http://localhost:8080`) |
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置环境变量
|
||||||
|
export AGILE_DB_API_KEY="your-api-key"
|
||||||
|
export AGILE_DB_BASE_URL="http://localhost:8080" # 可选
|
||||||
|
|
||||||
|
# 运行 MCP Server
|
||||||
|
lzwcai-mcp-agile-db
|
||||||
|
```
|
||||||
|
|
||||||
|
## 工具列表
|
||||||
|
|
||||||
|
### 数据源管理
|
||||||
|
- `list_datasources` - 获取数据源列表
|
||||||
|
- `get_datasource_detail` - 获取数据源详情
|
||||||
|
- `create_datasource` - 创建数据源
|
||||||
|
- `update_datasource` - 更新数据源
|
||||||
|
- `toggle_datasource_status` - 启用/停用数据源
|
||||||
|
- `delete_datasource` - 删除数据源
|
||||||
|
|
||||||
|
### 数据库与表管理
|
||||||
|
- `list_databases` - 获取数据库列表
|
||||||
|
- `list_tables` - 获取表列表
|
||||||
|
- `get_table_detail` - 获取表详情
|
||||||
|
- `create_table` - 创建表
|
||||||
|
- `alter_table` - 修改表结构
|
||||||
|
- `generate_table_by_description` - 通过自然语言生成表结构
|
||||||
|
|
||||||
|
### 表数据 CRUD
|
||||||
|
- `query_table_data` - 查询表数据
|
||||||
|
- `insert_table_row` - 插入行数据
|
||||||
|
- `update_table_row` - 更新行数据
|
||||||
|
- `delete_table_rows` - 删除行数据
|
||||||
|
- `export_table_excel` - 导出 Excel
|
||||||
|
|
||||||
|
### API 密钥管理
|
||||||
|
- `list_api_keys` - 获取密钥列表
|
||||||
|
- `create_api_key` - 创建密钥
|
||||||
|
- `toggle_api_key_status` - 启用/禁用密钥
|
||||||
|
- `delete_api_key` - 删除密钥
|
||||||
|
- `get_api_key_permissions` - 查看密钥权限
|
||||||
|
- `grant_api_key_permissions` - 授予权限
|
||||||
|
|
||||||
|
### 技能与工具管理
|
||||||
|
- `get_skill_by_datasource` - 获取技能信息
|
||||||
|
- `get_skill_tools` - 获取技能工具列表
|
||||||
|
- `create_skill` - 创建技能
|
||||||
|
- `create_sql_tool` - 创建 SQL 工具
|
||||||
|
- `delete_skill_tool` - 删除技能工具
|
||||||
|
- `update_skill_config` - 更新技能配置
|
||||||
|
|
||||||
|
### 数据导入
|
||||||
|
- `preview_import_data` - 预览导入数据
|
||||||
|
- `confirm_import_data` - 确认导入数据
|
||||||
|
|
||||||
|
### 表订阅与 SQL 执行
|
||||||
|
- `toggle_table_subscription` - 切换表订阅
|
||||||
|
- `execute_sql` - 执行 SQL 查询
|
||||||
|
|
||||||
|
## 架构
|
||||||
|
|
||||||
|
- `tools/_base.py` - 工具注册装饰器和基类
|
||||||
|
- `tools/*.py` - 工具实现文件
|
||||||
|
- `utils/api_client.py` - 统一 HTTP 客户端
|
||||||
|
- `utils/env_config.py` - 环境变量配置
|
||||||
|
- `utils/logger_config.py` - 日志配置
|
||||||
|
- `server.py` - MCP Server 注册和启动逻辑
|
||||||
1
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version
Normal file
1
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
0
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/README.md
Normal file
0
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/README.md
Normal file
5
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py
Normal file
5
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
"""lzwcai-mcp-agile-db MCP Server 包"""
|
||||||
|
|
||||||
|
from .server import main
|
||||||
|
|
||||||
|
__all__ = ["main"]
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,202 @@
|
|||||||
|
2026-06-11 09:30:49 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
|
||||||
|
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
|
||||||
|
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
|
||||||
|
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
|
||||||
|
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
|
||||||
|
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
|
||||||
|
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
|
||||||
|
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
|
||||||
|
2026-06-11 09:30:49 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
|
||||||
|
2026-06-11 09:30:49 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
|
||||||
|
2026-06-11 09:30:49 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
|
||||||
|
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000025426747BF0>
|
||||||
|
2026-06-11 09:30:50 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
|
||||||
|
2026-06-11 09:30:50 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
|
||||||
|
2026-06-11 09:30:50 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
|
2026-06-11 09:33:24 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
|
||||||
|
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
|
||||||
|
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
|
||||||
|
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
|
||||||
|
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
|
||||||
|
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
|
||||||
|
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
|
||||||
|
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
|
||||||
|
2026-06-11 09:33:24 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
|
||||||
|
2026-06-11 09:33:24 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
|
||||||
|
2026-06-11 09:33:24 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
|
||||||
|
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000020155C7D190>
|
||||||
|
2026-06-11 09:33:25 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
|
||||||
|
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
|
||||||
|
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
|
||||||
|
2026-06-11 09:33:25 - lzwcai_mcp_agile_db.server - INFO - [server.py:56] - ListTools 响应: 返回 33 个工具
|
||||||
|
2026-06-11 09:33:25 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
|
2026-06-11 09:33:45 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x0000020155844410>
|
||||||
|
2026-06-11 09:33:45 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||||
|
2026-06-11 09:33:45 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
|
||||||
|
2026-06-11 09:33:45 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_datasources
|
||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
|
||||||
|
2026-06-11 09:33:46 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
|
||||||
|
2026-06-11 09:33:46 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='D:\\anaconda3\\Library\\ssl\\cacert.pem'
|
||||||
|
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
|
||||||
|
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000020155E9D8E0>
|
||||||
|
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x0000020155E740D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
|
||||||
|
2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000020155D4CFE0>
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:33:40 GMT'), (b'Content-Type', b'application/json;charset=utf-8'), (b'Content-Length', b'51'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
|
||||||
|
2026-06-11 09:33:46 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
|
||||||
|
2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
|
||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
|
||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:91] - [API错误] 登录过期,请重新登录
|
||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.server - ERROR - [server.py:96] - 工具执行失败: list_datasources, 错误: 登录过期,请重新登录
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\server.py", line 84, in handle_call_tool
|
||||||
|
result = await tool_instance.execute(arguments or {})
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\tools\datasources.py", line 30, in execute
|
||||||
|
return self.client.get("/api/datasource/connection/list", params=params)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 102, in get
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 92, in _handle_response
|
||||||
|
raise Exception(error_msg)
|
||||||
|
Exception: 登录过期,请重新登录
|
||||||
|
2026-06-11 09:33:46 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
|
2026-06-11 09:39:21 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
|
||||||
|
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
|
||||||
|
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
|
||||||
|
2026-06-11 09:39:21 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
|
||||||
|
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
|
||||||
|
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
|
||||||
|
2026-06-11 09:39:21 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
|
||||||
|
2026-06-11 09:39:21 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='C:\\Users\\HiWin10\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\certifi\\cacert.pem'
|
||||||
|
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
|
||||||
|
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000027729C00710>
|
||||||
|
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x0000027729DFE9D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
|
||||||
|
2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x0000027729C23170>
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:39:15 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
|
||||||
|
2026-06-11 09:39:21 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list "HTTP/1.1 200 "
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
|
||||||
|
2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
|
||||||
|
2026-06-11 09:39:21 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
|
||||||
|
2026-06-11 09:39:52 - root - INFO - [logger_config.py:102] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\logs
|
||||||
|
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:162] - Initializing server 'lzwcai_mcp_agile_db'
|
||||||
|
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:439] - Registering handler for ListToolsRequest
|
||||||
|
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:519] - Registering handler for CallToolRequest
|
||||||
|
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:124] - ==================================================
|
||||||
|
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:125] - lzwcai-mcp-agile-db MCP Server 启动
|
||||||
|
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:130] - 已注册工具数量: 33
|
||||||
|
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:131] - ==================================================
|
||||||
|
2026-06-11 09:39:52 - lzwcai_mcp_agile_db.server - INFO - [server.py:133] - 开始运行 MCP Server (stdio 模式)
|
||||||
|
2026-06-11 09:39:52 - asyncio - DEBUG - [proactor_events.py:634] - Using proactor: IocpProactor
|
||||||
|
2026-06-11 09:39:52 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: root=InitializedNotification(method='notifications/initialized', params=None, jsonrpc='2.0')
|
||||||
|
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D06C860>
|
||||||
|
2026-06-11 09:39:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type ListToolsRequest
|
||||||
|
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.server - INFO - [server.py:42] - 收到 ListTools 请求
|
||||||
|
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:48] - [客户端初始化] base_url=https://dempdemo.lzwcai.com
|
||||||
|
2026-06-11 09:39:53 - lzwcai_mcp_agile_db.server - INFO - [server.py:56] - ListTools 响应: 返回 33 个工具
|
||||||
|
2026-06-11 09:39:53 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
|
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D188A40>
|
||||||
|
2026-06-11 09:40:08 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||||
|
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
|
||||||
|
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_datasources
|
||||||
|
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/connection/list
|
||||||
|
2026-06-11 09:40:08 - httpx - DEBUG - [_config.py:82] - load_ssl_context verify=True cert=None trust_env=True http2=False
|
||||||
|
2026-06-11 09:40:08 - httpx - DEBUG - [_config.py:148] - load_verify_locations cafile='D:\\anaconda3\\Library\\ssl\\cacert.pem'
|
||||||
|
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
|
||||||
|
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D28C560>
|
||||||
|
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x000002B57D2642D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
|
||||||
|
2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57CF80410>
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:40:02 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
|
||||||
|
2026-06-11 09:40:08 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/connection/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
|
||||||
|
2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
|
||||||
|
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
|
||||||
|
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - INFO - [server.py:86] - 工具执行成功: list_datasources
|
||||||
|
2026-06-11 09:40:08 - lzwcai_mcp_agile_db.server - DEBUG - [server.py:87] - 工具返回结果: {
|
||||||
|
"total": 14,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"createBy": "",
|
||||||
|
"createTime": "2026-06-10 16:47:43",
|
||||||
|
"updateBy": "",
|
||||||
|
"updateTime": "2026-06-10 16:47:43",
|
||||||
|
"remark": "设备报价管理系统包含设备基础信息、预设方案模板、报价单主表和明细表四个核心数据对象,支持从设备参数管理到整套产线方案配置的完整报价流程,为销售部门提供标准化报价服务,实现快速方案生成和精准成本核算。",
|
||||||
|
"id": "58",
|
||||||
|
"enterpriseId": "1937166012193443842",
|
||||||
|
"deptId": "1171",
|
||||||
|
"userId": "292",
|
||||||
|
"host": "host.docker.internal",
|
||||||
|
"port": 5432,
|
||||||
|
"datasourceName": "HMD产品",
|
||||||
|
"database...
|
||||||
|
2026-06-11 09:40:08 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
|
2026-06-11 09:40:55 - mcp.server.lowlevel.server - DEBUG - [server.py:675] - Received message: <mcp.shared.session.RequestResponder object at 0x000002B57D0617C0>
|
||||||
|
2026-06-11 09:40:55 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||||
|
2026-06-11 09:40:55 - mcp.server.lowlevel.server - DEBUG - [server.py:723] - Dispatching request of type CallToolRequest
|
||||||
|
2026-06-11 09:40:55 - lzwcai_mcp_agile_db.server - INFO - [server.py:66] - 收到 CallTool 请求: name=list_api_keys
|
||||||
|
2026-06-11 09:40:55 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:100] - [API请求] GET https://dempdemo.lzwcai.com/api/datasource/api_key/list
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - close.started
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - close.complete
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.started host='dempdemo.lzwcai.com' port=443 local_address=None timeout=30.0 socket_options=None
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - connect_tcp.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D2623C0>
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context=<ssl.SSLContext object at 0x000002B57D2642D0> server_hostname='dempdemo.lzwcai.com' timeout=30.0
|
||||||
|
2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.complete return_value=<httpcore._backends.sync.SyncStream object at 0x000002B57D2626F0>
|
||||||
|
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.complete
|
||||||
|
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_body.complete
|
||||||
|
2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_headers.complete return_value=(b'HTTP/1.1', 200, b'', [(b'Server', b'nginx/1.25.2'), (b'Date', b'Thu, 11 Jun 2026 01:40:49 GMT'), (b'Content-Type', b'application/json'), (b'Transfer-Encoding', b'chunked'), (b'Connection', b'keep-alive'), (b'Vary', b'Origin'), (b'Vary', b'Access-Control-Request-Method'), (b'Vary', b'Access-Control-Request-Headers'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block'), (b'X-Frame-Options', b'SAMEORIGIN'), (b'Strict-Transport-Security', b'max-age=31536000; includeSubDomains'), (b'X-Frame-Options', b'DENY'), (b'X-Content-Type-Options', b'nosniff'), (b'X-XSS-Protection', b'1; mode=block')])
|
||||||
|
2026-06-11 09:40:56 - httpx - INFO - [_client.py:1038] - HTTP Request: GET https://dempdemo.lzwcai.com/api/datasource/api_key/list?pageNum=1&pageSize=20 "HTTP/1.1 200 "
|
||||||
|
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.started request=<Request [b'GET']>
|
||||||
|
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - receive_response_body.complete
|
||||||
|
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.started
|
||||||
|
2026-06-11 09:40:56 - httpcore.http11 - DEBUG - [_trace.py:47] - response_closed.complete
|
||||||
|
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.utils.api_client - INFO - [api_client.py:74] - [API响应] HTTP 200
|
||||||
|
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.server - INFO - [server.py:86] - 工具执行成功: list_api_keys
|
||||||
|
2026-06-11 09:40:56 - lzwcai_mcp_agile_db.server - DEBUG - [server.py:87] - 工具返回结果: {
|
||||||
|
"total": 6,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"createBy": "",
|
||||||
|
"createTime": "2026-06-06 15:10:31",
|
||||||
|
"updateBy": "",
|
||||||
|
"updateTime": "2026-06-06 15:10:31",
|
||||||
|
"remark": null,
|
||||||
|
"id": "7",
|
||||||
|
"apiKey": "Lb8LgEJ7eBUU8QMifKUJvo9w6YLAotbKJ-w1DKU8ZrU",
|
||||||
|
"apiKeyName": "AWINBEXT",
|
||||||
|
"enterpriseId": "1937166012193443842",
|
||||||
|
"status": 0,
|
||||||
|
"expireTime": "2027-06-06T15:10:32.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"createBy": "",
|
||||||
|
"createTime": "2026-05-25 14:47:11",
|
||||||
|
"u...
|
||||||
|
2026-06-11 09:40:56 - mcp.server.lowlevel.server - DEBUG - [server.py:790] - Response sent
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.utils.api_client - ERROR - [api_client.py:91] - [API错误] 登录过期,请重新登录
|
||||||
|
2026-06-11 09:33:46 - lzwcai_mcp_agile_db.server - ERROR - [server.py:96] - 工具执行失败: list_datasources, 错误: 登录过期,请重新登录
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\server.py", line 84, in handle_call_tool
|
||||||
|
result = await tool_instance.execute(arguments or {})
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\tools\datasources.py", line 30, in execute
|
||||||
|
return self.client.get("/api/datasource/connection/list", params=params)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 102, in get
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_agile_db\lzwcai_mcp_agile_db\utils\api_client.py", line 92, in _handle_response
|
||||||
|
raise Exception(error_msg)
|
||||||
|
Exception: 登录过期,请重新登录
|
||||||
6
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py
Normal file
6
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def main():
|
||||||
|
print("Hello from lzwcai-mcp-agile-db!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
7
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml
Normal file
7
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[project]
|
||||||
|
name = "lzwcai-mcp-agile-db"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
138
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py
Normal file
138
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
lzwcai-mcp-agile-db MCP Server
|
||||||
|
数据库管理平台 MCP 工具服务,提供 33 个工具用于数据库管理、表操作、API 密钥管理等
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import anyio
|
||||||
|
|
||||||
|
import mcp.types as types
|
||||||
|
from mcp.server import NotificationOptions, Server
|
||||||
|
from mcp.server.models import InitializationOptions
|
||||||
|
from mcp.server.stdio import stdio_server
|
||||||
|
|
||||||
|
from .utils.logger_config import setup_system_logging, get_logger
|
||||||
|
from .utils.api_client import AgileDBAPIClient
|
||||||
|
from .tools._base import get_registered_tools
|
||||||
|
|
||||||
|
# 初始化日志系统
|
||||||
|
setup_system_logging(app_name="lzwcai_mcp_agile_db", log_level=logging.DEBUG)
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# 初始化 MCP Server
|
||||||
|
server = Server("lzwcai_mcp_agile_db")
|
||||||
|
|
||||||
|
# 全局 API 客户端
|
||||||
|
_api_client: AgileDBAPIClient = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_client() -> AgileDBAPIClient:
|
||||||
|
"""获取或创建 API 客户端"""
|
||||||
|
global _api_client
|
||||||
|
if _api_client is None:
|
||||||
|
_api_client = AgileDBAPIClient()
|
||||||
|
return _api_client
|
||||||
|
|
||||||
|
|
||||||
|
@server.list_tools()
|
||||||
|
async def handle_list_tools() -> list[types.Tool]:
|
||||||
|
"""列出所有可用工具"""
|
||||||
|
logger.info("收到 ListTools 请求")
|
||||||
|
|
||||||
|
tools = []
|
||||||
|
for tool_cls in get_registered_tools():
|
||||||
|
instance = tool_cls(get_api_client())
|
||||||
|
tool_def = instance.to_tool_def()
|
||||||
|
tools.append(
|
||||||
|
types.Tool(
|
||||||
|
name=tool_def["name"],
|
||||||
|
description=tool_def["description"],
|
||||||
|
inputSchema=tool_def["inputSchema"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"ListTools 响应: 返回 {len(tools)} 个工具")
|
||||||
|
return tools
|
||||||
|
|
||||||
|
|
||||||
|
@server.call_tool()
|
||||||
|
async def handle_call_tool(
|
||||||
|
name: str,
|
||||||
|
arguments: dict | None,
|
||||||
|
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
||||||
|
"""调用工具"""
|
||||||
|
logger.info(f"收到 CallTool 请求: name={name}")
|
||||||
|
|
||||||
|
# 查找对应的工具类
|
||||||
|
tool_cls = None
|
||||||
|
for cls in get_registered_tools():
|
||||||
|
if cls.name == name:
|
||||||
|
tool_cls = cls
|
||||||
|
break
|
||||||
|
|
||||||
|
if tool_cls is None:
|
||||||
|
logger.error(f"未找到工具: {name}")
|
||||||
|
raise ValueError(f"未知工具: {name}")
|
||||||
|
|
||||||
|
# 创建工具实例并执行
|
||||||
|
client = get_api_client()
|
||||||
|
tool_instance = tool_cls(client)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = await tool_instance.execute(arguments or {})
|
||||||
|
|
||||||
|
logger.info(f"工具执行成功: {name}")
|
||||||
|
logger.debug(f"工具返回结果: {json.dumps(result, ensure_ascii=False, indent=2)[:500]}...")
|
||||||
|
|
||||||
|
return [
|
||||||
|
types.TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(result, ensure_ascii=False, indent=2),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"工具执行失败: {name}, 错误: {e}", exc_info=True)
|
||||||
|
return [
|
||||||
|
types.TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps({"error": str(e), "tool_name": name}, ensure_ascii=False, indent=2),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def run_server():
|
||||||
|
"""运行 MCP Server (stdio 模式)"""
|
||||||
|
async with stdio_server() as streams:
|
||||||
|
await server.run(
|
||||||
|
streams[0],
|
||||||
|
streams[1],
|
||||||
|
InitializationOptions(
|
||||||
|
server_name="lzwcai_mcp_agile_db",
|
||||||
|
server_version="0.1.0",
|
||||||
|
capabilities=server.get_capabilities(
|
||||||
|
notification_options=NotificationOptions(),
|
||||||
|
experimental_capabilities={},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主入口"""
|
||||||
|
logger.info("=" * 50)
|
||||||
|
logger.info("lzwcai-mcp-agile-db MCP Server 启动")
|
||||||
|
|
||||||
|
# 导入所有工具模块(触发装饰器注册)
|
||||||
|
from . import tools # noqa: F401
|
||||||
|
|
||||||
|
logger.info(f"已注册工具数量: {len(get_registered_tools())}")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
|
||||||
|
logger.info("开始运行 MCP Server (stdio 模式)")
|
||||||
|
anyio.run(run_server)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
18
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py
Normal file
18
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
工具自动发现模块
|
||||||
|
自动导入 tools/ 目录下所有工具模块,触发 @register_tool 装饰器注册
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import pkgutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 获取当前包路径
|
||||||
|
_package_path = Path(__file__).parent
|
||||||
|
|
||||||
|
# 遍历所有 Python 文件(排除 __init__.py 和 _base.py)
|
||||||
|
for _, module_name, _ in pkgutil.iter_modules([str(_package_path)]):
|
||||||
|
if module_name.startswith("_"):
|
||||||
|
continue
|
||||||
|
# 动态导入模块,触发装饰器
|
||||||
|
importlib.import_module(f".{module_name}", __package__)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
116
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py
Normal file
116
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
"""
|
||||||
|
工具注册框架
|
||||||
|
提供 ToolDef 基类和 @register_tool 装饰器,用于声明式定义 MCP 工具
|
||||||
|
"""
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, Any, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..utils.api_client import AgileDBAPIClient
|
||||||
|
|
||||||
|
|
||||||
|
# 全局工具注册表
|
||||||
|
_registered_tools = []
|
||||||
|
|
||||||
|
|
||||||
|
def register_tool(name: str):
|
||||||
|
"""
|
||||||
|
工具注册装饰器
|
||||||
|
|
||||||
|
使用方式:
|
||||||
|
@register_tool("list_datasources")
|
||||||
|
class ListDatasourcesTool(ToolDef):
|
||||||
|
name = "list_datasources"
|
||||||
|
description = "获取数据源列表"
|
||||||
|
input_schema = {...}
|
||||||
|
|
||||||
|
async def execute(self, args):
|
||||||
|
return await self.client.get("/api/...", params=args)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 工具名称(唯一标识)
|
||||||
|
"""
|
||||||
|
def decorator(cls):
|
||||||
|
# 确保类有正确的 name 属性
|
||||||
|
if not hasattr(cls, 'name') or cls.name != name:
|
||||||
|
cls.name = name
|
||||||
|
|
||||||
|
# 注册到全局列表,避免重复注册
|
||||||
|
if cls not in _registered_tools:
|
||||||
|
_registered_tools.append(cls)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class ToolDef(ABC):
|
||||||
|
"""
|
||||||
|
工具定义基类
|
||||||
|
|
||||||
|
所有工具都应继承此类并使用 @register_tool 装饰器注册
|
||||||
|
|
||||||
|
类属性:
|
||||||
|
name: 工具名称(唯一标识)
|
||||||
|
description: 工具描述
|
||||||
|
input_schema: JSON Schema 格式的工具输入参数定义
|
||||||
|
|
||||||
|
实例属性:
|
||||||
|
client: AgileDBAPIClient 实例(由 server 注入)
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str = ""
|
||||||
|
description: str = ""
|
||||||
|
input_schema: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
|
||||||
|
|
||||||
|
def __init__(self, client: "AgileDBAPIClient"):
|
||||||
|
"""
|
||||||
|
初始化工具实例
|
||||||
|
|
||||||
|
Args:
|
||||||
|
client: API 客户端实例(由 server 注入)
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
执行工具逻辑
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: 工具输入参数(已校验)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 执行结果,将作为 MCP 工具返回值
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def to_tool_def(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
转换为 MCP 工具定义格式
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: MCP types.Tool 所需的参数
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"name": self.name,
|
||||||
|
"description": self.description,
|
||||||
|
"inputSchema": self.input_schema,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_registered_tools() -> list:
|
||||||
|
"""
|
||||||
|
获取所有已注册的工具类列表
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: 所有被 @register_tool 装饰的类
|
||||||
|
"""
|
||||||
|
return list(_registered_tools)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_registered_tools():
|
||||||
|
"""
|
||||||
|
清空所有已注册的工具(主要用于测试)
|
||||||
|
"""
|
||||||
|
_registered_tools.clear()
|
||||||
121
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py
Normal file
121
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"""
|
||||||
|
API 密钥管理工具 (工具 18-23)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("list_api_keys")
|
||||||
|
class ListApiKeysTool(ToolDef):
|
||||||
|
name = "list_api_keys"
|
||||||
|
description = "获取 API 密钥列表"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKeyName": {"type": "string", "description": "密钥名称模糊搜索"},
|
||||||
|
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
|
||||||
|
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
params = {k: v for k, v in args.items() if v is not None}
|
||||||
|
return self.client.get("/api/datasource/api_key/list", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("create_api_key")
|
||||||
|
class CreateApiKeyTool(ToolDef):
|
||||||
|
name = "create_api_key"
|
||||||
|
description = "创建新的 API 密钥"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKeyName": {"type": "string", "description": "密钥名称(最多50字)"},
|
||||||
|
},
|
||||||
|
"required": ["apiKeyName"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/api_key", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("toggle_api_key_status")
|
||||||
|
class ToggleApiKeyStatusTool(ToolDef):
|
||||||
|
name = "toggle_api_key_status"
|
||||||
|
description = "启用/禁用 API 密钥"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string", "description": "密钥 ID"},
|
||||||
|
"status": {"type": "integer", "enum": [0, 1], "description": "0=启用, 1=禁用"},
|
||||||
|
},
|
||||||
|
"required": ["id", "status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.put("/api/datasource/api_key", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("delete_api_key")
|
||||||
|
class DeleteApiKeyTool(ToolDef):
|
||||||
|
name = "delete_api_key"
|
||||||
|
description = "删除 API 密钥"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string", "description": "密钥 ID"},
|
||||||
|
},
|
||||||
|
"required": ["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.delete(f"/api/datasource/api_key/{args['id']}")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("get_api_key_permissions")
|
||||||
|
class GetApiKeyPermissionsTool(ToolDef):
|
||||||
|
name = "get_api_key_permissions"
|
||||||
|
description = "查看指定密钥的权限配置"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKeyId": {"type": "string", "description": "密钥 ID"},
|
||||||
|
},
|
||||||
|
"required": ["apiKeyId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.get(f"/api/datasource/api_key/permission/{args['apiKeyId']}")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("grant_api_key_permissions")
|
||||||
|
class GrantApiKeyPermissionsTool(ToolDef):
|
||||||
|
name = "grant_api_key_permissions"
|
||||||
|
description = "批量为 API 密钥授予权限"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiKeyId": {"type": "string", "description": "密钥 ID"},
|
||||||
|
"batchDatas": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "权限批量数据数组",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"connectionId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"permissionLevel": {"type": "string", "enum": ["connection", "database", "table"], "description": "权限级别"},
|
||||||
|
"permissionType": {"type": "string", "description": "权限类型(逗号分隔)"},
|
||||||
|
"databaseName": {"type": "string", "description": "数据库名(level=database/table 时)"},
|
||||||
|
"tableName": {"type": "string", "description": "表名(level=table 时)"},
|
||||||
|
},
|
||||||
|
"required": ["connectionId", "permissionLevel", "permissionType"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["apiKeyId", "batchDatas"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/api_key/permission/grant_batch", json_data=args)
|
||||||
72
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py
Normal file
72
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
"""
|
||||||
|
数据导入工具 (工具 30-31)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import io
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("preview_import_data")
|
||||||
|
class PreviewImportDataTool(ToolDef):
|
||||||
|
name = "preview_import_data"
|
||||||
|
description = "上传 Excel 文件,AI 智能识别并预览表结构/数据"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"connectionId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "test", "description": "环境,默认 test"},
|
||||||
|
"file_base64": {"type": "string", "description": "Excel 文件 base64 编码(.xlsx/.xls, <500KB)"},
|
||||||
|
"file_name": {"type": "string", "description": "文件名(如 data.xlsx)"},
|
||||||
|
},
|
||||||
|
"required": ["connectionId", "file_base64"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
connection_id = args.pop("connectionId")
|
||||||
|
target = args.pop("target", "test")
|
||||||
|
file_base64 = args.pop("file_base64")
|
||||||
|
file_name = args.pop("file_name", "import.xlsx")
|
||||||
|
|
||||||
|
# 解码 base64 文件
|
||||||
|
file_content = base64.b64decode(file_base64)
|
||||||
|
|
||||||
|
# 构建文件上传
|
||||||
|
files = {
|
||||||
|
"file": (file_name, io.BytesIO(file_content), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.client.upload(
|
||||||
|
f"/api/datasource/connection/{connection_id}/import_document/preview",
|
||||||
|
files=files,
|
||||||
|
params={"target": target},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("confirm_import_data")
|
||||||
|
class ConfirmImportDataTool(ToolDef):
|
||||||
|
name = "confirm_import_data"
|
||||||
|
description = "确认导入 AI 识别后的数据"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"connectionId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "description": "环境"},
|
||||||
|
"data": {"type": "object", "description": "导入数据(含 tableStructure + allData)"},
|
||||||
|
},
|
||||||
|
"required": ["connectionId", "data"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
connection_id = args.pop("connectionId")
|
||||||
|
target = args.pop("target", "test")
|
||||||
|
data = args.pop("data")
|
||||||
|
|
||||||
|
return self.client.post(
|
||||||
|
f"/api/datasource/connection/{connection_id}/import_document/confirm",
|
||||||
|
json_data=data,
|
||||||
|
params={"target": target},
|
||||||
|
)
|
||||||
151
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py
Normal file
151
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
"""
|
||||||
|
数据库与表管理工具 (工具 7-12)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("list_databases")
|
||||||
|
class ListDatabasesTool(ToolDef):
|
||||||
|
name = "list_databases"
|
||||||
|
description = "获取指定数据源下的数据库列表"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
|
||||||
|
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
|
||||||
|
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
params = {k: v for k, v in args.items() if v is not None}
|
||||||
|
return self.client.get("/api/datasource/config/list", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("list_tables")
|
||||||
|
class ListTablesTool(ToolDef):
|
||||||
|
name = "list_tables"
|
||||||
|
description = "获取指定数据源下的表列表"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
|
||||||
|
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
|
||||||
|
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
params = {k: v for k, v in args.items() if v is not None}
|
||||||
|
return self.client.get("/api/datasource/table/list", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("get_table_detail")
|
||||||
|
class GetTableDetailTool(ToolDef):
|
||||||
|
name = "get_table_detail"
|
||||||
|
description = "获取表的完整结构信息(字段列表、主键、类型等)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
},
|
||||||
|
"required": ["tableId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
table_id = args["tableId"]
|
||||||
|
return self.client.get(f"/api/datasource/table/{table_id}/detail")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("create_table")
|
||||||
|
class CreateTableTool(ToolDef):
|
||||||
|
name = "create_table"
|
||||||
|
description = "在指定数据库创建新表"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"connectionId": {"type": "string", "description": "数据源连接 ID"},
|
||||||
|
"databaseName": {"type": "string", "description": "目标数据库名"},
|
||||||
|
"tableName": {"type": "string", "description": "表名(小写字母+数字+下划线)"},
|
||||||
|
"tableComment": {"type": "string", "description": "表注释"},
|
||||||
|
"columns": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "字段定义数组",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"columnName": {"type": "string", "description": "字段名"},
|
||||||
|
"columnType": {"type": "string", "description": "字段类型(VARCHAR/INTEGER/SERIAL等)"},
|
||||||
|
"columnLength": {"type": "integer", "description": "字段长度"},
|
||||||
|
"isPrimaryKey": {"type": "boolean", "description": "是否主键"},
|
||||||
|
"isNullable": {"type": "boolean", "description": "是否可空"},
|
||||||
|
"isAutoIncrement": {"type": "boolean", "description": "是否自增"},
|
||||||
|
"columnComment": {"type": "string", "description": "字段注释"},
|
||||||
|
"defaultValue": {"type": "string", "description": "默认值"},
|
||||||
|
},
|
||||||
|
"required": ["columnName", "columnType"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["connectionId", "databaseName", "tableName", "columns"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
connection_id = args.pop("connectionId")
|
||||||
|
return self.client.post(f"/api/datasource/connection/{connection_id}/create_table", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("alter_table")
|
||||||
|
class AlterTableTool(ToolDef):
|
||||||
|
name = "alter_table"
|
||||||
|
description = "修改已有表结构(增/改/删字段)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"connectionId": {"type": "string", "description": "数据源连接 ID"},
|
||||||
|
"databaseName": {"type": "string", "description": "数据库名"},
|
||||||
|
"tableName": {"type": "string", "description": "表名"},
|
||||||
|
"operations": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "表结构变更操作数组",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"operation": {"type": "string", "enum": ["ADD_COLUMN", "DROP_COLUMN", "RENAME_COLUMN", "ALTER_COLUMN_TYPE", "SET_NOT_NULL", "DROP_NOT_NULL", "SET_DEFAULT", "DROP_DEFAULT"], "description": "变更类型"},
|
||||||
|
"column": {"type": "object", "description": "列定义(根据 operation 不同包含不同字段)"},
|
||||||
|
},
|
||||||
|
"required": ["operation", "column"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tableComment": {"type": "string", "description": "表注释"},
|
||||||
|
},
|
||||||
|
"required": ["connectionId", "databaseName", "tableName", "operations"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
connection_id = args.pop("connectionId")
|
||||||
|
return self.client.put(f"/api/datasource/connection/{connection_id}/alter_table", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("generate_table_by_description")
|
||||||
|
class GenerateTableByDescriptionTool(ToolDef):
|
||||||
|
name = "generate_table_by_description"
|
||||||
|
description = "通过自然语言描述让 AI 生成表结构(异步任务)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"requirement": {"type": "string", "description": "业务需求描述"},
|
||||||
|
"databaseId": {"type": "integer", "description": "关联的数据库 ID(可选)"},
|
||||||
|
},
|
||||||
|
"required": ["requirement"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/connection/generate_table", json_data=args)
|
||||||
173
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py
Normal file
173
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
"""
|
||||||
|
数据源管理工具 (工具 1-6)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("list_datasources")
|
||||||
|
class ListDatasourcesTool(ToolDef):
|
||||||
|
name = "list_datasources"
|
||||||
|
description = "获取数据源列表,支持搜索和状态筛选"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceName": {"type": "string", "description": "数据源名称模糊搜索"},
|
||||||
|
"status": {"type": "integer", "description": "0=运行中, 1=已停止, 不传=全部"},
|
||||||
|
"sourceType": {"type": "string", "enum": ["builtin", "external"], "description": "数据源类型"},
|
||||||
|
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
|
||||||
|
"pageSize": {"type": "integer", "default": 20, "description": "每页数量"},
|
||||||
|
},
|
||||||
|
"required": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
params = {k: v for k, v in args.items() if v is not None}
|
||||||
|
return self.client.get("/api/datasource/connection/list", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("get_datasource_detail")
|
||||||
|
class GetDatasourceDetailTool(ToolDef):
|
||||||
|
name = "get_datasource_detail"
|
||||||
|
description = "获取单个数据源的完整详情(含配置和实时结构)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
ds_id = args["datasourceId"]
|
||||||
|
result = {}
|
||||||
|
# 获取基本信息
|
||||||
|
try:
|
||||||
|
result["detail"] = self.client.get(f"/api/datasource/connection/{ds_id}")
|
||||||
|
except Exception as e:
|
||||||
|
result["detail"] = {"error": str(e)}
|
||||||
|
# 获取配置
|
||||||
|
try:
|
||||||
|
result["config"] = self.client.get(f"/api/datasource/config/{ds_id}")
|
||||||
|
except Exception as e:
|
||||||
|
result["config"] = {"error": str(e)}
|
||||||
|
# 获取实时结构
|
||||||
|
try:
|
||||||
|
result["structure"] = self.client.get(f"/api/datasource/connection/realtime/structure/{ds_id}")
|
||||||
|
except Exception as e:
|
||||||
|
result["structure"] = {"error": str(e)}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("create_datasource")
|
||||||
|
class CreateDatasourceTool(ToolDef):
|
||||||
|
name = "create_datasource"
|
||||||
|
description = "创建外部数据源连接(可选先测试连接)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceName": {"type": "string", "description": "数据源名称(3-20字)"},
|
||||||
|
"datasourceType": {"type": "string", "enum": ["mysql", "postgresql", "oracle", "sqlserver", "dameng"], "description": "数据库类型"},
|
||||||
|
"host": {"type": "string", "description": "数据库地址"},
|
||||||
|
"port": {"type": "integer", "description": "端口号"},
|
||||||
|
"databaseName": {"type": "string", "description": "要连接的数据库名"},
|
||||||
|
"username": {"type": "string", "description": "数据库用户名"},
|
||||||
|
"password": {"type": "string", "description": "密码"},
|
||||||
|
"remark": {"type": "string", "description": "数据源描述"},
|
||||||
|
"connectionType": {"type": "string", "enum": ["user_password", "ssl"], "description": "连接类型,默认 user_password"},
|
||||||
|
"test_first": {"type": "boolean", "default": True, "description": "是否先测试连接,默认 true"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceName", "datasourceType", "host", "port", "databaseName", "username"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
test_first = args.pop("test_first", True)
|
||||||
|
|
||||||
|
# 如果需要先测试连接
|
||||||
|
if test_first:
|
||||||
|
test_data = {
|
||||||
|
"datasourceName": args.get("datasourceName"),
|
||||||
|
"datasourceType": args.get("datasourceType"),
|
||||||
|
"host": args.get("host"),
|
||||||
|
"port": args.get("port"),
|
||||||
|
"databaseName": args.get("databaseName"),
|
||||||
|
"username": args.get("username"),
|
||||||
|
"password": args.get("password"),
|
||||||
|
"connectionType": args.get("connectionType", "user_password"),
|
||||||
|
}
|
||||||
|
test_result = self.client.post("/api/datasource/connection/test", json_data=test_data)
|
||||||
|
if test_result.get("code") != 200:
|
||||||
|
return {"success": False, "error": f"连接测试失败: {test_result.get('msg', '未知错误')}"}
|
||||||
|
|
||||||
|
# 创建数据源
|
||||||
|
return self.client.post("/api/datasource/connection", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("update_datasource")
|
||||||
|
class UpdateDatasourceTool(ToolDef):
|
||||||
|
name = "update_datasource"
|
||||||
|
description = "更新数据源连接信息"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"datasourceName": {"type": "string", "description": "更新名称"},
|
||||||
|
"host": {"type": "string", "description": "更新地址"},
|
||||||
|
"port": {"type": "integer", "description": "更新端口"},
|
||||||
|
"databaseName": {"type": "string", "description": "更新数据库名"},
|
||||||
|
"username": {"type": "string", "description": "更新用户名"},
|
||||||
|
"password": {"type": "string", "description": "新密码(不传则不变)"},
|
||||||
|
"remark": {"type": "string", "description": "更新描述"},
|
||||||
|
},
|
||||||
|
"required": ["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.put("/api/datasource/connection", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("toggle_datasource_status")
|
||||||
|
class ToggleDatasourceStatusTool(ToolDef):
|
||||||
|
name = "toggle_datasource_status"
|
||||||
|
description = "启用/停用数据源"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"status": {"type": "integer", "enum": [0, 1], "description": "0=启用, 1=停用"},
|
||||||
|
},
|
||||||
|
"required": ["id", "status"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.put("/api/datasource/connection/changeStatus", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("delete_datasource")
|
||||||
|
class DeleteDatasourceTool(ToolDef):
|
||||||
|
name = "delete_datasource"
|
||||||
|
description = "删除数据源(运行中会自动先停用)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {"type": "string", "description": "数据源 ID"},
|
||||||
|
},
|
||||||
|
"required": ["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
ds_id = args["id"]
|
||||||
|
# 先尝试停用(仅忽略已停用等预期错误)
|
||||||
|
try:
|
||||||
|
self.client.put("/api/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1})
|
||||||
|
except Exception as e:
|
||||||
|
# 记录日志但继续删除
|
||||||
|
logger.debug(f"停用数据源失败(可能已停用): {e}")
|
||||||
|
|
||||||
|
# 删除数据源
|
||||||
|
return self.client.delete(f"/api/datasource/connection/{ds_id}")
|
||||||
137
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py
Normal file
137
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
技能与工具管理工具 (工具 24-29)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("get_skill_by_datasource")
|
||||||
|
class GetSkillByDatasourceTool(ToolDef):
|
||||||
|
name = "get_skill_by_datasource"
|
||||||
|
description = "根据数据源获取技能信息"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.get(f"/api/datasource/skill/getByDatasource/{args['datasourceId']}")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("get_skill_tools")
|
||||||
|
class GetSkillToolsTool(ToolDef):
|
||||||
|
name = "get_skill_tools"
|
||||||
|
description = "获取技能下的工具列表"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"skillId": {"type": "string", "description": "技能 ID"},
|
||||||
|
},
|
||||||
|
"required": ["skillId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.get(f"/api/datasource/skill/getBySkillId/{args['skillId']}")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("create_skill")
|
||||||
|
class CreateSkillTool(ToolDef):
|
||||||
|
name = "create_skill"
|
||||||
|
description = "为数据源创建技能"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"name": {"type": "string", "description": "技能名称(不传则自动生成)"},
|
||||||
|
"description": {"type": "string", "description": "技能描述"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/skill/createOrGet", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("create_sql_tool")
|
||||||
|
class CreateSqlToolTool(ToolDef):
|
||||||
|
name = "create_sql_tool"
|
||||||
|
description = "将 SQL 查询创建为可复用工具(支持批量)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"skillId": {"type": "string", "description": "技能 ID"},
|
||||||
|
"tableIds": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "关联的表 ID 数组",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
},
|
||||||
|
"suggestions": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "SQL 工具建议数组",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string", "description": "工具名称"},
|
||||||
|
"businessDescription": {"type": "string", "description": "业务描述"},
|
||||||
|
"sqlTemplate": {"type": "string", "description": "SQL 模板(支持 #{param} 参数占位)"},
|
||||||
|
"sqlParams": {"type": "string", "description": "参数 JSON Schema(JSON 字符串或对象)"},
|
||||||
|
"resultType": {"type": "string", "enum": ["single", "list"], "default": "list", "description": "结果类型,默认 list"},
|
||||||
|
"businessScenario": {"type": "string", "description": "业务场景描述"},
|
||||||
|
},
|
||||||
|
"required": ["name", "businessDescription", "sqlTemplate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["skillId", "suggestions"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
# 处理 suggestions 中的 sqlParams
|
||||||
|
if "suggestions" in args and isinstance(args["suggestions"], list):
|
||||||
|
for suggestion in args["suggestions"]:
|
||||||
|
if "sqlParams" in suggestion and isinstance(suggestion["sqlParams"], dict):
|
||||||
|
suggestion["sqlParams"] = json.dumps(suggestion["sqlParams"])
|
||||||
|
return self.client.post("/api/datasource/skill/confirmTools", json_data=args)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("delete_skill_tool")
|
||||||
|
class DeleteSkillToolTool(ToolDef):
|
||||||
|
name = "delete_skill_tool"
|
||||||
|
description = "删除技能下的工具"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"skillToolId": {"type": "string", "description": "工具 ID"},
|
||||||
|
},
|
||||||
|
"required": ["skillToolId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.delete(f"/api/datasource/skill/tskilltool/{args['skillToolId']}")
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("update_skill_config")
|
||||||
|
class UpdateSkillConfigTool(ToolDef):
|
||||||
|
name = "update_skill_config"
|
||||||
|
description = "更新技能配置(如 MCP Server 配置模板)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"configTemplate": {"type": "string", "description": "配置模板 JSON 字符串"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId", "configTemplate"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
# 如果 configTemplate 是 dict,转为 JSON 字符串
|
||||||
|
if "configTemplate" in args and isinstance(args["configTemplate"], dict):
|
||||||
|
args["configTemplate"] = json.dumps(args["configTemplate"])
|
||||||
|
return self.client.post("/api/datasource/skill/updateOrGet", json_data=args)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
SQL 执行工具 (工具 33)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("execute_sql")
|
||||||
|
class ExecuteSqlTool(ToolDef):
|
||||||
|
name = "execute_sql"
|
||||||
|
description = "执行原生 SQL 查询"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"datasourceId": {"type": "string", "description": "数据源 ID"},
|
||||||
|
"executableSql": {"type": "string", "description": "SQL 语句"},
|
||||||
|
"sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"},
|
||||||
|
"businessName": {"type": "string", "description": "业务名称(可选)"},
|
||||||
|
"parameters": {"type": "object", "description": "参数定义(可选)"},
|
||||||
|
},
|
||||||
|
"required": ["datasourceId", "executableSql"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/sqlExecutionLog/testSqlWithSchema", json_data=args)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
表订阅工具 (工具 32)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("toggle_table_subscription")
|
||||||
|
class ToggleTableSubscriptionTool(ToolDef):
|
||||||
|
name = "toggle_table_subscription"
|
||||||
|
description = "切换表的订阅状态"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"configId": {"type": "string", "description": "数据库配置 ID"},
|
||||||
|
"tableName": {"type": "string", "description": "表名"},
|
||||||
|
"isSubscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"},
|
||||||
|
},
|
||||||
|
"required": ["configId", "tableName", "isSubscribe"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
return self.client.post("/api/datasource/subscription/toggle", json_data=args)
|
||||||
137
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py
Normal file
137
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
表数据 CRUD 工具 (工具 13-17)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from ._base import register_tool, ToolDef
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("query_table_data")
|
||||||
|
class QueryTableDataTool(ToolDef):
|
||||||
|
name = "query_table_data"
|
||||||
|
description = "查询内置表数据(分页)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||||
|
"pageNum": {"type": "integer", "default": 1, "description": "页码"},
|
||||||
|
"pageSize": {"type": "integer", "default": 10, "description": "每页数量"},
|
||||||
|
},
|
||||||
|
"required": ["tableId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
table_id = args.pop("tableId")
|
||||||
|
params = {k: v for k, v in args.items() if v is not None}
|
||||||
|
return self.client.get(f"/api/datasource/connection/builtin/table/{table_id}", params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("insert_table_row")
|
||||||
|
class InsertTableRowTool(ToolDef):
|
||||||
|
name = "insert_table_row"
|
||||||
|
description = "向内置表插入一行数据"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||||
|
"data": {"type": "object", "description": "行数据(键值对,键为字段名)"},
|
||||||
|
},
|
||||||
|
"required": ["tableId", "data"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
table_id = args.pop("tableId")
|
||||||
|
target = args.pop("target", "prod")
|
||||||
|
data = args.pop("data", {})
|
||||||
|
params = {"target": target} if target else {}
|
||||||
|
return self.client.post(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("update_table_row")
|
||||||
|
class UpdateTableRowTool(ToolDef):
|
||||||
|
name = "update_table_row"
|
||||||
|
description = "更新内置表的指定行"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||||
|
"primaryKey": {"type": "object", "description": "主键值(如 {\"id\": 1})"},
|
||||||
|
"data": {"type": "object", "description": "要更新的字段值"},
|
||||||
|
},
|
||||||
|
"required": ["tableId", "primaryKey", "data"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
table_id = args.pop("tableId")
|
||||||
|
target = args.pop("target", "prod")
|
||||||
|
primary_key = args.pop("primaryKey")
|
||||||
|
data = args.pop("data", {})
|
||||||
|
params = {"target": target} if target else {}
|
||||||
|
body = {"primaryKey": primary_key, "data": data}
|
||||||
|
return self.client.put(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("delete_table_rows")
|
||||||
|
class DeleteTableRowsTool(ToolDef):
|
||||||
|
name = "delete_table_rows"
|
||||||
|
description = "删除内置表的指定行(根据主键批量删除)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||||
|
"primaryKeys": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "主键数组(如 [{\"id\": 1}, {\"id\": 2}])",
|
||||||
|
"items": {"type": "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["tableId", "primaryKeys"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
table_id = args.pop("tableId")
|
||||||
|
target = args.pop("target", "prod")
|
||||||
|
primary_keys = args.pop("primaryKeys")
|
||||||
|
params = {"target": target} if target else {}
|
||||||
|
body = {"primaryKeys": primary_keys}
|
||||||
|
return self.client.delete(f"/api/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params)
|
||||||
|
|
||||||
|
|
||||||
|
@register_tool("export_table_excel")
|
||||||
|
class ExportTableExcelTool(ToolDef):
|
||||||
|
name = "export_table_excel"
|
||||||
|
description = "导出表数据为 Excel 文件(返回 base64 编码)"
|
||||||
|
input_schema = {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tableId": {"type": "string", "description": "表 ID"},
|
||||||
|
"target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"},
|
||||||
|
},
|
||||||
|
"required": ["tableId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, args: dict) -> dict:
|
||||||
|
args = dict(args)
|
||||||
|
table_id = args.pop("tableId")
|
||||||
|
target = args.pop("target", "prod")
|
||||||
|
params = {"target": target} if target else {}
|
||||||
|
result = self.client.get(f"/api/datasource/connection/builtin/table/{table_id}/export/excel", params=params)
|
||||||
|
|
||||||
|
# 处理二进制响应
|
||||||
|
if result.get("raw"):
|
||||||
|
content = result["data"]
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"file_base64": base64.b64encode(content).decode("utf-8"),
|
||||||
|
"message": "Excel 文件已导出,请解码 base64 内容获取文件",
|
||||||
|
}
|
||||||
|
return result
|
||||||
13
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py
Normal file
13
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from .env_config import get_api_key, get_base_url, get_env_config
|
||||||
|
from .logger_config import setup_system_logging, get_logger
|
||||||
|
from .api_client import AgileDBAPIClient, get_default_client
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'get_api_key',
|
||||||
|
'get_base_url',
|
||||||
|
'get_env_config',
|
||||||
|
'setup_system_logging',
|
||||||
|
'get_logger',
|
||||||
|
'AgileDBAPIClient',
|
||||||
|
'get_default_client',
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
196
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py
Normal file
196
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
"""
|
||||||
|
数据库管理平台 API 调用客户端
|
||||||
|
用于调用数据库管理平台的所有 API 接口
|
||||||
|
"""
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
from .env_config import get_api_key, get_base_url
|
||||||
|
from .logger_config import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# 默认超时配置(秒)
|
||||||
|
DEFAULT_TIMEOUT = 30.0
|
||||||
|
|
||||||
|
|
||||||
|
class AgileDBAPIClient:
|
||||||
|
"""数据库管理平台 API 客户端"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_url: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
default_timeout: float = DEFAULT_TIMEOUT,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
初始化 API 客户端
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_url: API 基础 URL(默认从环境变量 AGILE_DB_BASE_URL 读取)
|
||||||
|
api_key: API 密钥(默认从环境变量 AGILE_DB_API_KEY 读取)
|
||||||
|
default_timeout: 请求超时时间(秒),默认 30 秒
|
||||||
|
"""
|
||||||
|
if base_url is None:
|
||||||
|
base_url = get_base_url()
|
||||||
|
|
||||||
|
if api_key is None:
|
||||||
|
api_key = get_api_key()
|
||||||
|
|
||||||
|
self.base_url = base_url.rstrip('/')
|
||||||
|
self.api_key = api_key
|
||||||
|
self.default_timeout = default_timeout
|
||||||
|
self._client: Optional[httpx.Client] = None
|
||||||
|
|
||||||
|
logger.info(f"[客户端初始化] base_url={self.base_url}")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> httpx.Client:
|
||||||
|
"""懒加载 HTTP 客户端"""
|
||||||
|
if self._client is None:
|
||||||
|
self._client = httpx.Client(timeout=self.default_timeout)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def _get_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
||||||
|
"""获取请求头"""
|
||||||
|
headers = {
|
||||||
|
'Authorization': self.api_key if self.api_key.startswith('Bearer ') else f'Bearer {self.api_key}',
|
||||||
|
}
|
||||||
|
if extra_headers:
|
||||||
|
headers.update(extra_headers)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def _build_url(self, path: str) -> str:
|
||||||
|
"""构建完整 URL"""
|
||||||
|
if path.startswith('http://') or path.startswith('https://'):
|
||||||
|
return path
|
||||||
|
return f"{self.base_url}{path}"
|
||||||
|
|
||||||
|
def _handle_response(self, response: httpx.Response, url: str) -> Dict[str, Any]:
|
||||||
|
"""统一处理 API 响应"""
|
||||||
|
logger.info(f"[API响应] HTTP {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 204:
|
||||||
|
return {"success": True, "data": None}
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# 非 JSON 响应(如文件下载)
|
||||||
|
return {"success": True, "data": response.content, "raw": True}
|
||||||
|
|
||||||
|
# 检查平台 API 的 {code, msg} 格式
|
||||||
|
if isinstance(data, dict) and 'code' in data:
|
||||||
|
if data['code'] != 200:
|
||||||
|
error_msg = data.get('msg', '未知错误')
|
||||||
|
logger.error(f"[API错误] {error_msg}")
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""发送 GET 请求"""
|
||||||
|
url = self._build_url(path)
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] GET {url}")
|
||||||
|
response = self.client.get(url, headers=self._get_headers(), params=params)
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise Exception(f"API 请求超时: {url}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
def post(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""发送 POST 请求"""
|
||||||
|
url = self._build_url(path)
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] POST {url}")
|
||||||
|
headers = self._get_headers({'Content-Type': 'application/json'})
|
||||||
|
response = self.client.post(url, headers=headers, json=json_data, params=params)
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise Exception(f"API 请求超时: {url}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
def put(self, path: str, json_data: Optional[Dict[str, Any]] = None, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""发送 PUT 请求"""
|
||||||
|
url = self._build_url(path)
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] PUT {url}")
|
||||||
|
headers = self._get_headers({'Content-Type': 'application/json'})
|
||||||
|
response = self.client.put(url, headers=headers, json=json_data, params=params)
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise Exception(f"API 请求超时: {url}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
def delete(self, path: str, params: Optional[Dict[str, Any]] = None, json_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""发送 DELETE 请求"""
|
||||||
|
url = self._build_url(path)
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] DELETE {url}")
|
||||||
|
headers = self._get_headers()
|
||||||
|
if json_data is not None:
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
response = self.client.delete(url, headers=headers, params=params, json=json_data)
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise Exception(f"API 请求超时: {url}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
def upload(self, path: str, files: Dict[str, Any], params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
|
"""发送文件上传请求(multipart/form-data)"""
|
||||||
|
url = self._build_url(path)
|
||||||
|
try:
|
||||||
|
logger.info(f"[API请求] UPLOAD {url}")
|
||||||
|
# 文件上传不需要 Content-Type,httpx 会自动设置 multipart/form-data
|
||||||
|
headers = self._get_headers()
|
||||||
|
response = self.client.post(url, headers=headers, files=files, params=params)
|
||||||
|
return self._handle_response(response, url)
|
||||||
|
except httpx.TimeoutException:
|
||||||
|
raise Exception(f"API 请求超时: {url}")
|
||||||
|
except httpx.HTTPStatusError as e:
|
||||||
|
raise Exception(f"API 请求失败 (HTTP {e.response.status_code}): {url}")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
raise Exception(f"API 请求异常: {url}, 错误: {str(e)}")
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""关闭 HTTP 客户端"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.close()
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# 懒加载的默认客户端
|
||||||
|
_default_client: Optional[AgileDBAPIClient] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_default_client() -> AgileDBAPIClient:
|
||||||
|
"""获取默认客户端(懒加载)"""
|
||||||
|
global _default_client
|
||||||
|
if _default_client is None:
|
||||||
|
_default_client = AgileDBAPIClient()
|
||||||
|
return _default_client
|
||||||
60
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py
Normal file
60
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"""环境变量配置模块 - 数据库管理平台 MCP Server"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_key(default: Optional[str] = None) -> str:
|
||||||
|
"""
|
||||||
|
获取数据库管理平台 API 密钥
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default: 默认值(可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: API 密钥
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当 AGILE_DB_API_KEY 未设置且无默认值时
|
||||||
|
"""
|
||||||
|
value = os.environ.get("AGILE_DB_API_KEY", default or "")
|
||||||
|
if not value:
|
||||||
|
raise ValueError("环境变量 AGILE_DB_API_KEY 未设置")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_url(default: str = "http://localhost:8080") -> str:
|
||||||
|
"""
|
||||||
|
获取数据库管理平台后端地址
|
||||||
|
|
||||||
|
Args:
|
||||||
|
default: 默认值(默认 http://localhost:8080)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 后端 API 基础 URL
|
||||||
|
"""
|
||||||
|
return os.environ.get("AGILE_DB_BASE_URL", default)
|
||||||
|
|
||||||
|
|
||||||
|
def get_env_config() -> dict:
|
||||||
|
"""
|
||||||
|
获取所有环境配置
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 包含所有配置的字典
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"api_key": get_api_key(""),
|
||||||
|
"base_url": get_base_url(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def set_env_variable(key: str, value: str) -> None:
|
||||||
|
"""
|
||||||
|
设置环境变量(仅在当前进程中有效)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: 环境变量名
|
||||||
|
value: 环境变量值
|
||||||
|
"""
|
||||||
|
os.environ[key] = value
|
||||||
121
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py
Normal file
121
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
统一日志配置模块
|
||||||
|
提供系统级别的日志配置和管理
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerConfig:
|
||||||
|
"""日志配置管理类"""
|
||||||
|
|
||||||
|
def __init__(self, logs_dir: str = None):
|
||||||
|
"""初始化日志配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
logs_dir: 日志目录路径,默认为项目根目录下的logs文件夹
|
||||||
|
"""
|
||||||
|
if logs_dir:
|
||||||
|
self.logs_dir = Path(logs_dir)
|
||||||
|
else:
|
||||||
|
project_root = Path(__file__).parent.parent
|
||||||
|
self.logs_dir = project_root / "logs"
|
||||||
|
|
||||||
|
self.logs_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
self.log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
||||||
|
self.date_format = '%Y-%m-%d %H:%M:%S'
|
||||||
|
self.log_level = self._get_log_level_from_env()
|
||||||
|
self._initialized = False
|
||||||
|
|
||||||
|
def _get_log_level_from_env(self) -> int:
|
||||||
|
log_level_str = os.getenv('LOG_LEVEL', 'INFO').upper()
|
||||||
|
level_mapping = {
|
||||||
|
'DEBUG': logging.DEBUG,
|
||||||
|
'INFO': logging.INFO,
|
||||||
|
'WARNING': logging.WARNING,
|
||||||
|
'WARN': logging.WARNING,
|
||||||
|
'ERROR': logging.ERROR,
|
||||||
|
'CRITICAL': logging.CRITICAL,
|
||||||
|
'FATAL': logging.CRITICAL
|
||||||
|
}
|
||||||
|
return level_mapping.get(log_level_str, logging.INFO)
|
||||||
|
|
||||||
|
def setup_logging(self,
|
||||||
|
app_name: str = "lzwcai_mcp_agile_db",
|
||||||
|
log_level: int = logging.INFO,
|
||||||
|
max_file_size: int = 10 * 1024 * 1024,
|
||||||
|
backup_count: int = 5,
|
||||||
|
console_output: bool = True) -> logging.Logger:
|
||||||
|
if self._initialized:
|
||||||
|
return logging.getLogger()
|
||||||
|
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(log_level)
|
||||||
|
|
||||||
|
for handler in root_logger.handlers[:]:
|
||||||
|
root_logger.removeHandler(handler)
|
||||||
|
|
||||||
|
formatter = logging.Formatter(self.log_format, self.date_format)
|
||||||
|
|
||||||
|
# 1. 主日志文件 - 按大小滚动
|
||||||
|
main_log_file = self.logs_dir / f"{app_name}.log"
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
main_log_file,
|
||||||
|
maxBytes=max_file_size,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
file_handler.setLevel(log_level)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# 2. 错误日志文件
|
||||||
|
error_log_file = self.logs_dir / f"{app_name}_error.log"
|
||||||
|
error_handler = RotatingFileHandler(
|
||||||
|
error_log_file,
|
||||||
|
maxBytes=max_file_size,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding='utf-8'
|
||||||
|
)
|
||||||
|
error_handler.setLevel(logging.ERROR)
|
||||||
|
error_handler.setFormatter(formatter)
|
||||||
|
root_logger.addHandler(error_handler)
|
||||||
|
|
||||||
|
# 3. 控制台输出 (MCP协议使用stdio时,必须将日志输出到stderr)
|
||||||
|
if console_output:
|
||||||
|
console_handler = logging.StreamHandler(sys.stderr)
|
||||||
|
console_handler.setLevel(log_level)
|
||||||
|
console_formatter = logging.Formatter(
|
||||||
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
self.date_format
|
||||||
|
)
|
||||||
|
console_handler.setFormatter(console_formatter)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
root_logger.info(f"日志系统初始化完成 - 日志目录: {self.logs_dir}")
|
||||||
|
|
||||||
|
return root_logger
|
||||||
|
|
||||||
|
def get_module_logger(self, name: str) -> logging.Logger:
|
||||||
|
"""获取模块级别的 logger(继承根配置)"""
|
||||||
|
return logging.getLogger(name)
|
||||||
|
|
||||||
|
|
||||||
|
# 全局日志配置实例
|
||||||
|
logger_config = LoggerConfig()
|
||||||
|
|
||||||
|
|
||||||
|
def setup_system_logging(app_name: str = "lzwcai_mcp_agile_db",
|
||||||
|
log_level: int = logging.INFO) -> logging.Logger:
|
||||||
|
return logger_config.setup_logging(app_name, log_level)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(name: str) -> logging.Logger:
|
||||||
|
return logger_config.get_module_logger(name)
|
||||||
621
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md
Normal file
621
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md
Normal file
@@ -0,0 +1,621 @@
|
|||||||
|
# 数据库管理平台 - MCP 工具设计方案
|
||||||
|
|
||||||
|
> 基于平台现有功能,设计可供外部 AI Agent 通过 MCP 协议调用的工具集。
|
||||||
|
> 目标:**用户脱离平台界面,通过 MCP 调用即可使用数据库管理平台的核心功能。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、设计原则
|
||||||
|
|
||||||
|
1. **按用户场景分组**:不是简单映射 API,而是围绕用户真实工作流组织工具
|
||||||
|
2. **最小化调用链**:复杂操作尽量合并为一个 tool,减少多轮调用
|
||||||
|
3. **读写分离**:查询类工具可安全暴露,写操作需明确提示
|
||||||
|
4. **环境感知**:所有操作默认携带环境参数(prod/test),内置数据源特有
|
||||||
|
5. **权限前置**:调用方需提供 apiKeyId 或等效凭证,工具内部自动鉴权
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、工具清单
|
||||||
|
|
||||||
|
### 🗄️ 数据源管理
|
||||||
|
|
||||||
|
#### 1. `list_datasources`
|
||||||
|
- **用途**:获取数据源列表,支持搜索和状态筛选
|
||||||
|
- **对应前端**:DataSourceList.vue
|
||||||
|
- **对应 API**:`getConnectionList` ✅ 已实现
|
||||||
|
- **端点**:`GET /api/datasource/connection/list`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceName | string | 否 | 数据源名称模糊搜索 |
|
||||||
|
| status | int | 否 | 0=运行中, 1=已停止, 不传=全部 |
|
||||||
|
| sourceType | string | 否 | builtin/external |
|
||||||
|
| pageNum | int | 否 | 默认 1 |
|
||||||
|
| pageSize | int | 否 | 默认 20 |
|
||||||
|
|
||||||
|
**返回**:数据源列表(含名称、类型、状态、库数、表数、创建时间等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. `get_datasource_detail`
|
||||||
|
- **用途**:获取单个数据源的完整详情
|
||||||
|
- **对应前端**:DatabaseDetail.vue 头部
|
||||||
|
- **对应 API**:
|
||||||
|
- `getConnectionDetail(id)` ✅ 已实现 — `GET /api/datasource/connection/{id}`
|
||||||
|
- `getConnectionConfig(id)` ✅ 已实现 — `GET /api/datasource/config/{id}`
|
||||||
|
- `getConnectionRealtimeStructure(id)` ✅ 已实现 — `GET /api/datasource/connection/realtime/structure/{id}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
|
||||||
|
**返回**:数据源详情 + 数据库配置列表 + 实时结构(数据库列表、表列表、字段信息)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. `create_datasource`
|
||||||
|
- **用途**:创建外部数据源连接
|
||||||
|
- **对应前端**:CreateDataSource.vue
|
||||||
|
- **对应 API**:
|
||||||
|
- `testConnection(data)` ✅ 已实现 — `POST /api/datasource/connection/test`
|
||||||
|
- `postConnectionDetail(data)` ✅ 已实现 — `POST /api/datasource/connection`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceName | string | 是 | 数据源名称(3-20字) |
|
||||||
|
| datasourceType | string | 是 | mysql/postgresql/oracle/sqlserver/dameng |
|
||||||
|
| host | string | 是 | 数据库地址 |
|
||||||
|
| port | int | 是 | 端口号 |
|
||||||
|
| databaseName | string | 是 | 要连接的数据库名 |
|
||||||
|
| username | string | 是 | 数据库用户名 |
|
||||||
|
| password | string | 否 | 密码 |
|
||||||
|
| remark | string | 否 | 数据源描述 |
|
||||||
|
| connectionType | string | 否 | user_password/ssl,默认 user_password |
|
||||||
|
| test_first | bool | 否 | 是否先测试连接,默认 true |
|
||||||
|
|
||||||
|
**返回**:创建结果,含数据源 ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4. `update_datasource`
|
||||||
|
- **用途**:更新数据源连接信息
|
||||||
|
- **对应 API**:`putConnectionDetail(data)` ✅ 已实现 — `PUT /api/datasource/connection`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | string | 是 | 数据源 ID |
|
||||||
|
| datasourceName | string | 否 | 更新名称 |
|
||||||
|
| host | string | 否 | 更新地址 |
|
||||||
|
| port | int | 否 | 更新端口 |
|
||||||
|
| databaseName | string | 否 | 更新数据库名 |
|
||||||
|
| username | string | 否 | 更新用户名 |
|
||||||
|
| password | string | 否 | 新密码(不传则不变) |
|
||||||
|
| remark | string | 否 | 更新描述 |
|
||||||
|
|
||||||
|
**返回**:更新结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 5. `toggle_datasource_status`
|
||||||
|
- **用途**:启用/停用数据源
|
||||||
|
- **对应 API**:`putConnectionChangeStatus(data)` ✅ 已实现 — `PUT /api/datasource/connection/changeStatus`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | string | 是 | 数据源 ID |
|
||||||
|
| status | int | 是 | 0=启用, 1=停用 |
|
||||||
|
|
||||||
|
**返回**:操作结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 6. `delete_datasource`
|
||||||
|
- **用途**:删除数据源(运行中会自动先停用)
|
||||||
|
- **对应 API**:
|
||||||
|
- `putConnectionChangeStatus(data)` ✅ 已实现 — `PUT /api/datasource/connection/changeStatus`
|
||||||
|
- `deleteConnection(id)` ✅ 已实现 — `DELETE /api/datasource/connection/{id}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | string | 是 | 数据源 ID |
|
||||||
|
|
||||||
|
**返回**:删除结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📊 数据库与表管理
|
||||||
|
|
||||||
|
#### 7. `list_databases`
|
||||||
|
- **用途**:获取指定数据源下的数据库列表
|
||||||
|
- **对应 API**:`getConnectionConfigList(data)` ✅ 已实现 — `GET /api/datasource/config/list`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
|
||||||
|
**返回**:数据库列表(ID、名称、类型、状态、表数)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 8. `list_tables`
|
||||||
|
- **用途**:获取指定数据库下的表列表
|
||||||
|
- **对应前端**:DatabaseDetail.vue 左侧表列表
|
||||||
|
- **对应 API**:`getTableList(data)` ✅ 已实现 — `GET /api/datasource/table/list`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| databaseName | string | 否 | 数据库名过滤 |
|
||||||
|
| keyword | string | 否 | 表名模糊搜索 |
|
||||||
|
| pageNum | int | 否 | 默认 1 |
|
||||||
|
| pageSize | int | 否 | 默认 20 |
|
||||||
|
|
||||||
|
**返回**:表列表(表名、注释、字段数、创建时间等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 9. `get_table_detail`
|
||||||
|
- **用途**:获取表的完整结构信息
|
||||||
|
- **对应前端**:DatabaseDetail.vue 右侧字段列表
|
||||||
|
- **对应 API**:`getTableDetail(id)` ✅ 已实现 — `GET /api/datasource/table/{id}/detail`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
|
||||||
|
**返回**:表名、表注释、字段列表(字段名、类型、长度、主键、可空、默认值、注释、AI训练状态)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 10. `create_table`
|
||||||
|
- **用途**:在指定数据库创建新表
|
||||||
|
- **对应前端**:CreateBuiltinDataSource.vue 表结构编辑器
|
||||||
|
- **对应 API**:`postCreateTable(connectionId, data)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/create_table`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| databaseName | string | 是 | 目标数据库名 |
|
||||||
|
| tableName | string | 是 | 表名(小写字母+数字+下划线) |
|
||||||
|
| tableComment | string | 否 | 表注释 |
|
||||||
|
| columns | array | 是 | 字段定义数组 |
|
||||||
|
| columns[].columnName | string | 是 | 字段名 |
|
||||||
|
| columns[].columnType | string | 是 | 字段类型(VARCHAR/INTEGER/SERIAL等) |
|
||||||
|
| columns[].columnLength | int | 否 | 字段长度 |
|
||||||
|
| columns[].isPrimaryKey | bool | 否 | 是否主键 |
|
||||||
|
| columns[].isNullable | bool | 否 | 是否可空 |
|
||||||
|
| columns[].isAutoIncrement | bool | 否 | 是否自增 |
|
||||||
|
| columns[].columnComment | string | 否 | 字段注释 |
|
||||||
|
| columns[].defaultValue | string | 否 | 默认值 |
|
||||||
|
|
||||||
|
**返回**:创建结果,含表 ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 11. `alter_table`
|
||||||
|
- **用途**:修改已有表结构(增/改/删字段)
|
||||||
|
- **对应 API**:`putAlterTable(connectionId, data)` ✅ 已实现 — `PUT /api/datasource/connection/{connectionId}/alter_table`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| columns | array | 是 | 字段变更数组 |
|
||||||
|
| columns[].operation | string | 是 | ADD_COLUMN / MODIFY_COLUMN / DROP_COLUMN |
|
||||||
|
| columns[].columnName | string | 是 | 字段名 |
|
||||||
|
| columns[].columnType | string | 否 | 字段类型(ADD/MODIFY) |
|
||||||
|
| columns[].columnLength | int | 否 | 字段长度 |
|
||||||
|
| columns[].isPrimaryKey | bool | 否 | 是否主键 |
|
||||||
|
| columns[].isNullable | bool | 否 | 是否可空 |
|
||||||
|
| columns[].columnComment | string | 否 | 字段注释 |
|
||||||
|
| columns[].defaultValue | string | 否 | 默认值 |
|
||||||
|
| newTableName | string | 否 | 新表名(重命名) |
|
||||||
|
| newTableComment | string | 否 | 新表注释 |
|
||||||
|
|
||||||
|
**返回**:修改结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 12. `generate_table_by_description`
|
||||||
|
- **用途**:通过自然语言描述让 AI 生成表结构
|
||||||
|
- **对应前端**:CreateBuiltinDataSource.vue AI 生成表结构
|
||||||
|
- **对应 API**:`postGenerateTable(data)` ✅ 已实现 — `POST /api/datasource/connection/generate_table`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| description | string | 是 | 业务场景描述(至少6个字符) |
|
||||||
|
|
||||||
|
**返回**:AI 生成的表结构(表名、表注释、字段列表含类型/主键/注释等)
|
||||||
|
|
||||||
|
> **场景示例**:用户说"我需要一个商城系统,管理商品、分类和用户评价",AI 返回完整的表结构设计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📝 表数据操作 (内置数据源 CRUD)
|
||||||
|
|
||||||
|
#### 13. `query_table_data`
|
||||||
|
- **用途**:查询内置表数据(分页)
|
||||||
|
- **对应前端**:CustomizeDbTable.vue 线上/调试数据视图
|
||||||
|
- **对应 API**:`getBuiltinTableData(tableId, params)` ✅ 已实现 — `GET /api/datasource/connection/builtin/table/{tableId}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
| pageNum | int | 否 | 默认 1 |
|
||||||
|
| pageSize | int | 否 | 默认 10 |
|
||||||
|
|
||||||
|
**返回**:表结构信息 + 数据行(二维数组转换后的对象数组) + 总数
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 14. `insert_table_row`
|
||||||
|
- **用途**:向内置表插入一行数据
|
||||||
|
- **对应 API**:`postBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `POST /api/datasource/connection/builtin/table/{tableId}/rows`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
| data | object | 是 | 行数据(键值对,键为字段名) |
|
||||||
|
|
||||||
|
**返回**:插入结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 15. `update_table_row`
|
||||||
|
- **用途**:更新内置表的指定行
|
||||||
|
- **对应 API**:`putBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `PUT /api/datasource/connection/builtin/table/{tableId}/rows`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
| primaryKey | object | 是 | 主键值(如 {"id": 1}) |
|
||||||
|
| data | object | 是 | 要更新的字段值 |
|
||||||
|
|
||||||
|
**返回**:更新结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 16. `delete_table_rows`
|
||||||
|
- **用途**:删除内置表的指定行
|
||||||
|
- **对应 API**:`deleteBuiltinTableRows(tableId, data, params)` ✅ 已实现 — `DELETE /api/datasource/connection/builtin/table/{tableId}/rows`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
| primaryKeys | array | 是 | 主键数组(如 [{"id": 1}, {"id": 2}]) |
|
||||||
|
|
||||||
|
**返回**:删除结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 17. `export_table_excel`
|
||||||
|
- **用途**:导出表数据为 Excel 文件
|
||||||
|
- **对应 API**:`getBuiltinTableExportExcel(tableId, params)` ✅ 已实现 — `GET /api/datasource/connection/builtin/table/{tableId}/export/excel`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
|
||||||
|
**返回**:Excel 文件(二进制 blob)+ 文件名(从 Content-Disposition 解析)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔑 API 密钥与权限管理
|
||||||
|
|
||||||
|
#### 18. `list_api_keys`
|
||||||
|
- **用途**:获取 API 密钥列表
|
||||||
|
- **对应前端**:DataSourceKeys.vue
|
||||||
|
- **对应 API**:`getApiKeyList(data)` ✅ 已实现 — `GET /api/datasource/api_key/list`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| apiKeyName | string | 否 | 密钥名称模糊搜索 |
|
||||||
|
| pageNum | int | 否 | 默认 1 |
|
||||||
|
| pageSize | int | 否 | 默认 20 |
|
||||||
|
|
||||||
|
**返回**:密钥列表(名称、Key、状态、创建时间)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 19. `create_api_key`
|
||||||
|
- **用途**:创建新的 API 密钥
|
||||||
|
- **对应 API**:`postApiKey(data)` ✅ 已实现 — `POST /api/datasource/api_key`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| apiKeyName | string | 是 | 密钥名称(最多50字) |
|
||||||
|
|
||||||
|
**返回**:密钥信息(含 API Key 明文、ID)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 20. `toggle_api_key_status`
|
||||||
|
- **用途**:启用/禁用 API 密钥
|
||||||
|
- **对应 API**:`putApiKey(data)` ✅ 已实现 — `PUT /api/datasource/api_key`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | string | 是 | 密钥 ID |
|
||||||
|
| status | int | 是 | 0=启用, 1=禁用 |
|
||||||
|
|
||||||
|
**返回**:操作结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 21. `delete_api_key`
|
||||||
|
- **用途**:删除 API 密钥
|
||||||
|
- **对应 API**:`deleteApiKey(ids)` ✅ 已实现 — `DELETE /api/datasource/api_key/{ids}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | string | 是 | 密钥 ID |
|
||||||
|
|
||||||
|
**返回**:删除结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 22. `get_api_key_permissions`
|
||||||
|
- **用途**:查看指定密钥的权限配置
|
||||||
|
- **对应 API**:`getApiKeyPermission(apiKeyId)` ✅ 已实现 — `GET /api/datasource/api_key/permission/{apiKeyId}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| apiKeyId | string | 是 | 密钥 ID |
|
||||||
|
|
||||||
|
**返回**:三级权限(connectionPermissions / databasePermissions / tablePermissions)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 23. `grant_api_key_permissions`
|
||||||
|
- **用途**:批量为 API 密钥授予权限
|
||||||
|
- **对应前端**:DataSourceKeySetting.vue
|
||||||
|
- **对应 API**:`postApiKeyPermissionGrantBatch(data)` ✅ 已实现 — `POST /api/datasource/api_key/permission/grant_batch`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| apiKeyId | string | 是 | 密钥 ID |
|
||||||
|
| batchDatas | array | 是 | 权限批量数据数组 |
|
||||||
|
| batchDatas[].connectionId | string | 是 | 数据源 ID |
|
||||||
|
| batchDatas[].permissionLevel | string | 是 | connection/database/table |
|
||||||
|
| batchDatas[].permissionType | string | 是 | 权限类型(逗号分隔) |
|
||||||
|
| batchDatas[].databaseName | string | 否 | 数据库名(level=database/table 时) |
|
||||||
|
| batchDatas[].tableName | string | 否 | 表名(level=table 时) |
|
||||||
|
|
||||||
|
**返回**:授权结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🤖 技能与工具管理 (内置数据源 AI 能力)
|
||||||
|
|
||||||
|
#### 24. `get_skill_by_datasource`
|
||||||
|
- **用途**:根据数据源获取技能信息
|
||||||
|
- **对应 API**:`getSkillByDatasource(id)` ✅ 已实现 — `GET /api/datasource/skill/getByDatasource/{id}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
|
||||||
|
**返回**:技能信息(含 skillBool 标识)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 25. `get_skill_tools`
|
||||||
|
- **用途**:获取技能下的工具列表
|
||||||
|
- **对应 API**:`getSkillBySkillId(id)` ✅ 已实现 — `GET /api/datasource/skill/getBySkillId/{id}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| skillId | string | 是 | 技能 ID |
|
||||||
|
|
||||||
|
**返回**:工具列表(名称、描述、SQL模板、参数定义、业务场景)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 26. `create_skill`
|
||||||
|
- **用途**:为数据源创建技能
|
||||||
|
- **对应 API**:`postSkillCreateOrGet(data)` ✅ 已实现 — `POST /api/datasource/skill/createOrGet`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| name | string | 否 | 技能名称(不传则自动生成) |
|
||||||
|
| description | string | 否 | 技能描述 |
|
||||||
|
|
||||||
|
**返回**:技能 ID
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 27. `create_sql_tool`
|
||||||
|
- **用途**:将 SQL 查询创建为可复用工具
|
||||||
|
- **对应前端**:SqlControllerMsg.vue 添加到工具功能
|
||||||
|
- **对应 API**:`postSqlSkillConfirmTools(data)` ✅ 已实现 — `POST /api/datasource/skill/confirmTools`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| skillId | string | 是 | 技能 ID |
|
||||||
|
| name | string | 是 | 工具名称 |
|
||||||
|
| businessDescription | string | 是 | 业务描述 |
|
||||||
|
| sqlTemplate | string | 是 | SQL 模板(支持 #{param} 参数占位) |
|
||||||
|
| sqlParams | string | 否 | 参数 JSON Schema(默认空对象) |
|
||||||
|
| resultType | string | 否 | single/list,默认 list |
|
||||||
|
| businessScenario | string | 否 | 业务场景描述 |
|
||||||
|
|
||||||
|
**返回**:工具创建结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 28. `delete_skill_tool`
|
||||||
|
- **用途**:删除技能下的工具
|
||||||
|
- **对应 API**:`postDeleteSkillTool(skillToolId)` ✅ 已实现 — `DELETE /api/datasource/skill/tskilltool/{skillToolId}`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| skillToolId | string | 是 | 工具 ID |
|
||||||
|
|
||||||
|
**返回**:删除结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 29. `update_skill_config`
|
||||||
|
- **用途**:更新技能配置(如 MCP Server 配置模板)
|
||||||
|
- **对应 API**:`putSkillUpdateOrGet(data)` ✅ 已实现 — `POST /api/datasource/skill/updateOrGet`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| configTemplate | string | 是 | 配置模板 JSON 字符串 |
|
||||||
|
|
||||||
|
**返回**:更新结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📥 数据导入
|
||||||
|
|
||||||
|
#### 30. `preview_import_data`
|
||||||
|
- **用途**:上传 Excel 文件,AI 智能识别并预览表结构/数据
|
||||||
|
- **对应前端**:TableRecognition.vue
|
||||||
|
- **对应 API**:`postImportDocumentPreview(connectionId, file, target)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/import_document/preview`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| connectionId | int | 是 | 数据源 ID |
|
||||||
|
| target | string | 否 | prod/test,默认 test |
|
||||||
|
| file | binary | 是 | Excel 文件 (.xlsx/.xls, <500KB) |
|
||||||
|
|
||||||
|
**返回**:识别结果(表结构 + 数据预览)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 31. `confirm_import_data`
|
||||||
|
- **用途**:确认导入 AI 识别后的数据
|
||||||
|
- **对应 API**:`postImportDocumentConfirm(connectionId, data, params)` ✅ 已实现 — `POST /api/datasource/connection/{connectionId}/import_document/confirm`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| connectionId | int | 是 | 数据源 ID |
|
||||||
|
| target | string | 否 | prod/test |
|
||||||
|
| data | object | 是 | 导入数据(含 tableStructure + allData) |
|
||||||
|
|
||||||
|
**返回**:导入结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🏷️ 表订阅
|
||||||
|
|
||||||
|
#### 32. `toggle_table_subscription`
|
||||||
|
- **用途**:切换表的订阅状态
|
||||||
|
- **对应前端**:DatabaseDetail.vue 订阅按钮
|
||||||
|
- **对应 API**:`postDatasourceSubscriptionToggle(data)` ✅ 已实现 — `POST /api/datasource/subscription/toggle`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| tableId | string | 是 | 表 ID |
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| subscribe | bool | 是 | true=订阅, false=取消订阅 |
|
||||||
|
|
||||||
|
**返回**:操作结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔧 SQL 执行
|
||||||
|
|
||||||
|
#### 33. `execute_sql`
|
||||||
|
- **用途**:执行原生 SQL 查询
|
||||||
|
- **对应 API**:`executeSql(data)` ✅ 已实现 — `POST /api/datasource/sqlExecutionLog/testSqlWithSchema`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| datasourceId | string | 是 | 数据源 ID |
|
||||||
|
| sql | string | 是 | SQL 语句 |
|
||||||
|
| target | string | 否 | prod/test,默认 prod |
|
||||||
|
| params | object | 否 | 参数对象 |
|
||||||
|
|
||||||
|
**返回**:查询结果(表头、数据行、业务名称、描述等)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、推荐使用场景
|
||||||
|
|
||||||
|
### 场景 1:外部 AI Agent 管理数据库
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "帮我查一下有哪些数据源"
|
||||||
|
Agent: 调用 list_datasources()
|
||||||
|
|
||||||
|
用户: "看看 mall_db 有哪些表"
|
||||||
|
Agent: 调用 list_tables(datasourceId="xx", databaseName="mall_db")
|
||||||
|
|
||||||
|
用户: "users 表结构给我看一下"
|
||||||
|
Agent: 调用 get_table_detail(tableId="xx")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 2:通过 AI 描述自动生成表结构
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "我需要一个订单系统,包含订单、订单明细、支付方式"
|
||||||
|
Agent: 调用 generate_table_by_description(description="我需要一个订单系统...")
|
||||||
|
Agent: 返回 AI 生成的表结构,用户确认后调用 create_table() 批量创建
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 3:管理 API 密钥和权限
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "帮我创建一个叫'第三方报表系统'的 API Key"
|
||||||
|
Agent: 调用 create_api_key(apiKeyName="第三方报表系统")
|
||||||
|
|
||||||
|
用户: "给它开通 mall_db 数据库的读取权限"
|
||||||
|
Agent: 调用 grant_api_key_permissions(apiKeyId="xx", batchDatas=[{...}])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 4:管理表数据
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "查一下 users 表前 10 条数据"
|
||||||
|
Agent: 调用 query_table_data(tableId="xx", pageNum=1, pageSize=10)
|
||||||
|
|
||||||
|
用户: "新增一个用户,用户名是 test_user"
|
||||||
|
Agent: 调用 insert_table_row(tableId="xx", data={"user_name": "test_user", ...})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 5:创建 SQL 工具
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "把这个查询保存为工具,叫'按地区统计订单'"
|
||||||
|
Agent: 调用 create_sql_tool(skillId="xx", name="按地区统计订单",
|
||||||
|
sqlTemplate="SELECT region, COUNT(*) FROM orders GROUP BY region", ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 场景 6:导入 Excel 数据
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "帮我导入这份 Excel 到测试环境"
|
||||||
|
Agent: 调用 preview_import_data(connectionId=xx, target="test", file=...)
|
||||||
|
Agent: 展示识别结果,用户确认后调用 confirm_import_data(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、工具优先级建议
|
||||||
|
|
||||||
|
| 优先级 | 工具 | 理由 |
|
||||||
|
|--------|------|------|
|
||||||
|
| **P0 核心** | list_datasources, list_tables, get_table_detail, execute_sql, query_table_data | 覆盖 80% 查询场景 |
|
||||||
|
| **P1 常用** | create_datasource, create_table, generate_table_by_description, create_api_key, grant_api_key_permissions | 管理核心操作 |
|
||||||
|
| **P2 扩展** | insert/update/delete_table_row, export_table_excel, create_sql_tool, toggle_table_subscription | 数据操作与 AI 能力 |
|
||||||
|
| **P3 完整** | preview/confirm_import_data, update_skill_config, alter_table, delete_skill_tool | 高级功能 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、实现建议
|
||||||
|
|
||||||
|
1. **MCP Server 实现**:建议用 Python + `mcp` 库或 Node.js + `@modelcontextprotocol/sdk`
|
||||||
|
2. **鉴权方式**:工具内部复用平台现有的 API Key 鉴权机制,调用方传入 apiKeyId
|
||||||
|
3. **错误处理**:统一错误格式 `{ success: false, error: "描述" }`,与平台 API 拦截器保持一致
|
||||||
|
4. **环境默认值**:所有 target 参数默认 prod,调用方显式指定 test 才能操作测试数据
|
||||||
|
5. **批量操作**:对于 create_table 等可能涉及多表的场景,支持批量参数或循环调用
|
||||||
|
6. **AI 工具调用**:generate_table_by_description 等 AI 工具需要调用平台的 AI 服务接口(agent generic / aiTextGenerator)
|
||||||
|
7. **前端 API 映射**:所有 33 个 MCP 工具均已在前端 `src/server/database.ts` 中实现对应函数,可直接复用
|
||||||
303
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md
Normal file
303
lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
# 数据库管理平台 - 功能总览
|
||||||
|
|
||||||
|
> 本文档梳理 `databasePage` 组件集群与 `DatabaseDetailPage` 的整体功能架构。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、系统定位
|
||||||
|
|
||||||
|
这是一个面向业务用户的**数据库管理与智能问数一体化平台**,支持两种数据源模式:
|
||||||
|
|
||||||
|
| 模式 | 说明 | 核心差异 |
|
||||||
|
|------|------|----------|
|
||||||
|
| **外部数据源** | 远程 MySQL/PostgreSQL/Oracle/SQL Server/达梦 等 | 标准数据库连接,提供只读/CRUD 管理 |
|
||||||
|
| **内置数据源** | 平台内建 PostgreSQL 实例 | 支持 AI 技能、工具、环境切换(prod/test)、AI 训练 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、功能模块全景
|
||||||
|
|
||||||
|
### 1. 数据源管理
|
||||||
|
|
||||||
|
#### 1.1 数据源列表 (`DataSourceList.vue`)
|
||||||
|
- **卡片式展示**:名称、ID、类型徽章(内置/外部)、数据库类型、运行状态、库数/表数统计
|
||||||
|
- **无限滚动分页**:IntersectionObserver + useInfiniteScroll,每页 20 条
|
||||||
|
- **搜索与筛选**:按名称模糊搜索、按状态筛选(运行中/已停止)
|
||||||
|
- **数据源操作**:
|
||||||
|
- 编辑(外部→CreateDataSource, 内置→CreateBuiltinDataSource)
|
||||||
|
- 启用/停用(切换 status 0/1)
|
||||||
|
- 删除(运行中自动先停用再删除)
|
||||||
|
- **业务执行入口**:点击打开智能问数聊天界面 (ChatDebugging)
|
||||||
|
- **查看详情入口**:右侧 Drawer 打开 DatabaseDetail (95% 宽度)
|
||||||
|
|
||||||
|
#### 1.2 外部数据源创建/编辑 (`CreateDataSource.vue`) - 4步向导
|
||||||
|
| 步骤 | 内容 | 核心功能 |
|
||||||
|
|------|------|----------|
|
||||||
|
| Step 1 | 选择数据库类型 | MySQL/PostgreSQL/Oracle/SQL Server/达梦,自动填充默认端口 |
|
||||||
|
| Step 2 | 配置连接信息 | 名称、描述(AI润色)、主机、端口、认证(基础认证/SSL)、用户名密码(AI生成名称)、测试连接 |
|
||||||
|
| Step 3 | 选择数据库和表 | 树形展开、多选、搜索过滤、分页加载表列表、全选/清空 |
|
||||||
|
| Step 4 | 确认信息 | 汇总展示已选数据库和表标签 |
|
||||||
|
|
||||||
|
#### 1.3 内置数据源创建/编辑/添加表 (`CreateBuiltinDataSource.vue`)
|
||||||
|
| 模式 | 步骤 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| create | 名称描述→业务场景→表结构 | AI 生成表结构,批量创建数据库和表 |
|
||||||
|
| edit | 名称描述→表结构 | 更新已有内置数据源 |
|
||||||
|
| addTable | 业务场景→表结构 | 在已有内置数据源下新增表 |
|
||||||
|
|
||||||
|
- **AI 能力**:AI 优化描述、AI 生成数据库名、AI 生成表名/表描述/字段描述
|
||||||
|
- **表结构编辑器**:左右分栏(左侧表列表, 右侧 TableDetailEditor 预览/编辑)
|
||||||
|
- **提交流程**:创建数据源 → 创建 PostgreSQL 连接 → 创建数据库 → 批量建表 → 检查 AI 训练状态
|
||||||
|
|
||||||
|
### 2. 数据库详情 (`DatabaseDetail.vue`)
|
||||||
|
|
||||||
|
左侧面板 + 右侧面板的经典布局:
|
||||||
|
|
||||||
|
#### 2.1 左侧 - 数据库与表管理
|
||||||
|
- **数据库选择器**:下拉切换当前数据库,悬浮显示数据库详情(ID/名称/数据源/类型)
|
||||||
|
- **数据表列表**:
|
||||||
|
- 搜索过滤
|
||||||
|
- 多选/全选 (Checkbox)
|
||||||
|
- 无限滚动加载
|
||||||
|
- AI 补全(选中多个表批量触发)
|
||||||
|
- 更多操作: AI智能建表、智能导入表、删除库
|
||||||
|
- **底部功能按钮**:字段关联管理、AI补全管理
|
||||||
|
|
||||||
|
#### 2.2 右侧 - 详情视图(三种视图)
|
||||||
|
|
||||||
|
**视图 A: 表详情 (`fieldViewMode = fields`)**
|
||||||
|
- 字段列表表格: 字段名、类型、长度、可空、主键、默认值、注释、描述
|
||||||
|
- AI 训练状态徽章(已训练/未训练)
|
||||||
|
- 操作: 修改表、字段关联管理、表订阅(已订阅/未订阅)、刷新
|
||||||
|
- 用户 ID 字段关联提示(user_id/createById 等自动关联系统用户)
|
||||||
|
|
||||||
|
**视图 B: 线上数据 (`fieldViewMode = online`)**
|
||||||
|
- 通过 `CustomizeDbTable` 组件查看/编辑生产环境真实数据
|
||||||
|
- 支持增删改查、导出 Excel、智能导入数据
|
||||||
|
|
||||||
|
**视图 C: 调试数据 (`fieldViewMode = debug`)**
|
||||||
|
- 同上,但操作测试环境数据
|
||||||
|
|
||||||
|
#### 2.3 右侧 - 字段关联管理视图
|
||||||
|
- 为当前表的字段建立与目标表字段的映射关系
|
||||||
|
- 目标表数据更新时自动同步到当前表
|
||||||
|
- 支持保存配置、添加/删除关联
|
||||||
|
|
||||||
|
#### 2.4 右侧 - AI 补全管理视图
|
||||||
|
- AI 补全任务列表: 任务名、类型、状态(待执行/运行中/成功/失败)、进度
|
||||||
|
- 任务详情弹窗: 训练结果(格式化代码展示)、耗时、场景描述
|
||||||
|
- 支持重新执行、删除任务
|
||||||
|
|
||||||
|
### 3. 内建表数据管理 (`CustomizeDbTable.vue`)
|
||||||
|
- 基于 `CommonDbTable` 的完整 CRUD 表格
|
||||||
|
- 动态列配置: 根据表结构自动生成字段类型、宽度、可编辑性
|
||||||
|
- **操作能力**: 新增行、编辑行、删除行、导出 Excel、导入数据、刷新
|
||||||
|
- **导入流程**: 下载模板 → 上传 Excel → AI 识别预览 → 确认导入(调用 postImportDocumentConfirm)
|
||||||
|
- 环境切换: `target` prop 控制 prod/test
|
||||||
|
|
||||||
|
### 4. 智能导入识别 (`TableRecognition.vue`)
|
||||||
|
- **Step 1: 上传文件**: 选择目标环境(prod/test),上传 Excel (.xlsx/.xls, <500KB)
|
||||||
|
- **Step 2: 智能识别**: AI 解析 Excel,预览表结构/数据,支持编辑
|
||||||
|
- **两种模式**:
|
||||||
|
- `table` 模式: 根据 Excel 内容自动生成数据表结构
|
||||||
|
- `data` 模式: 根据规范模板识别并导入数据
|
||||||
|
- 空行过滤、字段标记、模板下载
|
||||||
|
|
||||||
|
### 5. 智能问数 / 业务执行 (`ChatDebugging.vue`)
|
||||||
|
- **三栏布局**: 左侧面板(可折叠) + 中间聊天区 + 右侧信息
|
||||||
|
- **左侧面板**:
|
||||||
|
- 数字员工列表(可收起)
|
||||||
|
- 新建会话按钮
|
||||||
|
- 会话列表(点击切换、删除)
|
||||||
|
- 技能列表(展开/折叠)
|
||||||
|
- 技能名称 + 编辑按钮
|
||||||
|
- 工具列表(名称、别名、编辑、删除)
|
||||||
|
- 至少保留一个工具
|
||||||
|
- **中间聊天区** (`ChatBusiness` 组件):
|
||||||
|
- 数据源插件嵌入输入区 (`DataSourcePlugIn`)
|
||||||
|
- 数据库下拉选择
|
||||||
|
- 环境切换(生产/测试,仅内置数据源)
|
||||||
|
- SQL 消息控制器 (`SqlControllerMsg`)
|
||||||
|
- 默认数据库查询提示词(常用查询/数据统计/新手指引)
|
||||||
|
|
||||||
|
#### 5.1 SQL 查询结果展示 (`SqlControllerMsg.vue`)
|
||||||
|
| 视图类型 | 说明 |
|
||||||
|
|----------|------|
|
||||||
|
| SQL 代码 | 展示可执行 SQL 语句,支持复制 |
|
||||||
|
| 图表可视化 | ChartGallery 组件渲染图表 |
|
||||||
|
| 查询数据 | JSON 格式原始数据,支持复制 |
|
||||||
|
| 文本说明 | 业务名称、描述、AI 解释 |
|
||||||
|
| 表格视图 | Ant Design Table 展示查询结果 |
|
||||||
|
| 添加到工具 | 将 SQL 查询保存为可复用工具 |
|
||||||
|
|
||||||
|
- **添加工具功能**:
|
||||||
|
- 重复检测(按 sqlTemplate 归一化比对)
|
||||||
|
- 仅生产环境可用
|
||||||
|
- 技能创建/更新流程(无技能时自动创建 skill + MCP 配置)
|
||||||
|
- 通过 EventBus 触发技能刷新
|
||||||
|
|
||||||
|
#### 5.2 数据源选择插件 (`DataSourcePlugIn.vue`)
|
||||||
|
- 紧凑的内联选择器: 数据源名称 → 数据库下拉 → 环境(prod/test)
|
||||||
|
- 监听 props 变化自动重置无效选择
|
||||||
|
- 暴露 getSelection/clearSelection/setEnvironment 方法
|
||||||
|
|
||||||
|
### 6. API 密钥管理 (`DataSourceKeys.vue` + `DataSourceKeySetting.vue`)
|
||||||
|
- **密钥列表**: 名称、API Key(脱敏显示/可切换显示)、状态开关(启用/禁用)、创建时间、操作(详情/编辑/删除)
|
||||||
|
- **两步创建流程**:
|
||||||
|
1. 输入密钥名称 → 调用 postApiKey 创建
|
||||||
|
2. 弹窗询问是否配置权限 → 打开权限配置
|
||||||
|
- **权限配置** (`DataSourceKeySetting.vue`) - 3步向导:
|
||||||
|
- Step 1: 选择数据源(单选) + 配置数据源级权限
|
||||||
|
- Step 2: 选择数据库(单选, 非必填) + 配置数据库级权限
|
||||||
|
- Step 3: 选择数据表(多选, 非必填) + 行内配置表级权限
|
||||||
|
- Step 4: 完成页(ApiKeyPermissionPreview 汇总) → 批量提交权限
|
||||||
|
- **权限选项**: 按层级不同(connection/database/table),每种有对应的读/写/管理等权限
|
||||||
|
- **API 调用文档**: 切换到文档视图查看调用说明
|
||||||
|
|
||||||
|
### 7. 表字段编辑器 (`TableDetailEditor.vue`)
|
||||||
|
- **表信息编辑**: 表名(AI生成)、表描述(AI优化)
|
||||||
|
- **字段列表表格**:
|
||||||
|
- 序号、字段名、字段类型(分组下拉)、长度、主键、自增、可空、默认值、注释
|
||||||
|
- AI 生成字段描述(逐字段)
|
||||||
|
- 添加字段(首个字段自动设为主键+自增)
|
||||||
|
- 删除字段(有id的字段记录到 deletedColumns)
|
||||||
|
- 主键设置后不可取消(已有字段)
|
||||||
|
- 自增字段自动切换为整数类型
|
||||||
|
- **两种模式**: `edit` (编辑) / `preview` (预览)
|
||||||
|
- **校验功能**: validateFields 供外部调用
|
||||||
|
- **字段类型** (面向 PostgreSQL): VARCHAR/TEXT/SERIAL/INTEGER/BIGINT/NUMERIC/TIMESTAMP/BOOLEAN/JSONB/INT2/INT4/INT8/SMALLINT/SMALLSERIAL/BIGSERIAL/BOOL/BIGSERIAL
|
||||||
|
|
||||||
|
### 8. 其他辅助组件
|
||||||
|
|
||||||
|
| 组件 | 功能 |
|
||||||
|
|------|------|
|
||||||
|
| `ChartGallery.vue` | 图表可视化渲染 |
|
||||||
|
| `AddToolModal.vue` | 添加工具弹窗(工具名、描述编辑+AI辅助) |
|
||||||
|
| `ApiKeyPermissionPreview.vue` | 权限汇总预览组件 |
|
||||||
|
| `ApiCallDocument.vue` | API 调用文档展示 |
|
||||||
|
| `DatabaseMessageList.vue` | 数据库消息列表 |
|
||||||
|
| `field-types-dictionary.json` | 字段类型字典 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、API 接口层 (server/database.ts)
|
||||||
|
|
||||||
|
### 数据源管理
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getConnectionList` | GET | 数据源列表(分页+搜索+筛选) |
|
||||||
|
| `getConnectionDetail` | GET | 数据源详情 |
|
||||||
|
| `postConnectionDetail` | POST | 创建数据源 |
|
||||||
|
| `putConnectionDetail` | PUT | 更新数据源 |
|
||||||
|
| `deleteConnection` | DELETE | 删除数据源 |
|
||||||
|
| `putConnectionChangeStatus` | PUT | 切换数据源状态(启用/停用) |
|
||||||
|
| `testConnection` | POST | 测试连接 |
|
||||||
|
|
||||||
|
### 内置数据库管理
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `postCreateBuiltinPostgreSQLConnection` | POST | 创建内置 PostgreSQL 连接 |
|
||||||
|
| `postCreateDatabase` | POST | 创建数据库 |
|
||||||
|
| `putAlterDatabase` | PUT | 修改数据库 |
|
||||||
|
| `postCreateTable` | POST | 创建表 |
|
||||||
|
| `putAlterTable` | PUT | 修改表 |
|
||||||
|
| `postGenerateTable` | POST | AI 生成表结构 |
|
||||||
|
| `putUpdateBuiltinDatabase` | PUT | 更新内置数据库 |
|
||||||
|
|
||||||
|
### 数据库配置
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getConnectionConfig` | GET | 获取数据库配置(含 skillBool) |
|
||||||
|
| `getConnectionConfigList` | GET | 数据库配置列表 |
|
||||||
|
| `postConnectionConfig` | POST | 创建数据库配置 |
|
||||||
|
| `putConnectionConfig` | PUT | 更新数据库配置 |
|
||||||
|
| `deleteConnectionConfig` | DELETE | 删除数据库配置 |
|
||||||
|
| `getConnectionRealtimeStructure` | GET | 获取实时数据库结构 |
|
||||||
|
|
||||||
|
### 表数据管理
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getTableList` | GET | 表列表 |
|
||||||
|
| `getTableDetail` | GET | 表详情 |
|
||||||
|
| `getBuiltinTableData` | GET | 获取内置表数据(分页) |
|
||||||
|
| `postBuiltinTableRows` | POST | 新增表数据行 |
|
||||||
|
| `putBuiltinTableRows` | PUT | 更新表数据行 |
|
||||||
|
| `deleteBuiltinTableRows` | DELETE | 删除表数据行 |
|
||||||
|
| `getBuiltinTableExportExcel` | GET | 导出表数据为 Excel |
|
||||||
|
|
||||||
|
### 数据导入
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `postImportDocumentPreview` | POST | 上传 Excel 预览识别结果 |
|
||||||
|
| `postImportDocumentConfirm` | POST | 确认导入数据 |
|
||||||
|
|
||||||
|
### 技能与工具
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getSkillByDatasource` | GET | 根据数据源获取技能 |
|
||||||
|
| `getSkillBySkillId` | GET | 根据技能ID获取工具列表 |
|
||||||
|
| `postSkillCreateOrGet` | POST | 创建或获取技能 |
|
||||||
|
| `putSkillUpdateOrGet` | PUT | 更新技能配置 |
|
||||||
|
| `postSkillToolUpdateOrGet` | POST | 创建/更新技能工具 |
|
||||||
|
| `postDeleteSkillTool` | POST | 删除技能工具 |
|
||||||
|
| `postSqlSkillConfirmTools` | POST | 确认并创建 SQL 工具 |
|
||||||
|
|
||||||
|
### AI 训练
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getAiTrainingList` | GET | AI 训练任务列表 |
|
||||||
|
| `postAiTrainingCreateBySelected` | POST | 创建 AI 训练任务 |
|
||||||
|
| `getAiTrainingDetail` | GET | 训练任务详情 |
|
||||||
|
|
||||||
|
### API 密钥
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `getApiKeyList` | GET | 密钥列表 |
|
||||||
|
| `postApiKey` | POST | 创建密钥 |
|
||||||
|
| `putApiKey` | PUT | 更新密钥 |
|
||||||
|
| `deleteApiKey` | DELETE | 删除密钥 |
|
||||||
|
| `getApiKeyPermission` | GET | 获取密钥权限 |
|
||||||
|
| `postApiKeyPermissionGrantBatch` | POST | 批量授予权限 |
|
||||||
|
|
||||||
|
### 订阅
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `postDatasourceSubscriptionToggle` | POST | 切换表订阅状态 |
|
||||||
|
|
||||||
|
### SQL 执行
|
||||||
|
| 函数 | 方法 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| `executeSql` | POST | 执行 SQL 查询 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、状态管理与通信
|
||||||
|
|
||||||
|
- **Pinia Store**: `useDatabaseChatStore` 管理选中数据库、环境、技能状态
|
||||||
|
- **EventBus**: `EVENTS.RELOAD_SKILL_DATA` 用于工具添加后刷新技能数据
|
||||||
|
- **权限控制**: `hasPermission()` 函数按权限标识控制按钮可见性
|
||||||
|
|
||||||
|
## 五、权限标识体系
|
||||||
|
|
||||||
|
| 权限标识 | 控制范围 |
|
||||||
|
|----------|----------|
|
||||||
|
| `database:create` | 创建数据源 |
|
||||||
|
| `database:edit` | 编辑数据源 |
|
||||||
|
| `database:delete` | 删除数据源 |
|
||||||
|
| `database:import` | 导入表/数据 |
|
||||||
|
| `database:export` | 导出表 |
|
||||||
|
| `database:table:create` | AI 智能建表 |
|
||||||
|
| `database:table:ai:complete` | AI 补全功能 |
|
||||||
|
| `database:apikey:view` | 查看密钥管理 |
|
||||||
|
| `database:apikey:create` | 创建密钥 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、关键技术特点
|
||||||
|
|
||||||
|
1. **AI 深度集成**: 描述润色、名称生成、表结构自动生成、字段描述生成、AI 补全、AI 训练
|
||||||
|
2. **双环境隔离**: 生产/测试环境切换,测试环境限制添加工具等敏感操作
|
||||||
|
3. **无限滚动**: IntersectionObserver + 哨兵元素实现列表分页加载
|
||||||
|
4. **三级权限体系**: 数据源 → 数据库 → 数据表,通过 API Key 控制访问粒度
|
||||||
|
5. **MCP 工具集成**: 内置数据源自动配置 MCP Server (`lzwcai-mcp-sqlexecutor`),支持 AI 工具注册
|
||||||
|
6. **Excel 智能导入**: AI 识别 Excel 内容,自动匹配表结构,支持表结构和数据两种导入模式
|
||||||
12
lzwcai_mcp_agile_db/main.py
Normal file
12
lzwcai_mcp_agile_db/main.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"""
|
||||||
|
Entry point for lzwcai-mcp-agile-db
|
||||||
|
Runs the MCP server for database management platform
|
||||||
|
"""
|
||||||
|
|
||||||
|
from lzwcai_mcp_agile_db.server import main
|
||||||
|
import os
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
os.environ["AGILE_DB_API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg"
|
||||||
|
os.environ["AGILE_DB_BASE_URL"] = "https://dempdemo.lzwcai.com"
|
||||||
|
main()
|
||||||
34
lzwcai_mcp_agile_db/pyproject.toml
Normal file
34
lzwcai_mcp_agile_db/pyproject.toml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "lzwcai-mcp-agile-db"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "MCP server for database management platform with 33 tools for datasource, table, data, API key, and skill management"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "lzwcai", email = "your-email@example.com"},
|
||||||
|
]
|
||||||
|
keywords = ["mcp", "database", "agile", "server"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
]
|
||||||
|
dependencies = [
|
||||||
|
"httpx>=0.28.1",
|
||||||
|
"mcp[cli]>=1.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
lzwcai-mcp-agile-db = "lzwcai_mcp_agile_db.server:main"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["lzwcai_mcp_agile_db"]
|
||||||
BIN
lzwcai_mcp_sqlexecutor/__pycache__/main.cpython-312.pyc
Normal file
BIN
lzwcai_mcp_sqlexecutor/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,25 +1,289 @@
|
|||||||
2026-05-25 14:32:22 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
2026-05-25 16:05:46 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||||
2026-05-25 14:32:22 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
2026-05-25 16:05:46 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:352] - 开始运行 MCP SQL Executor 服务器
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:304] - ============================================================
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:304] - ============================================================
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:305] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:306] - 版本: 0.1.0
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:307] - ============================================================
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:307] - ============================================================
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 162
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:311] - 环境配置 - Database ID: 240
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2008360664955854850
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:312] - 环境配置 - Skill ID: 2058819964077572098
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:313] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:314] - ============================================================
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:314] - ============================================================
|
||||||
2026-05-25 14:32:22 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
|
2026-05-25 16:05:46 - mcp_services - INFO - [main.py:319] - MCP 服务器已启动,等待客户端连接...
|
||||||
2026-05-25 14:32:35 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
2026-05-25 16:05:49 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2008360664955854850
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:283] - 调用第三方API,skill_id: 2058819964077572098
|
||||||
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850
|
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
2026-05-25 14:32:35 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850 "HTTP/1.1 200 "
|
2026-05-25 16:05:49 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||||
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2008360664955854850
|
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2008360824029028354', 'createBy': 'wxl06', 'createTime': '2026-01-06 10:11:49', 'updateBy': 'wxl06', 'updateTime': '2026-01-06 10:11:59', 'serviceId': '2008360664960049153', 'uniqueName': '查询订单信息', 'name': 'chaxundingdanxinxi_3acda9b4', 'description': 'chaxundingdanxinxi_3acda9b4: 查询订单表中的订单信息,包括订单号、产品名称、数量、购买客户、备注和状态等关键信息', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 4ms', 'toolType': 'sql', 'datasourceId': '162', 'sqlTemplate': 'SELECT order_id, product_name, quantity, customer, remarks, status FROM orders WHERE 1=1', 'sqlParams': '{"type":"object","required":[],"properties":{}}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","required":["employeeId"],"properties":{"employeeId":{"type":"number","description":"员工ID,用于标识员工的唯一数字标识符","examples":[1001,2002]},"targetDatabaseName":{"type":"string","description":"目标数据库名称"}}}', 'outputJsonSchema': '{"type":"object","properties":{"data":{"type":"array"}}}', 'lastExecutionTime': None}]}
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:288] - 成功{'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||||
2026-05-25 14:32:35 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
|
2026-05-25 16:05:49 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:324] - 成功处理 1 条技能数据
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:293] - 成功获取并处理 1 条数据
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:128] - API配置: 1 条
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:128] - API配置: 1 条
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2008360824029028354', 'businessName': 'chaxundingdanxinxi_3acda9b4', 'businessDescription': 'chaxundingdanxinxi_3acda9b4: 查询订单表中的订单信息,包括订单号、产品名称、数量、购买客户、备注和状态等关键信息', 'sqlTemplate': 'SELECT order_id, product_name, quantity, customer, remarks, status FROM orders WHERE 1=1', 'parameters': {'type': 'object', 'required': [], 'properties': {}}, 'datasourceId': '162'}]
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:129] - API配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||||
2026-05-25 14:32:35 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
|
2026-05-25 16:05:49 - mcp_services - INFO - [main.py:165] - 成功生成 1 个 MCP 工具
|
||||||
|
2026-05-25 16:05:52 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type CallToolRequest
|
||||||
|
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:190] - 收到工具调用请求: qian50tiaodaorushujumingxichaxun_32b1d628
|
||||||
|
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:230] - 正在调用测试SQL API...
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:132] - ================================================================================
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:133] - test_sql_with_schema 接口接收到的数据:
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:134] - 数据类型: <class 'dict'>
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:135] - 数据内容: {
|
||||||
|
"datasourceId": "240",
|
||||||
|
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
|
||||||
|
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。",
|
||||||
|
"sqlTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
|
||||||
|
"parameters": {},
|
||||||
|
"testParams": {}
|
||||||
|
}
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:136] - 数据源ID: 240
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:137] - 业务名称: qian50tiaodaorushujumingxichaxun_32b1d628
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:138] - 业务描述: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:139] - SQL模板: SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:140] - 参数定义: {}
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:141] - 测试参数: {}
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:144] - ================================================================================
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:153] - 正在调用测试SQL API: http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema
|
||||||
|
2026-05-25 16:05:52 - httpx - INFO - [_client.py:1025] - HTTP Request: POST http://192.168.2.236:8088/datasource/sqlExecutionLog/testSqlWithSchema "HTTP/1.1 200 "
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:170] - ================================================================================
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:171] - test_sql_with_schema 接口返回的数据:
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:172] - HTTP状态码: 200
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:173] - 响应数据类型: <class 'dict'>
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:174] - 响应数据内容: {
|
||||||
|
"msg": "操作成功",
|
||||||
|
"code": 200,
|
||||||
|
"data": {
|
||||||
|
"resultCount": 6,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"记录ID": 80001,
|
||||||
|
"任务ID": "3320260418001",
|
||||||
|
"原始数据": "{\"name\": \"张三\", \"mobile\": \"13800000101\", \"dept\": \"销售部\", \"amount\": 1200}",
|
||||||
|
"处理结果": "导入成功",
|
||||||
|
"错误原因": null,
|
||||||
|
"处理状态": "SUCCESS",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:19:19.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"记录ID": 80002,
|
||||||
|
"任务ID": "3320260418002",
|
||||||
|
"原始数据": "{\"name\": \"李四\", \"mobile\": \"13800000102\", \"dept\": \"财务部\", \"amount\": 980}",
|
||||||
|
"处理结果": "导入成功",
|
||||||
|
"错误原因": null,
|
||||||
|
"处理状态": "SUCCESS",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:19:34.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"记录ID": 80003,
|
||||||
|
"任务ID": "3320260418003",
|
||||||
|
"原始数据": "{\"name\": \"王五\", \"mobile\": \"13800000103\", \"dept\": \"技术部\", \"amount\": 1500}",
|
||||||
|
"处理结果": "导入失败",
|
||||||
|
"错误原因": "字段校验失败:mobile格式不正确",
|
||||||
|
"处理状态": "FAILED",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:19:49.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"记录ID": 80004,
|
||||||
|
"任务ID": "3320260418004",
|
||||||
|
"原始数据": "{\"name\": \"赵六\", \"mobile\": \"13800000104\", \"dept\": \"人事部\", \"amount\": 0}",
|
||||||
|
"处理结果": "导入成功",
|
||||||
|
"错误原因": null,
|
||||||
|
"处理状态": "SUCCESS",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:20:04.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"记录ID": 80005,
|
||||||
|
"任务ID": "3320260418005",
|
||||||
|
"原始数据": "{\"name\": \"孙七\", \"mobile\": \"13800000105\", \"dept\": \"运营部\", \"amount\": 2000}",
|
||||||
|
"处理结果": "导入失败",
|
||||||
|
"错误原因": "目标表写入失败:唯一键冲突",
|
||||||
|
"处理状态": "FAILED",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:20:19.000+08:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"记录ID": 80006,
|
||||||
|
"任务ID": "3320260418006",
|
||||||
|
"原始数据": "{\"name\": \"周八\", \"mobile\": \"13800000106\", \"dept\": \"法务部\", \"amount\": 300}",
|
||||||
|
"处理结果": "导入成功",
|
||||||
|
"错误原因": null,
|
||||||
|
"处理状态": "SUCCESS",
|
||||||
|
"关联目标表": "employee_import",
|
||||||
|
"创建时间": "2026-04-18T15:20:34.000+08:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"databaseName": "import_log_db_168",
|
||||||
|
"businessDescription": "qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。",
|
||||||
|
"originalTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
|
||||||
|
"convertedTemplate": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
|
||||||
|
"executionStatus": "success",
|
||||||
|
"businessName": "qian50tiaodaorushujumingxichaxun_32b1d628",
|
||||||
|
"testParams": {},
|
||||||
|
"errorMessage": null,
|
||||||
|
"executionTime": 8,
|
||||||
|
"datasourceId": "240",
|
||||||
|
"logId": "2058821884796174336",
|
||||||
|
"executableSql": "SELECT record_id AS \"记录ID\", task_id AS \"任务ID\", original_data AS \"原始数据\", process_result AS \"处理结果\", error_reason AS \"错误原因\", process_status AS \"处理状态\", target_table AS \"关联目标表\", created_time AS \"创建时间\" FROM import_record_detail LIMIT 50;",
|
||||||
|
"datasourceName": "import_log_db_168"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:176] - 响应code: 200
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:177] - 响应msg: 操作成功
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:178] - 响应data: {'resultCount': 6, 'data': [{'记录ID': 80001, '任务ID': '3320260418001', '原始数据': '{"name": "张三", "mobile": "13800000101", "dept": "销售部", "amount": 1200}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:19.000+08:00'}, {'记录ID': 80002, '任务ID': '3320260418002', '原始数据': '{"name": "李四", "mobile": "13800000102", "dept": "财务部", "amount": 980}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:34.000+08:00'}, {'记录ID': 80003, '任务ID': '3320260418003', '原始数据': '{"name": "王五", "mobile": "13800000103", "dept": "技术部", "amount": 1500}', '处理结果': '导入失败', '错误原因': '字段校验失败:mobile格式不正确', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:19:49.000+08:00'}, {'记录ID': 80004, '任务ID': '3320260418004', '原始数据': '{"name": "赵六", "mobile": "13800000104", "dept": "人事部", "amount": 0}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:04.000+08:00'}, {'记录ID': 80005, '任务ID': '3320260418005', '原始数据': '{"name": "孙七", "mobile": "13800000105", "dept": "运营部", "amount": 2000}', '处理结果': '导入失败', '错误原因': '目标表写入失败:唯一键冲突', '处理状态': 'FAILED', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:19.000+08:00'}, {'记录ID': 80006, '任务ID': '3320260418006', '原始数据': '{"name": "周八", "mobile": "13800000106", "dept": "法务部", "amount": 300}', '处理结果': '导入成功', '错误原因': None, '处理状态': 'SUCCESS', '关联目标表': 'employee_import', '创建时间': '2026-04-18T15:20:34.000+08:00'}], 'databaseName': 'import_log_db_168', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'originalTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'convertedTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'executionStatus': 'success', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'testParams': {}, 'errorMessage': None, 'executionTime': 8, 'datasourceId': '240', 'logId': '2058821884796174336', 'executableSql': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'datasourceName': 'import_log_db_168'}
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:179] - ================================================================================
|
||||||
|
2026-05-25 16:05:52 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:181] - 测试SQL API调用成功
|
||||||
|
2026-05-25 16:05:52 - mcp_services - INFO - [main.py:232] - 测试SQL API调用成功
|
||||||
|
2026-05-25 16:15:08 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||||
|
2026-05-25 16:15:08 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:246] - 开始运行 MCP SQL Executor 服务
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:204] - ============================================================
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:205] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:206] - 版本: 0.1.0
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:207] - ============================================================
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:210] - 环境配置 - Database ID: 240
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:211] - 环境配置 - Skill ID: 2058819964077572098
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:212] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:213] - ============================================================
|
||||||
|
2026-05-25 16:15:08 - mcp_services - INFO - [main.py:218] - MCP 服务已启动,等待客户端连接...
|
||||||
|
2026-05-25 16:15:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:107] - 收到列出工具请求
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:77] - 初始化查询配置,数据源: api
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:189] - 调用第三方 API,skill_id: 2058819964077572098
|
||||||
|
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
|
2026-05-25 16:15:09 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||||
|
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:85] - API调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:191] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||||
|
2026-05-25 16:15:09 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:325] - 成功处理 1 条技能数据
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:194] - 成功处理 1 条数据
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:85] - API 配置: 1 条
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:86] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||||
|
2026-05-25 16:15:09 - mcp_services - INFO - [main.py:110] - 成功生成 1 个 MCP 工具
|
||||||
|
2026-05-25 16:22:48 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||||
|
2026-05-25 16:22:48 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 29
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID:
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||||
|
2026-05-25 16:22:48 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||||
|
2026-05-25 16:22:51 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||||
|
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||||
|
2026-05-25 16:22:51 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id:
|
||||||
|
2026-05-25 16:22:51 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
|
||||||
|
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
|
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
||||||
|
yield
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
||||||
|
resp = self._pool.handle_request(req)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
|
||||||
|
raise exc from None
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
|
||||||
|
response = connection.handle_request(
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
|
||||||
|
raise exc
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
|
||||||
|
stream = self._connect(request)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
|
||||||
|
stream = self._network_backend.connect_tcp(**kwargs)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
|
||||||
|
with map_exceptions(exc_map):
|
||||||
|
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||||
|
self.gen.throw(value)
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
||||||
|
raise to_exc(exc) from exc
|
||||||
|
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
||||||
|
|
||||||
|
The above exception was the direct cause of the following exception:
|
||||||
|
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
|
||||||
|
response = self.client.get(url, headers=self._get_headers())
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
||||||
|
return self.request(
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
|
||||||
|
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
|
||||||
|
response = self._send_handling_auth(
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
|
||||||
|
response = self._send_handling_redirects(
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
|
||||||
|
response = self._send_single_request(request)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
|
||||||
|
response = transport.handle_request(request)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
||||||
|
with map_httpcore_exceptions():
|
||||||
|
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||||
|
self.gen.throw(value)
|
||||||
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
||||||
|
raise mapped_exc(message) from exc
|
||||||
|
httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
||||||
|
|
||||||
|
During handling of the above exception, another exception occurred:
|
||||||
|
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
|
||||||
|
raw_result = get_skill_by_id(skill_id)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
|
||||||
|
return default_client.get_skill_by_id(skill_id)
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
|
||||||
|
raise Exception(error_msg)
|
||||||
|
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
|
2026-05-25 16:22:53 - mcp_services - WARNING - [main.py:108] - API 获取失败,降级使用本地配置: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
|
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:58] - 成功加载 0 个业务查询配置
|
||||||
|
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||||
|
2026-05-25 16:22:53 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||||
|
2026-05-25 16:22:53 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||||
|
2026-05-25 16:23:09 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||||
|
2026-05-25 16:23:09 - mcp_services - INFO - [main.py:130] - 成功生成 0 个 MCP 工具
|
||||||
|
2026-05-25 16:26:37 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
||||||
|
2026-05-25 16:26:37 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:258] - 开始运行 MCP SQL Executor 服务
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:216] - ============================================================
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:217] - 正在启动 MCP 服务: lzwcai-mcp-sqlexecutor
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:218] - 版本: 0.1.0
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:219] - ============================================================
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:222] - 环境配置 - Database ID: 240
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:223] - 环境配置 - Skill ID: 2058819964077572098
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:224] - 环境配置 - Backend Base URL: http://192.168.2.236:8088
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:225] - ============================================================
|
||||||
|
2026-05-25 16:26:37 - mcp_services - INFO - [main.py:230] - MCP 服务已启动,等待客户端连接...
|
||||||
|
2026-05-25 16:26:38 - mcp.server.lowlevel.server - INFO - [server.py:720] - Processing request of type ListToolsRequest
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:127] - 收到列出工具请求
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:97] - 初始化查询配置,数据源: api
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:201] - 调用第三方 API,skill_id: 2058819964077572098
|
||||||
|
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:47] - 正在调用 API: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
|
2026-05-25 16:26:38 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098 "HTTP/1.1 200 "
|
||||||
|
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:54] - API 调用成功: http://192.168.2.236:8088/datasource/skill/getBySkillId/2058819964077572098
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:203] - 成功获取原始响应: {'msg': '查询成功', 'code': 200, 'data': [{'id': '2058819964287287298', 'createBy': 'wxl06', 'createTime': '2026-05-25 15:58:25', 'updateBy': None, 'updateTime': None, 'serviceId': '2058819964085960705', 'uniqueName': '前50条导入数据明细查询', 'name': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'description': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'visualizable': 1, 'toolPrompt': '查询成功,返回 6 行数据,执行时间: 1ms', 'toolType': 'sql', 'datasourceId': '240', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'sqlParams': '{}', 'resultType': 'list', 'sourceType': 'ai', 'trainingTaskId': None, 'tableMetadataIds': '', 'executionCount': 0, 'visualizationConfigs': None, 'inputJsonSchema': '{"type":"object","properties":{},"required":[]}', 'outputJsonSchema': '{"type":"object","properties":{"text":{"type":"string"}},"additionalProperties":false}', 'lastExecutionTime': None}]}
|
||||||
|
2026-05-25 16:26:38 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:205] - 成功处理 1 条技能数据
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:206] - 成功处理 1 条数据
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:105] - API 配置: 1 条
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:106] - API 配置数组: [{'id': '2058819964287287298', 'businessName': 'qian50tiaodaorushujumingxichaxun_32b1d628', 'businessDescription': 'qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: qian50tiaodaorushujumingxichaxun_32b1d628: 查询数据导入明细表中的前50条记录,包含原始数据、处理状态、错误原因等关键信息,用于快速预览最近的导入情况。', 'sqlTemplate': 'SELECT record_id AS "记录ID", task_id AS "任务ID", original_data AS "原始数据", process_result AS "处理结果", error_reason AS "错误原因", process_status AS "处理状态", target_table AS "关联目标表", created_time AS "创建时间" FROM import_record_detail LIMIT 50;', 'parameters': {}, 'datasourceId': '240'}]
|
||||||
|
2026-05-25 16:26:38 - mcp_services - INFO - [main.py:130] - 成功生成 1 个 MCP 工具
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
2025-12-31 12:57:27 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
|
||||||
2025-12-31 12:57:27 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:299] - ============================================================
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:302] - ============================================================
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:309] - ============================================================
|
|
||||||
2025-12-31 12:57:27 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
|
|
||||||
2025-12-31 12:57:28 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
|
||||||
2025-12-31 12:57:28 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id:
|
|
||||||
2025-12-31 12:57:28 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 12:57:29 - httpx - INFO - [_client.py:1025] - HTTP Request: GET http://192.168.11.24:8088/datasource/skill/getBySkillId/ "HTTP/1.1 404 "
|
|
||||||
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"}
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id
|
|
||||||
response.raise_for_status()
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status
|
|
||||||
raise HTTPStatusError(message, request=request, response=self)
|
|
||||||
httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/'
|
|
||||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
|
|
||||||
|
|
||||||
During handling of the above exception, another exception occurred:
|
|
||||||
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
|
|
||||||
raw_result = get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
|
|
||||||
return default_client.get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id
|
|
||||||
raise Exception(error_msg)
|
|
||||||
Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 12:57:29 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 12:57:29 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 12:57:29 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:54 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
|
||||||
2025-12-31 12:57:54 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:299] - ============================================================
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:302] - ============================================================
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 16
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://192.168.11.24:8088
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:309] - ============================================================
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
|
|
||||||
2025-12-31 12:57:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: local)...
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:55] - 成功加载 3 个业务查询配置
|
|
||||||
2025-12-31 12:57:54 - mcp_services - INFO - [main.py:123] - 本地配置: 3 条
|
|
||||||
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:31 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 15:00:31 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:34 - mcp_services - INFO - [main.py:329] - MCP 服务器已关闭
|
|
||||||
2025-12-31 15:00:53 - root - INFO - [logger_config.py:151] - 日志系统初始化完成 - 日志目录: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\logs
|
|
||||||
2025-12-31 15:00:53 - root - INFO - [logger_config.py:152] - 日志配置 - 级别: INFO, 文件大小限制: 10MB, 备份数量: 5
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:347] - 开始运行 MCP SQL Executor 服务器
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:299] - ============================================================
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:300] - 正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:301] - 版本: 0.1.0
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:302] - ============================================================
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:306] - 环境配置 - Database ID: 29
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:307] - 环境配置 - Skill ID:
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:308] - 环境配置 - Backend Base URL: http://lzwcai-demp-corp-manager:8086
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:309] - ============================================================
|
|
||||||
2025-12-31 15:00:53 - mcp_services - INFO - [main.py:314] - MCP 服务器已启动,等待客户端连接...
|
|
||||||
2025-12-31 15:00:54 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:119] - 初始化查询配置(数据源: api)...
|
|
||||||
2025-12-31 15:00:54 - mcp_services - INFO - [main.py:278] - 调用第三方API,skill_id:
|
|
||||||
2025-12-31 15:00:54 - lzwcai_mcp_sqlexecutor.utils.api_client - INFO - [api_client.py:71] - 正在调用API: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
|
||||||
yield
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
|
||||||
resp = self._pool.handle_request(req)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request
|
|
||||||
raise exc from None
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request
|
|
||||||
response = connection.handle_request(
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request
|
|
||||||
raise exc
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request
|
|
||||||
stream = self._connect(request)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect
|
|
||||||
stream = self._network_backend.connect_tcp(**kwargs)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp
|
|
||||||
with map_exceptions(exc_map):
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
|
||||||
self.gen.throw(value)
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
|
||||||
raise to_exc(exc) from exc
|
|
||||||
httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
|
||||||
|
|
||||||
The above exception was the direct cause of the following exception:
|
|
||||||
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id
|
|
||||||
response = self.client.get(
|
|
||||||
^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
|
||||||
return self.request(
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 825, in request
|
|
||||||
return self.send(request, auth=auth, follow_redirects=follow_redirects)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 914, in send
|
|
||||||
response = self._send_handling_auth(
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 942, in _send_handling_auth
|
|
||||||
response = self._send_handling_redirects(
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 979, in _send_handling_redirects
|
|
||||||
response = self._send_single_request(request)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1014, in _send_single_request
|
|
||||||
response = transport.handle_request(request)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
|
||||||
with map_httpcore_exceptions():
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
|
||||||
self.gen.throw(value)
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
|
||||||
raise mapped_exc(message) from exc
|
|
||||||
httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
|
||||||
|
|
||||||
During handling of the above exception, another exception occurred:
|
|
||||||
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
|
|
||||||
raw_result = get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
|
|
||||||
return default_client.get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id
|
|
||||||
raise Exception(error_msg)
|
|
||||||
Exception: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
2025-12-31 15:00:56 - mcp_services - WARNING - [main.py:131] - API获取失败,降级使用本地配置: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:55] - 成功加载 0 个业务查询配置
|
|
||||||
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具
|
|
||||||
2025-12-31 15:00:56 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest
|
|
||||||
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:156] - 收到列出工具请求
|
|
||||||
2025-12-31 15:00:56 - mcp_services - INFO - [main.py:165] - 成功生成 0 个 MCP 工具
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,110 +1,26 @@
|
|||||||
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:97] - API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
2026-05-25 16:22:53 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:69] - API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
2025-12-31 12:57:29 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:98] - 错误响应: {"timestamp":"2025-12-31T12:57:30.248+08:00","status":404,"error":"Not Found","path":"/datasource/skill/getBySkillId/"}
|
2026-05-25 16:22:53 - mcp_services - ERROR - [main.py:209] - API 调用失败: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 80, in get_skill_by_id
|
|
||||||
response.raise_for_status()
|
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_models.py", line 829, in raise_for_status
|
|
||||||
raise HTTPStatusError(message, request=request, response=self)
|
|
||||||
httpx.HTTPStatusError: Client error '404 ' for url 'http://192.168.11.24:8088/datasource/skill/getBySkillId/'
|
|
||||||
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
|
|
||||||
|
|
||||||
During handling of the above exception, another exception occurred:
|
|
||||||
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
|
|
||||||
raw_result = get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
|
|
||||||
return default_client.get_skill_by_id(skill_id)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 99, in get_skill_by_id
|
|
||||||
raise Exception(error_msg)
|
|
||||||
Exception: API请求失败 (HTTP 404): http://192.168.11.24:8088/datasource/skill/getBySkillId/
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:29 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 12:57:54 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:92] - 生成工具模式失败: 2006300000000000001, 错误: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:31 - mcp_services - ERROR - [main.py:170] - 列出工具失败: 'businessName'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 162, in handle_list_tools
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 80, in generate_tool_schema_from_query
|
|
||||||
tool_name = query['businessName']
|
|
||||||
~~~~~^^^^^^^^^^^^^^^^
|
|
||||||
KeyError: 'businessName'
|
|
||||||
2025-12-31 15:00:56 - lzwcai_mcp_sqlexecutor.utils.api_client - ERROR - [api_client.py:103] - API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
2025-12-31 15:00:56 - mcp_services - ERROR - [main.py:292] - API调用失败: API请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 101, in map_httpcore_exceptions
|
||||||
yield
|
yield
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 250, in handle_request
|
||||||
resp = self._pool.handle_request(req)
|
resp = self._pool.handle_request(req)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 216, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 256, in handle_request
|
||||||
raise exc from None
|
raise exc from None
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 196, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection_pool.py", line 236, in handle_request
|
||||||
response = connection.handle_request(
|
response = connection.handle_request(
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 99, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 101, in handle_request
|
||||||
raise exc
|
raise exc
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 76, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 78, in handle_request
|
||||||
stream = self._connect(request)
|
stream = self._connect(request)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 122, in _connect
|
File "D:\anaconda3\Lib\site-packages\httpcore\_sync\connection.py", line 124, in _connect
|
||||||
stream = self._network_backend.connect_tcp(**kwargs)
|
stream = self._network_backend.connect_tcp(**kwargs)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 205, in connect_tcp
|
File "D:\anaconda3\Lib\site-packages\httpcore\_backends\sync.py", line 207, in connect_tcp
|
||||||
with map_exceptions(exc_map):
|
with map_exceptions(exc_map):
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||||
self.gen.throw(value)
|
self.gen.throw(value)
|
||||||
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
File "D:\anaconda3\Lib\site-packages\httpcore\_exceptions.py", line 14, in map_exceptions
|
||||||
@@ -114,9 +30,9 @@ httpcore.ConnectError: [Errno 11001] getaddrinfo failed
|
|||||||
The above exception was the direct cause of the following exception:
|
The above exception was the direct cause of the following exception:
|
||||||
|
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 74, in get_skill_by_id
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 50, in get_skill_by_id
|
||||||
response = self.client.get(
|
response = self.client.get(url, headers=self._get_headers())
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
File "D:\anaconda3\Lib\site-packages\httpx\_client.py", line 1053, in get
|
||||||
return self.request(
|
return self.request(
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
@@ -137,7 +53,6 @@ Traceback (most recent call last):
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 249, in handle_request
|
||||||
with map_httpcore_exceptions():
|
with map_httpcore_exceptions():
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
File "D:\anaconda3\Lib\contextlib.py", line 158, in __exit__
|
||||||
self.gen.throw(value)
|
self.gen.throw(value)
|
||||||
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
File "D:\anaconda3\Lib\site-packages\httpx\_transports\default.py", line 118, in map_httpcore_exceptions
|
||||||
@@ -147,12 +62,12 @@ httpx.ConnectError: [Errno 11001] getaddrinfo failed
|
|||||||
During handling of the above exception, another exception occurred:
|
During handling of the above exception, another exception occurred:
|
||||||
|
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 281, in call_third_party_api
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\main.py", line 202, in call_third_party_api
|
||||||
raw_result = get_skill_by_id(skill_id)
|
raw_result = get_skill_by_id(skill_id)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 208, in get_skill_by_id
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 154, in get_skill_by_id
|
||||||
return default_client.get_skill_by_id(skill_id)
|
return default_client.get_skill_by_id(skill_id)
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 104, in get_skill_by_id
|
File "E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_sqlexecutor\lzwcai_mcp_sqlexecutor\utils\api_client.py", line 70, in get_skill_by_id
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
Exception: API 请求异常: http://lzwcai-demp-corp-manager:8086/datasource/skill/getBySkillId/, 错误: [Errno 11001] getaddrinfo failed
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,13 +1,13 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
# 支持直接运行和模块导入两种方式
|
|
||||||
try:
|
try:
|
||||||
from .utils import load_json, generate_tool_name, generate_input_schema
|
from .utils import load_json, generate_input_schema
|
||||||
from .utils import get_skill_by_id, DataSourceAPIClient, process_skill_response, test_sql_with_schema
|
from .utils import get_skill_by_id, process_skill_response, test_sql_with_schema
|
||||||
from .utils import get_database_id, get_skill_id, get_env_config
|
from .utils import get_skill_id, get_env_config
|
||||||
from .utils.logger_config import logger_config
|
from .utils.logger_config import logger_config
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from utils import load_json, generate_input_schema
|
from utils import load_json, generate_input_schema
|
||||||
@@ -19,41 +19,43 @@ from mcp.server.models import InitializationOptions
|
|||||||
from mcp.server import NotificationOptions, Server
|
from mcp.server import NotificationOptions, Server
|
||||||
import mcp.types as types
|
import mcp.types as types
|
||||||
|
|
||||||
# 初始化 MCP 专用日志器
|
|
||||||
mcp_logger = logger_config.setup_mcp_logging()
|
mcp_logger = logger_config.setup_mcp_logging()
|
||||||
|
|
||||||
# ========== 数据源配置 ==========
|
DATA_SOURCE_API = "api"
|
||||||
# 数据源类型常量
|
DATA_SOURCE_LOCAL = "local"
|
||||||
DATA_SOURCE_API = "api" # 仅使用API数据
|
DATA_SOURCE_BOTH = "both"
|
||||||
DATA_SOURCE_LOCAL = "local" # 仅使用本地JSON数据
|
|
||||||
DATA_SOURCE_BOTH = "both" # 合并本地和API数据
|
|
||||||
|
|
||||||
# 默认数据源(可修改)
|
|
||||||
DEFAULT_DATA_SOURCE = DATA_SOURCE_API
|
DEFAULT_DATA_SOURCE = DATA_SOURCE_API
|
||||||
# ================================
|
|
||||||
|
|
||||||
|
def _text_response(payload: dict[str, Any]) -> list[types.TextContent]:
|
||||||
|
"""Build a JSON text response."""
|
||||||
|
return [
|
||||||
|
types.TextContent(
|
||||||
|
type="text",
|
||||||
|
text=json.dumps(payload, ensure_ascii=False, indent=2)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_user_id(arguments: dict[str, Any]) -> Any:
|
||||||
|
"""Allow numeric 0, reject None and blank strings."""
|
||||||
|
user_id = arguments.get("userId")
|
||||||
|
if user_id is None:
|
||||||
|
return None
|
||||||
|
if isinstance(user_id, str) and not user_id.strip():
|
||||||
|
return None
|
||||||
|
return user_id
|
||||||
|
|
||||||
|
|
||||||
def get_queries():
|
def get_queries():
|
||||||
"""
|
"""Read local business query config."""
|
||||||
获取业务查询配置
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: 包含所有业务查询配置的列表
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 获取当前文件所在目录
|
|
||||||
current_dir = Path(__file__).parent
|
current_dir = Path(__file__).parent
|
||||||
|
|
||||||
# 构建 businessQueries.json 的路径
|
|
||||||
json_path = current_dir / "businessQueries.json"
|
json_path = current_dir / "businessQueries.json"
|
||||||
|
|
||||||
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
mcp_logger.debug(f"正在读取业务查询配置文件: {json_path}")
|
||||||
|
|
||||||
# 使用 load_json 方法读取 JSON 文件
|
|
||||||
queries = load_json(json_path)
|
queries = load_json(json_path)
|
||||||
|
|
||||||
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
mcp_logger.info(f"成功加载 {len(queries)} 个业务查询配置")
|
||||||
|
|
||||||
return queries
|
return queries
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
mcp_logger.error(f"加载业务查询配置失败: {e}", exc_info=True)
|
||||||
@@ -61,24 +63,11 @@ def get_queries():
|
|||||||
|
|
||||||
|
|
||||||
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
||||||
"""
|
"""Generate an MCP tool definition from one query config."""
|
||||||
根据查询配置生成 MCP 工具模式
|
|
||||||
|
|
||||||
Args:
|
|
||||||
query: 单个查询配置字典
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
types.Tool: MCP 工具对象
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 获取参数定义并生成 inputSchema
|
parameters = query.get("parameters", {})
|
||||||
parameters = query.get('parameters', {})
|
|
||||||
input_schema = generate_input_schema(parameters)
|
input_schema = generate_input_schema(parameters)
|
||||||
|
tool_name = query["businessName"]
|
||||||
# 生成工具名称(格式: tool_拼音_id)
|
|
||||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
|
||||||
tool_name = query['businessName']
|
|
||||||
# 构建工具描述,包含业务名称和业务描述
|
|
||||||
description = f"{query['businessName']}: {query['businessDescription']}"
|
description = f"{query['businessName']}: {query['businessDescription']}"
|
||||||
|
|
||||||
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
mcp_logger.debug(f"生成工具模式: {tool_name} - {query['businessName']}")
|
||||||
@@ -89,39 +78,27 @@ def generate_tool_schema_from_query(query: dict) -> types.Tool:
|
|||||||
inputSchema=input_schema
|
inputSchema=input_schema
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}", exc_info=True)
|
mcp_logger.error(
|
||||||
|
f"生成工具模式失败: {query.get('id', 'unknown')}, 错误: {e}",
|
||||||
|
exc_info=True
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
# 创建 MCP 服务器实例
|
|
||||||
server = Server("lzwcai-mcp-sqlexecutor")
|
server = Server("lzwcai-mcp-sqlexecutor")
|
||||||
|
|
||||||
# 缓存查询配置,避免重复加载
|
|
||||||
_queries_cache = None
|
_queries_cache = None
|
||||||
|
|
||||||
|
|
||||||
async def get_queries_cache(source: str = None):
|
async def get_queries_cache(source: str = None):
|
||||||
"""
|
"""Get or initialize query config cache."""
|
||||||
获取或初始化查询配置缓存
|
|
||||||
|
|
||||||
Args:
|
|
||||||
source: 数据源类型(默认使用 DEFAULT_DATA_SOURCE)
|
|
||||||
- "api": 仅使用API数据
|
|
||||||
- "local": 仅使用本地JSON数据
|
|
||||||
- "both": 合并本地和API数据
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
查询配置列表
|
|
||||||
"""
|
|
||||||
global _queries_cache
|
global _queries_cache
|
||||||
if _queries_cache is None:
|
if _queries_cache is None:
|
||||||
source = source or DEFAULT_DATA_SOURCE
|
source = source or DEFAULT_DATA_SOURCE
|
||||||
mcp_logger.info(f"初始化查询配置(数据源: {source})...")
|
mcp_logger.info(f"初始化查询配置,数据源: {source}")
|
||||||
|
|
||||||
if source == DATA_SOURCE_LOCAL:
|
if source == DATA_SOURCE_LOCAL:
|
||||||
_queries_cache = get_queries()
|
_queries_cache = get_queries()
|
||||||
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
mcp_logger.info(f"本地配置: {len(_queries_cache)} 条")
|
||||||
|
|
||||||
elif source == DATA_SOURCE_API:
|
elif source == DATA_SOURCE_API:
|
||||||
try:
|
try:
|
||||||
_queries_cache = await call_third_party_api()
|
_queries_cache = await call_third_party_api()
|
||||||
@@ -130,8 +107,7 @@ async def get_queries_cache(source: str = None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}")
|
mcp_logger.warning(f"API 获取失败,降级使用本地配置: {e}")
|
||||||
_queries_cache = get_queries()
|
_queries_cache = get_queries()
|
||||||
|
else:
|
||||||
else: # DATA_SOURCE_BOTH
|
|
||||||
local = get_queries()
|
local = get_queries()
|
||||||
try:
|
try:
|
||||||
api = await call_third_party_api()
|
api = await call_third_party_api()
|
||||||
@@ -139,32 +115,20 @@ async def get_queries_cache(source: str = None):
|
|||||||
mcp_logger.warning(f"API 获取失败: {e}")
|
mcp_logger.warning(f"API 获取失败: {e}")
|
||||||
api = []
|
api = []
|
||||||
_queries_cache = local + api
|
_queries_cache = local + api
|
||||||
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条(本地{len(local)}+API{len(api)})")
|
mcp_logger.info(f"配置总数: {len(_queries_cache)} 条,本地 {len(local)} + API {len(api)}")
|
||||||
|
|
||||||
return _queries_cache
|
return _queries_cache
|
||||||
|
|
||||||
|
|
||||||
@server.list_tools()
|
@server.list_tools()
|
||||||
async def handle_list_tools() -> list[types.Tool]:
|
async def handle_list_tools() -> list[types.Tool]:
|
||||||
"""
|
"""List all dynamically generated MCP tools."""
|
||||||
列出所有动态生成的 MCP 工具
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[types.Tool]: 所有可用的工具列表
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
mcp_logger.info("收到列出工具请求")
|
mcp_logger.info("收到列出工具请求")
|
||||||
|
|
||||||
queries = await get_queries_cache()
|
queries = await get_queries_cache()
|
||||||
tools = []
|
tools = [generate_tool_schema_from_query(query) for query in queries]
|
||||||
|
|
||||||
for query in queries:
|
|
||||||
tool = generate_tool_schema_from_query(query)
|
|
||||||
tools.append(tool)
|
|
||||||
|
|
||||||
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
mcp_logger.info(f"成功生成 {len(tools)} 个 MCP 工具")
|
||||||
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
mcp_logger.debug(f"工具列表: {[tool.name for tool in tools]}")
|
||||||
|
|
||||||
return tools
|
return tools
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
mcp_logger.error(f"列出工具失败: {e}", exc_info=True)
|
||||||
@@ -176,137 +140,84 @@ async def handle_call_tool(
|
|||||||
name: str,
|
name: str,
|
||||||
arguments: dict[str, Any] | None
|
arguments: dict[str, Any] | None
|
||||||
) -> list[types.TextContent]:
|
) -> list[types.TextContent]:
|
||||||
"""
|
"""Handle MCP tool invocation."""
|
||||||
处理工具调用请求
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 工具名称
|
|
||||||
arguments: 工具参数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[types.TextContent]: 工具执行结果(返回参数和对应的接口配置)
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
mcp_logger.info(f"收到工具调用请求: {name}")
|
mcp_logger.info(f"收到工具调用请求: {name}")
|
||||||
mcp_logger.debug(f"工具参数: {arguments}")
|
mcp_logger.debug(f"工具参数: {arguments}")
|
||||||
|
|
||||||
# 获取查询配置缓存
|
|
||||||
queries = await get_queries_cache()
|
queries = await get_queries_cache()
|
||||||
|
tool_item = next((query for query in queries if query["businessName"] == name), None)
|
||||||
|
|
||||||
# 根据工具名称查找对应的 item(接口配置)
|
if not tool_item:
|
||||||
tool_item = None
|
return _text_response({"error": f"未找到工具 {name} 对应的配置"})
|
||||||
for query in queries:
|
|
||||||
# tool_name = generate_tool_name(query['businessName'], query['id'])
|
|
||||||
tool_name = query['businessName']
|
|
||||||
if tool_name == name:
|
|
||||||
tool_item = query
|
|
||||||
break
|
|
||||||
|
|
||||||
# 构建返回结果
|
normalized_parameters = generate_input_schema(tool_item.get("parameters"))
|
||||||
import json
|
normalized_arguments = arguments or {}
|
||||||
|
user_id = _extract_user_id(normalized_arguments)
|
||||||
|
|
||||||
|
if user_id is None:
|
||||||
|
error_msg = f"工具 {name} 缺少必填参数 userId"
|
||||||
|
mcp_logger.warning(error_msg)
|
||||||
|
return _text_response({"error": error_msg, "required": ["userId"]})
|
||||||
|
|
||||||
if tool_item:
|
|
||||||
request_data = {
|
request_data = {
|
||||||
"datasourceId": tool_item.get("datasourceId"),
|
"datasourceId": tool_item.get("datasourceId"),
|
||||||
"businessName": tool_item.get("businessName"),
|
"businessName": tool_item.get("businessName"),
|
||||||
"businessDescription": tool_item.get("businessDescription"),
|
"businessDescription": tool_item.get("businessDescription"),
|
||||||
"sqlTemplate": tool_item.get("sqlTemplate"),
|
"sqlTemplate": tool_item.get("sqlTemplate"),
|
||||||
"parameters": tool_item.get("parameters"),
|
"parameters": normalized_parameters,
|
||||||
"testParams": arguments or {}
|
"testParams": normalized_arguments,
|
||||||
|
"userId": user_id
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果 arguments 中有 targetDatabaseName 且有值,添加到 request_data
|
target_database_name = normalized_arguments.get("targetDatabaseName")
|
||||||
if arguments and arguments.get("targetDatabaseName"):
|
if target_database_name:
|
||||||
request_data["targetDatabaseName"] = arguments["targetDatabaseName"]
|
request_data["targetDatabaseName"] = target_database_name
|
||||||
mcp_logger.debug(f"添加目标数据库名称: {arguments['targetDatabaseName']}")
|
mcp_logger.debug(f"添加目标数据库名称: {target_database_name}")
|
||||||
|
|
||||||
# 如果 arguments 中有 userId 且有值,添加到 request_data
|
|
||||||
if arguments and arguments.get("userId"):
|
|
||||||
request_data["userId"] = arguments["userId"]
|
|
||||||
mcp_logger.debug(f"添加用户ID: {arguments['userId']}")
|
|
||||||
|
|
||||||
# 调用测试SQL API
|
|
||||||
try:
|
try:
|
||||||
mcp_logger.info("正在调用测试 SQL API...")
|
mcp_logger.info("正在调用测试 SQL API...")
|
||||||
api_response = test_sql_with_schema(request_data)
|
result_payload = test_sql_with_schema(request_data)
|
||||||
mcp_logger.info("测试 SQL API 调用成功")
|
mcp_logger.info("测试 SQL API 调用成功")
|
||||||
|
|
||||||
# 只返回 API 响应结果
|
|
||||||
result_text = json.dumps(api_response, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"调用测试 SQL API 失败: {str(e)}"
|
error_msg = f"调用测试 SQL API 失败: {str(e)}"
|
||||||
mcp_logger.error(error_msg, exc_info=True)
|
mcp_logger.error(error_msg, exc_info=True)
|
||||||
result_text = json.dumps({"error": error_msg}, ensure_ascii=False, indent=2)
|
result_payload = {"error": error_msg}
|
||||||
else:
|
|
||||||
error_msg = f"未找到工具 {name} 对应的配置"
|
|
||||||
result_text = json.dumps({"error": error_msg}, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
mcp_logger.debug(f"工具调用结果: {result_text}")
|
mcp_logger.debug(f"工具调用结果: {result_payload}")
|
||||||
|
return _text_response(result_payload)
|
||||||
return [
|
|
||||||
types.TextContent(
|
|
||||||
type="text",
|
|
||||||
text=result_text
|
|
||||||
)
|
|
||||||
]
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
error_msg = f"工具调用失败: {name}, 错误: {e}"
|
||||||
mcp_logger.error(error_msg, exc_info=True)
|
mcp_logger.error(error_msg, exc_info=True)
|
||||||
return [
|
return [types.TextContent(type="text", text=f"错误: {error_msg}")]
|
||||||
types.TextContent(
|
|
||||||
type="text",
|
|
||||||
text=f"错误: {error_msg}"
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def call_third_party_api(skill_id: str = None) -> list:
|
async def call_third_party_api(skill_id: str = None) -> list:
|
||||||
"""
|
"""Call backend API and map the response into query configs."""
|
||||||
调用第三方API获取技能信息并返回处理后的数据
|
|
||||||
|
|
||||||
Args:
|
|
||||||
skill_id: 技能ID(默认从环境变量 SKILL_ID 读取,如果未设置则使用 1981000305474482178)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
处理后的查询配置列表(businessQueries格式)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
queries = await call_third_party_api()
|
|
||||||
# 返回: [{"id": "...", "businessName": "...", ...}, ...]
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 如果没有传入 skill_id,则从环境变量读取
|
|
||||||
if skill_id is None:
|
if skill_id is None:
|
||||||
skill_id = get_skill_id()
|
skill_id = get_skill_id()
|
||||||
|
|
||||||
mcp_logger.info(f"调用第三方 API,skill_id: {skill_id}")
|
mcp_logger.info(f"调用第三方 API,skill_id: {skill_id}")
|
||||||
|
|
||||||
# 获取原始数据
|
|
||||||
raw_result = get_skill_by_id(skill_id)
|
raw_result = get_skill_by_id(skill_id)
|
||||||
|
mcp_logger.info(f"成功获取原始响应: {raw_result}")
|
||||||
|
|
||||||
mcp_logger.info(f"成功{raw_result}")
|
|
||||||
|
|
||||||
# 处理并返回
|
|
||||||
processed_queries = process_skill_response(raw_result)
|
processed_queries = process_skill_response(raw_result)
|
||||||
|
mcp_logger.info(f"成功处理 {len(processed_queries)} 条数据")
|
||||||
mcp_logger.info(f"成功获取并处理 {len(processed_queries)} 条数据")
|
|
||||||
return processed_queries
|
return processed_queries
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"API 调用失败: {e}", exc_info=True)
|
mcp_logger.error(f"API 调用失败: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
async def async_main():
|
async def async_main():
|
||||||
"""MCP 服务器异步主函数"""
|
"""Async entry for the MCP server."""
|
||||||
try:
|
try:
|
||||||
mcp_logger.info("=" * 60)
|
mcp_logger.info("=" * 60)
|
||||||
mcp_logger.info("正在启动 MCP 服务器: lzwcai-mcp-sqlexecutor")
|
mcp_logger.info("正在启动 MCP 服务: lzwcai-mcp-sqlexecutor")
|
||||||
mcp_logger.info("版本: 0.1.0")
|
mcp_logger.info("版本: 0.1.0")
|
||||||
mcp_logger.info("=" * 60)
|
mcp_logger.info("=" * 60)
|
||||||
|
|
||||||
# 输出环境配置信息
|
|
||||||
env_config = get_env_config()
|
env_config = get_env_config()
|
||||||
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
mcp_logger.info(f"环境配置 - Database ID: {env_config['database_id']}")
|
||||||
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
mcp_logger.info(f"环境配置 - Skill ID: {env_config['skill_id']}")
|
||||||
@@ -316,8 +227,7 @@ async def async_main():
|
|||||||
from mcp.server.stdio import stdio_server
|
from mcp.server.stdio import stdio_server
|
||||||
|
|
||||||
async with stdio_server() as (read_stream, write_stream):
|
async with stdio_server() as (read_stream, write_stream):
|
||||||
mcp_logger.info("MCP 服务器已启动,等待客户端连接...")
|
mcp_logger.info("MCP 服务已启动,等待客户端连接...")
|
||||||
|
|
||||||
await server.run(
|
await server.run(
|
||||||
read_stream,
|
read_stream,
|
||||||
write_stream,
|
write_stream,
|
||||||
@@ -331,29 +241,24 @@ async def async_main():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
mcp_logger.info("MCP 服务器已关闭")
|
mcp_logger.info("MCP 服务已关闭")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"MCP 服务器运行失败: {e}", exc_info=True)
|
mcp_logger.error(f"MCP 服务运行失败: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""入口点函数(用于 console_scripts)"""
|
"""Console entrypoint."""
|
||||||
try:
|
try:
|
||||||
# 初始化系统日志
|
|
||||||
# MCP协议使用stdio通信,必须禁用控制台输出以避免干扰JSON-RPC通信
|
|
||||||
logger_config.setup_logging(
|
logger_config.setup_logging(
|
||||||
app_name="lzwcai_mcp_sqlexecutor",
|
app_name="lzwcai_mcp_sqlexecutor",
|
||||||
log_level=logging.INFO,
|
log_level=logging.INFO,
|
||||||
console_output=False # 禁用控制台输出
|
console_output=False
|
||||||
)
|
)
|
||||||
|
mcp_logger.info("开始运行 MCP SQL Executor 服务")
|
||||||
mcp_logger.info("开始运行 MCP SQL Executor 服务器")
|
|
||||||
asyncio.run(async_main())
|
asyncio.run(async_main())
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
mcp_logger.info("收到中断信号,正在关闭服务器...")
|
mcp_logger.info("收到中断信号,正在关闭服务...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
mcp_logger.error(f"程序运行失败: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -1,134 +1,81 @@
|
|||||||
"""
|
"""Backend API client helpers."""
|
||||||
第三方API调用客户端
|
|
||||||
用于调用外部数据源接口
|
import json
|
||||||
"""
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import logging
|
|
||||||
import json
|
|
||||||
from typing import Dict, Any, Optional, List
|
|
||||||
|
|
||||||
# 支持直接运行和模块导入两种方式
|
|
||||||
try:
|
try:
|
||||||
from .env_config import get_backend_base_url
|
from .env_config import get_backend_base_url
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from env_config import get_backend_base_url
|
from env_config import get_backend_base_url
|
||||||
|
|
||||||
# 获取日志记录器
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DataSourceAPIClient:
|
class DataSourceAPIClient:
|
||||||
"""数据源API客户端"""
|
"""HTTP client for backend datasource APIs."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_url: Optional[str] = None,
|
base_url: Optional[str] = None,
|
||||||
token: Optional[str] = None
|
token: Optional[str] = None
|
||||||
):
|
):
|
||||||
"""
|
|
||||||
初始化API客户端
|
|
||||||
|
|
||||||
Args:
|
|
||||||
base_url: API基础URL(默认从环境变量 BACKEND_BASE_URL 读取,如果未设置则使用 http://192.168.2.236:8088)
|
|
||||||
token: 认证令牌(Bearer Token)
|
|
||||||
"""
|
|
||||||
# 如果没有传入 base_url,则从环境变量读取
|
|
||||||
if base_url is None:
|
if base_url is None:
|
||||||
base_url = get_backend_base_url()
|
base_url = get_backend_base_url()
|
||||||
|
|
||||||
self.base_url = base_url.rstrip('/')
|
self.base_url = base_url.rstrip("/")
|
||||||
self.token = token or "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1iZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dnvqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
|
self.token = token or (
|
||||||
|
"eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiYTk4ODllLWM2ZGItNDQ5YS1i"
|
||||||
|
"ZmFjLTQ2YzMxODFlODg5NCJ9.dvi8zm0LsWvJ_h9zD5blnHFRxa4z4_WBm1R487ekE7HlHzrN6dn"
|
||||||
|
"vqhK8askqT5b1EcE8myHwRzLVMoI8UOjOrw"
|
||||||
|
)
|
||||||
self.client = httpx.Client(timeout=30.0)
|
self.client = httpx.Client(timeout=30.0)
|
||||||
|
|
||||||
def _get_headers(self) -> Dict[str, str]:
|
def _get_headers(self) -> Dict[str, str]:
|
||||||
"""
|
|
||||||
获取请求头
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
请求头字典
|
|
||||||
"""
|
|
||||||
return {
|
return {
|
||||||
'Authorization': f'Bearer {self.token}',
|
"Authorization": f"Bearer {self.token}",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
|
def get_skill_by_id(self, skill_id: str) -> Dict[str, Any]:
|
||||||
"""
|
"""Fetch skill details by skill id."""
|
||||||
根据技能ID获取技能信息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
skill_id: 技能ID
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
API响应数据
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
Exception: 请求失败时抛出
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
|
url = f"{self.base_url}/datasource/skill/getBySkillId/{skill_id}"
|
||||||
|
|
||||||
logger.info(f"正在调用 API: {url}")
|
logger.info(f"正在调用 API: {url}")
|
||||||
logger.debug(f"请求参数 - skill_id: {skill_id}")
|
logger.debug(f"请求参数 - skill_id: {skill_id}")
|
||||||
|
|
||||||
response = self.client.get(
|
response = self.client.get(url, headers=self._get_headers())
|
||||||
url,
|
|
||||||
headers=self._get_headers()
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查HTTP状态码
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
# 解析JSON响应
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
logger.info(f"API 调用成功: {url}")
|
logger.info(f"API 调用成功: {url}")
|
||||||
logger.debug(f"响应数据: {data}")
|
logger.debug(f"响应数据: {data}")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
except httpx.TimeoutException:
|
except httpx.TimeoutException:
|
||||||
error_msg = f"API 请求超时: {url}"
|
error_msg = f"API 请求超时: {url}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}"
|
error_msg = f"API 请求失败 (HTTP {e.response.status_code}): {url}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
logger.error(f"错误响应: {e.response.text}")
|
logger.error(f"错误响应: {e.response.text}")
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except httpx.RequestError as e:
|
except httpx.RequestError as e:
|
||||||
error_msg = f"API 请求异常: {url}, 错误: {str(e)}"
|
error_msg = f"API 请求异常: {url}, 错误: {str(e)}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"处理 API 响应时出错: {str(e)}"
|
error_msg = f"处理 API 响应时出错: {str(e)}"
|
||||||
logger.error(error_msg, exc_info=True)
|
logger.error(error_msg, exc_info=True)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
def test_sql_with_schema(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""Call backend SQL test endpoint."""
|
||||||
测试SQL语句并返回执行结果
|
|
||||||
|
|
||||||
Args:
|
|
||||||
request_data: 请求数据,包含以下字段:
|
|
||||||
- datasourceId: 数据源ID
|
|
||||||
- businessName: 业务名称
|
|
||||||
- businessDescription: 业务描述
|
|
||||||
- sqlTemplate: SQL模板
|
|
||||||
- parameters: 参数定义
|
|
||||||
- testParams: 测试参数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
API响应数据
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
Exception: 请求失败时抛出
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 详细记录传入的数据
|
|
||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
logger.info("test_sql_with_schema 接口接收到的数据:")
|
logger.info("test_sql_with_schema 接口接收到的数据:")
|
||||||
logger.info(f"数据类型: {type(request_data)}")
|
logger.info(f"数据类型: {type(request_data)}")
|
||||||
@@ -139,34 +86,22 @@ class DataSourceAPIClient:
|
|||||||
logger.info(f"SQL 模板: {request_data.get('sqlTemplate')}")
|
logger.info(f"SQL 模板: {request_data.get('sqlTemplate')}")
|
||||||
logger.info(f"参数定义: {request_data.get('parameters')}")
|
logger.info(f"参数定义: {request_data.get('parameters')}")
|
||||||
logger.info(f"测试参数: {request_data.get('testParams')}")
|
logger.info(f"测试参数: {request_data.get('testParams')}")
|
||||||
if 'userId' in request_data:
|
if "userId" in request_data:
|
||||||
logger.info(f"用户 ID: {request_data.get('userId')}")
|
logger.info(f"用户 ID: {request_data.get('userId')}")
|
||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
|
|
||||||
url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema"
|
url = f"{self.base_url}/datasource/sqlExecutionLog/testSqlWithSchema"
|
||||||
|
|
||||||
# 构建请求头(包含Content-Type)
|
|
||||||
headers = self._get_headers()
|
headers = self._get_headers()
|
||||||
headers['Content-Type'] = 'application/json'
|
headers["Content-Type"] = "application/json"
|
||||||
headers['Accept'] = '*/*'
|
headers["Accept"] = "*/*"
|
||||||
|
|
||||||
logger.info(f"正在调用测试 SQL API: {url}")
|
logger.info(f"正在调用测试 SQL API: {url}")
|
||||||
logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
|
logger.debug(f"请求数据: {json.dumps(request_data, ensure_ascii=False, indent=2)}")
|
||||||
|
|
||||||
# 发送POST请求
|
response = self.client.post(url, headers=headers, json=request_data)
|
||||||
response = self.client.post(
|
|
||||||
url,
|
|
||||||
headers=headers,
|
|
||||||
json=request_data
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查HTTP状态码
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
# 解析JSON响应
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
|
|
||||||
# 详细记录返回的数据
|
|
||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
logger.info("test_sql_with_schema 接口返回的数据:")
|
logger.info("test_sql_with_schema 接口返回的数据:")
|
||||||
logger.info(f"HTTP 状态码: {response.status_code}")
|
logger.info(f"HTTP 状态码: {response.status_code}")
|
||||||
@@ -179,152 +114,96 @@ class DataSourceAPIClient:
|
|||||||
logger.info("=" * 80)
|
logger.info("=" * 80)
|
||||||
|
|
||||||
logger.info("测试 SQL API 调用成功")
|
logger.info("测试 SQL API 调用成功")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
except httpx.TimeoutException:
|
except httpx.TimeoutException:
|
||||||
error_msg = f"测试 SQL API 请求超时: {url}"
|
error_msg = f"测试 SQL API 请求超时: {url}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
error_msg = f"测试 SQL API 请求失败 (HTTP {e.response.status_code}): {url}"
|
error_msg = f"测试 SQL API 请求失败 (HTTP {e.response.status_code}): {url}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
logger.error(f"错误响应: {e.response.text}")
|
logger.error(f"错误响应: {e.response.text}")
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except httpx.RequestError as e:
|
except httpx.RequestError as e:
|
||||||
error_msg = f"测试 SQL API 请求异常: {url}, 错误: {str(e)}"
|
error_msg = f"测试 SQL API 请求异常: {url}, 错误: {str(e)}"
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"处理测试 SQL API 响应时出错: {str(e)}"
|
error_msg = f"处理测试 SQL API 响应时出错: {str(e)}"
|
||||||
logger.error(error_msg, exc_info=True)
|
logger.error(error_msg, exc_info=True)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""关闭HTTP客户端"""
|
"""Close the underlying HTTP client."""
|
||||||
self.client.close()
|
self.client.close()
|
||||||
|
|
||||||
|
|
||||||
# 创建默认客户端实例
|
|
||||||
default_client = DataSourceAPIClient()
|
default_client = DataSourceAPIClient()
|
||||||
|
|
||||||
|
|
||||||
def get_skill_by_id(skill_id: str, base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
|
def get_skill_by_id(
|
||||||
"""
|
skill_id: str,
|
||||||
便捷函数:根据技能ID获取技能信息
|
base_url: Optional[str] = None,
|
||||||
|
token: Optional[str] = None
|
||||||
Args:
|
) -> Dict[str, Any]:
|
||||||
skill_id: 技能ID
|
"""Convenience wrapper for skill lookup."""
|
||||||
base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取)
|
|
||||||
token: 认证令牌(可选,使用默认值)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
API响应数据
|
|
||||||
"""
|
|
||||||
if base_url or token:
|
if base_url or token:
|
||||||
client = DataSourceAPIClient(
|
client = DataSourceAPIClient(base_url=base_url, token=token)
|
||||||
base_url=base_url,
|
|
||||||
token=token
|
|
||||||
)
|
|
||||||
return client.get_skill_by_id(skill_id)
|
return client.get_skill_by_id(skill_id)
|
||||||
else:
|
|
||||||
return default_client.get_skill_by_id(skill_id)
|
return default_client.get_skill_by_id(skill_id)
|
||||||
|
|
||||||
|
|
||||||
def test_sql_with_schema(request_data: Dict[str, Any], base_url: Optional[str] = None, token: Optional[str] = None) -> Dict[str, Any]:
|
def test_sql_with_schema(
|
||||||
"""
|
request_data: Dict[str, Any],
|
||||||
便捷函数:测试SQL语句并返回执行结果
|
base_url: Optional[str] = None,
|
||||||
|
token: Optional[str] = None
|
||||||
Args:
|
) -> Dict[str, Any]:
|
||||||
request_data: 请求数据,包含以下字段:
|
"""Convenience wrapper for SQL test endpoint."""
|
||||||
- datasourceId: 数据源ID
|
|
||||||
- businessName: 业务名称
|
|
||||||
- businessDescription: 业务描述
|
|
||||||
- sqlTemplate: SQL模板
|
|
||||||
- parameters: 参数定义
|
|
||||||
- testParams: 测试参数
|
|
||||||
base_url: API基础URL(可选,默认从环境变量 BACKEND_BASE_URL 读取)
|
|
||||||
token: 认证令牌(可选,使用默认值)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
API响应数据
|
|
||||||
"""
|
|
||||||
if base_url or token:
|
if base_url or token:
|
||||||
client = DataSourceAPIClient(
|
client = DataSourceAPIClient(base_url=base_url, token=token)
|
||||||
base_url=base_url,
|
|
||||||
token=token
|
|
||||||
)
|
|
||||||
return client.test_sql_with_schema(request_data)
|
return client.test_sql_with_schema(request_data)
|
||||||
else:
|
|
||||||
return default_client.test_sql_with_schema(request_data)
|
return default_client.test_sql_with_schema(request_data)
|
||||||
|
|
||||||
|
|
||||||
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
def process_skill_response(response: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""Map backend skill response into business query configs."""
|
||||||
处理API响应数据,映射为businessQueries格式
|
|
||||||
|
|
||||||
Args:
|
|
||||||
response: API原始响应数据
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
处理后的查询配置列表
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# 提取data数组
|
|
||||||
data_list = response.get("data", [])
|
data_list = response.get("data", [])
|
||||||
|
|
||||||
# 默认的员工ID参数schema
|
|
||||||
default_employee_schema = {
|
|
||||||
"type": "object",
|
|
||||||
"required": ["employeeId"],
|
|
||||||
"properties": {
|
|
||||||
"employeeId": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "员工ID,用于标识员工的唯一数字标识符",
|
|
||||||
"examples": [1001, 2002]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 映射每个skill为businessQuery格式
|
|
||||||
queries = []
|
queries = []
|
||||||
for skill in data_list:
|
|
||||||
# 解析sqlParams字符串为JSON对象
|
|
||||||
sql_params_str = skill.get("sqlParams") or "{}"
|
|
||||||
sql_params = json.loads(sql_params_str)
|
|
||||||
|
|
||||||
# 判断sqlParams是否为空对象
|
for skill in data_list:
|
||||||
is_empty_params = (
|
sql_params_raw = skill.get("sqlParams")
|
||||||
not sql_params.get("properties") or
|
sql_params: Dict[str, Any] = {}
|
||||||
len(sql_params.get("properties", {})) == 0
|
|
||||||
) and (
|
if isinstance(sql_params_raw, dict):
|
||||||
not sql_params.get("required") or
|
sql_params = sql_params_raw
|
||||||
len(sql_params.get("required", [])) == 0
|
elif isinstance(sql_params_raw, str) and sql_params_raw.strip():
|
||||||
|
try:
|
||||||
|
parsed_sql_params = json.loads(sql_params_raw)
|
||||||
|
if isinstance(parsed_sql_params, dict):
|
||||||
|
sql_params = parsed_sql_params
|
||||||
|
else:
|
||||||
|
logger.warning(
|
||||||
|
f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是对象,已回退为空对象"
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logger.warning(
|
||||||
|
f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的 sqlParams 不是合法 JSON,已回退为空对象"
|
||||||
)
|
)
|
||||||
|
|
||||||
# # 如果是空对象,使用默认的员工ID参数
|
|
||||||
# if is_empty_params:
|
|
||||||
# logger.info(f"技能 {skill.get('name')} (ID: {skill.get('id')}) 的sqlParams为空,使用默认员工ID参数")
|
|
||||||
# sql_params = default_employee_schema
|
|
||||||
|
|
||||||
# 映射字段
|
|
||||||
query = {
|
query = {
|
||||||
"id": skill.get("id"),
|
"id": skill.get("id"),
|
||||||
"businessName": skill.get("name"),
|
"businessName": skill.get("name"),
|
||||||
"businessDescription": skill.get("description"),
|
"businessDescription": skill.get("description"),
|
||||||
"sqlTemplate": skill.get("sqlTemplate"),
|
"sqlTemplate": skill.get("sqlTemplate"),
|
||||||
"parameters": sql_params,
|
"parameters": sql_params,
|
||||||
"datasourceId": skill.get("datasourceId")
|
"datasourceId": skill.get("datasourceId"),
|
||||||
}
|
}
|
||||||
queries.append(query)
|
queries.append(query)
|
||||||
|
|
||||||
logger.info(f"成功处理 {len(queries)} 条技能数据")
|
logger.info(f"成功处理 {len(queries)} 条技能数据")
|
||||||
return queries
|
return queries
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理 API 响应数据失败: {e}", exc_info=True)
|
logger.error(f"处理 API 响应数据失败: {e}", exc_info=True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,43 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Schema 生成工具模块
|
Schema 生成工具模块。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
|
||||||
def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]:
|
def _normalize_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
|
||||||
"""
|
"""将任意参数定义归一化为 object schema。"""
|
||||||
从查询配置的参数定义生成 MCP 工具的 inputSchema
|
if not isinstance(parameters, dict) or not parameters:
|
||||||
|
|
||||||
此函数会保留完整的 JSON Schema 信息,包括:
|
|
||||||
- type: Schema 类型(通常是 "object")
|
|
||||||
- required: 必填字段列表
|
|
||||||
- properties: 属性定义(包括每个属性的 type, description, format, examples 等)
|
|
||||||
- description: Schema 的整体描述(如果有)
|
|
||||||
- 以及其他任何 JSON Schema 标准字段
|
|
||||||
|
|
||||||
此函数还会自动添加以下字段(如果原始 parameters 中未定义):
|
|
||||||
- targetDatabaseName: 目标数据库名称(非必填,默认为空字符串)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
parameters: 查询配置中的参数定义字典,应该是一个完整的 JSON Schema 对象
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Dict[str, Any]: 符合 JSON Schema 规范的 inputSchema 对象
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> params = {
|
|
||||||
... "type": "object",
|
|
||||||
... "required": ["userId", "startTime"],
|
|
||||||
... "properties": {
|
|
||||||
... "userId": {
|
|
||||||
... "type": "integer",
|
|
||||||
... "description": "用户的唯一标识符",
|
|
||||||
... "examples": [10086]
|
|
||||||
... },
|
|
||||||
... "startTime": {
|
|
||||||
... "type": "string",
|
|
||||||
... "format": "date-time",
|
|
||||||
... "description": "查询的起始时间",
|
|
||||||
... "examples": ["2023-01-01 00:00:00"]
|
|
||||||
... }
|
|
||||||
... }
|
|
||||||
... }
|
|
||||||
>>> schema = generate_input_schema(params)
|
|
||||||
>>> # schema 将包含所有原始信息,包括 format 和 examples
|
|
||||||
>>> # 同时会自动添加 targetDatabaseName 字段
|
|
||||||
"""
|
|
||||||
# 如果 parameters 本身就是一个完整的 JSON Schema 对象,直接使用
|
|
||||||
# 但确保至少包含 type 和 properties
|
|
||||||
if not parameters:
|
|
||||||
# 如果 parameters 为空,返回一个空的 object schema
|
|
||||||
return {
|
return {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
|
|
||||||
# 深拷贝 parameters 以避免修改原始数据
|
input_schema = deepcopy(parameters)
|
||||||
input_schema = dict(parameters)
|
|
||||||
|
|
||||||
# 确保必需的字段存在
|
|
||||||
if "type" not in input_schema:
|
|
||||||
input_schema["type"] = "object"
|
input_schema["type"] = "object"
|
||||||
|
|
||||||
if "properties" not in input_schema:
|
if not isinstance(input_schema.get("properties"), dict):
|
||||||
input_schema["properties"] = {}
|
input_schema["properties"] = {}
|
||||||
|
|
||||||
if "required" not in input_schema:
|
if not isinstance(input_schema.get("required"), list):
|
||||||
input_schema["required"] = []
|
input_schema["required"] = []
|
||||||
|
|
||||||
# 添加 targetDatabaseName 字段(如果不存在)
|
return input_schema
|
||||||
|
|
||||||
|
|
||||||
|
def generate_input_schema(parameters: Dict[str, Any] | None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从查询配置的参数定义生成 MCP 工具的 inputSchema。
|
||||||
|
|
||||||
|
会统一补齐:
|
||||||
|
- `targetDatabaseName`:可选
|
||||||
|
- `userId`:必填
|
||||||
|
"""
|
||||||
|
input_schema = _normalize_schema(parameters)
|
||||||
|
|
||||||
if "targetDatabaseName" not in input_schema["properties"]:
|
if "targetDatabaseName" not in input_schema["properties"]:
|
||||||
input_schema["properties"]["targetDatabaseName"] = {
|
input_schema["properties"]["targetDatabaseName"] = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -79,39 +45,21 @@ def generate_input_schema(parameters: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
"default": ""
|
"default": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# 添加 userId 字段(如果不存在)
|
|
||||||
if "userId" not in input_schema["properties"]:
|
if "userId" not in input_schema["properties"]:
|
||||||
input_schema["properties"]["userId"] = {
|
input_schema["properties"]["userId"] = {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "当前ai平台用户id;"
|
"description": "当前 AI 平台用户 ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 保留所有其他字段,如 description, examples, format 等
|
if "userId" not in input_schema["required"]:
|
||||||
# JSON Schema 标准支持的字段都会被保留:
|
input_schema["required"].append("userId")
|
||||||
# - additionalProperties
|
|
||||||
# - patternProperties
|
|
||||||
# - minProperties / maxProperties
|
|
||||||
# - dependencies
|
|
||||||
# - 等等
|
|
||||||
|
|
||||||
return input_schema
|
return input_schema
|
||||||
|
|
||||||
|
|
||||||
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
|
def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
验证 inputSchema 是否符合基本的 JSON Schema 规范
|
验证 inputSchema 是否符合基本的 JSON Schema 规范。
|
||||||
|
|
||||||
Args:
|
|
||||||
schema: 要验证的 schema 对象
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
tuple[bool, str]: (是否有效, 错误消息或成功消息)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> schema = {"type": "object", "properties": {"id": {"type": "string"}}}
|
|
||||||
>>> is_valid, msg = validate_input_schema(schema)
|
|
||||||
>>> print(is_valid, msg)
|
|
||||||
True, "Schema 验证通过"
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(schema, dict):
|
if not isinstance(schema, dict):
|
||||||
return False, "Schema 必须是一个字典对象"
|
return False, "Schema 必须是一个字典对象"
|
||||||
@@ -125,19 +73,16 @@ def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
|
|||||||
if not isinstance(schema.get("properties"), dict):
|
if not isinstance(schema.get("properties"), dict):
|
||||||
return False, "Schema 的 properties 字段必须是一个字典对象"
|
return False, "Schema 的 properties 字段必须是一个字典对象"
|
||||||
|
|
||||||
# 验证 required 字段(如果存在)
|
|
||||||
if "required" in schema:
|
if "required" in schema:
|
||||||
required = schema["required"]
|
required = schema["required"]
|
||||||
if not isinstance(required, list):
|
if not isinstance(required, list):
|
||||||
return False, "Schema 的 required 字段必须是一个列表"
|
return False, "Schema 的 required 字段必须是一个列表"
|
||||||
|
|
||||||
# 验证所有 required 的字段都在 properties 中定义
|
|
||||||
properties = schema["properties"]
|
properties = schema["properties"]
|
||||||
for field in required:
|
for field in required:
|
||||||
if field not in properties:
|
if field not in properties:
|
||||||
return False, f"必填字段 '{field}' 未在 properties 中定义"
|
return False, f"必填字段 '{field}' 未在 properties 中定义"
|
||||||
|
|
||||||
# 验证 properties 中每个字段的定义
|
|
||||||
for prop_name, prop_def in schema["properties"].items():
|
for prop_name, prop_def in schema["properties"].items():
|
||||||
if not isinstance(prop_def, dict):
|
if not isinstance(prop_def, dict):
|
||||||
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
|
return False, f"属性 '{prop_name}' 的定义必须是一个字典对象"
|
||||||
@@ -146,4 +91,3 @@ def validate_input_schema(schema: Dict[str, Any]) -> tuple[bool, str]:
|
|||||||
return False, f"属性 '{prop_name}' 必须包含 type 字段"
|
return False, f"属性 '{prop_name}' 必须包含 type 字段"
|
||||||
|
|
||||||
return True, "Schema 验证通过"
|
return True, "Schema 验证通过"
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
"""
|
"""
|
||||||
Entry point for lzwcai-mcp-sqlexecutor
|
Repository-local launcher for lzwcai-mcp-sqlexecutor.
|
||||||
Runs the MCP server for SQL query execution
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ["databaseId"] = "162"
|
|
||||||
os.environ["skillId"] = "2008360664955854850"
|
|
||||||
os.environ["backendBaseUrl"] = "http://192.168.2.236:8088"
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Import and run the actual MCP server
|
|
||||||
from lzwcai_mcp_sqlexecutor.main import main
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Keep local developer defaults without overriding explicit environment settings.
|
||||||
|
os.environ.setdefault("databaseId", "240")
|
||||||
|
os.environ.setdefault("skillId", "2058819964077572098")
|
||||||
|
os.environ.setdefault("backendBaseUrl", "http://192.168.2.236:8088")
|
||||||
|
|
||||||
|
from lzwcai_mcp_sqlexecutor.main import main as package_main
|
||||||
|
|
||||||
|
package_main()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "lzwcai-mcp-sqlexecutor"
|
name = "lzwcai-mcp-sqlexecutor"
|
||||||
version = "0.1.12"
|
version = "0.1.13"
|
||||||
description = "MCP server for executing business SQL queries with dynamic tool generation"
|
description = "MCP server for executing business SQL queries with dynamic tool generation"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ WORKFLOW_EXECUTE_TIMEOUT = 600.0 # 工作流执行超时(10分钟,参考 wo
|
|||||||
|
|
||||||
# 异步轮询默认配置(参考 workflow-execution-api.md)
|
# 异步轮询默认配置(参考 workflow-execution-api.md)
|
||||||
DEFAULT_POLLING_INTERVAL = 1.0
|
DEFAULT_POLLING_INTERVAL = 1.0
|
||||||
DEFAULT_MAX_POLL_COUNT = 120
|
DEFAULT_MAX_POLL_COUNT = 800
|
||||||
DEFAULT_MAX_ERROR_COUNT = 3
|
DEFAULT_MAX_ERROR_COUNT = 3
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "lzwcai-workflow-to-mcp"
|
name = "lzwcai-workflow-to-mcp"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
description = "MCP server for executing business SQL queries with dynamic tool generation"
|
description = "MCP server for executing business SQL queries with dynamic tool generation"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user