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"]
|
||||
Reference in New Issue
Block a user