From 557361632cd1caa790422beb07388e22f70669b8 Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Thu, 11 Jun 2026 18:51:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(.kilo):=20=E6=96=B0=E5=A2=9E=20AgileDB=20?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E6=93=8D=E4=BD=9C=E6=8A=80=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 lzwcai-agile-db 技能文件,为 AI Agent 提供 AgileDB 数据库操作的场景化工作流指导。 该技能支持数据源浏览、表数据 CRUD、SQL 执行和 AI 生成表结构等功能,包含完整的工具列表和使用场景。 --- .kilo/plans/lzwcai-agile-db-skill.md | 451 +++++++++ .kilo/skills/lzwcai-agile-db.zip | Bin 0 -> 9699 bytes .kilo/skills/lzwcai-agile-db/SKILL.md | 893 ++++++++++++++++++ .kilo/skills/mcp-tool-testing.zip | Bin 0 -> 5992 bytes .kilo/skills/mcp-tool-testing/SKILL.md | 356 +++++++ lzwcai_mcp_agile_db/README.md | 8 +- .../lzwcai_mcp_agile_db/main.py | 6 - .../lzwcai_mcp_agile_db/pyproject.toml | 7 - .../lzwcai_mcp_agile_db/tools/_base.py | 2 +- .../lzwcai_mcp_agile_db/tools/api_keys.py | 12 +- .../lzwcai_mcp_agile_db/tools/data_import.py | 8 +- .../tools/database_tables.py | 12 +- .../lzwcai_mcp_agile_db/tools/datasources.py | 20 +- .../lzwcai_mcp_agile_db/tools/skills.py | 12 +- .../tools/sql_execution.py | 21 +- .../tools/subscriptions.py | 16 +- .../lzwcai_mcp_agile_db/tools/table_data.py | 10 +- .../lzwcai_mcp_agile_db/utils/api_client.py | 48 +- .../lzwcai_mcp_agile_db/utils/env_config.py | 16 +- lzwcai_mcp_agile_db/main.py | 4 +- lzwcai_mcp_agile_db/pyproject.toml | 2 +- 21 files changed, 1807 insertions(+), 97 deletions(-) create mode 100644 .kilo/plans/lzwcai-agile-db-skill.md create mode 100644 .kilo/skills/lzwcai-agile-db.zip create mode 100644 .kilo/skills/lzwcai-agile-db/SKILL.md create mode 100644 .kilo/skills/mcp-tool-testing.zip create mode 100644 .kilo/skills/mcp-tool-testing/SKILL.md delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml diff --git a/.kilo/plans/lzwcai-agile-db-skill.md b/.kilo/plans/lzwcai-agile-db-skill.md new file mode 100644 index 0000000..9134d36 --- /dev/null +++ b/.kilo/plans/lzwcai-agile-db-skill.md @@ -0,0 +1,451 @@ +# lzwcai-agile-db Skill 实现计划 + +## 目标 +创建一个 Kilo skill 文件,为 AI Agent 提供 AgileDB 数据库操作的场景化工作流指导。 + +## Skill 文件路径 +`.kilo/skills/lzwcai-agile-db/SKILL.md` + +## Skill 内容 + +以下为完整的 SKILL.md 内容: + +```markdown +# lzwcai-agile-db + +AgileDB 数据库操作技能。为 AI Agent 提供数据源浏览、表数据 CRUD、SQL 执行和 AI 生成表结构的场景化工作流。 + +## 可用 MCP 工具 + +本 skill 基于 `lzwcai_mcp_agile_db` MCP Server,提供以下工具: + +### 数据源管理 +| 工具 | 功能 | +|------|------| +| `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` | AI 根据自然语言生成表结构 | + +### 表数据操作 +| 工具 | 功能 | +|------|------| +| `query_table_data` | 查询表数据(分页) | +| `insert_table_row` | 插入一行数据 | +| `update_table_row` | 更新一行数据 | +| `delete_table_rows` | 删除数据行(按主键) | +| `export_table_excel` | 导出表数据为 Excel | + +### SQL 执行 +| 工具 | 功能 | +|------|------| +| `execute_sql` | 执行原生 SQL 查询 | + +--- + +## 场景 1:浏览数据源 + +当用户想要了解有哪些数据源、数据库或表时,使用此流程。 + +### 工作流程 + +``` +用户请求: "有哪些数据源?" / "看看 XX 数据源有哪些表?" + ↓ +1. 调用 list_datasources() + ↓ +2. 展示数据源列表(名称、类型、状态、数据库数、表数) + ↓ +3. 用户选择数据源后,调用 get_datasource_detail(datasourceId="xx") + ↓ +4. 展示数据库列表和实时表结构 +``` + +### 示例 + +**用户**: "帮我看看有哪些数据源" + +``` +调用: list_datasources() +返回: [{id: "1", name: "订单系统", type: "builtin", status: 0, databases: 3, tables: 12}, ...] +回复: 共找到 3 个数据源: + - 订单系统(内置,运行中,3 个库,12 张表) + - 用户中心(外部 MySQL,运行中,1 个库,5 张表) + - 报表系统(外部 PostgreSQL,已停止) +``` + +**用户**: "看看订单系统有哪些表" + +``` +调用: get_datasource_detail(datasourceId="1") +返回: {datasource: {...}, databases: [{name: "order_db", tables: [...]}]} +回复: 订单系统包含以下数据库: + order_db: + - orders (订单表, 15 个字段) + - order_items (订单明细, 10 个字段) + - users (用户表, 8 个字段) +``` + +### 注意事项 +- 数据源状态:`0`=运行中,`1`=已停止 +- 数据源类型:`builtin`=内置,`external`=外部 +- 如果用户只说"查一下数据库",先调用 `list_datasources()` 再引导选择 + +--- + +## 场景 2:查询表数据 + +当用户想要查看某张表的具体数据记录时,使用此流程。 + +### 工作流程 + +``` +用户请求: "查一下 users 表的数据" / "看看前 20 条订单" + ↓ +1. 如果不知道 tableId,先浏览数据源找到目标表 + ↓ +2. 调用 query_table_data(tableId="xx", pageNum=1, pageSize=10, target="prod") + ↓ +3. 展示数据(表格形式,包含字段名和值) + ↓ +4. 如果数据量大,提示用户可翻页或调整 pageSize +``` + +### 示例 + +**用户**: "查一下 users 表前 10 条数据" + +``` +调用: query_table_data(tableId="5", pageNum=1, pageSize=10) +返回: { + columns: [{name: "id", type: "INTEGER"}, {name: "username", type: "VARCHAR"}, ...], + data: [{id: 1, username: "admin", email: "admin@test.com"}, ...], + total: 156 +} +回复: users 表共 156 条记录,当前显示第 1-10 条: + | id | username | email | created_at | + |----|----------|-----------------|---------------------| + | 1 | admin | admin@test.com | 2024-01-15 10:30:00 | + | 2 | user1 | user1@test.com | 2024-01-16 14:20:00 | + ... +``` + +### 注意事项 +- `target` 参数:`prod`=生产环境,`test`=测试环境,默认 `prod` +- 如果用户未指定数量,默认 `pageSize=10` +- 数据返回格式为对象数组(已自动转换表头) +- 如果用户说"翻页",增加 `pageNum` 参数 + +--- + +## 场景 3:执行 SQL + +当用户需要执行自定义 SQL 查询时,使用此流程。 + +### 工作流程 + +``` +用户请求: "帮我执行 SQL..." / "查一下订单数大于 100 的用户" + ↓ +1. 如果用户直接提供 SQL,直接使用 + 如果用户提供自然语言需求,先转换为 SQL + ↓ +2. 调用 execute_sql(datasourceId="xx", executableSql="SELECT ...") + ↓ +3. 展示查询结果 + ↓ +4. 如果 SQL 执行失败,展示错误信息并建议修正 +``` + +### 示例 + +**用户**: "统计每个地区的订单数量" + +``` +转换为 SQL: SELECT region, COUNT(*) as order_count FROM orders GROUP BY region ORDER BY order_count DESC + +调用: execute_sql( + datasourceId="1", + executableSql="SELECT region, COUNT(*) as order_count FROM orders GROUP BY region ORDER BY order_count DESC" +) +返回: { + columns: [{name: "region"}, {name: "order_count"}], + data: [{region: "华东", order_count: 1250}, {region: "华南", order_count: 980}, ...] +} +回复: 各地区订单统计: + | 地区 | 订单数 | + |------|--------| + | 华东 | 1,250 | + | 华南 | 980 | + | 华北 | 756 | +``` + +### 注意事项 +- `datasourceId` 必须提供,如果用户未指定,先询问或使用最近使用的数据源 +- `sqlTemplate` 可选,用于模板化查询 +- `parameters` 可选,用于参数化查询(防止 SQL 注入) +- 执行危险操作(DELETE/DROP/UPDATE)前,**必须**向用户确认 +- 如果查询结果超过 100 行,建议用户使用 `query_table_data` 代替 + +--- + +## 场景 4:增删改数据 + +当用户需要修改表中的数据时,使用此流程。 + +### 4.1 插入数据 + +``` +用户请求: "新增一个用户,用户名是 test_user" + ↓ +1. 先调用 get_table_detail(tableId="xx") 了解表结构 + ↓ +2. 确认必填字段(非空字段、无默认值字段) + ↓ +3. 调用 insert_table_row(tableId="xx", data={...}) + ↓ +4. 确认插入成功 +``` + +**示例**: + +**用户**: "新增一个用户,用户名 test_user,邮箱 test@test.com" + +``` +调用: get_table_detail(tableId="5") +返回: {columns: [{name: "id", isPrimaryKey: true, isAutoIncrement: true}, {name: "username", isNullable: false}, ...]} + +调用: insert_table_row( + tableId="5", + data={"username": "test_user", "email": "test@test.com"} +) +回复: 已成功插入用户 test_user (ID 自动生成) +``` + +### 4.2 更新数据 + +``` +用户请求: "把用户 test_user 的邮箱改成 new@test.com" + ↓ +1. 确认主键字段和要更新的值 + ↓ +2. 调用 update_table_row(tableId="xx", primaryKey={主键}, data={要更新的字段}) + ↓ +3. 确认更新成功 +``` + +**示例**: + +**用户**: "把 ID 为 5 的用户邮箱改成 new@test.com" + +``` +调用: update_table_row( + tableId="5", + primaryKey={"id": 5}, + data={"email": "new@test.com"} +) +回复: 已更新用户 ID=5 的邮箱为 new@test.com +``` + +### 4.3 删除数据 + +``` +用户请求: "删除用户 test_user" + ↓ +1. ⚠️ 危险操作!先确认删除范围和影响 + ↓ +2. 向用户展示将要删除的数据,请求确认 + ↓ +3. 用户确认后,调用 delete_table_rows(tableId="xx", primaryKeys=[{主键}]) + ↓ +4. 确认删除成功 +``` + +**示例**: + +**用户**: "删除 ID 为 10 的订单" + +``` +回复: ⚠️ 确认要删除以下记录吗? + 订单 ID=10, 订单号=ORD-2024-0010, 金额=¥1,250.00 + 此操作不可恢复。请回复"确认删除"继续。 + +用户: "确认删除" + +调用: delete_table_rows( + tableId="3", + primaryKeys=[{"id": 10}] +) +回复: 已成功删除订单 ID=10 +``` + +### 注意事项 +- **删除操作必须二次确认** +- `primaryKey` 必须是对象格式,如 `{"id": 1}` +- `data` 只包含要更新的字段,不需要提供全部字段 +- 插入数据时,自增主键不需要提供 +- 如果操作涉及多行,使用批量操作或循环调用 + +--- + +## 场景 5:AI 生成表结构 + +当用户需要创建新表但不知道如何设计表结构时,使用 AI 辅助生成。 + +### 工作流程 + +``` +用户请求: "我需要一个用户表" / "帮我设计一个订单系统的表结构" + ↓ +1. 调用 generate_table_by_description(requirement="用户描述") + ↓ +2. 展示 AI 生成的表结构(表名、字段、类型、注释) + ↓ +3. 用户确认后,调用 create_table(connectionId="xx", databaseName="xx", tableName="xx", columns=[...]) + ↓ +4. 确认创建成功 +``` + +### 示例 + +**用户**: "帮我设计一个商品表,包含名称、价格、库存、分类" + +``` +调用: generate_table_by_description( + requirement="我需要一个商品表,包含商品名称、价格、库存、分类,用于电商系统" +) +返回: { + tableName: "products", + tableComment: "商品表", + columns: [ + {columnName: "id", columnType: "SERIAL", isPrimaryKey: true, isAutoIncrement: true, columnComment: "主键ID"}, + {columnName: "name", columnType: "VARCHAR", columnLength: 200, isNullable: false, columnComment: "商品名称"}, + {columnName: "price", columnType: "DECIMAL", columnLength: 10, columnComment: "价格"}, + {columnName: "stock", columnType: "INTEGER", columnComment: "库存数量"}, + {columnName: "category_id", columnType: "INTEGER", columnComment: "分类ID"}, + {columnName: "created_at", columnType: "TIMESTAMP", defaultValue: "NOW()", columnComment: "创建时间"} + ] +} + +回复: AI 已生成表结构设计: + 表名: products (商品表) + 字段: + - id (SERIAL, 主键, 自增) - 主键ID + - name (VARCHAR(200), 非空) - 商品名称 + - price (DECIMAL(10)) - 价格 + - stock (INTEGER) - 库存数量 + - category_id (INTEGER) - 分类ID + - created_at (TIMESTAMP, 默认NOW()) - 创建时间 + + 是否需要在此基础上修改或直接创建? + +用户: "直接创建到 order_db 数据库" + +调用: create_table( + connectionId="1", + databaseName="order_db", + tableName="products", + tableComment="商品表", + columns=[...] // 使用 AI 生成的 columns +) +回复: 已成功创建表 products (商品表) +``` + +### 注意事项 +- `requirement` 参数应尽可能详细,包含业务场景和字段需求 +- AI 生成的表结构可能需要用户调整(如字段长度、类型) +- 创建表前需要确认 `connectionId` 和 `databaseName` +- 常用字段类型:`VARCHAR`, `INTEGER`, `SERIAL`(自增主键), `DECIMAL`, `TIMESTAMP`, `TEXT`, `BOOLEAN` +- 如果用户描述模糊,先引导用户补充细节 + +--- + +## 最佳实践 + +### 1. 始终确认环境 +- 默认使用 `prod`(生产环境) +- 如果用户提到"测试"或"测试环境",使用 `target="test"` +- 执行危险操作前,明确告知用户当前环境 + +### 2. 提供上下文 +- 展示数据时,包含字段名和类型 +- 执行操作后,告知影响的行数或具体变化 +- 错误时,提供清晰的错误信息和建议 + +### 3. 分步引导 +- 如果用户请求不完整(如未指定数据源),先引导补充信息 +- 复杂操作分步执行,每步确认后继续 +- 对于多表操作,先展示整体计划 + +### 4. 数据展示 +- 表格数据使用 Markdown 表格格式 +- 长文本截断显示(最多 100 字符) +- 数字格式化(千位分隔符、货币符号) +- 时间格式化为可读格式 + +### 5. 错误处理 +- API 错误:展示错误信息,建议重试或检查参数 +- SQL 错误:展示 SQL 错误位置,建议修正 +- 连接错误:检查数据源状态,建议启用或重新配置 + +--- + +## 常用参数参考 + +### 数据源类型 +- `builtin` - 内置 PostgreSQL +- `external` - 外部数据库(MySQL/PostgreSQL/Oracle/SQL Server 等) + +### 数据源状态 +- `0` - 运行中 +- `1` - 已停止 + +### 环境 +- `prod` - 生产环境(默认) +- `test` - 测试环境 + +### 常用字段类型 +| 类型 | 用途 | 示例 | +|------|------|------| +| `SERIAL` | 自增主键 | `id SERIAL PRIMARY KEY` | +| `VARCHAR(n)` | 短文本 | `username VARCHAR(50)` | +| `TEXT` | 长文本 | `description TEXT` | +| `INTEGER` | 整数 | `stock INTEGER` | +| `DECIMAL(m,n)` | 精确小数 | `price DECIMAL(10,2)` | +| `TIMESTAMP` | 时间戳 | `created_at TIMESTAMP` | +| `BOOLEAN` | 布尔值 | `is_active BOOLEAN` | + +--- + +## 快速开始 + +如果用户说"帮我查一下数据库",按以下步骤操作: + +1. 调用 `list_datasources()` 获取数据源列表 +2. 展示列表,让用户选择或默认第一个运行中的数据源 +3. 调用 `get_datasource_detail(datasourceId="xx")` 获取数据库和表信息 +4. 引导用户选择要操作的表 +5. 根据用户意图调用相应的工具(查询/执行 SQL/增删改) +``` + +## 实施步骤 + +1. 创建目录 `.kilo/skills/lzwcai-agile-db/` +2. 创建文件 `SKILL.md` 并写入上述内容 +3. 验证 skill 文件格式正确 + +## 验证 + +创建完成后,skill 应该可以被 Kilo 自动识别和加载。 diff --git a/.kilo/skills/lzwcai-agile-db.zip b/.kilo/skills/lzwcai-agile-db.zip new file mode 100644 index 0000000000000000000000000000000000000000..72157c8aed1d1dd638a194d2d851c1449b954a95 GIT binary patch literal 9699 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 literal 0 HcmV?d00001 diff --git a/.kilo/skills/lzwcai-agile-db/SKILL.md b/.kilo/skills/lzwcai-agile-db/SKILL.md new file mode 100644 index 0000000..16f04f9 --- /dev/null +++ b/.kilo/skills/lzwcai-agile-db/SKILL.md @@ -0,0 +1,893 @@ +--- +name: lzwcai-agile-db +description: AgileDB 数据库管理平台的 MCP 技能。为 AI Agent 提供完整的数据库操作工作流指导,适合零基础用户。 +version: 0.2.0 +--- + +# lzwcai-agile-db + +AgileDB 数据库管理平台的 MCP 技能。为 AI Agent 提供完整的数据库操作工作流指导,适合零基础用户。 + + +## 完整工具清单(33 个工具) + +本 skill 基于 `lzwcai_mcp_agile_db` MCP Server,共提供 33 个工具,分为 9 大类: + +### 一、数据源管理(6 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `list_datasources` | 获取数据源列表 | 安全 | +| `get_datasource_detail` | 获取数据源详情(含数据库、表结构) | 安全 | +| `create_datasource` | 创建外部数据源连接 | 安全 | +| `update_datasource` | 更新数据源连接信息 | 中等 | +| `toggle_datasource_status` | 启用/停用数据源 | 中等 | +| `delete_datasource` | 删除数据源 | **危险** | + +### 二、数据库与表管理(6 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `list_databases` | 获取数据源下的数据库列表 | 安全 | +| `list_tables` | 获取数据源下的表列表 | 安全 | +| `get_table_detail` | 获取表结构详情(字段、类型、主键) | 安全 | +| `create_table` | 创建新表 | **危险** | +| `alter_table` | 修改表结构 | **危险** | +| `generate_table_by_description` | AI 根据自然语言生成表结构 | 安全(仅生成,不创建) | + +### 三、表数据 CRUD(5 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `query_table_data` | 查询表数据(分页) | 安全 | +| `insert_table_row` | 插入一行数据 | 中等 | +| `update_table_row` | 更新一行数据 | 中等 | +| `delete_table_rows` | 删除数据行(按主键) | **危险** | +| `export_table_excel` | 导出表数据为 Excel(base64) | 安全 | + +### 四、SQL 执行(1 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `execute_sql` | 执行原生 SQL 查询 | 中等/危险 | + +### 五、数据导入(2 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `preview_import_data` | 上传 Excel 文件,AI 智能识别并预览 | 安全 | +| `confirm_import_data` | 确认导入 AI 识别后的数据 | **危险** | + +### 六、表订阅(1 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `toggle_table_subscription` | 切换表的订阅状态 | 中等 | + +### 七、API 密钥管理(6 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `list_api_keys` | 获取 API 密钥列表 | 安全 | +| `create_api_key` | 创建新的 API 密钥 | 中等 | +| `toggle_api_key_status` | 启用/禁用 API 密钥 | 中等 | +| `delete_api_key` | 删除 API 密钥 | **危险** | +| `get_api_key_permissions` | 查看指定密钥的权限配置 | 安全 | +| `grant_api_key_permissions` | 批量为 API 密钥授予权限 | **危险** | + +### 八、技能与工具管理(6 个工具) + +| 工具 | 功能 | 危险等级 | +|------|------|----------| +| `get_skill_by_datasource` | 根据数据源获取技能信息 | 安全 | +| `get_skill_tools` | 获取技能下的工具列表 | 安全 | +| `create_skill` | 为数据源创建技能 | 中等 | +| `create_sql_tool` | 将 SQL 查询创建为可复用工具 | 中等 | +| `delete_skill_tool` | 删除技能下的工具 | **危险** | +| `update_skill_config` | 更新技能配置 | 中等 | + +--- + +## 核心概念说明 + +### 数据源是什么? + +数据源 = 一个数据库连接。它可以是: +- `builtin`:内置 PostgreSQL 数据库(系统自带) +- `external`:外部数据库(MySQL、PostgreSQL、Oracle、SQL Server、达梦等) + +### 数据源状态 + +- `0` = 运行中(正常) +- `1` = 已停止(不可用) + +### 环境参数 `target` + +- `prod` = 生产环境(正式数据,默认值) +- `test` = 测试环境(测试数据) + +### 主键 `primaryKey` + +主键是唯一标识一行数据的字段。例如 `{"id": 1}` 表示删除/更新 id=1 的那行数据。 + +### `connectionId` vs `datasourceId` + +- `datasourceId`:用于数据源列表、数据库列表、表列表等浏览类操作 +- `connectionId`:用于创建表、修改表、数据导入等需要直接操作连接的操作 +- 两者通常指向同一个东西,但 API 设计不同,请按照工具说明使用 + +--- + +## ⚠️ 安全确认原则(必须遵守) + +### 一、执行工具前的风险评估 + +当执行以下类型的操作时,**必须先询问用户确认**,不得擅作主张: + +1. **删除操作**:删除数据源、删除表数据、删除 API 密钥、删除技能工具等 + - 说明:此操作不可恢复,数据将永久丢失 + +2. **泄密风险操作**:导出包含敏感数据的表、创建 API 密钥、查看密钥详情等 + - 说明:可能导致敏感信息泄露,需确认用户授权 + +3. **政治敏感操作**:涉及政治相关数据的查询、修改、删除等 + - 说明:可能涉及合规风险,需确认用户意图 + +4. **疑似违规内容**:涉及色情、暴力、违法等内容的操作 + - 说明:可能违反法律法规,必须拒绝执行并告知用户 + +#### 确认格式 + +执行上述操作前,必须使用以下格式向用户确认: + +``` +⚠️ 安全提醒:此操作存在 [具体风险类型] 风险。 +具体说明:[说明可能的后果] +请确认是否继续?(回复"确认"继续,或取消操作) +``` + +**只有在用户明确确认后才能继续执行。** + +--- + +### 二、多步骤场景必须逐步确认 + +当完成任务需要调用多个工具时(如:先查询表结构 → 插入数据 → 确认结果),**不得一次性自动连续执行**: + +1. **每完成一步后暂停**,向用户展示当前步骤的结果 +2. **询问用户是否继续下一步**,等待确认后再执行 +3. **不要假设用户的意图**,即使用户的请求看似明确,也需要分步确认 + +**示例**: +``` +用户:"帮我新增一个用户" + +❌ 错误做法:自动调用 get_table_detail → insert_table_row → 返回结果(一次性执行完) + +✅ 正确做法: + 1. 调用 get_table_detail 了解表结构 + 2. 展示必填字段清单,询问用户:"请提供以下必填字段的值:..." + 3. 用户回复后,展示将要插入的数据预览 + 4. 询问:"确认插入以上数据?" + 5. 用户确认后才调用 insert_table_row +``` + +--- + +### 三、遇到多项选择时必须询问用户 + +当执行任务过程中遇到需要选择的场景时,**AI 不得擅自做主选择**: + +1. **列出所有可选方案**,说明每个方案的优缺点或适用场景 +2. **等待用户明确选择**,不要猜测用户意图 +3. **不要默认选择第一个**或看似最合理的选项 + +#### 常见需要询问的选择场景: +- 有多个数据源可选时 +- 有多个数据库可选时 +- 有多个表可选时 +- 有多种操作方式可选时(如:用 SQL 查询 vs 用表数据查询工具) +- 有多种字段类型可选时 +- 用户描述模糊,存在多种理解方式时 + +**示例**: +``` +用户:"查一下订单数据" + +❌ 错误做法:直接选择第一个数据源和第一个订单表进行查询 + +✅ 正确做法: + "找到以下数据源包含订单相关表: + 1. HMD产品 → order_db → orders 表(156 条记录) + 2. 测试数据源 → test_db → test_orders 表(10 条记录) + 请问您想查询哪个?" +``` + +--- + +## 场景 1:浏览数据源(新手入门第一步) + +当用户想了解系统里有哪些数据库或表时,使用此流程。 + +### 工作流程 + +``` +用户请求: "有哪些数据源?" / "看看 XX 数据源有哪些表?" / "帮我查一下数据库" + ↓ +1. 调用 list_datasources() + ↓ +2. 展示数据源列表(名称、类型、状态、数据库数、表数) + ↓ +3. 用户选择数据源后,调用 get_datasource_detail(datasourceId="xx") + ↓ +4. 展示数据库列表和实时表结构 +``` + +### 示例 + +**用户**: "帮我看看有哪些数据源" + +``` +调用: list_datasources() +返回: { + "total": 14, + "rows": [ + {"id": "58", "datasourceName": "HMD产品", "host": "host.docker.internal", "port": 5432, "status": 0, ...}, + ... + ] +} +回复: 共找到 14 个数据源: + 1. HMD产品(PostgreSQL, host.docker.internal:5432, 运行中) + 2. ... + 请告诉我你想看哪个数据源? +``` + +**用户**: "看看 HMD 产品有哪些表" + +``` +调用: get_datasource_detail(datasourceId="58") +返回: { + "detail": {...}, + "config": {...}, + "structure": { + "databases": [ + {"name": "order_db", "tables": [ + {"tableName": "orders", "columns": [...]}, + ... + ]} + ] + } +} +回复: HMD产品 数据源包含以下数据库和表: + order_db: + - orders (订单表, 15 个字段) + - users (用户表, 8 个字段) +``` + +### 注意事项 +- 数据源类型为 `builtin` 表示系统内置数据库,类型为具体的数据库引擎名称(mysql、postgresql 等)表示外部数据源 +- 如果用户只说"查一下数据库",先调用 `list_datasources()` 再引导选择 +- 已停止的数据源(status=1)无法访问其结构 + +--- + +## 场景 2:查询表数据 + +当用户想要查看某张表的具体数据记录时,使用此流程。 + +### 工作流程 + +``` +用户请求: "查一下 users 表的数据" / "看看前 20 条订单" + ↓ +1. 如果不知道 tableId,先浏览数据源找到目标表 + ↓ +2. 调用 query_table_data(tableId="xx", pageNum=1, pageSize=10, target="prod") + ↓ +3. 展示数据(表格形式,包含字段名和值) + ↓ +4. 如果数据量大,提示用户可翻页或调整 pageSize +``` + +### 示例 + +**用户**: "查一下 users 表前 10 条数据" + +``` +调用: query_table_data(tableId="5", pageNum=1, pageSize=10) +返回: { + "columns": [{"name": "id", "type": "INTEGER"}, {"name": "username", "type": "VARCHAR"}, ...], + "data": [{"id": 1, "username": "admin", "email": "admin@test.com"}, ...], + "total": 156 +} +回复: users 表共 156 条记录,当前显示第 1-10 条: + | id | username | email | created_at | + |----|----------|-----------------|---------------------| + | 1 | admin | admin@test.com | 2024-01-15 10:30:00 | + | 2 | user1 | user1@test.com | 2024-01-16 14:20:00 | + ... +``` + +### 注意事项 +- `target` 参数:`prod`=生产环境(默认),`test`=测试环境 +- 如果用户未指定数量,默认 `pageSize=10` +- 如果用户说"翻页",增加 `pageNum` 参数 +- 如果用户想看更多数据,可以增大 `pageSize`(最大根据 API 限制) +- 数据返回格式为对象数组 + +--- + +## 场景 3:执行 SQL + +当用户需要执行自定义 SQL 查询时,使用此流程。 + +### 工作流程 + +``` +用户请求: "帮我执行 SQL..." / "查一下订单数大于 100 的用户" + ↓ +1. 如果用户直接提供 SQL,直接使用 + 如果用户提供自然语言需求,先转换为 SQL + ↓ +2. 调用 execute_sql(datasourceId="xx", executableSql="SELECT ...") + ↓ +3. 展示查询结果 + ↓ +4. 如果 SQL 执行失败,展示错误信息并建议修正 +``` + +### 示例 + +**用户**: "统计每个地区的订单数量" + +``` +转换为 SQL: SELECT region, COUNT(*) as order_count FROM orders GROUP BY region ORDER BY order_count DESC + +调用: execute_sql( + datasourceId="58", + executableSql="SELECT region, COUNT(*) as order_count FROM orders GROUP BY region ORDER BY order_count DESC" +) +返回: { + "columns": [{"name": "region"}, {"name": "order_count"}], + "data": [{"region": "华东", "order_count": 1250}, {"region": "华南", "order_count": 980}, ...] +} +回复: 各地区订单统计: + | 地区 | 订单数 | + |------|--------| + | 华东 | 1,250 | + | 华南 | 980 | + | 华北 | 756 | +``` + +### 注意事项 +- `datasourceId` 必须提供,如果用户未指定,先询问 +- `sqlTemplate` 可选,用于模板化查询 +- `parameters` 可选,用于参数化查询(防止 SQL 注入) +- **执行危险操作(DELETE/DROP/TRUNCATE)前,必须向用户确认** +- 如果查询结果超过 100 行,建议用户使用 `query_table_data` 代替 +- 对于只读查询(SELECT),可以直接执行;对于写操作(INSERT/UPDATE/DELETE),必须二次确认 + +--- + +## 场景 4:增删改数据 + +当用户需要修改表中的数据时,使用此流程。 + +### 4.1 插入数据 + +``` +用户请求: "新增一个用户,用户名是 test_user" + ↓ +1. 先调用 get_table_detail(tableId="xx") 了解表结构 + ↓ +2. 确认必填字段(非空字段、无默认值字段) + ↓ +3. 调用 insert_table_row(tableId="xx", data={...}) + ↓ +4. 确认插入成功 +``` + +**示例**: + +**用户**: "新增一个用户,用户名 test_user,邮箱 test@test.com" + +``` +调用: get_table_detail(tableId="5") +返回: {columns: [{name: "id", isPrimaryKey: true, isAutoIncrement: true}, {name: "username", isNullable: false}, ...]} + +调用: insert_table_row( + tableId="5", + data={"username": "test_user", "email": "test@test.com"} +) +回复: 已成功插入用户 test_user (ID 自动生成) +``` + +### 4.2 更新数据 + +``` +用户请求: "把用户 test_user 的邮箱改成 new@test.com" + ↓ +1. 确认主键字段和要更新的值 + ↓ +2. 调用 update_table_row(tableId="xx", primaryKey={主键}, data={要更新的字段}) + ↓ +3. 确认更新成功 +``` + +**示例**: + +**用户**: "把 ID 为 5 的用户邮箱改成 new@test.com" + +``` +调用: update_table_row( + tableId="5", + primaryKey={"id": 5}, + data={"email": "new@test.com"} +) +回复: 已更新用户 ID=5 的邮箱为 new@test.com +``` + +### 4.3 删除数据 + +``` +用户请求: "删除用户 test_user" + ↓ +1. ⚠️ 安全提醒:此操作存在删除风险。 + 说明:数据将永久删除,不可恢复。 + 请确认是否继续? + ↓ +2. ⚠️ 危险操作!先确认删除范围和影响 + ↓ +3. 向用户展示将要删除的数据,请求确认 + ↓ +4. 用户确认后,调用 delete_table_rows(tableId="xx", primaryKeys=[{主键}]) + ↓ +5. 确认删除成功 +``` + +**示例**: + +**用户**: "删除 ID 为 10 的订单" + +``` +回复: ⚠️ 确认要删除以下记录吗? + 订单 ID=10, 订单号=ORD-2024-0010, 金额=¥1,250.00 + 此操作不可恢复。请回复"确认删除"继续。 + +用户: "确认删除" + +调用: delete_table_rows( + tableId="3", + primaryKeys=[{"id": 10}] +) +回复: 已成功删除订单 ID=10 +``` + +**注意:如果删除内容涉及敏感数据(如用户隐私、政治相关内容、疑似违规内容),必须额外说明后果并再次确认。** + +### 4.4 导出表数据为 Excel + +``` +用户请求: "把 users 表数据导出成 Excel" + ↓ +1. 调用 export_table_excel(tableId="xx", target="prod") + ↓ +2. 返回 base64 编码的 Excel 文件内容 + ↓ +3. 提示用户解码 base64 获取文件 +``` + +**示例**: + +``` +调用: export_table_excel(tableId="5") +返回: { + "success": true, + "file_base64": "UEsDBBQAAAAI...", + "message": "Excel 文件已导出,请解码 base64 内容获取文件" +} +回复: 已成功导出 users 表数据为 Excel 文件 + 文件内容已 base64 编码,请解码后保存为 .xlsx 文件 +``` + +### 注意事项 +- **删除操作必须二次确认** +- `primaryKey` 必须是对象格式,如 `{"id": 1}` +- `primaryKeys` 是数组格式,如 `[{"id": 1}, {"id": 2}]` +- `data` 只包含要更新的字段,不需要提供全部字段 +- 插入数据时,自增主键不需要提供 +- 如果操作涉及多行,使用批量操作或循环调用 +- 增删改操作默认作用于 `prod` 环境 + +--- + +## 场景 5:AI 生成表结构 + +当用户需要创建新表但不知道如何设计表结构时,使用 AI 辅助生成。 + +### 工作流程 + +``` +用户请求: "我需要一个用户表" / "帮我设计一个订单系统的表结构" + ↓ +1. 调用 generate_table_by_description(requirement="用户描述") + ↓ +2. 展示 AI 生成的表结构(表名、字段、类型、注释) + ↓ +3. 用户确认后,调用 create_table(connectionId="xx", databaseName="xx", tableName="xx", columns=[...]) + ↓ +4. 确认创建成功 +``` + +### 示例 + +**用户**: "帮我设计一个商品表,包含名称、价格、库存、分类" + +``` +调用: generate_table_by_description( + requirement="我需要一个商品表,包含商品名称、价格、库存、分类,用于电商系统" +) +返回: { + "tableName": "products", + "tableComment": "商品表", + "columns": [ + {"columnName": "id", "columnType": "SERIAL", "isPrimaryKey": true, "isAutoIncrement": true, "columnComment": "主键ID"}, + {"columnName": "name", "columnType": "VARCHAR", "columnLength": 200, "isNullable": false, "columnComment": "商品名称"}, + {"columnName": "price", "columnType": "DECIMAL", "columnLength": 10, "columnComment": "价格"}, + {"columnName": "stock", "columnType": "INTEGER", "columnComment": "库存数量"}, + {"columnName": "category_id", "columnType": "INTEGER", "columnComment": "分类ID"}, + {"columnName": "created_at", "columnType": "TIMESTAMP", "defaultValue": "NOW()", "columnComment": "创建时间"} + ] +} + +回复: AI 已生成表结构设计: + 表名: products (商品表) + 字段: + - id (SERIAL, 主键, 自增) - 主键ID + - name (VARCHAR(200), 非空) - 商品名称 + - price (DECIMAL(10)) - 价格 + - stock (INTEGER) - 库存数量 + - category_id (INTEGER) - 分类ID + - created_at (TIMESTAMP, 默认NOW()) - 创建时间 + + 是否需要在此基础上修改或直接创建? + +用户: "直接创建到 order_db 数据库" + +调用: create_table( + connectionId="58", + databaseName="order_db", + tableName="products", + tableComment="商品表", + columns=[...] // 使用 AI 生成的 columns +) +回复: 已成功创建表 products (商品表) +``` + +### 修改表结构(alter_table) + +如果用户需要修改已有表的结构: + +``` +用户请求: "给 users 表加一个 phone 字段" + ↓ +1. 确认表结构和要修改的内容 + ↓ +2. 构建 operations 数组(支持多种变更类型) + ↓ +3. 调用 alter_table(connectionId="xx", databaseName="xx", tableName="xx", operations=[...]) + ↓ +4. 确认修改成功 +``` + +**可用的 operations 类型**: +- `ADD_COLUMN`:添加字段 +- `DROP_COLUMN`:删除字段 +- `RENAME_COLUMN`:重命名字段 +- `ALTER_COLUMN_TYPE`:修改字段类型 +- `SET_NOT_NULL`:设置为非空 +- `DROP_NOT_NULL`:取消非空约束 +- `SET_DEFAULT`:设置默认值 +- `DROP_DEFAULT`:删除默认值 + +### 注意事项 +- `requirement` 参数应尽可能详细,包含业务场景和字段需求 +- AI 生成的表结构可能需要用户调整(如字段长度、类型) +- 创建表前需要确认 `connectionId` 和 `databaseName` +- 常用字段类型:`VARCHAR`, `INTEGER`, `SERIAL`(自增主键), `DECIMAL`, `TIMESTAMP`, `TEXT`, `BOOLEAN` +- 如果用户描述模糊,先引导用户补充细节 + +--- + +## 场景 6:数据导入(Excel 到数据库) + +当用户需要从 Excel 文件导入数据到数据库时,使用此流程。 + +### 工作流程 + +``` +用户请求: "帮我导入这个 Excel 文件" / "把表格数据导入数据库" + ↓ +1. 用户将 Excel 文件转为 base64 编码 + ↓ +2. 调用 preview_import_data(connectionId="xx", file_base64="...", file_name="data.xlsx", target="test") + ↓ +3. 展示 AI 识别的表结构和数据预览 + ↓ +4. ⚠️ 安全提醒:此操作可能涉及数据安全风险。 + 说明:导入的数据将写入数据库,请确认数据来源合法合规,不包含敏感信息、政治内容或违规内容。 + 请确认是否继续? + ↓ +5. 用户确认无误后,调用 confirm_import_data(connectionId="xx", data={...}, target="test") + ↓ +6. 确认导入成功 +``` + +### 注意事项 +- 文件大小限制:< 500KB +- 支持格式:.xlsx / .xls +- 导入前默认使用 `test` 环境(安全做法) +- 如果用户要导入到正式环境,必须二次确认 +- base64 编码的文件内容需要提供文件名 + +--- + +## 场景 7:API 密钥管理 + +当用户需要管理 API 密钥时,使用此流程。 + +### 7.1 查看密钥列表 + +``` +调用: list_api_keys() +返回: { + "total": 6, + "rows": [ + {"id": "7", "apiKeyName": "AWINBEXT", "apiKey": "Lb8Lg...", "status": 0, "expireTime": "2027-06-06..."}, + ... + ] +} +``` + +### 7.2 创建新密钥 + +``` +调用: create_api_key(apiKeyName="新密钥名称") +返回: 包含新创建的密钥信息 +``` + +### 7.3 启用/禁用密钥 + +``` +调用: toggle_api_key_status(id="7", status=0) // 0=启用, 1=禁用 +``` + +### 7.4 删除密钥 + +``` +⚠️ 删除前确认: +⚠️ 安全提醒:此操作存在删除风险。 +说明:API 密钥删除后不可恢复,依赖该密钥的服务将失效。 +请确认是否继续? + +回复: 确认要删除 API 密钥 "AWINBEXT" 吗?此操作不可恢复。 + +调用: delete_api_key(id="7") +``` + +### 7.5 查看密钥权限 + +``` +调用: get_api_key_permissions(apiKeyId="7") +``` + +### 7.6 批量授权 + +``` +调用: grant_api_key_permissions( + apiKeyId="7", + batchDatas=[ + { + "connectionId": "58", + "permissionLevel": "connection", + "permissionType": "read,write" + }, + { + "connectionId": "58", + "permissionLevel": "database", + "databaseName": "order_db", + "permissionType": "read" + } + ] +) +``` + +权限级别说明: +- `connection`:数据源级别权限 +- `database`:数据库级别权限 +- `table`:表级别权限 + +--- + +## 场景 8:技能与工具管理 + +当用户需要创建和管理自定义技能时,使用此流程。 + +### 8.1 查看数据源关联的技能 + +``` +调用: get_skill_by_datasource(datasourceId="58") +``` + +### 8.2 创建技能 + +``` +调用: create_skill(datasourceId="58", name="订单查询技能", description="用于订单数据的常用查询") +``` + +### 8.3 查看技能下的工具 + +``` +调用: get_skill_tools(skillId="xx") +``` + +### 8.4 将 SQL 创建为可复用工具 + +``` +调用: create_sql_tool( + skillId="xx", + tableIds=["5"], + suggestions=[{ + "name": "查询活跃用户", + "businessDescription": "查询所有状态为活跃的用户", + "sqlTemplate": "SELECT * FROM users WHERE status = #{status}", + "sqlParams": {"status": {"type": "string", "default": "active"}}, + "resultType": "list", + "businessScenario": "用于查看当前活跃用户列表" + }] +) +``` + +### 8.5 删除技能工具 + +``` +⚠️ 删除前确认: +⚠️ 安全提醒:此操作存在删除风险。 +说明:技能工具删除后不可恢复。 +请确认是否继续? + +调用: delete_skill_tool(skillToolId="xx") +``` + +### 8.6 更新技能配置 + +``` +调用: update_skill_config( + datasourceId="58", + configTemplate='{"mcpServer": "..."}' // JSON 字符串 +) +``` + +--- + +## 场景 9:表订阅管理 + +``` +用户请求: "订阅 orders 表" / "取消订阅 users 表" + +调用: toggle_table_subscription( + configId="数据库配置ID", + tableName="orders", + isSubscribe=true // true=订阅, false=取消订阅 +) +``` + +--- + +## 最佳实践 + +### 1. 安全第一 + +#### 通用安全确认规则 + +- 执行任何工具前,评估是否存在删除、泄密、政治敏感、似黄等风险 +- 存在风险时,**必须询问用户确认**,说明后果,不得擅作主张 +- 用户未明确确认前,不得执行 + +- 执行任何写操作(INSERT/UPDATE/DELETE)前,先确认环境(prod vs test) +- 删除操作必须向用户展示将要删除的数据并二次确认 +- 危险操作(DELETE/DROP/TRUNCATE/UPDATE 影响多行)前必须明确告知用户风险 +- 数据导入时默认使用 `test` 环境 + +### 2. 分步引导 + +- 新手可能不知道 `datasourceId`、`tableId` 等参数,先通过列表工具引导获取 +- 复杂操作分步执行,每步确认后继续 +- 如果用户请求不完整(如未指定数据源),先引导补充信息 + +### 3. 数据展示 + +- 表格数据使用 Markdown 表格格式 +- 长文本截断显示(最多 100 字符) +- 数字格式化(千位分隔符、货币符号) +- 时间格式化为可读格式 +- 展示数据时包含字段名和类型 + +### 4. 错误处理 + +- API 错误:展示错误信息,建议重试或检查参数 +- SQL 错误:展示 SQL 错误位置,建议修正 +- 连接错误:检查数据源状态,建议启用或重新配置 +- "登录过期":提示用户检查 `API_KEY` 环境变量 + +### 5. 沟通方式 + +- 向小白用户解释时,避免使用技术术语(如 JSON Schema、SERIAL 等),用通俗语言 +- 操作完成后告知结果(成功/失败/影响行数) +- 如果用户操作成功,给出明确的反馈信息 + +--- + +## 常用参数参考 + +### 数据源类型 +- `builtin` - 内置 PostgreSQL +- `external` - 外部数据库(MySQL/PostgreSQL/Oracle/SQL Server/达梦等) + +### 数据源状态 +- `0` - 运行中 +- `1` - 已停止 + +### 环境 +- `prod` - 生产环境(默认) +- `test` - 测试环境 + +### 常用字段类型 +| 类型 | 用途 | 示例 | +|------|------|------| +| `SERIAL` | 自增主键 | `id SERIAL PRIMARY KEY` | +| `VARCHAR(n)` | 短文本 | `username VARCHAR(50)` | +| `TEXT` | 长文本 | `description TEXT` | +| `INTEGER` | 整数 | `stock INTEGER` | +| `DECIMAL(m,n)` | 精确小数 | `price DECIMAL(10,2)` | +| `TIMESTAMP` | 时间戳 | `created_at TIMESTAMP` | +| `BOOLEAN` | 布尔值 | `is_active BOOLEAN` | + +### 表结构变更操作类型(alter_table) +| 类型 | 用途 | +|------|------| +| `ADD_COLUMN` | 添加字段 | +| `DROP_COLUMN` | 删除字段 | +| `RENAME_COLUMN` | 重命名字段 | +| `ALTER_COLUMN_TYPE` | 修改字段类型 | +| `SET_NOT_NULL` | 设置为非空 | +| `DROP_NOT_NULL` | 取消非空约束 | +| `SET_DEFAULT` | 设置默认值 | +| `DROP_DEFAULT` | 删除默认值 | + +### 权限级别(API 密钥授权) +| 级别 | 范围 | +|------|------| +| `connection` | 整个数据源 | +| `database` | 指定数据库 | +| `table` | 指定表 | + +--- + +## 快速开始 + +如果用户说"帮我查一下数据库",按以下步骤操作: + +1. 调用 `list_datasources()` 获取数据源列表 +2. 展示列表,让用户选择或默认第一个运行中的数据源 +3. 调用 `get_datasource_detail(datasourceId="xx")` 获取数据库和表信息 +4. 引导用户选择要操作的表 +5. 根据用户意图调用相应的工具: + - 想看数据 → `query_table_data` + - 想执行 SQL → `execute_sql` + - 想新增数据 → `insert_table_row` + - 想修改数据 → `update_table_row` + - 想删除数据 → `delete_table_rows`(需确认) + - 想导出 Excel → `export_table_excel` + - 想创建表 → `generate_table_by_description` + `create_table` + - 想导入 Excel → `preview_import_data` + `confirm_import_data` diff --git a/.kilo/skills/mcp-tool-testing.zip b/.kilo/skills/mcp-tool-testing.zip new file mode 100644 index 0000000000000000000000000000000000000000..5303d592a1209602192f3405e24cad07b1b248e1 GIT binary patch literal 5992 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 PQ2trJ{@G(l{!ae^Xf`D> literal 0 HcmV?d00001 diff --git a/.kilo/skills/mcp-tool-testing/SKILL.md b/.kilo/skills/mcp-tool-testing/SKILL.md new file mode 100644 index 0000000..49ef116 --- /dev/null +++ b/.kilo/skills/mcp-tool-testing/SKILL.md @@ -0,0 +1,356 @@ +--- +name: mcp-tool-testing +description: MCP 工具通用测试技能。自动发现、执行和组合测试任何 MCP 服务器中的工具,支持场景自动创造和风险前置询问。 +version: 0.1.0 +--- + +# MCP Tool Testing Skill + +这是一个通用的 MCP(Model Context Protocol)工具测试技能。适用于任何 MCP 环境,无论是自研的还是第三方的。 + +## 核心原则 + +1. **通用性**:不假设任何具体的服务器名称、工具名称或项目结构 +2. **自动发现**:通过 MCP 协议本身获取当前环境中可用的所有工具 +3. **智能执行**:能判断的入参自动执行,不能判断的询问用户 +4. **场景自动创造**:根据工具自动匹配并创造测试场景,无需用户指定 +5. **风险前置**:有风险的操作必须询问用户确认,绝不擅作主张 +6. **结果汇总**:最终只返回执行工具的入参、出参和理解 + +## 执行流程 + +### 第一步:发现可用工具 + +通过 MCP 的 `tools/list` 方法获取当前环境中所有可用的工具。 + +**获取每个工具的信息:** +- 工具名称(name) +- 工具描述(description) +- 入参 schema(inputSchema): + - 参数名称 + - 参数类型(string, integer, boolean, object, array, enum 等) + - 是否必填(required) + - 默认值(default) + - 枚举值(enum) + - 参数描述(description) + +### 第二步:分析工具并分类 + +根据工具名称和描述,自动对工具进行分类: + +1. **查询类**:list_xxx, get_xxx, query_xxx, search_xxx, find_xxx +2. **创建类**:create_xxx, add_xxx, insert_xxx, new_xxx +3. **更新类**:update_xxx, edit_xxx, modify_xxx, alter_xxx +4. **删除类**:delete_xxx, remove_xxx, drop_xxx +5. **执行类**:execute_xxx, run_xxx, call_xxx, invoke_xxx +6. **状态类**:toggle_xxx, enable_xxx, disable_xxx, start_xxx, stop_xxx +7. **其他**:无法归类的工具 + +### 第三步:执行单个工具 + +对每个工具,按以下策略执行: + +#### 入参判断逻辑 + +``` +对于每个必填参数(required 中的参数): + 1. schema 有 default 值 → 使用该默认值 + 2. schema 有 enum 且只有一个值 → 使用该值 + 3. schema 有 enum 且有多个值 → 使用第一个,或询问用户 + 4. 参数名是常见的通用类型: + - pageNum, page, offset → 使用 1 + - pageSize, limit, size → 使用 10 + - target, environment → 使用 "prod" 或第一个 enum 值 + - status, enabled → 使用 0 或 true + - name, title → 使用测试名称如 "test_item" + - id, xxxId → 需要从其他工具查询获取,或询问用户 + 5. 可以从之前执行的工具结果中获取 → 使用前一个工具的输出 + 6. 以上都不满足 → 询问用户 + +对于非必填参数: + - 一般不传,使用服务器默认值 + - 如果需要测试特定功能,可以传一个合理值 +``` + +#### 询问用户的标准 + +当遇到以下情况时必须询问: + +**1. 参数不明确** + - 参数是必填的,且值完全不明确 + - 例如:某个特定的业务 ID、密钥、路径等 + +**2. 参数有多个合理选项且无法自动判断** + - 例如:enum 有 ["mysql", "postgresql", "oracle"],不知道测试用哪个 + +**3. 参数涉及敏感信息** + - 例如:password, token, apiKey, secret 等 + +**4. 高风险操作必须询问(绝不擅作主张)** + + 以下类型的工具/操作在执行前必须先询问用户确认: + + - **删除类**:delete_xxx, remove_xxx, drop_xxx, destroy_xxx + - 风险:数据丢失,不可恢复 + - 询问内容:确认是否要执行删除操作,指定删除目标或使用测试数据 + + - **泄密风险类**:export_xxx, dump_xxx, download_xxx, get_all_xxx(大量数据) + - 风险:可能导出敏感业务数据、用户信息、密钥等 + - 询问内容:确认是否要导出数据,是否使用脱敏测试数据 + + - **政治敏感类**:涉及政府、政策、领导人等相关内容的工具 + - 风险:可能触及政治敏感话题 + - 询问内容:确认测试方向和范围 + + - **似黄/色情风险类**:涉及用户生成内容、图片、文本审核等相关工具 + - 风险:可能接触到不当内容 + - 询问内容:确认是否使用安全的测试数据 + + - **生产环境操作类**:影响生产环境数据的工具 + - 风险:可能影响真实业务 + - 询问内容:确认是否在测试环境执行,或使用只读模式 + +**5. 更新/修改类操作需要确认** + - 例如:update_xxx, modify_xxx 可能改变真实数据 + +**高风险操作询问格式:** + +``` +⚠️ 高风险操作提醒 + +工具:[工具名] +风险类型:[删除/泄密/政治敏感/似黄/生产环境影响] +具体风险:[描述可能的风险] + +请选择: +1. 确认执行,使用测试数据 +2. 确认执行,使用真实数据(我知道风险) +3. 跳过此工具 +4. 其他指示 +``` + +**普通询问格式:** + +``` +工具:[工具名] +参数:[参数名](类型:[类型],必填:是/否) +用途:[从 description 中提取] + +请选择: +1. [建议选项1,如果有] +2. [建议选项2,如果有] +3. 提供自定义值 + +或者告诉我使用什么值。 +``` + +#### 执行顺序策略 + +1. **先执行无参数或少参数的查询工具** + - 如 list_xxx、get_all_xxx 等 + - 这些工具通常不需要太多参数,可以直接执行 + - 执行结果可以为后续工具提供 ID 等参数 + +2. **利用查询结果作为后续工具的入参** + - 例如:list_datasources 返回的 id 用于 get_datasource_detail + - 例如:list_tables 返回的 tableId 用于 query_table_data + +3. **最后执行创建/修改/删除类工具** + - 这些通常需要更多上下文参数 + - 删除操作要特别小心,确认是测试数据 + +### 第四步:场景自动创造与搭配测试 + +这是本技能的核心能力:根据当前所有可用工具,自动创造有意义的测试场景,无需用户指定。 + +#### 场景识别规则 + +根据工具的名称、描述和参数,自动匹配以下 8 种场景模式: + +**1. 查询链路场景** + - 匹配规则:存在 list_xxx + get_xxx_detail/xxx_detail + query/get_data 类工具 + - 创造方式:list 获取列表 → 取第一条的 ID → get detail 获取详情 → 用详情中的 ID 查询关联数据 + - 示例:list_datasources → get_datasource_detail(id) → list_tables(datasourceId) → get_table_detail(tableId) + +**2. 完整 CRUD 场景** + - 匹配规则:存在 create_xxx + list/query_xxx + update_xxx + delete_xxx 类工具 + - 创造方式:create 创建测试数据 → list 确认存在 → update 修改 → list 确认修改 → delete 删除 → list 确认删除 + - 示例:create_api_key → list_api_keys → toggle_api_key_status → list_api_keys → delete_api_key → list_api_keys + +**3. 状态变更验证场景** + - 匹配规则:存在 get/list_xxx + toggle/enable/disable/start/stop_xxx 类工具 + - 创造方式:get 初始状态 → toggle 变更状态 → get 验证状态已变更 → toggle 恢复 → get 验证恢复 + - 示例:get_datasource_detail → toggle_datasource_status(status=1) → get_datasource_detail → toggle_datasource_status(status=0) → get_datasource_detail + +**4. 参数传递链路场景** + - 匹配规则:工具 A 的输出字段与工具 B 的输入参数名匹配或语义相关 + - 创造方式:执行 A → 从结果提取字段 → 作为 B 的输入 → 执行 B → 继续传递给 C + - 示例:list_datasources 返回 datasourceId → get_datasource_detail(datasourceId) 返回 connectionId → execute_sql(connectionId, sql) + +**5. 批量操作场景** + - 匹配规则:存在接受 array 类型参数的工具(如批量删除、批量授权) + - 创造方式:list 获取多条记录 → 提取 IDs 数组 → 传递给批量操作工具 → 验证结果 + - 示例:list_api_keys → 提取 ids → grant_api_key_permissions(batchDatas=[{...}]) + +**6. 条件分支场景** + - 匹配规则:工具支持不同枚举参数或可选参数,产生不同行为 + - 创造方式:同一工具传不同参数 → 对比输出差异 + - 示例:query_table_data(tableId, target="prod") vs query_table_data(tableId, target="test") + +**7. 异常处理场景** + - 匹配规则:任何工具 + - 创造方式:传入无效 ID、空参数、错误类型 → 验证错误处理是否合理 + - 示例:get_datasource_detail(datasourceId="invalid") → 验证返回错误信息 + +**8. 跨服务器场景**(当有多个 MCP 服务器时) + - 匹配规则:不同服务器的工具之间存在业务关联 + - 创造方式:服务器A的输出 → 作为服务器B的输入 + - 示例:IoT 获取设备列表 → Dify workflow 处理设备数据 → SQL 执行存储结果 + +#### 场景自动创造流程 + +``` +1. 收集所有工具,建立工具字典 + - key: 工具名 + - value: {description, inputSchema, outputSample, category} + +2. 构建参数依赖图 + - 分析每个工具的输入参数名(如 datasourceId, tableId, apiKeyId) + - 分析每个工具的输出字段(从执行结果中提取) + - 建立 "输出字段 → 输入参数" 的映射关系 + +3. 匹配场景模式 + - 遍历上述 8 种场景规则 + - 对每种规则,检查当前工具集是否满足匹配条件 + - 满足则创造具体场景实例 + +4. 场景优先级排序 + - P0: 查询链路(最基础,最先执行) + - P1: CRUD 完整链路(验证完整生命周期) + - P2: 状态变更验证(验证状态管理) + - P3: 参数传递链路(验证工具间协作) + - P4: 批量操作、条件分支、异常处理 + - P5: 跨服务器场景(最复杂,最后执行) + +5. 执行场景 + - 按优先级依次执行 + - 前一步的输出自动传递给下一步 + - 任何一步失败则标记场景失败,继续下一个场景 + - 场景中包含高风险操作时,执行前询问用户 +``` + +#### 场景执行记录格式 + +```markdown +### 场景:[场景名称] +- **触发规则**:[匹配的场景模式,如 "查询链路场景"] +- **涉及工具**:工具A → 工具B → 工具C +- **执行过程**: + | 步骤 | 工具 | 入参 | 出参摘要 | 状态 | + |------|------|------|---------|------| + | 1 | list_xxx | {} | 返回 5 条记录 | ✅ | + | 2 | get_xxx | {id: "从步骤1获取"} | 返回详情 | ✅ | + | 3 | query_xxx | {xxxId: "从步骤2获取"} | 返回结果 | ✅ | +- **数据流**:步骤1.id → 步骤2.id → 步骤2.connectionId → 步骤3.connectionId +- **场景结果**:✅ 全部成功 / ❌ 步骤N失败(原因:...) +- **理解**:[对这个场景的整体理解,工具间如何协作] +``` + +**场景测试要点:** +- 记录完整的数据流转过程 +- 验证工具之间的兼容性 +- 检查数据格式是否一致 +- 场景创造过程不需要用户干预,自动完成 +- 只在遇到无法判断的参数或高风险操作时才询问用户 + +### 第五步:生成报告 + +最终只返回以下内容: + +```markdown +# MCP 工具测试报告 + +## 工具列表 + +共发现 [N] 个工具: + +| 序号 | 工具名 | 描述 | 必填参数 | 分类 | +|------|--------|------|---------|------| + +## 工具执行结果 + +### 1. [工具名] +- **描述**:[工具描述] +- **入参**:`{JSON}` +- **出参**:`{JSON 摘要}` +- **状态**:✅ 成功 / ❌ 失败 / ⚠️ 跳过(原因:...) +- **理解**:[你对这个工具功能的简要理解] + +### 2. [工具名] +... + +## 场景测试 + +### 场景1:[场景名称] +- **流程**:工具A → 工具B → 工具C +- **数据流**:[描述数据如何在工具间传递] +- **结果**:[最终结果摘要] +- **状态**:✅ 成功 / ❌ 失败 + +## 总结 + +| 指标 | 数量 | +|------|------| +| 发现工具总数 | N | +| 成功执行 | N | +| 执行失败 | N | +| 跳过(需用户提供信息) | N | +| 高风险操作(已询问用户) | N | +| 场景测试数 | N | + +## 待确认事项 + +列出需要用户提供的参数或信息: +1. [工具名] 的 [参数名]:[说明] +2. ... +``` + +## 特殊处理 + +### 动态工具 + +有些服务器启动时从外部加载工具定义(如从 API、配置文件、工作流引擎等): +1. 通过 MCP 协议获取实际可用的工具列表 +2. 对动态获取的工具同样按照上述流程测试 +3. 记录工具的来源信息(如果有) + +### 需要认证/配置的服务器 + +某些 MCP 服务器需要特定的环境变量、API Key、Token 等: +1. 检查服务器是否正常运行 +2. 如果启动失败,记录错误信息并跳过 +3. 如果运行正常但工具调用失败(如 401),询问用户提供认证信息 + +### 失败处理 + +- **网络超时**:记录错误,标记为失败,继续下一个 +- **参数错误**:检查是否需要补充参数,或询问用户 +- **服务器未运行**:记录原因,跳过该服务器的所有工具 +- **工具不存在**:可能工具定义已变更,重新获取工具列表 + +### 输出处理 + +- 输出过长时适当截断(如超过 2000 字符) +- 保留关键信息:状态码、主要数据、错误信息 +- 对于列表类输出,显示前几条和总数 + +## 注意事项 + +1. **保持参数一致性**:同一个 ID 在多个工具中保持相同 +2. **风险前置**:高风险操作必须先询问用户,绝不擅作主张 +3. **记录完整**:每个工具的入参、出参都要记录 +4. **及时询问**:遇到不明确的参数不要猜测,询问用户 +5. **场景优先**:优先测试有意义的场景组合,而不是孤立地测试每个工具 +6. **自动创造**:场景不需要用户指定,根据工具自动匹配和创造 +7. **最终输出**:只返回入参、出参和理解,不需要冗长的过程描述 +8. **五类风险**:删除、泄密、政治、似黄、生产环境影响——必须询问用户 diff --git a/lzwcai_mcp_agile_db/README.md b/lzwcai_mcp_agile_db/README.md index 383820b..8ae50f6 100644 --- a/lzwcai_mcp_agile_db/README.md +++ b/lzwcai_mcp_agile_db/README.md @@ -6,8 +6,8 @@ | 变量名 | 必填 | 说明 | |--------|------|------| -| `AGILE_DB_API_KEY` | 是 | 数据库管理平台的 API 密钥 | -| `AGILE_DB_BASE_URL` | 否 | 数据库管理平台后端地址(默认 `http://localhost:8080`) | +| `API_KEY` | 是 | 数据库管理平台的 API 密钥(格式: `Bearer `) | +| `backendBaseUrl` | 否 | 数据库管理平台后端地址(默认 `http://lzwcai-demp-corp-manager:8086`) | ## 安装 @@ -19,8 +19,8 @@ pip install -e . ```bash # 设置环境变量 -export AGILE_DB_API_KEY="your-api-key" -export AGILE_DB_BASE_URL="http://localhost:8080" # 可选 +export API_KEY="Bearer your-token" +export backendBaseUrl="https://dempdemo.lzwcai.com" # 可选 # 运行 MCP Server lzwcai-mcp-agile-db diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py deleted file mode 100644 index 194ed3d..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/main.py +++ /dev/null @@ -1,6 +0,0 @@ -def main(): - print("Hello from lzwcai-mcp-agile-db!") - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml deleted file mode 100644 index 5e91253..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/pyproject.toml +++ /dev/null @@ -1,7 +0,0 @@ -[project] -name = "lzwcai-mcp-agile-db" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" -requires-python = ">=3.12" -dependencies = [] diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py index 52ccf5e..7b3468c 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py @@ -26,7 +26,7 @@ def register_tool(name: str): input_schema = {...} async def execute(self, args): - return await self.client.get("/api/...", params=args) + return await self.client.get("/datasource/...", params=args) Args: name: 工具名称(唯一标识) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py index c309701..16b29c5 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py @@ -22,7 +22,7 @@ class ListApiKeysTool(ToolDef): 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) + return await self.client.get("/datasource/api_key/list", params=params) @register_tool("create_api_key") @@ -38,7 +38,7 @@ class CreateApiKeyTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/api_key", json_data=args) + return await self.client.post("/datasource/api_key", json_data=args) @register_tool("toggle_api_key_status") @@ -55,7 +55,7 @@ class ToggleApiKeyStatusTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.put("/api/datasource/api_key", json_data=args) + return await self.client.put("/datasource/api_key", json_data=args) @register_tool("delete_api_key") @@ -71,7 +71,7 @@ class DeleteApiKeyTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.delete(f"/api/datasource/api_key/{args['id']}") + return await self.client.delete(f"/datasource/api_key/{args['id']}") @register_tool("get_api_key_permissions") @@ -87,7 +87,7 @@ class GetApiKeyPermissionsTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.get(f"/api/datasource/api_key/permission/{args['apiKeyId']}") + return await self.client.get(f"/datasource/api_key/permission/{args['apiKeyId']}") @register_tool("grant_api_key_permissions") @@ -118,4 +118,4 @@ class GrantApiKeyPermissionsTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/api_key/permission/grant_batch", json_data=args) + return await self.client.post("/datasource/api_key/permission/grant_batch", json_data=args) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py index 9b4ef0d..26c2478 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py @@ -38,8 +38,8 @@ class PreviewImportDataTool(ToolDef): "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", + return await self.client.upload( + f"/datasource/connection/{connection_id}/import_document/preview", files=files, params={"target": target}, ) @@ -65,8 +65,8 @@ class ConfirmImportDataTool(ToolDef): target = args.pop("target", "test") data = args.pop("data") - return self.client.post( - f"/api/datasource/connection/{connection_id}/import_document/confirm", + return await self.client.post( + f"/datasource/connection/{connection_id}/import_document/confirm", json_data=data, params={"target": target}, ) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py index 274838c..e524b44 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py @@ -22,7 +22,7 @@ class ListDatabasesTool(ToolDef): 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) + return await self.client.get("/datasource/config/list", params=params) @register_tool("list_tables") @@ -42,7 +42,7 @@ class ListTablesTool(ToolDef): 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) + return await self.client.get("/datasource/table/list", params=params) @register_tool("get_table_detail") @@ -59,7 +59,7 @@ class GetTableDetailTool(ToolDef): async def execute(self, args: dict) -> dict: table_id = args["tableId"] - return self.client.get(f"/api/datasource/table/{table_id}/detail") + return await self.client.get(f"/datasource/table/{table_id}/detail") @register_tool("create_table") @@ -98,7 +98,7 @@ class CreateTableTool(ToolDef): 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) + return await self.client.post(f"/datasource/connection/{connection_id}/create_table", json_data=args) @register_tool("alter_table") @@ -131,7 +131,7 @@ class AlterTableTool(ToolDef): 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) + return await self.client.put(f"/datasource/connection/{connection_id}/alter_table", json_data=args) @register_tool("generate_table_by_description") @@ -148,4 +148,4 @@ class GenerateTableByDescriptionTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/connection/generate_table", json_data=args) + return await self.client.post("/datasource/connection/generate_table", json_data=args) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py index 9cb5e66..c38ea17 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py @@ -27,7 +27,7 @@ class ListDatasourcesTool(ToolDef): 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) + return await self.client.get("/datasource/connection/list", params=params) @register_tool("get_datasource_detail") @@ -47,17 +47,17 @@ class GetDatasourceDetailTool(ToolDef): result = {} # 获取基本信息 try: - result["detail"] = self.client.get(f"/api/datasource/connection/{ds_id}") + result["detail"] = await self.client.get(f"/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}") + result["config"] = await self.client.get(f"/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}") + result["structure"] = await self.client.get(f"/datasource/connection/realtime/structure/{ds_id}") except Exception as e: result["structure"] = {"error": str(e)} return result @@ -100,12 +100,12 @@ class CreateDatasourceTool(ToolDef): "password": args.get("password"), "connectionType": args.get("connectionType", "user_password"), } - test_result = self.client.post("/api/datasource/connection/test", json_data=test_data) + test_result = await self.client.post("/datasource/connection/test", json_data=test_data) if test_result.get("code") != 200: return {"success": False, "error": f"连接测试失败: {test_result.get('msg', '未知错误')}"} # 创建数据源 - return self.client.post("/api/datasource/connection", json_data=args) + return await self.client.post("/datasource/connection", json_data=args) @register_tool("update_datasource") @@ -128,7 +128,7 @@ class UpdateDatasourceTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.put("/api/datasource/connection", json_data=args) + return await self.client.put("/datasource/connection", json_data=args) @register_tool("toggle_datasource_status") @@ -145,7 +145,7 @@ class ToggleDatasourceStatusTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.put("/api/datasource/connection/changeStatus", json_data=args) + return await self.client.put("/datasource/connection/changeStatus", json_data=args) @register_tool("delete_datasource") @@ -164,10 +164,10 @@ class DeleteDatasourceTool(ToolDef): ds_id = args["id"] # 先尝试停用(仅忽略已停用等预期错误) try: - self.client.put("/api/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1}) + await self.client.put("/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}") + return await self.client.delete(f"/datasource/connection/{ds_id}") diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py index 50cbe5b..1a3af12 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py @@ -20,7 +20,7 @@ class GetSkillByDatasourceTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.get(f"/api/datasource/skill/getByDatasource/{args['datasourceId']}") + return await self.client.get(f"/datasource/skill/getByDatasource/{args['datasourceId']}") @register_tool("get_skill_tools") @@ -36,7 +36,7 @@ class GetSkillToolsTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.get(f"/api/datasource/skill/getBySkillId/{args['skillId']}") + return await self.client.get(f"/datasource/skill/getBySkillId/{args['skillId']}") @register_tool("create_skill") @@ -54,7 +54,7 @@ class CreateSkillTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/skill/createOrGet", json_data=args) + return await self.client.post("/datasource/skill/createOrGet", json_data=args) @register_tool("create_sql_tool") @@ -97,7 +97,7 @@ class CreateSqlToolTool(ToolDef): 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) + return await self.client.post("/datasource/skill/confirmTools", json_data=args) @register_tool("delete_skill_tool") @@ -113,7 +113,7 @@ class DeleteSkillToolTool(ToolDef): } async def execute(self, args: dict) -> dict: - return self.client.delete(f"/api/datasource/skill/tskilltool/{args['skillToolId']}") + return await self.client.delete(f"/datasource/skill/tskilltool/{args['skillToolId']}") @register_tool("update_skill_config") @@ -134,4 +134,4 @@ class UpdateSkillConfigTool(ToolDef): # 如果 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) + return await self.client.post("/datasource/skill/updateOrGet", json_data=args) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py index e041731..07ddf92 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py @@ -13,13 +13,26 @@ class ExecuteSqlTool(ToolDef): "type": "object", "properties": { "datasourceId": {"type": "string", "description": "数据源 ID"}, - "executableSql": {"type": "string", "description": "SQL 语句"}, + "sql": {"type": "string", "description": "SQL 语句"}, + "target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"}, "sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"}, "businessName": {"type": "string", "description": "业务名称(可选)"}, - "parameters": {"type": "object", "description": "参数定义(可选)"}, + "params": {"type": "object", "description": "SQL 参数对象(可选)"}, }, - "required": ["datasourceId", "executableSql"], + "required": ["datasourceId", "sql"], } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/sqlExecutionLog/testSqlWithSchema", json_data=args) + # 映射参数名为后端 API 期望的格式 + body = {} + if "datasourceId" in args: + body["datasourceId"] = args["datasourceId"] + if "sql" in args: + body["executableSql"] = args["sql"] + if "sqlTemplate" in args: + body["sqlTemplate"] = args["sqlTemplate"] + if "businessName" in args: + body["businessName"] = args["businessName"] + if "params" in args: + body["parameters"] = args["params"] + return await self.client.post("/datasource/sqlExecutionLog/testSqlWithSchema", json_data=body) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py index 33394ad..6080cf0 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py @@ -12,12 +12,18 @@ class ToggleTableSubscriptionTool(ToolDef): input_schema = { "type": "object", "properties": { - "configId": {"type": "string", "description": "数据库配置 ID"}, - "tableName": {"type": "string", "description": "表名"}, - "isSubscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"}, + "tableId": {"type": "string", "description": "表 ID"}, + "datasourceId": {"type": "string", "description": "数据源 ID"}, + "subscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"}, }, - "required": ["configId", "tableName", "isSubscribe"], + "required": ["tableId", "datasourceId", "subscribe"], } async def execute(self, args: dict) -> dict: - return self.client.post("/api/datasource/subscription/toggle", json_data=args) + # 映射参数名为后端 API 期望的格式 + body = { + "tableId": args["tableId"], + "datasourceId": args["datasourceId"], + "subscribe": args["subscribe"], + } + return await self.client.post("/datasource/subscription/toggle", json_data=body) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py index 55baed8..67bf8c0 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py @@ -26,7 +26,7 @@ class QueryTableDataTool(ToolDef): 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) + return await self.client.get(f"/datasource/connection/builtin/table/{table_id}", params=params) @register_tool("insert_table_row") @@ -49,7 +49,7 @@ class InsertTableRowTool(ToolDef): 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) + return await self.client.post(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=data, params=params) @register_tool("update_table_row") @@ -75,7 +75,7 @@ class UpdateTableRowTool(ToolDef): 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) + return await self.client.put(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params) @register_tool("delete_table_rows") @@ -103,7 +103,7 @@ class DeleteTableRowsTool(ToolDef): 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) + return await self.client.delete(f"/datasource/connection/builtin/table/{table_id}/rows", json_data=body, params=params) @register_tool("export_table_excel") @@ -124,7 +124,7 @@ class ExportTableExcelTool(ToolDef): 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) + result = await self.client.get(f"/datasource/connection/builtin/table/{table_id}/export/excel", params=params) # 处理二进制响应 if result.get("raw"): diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py index 744622f..c0dd25a 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py @@ -30,8 +30,8 @@ class AgileDBAPIClient: 初始化 API 客户端 Args: - base_url: API 基础 URL(默认从环境变量 AGILE_DB_BASE_URL 读取) - api_key: API 密钥(默认从环境变量 AGILE_DB_API_KEY 读取) + base_url: API 基础 URL(默认从环境变量 backendBaseUrl 读取) + api_key: API 密钥(默认从环境变量 API_KEY 读取) default_timeout: 请求超时时间(秒),默认 30 秒 """ if base_url is None: @@ -43,15 +43,15 @@ class AgileDBAPIClient: self.base_url = base_url.rstrip('/') self.api_key = api_key self.default_timeout = default_timeout - self._client: Optional[httpx.Client] = None + self._client: Optional[httpx.AsyncClient] = None logger.info(f"[客户端初始化] base_url={self.base_url}") @property - def client(self) -> httpx.Client: + def client(self) -> httpx.AsyncClient: """懒加载 HTTP 客户端""" if self._client is None: - self._client = httpx.Client(timeout=self.default_timeout) + self._client = httpx.AsyncClient(timeout=self.default_timeout) return self._client def _get_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]: @@ -64,10 +64,14 @@ class AgileDBAPIClient: return headers def _build_url(self, path: str) -> str: - """构建完整 URL""" + """构建完整 URL,自动去掉路径中多余的 /api 前缀""" if path.startswith('http://') or path.startswith('https://'): return path - return f"{self.base_url}{path}" + # 去掉 /api 前缀,因为 base_url 已经包含完整地址 + clean_path = path + if clean_path.startswith('/api'): + clean_path = clean_path[4:] + return f"{self.base_url}{clean_path}" def _handle_response(self, response: httpx.Response, url: str) -> Dict[str, Any]: """统一处理 API 响应""" @@ -93,12 +97,12 @@ class AgileDBAPIClient: return data - def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + async 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) + response = await self.client.get(url, headers=self._get_headers(), params=params) return self._handle_response(response, url) except httpx.TimeoutException: raise Exception(f"API 请求超时: {url}") @@ -107,13 +111,13 @@ class AgileDBAPIClient: 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]: + async 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) + response = await 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}") @@ -122,13 +126,13 @@ class AgileDBAPIClient: 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]: + async 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) + response = await 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}") @@ -137,7 +141,7 @@ class AgileDBAPIClient: 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]: + async 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: @@ -145,7 +149,7 @@ class AgileDBAPIClient: 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) + response = await 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}") @@ -154,14 +158,14 @@ class AgileDBAPIClient: 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]: + async 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) + response = await self.client.post(url, headers=headers, files=files, params=params) return self._handle_response(response, url) except httpx.TimeoutException: raise Exception(f"API 请求超时: {url}") @@ -170,17 +174,17 @@ class AgileDBAPIClient: except httpx.RequestError as e: raise Exception(f"API 请求异常: {url}, 错误: {str(e)}") - def close(self): + async def close(self): """关闭 HTTP 客户端""" if self._client is not None: - self._client.close() + await self._client.aclose() self._client = None - def __enter__(self): + async def __aenter__(self): return self - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() return False diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py index 6de69e0..6e19b1d 100644 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py +++ b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py @@ -6,7 +6,7 @@ from typing import Optional def get_api_key(default: Optional[str] = None) -> str: """ - 获取数据库管理平台 API 密钥 + 获取 API 密钥 Args: default: 默认值(可选) @@ -15,25 +15,25 @@ def get_api_key(default: Optional[str] = None) -> str: str: API 密钥 Raises: - ValueError: 当 AGILE_DB_API_KEY 未设置且无默认值时 + ValueError: 当 API_KEY 未设置且无默认值时 """ - value = os.environ.get("AGILE_DB_API_KEY", default or "") + value = os.environ.get("API_KEY", default or "") if not value: - raise ValueError("环境变量 AGILE_DB_API_KEY 未设置") + raise ValueError("环境变量 API_KEY 未设置") return value -def get_base_url(default: str = "http://localhost:8080") -> str: +def get_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: """ - 获取数据库管理平台后端地址 + 获取后端服务地址 Args: - default: 默认值(默认 http://localhost:8080) + default: 默认值(默认 http://lzwcai-demp-corp-manager:8086) Returns: str: 后端 API 基础 URL """ - return os.environ.get("AGILE_DB_BASE_URL", default) + return os.environ.get("backendBaseUrl", default) def get_env_config() -> dict: diff --git a/lzwcai_mcp_agile_db/main.py b/lzwcai_mcp_agile_db/main.py index f53b660..4fea9dd 100644 --- a/lzwcai_mcp_agile_db/main.py +++ b/lzwcai_mcp_agile_db/main.py @@ -7,6 +7,6 @@ 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" + os.environ["API_KEY"] = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ0b2tlbl90eXBlIjoiTE9HSU4iLCJsb2dpbl91c2VyX2tleSI6IjJiMDY0YzMzLTBiZWYtNDU0NC04NWY1LTRmNTFiOGMxMmI5NSJ9.Uv599TvlQvlTlwrnZGo3Tl2eLAvM0ldE9vpMI5jHxbTf4_tVSRA60rUNIV4LBiw6pt1r_xIi7aFcTRE2PeN5sg" + os.environ["backendBaseUrl"] = "https://dempdemo.lzwcai.com" main() diff --git a/lzwcai_mcp_agile_db/pyproject.toml b/lzwcai_mcp_agile_db/pyproject.toml index 7262bad..a740844 100644 --- a/lzwcai_mcp_agile_db/pyproject.toml +++ b/lzwcai_mcp_agile_db/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lzwcai-mcp-agile-db" -version = "0.1.0" +version = "0.1.3" 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"