diff --git a/.kilo/plans/lzwcai-agile-db-skill.md b/.kilo/plans/lzwcai-agile-db-skill.md deleted file mode 100644 index 9134d36..0000000 --- a/.kilo/plans/lzwcai-agile-db-skill.md +++ /dev/null @@ -1,451 +0,0 @@ -# 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 deleted file mode 100644 index 72157c8..0000000 Binary files a/.kilo/skills/lzwcai-agile-db.zip and /dev/null differ diff --git a/.kilo/skills/lzwcai-agile-db/SKILL.md b/.kilo/skills/lzwcai-agile-db/SKILL.md deleted file mode 100644 index 16f04f9..0000000 --- a/.kilo/skills/lzwcai-agile-db/SKILL.md +++ /dev/null @@ -1,893 +0,0 @@ ---- -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 deleted file mode 100644 index 5303d59..0000000 Binary files a/.kilo/skills/mcp-tool-testing.zip and /dev/null differ diff --git a/.kilo/skills/mcp-tool-testing/SKILL.md b/.kilo/skills/mcp-tool-testing/SKILL.md deleted file mode 100644 index 49ef116..0000000 --- a/.kilo/skills/mcp-tool-testing/SKILL.md +++ /dev/null @@ -1,356 +0,0 @@ ---- -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/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7a73a41..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/PKG-INFO b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/PKG-INFO deleted file mode 100644 index 005f507..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-demp-tool-server-dify-to-mcp -Version: 0.1.0 -Summary: 这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。 -Requires-Python: >=3.10 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: mcp>=1.1.2 -Requires-Dist: omegaconf>=2.3.0 -Requires-Dist: pip>=24.3.1 -Requires-Dist: python-dotenv>=1.0.1 -Requires-Dist: requests -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/README.md b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/README.md deleted file mode 100644 index 5216d21..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# lzwcai-mcp-server-package - -#### 介绍 -lzwcai-mcp-server-package - -#### 软件架构 -软件架构说明 - - -#### 安装教程 - -1. xxxx -2. xxxx -3. xxxx - -#### 使用说明 - -1. xxxx -2. xxxx -3. xxxx - -#### 参与贡献 - -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request - - -#### 特技 - -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/PKG-INFO b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/PKG-INFO deleted file mode 100644 index ca5b98b..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-demp-tool-server-dify-to-mcp -Version: 0.1.4 -Summary: 这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。 -Requires-Python: >=3.10 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: mcp>=1.1.2 -Requires-Dist: omegaconf>=2.3.0 -Requires-Dist: pip>=24.3.1 -Requires-Dist: python-dotenv>=1.0.1 -Requires-Dist: requests -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/SOURCES.txt b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/SOURCES.txt deleted file mode 100644 index 5c7186f..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/SOURCES.txt +++ /dev/null @@ -1,23 +0,0 @@ -README.md -pyproject.toml -setup.cfg -lzwcai_demp_tool_server_dify_to_mcp.egg-info/PKG-INFO -lzwcai_demp_tool_server_dify_to_mcp.egg-info/SOURCES.txt -lzwcai_demp_tool_server_dify_to_mcp.egg-info/dependency_links.txt -lzwcai_demp_tool_server_dify_to_mcp.egg-info/entry_points.txt -lzwcai_demp_tool_server_dify_to_mcp.egg-info/requires.txt -lzwcai_demp_tool_server_dify_to_mcp.egg-info/top_level.txt -src/__init__.py -src/create_mcp.py -src/create_mcp_util.py -src/chat/__init__.py -src/chat/chat_server.py -src/completion/completion_server.py -src/completion/test.py -src/core/__init__.py -src/core/core_server.py -src/difyTaskCall/task_instance.py -src/utils/tool_translation.py -src/utils/translator.py -src/workflow/__init__.py -src/workflow/workflow_server.py \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/dependency_links.txt b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/entry_points.txt b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/entry_points.txt deleted file mode 100644 index e3a3035..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -lzwcai-demp-tool-server-dify-to-mcp = src.create_mcp:run_main diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/requires.txt b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/requires.txt deleted file mode 100644 index 5d3b552..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/requires.txt +++ /dev/null @@ -1,7 +0,0 @@ -httpx>=0.28.1 -mcp>=1.1.2 -omegaconf>=2.3.0 -pip>=24.3.1 -python-dotenv>=1.0.1 -requests -pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/top_level.txt b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/top_level.txt deleted file mode 100644 index 85de9cf..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -src diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/main.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/main.py deleted file mode 100644 index b5987e3..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/main.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -""" -主入口文件 -用于启动 Dify MCP 服务器,并配置命令行参数 -""" -import os -import sys - -# Mock 配置参数 -def setup_mock_arguments(): - """ - 设置模拟命令行参数 - 这些参数可以根据实际需求进行修改 - """ - # 默认配置 - default_config = { - "base_url": "http://192.168.2.236:3001/v1", - "app_sks": ["app-YFHByB4whARWVqXN2LcuPudq"], - "mode_type": "workflow", - "transport": "stdio" - } - - # 如果没有提供命令行参数,则添加默认参数 - if len(sys.argv) == 1: - sys.argv.extend([ - "--base-url", default_config["base_url"], - "--app-sks", *default_config["app_sks"], - "--mode-type", default_config["mode_type"] - ]) - - return default_config - - -def main(): - """ - 主函数:设置命令行参数并启动服务器 - """ - # 设置模拟命令行参数 - config = setup_mock_arguments() - - # 导入并运行 MCP 服务器 - try: - from src.create_mcp import run_main - - # 获取传输模式 - transport_mode = config.get("transport", "stdio") - - # 运行服务器(不输出额外信息,避免干扰 STDIO 通信) - run_main(transport=transport_mode) - - except ImportError as e: - print(f"[ERROR] 导入错误: {e}", file=sys.stderr) - print("请确保已正确安装所有依赖包", file=sys.stderr) - sys.exit(1) - except Exception as e: - print(f"[ERROR] 运行错误: {e}", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/pyproject.toml b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/pyproject.toml deleted file mode 100644 index 5779d9b..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "lzwcai-demp-tool-server-dify-to-mcp" -version = "0.1.4" -description = "这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。" -requires-python = ">=3.10" -dependencies = [ - "httpx>=0.28.1", - "mcp>=1.1.2", - "omegaconf>=2.3.0", - "pip>=24.3.1", - "python-dotenv>=1.0.1", - "requests", - "pypinyin>=0.54.0", -] - -[tool.setuptools] -packages = {find = {where = ["."], include = ["src*"]}} -include-package-data = true - -[project.scripts] -lzwcai-demp-tool-server-dify-to-mcp = "src.create_mcp:run_main" - -[tool.hatch.build.targets.wheel] -packages = ["src"] - -[tool.setuptools.package-data] -"*" = ["*.env"] -"src" = ["**/*.env"] \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/setup.cfg b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/setup.cfg deleted file mode 100644 index 8bfd5a1..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c0b589d..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/create_mcp.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/create_mcp.cpython-312.pyc deleted file mode 100644 index e46ec7f..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/create_mcp.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ab38063..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/chat_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/chat_server.cpython-312.pyc deleted file mode 100644 index b9ab603..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/chat_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/chat_server.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/chat_server.py deleted file mode 100644 index 7d3e608..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/chat_server.py +++ /dev/null @@ -1,7 +0,0 @@ -class ChatDifyAPI: - def __init__(self, base_url: str, app_sks: str): - self.base_url = base_url - self.app_sks = app_sks - - def process_task(self, task_id: str, **kwargs): - pass diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/__pycache__/completion_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/__pycache__/completion_server.cpython-312.pyc deleted file mode 100644 index 93fb769..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/__pycache__/completion_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/completion_server.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/completion_server.py deleted file mode 100644 index 23605c3..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/completion_server.py +++ /dev/null @@ -1,212 +0,0 @@ -import requests -from abc import ABC -import logging -import json -import re -import pypinyin - -logger = logging.getLogger(__name__) - - -def pinyin_to_camel(pinyin): - """ - 将拼音列表转换为驼峰命名 - 例如: ['ni', 'hao', 'a'] -> 'NiHaoA' - 所有非字母数字字符会被替换为下划线 - """ - # 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线 - cleaned = re.sub(r'[^\w\s]', '_', pinyin) - # 将空格也替换为下划线 - cleaned = re.sub(r'\s+', '_', cleaned) - # 移除连续的下划线并去除首尾下划线 - cleaned = re.sub(r'_+', '_', cleaned).strip('_') - - # 转换为拼音并生成驼峰命名 - pinyin_list = pypinyin.lazy_pinyin(cleaned) - return "tool_" + "".join(word.capitalize() for word in pinyin_list) - - -class CompletionDifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - userId="pp666", - files=None, - ): - url = f"{self.dify_base_url}/completion-messages" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - if conversation_id: - data["conversation_id"] = conversation_id - - if response_mode == "streaming": - response = requests.post(url, headers=headers, json=data, stream=True) - - # 处理流式响应 - full_answer = "" - for line in response.iter_lines(): - if line: - # 跳过 "data:" 前缀 - decoded_line = line.decode("utf-8") - if decoded_line.startswith("data:"): - try: - json_str = decoded_line[5:].strip() - data = json.loads(json_str) - if data.get("event") == "message" and "answer" in data: - # 累积完整答案 - full_answer += data["answer"] - # 这里也可以选择处理每个部分响应,例如返回生成器 - # yield data - except json.JSONDecodeError: - logger.warning(f"无法解析JSON数据: {decoded_line}") - - # 创建一个符合非流式响应格式的结果 - response_data = {"answer": full_answer} - # 处理可能包含代码块的数据 - processed_data = self.process_answer_code_block(response_data) - return processed_data - else: - response = requests.post(url, headers=headers, json=data) - response_data = response.json() - # 处理可能包含代码块的数据 - processed_data = self.process_answer_code_block(response_data) - return processed_data - - def upload_file(self, api_key, file_path, user="pp666"): - - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - files = {"file": open(file_path, "rb")} - data = {"user": user} - response = requests.post(url, headers=headers, files=files, data=data) - response.raise_for_status() - return response.json() - - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - # params = {"user": user} - response = requests.get(url, headers=headers) - response.raise_for_status() - - response_map = response.json() - # 翻译工具名称 - from src.utils.tool_translation import TranslationService - - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - - # 翻译工具描述 - # tool_description = response_map.get("description") - # if tool_description: - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = ( - # f"{tool_description} ({translated_description})" - # ) - - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - return { - "user_input_form": [ - {"string": {"variable": "query", "label": "查询内容", "required": True}} - ] - } - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - @staticmethod - def process_answer_code_block(data): - try: - # 获取answer字段 - answer = data.get("answer", "") - - # 构造符合workflow_finished格式的输出 - formatted_response = [ - {"event": "workflow_finished", "data": {"outputs": {"result": answer}}} - ] - - # 尝试处理可能的代码块 - if answer.startswith("```") and answer.endswith("```"): - try: - # 移除代码块标记并解析JSON - code_content = answer.strip("```").strip() - json_data = json.loads(code_content) - - # 如果包含description字段,用它替换answer - if "description" in json_data: - formatted_response[0]["data"]["outputs"]["result"] = json_data[ - "description" - ] - except json.JSONDecodeError: - # 如果不是有效的JSON,保留原始代码块内容 - pass - - return formatted_response - except Exception as e: - logger.warning(f"处理答案代码块时出错: {str(e)}") - # 发生错误时返回符合格式的基础响应 - return [ - { - "event": "workflow_finished", - "data": { - "outputs": { - "error": str(e), - "fallback": data.get("answer", str(data)), - } - }, - } - ] diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/test.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/test.py deleted file mode 100644 index 4664edf..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/test.py +++ /dev/null @@ -1,104 +0,0 @@ -import requests -from abc import ABC -import logging -import json - -logger = logging.getLogger(__name__) - -res = { - "event": "message", - "task_id": "49c9ea1b-7b43-475b-a680-d769fb238a45", - "id": "432ab98e-5e36-4a29-abe5-e01281c3678c", - "message_id": "432ab98e-5e36-4a29-abe5-e01281c3678c", - "mode": "completion", - "answer": '```\n{\n "description": "该API的具体功能描述暂时不明确,因为提供的API信息 \'今天打老虎啊按时啊啊\' 并不是有效的API名称或描述。请提供正确的API名称和相关输入输出信息,以便我能为其补充完善的API描述。"\n}\n```', - "metadata": { - "usage": { - "prompt_tokens": 73, - "prompt_unit_price": "0.0", - "prompt_price_unit": "0.0", - "prompt_price": "0.0", - "completion_tokens": 61, - "completion_unit_price": "0.0", - "completion_price_unit": "0.0", - "completion_price": "0.0", - "total_tokens": 134, - "total_price": "0.0", - "currency": "USD", - "latency": 1.896302318200469, - } - }, - "created_at": 1747233054, -} - - -def process_answer_code_block(data): - try: - # 获取answer字段 - answer = data.get("answer", "") - - # 检查answer是否是代码块格式 - if answer.startswith("```") and answer.endswith("```"): - # 移除代码块标记并解析JSON - code_content = answer.strip("```").strip() - json_data = json.loads(code_content) - - # 获取description字段 - if "description" in json_data: - return json_data["description"] - - # 如果不是预期格式,则返回原始answer - return data.get("answer", data) - except Exception as e: - logger.warning(f"处理答案代码块时出错: {str(e)}") - # 发生错误时返回原始数据 - return data.get("answer", data) - - -def chat_message_test( - api_key, - inputs={}, - response_mode="blocking", - conversation_id=None, - userId="pp666", - files=None, -): - url = "https://ops.lzwcai.com/v1/completion-messages" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - if conversation_id: - data["conversation_id"] = conversation_id - if response_mode == "streaming": - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - return response - else: - response = requests.post(url, headers=headers, json=data) - return response.json() - - -if __name__ == "__main__": - print("开始执行主程序") - try: - print("准备调用chat_message方法") - res = chat_message_test( - api_key="app-Ppemii3c0ROPoLvRwskgZ7Il", - inputs={"query": "今天打老虎啊按时啊啊"}, - response_mode="streaming", - userId="abc-123", - ) - print("chat_message方法调用完成") - - # 打印响应内容 - print("响应内容:", res) - # print(process_answer_code_block(res)) - except Exception as e: - print(f"执行过程中出现错误: {e}") diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/core_server.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/core_server.py deleted file mode 100644 index c0bae3f..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/core_server.py +++ /dev/null @@ -1,172 +0,0 @@ -import requests -from abc import ABC -import logging -import json -import re -import pypinyin - -logger = logging.getLogger(__name__) - - -def pinyin_to_camel(pinyin): - """ - 将拼音列表转换为驼峰命名 - 例如: ['ni', 'hao', 'a'] -> 'NiHaoA' - 所有非字母数字字符会被替换为下划线 - """ - # 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线 - cleaned = re.sub(r'[^\w\s]', '_', pinyin) - # 将空格也替换为下划线 - cleaned = re.sub(r'\s+', '_', cleaned) - # 移除连续的下划线并去除首尾下划线 - cleaned = re.sub(r'_+', '_', cleaned).strip('_') - - # 转换为拼音并生成驼峰命名 - pinyin_list = pypinyin.lazy_pinyin(cleaned) - return "tool_" + "".join(word.capitalize() for word in pinyin_list) - - -class DifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - print("dify_app_params", dify_app_params) - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - user="pp666", - files=None, - ): - url = f"{self.dify_base_url}/workflows/run" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": user, - } - logger.info("Sending data to Dify API: %s", data) - logger.info("Sending headers to Dify API: %s", headers) - logger.info("Sending url to Dify API: %s", url) - if conversation_id: - data["conversation_id"] = conversation_id - if files: - files_data = [] - for file_info in files: - file_path = file_info.get("path") - transfer_method = file_info.get("transfer_method") - if transfer_method == "local_file": - files_data.append(("file", open(file_path, "rb"))) - elif transfer_method == "remote_url": - pass - response = requests.post( - url, - headers=headers, - data=data, - files=files_data, - stream=response_mode == "streaming", - ) - else: - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - response.raise_for_status() - if response_mode == "streaming": - for line in response.iter_lines(): - if line: - if line.startswith(b"data:"): - try: - json_data = json.loads(line[5:].decode("utf-8")) - yield json_data - except json.JSONDecodeError: - print(f"Error decoding JSON: {line}") - else: - return response.json() - - def upload_file(self, api_key, file_path, user="pp666"): - - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - files = {"file": open(file_path, "rb")} - data = {"user": user} - response = requests.post(url, headers=headers, files=files, data=data) - response.raise_for_status() - return response.json() - - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - - from src.utils.tool_translation import TranslationService - - response_map = response.json() - # 翻译工具名称 - # tool_name = response_map.get("name") - # translated_name = TranslationService.translate_tool_name(tool_name) - # response_map["name"] = translated_name - # # 翻译工具描述 - # tool_description = response_map.get("description") - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = translated_description - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/parameters" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp.py deleted file mode 100644 index 3cd085e..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp.py +++ /dev/null @@ -1,318 +0,0 @@ -import asyncio -import json -import os -import logging -import argparse -from abc import ABC - -import mcp.server.stdio -import mcp.types as types -import requests -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from omegaconf import OmegaConf - -# from src.workflow.workflow_server import WorkflowDifyAPI -from src.difyTaskCall.task_instance import TaskInstance - -# 配置日志记录 -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Dify MCP服务器配置") - parser.add_argument( - "--base-url", - type=str, - help="API基础URL", - default="http://192.168.11.24:3001/v1", - ) - parser.add_argument( - "--app-sks", - nargs="+", - help="应用秘钥列表", - default=["app-d7s00CJ2NY4LJzUEiZsVDnPN"], - ) - parser.add_argument( - "--mode-type", - type=str, - help="Dify应用模式类型 (workflow, chat, completion)", - default="workflow", - choices=["workflow", "chat", "completion"], - ) - return parser.parse_args() - - -def get_app_info(base_url=None, app_sks=None, mode_type=None): - # 获取命令行参数 - args = parse_arguments() - # 命令行参数优先,其次是函数参数,最后是默认值 - if args.base_url is not None: - base_url = args.base_url - if base_url is None: - base_url = "http://192.168.11.24:3001/v1" - - if args.app_sks is not None: - app_sks = args.app_sks - if app_sks is None: - app_sks = ["app-d7s00CJ2NY4LJzUEiZsVDnPN"] - - # 确保 app_sks 始终是列表类型 - if isinstance(app_sks, str): - # 如果是字符串,转换为列表 - app_sks = [app_sks] - - if args.mode_type is not None: - mode_type = args.mode_type - if mode_type is None: - mode_type = "workflow" - return base_url, app_sks, mode_type - # return "https://dempdify.lzwcai.com/v1", ["app-X6wAy5nkvWB3hR69cgvIjC3r"], "workflow" - - - -# 初始化服务器和Dify API -base_url, dify_app_sks, dify_app_mode_type = get_app_info() -server = Server("dify_mcp_server") -task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type) -dify_api = task_instance.get_task_instance(dify_app_mode_type) - - -def process_user_input_form(user_input_form): - """ - 处理Dify应用的用户输入表单,转换为JSON Schema格式 - - 参数: - user_input_form: Dify应用的用户输入表单配置 - - 返回: - 处理后的inputSchema字典 - """ - inputSchema = dict( - type="object", - properties={}, - required=[], - ) - - property_num = len(user_input_form) - if property_num > 0: - for j in range(property_num): - param = user_input_form[j] - param_type = list(param.keys())[0] - param_info = param[param_type] - property_name = param_info["variable"] - - # 根据不同控件类型处理 - if param_type == "text-input": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "paragraph": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - "format": "paragraph", - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "select": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - "enum": param_info["options"], - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "file_upload": - # 文件上传控件处理 - file_type_schema = { - "type": "object", - "description": param_info["label"], - "properties": { - "file_url": {"type": "string", "description": "文件URL"}, - "file_name": {"type": "string", "description": "文件名称"}, - }, - "required": ["file_url"], - } - - # 处理图片上传配置 - if "image" in param_info and param_info["image"]["enabled"]: - image_config = param_info["image"] - file_type_schema["properties"]["type"] = { - "type": "string", - "description": "文件类型,支持png、jpg、jpeg、webp、gif", - "enum": ["png", "jpg", "jpeg", "webp", "gif"], - } - - # 处理数量限制 - number_limits = image_config.get("number_limits", 3) - if number_limits > 1: - # 如果允许多个文件,则使用数组 - inputSchema["properties"][property_name] = { - "type": "array", - "description": param_info["label"], - "items": file_type_schema, - "maxItems": number_limits, - } - else: - # 如果只允许单个文件 - inputSchema["properties"][property_name] = file_type_schema - else: - # 如果没有特定的图片配置,使用一般文件配置 - inputSchema["properties"][property_name] = file_type_schema - - else: - # 默认处理为字符串类型 - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - } - - # 处理必填字段 - if param_info.get("required", False): - inputSchema["required"].append(property_name) - - # 添加必填的userId参数,支持数字或字符串类型 - # inputSchema["properties"]["userId"] = dict( - # oneOf=[{"type": "number"}, {"type": "string"}], - # description="您的员工ID,用于识别您的员工身份", - # ) - # inputSchema["required"].append("userId") - - return inputSchema - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出可用的工具 - 返回: - 工具列表,每个工具都使用JSON Schema验证其参数 - """ - tools = [] - tool_names = dify_api.dify_app_names - tool_infos = dify_api.dify_app_infos - tool_params = dify_api.dify_app_params - tool_num = len(tool_names) - for i in range(tool_num): - # 加载每个工具的应用信息 - app_info = tool_infos[i] - # 加载每个工具的应用参数 - app_param = tool_params[i] - # 处理用户输入表单 - inputSchema = process_user_input_form(app_param["user_input_form"]) - - tools.append( - types.Tool( - name=app_info["name"], - description=app_info["description"], - inputSchema=inputSchema, - ) - ) - return tools - - -@server.call_tool() -async def handle_call_tool( - name: str, arguments: dict | None -) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """ - 调用工具处理请求 - 参数: - name: 工具名称 - arguments: 工具参数 - 返回: - 处理结果列表 - """ - tool_names = dify_api.dify_app_names - if name in tool_names: - tool_idx = tool_names.index(name) - tool_sk = dify_api.dify_app_sks[tool_idx] - responses = dify_api.chat_message( - tool_sk, - inputs=arguments, - userId=arguments.get("userId", "pp666"), - ) - for res in responses: - if res["event"] == "workflow_finished": - outputs = res["data"]["outputs"] - mcp_out = [] - for _, v in outputs.items(): - mcp_out.append(types.TextContent(type="text", text=v)) - return mcp_out - else: - raise ValueError(f"Unknown tool: {name}") - - -def run_main(transport="stdio"): - """ - 主函数:使用stdin/stdout流运行服务器 - """ - if transport == "stdio": - import anyio - from mcp.server.stdio import stdio_server - - async def arun(): - async with stdio_server() as streams: - await server.run( - streams[0], - streams[1], - InitializationOptions( - server_name="dify_mcp_server", - server_version="0.0.6", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - anyio.run(arun) - - else: - from mcp.server.sse import SseServerTransport - from starlette.applications import Starlette - from starlette.responses import Response - from starlette.routing import Mount, Route - - sse = SseServerTransport("/messages/") - - async def handle_sse(request): - async with sse.connect_sse( - request.scope, request.receive, request._send - ) as streams: - await server.run( - streams[0], streams[1], server.create_initialization_options() - ) - return Response() - - starlette_app = Starlette( - debug=True, - routes=[ - Route("/sse", endpoint=handle_sse, methods=["GET"]), - Mount("/messages/", app=sse.handle_post_message), - ], - ) - - import uvicorn - - uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info") - - -if __name__ == "__main__": - run_main() diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp_util.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp_util.py deleted file mode 100644 index 7bff99a..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp_util.py +++ /dev/null @@ -1,373 +0,0 @@ -import json -from typing import Dict, List, Any, Tuple, Optional -import logging -from pathlib import Path - - -# 常量定义 -DEFAULT_NUMBER_LIMITS = 3 # 默认文件数量限制 - - -def process_user_input_form( - user_input_form: List[Dict[str, Any]], -) -> Tuple[Dict[str, Any], List[str]]: - """ - 处理Dify用户输入表单,生成对应的JSON Schema properties和required列表 - - 参数: - user_input_form: Dify应用的用户输入表单配置 - - 返回: - properties: 表单字段的properties字典 - required: 必填字段列表 - """ - properties = {} - required = [] - - if not user_input_form: - return properties, required - - for param in user_input_form: - try: - # 直接获取字典的第一个键,而不是通过list转换 - param_type = next(iter(param)) - param_info = param[param_type] - property_name = param_info["variable"] - - properties[property_name] = { - "type": param_type, - "description": param_info["label"], - } - - if param_info.get("required", False): - required.append(property_name) - except (KeyError, StopIteration) as e: - logging.warning(f"处理用户输入表单项时出错: {e}, 跳过此项") - continue - - return properties, required - - -def process_file_upload(file_upload: Optional[Dict[str, Any]]) -> Dict[str, Any]: - """ - 处理Dify文件上传配置,生成对应的JSON Schema properties - - 设计为通用实现,支持任意文件类型配置 - - 参数: - file_upload: Dify应用的文件上传配置 - - 返回: - file_properties: 文件上传的properties字典 - """ - # 检查是否存在文件上传配置 - if not file_upload: - return {} - - # 收集所有启用的文件类型信息 - enabled_types = [] - max_items = 0 - supported_transfer_methods = set() - file_type_configs = {} - - for file_type, config in file_upload.items(): - if not config.get("enabled", False): - continue - - enabled_types.append(file_type) - number_limits = config.get("number_limits", DEFAULT_NUMBER_LIMITS) - max_items += number_limits - type_transfer_methods = config.get("transfer_methods", []) - supported_transfer_methods.update(type_transfer_methods) - - # 存储每种文件类型的详细配置 - file_type_configs[file_type] = { - "number_limits": number_limits, - "transfer_methods": type_transfer_methods, - } - - # 如果没有启用的文件类型,返回空字典 - if not enabled_types: - return {} - - # 构建文件项的JSON Schema - file_item_schema = _build_file_item_schema( - enabled_types, supported_transfer_methods, file_type_configs - ) - - # 构建最终的files属性 - file_properties = { - "files": { - "type": "array", - "items": file_item_schema, - "description": "支持多种文件类型的文件列表", - "maxItems": max_items, - } - } - - return file_properties - - -def _build_file_item_schema( - enabled_types: List[str], - supported_transfer_methods: set, - file_type_configs: Dict[str, Dict[str, Any]], -) -> Dict[str, Any]: - """ - 构建文件项的JSON Schema - - 参数: - enabled_types: 启用的文件类型列表 - supported_transfer_methods: 支持的传输方式集合 - file_type_configs: 每种文件类型的配置信息 - - 返回: - file_item_schema: 文件项的JSON Schema - """ - file_item_schema = { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": enabled_types, - "description": "文件类型", - }, - "transfer_method": { - "type": "string", - "enum": list(supported_transfer_methods), - "description": "传输方式", - }, - }, - } - - # 添加条件属性 - if "remote_url" in supported_transfer_methods: - file_item_schema["properties"]["url"] = { - "type": "string", - "format": "uri", - "description": "文件URL (当传输方式为remote_url时使用)", - } - - if "local_file" in supported_transfer_methods: - file_item_schema["properties"]["upload_file_id"] = { - "type": "string", - "description": "上传文件ID (当传输方式为local_file时使用)", - } - - # 添加条件验证逻辑 - file_item_schema["allOf"] = [] - - # 为每种文件类型添加验证规则 - for file_type, type_config in file_type_configs.items(): - type_methods = type_config["transfer_methods"] - - # 基本验证 - file_item_schema["allOf"].append( - { - "if": {"properties": {"type": {"const": file_type}}}, - "then": { - "properties": { - "transfer_method": { - "enum": type_methods, - "description": f"{file_type}类型支持的传输方式: {', '.join(type_methods)}", - } - } - }, - } - ) - - # 为每种传输方法添加类型特定的验证 - _add_transfer_method_validations(file_item_schema, file_type, type_methods) - - return file_item_schema - - -def _add_transfer_method_validations( - file_item_schema: Dict[str, Any], file_type: str, type_methods: List[str] -) -> None: - """ - 为每种传输方法添加类型特定的验证 - - 参数: - file_item_schema: 文件项JSON Schema - file_type: 文件类型 - type_methods: 该类型支持的传输方法列表 - """ - for method in type_methods: - if method == "remote_url": - file_item_schema["allOf"].append( - { - "if": { - "properties": { - "type": {"const": file_type}, - "transfer_method": {"const": "remote_url"}, - }, - "required": ["type", "transfer_method"], - }, - "then": {"required": ["url"]}, - } - ) - elif method == "local_file": - file_item_schema["allOf"].append( - { - "if": { - "properties": { - "type": {"const": file_type}, - "transfer_method": {"const": "local_file"}, - }, - "required": ["type", "transfer_method"], - }, - "then": {"required": ["upload_file_id"]}, - } - ) - - -def convert_dify_params_to_schema(tool_params: Dict[str, Any]) -> Dict[str, Any]: - """ - 将用户输入表单和文件上传配置组合成新的结构 - - 参数: - tool_params: 包含user_input_form和file_upload的参数字典 - - 返回: - 组合后的结构 - """ - # 参数验证 - if not isinstance(tool_params, dict): - raise TypeError("tool_params 必须是字典类型") - - # 处理用户输入表单 - properties, required = process_user_input_form( - tool_params.get("user_input_form", []) - ) - - # 处理文件上传配置 - file_properties = process_file_upload(tool_params.get("file_upload")) - - # 创建新的结构 - result = { - "inputs": {"type": "object", "properties": properties, "required": required}, - # "userId": { - # "oneOf": [{"type": "number"}, {"type": "string"}], - # "description": "您的员工ID,用于识别您的员工身份", - # "required": True, - # }, - } - - # 如果有文件上传配置,添加files字段 - if file_properties: - result["files"] = file_properties.get("files", {}) - - return result - - -def finalize_schema_structure(mock_result: Dict[str, Any]) -> Dict[str, Any]: - """ - 将mock_result构建为符合要求的map_mock_result格式 - - 参数: - mock_result: 通过convert_dify_params_to_schema函数获取的结果 - - 返回: - 构建后的map_mock_result字典 - """ - # 确定required字段 - # required_fields = ["userId"] - required_fields = [] - - # 只有当inputs的required有值时,才添加inputs到顶层required - if ( - mock_result.get("inputs", {}).get("required") - and len(mock_result["inputs"]["required"]) > 0 - ): - required_fields.append("inputs") - - # 如果有文件上传,也可以考虑添加files到required - if "files" in mock_result: - # 可以根据需求决定是否将files添加到required - # required_fields.append("files") - pass - - return { - "type": "object", - "properties": mock_result, - "required": required_fields, - } - - -def create_json_file(data: Dict[str, Any], filename: str = "output.json") -> None: - """ - 将数据保存为JSON文件 - - 参数: - data: 要保存的数据 - filename: 保存的文件名 - """ - try: - # 获取mock数据 - mock_result = convert_dify_params_to_schema(data) - - # 使用封装的方法构建map_mock_result - map_mock_result = finalize_schema_structure(mock_result) - - # 确保目标目录存在 - output_path = Path(filename) - output_path.parent.mkdir(parents=True, exist_ok=True) - - # 将结果写入JSON文件 - with open(filename, "w", encoding="utf-8") as f: - json.dump(map_mock_result, f, ensure_ascii=False, indent=4) - - logging.info(f"已成功将数据保存至 {filename}") - print(f"已成功将数据保存至 {filename}") - - except Exception as e: - error_msg = f"保存JSON文件时出错: {e}" - logging.error(error_msg) - raise IOError(error_msg) - - -if __name__ == "__main__": - # 配置日志 - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", - ) - - # 示例数据 - api_data = { - "opening_statement": "", - "suggested_questions": [], - "suggested_questions_after_answer": {"enabled": False}, - "speech_to_text": {"enabled": False}, - "text_to_speech": {"enabled": False, "language": "", "voice": ""}, - "retriever_resource": {"enabled": False}, - "annotation_reply": {"enabled": False}, - "more_like_this": {"enabled": False}, - "user_input_form": [ - { - "paragraph": { - "label": "文案内容", - "max_length": 33024, - "options": [], - "required": True, - "type": "paragraph", - "variable": "content", - } - } - ], - "sensitive_word_avoidance": {"enabled": False}, - "file_upload": { - "image": { - "enabled": False, - "number_limits": 3, - "transfer_methods": ["local_file", "remote_url"], - } - }, - "system_parameters": {"image_file_size_limit": "10"}, - } - - try: - create_json_file(api_data) - except Exception as e: - logging.error(f"程序执行失败: {e}") diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc deleted file mode 100644 index 12f81e6..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/task_instance.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/task_instance.py deleted file mode 100644 index 9e95791..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/task_instance.py +++ /dev/null @@ -1,53 +0,0 @@ -from abc import ABC - - -# class WorkflowDifyAPI和chatDifyApi和completionDifyApi -# dify_app_mode_type :workflow, chat, completion - - -class TaskInstance(ABC): - def __init__(self, base_url, dify_app_sks, dify_app_mode_type): - self.base_url = base_url - self.dify_app_sks = dify_app_sks - self.dify_app_mode_type = dify_app_mode_type - - def get_task_instance(self, task_id: str): - """ - 根据dify_app_mode_type返回相应的API实例 - - Args: - task_id: 任务ID - - Returns: - 返回对应的API实例 - - Raises: - ValueError: 当dify_app_mode_type无效时抛出异常 - """ - from src.workflow.workflow_server import WorkflowDifyAPI - from src.completion.completion_server import CompletionDifyAPI - from src.chat.chat_server import ChatDifyAPI - - # 使用字典映射提高代码灵活性和可维护性 - api_classes = { - "workflow": WorkflowDifyAPI, - "chat": ChatDifyAPI, - "completion": CompletionDifyAPI, - } - - # 检查mode_type是否有效 - if self.dify_app_mode_type.lower() not in api_classes: - supported_types = ", ".join(api_classes.keys()) - raise ValueError( - f"不支持的dify_app_mode_type: {self.dify_app_mode_type},支持的类型: {supported_types}" - ) - - # 获取对应的API类 - api_class = api_classes[self.dify_app_mode_type.lower()] - - # 这里假设所有API类都接受相同的参数集 - # 如果各API类构造函数参数不同,需要针对每种类型单独处理 - return api_class( - self.base_url, - self.dify_app_sks, - ) diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/tool_translation.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/tool_translation.cpython-312.pyc deleted file mode 100644 index 2085820..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/tool_translation.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/translator.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/translator.cpython-312.pyc deleted file mode 100644 index 2a681ce..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/translator.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/tool_translation.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/tool_translation.py deleted file mode 100644 index 7361457..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/tool_translation.py +++ /dev/null @@ -1,153 +0,0 @@ -import os -import sys -from typing import Dict, Any, Optional - - -# 导入翻译函数 -from .translator import translate - - -class TranslationService: - """翻译服务类,用于处理各种翻译需求""" - - @staticmethod - def create_prompt( - content: str, - target_lang: str, - use_case: str, - style: str, - prompt_type: str = "general", - keep_terms_desc: str = "核心术语", - ) -> str: - """ - 创建翻译提示 - - Args: - content: 待翻译内容 - target_lang: 目标语言 - use_case: 使用场景 - style: 翻译风格 - prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description" - keep_terms_desc: 保留术语的描述 - - Returns: - str: 格式化的翻译提示 - """ - if prompt_type == "tool_name": - keep_terms_desc = "核心术语(如 小写,词语需要用下划线连接)" - elif prompt_type == "tool_description": - keep_terms_desc = "核心术语(这是一段话)" - - return f""" -角色:专业本地化翻译专家 -任务:将以下内容翻译为{target_lang}(目标用途:{use_case}) -要求: -1. 仅返回译文,不含解释或原文; -2. 保留{keep_terms_desc}; -3. 符合{style}风格; -4. 特殊符号保持原样。 - -示例输出格式: -Translated Text - -待翻译内容: -{content} -""" - - @staticmethod - def translate_text( - content: str, - target_lang: str, - use_case: str = "", - style: str = "正式且符合技术品牌调性", - prompt_type: str = "general", - ) -> Dict[str, Any]: - """ - 翻译文本 - - Args: - content: 待翻译内容 - target_lang: 目标语言 - use_case: 使用场景,默认为空 - style: 翻译风格,默认为"正式且符合技术品牌调性" - prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description" - - Returns: - Dict: 包含翻译结果的字典 - """ - prompt = TranslationService.create_prompt( - content=content, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type=prompt_type, - ) - - try: - result = translate(prompt, target_lang) - return result - except Exception as e: - print(f"翻译出错: {str(e)}") - return {"translated_text": "", "error": str(e)} - - @staticmethod - def translate_tool_name( - name: str, - target_lang: str = "英语", - use_case: str = "工具名称", - style: str = "正式且符合技术品牌调性,大模型能理解", - ) -> str: - """ - 翻译工具名称的便捷方法 - - Returns: - str: 翻译后的工具名称 - """ - result = TranslationService.translate_text( - content=name, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type="tool_name", - ) - return result.get("translated_text", "") - - @staticmethod - def translate_tool_description( - description: str, - target_lang: str = "英语", - use_case: str = "工具描述", - style: str = "正式且符合技术品牌调性,大模型能理解", - ) -> str: - """ - 翻译工具描述的便捷方法 - - Returns: - str: 翻译后的工具描述 - """ - result = TranslationService.translate_text( - content=description, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type="tool_description", - ) - return result.get("translated_text", "") - - -def translation_example(): - """翻译功能使用示例""" - - # 示例1: 翻译工具名称 - tool_name = "万川AI新媒体平台【测试环境】" - translated_name = TranslationService.translate_tool_name(tool_name) - print(f"工具名称翻译: {translated_name}") - - # 示例2: 翻译工具描述 - description = "21日,辛柏青发布讣告宣布妻子朱媛媛抗癌五年后离世。此前在一次路演现场,当观众问及朱媛媛时辛柏青2秒停顿藏着" - translated_desc = TranslationService.translate_tool_description(description) - print(f"工具描述翻译: {translated_desc}") - - -if __name__ == "__main__": - translation_example() diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/translator.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/translator.py deleted file mode 100644 index b0b777b..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/translator.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import requests -import json -from dotenv import load_dotenv - -# 加载环境变量 -load_dotenv() -# ========== 模型相关 ========== -# 从.env文件获取模型API配置 -BASE_URL = os.getenv("BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1") -API_KEY = os.getenv("OPENAI_API_KEY", "sk-c5a912a6bc8e4c9cbdbdf68232352a03") -TEMPERATURE = float(os.getenv("MODEL_TEMPERATURE", "0.7")) - - -def translate(content, target_language): - """ - 翻译文本内容到目标语言 - - :param content: 要翻译的内容 - :param target_language: 目标语言,如'en'(英语), 'zh'(中文), 'ja'(日语), 'fr'(法语)等 - :return: 翻译后的内容,如果翻译失败则返回原文和错误信息 - """ - if not content or not target_language: - return {"error": "内容或目标语言不能为空", "translated_text": content} - - # 确保API密钥已设置 - if not API_KEY: - return {"error": "API密钥未设置,请检查.env文件", "translated_text": content} - - try: - # 构建API请求头 - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {API_KEY}", - } - - # 构建翻译提示 - prompt = f"请将以下内容翻译成{target_language},只返回翻译结果,不要包含任何解释或原文:\n\n{content}" - - # 构建API请求体 - data = { - "model": "qwen-max", # 使用通义千问模型,可以根据实际需要更改 - "messages": [{"role": "user", "content": prompt}], - "temperature": TEMPERATURE, - } - - # 发送API请求 - response = requests.post( - f"{BASE_URL}/chat/completions", headers=headers, json=data - ) - - # 解析响应 - if response.status_code == 200: - result = response.json() - translated_text = result["choices"][0]["message"]["content"].strip() - return {"translated_text": translated_text} - else: - error_message = ( - f"翻译失败,状态码: {response.status_code}, 响应: {response.text}" - ) - return {"error": error_message, "translated_text": content} - - except Exception as e: - return {"error": f"翻译过程中发生错误: {str(e)}", "translated_text": content} diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index bfa3884..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/workflow_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/workflow_server.cpython-312.pyc deleted file mode 100644 index 352df89..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/workflow_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/workflow_server.py b/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/workflow_server.py deleted file mode 100644 index 8449368..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/workflow_server.py +++ /dev/null @@ -1,175 +0,0 @@ -import requests -from abc import ABC -import logging -import json -import re - -logger = logging.getLogger(__name__) - -import pypinyin - - -def pinyin_to_camel(pinyin): - """ - 将拼音列表转换为驼峰命名 - 例如: ['ni', 'hao', 'a'] -> 'NiHaoA' - 所有非字母数字字符会被替换为下划线 - """ - # 使用正则表达式将所有非字母数字字符(包括所有标点符号)替换为下划线 - cleaned = re.sub(r'[^\w\s]', '_', pinyin) - # 将空格也替换为下划线 - cleaned = re.sub(r'\s+', '_', cleaned) - # 移除连续的下划线并去除首尾下划线 - cleaned = re.sub(r'_+', '_', cleaned).strip('_') - - # 转换为拼音并生成驼峰命名 - pinyin_list = pypinyin.lazy_pinyin(cleaned) - return "tool_" + "".join(word.capitalize() for word in pinyin_list) - - -class WorkflowDifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - userId="pp666", - files=None, - ): - url = f"{self.dify_base_url}/workflows/run" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - logger.info("Sending data to Dify API: %s", data) - logger.info("Sending headers to Dify API: %s", headers) - logger.info("Sending url to Dify API: %s", url) - if conversation_id: - data["conversation_id"] = conversation_id - if files: - files_data = [] - for file_info in files: - file_path = file_info.get("path") - transfer_method = file_info.get("transfer_method") - if transfer_method == "local_file": - files_data.append(("file", open(file_path, "rb"))) - elif transfer_method == "remote_url": - pass - response = requests.post( - url, - headers=headers, - data=data, - files=files_data, - stream=response_mode == "streaming", - ) - else: - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - response.raise_for_status() - if response_mode == "streaming": - for line in response.iter_lines(): - if line: - if line.startswith(b"data:"): - try: - json_data = json.loads(line[5:].decode("utf-8")) - yield json_data - except json.JSONDecodeError: - print(f"Error decoding JSON: {line}") - else: - return response.json() - - def upload_file(self, api_key, file_path, user="pp666"): - - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - files = {"file": open(file_path, "rb")} - data = {"user": user} - response = requests.post(url, headers=headers, files=files, data=data) - response.raise_for_status() - return response.json() - - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - - from src.utils.tool_translation import TranslationService - - response_map = response.json() - - # 翻译工具名称 - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - - # 翻译工具描述 - # tool_description = response_map.get("description") - # if tool_description: - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = ( - # f"{tool_description} ({translated_description})" - # ) - - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/parameters" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/PKG-INFO b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/PKG-INFO deleted file mode 100644 index 450e42d..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-demp-tool-server-dify-to-mcp-test -Version: 0.0.15 -Summary: 这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。 -Requires-Python: >=3.10 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: mcp>=1.1.2 -Requires-Dist: omegaconf>=2.3.0 -Requires-Dist: pip>=24.3.1 -Requires-Dist: python-dotenv>=1.0.1 -Requires-Dist: requests -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/README.md b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/dify-api.md b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/dify-api.md deleted file mode 100644 index 2e3d1a2..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/dify-api.md +++ /dev/null @@ -1,267 +0,0 @@ -# Dify Workflow API 文档 - -## 概述 - -Workflow 应用无会话支持,适合用于翻译、文章写作、总结等 AI 场景。 - -**Base URL:** `http://192.168.2.236:3001/v1` - -## 认证 - -所有 API 请求需在 HTTP Header 中包含 API-Key: - -``` -Authorization: Bearer {API_KEY} -``` - -## 1. 执行 Workflow - -**接口:** `POST /workflows/run` - -### 请求参数 - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| inputs | object | ✓ | 允许传入 App 定义的各变量值。inputs 参数包含了多组键值对,每组的键对应一个特定变量,每组的值则是该变量的具体值 | -| response_mode | string | ✓ | 返回响应模式:`streaming`(流式,推荐)或`blocking`(阻塞,Cloudflare 限制 100 秒超时) | -| user | string | ✓ | 用户标识,用于定义终端用户的身份,方便检索、统计。需保证用户标识在应用内唯一 | - -### 文件列表类型变量 - -| 字段 | 类型 | 说明 | -|------|------|------| -| type | string | 文件类型:document/image/audio/video/custom | -| transfer_method | string | 传递方式:remote_url(图片地址)或local_file(上传文件) | -| url | string | 图片地址(仅当传递方式为 remote_url 时) | -| upload_file_id | string | 上传文件 ID(仅当传递方式为 local_file 时) | - -**支持的文件类型:** -- **document**: TXT, MD, PDF, HTML, XLSX, DOCX, CSV, PPTX, XML, EPUB -- **image**: JPG, PNG, GIF, WEBP, SVG -- **audio**: MP3, WAV, M4A, WEBM, AMR -- **video**: MP4, MOV, MPEG - -### 响应格式 - -#### CompletionResponse(阻塞模式) - -| 字段 | 类型 | 说明 | -|------|------|------| -| workflow_run_id | string | workflow 执行 ID | -| task_id | string | 任务 ID,用于请求跟踪和停止响应接口 | -| data.id | string | workflow 执行 ID | -| data.workflow_id | string | 关联 Workflow ID | -| data.status | string | 执行状态:running/succeeded/failed/stopped | -| data.outputs | json | 可选,输出内容 | -| data.error | string | 可选,错误原因 | -| data.elapsed_time | float | 可选,耗时(秒) | -| data.total_tokens | int | 可选,总使用 tokens | -| data.total_steps | int | 总步数,默认 0 | -| data.created_at | timestamp | 开始时间 | -| data.finished_at | timestamp | 结束时间 | - -#### ChunkCompletionResponse(流式模式) - -**事件类型:** - -**1. workflow_started** - -| 字段 | 类型 | 说明 | -|------|------|------| -| task_id | string | 任务 ID | -| workflow_run_id | string | workflow 执行 ID | -| event | string | 固定为 workflow_started | -| data.id | string | workflow 执行 ID | -| data.workflow_id | string | 关联 Workflow ID | -| data.sequence_number | int | 自增序号,从 1 开始 | -| data.created_at | timestamp | 开始时间 | - -**2. node_started** - -| 字段 | 类型 | 说明 | -|------|------|------| -| data.node_id | string | 节点 ID | -| data.node_type | string | 节点类型 | -| data.title | string | 节点名称 | -| data.index | int | 执行序号 | -| data.predecessor_node_id | string | 前置节点 ID | -| data.inputs | object | 节点中所有使用到的前置节点变量内容 | - -**3. node_finished** - -| 字段 | 类型 | 说明 | -|------|------|------| -| data.node_id | string | 节点 ID | -| data.status | string | 执行状态:running/succeeded/failed/stopped | -| data.outputs | json | 可选,输出内容 | -| data.error | string | 可选,错误原因 | -| data.elapsed_time | float | 可选,耗时(秒) | -| data.execution_metadata.total_tokens | int | 可选,总使用 tokens | -| data.execution_metadata.total_price | decimal | 可选,总费用 | -| data.execution_metadata.currency | string | 可选,货币(USD/RMB) | - -**4. workflow_finished** - -| 字段 | 类型 | 说明 | -|------|------|------| -| data.status | string | 执行状态:running/succeeded/failed/stopped | -| data.outputs | json | 可选,输出内容 | -| data.error | string | 可选,错误原因 | -| data.total_tokens | int | 可选,总使用 tokens | -| data.finished_at | timestamp | 结束时间 | - -**5. tts_message** - TTS 音频流事件(Mp3 格式,base64 编码) - -| 字段 | 类型 | 说明 | -|------|------|------| -| task_id | string | 任务 ID | -| message_id | string | 消息唯一 ID | -| audio | string | Base64 编码的音频内容 | -| created_at | int | 创建时间戳 | - -**6. tts_message_end** - TTS 音频流结束 - -**7. ping** - 每 10 秒心跳保活 - -### 错误码 - -| 状态码 | 错误码 | 说明 | -|--------|--------|------| -| 400 | invalid_param | 传入参数异常 | -| 400 | app_unavailable | App 配置不可用 | -| 400 | provider_not_initialize | 无可用模型凭据配置 | -| 400 | provider_quota_exceeded | 模型调用额度不足 | -| 400 | workflow_request_error | workflow 执行失败 | -| 500 | - | 服务内部异常 | - ---- - -## 2. 查询 Workflow 执行状态 - -**接口:** `GET /workflows/run/:workflow_run_id` - -### 响应字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | string | workflow 执行 ID | -| workflow_id | string | 关联的 Workflow ID | -| status | string | 执行状态:running/succeeded/failed/stopped | -| inputs | json | 任务输入内容 | -| outputs | json | 任务输出内容 | -| error | string | 错误原因 | -| total_steps | int | 任务执行总步数 | -| total_tokens | int | 任务执行总 tokens | -| created_at | timestamp | 任务开始时间 | -| finished_at | timestamp | 任务结束时间 | -| elapsed_time | float | 耗时(秒) | - ---- - -## 3. 停止 Workflow 执行 - -**接口:** `POST /workflows/tasks/:task_id/stop` - -### 请求参数 - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| user | string | ✓ | 用户标识,必须和发送消息接口传入 user 保持一致 | - ---- - -## 4. 上传文件 - -**接口:** `POST /files/upload` - -### 请求参数 - -| 参数 | 类型 | 必填 | 说明 | -|------|------|------|------| -| file | file | ✓ | 要上传的文件 | -| user | string | ✓ | 用户标识 | - -### 响应字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | uuid | ID | -| name | string | 文件名 | -| size | int | 文件大小(byte) | -| extension | string | 文件后缀 | -| mime_type | string | 文件 mime-type | -| created_by | uuid | 上传人 ID | -| created_at | timestamp | 上传时间 | - -### 错误码 - -| 状态码 | 错误码 | 说明 | -|--------|--------|------| -| 400 | no_file_uploaded | 必须提供文件 | -| 413 | file_too_large | 文件太大 | -| 415 | unsupported_file_type | 不支持的文件类型 | -| 503 | s3_connection_failed | 无法连接到 S3 服务 | - ---- - -## 5. 获取 Workflow 日志 - -**接口:** `GET /workflows/logs` - -### 查询参数 - -| 参数 | 类型 | 说明 | 默认值 | -|------|------|------|--------| -| keyword | string | 关键字 | - | -| status | string | 执行状态:succeeded/failed/stopped | - | -| page | int | 当前页码 | 1 | -| limit | int | 每页条数 | 20 | - -### 响应字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| page | int | 当前页码 | -| limit | int | 每页条数 | -| total | int | 总条数 | -| has_more | bool | 是否还有更多数据 | -| data[].workflow_run.id | string | 标识 | -| data[].workflow_run.status | string | 执行状态 | -| data[].workflow_run.elapsed_time | float | 耗时(秒) | -| data[].workflow_run.total_tokens | int | 消耗的 token 数量 | - ---- - -## 6. 获取应用信息 - -**接口:** `GET /info` - -### 响应字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| name | string | 应用名称 | -| description | string | 应用描述 | -| tags | array[string] | 应用标签 | - ---- - -## 7. 获取应用参数 - -**接口:** `GET /parameters` - -### 响应字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| user_input_form[].text-input.label | string | 控件展示标签名 | -| user_input_form[].text-input.variable | string | 控件 ID | -| user_input_form[].text-input.required | bool | 是否必填 | -| user_input_form[].text-input.default | string | 默认值 | -| file_upload.image.enabled | bool | 是否开启 | -| file_upload.image.number_limits | int | 图片数量限制,默认 3 | -| file_upload.image.transfer_methods | array[string] | 传递方式:remote_url/local_file | -| system_parameters.file_size_limit | int | 文档上传大小限制(MB) | -| system_parameters.image_file_size_limit | int | 图片文件上传大小限制(MB) | -| system_parameters.audio_file_size_limit | int | 音频文件上传大小限制(MB) | -| system_parameters.video_file_size_limit | int | 视频文件上传大小限制(MB) | \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/.gitkeep b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/.gitkeep deleted file mode 100644 index dce7f8c..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/.gitkeep +++ /dev/null @@ -1,2 +0,0 @@ -# 此文件用于确保 logs 目录被 Git 跟踪 -# 日志文件会自动生成在此目录中 \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log deleted file mode 100644 index b9251e7..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/lzwcai_demp_tool_server_dify_to_mcp_test.log +++ /dev/null @@ -1,156 +0,0 @@ -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:05:23 -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 -2026-01-19 16:05:23 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ -2026-01-19 16:05:23 - __main__ - INFO - [main.py:50] - ================================================================================ -2026-01-19 16:05:23 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 -2026-01-19 16:05:23 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 16:05:23 - __main__ - INFO - [main.py:53] - ================================================================================ -2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters -2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'} -2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} -2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 -2026-01-19 16:05:25 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 16:05:25 - __main__ - INFO - [main.py:68] - 传输模式: stdio -2026-01-19 16:05:25 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'} -2026-01-19 16:05:26 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 16:05:26 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}] -2026-01-19 16:06:14 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['remote_url'], 'allowed_file_extensions': [], 'is_list': False}] -2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]} -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'} -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file,值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3) -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节) -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB) -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: e2ec6720-9cf9-4a64-aa31-102e787a343b) -2026-01-19 16:06:14 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: e2ec6720-9cf9-4a64-aa31-102e787a343b, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件 -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件 -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象 -2026-01-19 16:06:14 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'} -2026-01-19 16:06:14 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}} -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'} -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run -2026-01-19 16:06:14 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} 400 BAD REQUEST -2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:152] - API request failed with status 400 -2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:153] - Response content: {"code": "invalid_param", "message": "File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx", "status": 400} - -2026-01-19 16:06:14 - src.workflow.workflow_server - ERROR - [workflow_server.py:154] - Request data: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'e2ec6720-9cf9-4a64-aa31-102e787a343b'}}, 'response_mode': 'streaming', 'user': 'pp666'} -2026-01-19 16:06:14 - src.create_mcp - ERROR - [create_mcp.py:178] - 工具 tool_HeTongShenChaGongZuoLiu 调用 Dify API 失败: [400] invalid_param: File validation failed for file: eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 16:21:31 -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 -2026-01-19 16:21:31 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ -2026-01-19 16:21:31 - __main__ - INFO - [main.py:50] - ================================================================================ -2026-01-19 16:21:31 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 -2026-01-19 16:21:31 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 16:21:31 - __main__ - INFO - [main.py:53] - ================================================================================ -2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.168.11.24:3001/v1/parameters -2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76'} -2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} -2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 -2026-01-19 16:21:33 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 16:21:33 - __main__ - INFO - [main.py:68] - 传输模式: stdio -2026-01-19 16:21:33 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.168.11.24:3001/v1', 'app_sks': ['app-RM7IGxvdq48ixu1JXFiUZh76'], 'mode_type': 'workflow', 'transport': 'stdio'} -2026-01-19 16:21:37 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type ListToolsRequest -2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:104] - 工具 tool_HeTongShenChaGongZuoLiu 的 parameters 数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'file': {'variable': 'contact_file', 'label': 'contact_file', 'type': 'file', 'max_length': 4096, 'required': True, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 16:21:37 - src.create_mcp - INFO - [create_mcp.py:110] - 工具 tool_HeTongShenChaGongZuoLiu 提取的文件字段: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}] -2026-01-19 16:21:46 - mcp.server.lowlevel.server - INFO - [server.py:619] - Processing request of type CallToolRequest -2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:144] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段信息: [{'variable': 'contact_file', 'label': 'contact_file', 'required': True, 'max_length': 4096, 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_extensions': [], 'is_list': False}] -2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:145] - 工具 tool_HeTongShenChaGongZuoLiu 调用前的 arguments: {'contact_file': [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}]} -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:64] - 工具 tool_HeTongShenChaGongZuoLiu 的文件字段变量名: {'contact_file'} -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:73] - 工具 tool_HeTongShenChaGongZuoLiu: 发现文件字段 contact_file,值: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:168] - 工具 tool_HeTongShenChaGongZuoLiu: 处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:85] - 工具 tool_HeTongShenChaGongZuoLiu: 准备处理文件列表: [{'url': 'http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx'}] -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:322] - 从URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 提取的文件扩展名: docx -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:333] - URL http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx 匹配文件类型: document -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:96] - 工具 tool_HeTongShenChaGongZuoLiu: 自动识别文件类型为 document -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:242] - 开始上传远程文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:400] - 开始处理文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:86] - 正在下载文件: http://192.168.11.24:9000/lzwcai/upload/2026-01-19/eb25b2b233a8719f680d4b0b9c5a80e7/eb25b2b233a8719f680d4b0b9c5a80e7.docx (尝试 1/3) -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:100] - 文件下载成功: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 20445 字节) -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:189] - 准备上传文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx (大小: 0.02 MB) -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:220] - 文件上传成功: eb25b2b233a8719f680d4b0b9c5a80e7.docx (ID: c09a25e0-9d92-4b8d-b098-a59d6be10921) -2026-01-19 16:21:46 - src.utils.upload_file - INFO - [upload_file.py:417] - 已清理临时文件: C:\Users\HiWin10\AppData\Local\Temp\eb25b2b233a8719f680d4b0b9c5a80e7.docx -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:255] - 文件上传成功 - ID: c09a25e0-9d92-4b8d-b098-a59d6be10921, 名称: eb25b2b233a8719f680d4b0b9c5a80e7.docx, 大小: 20445 bytes -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:274] - 文件预处理完成,共处理 1 个文件 -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:111] - 工具 tool_HeTongShenChaGongZuoLiu: 文件预处理完成,成功上传 1 个文件 -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:219] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 是 file 类型,输出单个对象 -2026-01-19 16:21:46 - src.create_mcp_utils - INFO - [create_mcp_utils.py:221] - 工具 tool_HeTongShenChaGongZuoLiu: 字段 contact_file 最终值: {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'} -2026-01-19 16:21:46 - src.create_mcp - INFO - [create_mcp.py:149] - 工具 tool_HeTongShenChaGongZuoLiu 处理后的 arguments: {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}} -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:131] - Sending data to Dify API: {'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'} -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:132] - Sending headers to Dify API: {'Authorization': 'Bearer app-RM7IGxvdq48ixu1JXFiUZh76', 'Content-Type': 'application/json'} -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:133] - Sending url to Dify API: http://192.168.11.24:3001/v1/workflows/run -2026-01-19 16:21:46 - src.workflow.workflow_server - INFO - [workflow_server.py:148] - Response1:{'inputs': {'contact_file': {'transfer_method': 'local_file', 'type': 'document', 'upload_file_id': 'c09a25e0-9d92-4b8d-b098-a59d6be10921'}}, 'response_mode': 'streaming', 'user': 'pp666'} 200 OK -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:39 -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 -2026-01-19 22:09:39 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ -2026-01-19 22:09:39 - __main__ - INFO - [main.py:50] - ================================================================================ -2026-01-19 22:09:39 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 -2026-01-19 22:09:39 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:09:39 - __main__ - INFO - [main.py:53] - ================================================================================ -2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters -2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} -2026-01-19 22:09:54 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:09:55 -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 -2026-01-19 22:09:55 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ -2026-01-19 22:09:55 - __main__ - INFO - [main.py:50] - ================================================================================ -2026-01-19 22:09:55 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 -2026-01-19 22:09:55 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:09:55 - __main__ - INFO - [main.py:53] - ================================================================================ -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:213] - ================================================================================ -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:214] - 日志系统初始化完成 - 2026-01-19 22:10:01 -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:215] - 日志级别: INFO -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:216] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:217] - 控制台输出: False -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:218] - 文件输出: True -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:219] - 文件轮转: 最大10MB, 保留5个备份 -2026-01-19 22:10:01 - src.utils.logger_config - INFO - [logger_config.py:220] - ================================================================================ -2026-01-19 22:10:01 - __main__ - INFO - [main.py:50] - ================================================================================ -2026-01-19 22:10:01 - __main__ - INFO - [main.py:51] - Dify MCP 服务器启动 -2026-01-19 22:10:01 - __main__ - INFO - [main.py:52] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_demp_tool_server_dify_to_mcp_test\lzwcai_demp_tool_server_dify_to_mcp_test\logs\lzwcai_demp_tool_server_dify_to_mcp_test.log -2026-01-19 22:10:01 - __main__ - INFO - [main.py:53] - ================================================================================ -2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters -2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} -2026-01-19 22:10:06 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:322] - 调用 /parameters API: http://192.167.30.8:3001/v1/parameters -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:328] - /parameters API 响应状态码: 200 -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:323] - 请求头: {'Authorization': 'Bearer app-0dlxPuFY8HBXo6PpoeStQCjA'} -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:333] - /parameters API 响应数据: {'opening_statement': '', 'suggested_questions': [], 'suggested_questions_after_answer': {'enabled': False}, 'speech_to_text': {'enabled': False}, 'text_to_speech': {'enabled': False, 'voice': '', 'language': ''}, 'retriever_resource': {'enabled': True}, 'annotation_reply': {'enabled': False}, 'more_like_this': {'enabled': False}, 'user_input_form': [{'paragraph': {'variable': 'contentText', 'label': '订单内容', 'type': 'paragraph', 'max_length': 48000, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['image', 'document'], 'allowed_file_extensions': []}}, {'file': {'variable': 'contentFile', 'label': '文件内容', 'type': 'file', 'max_length': 5, 'required': False, 'options': [], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'allowed_file_types': ['document', 'image'], 'allowed_file_extensions': []}}], 'sensitive_word_avoidance': {'enabled': False}, 'file_upload': {'image': {'enabled': False, 'number_limits': 3, 'transfer_methods': ['local_file', 'remote_url']}, 'enabled': False, 'allowed_file_types': ['image'], 'allowed_file_extensions': ['.JPG', '.JPEG', '.PNG', '.GIF', '.WEBP', '.SVG'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], 'number_limits': 3, 'fileUploadConfig': {'file_size_limit': 15, 'batch_count_limit': 5, 'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'workflow_file_upload_limit': 10}}, 'system_parameters': {'image_file_size_limit': 10, 'video_file_size_limit': 100, 'audio_file_size_limit': 50, 'file_size_limit': 15, 'workflow_file_upload_limit': 10}} -2026-01-19 22:10:10 - src.workflow.workflow_server - INFO - [workflow_server.py:324] - 请求参数: {'user': 'pp666'} -2026-01-19 22:10:13 - __main__ - INFO - [main.py:68] - 传输模式: stdio -2026-01-19 22:10:13 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'} -2026-01-19 22:10:17 - __main__ - INFO - [main.py:68] - 传输模式: stdio -2026-01-19 22:10:17 - __main__ - INFO - [main.py:69] - 配置参数: {'base_url': 'http://192.167.30.8:3001/v1', 'app_sks': ['app-0dlxPuFY8HBXo6PpoeStQCjA'], 'mode_type': 'workflow', 'transport': 'stdio'} diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO deleted file mode 100644 index bb8c4fe..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-demp-tool-server-dify-to-mcp-test -Version: 0.1.2 -Summary: 这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。 -Requires-Python: >=3.10 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: mcp>=1.1.2 -Requires-Dist: omegaconf>=2.3.0 -Requires-Dist: pip>=24.3.1 -Requires-Dist: python-dotenv>=1.0.1 -Requires-Dist: requests -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/SOURCES.txt b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/SOURCES.txt deleted file mode 100644 index 5b057d9..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/SOURCES.txt +++ /dev/null @@ -1,27 +0,0 @@ -README.md -pyproject.toml -setup.cfg -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/PKG-INFO -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/SOURCES.txt -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/dependency_links.txt -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/entry_points.txt -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/requires.txt -lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/top_level.txt -src/__init__.py -src/create_mcp.py -src/create_mcp_update.py -src/create_mcp_utils.py -src/chat/__init__.py -src/chat/chat_server.py -src/completion/completion_server.py -src/completion/test.py -src/core/__init__.py -src/core/core_server.py -src/difyTaskCall/task_instance.py -src/utils/dify_workflow_schema.py -src/utils/logger_config.py -src/utils/tool_translation.py -src/utils/translator.py -src/utils/upload_file.py -src/workflow/__init__.py -src/workflow/workflow_server.py \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/dependency_links.txt b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/entry_points.txt b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/entry_points.txt deleted file mode 100644 index ebcb63a..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -lzwcai-demp-tool-server-dify-to-mcp-test = src.create_mcp:run_main diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/requires.txt b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/requires.txt deleted file mode 100644 index 5d3b552..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/requires.txt +++ /dev/null @@ -1,7 +0,0 @@ -httpx>=0.28.1 -mcp>=1.1.2 -omegaconf>=2.3.0 -pip>=24.3.1 -python-dotenv>=1.0.1 -requests -pypinyin>=0.54.0 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/top_level.txt b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/top_level.txt deleted file mode 100644 index 85de9cf..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -src diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py deleted file mode 100644 index 959616b..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env python3 -""" -主入口文件 -用于启动 Dify MCP 服务器,并配置命令行参数 -""" -import os -import sys -import logging - -# 导入日志配置 -from src.utils.logger_config import setup_logging, get_logger - -# Mock 配置参数 -def setup_mock_arguments(): - """ - 设置模拟命令行参数 - 这些参数可以根据实际需求进行修改 - """ - # 默认配置 - default_config = { - "base_url": "http://192.168.2.236:3001/v1", - "app_sks": ["app-IfJayK9uu5oTo54Rpr2AS7wl"], - "mode_type": "workflow", - "transport": "stdio" - } - - # 如果没有提供命令行参数,则添加默认参数 - if len(sys.argv) == 1: - sys.argv.extend([ - "--base-url", default_config["base_url"], - "--app-sks", *default_config["app_sks"], - "--mode-type", default_config["mode_type"] - ]) - - return default_config - - -def main(): - """ - 主函数:设置命令行参数并启动服务器 - """ - # 初始化日志系统(MCP模式下禁用控制台输出,避免干扰stdio通信) - try: - log_file_path = setup_logging( - log_level=logging.INFO, - console_output=False, # MCP模式下禁用控制台输出 - file_output=True - ) - logger = get_logger(__name__) - logger.info("=" * 80) - logger.info("Dify MCP 服务器启动") - logger.info(f"日志文件: {log_file_path}") - logger.info("=" * 80) - except Exception as e: - # 如果日志初始化失败,使用stderr输出错误 - print(f"[ERROR] 日志系统初始化失败: {e}", file=sys.stderr) - - # 设置模拟命令行参数 - config = setup_mock_arguments() - - # 导入并运行 MCP 服务器 - try: - from src.create_mcp import run_main - - # 获取传输模式 - transport_mode = config.get("transport", "stdio") - - logger.info(f"传输模式: {transport_mode}") - logger.info(f"配置参数: {config}") - - # 运行服务器(不输出额外信息,避免干扰 STDIO 通信) - run_main(transport=transport_mode) - - except ImportError as e: - logger.error(f"导入错误: {e}", exc_info=True) - print(f"[ERROR] 导入错误: {e}", file=sys.stderr) - print("请确保已正确安装所有依赖包", file=sys.stderr) - sys.exit(1) - except Exception as e: - logger.error(f"运行错误: {e}", exc_info=True) - print(f"[ERROR] 运行错误: {e}", file=sys.stderr) - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml deleted file mode 100644 index af294ed..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml +++ /dev/null @@ -1,32 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "lzwcai-demp-tool-server-dify-to-mcp-test" -version = "0.1.2" -description = "这是一个Dify to MCP的集成工具;通过Dify的API接口,将Dify的模型部署到MCP平台,并进行推理。" -requires-python = ">=3.10" -dependencies = [ - "httpx>=0.28.1", - "mcp>=1.1.2", - "omegaconf>=2.3.0", - "pip>=24.3.1", - "python-dotenv>=1.0.1", - "requests", - "pypinyin>=0.54.0", -] - -[tool.setuptools] -packages = {find = {where = ["."], include = ["src*"]}} -include-package-data = true - -[project.scripts] -lzwcai-demp-tool-server-dify-to-mcp-test = "src.create_mcp:run_main" - -[tool.hatch.build.targets.wheel] -packages = ["src"] - -[tool.setuptools.package-data] -"*" = ["*.env"] -"src" = ["**/*.env"] \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/setup.cfg b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/setup.cfg deleted file mode 100644 index 8bfd5a1..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 12ecc7c..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc deleted file mode 100644 index 3eb52a1..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc deleted file mode 100644 index aec247e..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index bc6ec2a..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/chat_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/chat_server.cpython-312.pyc deleted file mode 100644 index 48e3fa7..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/chat_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/chat_server.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/chat_server.py deleted file mode 100644 index 7d3e608..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/chat_server.py +++ /dev/null @@ -1,7 +0,0 @@ -class ChatDifyAPI: - def __init__(self, base_url: str, app_sks: str): - self.base_url = base_url - self.app_sks = app_sks - - def process_task(self, task_id: str, **kwargs): - pass diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/__pycache__/completion_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/__pycache__/completion_server.cpython-312.pyc deleted file mode 100644 index 93da592..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/__pycache__/completion_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/completion_server.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/completion_server.py deleted file mode 100644 index ae62c6f..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/completion_server.py +++ /dev/null @@ -1,203 +0,0 @@ -import requests -from abc import ABC -import logging -import json -import pypinyin -from src.utils.logger_config import get_logger - -logger = get_logger(__name__) - - -def pinyin_to_camel(pinyin): - """ - 将拼音列表转换为驼峰命名 - 例如: ['ni', 'hao', 'a'] -> 'NiHaoA' - """ - pinyin_list = pypinyin.lazy_pinyin(pinyin) - return "tool_" + "".join(word.capitalize() for word in pinyin_list) - - -class CompletionDifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - userId="pp666", - files=None, - ): - url = f"{self.dify_base_url}/completion-messages" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - if conversation_id: - data["conversation_id"] = conversation_id - - if response_mode == "streaming": - response = requests.post(url, headers=headers, json=data, stream=True) - - # 处理流式响应 - full_answer = "" - for line in response.iter_lines(): - if line: - # 跳过 "data:" 前缀 - decoded_line = line.decode("utf-8") - if decoded_line.startswith("data:"): - try: - json_str = decoded_line[5:].strip() - data = json.loads(json_str) - if data.get("event") == "message" and "answer" in data: - # 累积完整答案 - full_answer += data["answer"] - # 这里也可以选择处理每个部分响应,例如返回生成器 - # yield data - except json.JSONDecodeError: - logger.warning(f"无法解析JSON数据: {decoded_line}") - - # 创建一个符合非流式响应格式的结果 - response_data = {"answer": full_answer} - # 处理可能包含代码块的数据 - processed_data = self.process_answer_code_block(response_data) - return processed_data - else: - response = requests.post(url, headers=headers, json=data) - response_data = response.json() - # 处理可能包含代码块的数据 - processed_data = self.process_answer_code_block(response_data) - return processed_data - - def upload_file(self, api_key, file_path, user="pp666"): - - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - files = {"file": open(file_path, "rb")} - data = {"user": user} - response = requests.post(url, headers=headers, files=files, data=data) - response.raise_for_status() - return response.json() - - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - # params = {"user": user} - response = requests.get(url, headers=headers) - response.raise_for_status() - - response_map = response.json() - # 翻译工具名称 - from src.utils.tool_translation import TranslationService - - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - - # 翻译工具描述 - # tool_description = response_map.get("description") - # if tool_description: - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = ( - # f"{tool_description} ({translated_description})" - # ) - - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - return { - "user_input_form": [ - {"string": {"variable": "query", "label": "查询内容", "required": True}} - ] - } - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - @staticmethod - def process_answer_code_block(data): - try: - # 获取answer字段 - answer = data.get("answer", "") - - # 构造符合workflow_finished格式的输出 - formatted_response = [ - {"event": "workflow_finished", "data": {"outputs": {"result": answer}}} - ] - - # 尝试处理可能的代码块 - if answer.startswith("```") and answer.endswith("```"): - try: - # 移除代码块标记并解析JSON - code_content = answer.strip("```").strip() - json_data = json.loads(code_content) - - # 如果包含description字段,用它替换answer - if "description" in json_data: - formatted_response[0]["data"]["outputs"]["result"] = json_data[ - "description" - ] - except json.JSONDecodeError: - # 如果不是有效的JSON,保留原始代码块内容 - pass - - return formatted_response - except Exception as e: - logger.warning(f"处理答案代码块时出错: {str(e)}") - # 发生错误时返回符合格式的基础响应 - return [ - { - "event": "workflow_finished", - "data": { - "outputs": { - "error": str(e), - "fallback": data.get("answer", str(data)), - } - }, - } - ] diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/test.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/test.py deleted file mode 100644 index 4664edf..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/test.py +++ /dev/null @@ -1,104 +0,0 @@ -import requests -from abc import ABC -import logging -import json - -logger = logging.getLogger(__name__) - -res = { - "event": "message", - "task_id": "49c9ea1b-7b43-475b-a680-d769fb238a45", - "id": "432ab98e-5e36-4a29-abe5-e01281c3678c", - "message_id": "432ab98e-5e36-4a29-abe5-e01281c3678c", - "mode": "completion", - "answer": '```\n{\n "description": "该API的具体功能描述暂时不明确,因为提供的API信息 \'今天打老虎啊按时啊啊\' 并不是有效的API名称或描述。请提供正确的API名称和相关输入输出信息,以便我能为其补充完善的API描述。"\n}\n```', - "metadata": { - "usage": { - "prompt_tokens": 73, - "prompt_unit_price": "0.0", - "prompt_price_unit": "0.0", - "prompt_price": "0.0", - "completion_tokens": 61, - "completion_unit_price": "0.0", - "completion_price_unit": "0.0", - "completion_price": "0.0", - "total_tokens": 134, - "total_price": "0.0", - "currency": "USD", - "latency": 1.896302318200469, - } - }, - "created_at": 1747233054, -} - - -def process_answer_code_block(data): - try: - # 获取answer字段 - answer = data.get("answer", "") - - # 检查answer是否是代码块格式 - if answer.startswith("```") and answer.endswith("```"): - # 移除代码块标记并解析JSON - code_content = answer.strip("```").strip() - json_data = json.loads(code_content) - - # 获取description字段 - if "description" in json_data: - return json_data["description"] - - # 如果不是预期格式,则返回原始answer - return data.get("answer", data) - except Exception as e: - logger.warning(f"处理答案代码块时出错: {str(e)}") - # 发生错误时返回原始数据 - return data.get("answer", data) - - -def chat_message_test( - api_key, - inputs={}, - response_mode="blocking", - conversation_id=None, - userId="pp666", - files=None, -): - url = "https://ops.lzwcai.com/v1/completion-messages" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - if conversation_id: - data["conversation_id"] = conversation_id - if response_mode == "streaming": - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - return response - else: - response = requests.post(url, headers=headers, json=data) - return response.json() - - -if __name__ == "__main__": - print("开始执行主程序") - try: - print("准备调用chat_message方法") - res = chat_message_test( - api_key="app-Ppemii3c0ROPoLvRwskgZ7Il", - inputs={"query": "今天打老虎啊按时啊啊"}, - response_mode="streaming", - userId="abc-123", - ) - print("chat_message方法调用完成") - - # 打印响应内容 - print("响应内容:", res) - # print(process_answer_code_block(res)) - except Exception as e: - print(f"执行过程中出现错误: {e}") diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/core_server.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/core_server.py deleted file mode 100644 index ac7d893..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/core_server.py +++ /dev/null @@ -1,213 +0,0 @@ -import requests -from abc import ABC -import logging -import json - -# 导入 pypinyin 用于中文转拼音 -try: - import pypinyin -except ImportError: - pypinyin = None - logging.warning("pypinyin 模块未安装,将使用简化的命名方式") - -logger = logging.getLogger(__name__) - - -def pinyin_to_camel(pinyin): - """ - 将中文名称转换为工具名称 - - 处理逻辑: - 1. 如果安装了 pypinyin,将中文转换为拼音,然后转为驼峰命名 - 2. 如果未安装 pypinyin,将所有非字母数字字符替换为下划线 - 3. 所有符号都会被替换成下划线 - - 示例: - "你好啊" -> "tool_NiHaoA" (有pypinyin) - "测试-工具" -> "tool_测试_工具" (无pypinyin) - "Hello World!" -> "tool_Hello_World_" (无pypinyin) - - Args: - pinyin: 输入的字符串(可能包含中文、英文、符号等) - - Returns: - str: 格式化后的工具名称,以 "tool_" 开头 - """ - import re - - if pypinyin is None: - # 如果 pypinyin 未安装,使用简化的命名方式 - # 将所有非字母数字字符(包括空格、符号等)替换为下划线 - cleaned = re.sub(r'[^\w]', '_', str(pinyin)) - # 移除连续的下划线 - cleaned = re.sub(r'_+', '_', cleaned) - # 移除首尾的下划线 - cleaned = cleaned.strip('_') - return "tool_" + cleaned if cleaned else "tool_unnamed" - - # 使用 pypinyin 转换中文为拼音 - pinyin_list = pypinyin.lazy_pinyin(pinyin) - - # 处理每个拼音单词 - processed_words = [] - for word in pinyin_list: - # 将所有非字母数字字符替换为下划线 - cleaned_word = re.sub(r'[^\w]', '_', word) - # 移除连续的下划线 - cleaned_word = re.sub(r'_+', '_', cleaned_word) - # 移除首尾的下划线 - cleaned_word = cleaned_word.strip('_') - - if cleaned_word: - # 首字母大写(驼峰命名) - processed_words.append(cleaned_word.capitalize()) - - # 拼接所有单词 - result = "".join(processed_words) if processed_words else "Unnamed" - return "tool_" + result - - -class DifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - logger.info(f"Dify应用参数: {dify_app_params}") - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - user="pp666", - files=None, - ): - url = f"{self.dify_base_url}/workflows/run" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": user, - } - logger.info("Sending data to Dify API: %s", data) - logger.info("Sending headers to Dify API: %s", headers) - logger.info("Sending url to Dify API: %s", url) - if conversation_id: - data["conversation_id"] = conversation_id - if files: - files_data = [] - for file_info in files: - file_path = file_info.get("path") - transfer_method = file_info.get("transfer_method") - if transfer_method == "local_file": - files_data.append(("file", open(file_path, "rb"))) - elif transfer_method == "remote_url": - pass - response = requests.post( - url, - headers=headers, - data=data, - files=files_data, - stream=response_mode == "streaming", - ) - else: - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - response.raise_for_status() - if response_mode == "streaming": - for line in response.iter_lines(): - if line: - if line.startswith(b"data:"): - try: - json_data = json.loads(line[5:].decode("utf-8")) - yield json_data - except json.JSONDecodeError: - logger.error(f"JSON解码错误: {line}") - else: - return response.json() - - def upload_file(self, api_key, file_path, user="pp666"): - - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - files = {"file": open(file_path, "rb")} - data = {"user": user} - response = requests.post(url, headers=headers, files=files, data=data) - response.raise_for_status() - return response.json() - - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - - from src.utils.tool_translation import TranslationService - - response_map = response.json() - # 翻译工具名称 - # tool_name = response_map.get("name") - # translated_name = TranslationService.translate_tool_name(tool_name) - # response_map["name"] = translated_name - # # 翻译工具描述 - # tool_description = response_map.get("description") - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = translated_description - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/parameters" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py deleted file mode 100644 index 57f818a..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py +++ /dev/null @@ -1,253 +0,0 @@ -import asyncio -import json -import os -import logging -import argparse -from abc import ABC - -import mcp.server.stdio -import mcp.types as types -import requests -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from omegaconf import OmegaConf - -# from src.workflow.workflow_server import WorkflowDifyAPI -from src.difyTaskCall.task_instance import TaskInstance -from src.utils.dify_workflow_schema import process_user_input_form, extract_file_fields -from src.create_mcp_utils import process_file_arguments -from src.utils.logger_config import get_logger -from src.workflow.workflow_server import DifyAPIError - -# 使用统一的日志配置 -logger = get_logger(__name__) - - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Dify MCP服务器配置") - parser.add_argument( - "--base-url", - type=str, - help="API基础URL", - default="http://192.168.2.236:3001/v1", - ) - parser.add_argument( - "--app-sks", - nargs="+", - help="应用秘钥列表", - default=["app-RBS0TuYEnqm8Q1cRQingkuhf"], - ) - parser.add_argument( - "--mode-type", - type=str, - help="Dify应用模式类型 (workflow, chat, completion)", - default="workflow", - choices=["workflow", "chat", "completion"], - ) - return parser.parse_args() - - -def get_app_info(base_url=None, app_sks=None, mode_type=None): - # 获取命令行参数 - args = parse_arguments() - # 命令行参数优先,其次是函数参数,最后是默认值 - if args.base_url is not None: - base_url = args.base_url - if base_url is None: - base_url = "http://192.168.2.236:3001/v1" - - if args.app_sks is not None: - app_sks = args.app_sks - if app_sks is None: - app_sks = ["app-RBS0TuYEnqm8Q1cRQingkuhf"] - - if args.mode_type is not None: - mode_type = args.mode_type - if mode_type is None: - mode_type = "workflow" - - return base_url, app_sks, mode_type - - -# 初始化服务器和Dify API -base_url, dify_app_sks, dify_app_mode_type = get_app_info() -server = Server("dify_mcp_server") -task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type) -dify_api = task_instance.get_task_instance(dify_app_mode_type) - -# 创建工具 -file_config = { - "file_fields": {}, # 字典,key为工具名称,value为该工具的文件字段列表 - "file_type_dicts": {} # 字典,key为工具名称,value为该工具的文件类型字典 -} - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出可用的工具 - 返回: - 工具列表,每个工具都使用JSON Schema验证其参数 - """ - tools = [] - tool_names = dify_api.dify_app_names - tool_infos = dify_api.dify_app_infos - tool_params = dify_api.dify_app_params - tool_num = len(tool_names) - for i in range(tool_num): - # 加载每个工具的应用信息 - app_info = tool_infos[i] - # 加载每个工具的应用参数 - app_param = tool_params[i] - - # 记录 parameters API 返回的原始数据 - logger.info(f"工具 {app_info['name']} 的 parameters 数据: {app_param}") - - # 处理用户输入表单 - inputSchema = process_user_input_form(app_param["user_input_form"]) - # 提取所有文件字段并存储到全局字典中 - tool_file_fields = extract_file_fields(app_param["user_input_form"]) - logger.info(f"工具 {app_info['name']} 提取的文件字段: {tool_file_fields}") - file_config["file_fields"][app_info["name"]] = tool_file_fields - - - - tools.append( - types.Tool( - name=app_info["name"], - description=app_info["description"], - inputSchema=inputSchema, - ) - ) - return tools - - -@server.call_tool() -async def handle_call_tool( - name: str, arguments: dict | None -) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """ - 调用工具处理请求 - 参数: - name: 工具名称 - arguments: 工具参数 - 返回: - 处理结果列表 - """ - tool_names = dify_api.dify_app_names - if name in tool_names: - tool_idx = tool_names.index(name) - tool_sk = dify_api.dify_app_sks[tool_idx] - - # 获取当前工具的文件字段信息 - current_tool_file_fields = file_config["file_fields"].get(name, []) - logger.info(f"工具 {name} 的文件字段信息: {current_tool_file_fields}") - logger.info(f"工具 {name} 调用前的 arguments: {arguments}") - - # 使用工具函数处理文件字段 - processed_arguments = process_file_arguments(arguments, current_tool_file_fields, dify_api, name) - logger.info(f"工具 {name} 处理后的 arguments: {processed_arguments}") - - try: - responses = dify_api.chat_message( - tool_sk, - inputs=processed_arguments, - ) - - # 初始化 outputs 变量,避免未定义错误 - outputs = {} - for res in responses: - if res["event"] == "workflow_finished": - outputs = res["data"]["outputs"] - break # 找到 workflow_finished 事件后退出循环 - - # 构建 MCP 输出 - mcp_out = [] - if outputs: - for _, v in outputs.items(): - mcp_out.append(types.TextContent(type="text", text=str(v))) - else: - # 如果没有获取到 outputs,返回错误信息 - logger.warning(f"工具 {name} 未获取到 workflow_finished 事件或 outputs 为空") - mcp_out.append(types.TextContent(type="text", text="工具执行完成,但未返回输出结果")) - - return mcp_out - - except DifyAPIError as e: - # 捕获 Dify API 错误,直接返回给用户 - logger.error(f"工具 {name} 调用 Dify API 失败: {e}") - error_message = f"API调用失败: {e.message}" - return [types.TextContent(type="text", text=error_message)] - - except requests.exceptions.RequestException as e: - # 捕获网络请求错误 - logger.error(f"工具 {name} 网络请求失败: {e}") - error_message = f"网络请求失败: {str(e)}" - return [types.TextContent(type="text", text=error_message)] - - except Exception as e: - # 捕获其他未知错误 - logger.error(f"工具 {name} 执行时发生未知错误: {e}", exc_info=True) - error_message = f"工具执行失败: {str(e)}" - return [types.TextContent(type="text", text=error_message)] - else: - raise ValueError(f"Unknown tool: {name}") - - -def run_main(transport="stdio"): - """ - 主函数:使用stdin/stdout流运行服务器 - """ - if transport == "stdio": - import anyio - from mcp.server.stdio import stdio_server - - async def arun(): - async with stdio_server() as streams: - await server.run( - streams[0], - streams[1], - InitializationOptions( - server_name="dify_mcp_server", - server_version="0.0.6", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - anyio.run(arun) - - else: - from mcp.server.sse import SseServerTransport - from starlette.applications import Starlette - from starlette.responses import Response - from starlette.routing import Mount, Route - - sse = SseServerTransport("/messages/") - - async def handle_sse(request): - async with sse.connect_sse( - request.scope, request.receive, request._send - ) as streams: - await server.run( - streams[0], streams[1], server.create_initialization_options() - ) - return Response() - - starlette_app = Starlette( - debug=True, - routes=[ - Route("/sse", endpoint=handle_sse, methods=["GET"]), - Mount("/messages/", app=sse.handle_post_message), - ], - ) - - import uvicorn - - uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info") - - -if __name__ == "__main__": - run_main() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_update.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_update.py deleted file mode 100644 index 66c7a10..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_update.py +++ /dev/null @@ -1,320 +0,0 @@ -import asyncio -import json -import os -import logging -import argparse -from abc import ABC - -import mcp.server.stdio -import mcp.types as types -import requests -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from omegaconf import OmegaConf - -# from src.workflow.workflow_server import WorkflowDifyAPI -from src.difyTaskCall.task_instance import TaskInstance -from src.utils.dify_workflow_schema import process_user_input_form -# 配置日志记录 -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -def parse_arguments(): - parser = argparse.ArgumentParser(description="Dify MCP服务器配置") - parser.add_argument( - "--base-url", - type=str, - help="API基础URL", - default="http://192.168.2.236:3001/v1", - ) - parser.add_argument( - "--app-sks", - nargs="+", - help="应用秘钥列表", - default=["app-XaRWpeL2Yfdguc5ul3ScXvPE"], - ) - parser.add_argument( - "--mode-type", - type=str, - help="Dify应用模式类型 (workflow, chat, completion)", - default="workflow", - choices=["workflow", "chat", "completion"], - ) - return parser.parse_args() - - -def get_app_info(base_url=None, app_sks=None, mode_type=None): - # 获取命令行参数 - args = parse_arguments() - # 命令行参数优先,其次是函数参数,最后是默认值 - if args.base_url is not None: - base_url = args.base_url - if base_url is None: - base_url = "http://192.168.2.236:3001/v1" - - if args.app_sks is not None: - app_sks = args.app_sks - if app_sks is None: - app_sks = ["app-XaRWpeL2Yfdguc5ul3ScXvPE"] - - if args.mode_type is not None: - mode_type = args.mode_type - if mode_type is None: - mode_type = "workflow" - - return base_url, app_sks, mode_type - - -# 初始化服务器和Dify API -base_url, dify_app_sks, dify_app_mode_type = get_app_info() -server = Server("dify_mcp_server") -task_instance = TaskInstance(base_url, dify_app_sks, dify_app_mode_type) -dify_api = task_instance.get_task_instance(dify_app_mode_type) - - -def process_user_input_form1(user_input_form): - """ - 处理Dify应用的用户输入表单,转换为JSON Schema格式 - - 参数: - user_input_form: Dify应用的用户输入表单配置 - - 返回: - 处理后的inputSchema字典 - """ - inputSchema = dict( - type="object", - properties={}, - required=[], - ) - - property_num = len(user_input_form) - if property_num > 0: - for j in range(property_num): - param = user_input_form[j] - param_type = list(param.keys())[0] - param_info = param[param_type] - property_name = param_info["variable"] - - # 根据不同控件类型处理 - if param_type == "text-input": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "paragraph": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - "format": "paragraph", - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "select": - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - "enum": param_info["options"], - } - if "default" in param_info: - inputSchema["properties"][property_name]["default"] = param_info[ - "default" - ] - - elif param_type == "file_upload": - # 文件上传控件处理 - file_type_schema = { - "type": "object", - "description": param_info["label"], - "properties": { - "file_url": {"type": "string", "description": "文件URL"}, - "file_name": {"type": "string", "description": "文件名称"}, - }, - "required": ["file_url"], - } - - # 处理图片上传配置 - if "image" in param_info and param_info["image"]["enabled"]: - image_config = param_info["image"] - file_type_schema["properties"]["type"] = { - "type": "string", - "description": "文件类型,支持png、jpg、jpeg、webp、gif", - "enum": ["png", "jpg", "jpeg", "webp", "gif"], - } - - # 处理数量限制 - number_limits = image_config.get("number_limits", 3) - if number_limits > 1: - # 如果允许多个文件,则使用数组 - inputSchema["properties"][property_name] = { - "type": "array", - "description": param_info["label"], - "items": file_type_schema, - "maxItems": number_limits, - } - else: - # 如果只允许单个文件 - inputSchema["properties"][property_name] = file_type_schema - else: - # 如果没有特定的图片配置,使用一般文件配置 - inputSchema["properties"][property_name] = file_type_schema - - else: - # 默认处理为字符串类型 - inputSchema["properties"][property_name] = { - "type": "string", - "description": param_info["label"], - } - - # 处理必填字段 - if param_info.get("required", False): - inputSchema["required"].append(property_name) - - # 添加必填的userId参数,支持数字或字符串类型 - inputSchema["properties"]["userId"] = dict( - oneOf=[{"type": "number"}, {"type": "string"}], - description="您的员工ID,用于识别您的员工身份", - ) - inputSchema["required"].append("userId") - - return inputSchema - - -@server.list_tools() -async def handle_list_tools() -> list[types.Tool]: - """ - 列出可用的工具 - 返回: - 工具列表,每个工具都使用JSON Schema验证其参数 - """ - tools = [] - tool_names = dify_api.dify_app_names - tool_infos = dify_api.dify_app_infos - tool_params = dify_api.dify_app_params - tool_num = len(tool_names) - for i in range(tool_num): - # 加载每个工具的应用信息 - app_info = tool_infos[i] - # 加载每个工具的应用参数 - app_param = tool_params[i] - # 处理用户输入表单 - inputSchema = process_user_input_form(app_param["user_input_form"]) - - tools.append( - types.Tool( - name=app_info["name"], - description=app_info["description"], - inputSchema=inputSchema, - ) - ) - return tools - - -@server.call_tool() -async def handle_call_tool( - name: str, arguments: dict | None -) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """ - 调用工具处理请求 - 参数: - name: 工具名称 - arguments: 工具参数 - 返回: - 处理结果列表 - """ - tool_names = dify_api.dify_app_names - if name in tool_names: - tool_idx = tool_names.index(name) - tool_sk = dify_api.dify_app_sks[tool_idx] - - # 提取files参数,并创建不包含files的inputs对象 - files = arguments.get("files", None) if arguments else None - inputs = {k: v for k, v in (arguments or {}).items() if k != "files"} - - responses = dify_api.chat_message( - tool_sk, - inputs=inputs, - userId=arguments.get("userId", "pp666") if arguments else "pp666", - files=files, - ) - - - for res in responses: - if res["event"] == "workflow_finished": - outputs = res["data"]["outputs"] - mcp_out = [] - for _, v in outputs.items(): - mcp_out.append(types.TextContent(type="text", text=v)) - return mcp_out - else: - raise ValueError(f"Unknown tool: {name}") - - -def run_main(transport="stdio"): - """ - 主函数:使用stdin/stdout流运行服务器 - """ - if transport == "stdio": - import anyio - from mcp.server.stdio import stdio_server - - async def arun(): - async with stdio_server() as streams: - await server.run( - streams[0], - streams[1], - InitializationOptions( - server_name="dify_mcp_server", - server_version="0.0.6", - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) - - anyio.run(arun) - - else: - from mcp.server.sse import SseServerTransport - from starlette.applications import Starlette - from starlette.responses import Response - from starlette.routing import Mount, Route - - sse = SseServerTransport("/messages/") - - async def handle_sse(request): - async with sse.connect_sse( - request.scope, request.receive, request._send - ) as streams: - await server.run( - streams[0], streams[1], server.create_initialization_options() - ) - return Response() - - starlette_app = Starlette( - debug=True, - routes=[ - Route("/sse", endpoint=handle_sse, methods=["GET"]), - Mount("/messages/", app=sse.handle_post_message), - ], - ) - - import uvicorn - - uvicorn.run(starlette_app, host="0.0.0.0", port=8080, log_level="info") - - -if __name__ == "__main__": - run_main() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py deleted file mode 100644 index b3c3e5c..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py +++ /dev/null @@ -1,342 +0,0 @@ -""" -MCP创建工具的辅助函数模块 - -包含文件字段处理、参数预处理等功能 -""" - -import logging -import re -from urllib.parse import urlparse -import os -from src.utils.logger_config import get_logger - -logger = get_logger(__name__) - -file_type_details = { - "document": { - "extensions": "TXT, MD, MARKDOWN, PDF, HTML, XLSX, XLS, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB", - "description": "文档文件" - }, - "image": { - "extensions": "JPG, JPEG, PNG, GIF, WEBP, SVG", - "description": "图片文件" - }, - "audio": { - "extensions": "MP3, M4A, WAV, WEBM, AMR", - "description": "音频文件" - }, - "video": { - "extensions": "MP4, MOV, MPEG, MPGA", - "description": "视频文件" - }, - "custom": { - "extensions": "", - "description": "其他文件类型" - } - } - -def process_file_arguments(arguments, current_tool_file_fields, dify_api, tool_name): - """ - 处理arguments中的文件类型字段 - - Args: - arguments (dict): 工具调用的参数字典 - current_tool_file_fields (list): 当前工具的文件字段信息列表 - 数据结构: [{'variable': 'txt', 'label': '输入', 'required': True, 'max_length': 48, - 'allowed_file_types': ['document'], 'allowed_file_upload_methods': ['local_file', 'remote_url'], - 'allowed_file_extensions': [], 'is_list': False}] - dify_api: Dify API实例,包含file_parameter_pretreatment方法 - tool_name (str): 工具名称,用于日志记录 - - Returns: - dict: 处理后的arguments字典 - - Raises: - Exception: 当文件处理过程中发生严重错误时抛出 - """ - if not arguments or not current_tool_file_fields: - logger.info(f"工具 {tool_name}: 无需处理文件字段 (arguments={bool(arguments)}, file_fields={len(current_tool_file_fields) if current_tool_file_fields else 0})") - return arguments - - # 创建文件字段变量名到字段信息的映射,用于快速查找 - file_field_map = {field['variable']: field for field in current_tool_file_fields} - file_field_variables = set(file_field_map.keys()) - logger.info(f"工具 {tool_name} 的文件字段变量名: {file_field_variables}") - - # 创建arguments的副本,避免修改原始数据 - processed_arguments = arguments.copy() - - # 处理arguments中的文件字段 - for arg_name, arg_value in arguments.items(): - # 检查参数名是否是文件字段 - if arg_name in file_field_variables: - logger.info(f"工具 {tool_name}: 发现文件字段 {arg_name},值: {arg_value}") - - # 检查参数值是否包含文件信息 - files_to_process = _extract_files_from_argument(arg_value, arg_name, tool_name) - - if not files_to_process: - logger.info(f"工具 {tool_name}: 字段 {arg_name} 不是文件格式,跳过处理") - continue - - # 调用文件预处理方法 - try: - - logger.info(f"工具 {tool_name}: 准备处理文件列表: {files_to_process}") - - # 为每个文件对象添加必要的字段 - for file_obj in files_to_process: - # 设置传输方式为 remote_url(从URL下载并上传) - if 'transfer_method' not in file_obj: - file_obj['transfer_method'] = 'remote_url' - - # 自动识别文件类型 - if 'type' not in file_obj and 'url' in file_obj: - file_obj['type'] = get_file_type_from_url(file_obj['url']) - logger.info(f"工具 {tool_name}: 自动识别文件类型为 {file_obj['type']}") - - # 调用文件预处理:下载文件并上传到Dify,获取upload_file_id - processed_files = dify_api.file_parameter_pretreatment(files_to_process) - - if not processed_files or len(processed_files) == 0: - logger.error(f"工具 {tool_name}: 文件预处理失败,未返回有效结果") - continue - - # 过滤掉上传失败的文件 - valid_files = [f for f in processed_files if f.get('upload_file_id') and not f.get('upload_error')] - if not valid_files: - logger.error(f"工具 {tool_name}: 所有文件上传失败") - continue - - logger.info(f"工具 {tool_name}: 文件预处理完成,成功上传 {len(valid_files)} 个文件") - - # 获取当前字段的配置信息,判断是单文件还是多文件 - field_config = file_field_map.get(arg_name, {}) - is_list = field_config.get('is_list', False) - - # 更新processed_arguments中的值 - _update_processed_argument(processed_arguments, arg_name, arg_value, valid_files, tool_name, is_list) - - except Exception as e: - logger.error(f"工具 {tool_name}: 处理文件字段 {arg_name} 时发生错误: {str(e)}") - # 继续执行,不中断整个流程 - continue - - return processed_arguments - - -def _extract_files_from_argument(arg_value, arg_name, tool_name): - """ - 从参数值中提取文件信息 - - Args: - arg_value: 参数值(可以是字符串URL、文件对象或文件列表) - arg_name (str): 参数名称 - tool_name (str): 工具名称 - - Returns: - list: 文件列表,如果不是文件格式则返回None - """ - # 情况1: 单个字符串URL - if isinstance(arg_value, str): - # 检查是否是有效的URL - if arg_value.startswith(('http://', 'https://')): - file_obj = {'url': arg_value} - logger.info(f"工具 {tool_name}: 将字符串URL转换为文件对象: {file_obj}") - return [file_obj] - else: - logger.warning(f"工具 {tool_name}: 字符串不是有效的URL: {arg_value}") - return None - - # 情况2: 单个文件对象 - if isinstance(arg_value, dict) and _is_file_object(arg_value): - files_to_process = [arg_value] - logger.info(f"工具 {tool_name}: 处理单个文件对象: {files_to_process}") - return files_to_process - - # 情况3: 文件列表 - elif isinstance(arg_value, list) and len(arg_value) > 0: - # 检查列表中是否包含文件对象或URL字符串 - valid_files = [] - for item in arg_value: - if isinstance(item, dict) and _is_file_object(item): - valid_files.append(item) - elif isinstance(item, str) and item.startswith(('http://', 'https://')): - valid_files.append({'url': item}) - - if valid_files: - logger.info(f"工具 {tool_name}: 处理文件列表: {valid_files}") - return valid_files - - return None - - -def _is_file_object(obj): - """ - 判断对象是否是文件对象 - - Args: - obj (dict): 要检查的对象 - - Returns: - bool: 如果是文件对象返回True,否则返回False - """ - if not isinstance(obj, dict): - return False - - # 检查是否包含文件对象的关键字段 - file_indicators = ['type', 'transfer_method', 'url', 'upload_file_id', 'file_url', 'file_name'] - return any(key in obj for key in file_indicators) - - -def _update_processed_argument(processed_arguments, arg_name, original_value, processed_files, tool_name, is_list=False): - """ - 更新处理后的参数值 - - 根据字段类型决定输出格式: - - file-list (is_list=True): 输出列表格式 [] - - file (is_list=False): 输出对象格式 {} - - Args: - processed_arguments (dict): 处理后的参数字典 - arg_name (str): 参数名称 - original_value: 原始参数值(可以是字符串URL、文件对象或文件列表) - processed_files (list): 处理后的文件对象列表 - tool_name (str): 工具名称 - is_list (bool): 是否为多文件类型 (file-list=True, file=False) - """ - if not processed_files: - logger.warning(f"工具 {tool_name}: 文件处理后为空,保持原值") - return - - if is_list: - # file-list 类型:使用完整列表 - processed_arguments[arg_name] = processed_files - logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file-list 类型,输出 {len(processed_files)} 个文件") - else: - # file 类型:只取第一个文件对象 - processed_arguments[arg_name] = processed_files[0] - logger.info(f"工具 {tool_name}: 字段 {arg_name} 是 file 类型,输出单个对象") - - logger.info(f"工具 {tool_name}: 字段 {arg_name} 最终值: {processed_arguments[arg_name]}") - - -def validate_file_field_configuration(file_fields, tool_name): - """ - 验证文件字段配置的有效性 - - Args: - file_fields (list): 文件字段配置列表 - tool_name (str): 工具名称 - - Returns: - bool: 配置是否有效 - """ - if not file_fields: - return True - - for field in file_fields: - if not isinstance(field, dict): - logger.warning(f"工具 {tool_name}: 文件字段配置不是字典格式: {field}") - return False - - if 'variable' not in field or not field['variable']: - logger.warning(f"工具 {tool_name}: 文件字段缺少variable字段: {field}") - return False - - return True - - -def get_file_field_summary(file_fields, tool_name): - """ - 获取文件字段的摘要信息 - - Args: - file_fields (list): 文件字段配置列表 - tool_name (str): 工具名称 - - Returns: - dict: 文件字段摘要信息 - """ - if not file_fields: - return { - 'count': 0, - 'variables': [], - 'required_count': 0, - 'optional_count': 0 - } - - variables = [field.get('variable', '') for field in file_fields if field.get('variable')] - required_count = sum(1 for field in file_fields if field.get('required', False)) - optional_count = len(file_fields) - required_count - - summary = { - 'count': len(file_fields), - 'variables': variables, - 'required_count': required_count, - 'optional_count': optional_count - } - - logger.info(f"工具 {tool_name} 文件字段摘要: {summary}") - return summary - - -def get_file_type_from_url(url): - """ - 根据URL地址返回文件类型 - - Args: - url (str): 文件的URL地址 - - Returns: - str: 文件类型 ('document', 'image', 'audio', 'video', 'custom') - - Examples: - >>> get_file_type_from_url("http://example.com/file.pdf") - 'document' - >>> get_file_type_from_url("http://example.com/image.jpg") - 'image' - >>> get_file_type_from_url("http://example.com/video.mp4") - 'video' - """ - if not url or not isinstance(url, str): - logger.warning(f"无效的URL: {url}") - return 'custom' - - try: - # 解析URL获取路径 - parsed_url = urlparse(url) - path = parsed_url.path - - # 从路径中提取文件扩展名 - file_extension = os.path.splitext(path)[1].lower().lstrip('.') - - # 如果没有扩展名,尝试从查询参数中提取 - if not file_extension: - # 使用正则表达式匹配常见的文件扩展名模式 - extension_pattern = r'\.([a-zA-Z0-9]{2,5})(?:\?|$|&)' - match = re.search(extension_pattern, url) - if match: - file_extension = match.group(1).lower() - - logger.info(f"从URL {url} 提取的文件扩展名: {file_extension}") - - # 根据扩展名判断文件类型 - for file_type, details in file_type_details.items(): - if file_type == 'custom': - continue - - # 将支持的扩展名转换为小写列表 - supported_extensions = [ext.strip().lower() for ext in details['extensions'].split(',')] - - if file_extension in supported_extensions: - logger.info(f"URL {url} 匹配文件类型: {file_type}") - return file_type - - # 如果没有匹配到任何类型,返回custom - logger.info(f"URL {url} 未匹配到已知文件类型,返回custom") - return 'custom' - - except Exception as e: - logger.error(f"解析URL {url} 时发生错误: {str(e)}") - return 'custom' \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc deleted file mode 100644 index 018363a..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/task_instance.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/task_instance.py deleted file mode 100644 index 9e95791..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/task_instance.py +++ /dev/null @@ -1,53 +0,0 @@ -from abc import ABC - - -# class WorkflowDifyAPI和chatDifyApi和completionDifyApi -# dify_app_mode_type :workflow, chat, completion - - -class TaskInstance(ABC): - def __init__(self, base_url, dify_app_sks, dify_app_mode_type): - self.base_url = base_url - self.dify_app_sks = dify_app_sks - self.dify_app_mode_type = dify_app_mode_type - - def get_task_instance(self, task_id: str): - """ - 根据dify_app_mode_type返回相应的API实例 - - Args: - task_id: 任务ID - - Returns: - 返回对应的API实例 - - Raises: - ValueError: 当dify_app_mode_type无效时抛出异常 - """ - from src.workflow.workflow_server import WorkflowDifyAPI - from src.completion.completion_server import CompletionDifyAPI - from src.chat.chat_server import ChatDifyAPI - - # 使用字典映射提高代码灵活性和可维护性 - api_classes = { - "workflow": WorkflowDifyAPI, - "chat": ChatDifyAPI, - "completion": CompletionDifyAPI, - } - - # 检查mode_type是否有效 - if self.dify_app_mode_type.lower() not in api_classes: - supported_types = ", ".join(api_classes.keys()) - raise ValueError( - f"不支持的dify_app_mode_type: {self.dify_app_mode_type},支持的类型: {supported_types}" - ) - - # 获取对应的API类 - api_class = api_classes[self.dify_app_mode_type.lower()] - - # 这里假设所有API类都接受相同的参数集 - # 如果各API类构造函数参数不同,需要针对每种类型单独处理 - return api_class( - self.base_url, - self.dify_app_sks, - ) diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/dify_workflow_schema.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/dify_workflow_schema.cpython-312.pyc deleted file mode 100644 index ac7fbf9..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/dify_workflow_schema.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/logger_config.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index cdef1ad..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/logger_config.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/upload_file.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/upload_file.cpython-312.pyc deleted file mode 100644 index 799a0dd..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/upload_file.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/dify_workflow_schema.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/dify_workflow_schema.py deleted file mode 100644 index c2b1f1c..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/dify_workflow_schema.py +++ /dev/null @@ -1,400 +0,0 @@ - - -import json -import logging - -# 获取日志器 -logger = logging.getLogger(__name__) - -data={ - "user_input_form": [ - { - "file": { - "variable": "files", - "label": "files", - "type": "file", - "max_length": 48, - "required": True, - "options": [], - "allowed_file_upload_methods": [ - "local_file", - "remote_url" - ], - "allowed_file_types": [ - "image", - "document" - ], - "allowed_file_extensions": [] - } - } - ], - -} - - -data2={ - "user_input_form": [ - { - "paragraph": { - "label": "产品名称", - "max_length": 33024, - "options": [], - "required": True, - "type": "paragraph", - "variable": "name" - } - }, - { - "paragraph": { - "label": "补充描述", - "max_length": 33024, - "options": [], - "required": False, - "type": "paragraph", - "variable": "desc" - } - } - ], -} - -data3={ - "user_input_form": [ - { - "file": { - "allowed_file_extensions": [], - "allowed_file_types": [ - "document","image" - ], - "allowed_file_upload_methods": [ - "local_file", - "remote_url" - ], - "label": "输入", - "max_length": 48, - "options": [], - "required": True, - "type": "file", - "variable": "txt" - } - } - ] -} - -def generate_file_type_description(allowed_file_types): - """ - 根据allowed_file_types生成文件类型描述 - - 参数: - allowed_file_types (list): 允许的文件类型列表 - - 返回: - str: 生成的文件类型描述 - """ - # 定义各种文件类型的具体格式和中文描述 - file_type_details = { - "document": { - "extensions": "TXT, MD, MARKDOWN, PDF, HTML, XLSX, XLS, DOCX, CSV, EML, MSG, PPTX, PPT, XML, EPUB", - "description": "文档文件" - }, - "image": { - "extensions": "JPG, JPEG, PNG, GIF, WEBP, SVG", - "description": "图片文件" - }, - "audio": { - "extensions": "MP3, M4A, WAV, WEBM, AMR", - "description": "音频文件" - }, - "video": { - "extensions": "MP4, MOV, MPEG, MPGA", - "description": "视频文件" - }, - "custom": { - "extensions": "", - "description": "其他文件类型" - } - } - - if not allowed_file_types or len(allowed_file_types) == 0: - return "请提供有效的文件URL地址" - - # 生成描述 - descriptions = [] - all_extensions = [] - - for file_type in allowed_file_types: - if file_type in file_type_details: - detail = file_type_details[file_type] - if detail["extensions"]: - descriptions.append(f"{detail['description']}({detail['extensions']})") - all_extensions.extend(detail["extensions"].split(", ")) - else: - descriptions.append(detail["description"]) - - if descriptions: - if len(descriptions) == 1: - return f"请提供{descriptions[0]}的URL地址" - else: - return f"请提供文件URL地址,支持的文件类型:{' | '.join(descriptions)}" - else: - return "请提供有效的文件URL地址" - - -def process_user_input_form(user_input_form): - """ - 处理Dify应用的用户输入表单,转换为JSON Schema格式 - - 支持的控件类型: - - text-input (object): 文本输入控件 - * label (string): 控件展示标签名 - * variable (string): 控件 ID - * required (bool): 是否必填 - * default (string): 默认值 - - - paragraph (object): 段落文本输入控件 - * label (string): 控件展示标签名 - * variable (string): 控件 ID - * required (bool): 是否必填 - * default (string): 默认值 - - - select (object): 下拉控件 - * label (string): 控件展示标签名 - * variable (string): 控件 ID - * required (bool): 是否必填 - * default (string): 默认值 - * options (array[string]): 选项值 - - - file (object): 文件上传控件 (支持复杂的文件处理逻辑) - - 参数: - user_input_form (array[object]): 用户输入表单配置 - - 返回: - dict: 处理后的inputSchema字典,符合JSON Schema规范 - """ - # 初始化基础schema结构 - inputSchema = { - "type": "object", - "properties": {}, - "required": [], - } - - # 如果没有用户输入表单配置,跳过处理 - if not user_input_form or len(user_input_form) == 0: - pass - else: - # 遍历处理每个表单控件 - for form_item in user_input_form: - # 检查form_item是否为None或空 - if not form_item or not isinstance(form_item, dict): - continue - - # 获取控件类型和配置信息 - control_type = list(form_item.keys())[0] - logger.debug(f"处理控件类型: {control_type}") - control_config = form_item[control_type] - - # 检查control_config是否为None - if not control_config or not isinstance(control_config, dict): - continue - - # 提取控件基础属性 - variable = control_config.get("variable", "") - label = control_config.get("label", "") - required = control_config.get("required", False) - default_value = control_config.get("default") - - # 跳过没有variable的无效控件 - if not variable: - continue - - # 根据控件类型进行相应处理 - property_schema = None - - if control_type == "text-input": - # 文本输入控件处理 - property_schema = { - "type": "string", - "description": label or f"文本输入字段: {variable}", - } - # 设置默认值 - if default_value is not None: - property_schema["default"] = str(default_value) - - elif control_type == "paragraph": - # 段落文本输入控件处理 - property_schema = { - "type": "string", - "description": label or f"段落文本字段: {variable}", - "format": "textarea", # 标识为多行文本输入 - } - # 设置默认值 - if default_value is not None: - property_schema["default"] = str(default_value) - - elif control_type == "select": - # 下拉控件处理 - options = control_config.get("options", []) - property_schema = { - "type": "string", - "description": label or f"下拉选择字段: {variable}", - } - - # 设置选项枚举值 - if options and len(options) > 0: - property_schema["enum"] = options - - # 设置默认值 - if default_value is not None: - property_schema["default"] = str(default_value) - - elif control_type in ["file", "file-list"]: - # 文件上传控件处理 - 简化版本,仅支持remote_url - # 获取允许的文件类型 - allowed_file_types = control_config.get("allowed_file_types", []) - - # 生成动态的URL描述 - url_description = generate_file_type_description(allowed_file_types) - - file_schema = { - "type": "object", - "description": label or f"文件上传字段: {variable}", - "properties": { - "url": { - "type": "string", - "description": url_description - } - }, - "required": ["url"] - } - - # 判断是单文件还是多文件 - # file-list 类型或 max_length > 1 都视为多文件 - actual_type = control_config.get("type", control_type) - max_length = control_config.get("max_length", 1) - is_multi_file = actual_type == "file-list" or max_length > 1 - - if is_multi_file: - # 多文件上传场景 - property_schema = { - "type": "array", - "description": label or f"多文件上传字段: {variable}", - "items": file_schema, - "maxItems": max_length, - "minItems": 1 if required else 0 - } - else: - # 单文件上传场景 - property_schema = file_schema - - else: - # 未知控件类型的默认处理 - property_schema = { - "type": "string", - "description": label or f"未知类型字段: {variable} (类型: {control_type})", - } - if default_value is not None: - property_schema["default"] = str(default_value) - - # 将处理后的属性添加到schema中 - if property_schema: - inputSchema["properties"][variable] = property_schema - - # 处理必填字段约束 - if required: - inputSchema["required"].append(variable) - - - - return inputSchema - - -def extract_file_fields(user_input_form): - """ - 从用户输入表单中提取所有type为file的字段信息 - - 参数: - user_input_form (array[object]): 用户输入表单配置 - - 返回: - list: 包含所有file类型字段信息的列表,每个元素包含: - - variable (str): 字段变量名 - - label (str): 字段标签 - - required (bool): 是否必填 - - max_length (int): 最大文件数量 - - allowed_file_types (list): 允许的文件类型 - - allowed_file_upload_methods (list): 允许的上传方式 - - allowed_file_extensions (list): 允许的文件扩展名 - - is_list (bool): 是否为多文件类型 (file-list=True, file=False) - """ - file_fields = [] - - # 如果没有用户输入表单配置,返回空列表 - if not user_input_form or len(user_input_form) == 0: - return file_fields - - # 遍历处理每个表单控件 - for form_item in user_input_form: - # 检查form_item是否为None或空 - if not form_item or not isinstance(form_item, dict): - continue - - # 获取控件类型和配置信息 - control_type = list(form_item.keys())[0] - control_config = form_item[control_type] - - # 检查control_config是否为None - if not control_config or not isinstance(control_config, dict): - continue - - # 只处理type为file或file-list的字段 - if control_type in ["file", "file-list"] or control_config.get("type") in ["file", "file-list"]: - # 判断是否为多文件类型 - # 优先使用 control_config 中的 type,其次使用 control_type - actual_type = control_config.get("type", control_type) - is_list = actual_type == "file-list" - - # 提取文件字段的详细信息 - file_field_info = { - "variable": control_config.get("variable", ""), - "label": control_config.get("label", ""), - "required": control_config.get("required", False), - "max_length": control_config.get("max_length", 1), - "allowed_file_types": control_config.get("allowed_file_types", []), - "allowed_file_upload_methods": control_config.get("allowed_file_upload_methods", []), - "allowed_file_extensions": control_config.get("allowed_file_extensions", []), - "is_list": is_list # 新增:标识是否为多文件类型 - } - - # 只添加有效的字段(必须有variable) - if file_field_info["variable"]: - file_fields.append(file_field_info) - - return file_fields - - -if __name__ == "__main__": - # run_main() - result = process_user_input_form(data3["user_input_form"]) - print("开始生成 Schema...", result) - - # 保存到当前目录下的JSON文件 - output_file = "process_user_input_form_output.json" - with open(output_file, 'w', encoding='utf-8') as f: - json.dump(result, f, ensure_ascii=False, indent=2) - - # print(f"结果已保存到: {output_file}") - - # # 测试新的extract_file_fields方法 - # print("\n=== 测试 extract_file_fields 方法 ===") - - # # 测试data(包含file字段) - # file_fields_data = extract_file_fields(data["user_input_form"]) - # print("data中的file字段:", file_fields_data) - - # # 测试data2(不包含file字段) - # file_fields_data2 = extract_file_fields(data2["user_input_form"]) - # print("data2中的file字段:", file_fields_data2) - - # 测试data3(包含file字段) - # file_fields_data3 = extract_file_fields(data3["user_input_form"]) - # print("data3中的file字段:", file_fields_data3) diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/logger_config.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/logger_config.py deleted file mode 100644 index f598d1d..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/logger_config.py +++ /dev/null @@ -1,552 +0,0 @@ -""" -统一日志配置模块 - -这个模块提供了整个项目的统一日志配置和管理功能,确保所有组件使用一致的日志格式和输出方式。 - -主要功能: -1. 统一的日志格式配置 -2. 支持控制台和文件双重输出 -3. 日志文件轮转管理 -4. MCP模式下的特殊处理(禁用控制台输出) -5. 便捷的日志器获取接口 -6. 丰富的日志工具函数 - -设计特点: -- 单例模式确保配置一致性 -- 支持动态配置调整 -- 异常安全的编码处理 -- 详细的调试信息记录 - -作者: lzwcai -版本: 1.0.0 -""" - -import logging -import logging.handlers -import sys -from datetime import datetime -from pathlib import Path -from typing import Optional - - -class LoggerConfig: - """ - 日志配置管理器 - - 这个类采用单例模式管理整个项目的日志配置。 - 它提供了统一的日志格式、文件轮转、编码处理等功能。 - - 主要特性: - - 单例模式:确保全局日志配置一致 - - 双重输出:同时支持控制台和文件输出 - - 文件轮转:自动管理日志文件大小和数量 - - 编码安全:正确处理中文字符 - - MCP兼容:支持MCP模式下的特殊需求 - - 配置参数: - DEFAULT_LOG_LEVEL: 默认日志级别(INFO) - DEFAULT_LOG_FORMAT: 日志格式模板 - DEFAULT_DATE_FORMAT: 时间格式 - LOG_FILE_NAME: 日志文件名 - MAX_LOG_SIZE: 单个日志文件最大大小(10MB) - BACKUP_COUNT: 保留的备份文件数量(5个) - """ - - # ==================== 默认配置常量 ==================== - - # 默认日志级别:INFO级别平衡了信息量和性能 - DEFAULT_LOG_LEVEL = logging.INFO - - # 默认日志格式:包含时间、模块名、级别、文件位置、消息内容 - DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" - - # 默认时间格式:标准的年-月-日 时:分:秒格式 - DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - - # ==================== 日志文件配置 ==================== - - # 日志文件名:使用项目名称作为前缀 - LOG_FILE_NAME = "lzwcai_demp_tool_server_dify_to_mcp_test.log" - - # 单个日志文件最大大小:10MB - MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB - - # 保留的备份文件数量:5个(总共约50MB的日志存储) - BACKUP_COUNT = 5 - - # ==================== 单例模式状态 ==================== - - # 初始化标志:确保只初始化一次 - _initialized = False - - # 日志文件路径:记录当前使用的日志文件路径 - _log_file_path = None - - @classmethod - def setup_logging( - cls, - log_level: int = DEFAULT_LOG_LEVEL, - log_file: Optional[str] = None, - console_output: bool = True, - file_output: bool = True - ) -> str: - """ - 设置项目统一日志配置 - - 这是日志系统的核心初始化方法,负责配置整个项目的日志输出。 - 采用单例模式,确保在整个应用生命周期中只初始化一次。 - - 配置流程: - 1. 检查是否已经初始化(单例模式) - 2. 确定日志文件路径(自动或手动指定) - 3. 创建必要的目录结构 - 4. 配置根日志器和处理器 - 5. 设置日志格式化器 - 6. 添加控制台和文件处理器 - 7. 记录初始化信息 - - 特殊处理: - - MCP模式下通常禁用控制台输出,避免干扰stdio通信 - - Windows系统下的UTF-8编码处理 - - 日志文件的自动轮转管理 - - 参数: - log_level: 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL) - log_file: 日志文件路径,None时使用默认路径 - console_output: 是否输出到控制台(MCP模式下通常为False) - file_output: 是否输出到文件(通常为True) - - 返回: - str: 实际使用的日志文件路径 - - 注意事项: - - 这个方法是线程安全的 - - 重复调用会直接返回已配置的路径 - - 日志文件会自动创建必要的目录 - """ - # 单例模式检查:如果已经初始化,直接返回 - if cls._initialized: - return cls._log_file_path - - # ==================== 日志文件路径配置 ==================== - - if log_file is None: - # 自动确定日志文件路径:项目根目录/logs/ + 默认文件名 - project_root = cls._get_project_root() - logs_dir = project_root / "logs" - logs_dir.mkdir(parents=True, exist_ok=True) - log_file = logs_dir / cls.LOG_FILE_NAME - else: - # 使用指定的日志文件路径 - log_file = Path(log_file) - - # 确保日志目录存在(递归创建) - log_file.parent.mkdir(parents=True, exist_ok=True) - cls._log_file_path = str(log_file) - - # ==================== 根日志器配置 ==================== - - # 配置根日志器,这样可以捕获所有模块的日志 - root_logger = logging.getLogger() - root_logger.setLevel(log_level) - - # 清除根日志器上现有的处理器,避免重复配置 - for handler in root_logger.handlers[:]: - root_logger.removeHandler(handler) - - # ==================== 日志格式化器 ==================== - - # 创建统一的日志格式化器 - formatter = logging.Formatter( - fmt=cls.DEFAULT_LOG_FORMAT, # 日志格式模板 - datefmt=cls.DEFAULT_DATE_FORMAT # 时间格式 - ) - - # ==================== 控制台处理器配置 ==================== - - if console_output: - # 控制台输出处理器,支持彩色输出和UTF-8编码 - import io - - # 处理Windows系统的编码问题 - if hasattr(sys.stdout, 'buffer'): - # 在Windows上强制使用UTF-8编码,避免中文乱码 - # errors='replace'确保即使有编码问题也不会崩溃 - console_stream = io.TextIOWrapper( - sys.stdout.buffer, - encoding='utf-8', - errors='replace' - ) - else: - # Unix/Linux系统通常默认支持UTF-8 - console_stream = sys.stdout - - # 创建控制台处理器 - console_handler = logging.StreamHandler(console_stream) - console_handler.setLevel(log_level) - console_handler.setFormatter(formatter) - root_logger.addHandler(console_handler) - - # ==================== 文件处理器配置 ==================== - - if file_output: - # 文件输出处理器,支持自动轮转 - file_handler = logging.handlers.RotatingFileHandler( - filename=cls._log_file_path, # 日志文件路径 - maxBytes=cls.MAX_LOG_SIZE, # 单文件最大大小 - backupCount=cls.BACKUP_COUNT, # 备份文件数量 - encoding='utf-8' # 文件编码 - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # ==================== 初始化完成标记 ==================== - - # 标记为已初始化,防止重复配置 - cls._initialized = True - - # ==================== 记录初始化信息 ==================== - - # 获取当前模块的日志器并记录初始化信息 - logger = logging.getLogger(__name__) - logger.info("=" * 80) - logger.info(f"日志系统初始化完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - logger.info(f"日志级别: {logging.getLevelName(log_level)}") - logger.info(f"日志文件: {cls._log_file_path}") - logger.info(f"控制台输出: {console_output}") - logger.info(f"文件输出: {file_output}") - logger.info(f"文件轮转: 最大{cls.MAX_LOG_SIZE // (1024*1024)}MB, 保留{cls.BACKUP_COUNT}个备份") - logger.info("=" * 80) - - return cls._log_file_path - - @classmethod - def _get_project_root(cls) -> Path: - """ - 获取项目根目录 - - 这个方法通过向上遍历目录树来查找项目根目录。 - 它会寻找常见的项目标识文件来确定根目录位置。 - - 查找策略: - 1. 从当前文件所在目录开始向上查找 - 2. 寻找项目标识文件:pyproject.toml, setup.py, main.py - 3. 找到任一标识文件的目录即为项目根目录 - 4. 如果都找不到,使用当前文件的上级目录作为备选 - - 返回: - Path: 项目根目录的路径对象 - - 注意事项: - - 这个方法假设项目结构相对标准 - - 在特殊的部署环境中可能需要调整 - - 备选方案确保总是返回有效路径 - """ - # 从当前文件向上查找项目根目录 - current_path = Path(__file__).parent - - # 向上遍历目录树 - while current_path.parent != current_path: # 避免到达文件系统根目录 - # 检查常见的项目标识文件 - if (current_path / "pyproject.toml").exists() or \ - (current_path / "setup.py").exists() or \ - (current_path / "main.py").exists(): - return current_path - current_path = current_path.parent - - # 备选方案:如果找不到标识文件,使用预设的相对路径 - # 这个路径基于当前的项目结构:utils -> src -> 项目根 - return Path(__file__).parent.parent.parent - - @classmethod - def get_logger(cls, name: str) -> logging.Logger: - """ - 获取配置好的日志器 - - 这是获取日志器的标准方法,确保返回的日志器使用统一的配置。 - 如果日志系统尚未初始化,会自动进行初始化。 - - 参数: - name: 日志器名称,通常使用模块的 __name__ 变量 - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = LoggerConfig.get_logger(__name__) - logger.info("这是一条信息日志") - - 特性: - - 自动初始化:首次调用时自动配置日志系统(MCP模式下禁用控制台输出) - - 层次化命名:支持Python日志器的层次化命名 - - 统一配置:所有日志器使用相同的格式和输出配置 - """ - # 检查是否已初始化,未初始化则使用默认配置初始化 - # 重要:在MCP模式下禁用控制台输出,避免干扰stdio通信 - if not cls._initialized: - cls.setup_logging(console_output=False, file_output=True) - - # 返回指定名称的日志器 - return logging.getLogger(name) - - # ==================== 日志工具方法 ==================== - - @classmethod - def log_function_entry(cls, logger: logging.Logger, func_name: str, **kwargs): - """ - 记录函数入口日志 - - 用于调试和性能分析,记录函数被调用时的参数信息。 - 通常在DEBUG级别输出,不会影响生产环境的性能。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - **kwargs: 函数参数(键值对形式) - - 使用示例: - LoggerConfig.log_function_entry(logger, "process_data", user_id=123, action="login") - """ - args_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()]) - logger.debug(f"进入函数 {func_name}({args_str})") - - @classmethod - def log_function_exit(cls, logger: logging.Logger, func_name: str, result=None): - """ - 记录函数出口日志 - - 与log_function_entry配对使用,记录函数执行完成和返回值。 - 有助于跟踪函数执行流程和调试返回值问题。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - result: 函数返回值(可选) - - 使用示例: - LoggerConfig.log_function_exit(logger, "process_data", result={"status": "success"}) - """ - if result is not None: - logger.debug(f"退出函数 {func_name},返回值: {result}") - else: - logger.debug(f"退出函数 {func_name}") - - @classmethod - def log_api_request(cls, logger: logging.Logger, method: str, url: str, **kwargs): - """ - 记录API请求日志 - - 标准化API请求的日志记录,包含HTTP方法、URL和请求参数。 - 有助于API调用的监控和调试。 - - 参数: - logger: 日志器实例 - method: HTTP方法(GET, POST, PUT, DELETE等) - url: 请求URL - **kwargs: 请求参数(可选) - - 使用示例: - LoggerConfig.log_api_request(logger, "POST", "https://api.example.com/users", - headers={"Authorization": "Bearer xxx"}) - """ - logger.info(f"API请求 - {method} {url}") - if kwargs: - logger.debug(f"请求参数: {kwargs}") - - @classmethod - def log_api_response(cls, logger: logging.Logger, status_code: int, response_time: float = None): - """ - 记录API响应日志 - - 记录API响应的状态码和响应时间,用于性能监控和问题诊断。 - - 参数: - logger: 日志器实例 - status_code: HTTP状态码 - response_time: 响应时间(秒,可选) - - 使用示例: - LoggerConfig.log_api_response(logger, 200, 0.156) - """ - if response_time: - logger.info(f"API响应 - 状态码: {status_code}, 响应时间: {response_time:.3f}s") - else: - logger.info(f"API响应 - 状态码: {status_code}") - - @classmethod - def log_error_with_context(cls, logger: logging.Logger, error: Exception, context: str = ""): - """ - 记录带上下文的错误日志 - - 提供丰富的错误信息记录,包含异常类型、错误消息、上下文信息和详细堆栈。 - 这是错误处理的标准方法。 - - 参数: - logger: 日志器实例 - error: 异常对象 - context: 错误发生的上下文描述(可选) - - 使用示例: - try: - risky_operation() - except Exception as e: - LoggerConfig.log_error_with_context(logger, e, "处理用户请求时") - """ - if context: - logger.error(f"错误发生在 {context}: {type(error).__name__}: {str(error)}") - else: - logger.error(f"错误: {type(error).__name__}: {str(error)}") - # 记录详细的异常堆栈信息(仅在DEBUG级别显示) - logger.debug("错误详情:", exc_info=True) - - -# ==================== 便捷函数 ==================== - -def get_logger(name: str) -> logging.Logger: - """ - 获取日志器的便捷函数 - - 这是LoggerConfig.get_logger的简化版本,提供更简洁的调用方式。 - 推荐在模块级别使用这个函数获取日志器。 - - 参数: - name: 日志器名称,通常使用 __name__ - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = get_logger(__name__) - """ - return LoggerConfig.get_logger(name) - - -def setup_logging(**kwargs) -> str: - """ - 设置日志的便捷函数 - - 这是LoggerConfig.setup_logging的简化版本,支持所有相同的参数。 - - 参数: - **kwargs: 传递给LoggerConfig.setup_logging的所有参数 - - 返回: - str: 日志文件路径 - - 使用示例: - log_file = setup_logging(log_level=logging.DEBUG, console_output=False) - """ - return LoggerConfig.setup_logging(**kwargs) - - -# ==================== 装饰器 ==================== - -def log_function_calls(logger: Optional[logging.Logger] = None): - """ - 函数调用日志装饰器 - - 这个装饰器自动记录函数的调用和返回,包括参数和返回值。 - 主要用于调试和性能分析,在生产环境中通常设置为DEBUG级别。 - - 特性: - - 自动记录函数入口和出口 - - 记录函数参数(kwargs) - - 记录返回值 - - 自动处理异常并记录错误上下文 - - 支持自定义日志器或自动获取 - - 参数: - logger: 可选的日志器实例,None时自动获取函数所在模块的日志器 - - 返回: - 装饰器函数 - - 使用示例: - @log_function_calls() - def process_user_data(user_id, action="login"): - # 函数实现 - return {"status": "success"} - - # 或者指定日志器 - @log_function_calls(logger=my_logger) - def another_function(): - pass - - 注意事项: - - 会记录所有kwargs参数,注意不要记录敏感信息 - - 返回值也会被记录,大对象可能影响性能 - - 异常会被重新抛出,不会被吞掉 - """ - def decorator(func): - def wrapper(*args, **kwargs): - nonlocal logger - # 如果没有提供日志器,自动获取函数所在模块的日志器 - if logger is None: - logger = get_logger(func.__module__) - - func_name = func.__name__ - - # 记录函数入口(只记录kwargs,避免记录过多信息) - LoggerConfig.log_function_entry(logger, func_name, **kwargs) - - try: - # 执行原函数 - result = func(*args, **kwargs) - - # 记录函数出口和返回值 - LoggerConfig.log_function_exit(logger, func_name, result) - return result - - except Exception as e: - # 记录异常信息并重新抛出 - LoggerConfig.log_error_with_context(logger, e, f"函数 {func_name}") - raise - - return wrapper - return decorator - - -# ==================== 测试代码 ==================== - -if __name__ == "__main__": - """ - 日志配置测试代码 - - 这个测试代码演示了日志系统的基本功能,包括: - 1. 日志系统初始化 - 2. 不同级别的日志输出 - 3. 日志文件路径获取 - 4. 装饰器功能测试 - - 运行方式: - python -m src.utils.logger_config - """ - # 初始化日志系统(DEBUG级别,同时输出到控制台和文件) - log_file = setup_logging(log_level=logging.DEBUG) - test_logger = get_logger(__name__) - - test_logger.info("开始测试日志配置...") - - # 测试不同级别的日志输出 - test_logger.debug("这是一个调试消息 - 用于开发调试") - test_logger.info("这是一个信息消息 - 记录重要信息") - test_logger.warning("这是一个警告消息 - 提醒注意事项") - test_logger.error("这是一个错误消息 - 记录错误情况") - - # 测试工具方法 - LoggerConfig.log_api_request(test_logger, "GET", "https://api.example.com/test") - LoggerConfig.log_api_response(test_logger, 200, 0.123) - - # 测试装饰器 - @log_function_calls() - def test_function(param1, param2="default"): - """测试函数""" - return {"result": "success", "param1": param1} - - # 调用测试函数 - result = test_function("test_value", param2="custom") - - # 输出日志文件位置 - test_logger.info(f"日志文件位置: {log_file}") - test_logger.info("日志配置测试完成!") \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/tool_translation.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/tool_translation.py deleted file mode 100644 index 7361457..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/tool_translation.py +++ /dev/null @@ -1,153 +0,0 @@ -import os -import sys -from typing import Dict, Any, Optional - - -# 导入翻译函数 -from .translator import translate - - -class TranslationService: - """翻译服务类,用于处理各种翻译需求""" - - @staticmethod - def create_prompt( - content: str, - target_lang: str, - use_case: str, - style: str, - prompt_type: str = "general", - keep_terms_desc: str = "核心术语", - ) -> str: - """ - 创建翻译提示 - - Args: - content: 待翻译内容 - target_lang: 目标语言 - use_case: 使用场景 - style: 翻译风格 - prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description" - keep_terms_desc: 保留术语的描述 - - Returns: - str: 格式化的翻译提示 - """ - if prompt_type == "tool_name": - keep_terms_desc = "核心术语(如 小写,词语需要用下划线连接)" - elif prompt_type == "tool_description": - keep_terms_desc = "核心术语(这是一段话)" - - return f""" -角色:专业本地化翻译专家 -任务:将以下内容翻译为{target_lang}(目标用途:{use_case}) -要求: -1. 仅返回译文,不含解释或原文; -2. 保留{keep_terms_desc}; -3. 符合{style}风格; -4. 特殊符号保持原样。 - -示例输出格式: -Translated Text - -待翻译内容: -{content} -""" - - @staticmethod - def translate_text( - content: str, - target_lang: str, - use_case: str = "", - style: str = "正式且符合技术品牌调性", - prompt_type: str = "general", - ) -> Dict[str, Any]: - """ - 翻译文本 - - Args: - content: 待翻译内容 - target_lang: 目标语言 - use_case: 使用场景,默认为空 - style: 翻译风格,默认为"正式且符合技术品牌调性" - prompt_type: 提示类型,可选值: "general", "tool_name", "tool_description" - - Returns: - Dict: 包含翻译结果的字典 - """ - prompt = TranslationService.create_prompt( - content=content, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type=prompt_type, - ) - - try: - result = translate(prompt, target_lang) - return result - except Exception as e: - print(f"翻译出错: {str(e)}") - return {"translated_text": "", "error": str(e)} - - @staticmethod - def translate_tool_name( - name: str, - target_lang: str = "英语", - use_case: str = "工具名称", - style: str = "正式且符合技术品牌调性,大模型能理解", - ) -> str: - """ - 翻译工具名称的便捷方法 - - Returns: - str: 翻译后的工具名称 - """ - result = TranslationService.translate_text( - content=name, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type="tool_name", - ) - return result.get("translated_text", "") - - @staticmethod - def translate_tool_description( - description: str, - target_lang: str = "英语", - use_case: str = "工具描述", - style: str = "正式且符合技术品牌调性,大模型能理解", - ) -> str: - """ - 翻译工具描述的便捷方法 - - Returns: - str: 翻译后的工具描述 - """ - result = TranslationService.translate_text( - content=description, - target_lang=target_lang, - use_case=use_case, - style=style, - prompt_type="tool_description", - ) - return result.get("translated_text", "") - - -def translation_example(): - """翻译功能使用示例""" - - # 示例1: 翻译工具名称 - tool_name = "万川AI新媒体平台【测试环境】" - translated_name = TranslationService.translate_tool_name(tool_name) - print(f"工具名称翻译: {translated_name}") - - # 示例2: 翻译工具描述 - description = "21日,辛柏青发布讣告宣布妻子朱媛媛抗癌五年后离世。此前在一次路演现场,当观众问及朱媛媛时辛柏青2秒停顿藏着" - translated_desc = TranslationService.translate_tool_description(description) - print(f"工具描述翻译: {translated_desc}") - - -if __name__ == "__main__": - translation_example() diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/translator.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/translator.py deleted file mode 100644 index b0b777b..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/translator.py +++ /dev/null @@ -1,64 +0,0 @@ -import os -import requests -import json -from dotenv import load_dotenv - -# 加载环境变量 -load_dotenv() -# ========== 模型相关 ========== -# 从.env文件获取模型API配置 -BASE_URL = os.getenv("BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1") -API_KEY = os.getenv("OPENAI_API_KEY", "sk-c5a912a6bc8e4c9cbdbdf68232352a03") -TEMPERATURE = float(os.getenv("MODEL_TEMPERATURE", "0.7")) - - -def translate(content, target_language): - """ - 翻译文本内容到目标语言 - - :param content: 要翻译的内容 - :param target_language: 目标语言,如'en'(英语), 'zh'(中文), 'ja'(日语), 'fr'(法语)等 - :return: 翻译后的内容,如果翻译失败则返回原文和错误信息 - """ - if not content or not target_language: - return {"error": "内容或目标语言不能为空", "translated_text": content} - - # 确保API密钥已设置 - if not API_KEY: - return {"error": "API密钥未设置,请检查.env文件", "translated_text": content} - - try: - # 构建API请求头 - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {API_KEY}", - } - - # 构建翻译提示 - prompt = f"请将以下内容翻译成{target_language},只返回翻译结果,不要包含任何解释或原文:\n\n{content}" - - # 构建API请求体 - data = { - "model": "qwen-max", # 使用通义千问模型,可以根据实际需要更改 - "messages": [{"role": "user", "content": prompt}], - "temperature": TEMPERATURE, - } - - # 发送API请求 - response = requests.post( - f"{BASE_URL}/chat/completions", headers=headers, json=data - ) - - # 解析响应 - if response.status_code == 200: - result = response.json() - translated_text = result["choices"][0]["message"]["content"].strip() - return {"translated_text": translated_text} - else: - error_message = ( - f"翻译失败,状态码: {response.status_code}, 响应: {response.text}" - ) - return {"error": error_message, "translated_text": content} - - except Exception as e: - return {"error": f"翻译过程中发生错误: {str(e)}", "translated_text": content} diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/upload_file.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/upload_file.py deleted file mode 100644 index 918b01c..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/upload_file.py +++ /dev/null @@ -1,461 +0,0 @@ -""" -Dify文件上传工具 - 优化版 - -主要功能: -- 从URL下载文件并自动上传到Dify API -- 支持常见文件类型:JPG、PNG、GIF、PDF、DOCX、TXT等 -- 自动处理文件大小检查、MIME类型识别、临时文件清理 - -使用方法: - from upload_file import upload_file_from_url - - result = upload_file_from_url( - file_url="http://example.com/image.jpg", - base_url="http://192.168.2.236:3001/v1", - api_key="app-QdfDKqHAI3dlB6tvnibuh6rv" - ) - - file_id = result['id'] # 获取上传后的文件ID -""" - -import os -import tempfile -import requests -import logging -from urllib.parse import urlparse, unquote -from typing import Optional - -# 获取模块级别的logger,避免影响全局日志配置 -logger = logging.getLogger(__name__) - -# 常用MIME类型映射 -MIME_TYPE_MAP = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.webp': 'image/webp', - '.svg': 'image/svg+xml', - '.pdf': 'application/pdf', - '.txt': 'text/plain', - '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' -} - - -def download_file_from_url( - url: str, - download_dir: Optional[str] = None, - filename: Optional[str] = None, - timeout: int = 30, - max_retries: int = 3 -) -> str: - """ - 从URL下载文件到本地并返回文件路径 - - Args: - url: 文件的URL地址 - download_dir: 下载目录,如果为None则使用系统临时目录 - filename: 指定文件名,如果为None则从URL中提取 - timeout: 请求超时时间(秒) - max_retries: 最大重试次数 - - Returns: - str: 下载后的本地文件路径 - - Raises: - Exception: 下载失败时抛出异常 - """ - - # 设置下载目录 - if download_dir is None: - download_dir = tempfile.gettempdir() - - # 确保下载目录存在 - os.makedirs(download_dir, exist_ok=True) - - # 提取文件名 - if filename is None: - filename = _extract_filename_from_url(url) - - # 构建完整的文件路径 - file_path = os.path.join(download_dir, filename) - - # 下载文件(带重试机制) - for attempt in range(max_retries): - try: - logger.info(f"正在下载文件: {url} (尝试 {attempt + 1}/{max_retries})") - - # 发送GET请求下载文件 - response = requests.get(url, timeout=timeout, stream=True) - response.raise_for_status() - - # 写入文件 - with open(file_path, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - if chunk: - f.write(chunk) - - # 验证文件是否下载成功 - if os.path.exists(file_path) and os.path.getsize(file_path) > 0: - logger.info(f"文件下载成功: {file_path} (大小: {os.path.getsize(file_path)} 字节)") - return file_path - else: - raise Exception("下载的文件为空或不存在") - - except requests.exceptions.Timeout: - error_msg = f"请求超时 (超过 {timeout} 秒)" - logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}") - if attempt == max_retries - 1: - raise Exception(f"下载失败:{error_msg}") - - except requests.exceptions.RequestException as e: - error_msg = f"请求异常: {str(e)}" - logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}") - if attempt == max_retries - 1: - raise Exception(f"下载失败:{error_msg}") - - except Exception as e: - error_msg = f"下载过程中发生错误: {str(e)}" - logger.warning(f"{error_msg}, 尝试 {attempt + 1}/{max_retries}") - if attempt == max_retries - 1: - raise Exception(f"下载失败:{error_msg}") - - raise Exception("下载失败:已达到最大重试次数") - - -def _extract_filename_from_url(url: str) -> str: - """ - 从URL中提取文件名 - - Args: - url: 文件URL - - Returns: - str: 提取的文件名 - """ - try: - # 解析URL - parsed_url = urlparse(url) - path = unquote(parsed_url.path) - - # 从路径中提取文件名 - filename = os.path.basename(path) - - # 如果没有找到文件名或文件名为空,使用默认名称 - if not filename or filename == '/': - filename = "downloaded_file" - - # 移除查询参数(如果文件名中包含) - if '?' in filename: - filename = filename.split('?')[0] - - return filename - - except Exception as e: - logger.warning(f"无法从URL提取文件名: {str(e)}, 使用默认文件名") - return "downloaded_file" - - -def upload_file_to_dify( - file_path: str, - base_url: str, - api_key: str, - user: str = "default_user", - verify_ssl: bool = False -) -> dict: - """ - 上传文件到Dify API - - Args: - file_path: 本地文件路径 - base_url: Dify API基础URL (例如: http://192.168.2.236:3001/v1) - api_key: API密钥 - user: 用户标识 - verify_ssl: 是否验证SSL证书 - - Returns: - dict: 上传响应结果 - - Raises: - Exception: 上传失败时抛出异常 - """ - # 检查文件是否存在 - if not os.path.exists(file_path): - raise FileNotFoundError(f"文件不存在: {file_path}") - - # 检查文件大小 - file_size = os.path.getsize(file_path) - file_size_mb = file_size / (1024 * 1024) - logger.info(f"准备上传文件: {file_path} (大小: {file_size_mb:.2f} MB)") - - # 检查文件大小是否超过限制 - if file_size_mb > 10: - logger.warning(f"文件大小 {file_size_mb:.2f} MB 可能超过服务器限制 (10 MB)") - - upload_url = f"{base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - - try: - with open(file_path, "rb") as f: - # 获取文件扩展名和MIME类型 - file_ext = os.path.splitext(file_path)[1].lower() - mime_type = MIME_TYPE_MAP.get(file_ext, "application/octet-stream") - - # 构建文件上传数据 - files = {"file": (os.path.basename(file_path), f, mime_type)} - data = {"user": user} - - # 发送上传请求 - response = requests.post( - upload_url, - headers=headers, - files=files, - data=data, - verify=verify_ssl - ) - - # 检查响应 - if response.status_code == 201: - result = response.json() - logger.info(f"文件上传成功: {result.get('name', 'unknown')} (ID: {result.get('id', 'unknown')})") - return result - else: - error_msg = f"上传失败 (状态码: {response.status_code}): {response.text}" - logger.error(error_msg) - raise requests.exceptions.HTTPError(error_msg) - - except requests.exceptions.RequestException as e: - logger.error(f"上传文件请求失败: {str(e)}") - raise - except Exception as e: - logger.error(f"上传文件失败: {str(e)}") - raise - - - -def check_app_config(base_url: str, api_key: str): - """ - 检查Dify应用的文件上传配置 - - Args: - base_url: Dify API基础URL - api_key: API密钥 - """ - try: - # 获取应用参数配置 - config_url = f"{base_url}/parameters" - headers = {"Authorization": f"Bearer {api_key}"} - - response = requests.get(config_url, headers=headers, verify=False) - response.raise_for_status() - - config = response.json() - logger.info(f"应用配置获取成功") - - # 检查文件上传配置 - file_upload = config.get("file_upload", {}) - if file_upload.get("enabled", False): - logger.info("✓ 文件上传功能已启用") - logger.info(f" - 允许的文件类型: {file_upload.get('allowed_file_types', [])}") - logger.info(f" - 允许的文件扩展名: {file_upload.get('allowed_file_extensions', [])}") - logger.info(f" - 文件大小限制: {file_upload.get('fileUploadConfig', {}).get('image_file_size_limit', 'N/A')} MB") - else: - logger.warning("✗ 文件上传功能未启用") - - return config - - except Exception as e: - logger.error(f"检查应用配置失败: {str(e)}") - return None - - -def test_download_and_upload(): - """ - 测试下载和上传功能的示例 - """ - # 测试用的文件URL - file_url = "http://192.168.2.236:9000/lzwcai/upload/2025-07-29/34b28da03f3c43b0921ba1b76857bbc0/34b28da03f3c43b0921ba1b76857bbc0.JPG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20250729%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250729T075242Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=80f58d37c3bd52fb2b25efa36b5df0d73c8da7c773e7a7066dd6563710c619d6" - - # 上传API配置 - base_url = "http://192.168.2.236:3001/v1" - upload_url = f"{base_url}/files/upload" - api_key = "app-QdfDKqHAI3dlB6tvnibuh6rv" - headers = {"Authorization": f"Bearer {api_key}"} - user = "abc-123" - - try: - logger.info("开始测试下载和上传流程...") - - # 0. 检查应用配置 - logger.info("检查Dify应用配置...") - config = check_app_config(base_url, api_key) - if not config: - logger.error("无法获取应用配置,终止测试") - return - - # 1. 下载文件 - logger.info(f"正在下载文件: {file_url}") - file_path = download_file_from_url(file_url) - logger.info(f"文件下载成功,保存路径: {file_path}") - - # 2. 上传文件 - logger.info(f"正在上传文件到: {upload_url}") - - # 检查文件大小 - file_size = os.path.getsize(file_path) - file_size_mb = file_size / (1024 * 1024) - logger.info(f"文件大小: {file_size_mb:.2f} MB") - - # 检查文件大小是否超过限制(通常为10MB对于图片) - if file_size_mb > 10: - logger.warning(f"文件大小 {file_size_mb:.2f} MB 可能超过服务器限制") - - with open(file_path, "rb") as f: - # 获取文件扩展名来确定MIME类型 - file_ext = os.path.splitext(file_path)[1].lower() - mime_type = "image/jpeg" if file_ext in ['.jpg', '.jpeg'] else "application/octet-stream" - - # 构建文件上传数据,指定正确的MIME类型 - files = {"file": (os.path.basename(file_path), f, mime_type)} - data = {"user": user} - - # 不要在headers中设置Content-Type,让requests自动处理multipart/form-data - upload_headers = headers.copy() - if "Content-Type" in upload_headers: - del upload_headers["Content-Type"] - - # 添加SSL验证跳过选项,用于自签名证书 - response = requests.post(upload_url, headers=upload_headers, files=files, data=data, verify=False) - - # 打印详细的响应信息用于调试 - logger.info(f"响应状态码: {response.status_code}") - logger.info(f"响应头: {response.headers}") - logger.info(f"响应内容: {response.text}") - - response.raise_for_status() - - result = response.json() - logger.info(f"文件上传成功,响应: {result}") - - # 3. 清理临时文件 - try: - os.remove(file_path) - logger.info(f"已清理临时文件: {file_path}") - except Exception as e: - logger.warning(f"清理临时文件失败: {str(e)}") - - return result - - except Exception as e: - logger.error(f"测试失败: {str(e)}") - raise - - -def upload_file_from_url( - file_url: str, - base_url: str, - api_key: str, - user: str = "default_user", - verify_ssl: bool = False -) -> dict: - """ - 从URL下载文件并上传到Dify API - 一站式解决方案 - - 这是一个优化后的方法,只需要提供URL地址、base_url和api_key,就能自动完成下载和上传。 - 支持常见的文件类型:JPG、PNG、GIF、PDF、DOCX、TXT等。 - 自动处理文件大小检查、MIME类型识别、临时文件清理等。 - - Args: - file_url: 要下载的文件URL - base_url: Dify API基础URL (例如: http://192.168.2.236:3001/v1) - api_key: API密钥 (例如: app-QdfDKqHAI3dlB6tvnibuh6rv) - user: 用户标识 (可选,默认为 default_user) - verify_ssl: 是否验证SSL证书 (可选,默认为 False) - - Returns: - dict: 上传成功后的结果,包含文件ID、名称、大小等信息 - 示例: { - 'id': 'a239b623-40a8-482c-859f-bb8368d5b1fe', - 'name': 'example.jpg', - 'size': 495240, - 'extension': 'jpg', - 'mime_type': 'image/jpeg', - 'created_by': '92c4b250-e0e7-4123-900d-f5c2187679a2', - 'created_at': 1753777420 - } - - 使用示例: - result = upload_file_from_url( - file_url="http://example.com/image.jpg", - base_url="http://192.168.2.236:3001/v1", - api_key="app-QdfDKqHAI3dlB6tvnibuh6rv" - ) - file_id = result['id'] # 获取上传后的文件ID - - Raises: - Exception: 下载或上传失败时抛出异常 - """ - temp_file_path = None - try: - logger.info(f"开始处理文件: {file_url}") - - # 1. 下载文件到临时目录 - temp_file_path = download_file_from_url(file_url) - - # 2. 上传文件到Dify (复用已有的上传函数) - result = upload_file_to_dify(temp_file_path, base_url, api_key, user, verify_ssl) - return result - - except Exception as e: - logger.error(f"处理文件失败: {str(e)}") - raise - finally: - # 清理临时文件 - if temp_file_path and os.path.exists(temp_file_path): - try: - os.remove(temp_file_path) - logger.info(f"已清理临时文件: {temp_file_path}") - except Exception as e: - logger.warning(f"清理临时文件失败: {str(e)}") - - -def test_upload_functionality(): - """ - 测试文件上传功能的示例 - 注意:这个函数包含示例配置,实际使用时请替换为真实的配置 - """ - # 示例配置 - 实际使用时请替换为真实的配置 - file_url = "https://example.com/test-image.jpg" # 替换为实际的文件URL - base_url = "http://localhost:3001/v1" # 替换为实际的Dify API地址 - api_key = "your-api-key-here" # 替换为实际的API密钥 - - try: - logger.info("=== 开始测试文件上传功能 ===") - - # 检查应用配置 - logger.info("检查Dify应用配置...") - config = check_app_config(base_url, api_key) - if not config: - logger.warning("无法获取应用配置,但继续测试上传功能") - - # 调用上传方法 - result = upload_file_from_url(file_url, base_url, api_key) - - logger.info("=== 上传成功!结果如下 ===") - logger.info(f"文件ID: {result.get('id')}") - logger.info(f"文件名: {result.get('name')}") - logger.info(f"文件大小: {result.get('size')} 字节") - logger.info(f"文件类型: {result.get('mime_type')}") - logger.info(f"扩展名: {result.get('extension')}") - - return result - - except Exception as e: - logger.error(f"测试失败: {str(e)}") - raise - - -if __name__ == "__main__": - # 运行测试 - 注意:需要先配置正确的URL和API密钥 - print("警告:测试函数包含示例配置,请先修改为实际配置后再运行") - # test_upload_functionality() # 取消注释并配置后运行 \ No newline at end of file diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__init__.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/__init__.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index ae00237..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc deleted file mode 100644 index 7c4f86e..0000000 Binary files a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/workflow_server.py b/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/workflow_server.py deleted file mode 100644 index 97b51bb..0000000 --- a/lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/workflow_server.py +++ /dev/null @@ -1,352 +0,0 @@ -import requests -from abc import ABC -import logging -import json -from src.utils.logger_config import get_logger - -# 导入 pypinyin 用于中文转拼音 -try: - import pypinyin -except ImportError: - pypinyin = None - logging.warning("pypinyin 模块未安装,将使用简化的命名方式") - -logger = get_logger(__name__) - - -class DifyAPIError(Exception): - """Dify API 错误异常类""" - - def __init__(self, status_code: int, error_code: str, message: str, request_data: dict = None): - self.status_code = status_code - self.error_code = error_code - self.message = message - self.request_data = request_data - super().__init__(self.message) - - def __str__(self): - return f"[{self.status_code}] {self.error_code}: {self.message}" - - def to_dict(self): - return { - "status_code": self.status_code, - "error_code": self.error_code, - "message": self.message - } - -def pinyin_to_camel(pinyin): - """ - 将中文名称转换为工具名称 - - 处理逻辑: - 1. 如果安装了 pypinyin,将中文转换为拼音,然后转为驼峰命名 - 2. 如果未安装 pypinyin,将所有非字母数字字符替换为下划线 - 3. 所有符号都会被替换成下划线 - - 示例: - "你好啊" -> "tool_NiHaoA" (有pypinyin) - "测试-工具" -> "tool_测试_工具" (无pypinyin) - "Hello World!" -> "tool_Hello_World_" (无pypinyin) - - Args: - pinyin: 输入的字符串(可能包含中文、英文、符号等) - - Returns: - str: 格式化后的工具名称,以 "tool_" 开头 - """ - import re - - if pypinyin is None: - # 如果 pypinyin 未安装,使用简化的命名方式 - # 将所有非字母数字字符(包括空格、符号等)替换为下划线 - cleaned = re.sub(r'[^\w]', '_', str(pinyin)) - # 移除连续的下划线 - cleaned = re.sub(r'_+', '_', cleaned) - # 移除首尾的下划线 - cleaned = cleaned.strip('_') - return "tool_" + cleaned if cleaned else "tool_unnamed" - - # 使用 pypinyin 转换中文为拼音 - pinyin_list = pypinyin.lazy_pinyin(pinyin) - - # 处理每个拼音单词 - processed_words = [] - for word in pinyin_list: - # 将所有非字母数字字符替换为下划线 - cleaned_word = re.sub(r'[^\w]', '_', word) - # 移除连续的下划线 - cleaned_word = re.sub(r'_+', '_', cleaned_word) - # 移除首尾的下划线 - cleaned_word = cleaned_word.strip('_') - - if cleaned_word: - # 首字母大写(驼峰命名) - processed_words.append(cleaned_word.capitalize()) - - # 拼接所有单词 - result = "".join(processed_words) if processed_words else "Unnamed" - return "tool_" + result - - -class WorkflowDifyAPI(ABC): - def __init__(self, base_url: str, dify_app_sks: list, user="pp666"): - # dify configs - self.dify_base_url = base_url - self.dify_app_sks = dify_app_sks - self.user = user - - # dify app infos - dify_app_infos = [] - dify_app_params = [] - dify_app_metas = [] - for key in self.dify_app_sks: - dify_app_infos.append(self.get_app_info(key)) - dify_app_params.append(self.get_app_parameters(key)) - dify_app_metas.append(self.get_app_meta(key)) - - self.dify_app_infos = dify_app_infos - self.dify_app_params = dify_app_params - self.dify_app_metas = dify_app_metas - self.dify_app_names = [x["name"] for x in dify_app_infos] - - def chat_message( - self, - api_key, - inputs={}, - response_mode="streaming", - conversation_id=None, - userId="pp666", - files=None, - ): - url = f"{self.dify_base_url}/workflows/run" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = { - "inputs": inputs, - "response_mode": response_mode, - "user": userId, - } - logger.info("Sending data to Dify API: %s", data) - logger.info("Sending headers to Dify API: %s", headers) - logger.info("Sending url to Dify API: %s", url) - if conversation_id: - data["conversation_id"] = conversation_id - if files: - files_data = self.file_parameter_pretreatment(files) - if files_data and len(files_data) > 0: - data["inputs"]["files"] = files_data[0] - # For workflow API, we send files data in the JSON payload, not as multipart files - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - else: - response = requests.post( - url, headers=headers, json=data, stream=response_mode == "streaming" - ) - logger.info(f"Response1:{data} {response.status_code} {response.reason}") - - # Add debugging for error responses - if response.status_code != 200: - logger.error(f"API request failed with status {response.status_code}") - logger.error(f"Response content: {response.text}") - logger.error(f"Request data: {data}") - - # 解析错误响应并抛出带有详细信息的异常 - try: - error_data = response.json() - error_message = error_data.get("message", response.text) - error_code = error_data.get("code", "unknown_error") - except json.JSONDecodeError: - error_message = response.text - error_code = "unknown_error" - - raise DifyAPIError( - status_code=response.status_code, - error_code=error_code, - message=error_message, - request_data=data - ) - if response_mode == "streaming": - def stream_generator(): - for line in response.iter_lines(): - if line: - if line.startswith(b"data:"): - try: - json_data = json.loads(line[5:].decode("utf-8")) - yield json_data - except json.JSONDecodeError: - logger.error(f"Error decoding JSON: {line}") - return stream_generator() - else: - return response.json() - - def upload_file(self, api_key, file_path, user="pp666"): - url = f"{self.dify_base_url}/files/upload" - headers = {"Authorization": f"Bearer {api_key}"} - data = {"user": user} - - with open(file_path, "rb") as f: - files = {"file": f} - response = requests.post(url, headers=headers, files=files, data=data) - - response.raise_for_status() - return response.json() - def upload_file_remote_url(self, file_url): - from src.utils.upload_file import upload_file_from_url - base_url = self.dify_base_url - api_key = self.dify_app_sks[0] - return upload_file_from_url(file_url, base_url, api_key) - - def file_parameter_pretreatment(self, files): - """ - 文件参数预处理方法 - - 传入的"files"数据结构是这样的: [ - { - "type": "image", - "transfer_method": "remote_url", - "url": "http://example.com/image.jpg" - } - ] - - 处理逻辑: - 1. 遍历files列表中的每个文件对象 - 2. 对于transfer_method为"remote_url"的文件,调用upload_file_remote_url方法 - 3. 将返回的对象的id字段存入原对象的upload_file_id字段 - 4. 设置transfer_method为"local_file"(因为已经上传到Dify服务器) - 5. 返回处理好的files列表 - - Args: - files (list): 文件列表,每个元素包含type、transfer_method、url等字段 - - Returns: - list: 处理后的文件列表,每个文件对象包含upload_file_id和transfer_method字段 - """ - if not files or not isinstance(files, list): - logger.warning("文件参数为空或格式不正确") - return files - - processed_files = [] - - for file_obj in files: - # 创建文件对象的副本,避免修改原始数据 - processed_file = file_obj.copy() - - # 检查是否需要处理远程URL文件 - if (processed_file.get("transfer_method") == "remote_url" and - processed_file.get("url")): - - try: - logger.info(f"开始上传远程文件: {processed_file['url']}") - - # 调用upload_file_remote_url方法:下载文件并上传到Dify - upload_result = self.upload_file_remote_url(processed_file["url"]) - - # 将返回的对象的id存入upload_file_id字段 - if upload_result and "id" in upload_result: - processed_file["upload_file_id"] = upload_result["id"] - # 修改transfer_method为local_file,因为文件已经上传到Dify服务器 - processed_file["transfer_method"] = "local_file" - # 移除url字段,因为已经不需要了 - processed_file.pop("url", None) - - logger.info(f"文件上传成功 - ID: {upload_result['id']}, " - f"名称: {upload_result.get('name', 'N/A')}, " - f"大小: {upload_result.get('size', 'N/A')} bytes") - else: - logger.error(f"文件上传失败,未获取到有效的文件ID,响应: {upload_result}") - processed_file["upload_error"] = "未获取到有效的文件ID" - - except Exception as e: - logger.error(f"文件上传过程中发生错误: {str(e)}", exc_info=True) - # 记录错误信息,但继续处理其他文件 - processed_file["upload_error"] = str(e) - - elif processed_file.get("transfer_method") == "local_file": - # 如果已经是local_file,确保有upload_file_id - if not processed_file.get("upload_file_id"): - logger.warning("local_file类型的文件缺少upload_file_id字段") - - processed_files.append(processed_file) - - logger.info(f"文件预处理完成,共处理 {len(processed_files)} 个文件") - return processed_files - def stop_response(self, api_key, task_id, user="pp666"): - - url = f"{self.dify_base_url}/chat-messages/{task_id}/stop" - headers = { - "Authorization": f"Bearer {api_key}", - "Content-Type": "application/json", - } - data = {"user": user} - response = requests.post(url, headers=headers, json=data) - response.raise_for_status() - return response.json() - - def get_app_info(self, api_key, user="pp666"): - - url = f"{self.dify_base_url}/info" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - - response_map = response.json() - - # 翻译工具名称 - tool_name = response_map.get("name") - if tool_name: - # translated_name = TranslationService.translate_tool_name(tool_name) - translated_name = pinyin_to_camel(tool_name) - response_map["name"] = translated_name - - # 翻译工具描述 - # tool_description = response_map.get("description") - # if tool_description: - # translated_description = TranslationService.translate_tool_description( - # tool_description - # ) - # response_map["description"] = ( - # f"{tool_description} ({translated_description})" - # ) - - return response_map - - def get_app_parameters(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/parameters" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - - logger.info(f"调用 /parameters API: {url}") - logger.info(f"请求头: {headers}") - logger.info(f"请求参数: {params}") - - response = requests.get(url, headers=headers, params=params) - - logger.info(f"/parameters API 响应状态码: {response.status_code}") - - response.raise_for_status() - - response_data = response.json() - logger.info(f"/parameters API 响应数据: {response_data}") - - return response_data - - def get_app_meta(self, api_key, user="pp666"): - url = f"{self.dify_base_url}/meta" - headers = {"Authorization": f"Bearer {api_key}"} - params = {"user": user} - response = requests.get(url, headers=headers, params=params) - response.raise_for_status() - return response.json() - - - -if __name__ == "__main__": - dify_api = WorkflowDifyAPI( - "https://ops.lzwcai.com/v1", - ["app-ZmLuBlRmViseUdOonqLyNSku", "app-AHjfp8k4nawQSJi0us8x3J5Q"], - ) - dify_api.upload_file_remote_url("http://192.168.2.236:9000/lzwcai/upload/2025-07-29/34b28da03f3c43b0921ba1b76857bbc0/34b28da03f3c43b0921ba1b76857bbc0.JPG?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minio%2F20250729%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250729T075242Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=80f58d37c3bd52fb2b25efa36b5df0d73c8da7c773e7a7066dd6563710c619d6") diff --git a/lzwcai_mcp_agile_db/README.md b/lzwcai_mcp_agile_db/README.md deleted file mode 100644 index 8ae50f6..0000000 --- a/lzwcai_mcp_agile_db/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# lzwcai-mcp-agile-db - -数据库管理平台 MCP Server,提供 33 个工具用于数据库管理、表操作、数据 CRUD、API 密钥管理、技能与工具管理等。 - -## 环境变量 - -| 变量名 | 必填 | 说明 | -|--------|------|------| -| `API_KEY` | 是 | 数据库管理平台的 API 密钥(格式: `Bearer `) | -| `backendBaseUrl` | 否 | 数据库管理平台后端地址(默认 `http://lzwcai-demp-corp-manager:8086`) | - -## 安装 - -```bash -pip install -e . -``` - -## 运行 - -```bash -# 设置环境变量 -export API_KEY="Bearer your-token" -export backendBaseUrl="https://dempdemo.lzwcai.com" # 可选 - -# 运行 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 注册和启动逻辑 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version deleted file mode 100644 index e4fba21..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/README.md b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py deleted file mode 100644 index 72715c3..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""lzwcai-mcp-agile-db MCP Server 包""" - -from .server import main - -__all__ = ["main"] diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a0a9bb2..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/server.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/server.cpython-312.pyc deleted file mode 100644 index 9caadc5..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db.log b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db.log deleted file mode 100644 index 1379807..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db.log +++ /dev/null @@ -1,202 +0,0 @@ -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: -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: -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: -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= -2026-06-11 09:33:46 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context= 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= -2026-06-11 09:33:46 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -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= -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= -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= -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= -2026-06-11 09:39:21 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context= 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= -2026-06-11 09:39:21 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -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= -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= -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= -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: -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: -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= -2026-06-11 09:40:08 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context= 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= -2026-06-11 09:40:08 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -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= -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= -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= -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: -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= -2026-06-11 09:40:55 - httpcore.connection - DEBUG - [_trace.py:47] - start_tls.started ssl_context= 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= -2026-06-11 09:40:55 - httpcore.http11 - DEBUG - [_trace.py:47] - send_request_headers.started request= -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= -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= -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= -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 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db_error.log b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db_error.log deleted file mode 100644 index fa261fd..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db_error.log +++ /dev/null @@ -1,15 +0,0 @@ -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: 登录过期,请重新登录 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py deleted file mode 100644 index 035da04..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/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() diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py deleted file mode 100644 index d47fce1..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -工具自动发现模块 -自动导入 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__) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index a9dadb2..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/_base.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/_base.cpython-312.pyc deleted file mode 100644 index 282110b..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/_base.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/api_keys.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/api_keys.cpython-312.pyc deleted file mode 100644 index bf3dd89..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/api_keys.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/data_import.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/data_import.cpython-312.pyc deleted file mode 100644 index e15c973..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/data_import.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/database_tables.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/database_tables.cpython-312.pyc deleted file mode 100644 index 8f85dc8..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/database_tables.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/datasources.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/datasources.cpython-312.pyc deleted file mode 100644 index 0e71be6..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/datasources.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/skills.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/skills.cpython-312.pyc deleted file mode 100644 index 8a0ffae..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/skills.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/sql_execution.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/sql_execution.cpython-312.pyc deleted file mode 100644 index c327e76..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/sql_execution.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/subscriptions.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/subscriptions.cpython-312.pyc deleted file mode 100644 index f88c827..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/subscriptions.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/table_data.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/table_data.cpython-312.pyc deleted file mode 100644 index cde5aac..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/table_data.cpython-312.pyc and /dev/null differ 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 deleted file mode 100644 index 7b3468c..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -工具注册框架 -提供 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("/datasource/...", 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() 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 deleted file mode 100644 index 16b29c5..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -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 await self.client.get("/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 await self.client.post("/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 await self.client.put("/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 await self.client.delete(f"/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 await self.client.get(f"/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 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 deleted file mode 100644 index 26c2478..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -数据导入工具 (工具 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 await self.client.upload( - f"/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 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 deleted file mode 100644 index e524b44..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -数据库与表管理工具 (工具 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 await self.client.get("/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 await self.client.get("/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 await self.client.get(f"/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 await self.client.post(f"/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 await self.client.put(f"/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 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 deleted file mode 100644 index c38ea17..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py +++ /dev/null @@ -1,173 +0,0 @@ -""" -数据源管理工具 (工具 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 await self.client.get("/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"] = await self.client.get(f"/datasource/connection/{ds_id}") - except Exception as e: - result["detail"] = {"error": str(e)} - # 获取配置 - try: - result["config"] = await self.client.get(f"/datasource/config/{ds_id}") - except Exception as e: - result["config"] = {"error": str(e)} - # 获取实时结构 - try: - result["structure"] = await self.client.get(f"/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 = 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 await self.client.post("/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 await self.client.put("/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 await self.client.put("/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: - await self.client.put("/datasource/connection/changeStatus", json_data={"id": ds_id, "status": 1}) - except Exception as e: - # 记录日志但继续删除 - logger.debug(f"停用数据源失败(可能已停用): {e}") - - # 删除数据源 - 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 deleted file mode 100644 index 1a3af12..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -技能与工具管理工具 (工具 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 await self.client.get(f"/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 await self.client.get(f"/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 await self.client.post("/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 await self.client.post("/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 await self.client.delete(f"/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 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 deleted file mode 100644 index 07ddf92..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -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"}, - "sql": {"type": "string", "description": "SQL 语句"}, - "target": {"type": "string", "enum": ["prod", "test"], "default": "prod", "description": "环境,默认 prod"}, - "sqlTemplate": {"type": "string", "description": "SQL 模板(可选)"}, - "businessName": {"type": "string", "description": "业务名称(可选)"}, - "params": {"type": "object", "description": "SQL 参数对象(可选)"}, - }, - "required": ["datasourceId", "sql"], - } - - async def execute(self, args: dict) -> dict: - # 映射参数名为后端 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 deleted file mode 100644 index 6080cf0..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -表订阅工具 (工具 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": { - "tableId": {"type": "string", "description": "表 ID"}, - "datasourceId": {"type": "string", "description": "数据源 ID"}, - "subscribe": {"type": "boolean", "description": "true=订阅, false=取消订阅"}, - }, - "required": ["tableId", "datasourceId", "subscribe"], - } - - async def execute(self, args: dict) -> dict: - # 映射参数名为后端 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 deleted file mode 100644 index 67bf8c0..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -表数据 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 await self.client.get(f"/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 await self.client.post(f"/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 await self.client.put(f"/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 await self.client.delete(f"/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 = await self.client.get(f"/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 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py deleted file mode 100644 index 1b6c953..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -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', -] diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index af8ee54..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/api_client.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/api_client.cpython-312.pyc deleted file mode 100644 index 995bf01..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/api_client.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/env_config.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/env_config.cpython-312.pyc deleted file mode 100644 index 0a4b061..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/env_config.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/logger_config.cpython-312.pyc b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index 6c581fd..0000000 Binary files a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/logger_config.cpython-312.pyc and /dev/null differ 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 deleted file mode 100644 index c0dd25a..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -数据库管理平台 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(默认从环境变量 backendBaseUrl 读取) - api_key: API 密钥(默认从环境变量 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.AsyncClient] = None - - logger.info(f"[客户端初始化] base_url={self.base_url}") - - @property - def client(self) -> httpx.AsyncClient: - """懒加载 HTTP 客户端""" - if self._client is None: - 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]: - """获取请求头""" - 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,自动去掉路径中多余的 /api 前缀""" - if path.startswith('http://') or path.startswith('https://'): - return path - # 去掉 /api 前缀,因为 base_url 已经包含完整地址 - clean_path = path - if clean_path.startswith('/api'): - clean_path = clean_path[4:] - return f"{self.base_url}{clean_path}" - - 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 - - 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 = 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}") - 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)}") - - 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 = 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}") - 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)}") - - 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 = 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}") - 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)}") - - 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: - logger.info(f"[API请求] DELETE {url}") - headers = self._get_headers() - if json_data is not None: - headers['Content-Type'] = 'application/json' - 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}") - 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)}") - - 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 = 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}") - 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)}") - - async def close(self): - """关闭 HTTP 客户端""" - if self._client is not None: - await self._client.aclose() - self._client = None - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - await 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 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 deleted file mode 100644 index 6e19b1d..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py +++ /dev/null @@ -1,60 +0,0 @@ -"""环境变量配置模块 - 数据库管理平台 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: 当 API_KEY 未设置且无默认值时 - """ - value = os.environ.get("API_KEY", default or "") - if not value: - raise ValueError("环境变量 API_KEY 未设置") - return value - - -def get_base_url(default: str = "http://lzwcai-demp-corp-manager:8086") -> str: - """ - 获取后端服务地址 - - Args: - default: 默认值(默认 http://lzwcai-demp-corp-manager:8086) - - Returns: - str: 后端 API 基础 URL - """ - return os.environ.get("backendBaseUrl", 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 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py deleted file mode 100644 index 4e17b5c..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py +++ /dev/null @@ -1,121 +0,0 @@ -# -*- 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) diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md deleted file mode 100644 index 33cec1a..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md +++ /dev/null @@ -1,621 +0,0 @@ -# 数据库管理平台 - 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` 中实现对应函数,可直接复用 diff --git a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md b/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md deleted file mode 100644 index 4ce2b3a..0000000 --- a/lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md +++ /dev/null @@ -1,303 +0,0 @@ -# 数据库管理平台 - 功能总览 - -> 本文档梳理 `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 内容,自动匹配表结构,支持表结构和数据两种导入模式 diff --git a/lzwcai_mcp_agile_db/main.py b/lzwcai_mcp_agile_db/main.py deleted file mode 100644 index 4fea9dd..0000000 --- a/lzwcai_mcp_agile_db/main.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -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["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 deleted file mode 100644 index a740844..0000000 --- a/lzwcai_mcp_agile_db/pyproject.toml +++ /dev/null @@ -1,34 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "lzwcai-mcp-agile-db" -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" -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"] diff --git a/lzwcai_mcp_api_converter/PKG-INFO b/lzwcai_mcp_api_converter/PKG-INFO deleted file mode 100644 index 8609c74..0000000 --- a/lzwcai_mcp_api_converter/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-mcp-dyntoolapi -Version: 0.1.27 -Summary: 基于FastMCP框架的动态API工具服务器,自动将企业业务API配置转换为MCP协议工具,支持多种传输方式、企业认证和参数验证,为AI助手提供标准化的业务接口访问能力。 -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -Requires-Dist: dynaconf>=3.2.11 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: jinja2==3.1.6 -Requires-Dist: mcp[cli]>=1.8.0 -Requires-Dist: requests>=2.31.0 -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/PKG-INFO b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/PKG-INFO deleted file mode 100644 index 1b601bb..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-mcp-api-converter -Version: 0.2.5 -Summary: 基于FastMCP框架的动态API工具服务器,自动将企业业务API配置转换为MCP协议工具,支持多种传输方式、企业认证和参数验证,为AI助手提供标准化的业务接口访问能力。 -Requires-Python: >=3.10 -Description-Content-Type: text/markdown -Requires-Dist: dynaconf>=3.2.11 -Requires-Dist: httpx>=0.28.1 -Requires-Dist: jinja2==3.1.6 -Requires-Dist: mcp[cli]>=1.8.0 -Requires-Dist: requests>=2.31.0 -Requires-Dist: pypinyin>=0.54.0 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/SOURCES.txt b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/SOURCES.txt deleted file mode 100644 index 48815ae..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/SOURCES.txt +++ /dev/null @@ -1,25 +0,0 @@ -pyproject.toml -setup.cfg -lzwcai_mcp_api_converter/__init__.py -lzwcai_mcp_api_converter.egg-info/PKG-INFO -lzwcai_mcp_api_converter.egg-info/SOURCES.txt -lzwcai_mcp_api_converter.egg-info/dependency_links.txt -lzwcai_mcp_api_converter.egg-info/entry_points.txt -lzwcai_mcp_api_converter.egg-info/requires.txt -lzwcai_mcp_api_converter.egg-info/top_level.txt -lzwcai_mcp_api_converter/src/__init__.py -lzwcai_mcp_api_converter/src/api_config.json -lzwcai_mcp_api_converter/src/create_mcp.py -lzwcai_mcp_api_converter/src/business/__init__.py -lzwcai_mcp_api_converter/src/business/business_util.py -lzwcai_mcp_api_converter/src/business/get_business_api.py -lzwcai_mcp_api_converter/src/core/__init__.py -lzwcai_mcp_api_converter/src/core/api_auth_service.py -lzwcai_mcp_api_converter/src/core/api_base.py -lzwcai_mcp_api_converter/src/core/core_server.py -lzwcai_mcp_api_converter/src/core/get_auth.py -lzwcai_mcp_api_converter/src/core/plugin_base.py -lzwcai_mcp_api_converter/src/util/__init__.py -lzwcai_mcp_api_converter/src/util/api_helper.py -lzwcai_mcp_api_converter/src/util/logger_config.py -lzwcai_mcp_api_converter/src/util/nested_value.py \ No newline at end of file diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/dependency_links.txt b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/entry_points.txt b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/entry_points.txt deleted file mode 100644 index 7504d25..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -lzwcai-mcp-api-converter = lzwcai_mcp_api_converter.src.create_mcp:run_main diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/requires.txt b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/requires.txt deleted file mode 100644 index df9559c..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/requires.txt +++ /dev/null @@ -1,6 +0,0 @@ -dynaconf>=3.2.11 -httpx>=0.28.1 -jinja2==3.1.6 -mcp[cli]>=1.8.0 -requests>=2.31.0 -pypinyin>=0.54.0 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/top_level.txt b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/top_level.txt deleted file mode 100644 index f33e29f..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -lzwcai_mcp_api_converter diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.log b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.log deleted file mode 100644 index 46fc2d7..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.log +++ /dev/null @@ -1,96 +0,0 @@ -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:57:40 -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 17:57:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:57:52 -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 17:57:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:58:11 -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 17:58:11 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:58:59 -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 17:58:59 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 17:59:12 -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 17:59:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:04:50 -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:04:50 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:06:12 -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:06:12 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:10:29 -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:10:29 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:10:40 -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:10:40 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:11:20 -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:11:20 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:13:17 -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:13:17 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:215] - ================================================================================ -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2026-03-18 18:16:52 -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:217] - 日志级别: INFO -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_api_converter\lzwcai_mcp_api_converter.log -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:219] - 控制台输出: False -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:220] - 文件输出: True -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2026-03-18 18:16:52 - lzwcai_mcp_api_converter.src.util.logger_config - INFO - [logger_config.py:222] - ================================================================================ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__init__.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 1d71216..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__init__.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 748969b..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/create_mcp.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/create_mcp.cpython-312.pyc deleted file mode 100644 index b7f6256..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/create_mcp.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config.json b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config.json deleted file mode 100644 index 9e26dfe..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_dcqwlucfo7h.json b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_dcqwlucfo7h.json deleted file mode 100644 index 347b642..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_dcqwlucfo7h.json +++ /dev/null @@ -1,555 +0,0 @@ -{ - "serverName": "lzwcai_mcp_api_converter", - "description": "金蝶销售报表", - "domainUrl": "http://39.108.116.74", - "packageName": "lzwcai-mcp-dyntoolapi", - "version": "1.0.0", - "apiConfig": [ - { - "id": "2033382693160300546", - "enterpriseId": "1932095424144715777", - "bizSysId": "2029468454441897985", - "domainUrl": "http://39.108.116.74", - "interfaceName": "金蝶销售报表", - "businessPrompts": "金蝶销售报表", - "returnType": "JSON", - "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", - "header": null, - "apiUrl": "/K3Cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.GetSysReportData.common.kdsvc", - "parametersFormat": "2", - "method": "POST", - "status": 1, - "version": "1.0.0", - "authenticationRequired": 1, - "responseExample": "{\"Result\": {\"IsSuccess\": true, \"RowCount\": 7954, \"Rows\": [[\"销售出库单\", \"101XSCKD20250100553\", \"金属粉\", \"1,194.69\", \"1,048.65\"], [\"应收单\", \"101YSD2025010486\", \"金属粉\", \"\", \"\"], [\"应收单\", \"101YSD2025011218\", \"金属粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010486-000001\", \"金属粉\", \"\", \"\"], [\"\", \"\", \"金属粉\", \"1194.69\", \"1048.65\"], [\"应收单\", \"101YSD2025011218\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010058-000001\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100073\", \"胶水转印纸\", \"1,858.41\", \"2,498.98\"], [\"应收单\", \"101YSD2025010058\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"1858.41\", \"2498.98\"], [\"应收单\", \"101YSD2025010161\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100170\", \"胶水转印纸\", \"663.72\", \"690.61\"], [\"应收单\", \"101YSD2025011218\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010161-000001\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"663.72\", \"690.61\"], [\"销售出库单\", \"101XSCKD20250100318\", \"胶水转印纸\", \"973.45\", \"1,381.22\"], [\"应收单\", \"101YSD2025010329\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011218\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010329-000001\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"973.45\", \"1381.22\"], [\"应收单\", \"101YSD2025010553-000001\", \"单色粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010553\", \"单色粉\", \"\", \"\"], [\"应收单\", \"101YSD2025011218\", \"单色粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100625\", \"单色粉\", \"3,743.36\", \"1,950.23\"], [\"\", \"\", \"单色粉\", \"3743.36\", \"1950.23\"], [\"\", \"\", \"\", \"8433.63\", \"7569.69\"], [\"应收单\", \"101YSD2025011292\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010306-000001\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010306\", \"转印粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100360\", \"转印粉\", \"5,238.94\", \"2,841.64\"], [\"\", \"\", \"转印粉\", \"5238.94\", \"2841.64\"], [\"\", \"\", \"\", \"5238.94\", \"2841.64\"], [\"应收单\", \"101YSD2025011290\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025011290\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298-000001\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298-000001\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298\", \"树脂类\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100349\", \"树脂类\", \"117,244.91\", \"106,586.28\"], [\"销售出库单\", \"101XSCKD20250100349\", \"树脂类\", \"20,401.11\", \"18,546.46\"], [\"\", \"\", \"树脂类\", \"137646.02\", \"125132.74\"], [\"销售出库单\", \"101XSCKD20250100349\", \"树脂类\", \"117,982.3\", \"107,256.64\"], [\"应收单\", \"101YSD2025010298\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298-000001\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025011290\", \"树脂类\", \"\", \"\"], [\"\", \"\", \"树脂类\", \"117982.3\", \"107256.64\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010332-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010332\", \"真空转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100347\", \"真空转印纸\", \"973.45\", \"1,381.22\"], [\"\", \"\", \"真空转印纸\", \"973.45\", \"1381.22\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010456-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010456\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100523\", \"胶水转印纸\", \"1,460.18\", \"2,071.81\"], [\"\", \"\", \"胶水转印纸\", \"1460.18\", \"2071.81\"], [\"销售出库单\", \"101XSCKD20250100074\", \"真空转印纸\", \"2,787.61\", \"4,077.93\"], [\"应收单\", \"101YSD2025010055-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010055\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"\", \"\", \"真空转印纸\", \"2787.61\", \"4077.93\"], [\"销售出库单\", \"101XSCKD20250100410\", \"胶水转印纸\", \"973.45\", \"1,381.22\"], [\"应收单\", \"101YSD2025010355\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010355-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"973.45\", \"1381.22\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010504-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010504\", \"真空转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100591\", \"真空转印纸\", \"663.72\", \"690.61\"], [\"\", \"\", \"真空转印纸\", \"663.72\", \"690.61\"], [\"销售出库单\", \"101XSCKD20250100069\", \"真空转印纸\", \"663.72\", \"690.61\"], [\"应收单\", \"101YSD2025010082\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010082-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"\", \"\", \"真空转印纸\", \"663.72\", \"690.61\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010419-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010419\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100477\", \"胶水转印纸\", \"973.45\", \"1,076.3\"], [\"\", \"\", \"胶水转印纸\", \"973.45\", \"1076.3\"], [\"应收单\", \"101YSD2025010524\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100574\", \"胶水转印纸\", \"973.45\", \"1,381.22\"], [\"应收单\", \"101YSD2025010524-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"973.45\", \"1381.22\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010517-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010055-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010055\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010517\", \"真空转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100514\", \"真空转印纸\", \"973.45\", \"1,378.58\"], [\"销售出库单\", \"101XSCKD20250100031\", \"真空转印纸\", \"5,110.62\", \"7,582.18\"], [\"\", \"\", \"真空转印纸\", \"6084.07\", \"8960.76\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010822-000001\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100695\", \"胶水转印纸\", \"1,858.41\", \"2,762.4\"], [\"应收单\", \"101YSD2025010822\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"1858.41\", \"2762.4\"], [\"销售出库单\", \"101XSCKD20250100349\", \"树脂类\", \"57,823.01\", \"52,566.37\"], [\"应收单\", \"101YSD2025010298\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025010298-000001\", \"树脂类\", \"\", \"\"], [\"应收单\", \"101YSD2025011290\", \"树脂类\", \"\", \"\"], [\"\", \"\", \"树脂类\", \"57823.01\", \"52566.37\"], [\"应收单\", \"101YSD2025010075-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010075\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100104\", \"胶水转印纸\", \"2,323.01\", \"2,632.18\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"2323.01\", \"2632.18\"], [\"销售出库单\", \"101XSCKD20250100342\", \"耐候转印纸\", \"442.48\", \"147.24\"], [\"应收单\", \"101YSD2025010332-000001\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010332\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"耐候转印纸\", \"\", \"\"], [\"\", \"\", \"耐候转印纸\", \"442.48\", \"147.24\"], [\"应收单\", \"101YSD2025011291\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010332\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010332-000001\", \"耐候转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100342\", \"耐候转印纸\", \"442.48\", \"147.24\"], [\"\", \"\", \"耐候转印纸\", \"442.48\", \"147.24\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100269\", \"胶水转印纸\", \"4,646.02\", \"6,906.05\"], [\"应收单\", \"101YSD2025010239\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010239-000001\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"4646.02\", \"6906.05\"], [\"销售出库单\", \"101XSCKD20250100630\", \"耐候转印纸\", \"1,548.67\", \"695.83\"], [\"应收单\", \"101YSD2025010568\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010568-000001\", \"耐候转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"耐候转印纸\", \"\", \"\"], [\"\", \"\", \"耐候转印纸\", \"1548.67\", \"695.83\"], [\"应收单\", \"101YSD2025010607\", \"胶水转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100668\", \"胶水转印纸\", \"973.45\", \"1,381.22\"], [\"应收单\", \"101YSD2025010607-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025011291\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"973.45\", \"1381.22\"], [\"\", \"\", \"\", \"341238.95\", \"321339.59\"], [\"销售出库单\", \"101XSCKD20250100206\", \"真空转印纸\", \"2,123.89\", \"2,762.4\"], [\"应收单\", \"101YSD2025010176\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010833\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010176-000001\", \"真空转印纸\", \"\", \"\"], [\"\", \"\", \"真空转印纸\", \"2123.89\", \"2762.4\"], [\"销售出库单\", \"101XSCKD20250100153\", \"真空转印纸\", \"2,123.89\", \"2,454.46\"], [\"应收单\", \"101YSD2025010140\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010140-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010832\", \"真空转印纸\", \"\", \"\"], [\"\", \"\", \"真空转印纸\", \"2123.89\", \"2454.46\"], [\"应收单\", \"101YSD2025010832\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010140-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010140\", \"真空转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100153\", \"真空转印纸\", \"1,061.95\", \"1,052.25\"], [\"\", \"\", \"真空转印纸\", \"1061.95\", \"1052.25\"], [\"销售出库单\", \"101XSCKD20250100153\", \"真空转印纸\", \"2,123.89\", \"2,762.41\"], [\"应收单\", \"101YSD2025010140-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010140\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010832\", \"真空转印纸\", \"\", \"\"], [\"\", \"\", \"真空转印纸\", \"2123.89\", \"2762.41\"], [\"应收单\", \"101YSD2025010832\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138-000001\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138\", \"转印粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100151\", \"转印粉\", \"6,469.03\", \"3,585.61\"], [\"\", \"\", \"转印粉\", \"6469.03\", \"3585.61\"], [\"应收单\", \"101YSD2025010138\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138-000001\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010832\", \"转印粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100151\", \"转印粉\", \"15,601.77\", \"8,618.39\"], [\"\", \"\", \"转印粉\", \"15601.77\", \"8618.39\"], [\"应收单\", \"101YSD2025010832\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138-000001\", \"转印粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100151\", \"转印粉\", \"9,399.12\", \"5,503.47\"], [\"\", \"\", \"转印粉\", \"9399.12\", \"5503.47\"], [\"销售出库单\", \"101XSCKD20250100151\", \"转印粉\", \"3,101.33\", \"1,672.56\"], [\"应收单\", \"101YSD2025010138-000001\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010832\", \"转印粉\", \"\", \"\"], [\"\", \"\", \"转印粉\", \"3101.33\", \"1672.56\"], [\"应收单\", \"101YSD2025010176-000001\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010833\", \"真空转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010176\", \"真空转印纸\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100206\", \"真空转印纸\", \"1,061.95\", \"1,381.22\"], [\"\", \"\", \"真空转印纸\", \"1061.95\", \"1381.22\"], [\"应收单\", \"101YSD2025010832\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138-000001\", \"转印粉\", \"\", \"\"], [\"应收单\", \"101YSD2025010138\", \"转印粉\", \"\", \"\"], [\"销售出库单\", \"101XSCKD20250100151\", \"转印粉\", \"2,674.78\", \"1,557.46\"], [\"\", \"\", \"转印粉\", \"2674.78\", \"1557.46\"], [\"\", \"\", \"\", \"45741.6\", \"31350.23\"], [\"销售出库单\", \"101XSCKD20250100276\", \"胶水转印纸\", \"7,306.19\", \"7,398.51\"], [\"销售退货单\", \"101XSTHD20250127283\", \"胶水转印纸\", \"-7,433.63\", \"-7,527.56\"], [\"应收单\", \"101YSD2025010791\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010790\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010243\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010076\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010243-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010076-000001\", \"胶水转印纸\", \"\", \"\"], [\"\", \"\", \"胶水转印纸\", \"-127.44\", \"-129.05\"], [\"应收单\", \"101YSD2025010790\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010076-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010076-000001\", \"胶水转印纸\", \"\", \"\"], [\"应收单\", \"101YSD2025010243-000001\", \"胶水转印纸\", \"\", \"\"]]}}", - "crudType": "0", - "isView": 0, - "templateType": "markdown", - "viewTemplates": null, - "parameters": [ - { - "keyParam": null, - "required": 0, - "paramName": "formId", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "1", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "2", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.FieldKeys", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "3", - "tags": "0", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.SchemeId", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "4", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.StartRow", - "paramType": "INTEGER", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "5", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Limit", - "paramType": "INTEGER", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "6", - "tags": "1", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.IsVerifyBaseDataField", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "7", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "8", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartDate", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "9", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndDate", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "10", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartMaterial", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "11", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartMaterial.FNUMBER", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "12", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndMaterial", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "13", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndMaterial.FNUMBER", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "14", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndCustomer", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "15", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndCustomer.FNumber", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "16", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartCustomer", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "17", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartCustomer.FNumber", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "18", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndDepartment", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "19", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FEndDepartment.FNUMBER", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "20", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartDepartment", - "paramType": "OBJECT", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "21", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FStartDepartment.FNUMBER", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "22", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FBillStatus", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "23", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FIsIncludeSerMat", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "24", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FIsIncludeInnerExchangeBill", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "25", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FSettleOrgId", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "26", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FSaleOrgId", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "27", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FSuite", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "28", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "data.Model.FIsGroupCust", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2033382693160300546", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "29", - "tags": "2", - "example": null - } - ] - } - ] -} \ No newline at end of file diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_w8kgb73ib3.json b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_w8kgb73ib3.json deleted file mode 100644 index f2ddf69..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_w8kgb73ib3.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "serverName": "lzwcai_mcp_api_converter", - "description": "登录、单据查询", - "domainUrl": "http://39.108.116.74", - "packageName": "lzwcai-mcp-dyntoolapi", - "version": "1.0.0", - "apiConfig": [ - { - "id": "2029506334288154626", - "enterpriseId": "1932095424144715777", - "bizSysId": "2029468454441897985", - "domainUrl": "http://39.108.116.74", - "interfaceName": "登录", - "businessPrompts": "登录", - "returnType": "JSON", - "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", - "header": null, - "apiUrl": "/K3Cloud/Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUser.common.kdsvc", - "parametersFormat": "2", - "method": "POST", - "status": 1, - "version": "1.0.0", - "authenticationRequired": 0, - "responseExample": null, - "crudType": "0", - "isView": 0, - "templateType": "markdown", - "viewTemplates": null, - "parameters": [ - { - "keyParam": null, - "required": 1, - "paramName": "parameters", - "paramType": "ARRAY", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334288154626", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "1", - "tags": "2", - "example": null - } - ] - }, - { - "id": "2029506334388817922", - "enterpriseId": "1932095424144715777", - "bizSysId": "2029468454441897985", - "domainUrl": "http://39.108.116.74", - "interfaceName": "单据查询", - "businessPrompts": "单据查询", - "returnType": "JSON", - "returnConversion": "{\"success_param\": \"code==200\", \"status_param\": \"code\", \"msg_param\": \"msg\", \"data\": \"data\"}", - "header": null, - "apiUrl": "/k3cloud/Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.ExecuteBillQuery.common.kdsvc", - "parametersFormat": "2", - "method": "POST", - "status": 1, - "version": "1.0.0", - "authenticationRequired": 1, - "responseExample": null, - "crudType": "4", - "isView": 0, - "templateType": "markdown", - "viewTemplates": null, - "parameters": [ - { - "keyParam": null, - "required": 1, - "paramName": "parameters", - "paramType": "ARRAY", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "1", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].FormId", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "2", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].TopRowCount", - "paramType": "INTEGER", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "3", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].Limit", - "paramType": "INTEGER", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "4", - "tags": "1", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].StartRow", - "paramType": "INTEGER", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "5", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].FilterString", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "6", - "tags": "2", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].OrderString", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "7", - "tags": "3", - "example": null - }, - { - "keyParam": null, - "required": 0, - "paramName": "parameters[].FieldKeys", - "paramType": "STRING", - "paramPrompts": "", - "defaultValue": null, - "assoKey": null, - "assoApiId": null, - "memory": 0, - "apiId": "2029506334388817922", - "requestType": "body", - "dataFormat": null, - "validity": null, - "sort": "8", - "tags": "0", - "example": null - } - ] - } - ] -} \ No newline at end of file diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__init__.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 7f75b3b..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/business_util.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/business_util.cpython-312.pyc deleted file mode 100644 index a6a7be8..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/business_util.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/get_business_api.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/get_business_api.cpython-312.pyc deleted file mode 100644 index eb2aa08..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/get_business_api.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/business_util.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/business_util.py deleted file mode 100644 index a00afd0..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/business_util.py +++ /dev/null @@ -1,716 +0,0 @@ -""" -业务工具模块 - API参数处理和JSON Schema操作 - -这个模块提供了API参数到JSON Schema转换、参数验证、API配置操作等核心业务功能。 -它是连接业务API配置和MCP工具定义的桥梁。 - -主要功能: -1. API参数列表转换为JSON Schema -2. 参数验证和默认值处理 -3. Schema提示文本生成 -4. API配置数组操作 -5. 必填参数检查 - -核心组件: -- 参数类型常量定义 -- JSON Schema生成器 -- 参数验证器 -- 配置操作工具 - -设计原则: -- 类型安全:严格的类型检查和转换 -- 容错性:完善的异常处理机制 -- 可扩展性:支持新的参数类型和验证规则 -- 标准化:符合JSON Schema规范 - -作者: lzwcai -版本: 1.0.0 -""" - -import json -import copy -from typing import Dict, List, Any, Optional, Union -from ..util.logger_config import get_logger - -# 获取日志器实例 -logger = get_logger(__name__) - - -# ==================== 常量定义 ==================== - -class ParamType: - """ - API参数类型常量 - - 定义业务平台API参数的所有支持类型。 - 这些类型会被映射到对应的JSON Schema类型。 - """ - STRING = "STRING" # 字符串类型 - NUMBER = "NUMBER" # 数字类型(浮点数) - INTEGER = "INTEGER" # 整数类型 - BOOLEAN = "BOOLEAN" # 布尔类型 - ARRAY = "ARRAY" # 数组类型 - OBJECT = "OBJECT" # 对象类型 - - -class JsonSchemaType: - """ - JSON Schema类型常量 - - 定义JSON Schema规范中的标准类型。 - 用于将业务参数类型转换为标准Schema类型。 - """ - STRING = "string" # 字符串 - NUMBER = "number" # 数字 - BOOLEAN = "boolean" # 布尔值 - ARRAY = "array" # 数组 - OBJECT = "object" # 对象 - - -class RequestType: - """ - 请求类型常量 - - 定义API参数在HTTP请求中的位置类型。 - 用于将参数正确分组到请求的不同部分。 - """ - HEADER = "header" # 请求头参数 - QUERY = "query" # 查询参数(URL参数) - BODY = "body" # 请求体参数(JSON格式) - FORM = "form" # 表单参数(application/x-www-form-urlencoded) - FORMDATA = "formdata" # 多部分表单参数(multipart/form-data,支持文件上传) - PATH = "path" # 路径参数 - PARAMS = "params" # 路径参数(别名) - LZWCAI_CONFIG = "lzwcaiConfig" # lzwcaiConfig参数(新的用户ID存储位置) - - -# ==================== 类型映射配置 ==================== - -# 参数类型映射表:业务参数类型 -> JSON Schema类型 -PARAM_TYPE_MAPPING = { - ParamType.STRING: JsonSchemaType.STRING, # 字符串 -> string - ParamType.NUMBER: JsonSchemaType.NUMBER, # 数字 -> number - ParamType.INTEGER: JsonSchemaType.NUMBER, # 整数 -> number(JSON Schema中整数也是number) - ParamType.BOOLEAN: JsonSchemaType.BOOLEAN, # 布尔 -> boolean - ParamType.ARRAY: JsonSchemaType.ARRAY, # 数组 -> array - ParamType.OBJECT: JsonSchemaType.OBJECT, # 对象 -> object -} - -# ==================== 默认参数配置 ==================== - -# 默认用户ID参数配置 -# 这个参数会自动添加到所有API的Schema中,用于标识当前用户 -DEFAULT_USER_ID_PARAM = { - "paramName": "userId", # 参数名称 - "paramType": ParamType.STRING, # 参数类型:字符串 - "paramPrompts": "当前与您对话的用户信息的用户ID", # 参数描述 - "requestType": RequestType.LZWCAI_CONFIG, # 请求类型:lzwcaiConfig类型 - "required": 1, # 必填参数 -} - - -# ==================== 核心函数 ==================== - -def generate_json_schema(api_params: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - API参数列表转JSON Schema格式 - - 这是模块的核心函数,负责将业务平台的API参数列表转换为符合JSON Schema规范的对象。 - 生成的Schema用于MCP工具的参数定义和验证。 - - 转换流程: - 1. 验证输入参数类型 - 2. 过滤掉header类型的参数(header参数单独处理) - 3. 添加默认的userId参数(存储在lzwcaiConfig中) - 4. 按请求类型分组参数(query, body, lzwcaiConfig等) - 5. 为每个分组创建Schema属性 - 6. 清理空的required列表 - - Schema结构: - { - "type": "object", - "properties": { - "query": { - "type": "object", - "properties": {...}, - "required": [...] - }, - "body": { - "type": "object", - "properties": {...}, - "required": [...] - }, - "lzwcaiConfig": { - "type": "object", - "properties": { - "userId": { - "type": "string", - "description": "当前与您对话的用户信息的用户ID" - } - }, - "required": ["userId"] - } - }, - "required": ["query", "body", "lzwcaiConfig"] // 只包含有参数的分组 - } - - 参数: - api_params: API参数对象列表,每个对象包含: - - paramName: 参数名称 - - paramType: 参数类型(STRING, NUMBER等) - - paramPrompts: 参数描述 - - requestType: 请求类型(header, query, body等) - - required: 是否必填(1为必填,0为可选) - - defaultValue: 默认值(可选) - - 返回: - dict: 符合JSON Schema规范的对象 - - 异常处理: - TypeError: 如果api_params不是列表类型 - - 设计考虑: - - header参数被过滤掉,因为它们在HTTP请求中单独处理 - - 自动添加userId参数到lzwcaiConfig分组,确保所有API都能获取用户信息 - - 按请求类型分组,便于后续的参数处理和验证 - - 清理空的required列表,保持Schema的简洁性 - """ - # 参数类型验证 - if not isinstance(api_params, list): - raise TypeError("api_params must be a list") - - logger.debug(f"生成JSON Schema,参数数量: {len(api_params)}") - - # 创建基础Schema结构 - schema = { - "type": JsonSchemaType.OBJECT, # 根类型为对象 - "properties": {}, # 属性定义 - "required": [] # 必填字段列表 - } - - # 过滤参数并添加默认userId参数 - # header参数在HTTP请求中单独处理,不包含在Schema中 - filtered_params = [ - param for param in api_params - if param.get("requestType") != RequestType.HEADER - ] - - # 添加默认的userId参数到lzwcaiConfig分组,确保所有API都能获取用户信息 - filtered_params.append(DEFAULT_USER_ID_PARAM) - - logger.debug(f"过滤后参数数量: {len(filtered_params)}") - - # 按请求类型分组参数 - param_groups = _group_parameters_by_type(filtered_params) - logger.debug(f"参数分组: {list(param_groups.keys())}") - - # 为每个请求类型创建Schema属性 - for req_type, params in param_groups.items(): - logger.debug(f"处理参数组 {req_type},包含 {len(params)} 个参数") - _add_request_type_to_schema(schema, req_type, params) - - # 清理空的required列表,保持Schema简洁 - _cleanup_empty_required_lists(schema) - - logger.debug("JSON Schema生成完成") - return schema - - -# ==================== 辅助函数 ==================== - -def _group_parameters_by_type( - params: List[Dict[str, Any]], -) -> Dict[str, List[Dict[str, Any]]]: - """ - 按请求类型分组参数 - - 将参数列表按照requestType字段进行分组,便于后续按类型处理。 - 如果参数没有指定requestType,默认归类为query类型。 - - 参数: - params: 参数字典列表 - - 返回: - Dict[str, List[Dict[str, Any]]]: 按类型分组的参数字典 - 格式: {"query": [...], "body": [...], "lzwcaiConfig": [...]} - """ - param_groups = {} - for param in params: - # 获取请求类型,默认为query - req_type = param.get("requestType", RequestType.QUERY) - - # 记录参数名和类型,便于调试 - param_name = param.get("paramName", "未命名参数") - logger.debug(f"参数 '{param_name}' 归类为 {req_type} 类型") - - # 初始化分组 - if req_type not in param_groups: - param_groups[req_type] = [] - logger.debug(f"创建新的参数分组: {req_type}") - - # 添加参数到对应分组 - param_groups[req_type].append(param) - - logger.debug(f"参数分组完成,共 {len(param_groups)} 个分组") - return param_groups - - -def _add_request_type_to_schema( - schema: Dict[str, Any], req_type: str, params: List[Dict[str, Any]] -) -> None: - """ - 向Schema添加请求类型分组 - - 为指定的请求类型创建Schema属性,并处理该类型下的所有参数。 - 每个请求类型都会成为根Schema的一个属性。 - - 参数: - schema: 根Schema对象 - req_type: 请求类型(如"query", "body", "lzwcaiConfig") - params: 该类型下的参数列表 - """ - # 如果该请求类型还没有在Schema中定义,创建它 - if req_type not in schema["properties"]: - schema["properties"][req_type] = { - "type": JsonSchemaType.OBJECT, # 每个请求类型都是对象类型 - "properties": {}, # 该类型下的参数定义 - "required": [], # 该类型下的必填参数 - } - - # 如果该类型有参数,将其标记为根级别的必填字段 - if params: - schema["required"].append(req_type) - - # 处理该类型下的每个参数 - for param in params: - _add_parameter_to_schema(schema["properties"][req_type], param) - - -def _add_parameter_to_schema( - type_schema: Dict[str, Any], param: Dict[str, Any] -) -> None: - """ - 向类型Schema添加单个参数 - - 将单个参数的定义添加到指定类型的Schema中,包括参数类型、描述、 - 默认值、示例等信息。 - - 参数处理: - 1. 提取参数基本信息(名称、类型、描述) - 2. 处理默认值(添加到描述中并设置default字段) - 3. 映射参数类型到JSON Schema类型 - 4. 创建参数定义对象 - 5. 添加到Schema的properties中 - 6. 处理必填参数标记 - - 参数: - type_schema: 类型Schema对象(如query、body的Schema) - param: 参数定义字典 - """ - # 获取参数名称,如果没有名称则跳过 - param_name = param.get("paramName") - if not param_name: - logger.warning(f"参数缺少名称,跳过: {param}") - return - - # 获取参数类型和描述 - param_type = param.get("paramType", ParamType.STRING) - param_desc = param.get("paramPrompts", "") - is_required = param.get("required") == 1 - - logger.debug(f"添加参数: {param_name}, 类型: {param_type}, 必填: {is_required}") - - # 如果有默认值,添加到描述中 - if param.get("defaultValue") is not None: - param_desc += f"(默认值为{param['defaultValue']})" - logger.debug(f"参数 {param_name} 有默认值: {param['defaultValue']}") - - # 将业务参数类型映射到JSON Schema类型 - json_type = PARAM_TYPE_MAPPING.get(param_type, JsonSchemaType.STRING) - if param_type not in PARAM_TYPE_MAPPING: - logger.warning(f"未知的参数类型 {param_type},使用默认类型 string") - - # 创建参数定义对象 - param_def = { - "type": json_type, # JSON Schema类型 - "description": param_desc, # 参数描述 - } - - # 添加可选字段 - if param.get("defaultValue") is not None: - param_def["default"] = param["defaultValue"] # 默认值 - if param.get("example") is not None: - param_def["example"] = param["example"] # 示例值 - - # 添加到类型Schema的properties中 - type_schema["properties"][param_name] = param_def - - # 如果是必填参数,添加到required列表中 - if is_required: - type_schema["required"].append(param_name) - logger.debug(f"参数 {param_name} 标记为必填") - - -def _cleanup_empty_required_lists(schema: Dict[str, Any]) -> None: - """ - 清理Schema中的空required列表 - - 移除Schema中所有空的required数组,保持Schema的简洁性。 - 这包括嵌套属性中的required列表和顶级的required列表。 - - 清理规则: - 1. 遍历所有请求类型的Schema - 2. 如果某个类型的required列表为空,删除该字段 - 3. 如果顶级required列表为空,删除该字段 - - 参数: - schema: 要清理的Schema对象 - """ - # 清理嵌套属性中的空required列表 - for req_type in list(schema["properties"].keys()): - type_schema = schema["properties"][req_type] - if not type_schema.get("required"): - # 如果required列表为空,删除该字段 - type_schema.pop("required", None) - - # 清理顶级的空required列表 - if not schema.get("required"): - schema.pop("required", None) - - -def create_structured_data( - schema: Dict[str, Any], params: Dict[str, Any] -) -> Dict[str, Any]: - """ - Generate data structure based on schema and input parameters. - - Automatically matches input parameters to schema fields and creates - a properly structured data object. - - Args: - schema: JSON schema object defining the data structure - params: Parameter values to fill - - Returns: - dict: Structured data object conforming to schema - - Raises: - TypeError: If schema or params are not dictionaries - """ - if not isinstance(schema, dict): - raise TypeError("schema must be a dictionary") - if not isinstance(params, dict): - raise TypeError("params must be a dictionary") - - result = {} - - # Process each top-level schema property - for field_name, field_schema in schema.get("properties", {}).items(): - result[field_name] = {} - field_properties = field_schema.get("properties", {}) - - # Find matching parameters for this field - matched_params = { - param_name: param_value - for param_name, param_value in params.items() - if param_name in field_properties - } - - # Only include field if there are matching parameters - if matched_params: - result[field_name] = matched_params - - return result - - -def generate_schema_prompt(schema: Dict[str, Any]) -> str: - """ - Generate descriptive prompt from JSON Schema for LLM guidance. - - Args: - schema: JSON Schema object returned by generate_json_schema - - Returns: - str: Structured prompt text for guiding LLM parameter generation - - Raises: - TypeError: If schema is not a dictionary - """ - if not isinstance(schema, dict): - raise TypeError("schema must be a dictionary") - - prompt_parts = ["当前工具所需参数:\n"] - - # Process each parameter group - for group_name, group_schema in schema.get("properties", {}).items(): - prompt_parts.append(f"## {group_name} 参数组:") - - required_params = set(group_schema.get("required", [])) - - # Process each parameter in the group - for param_name, param_info in group_schema.get("properties", {}).items(): - param_type = param_info.get("type", JsonSchemaType.STRING) - param_desc = param_info.get("description", "") - required_mark = "(必填)" if param_name in required_params else "(可选)" - - prompt_parts.append( - f"- {param_name}{required_mark}: {param_desc}, 类型: {param_type}" - ) - - prompt_parts.append("") - - # Add output format guidance - prompt_parts.extend( - [ - "参数格式要求:JSON对象,包含所有必填字段。", - "示例格式:", - "```json", - "{", - ] - ) - - for group_name in schema.get("properties", {}): - prompt_parts.append(f' "{group_name}": {{') - prompt_parts.append(" // 相关参数") - prompt_parts.append(" },") - - prompt_parts.extend(["}", "```"]) - - return "\n".join(prompt_parts) - - -def _remove_property_from_schema(schema: Dict[str, Any], property_name: str) -> None: - """ - Remove a property from schema (helper function to reduce code duplication). - - Args: - schema: Schema object to modify - property_name: Name of property to remove - """ - # Remove from properties - if isinstance(schema.get("properties"), dict): - schema["properties"].pop(property_name, None) - - # Remove from required list - if isinstance(schema.get("required"), list): - try: - schema["required"].remove(property_name) - except ValueError: - pass # Property not in required list, ignore - - # Remove empty required list - if not schema["required"]: - schema.pop("required", None) - - -def remove_property_from_api_item( - api_item: Dict[str, Any], property_name: str -) -> Dict[str, Any]: - """ - Remove specified property from a single API object. - - Args: - api_item: API object (single element from generate_api_array result) - property_name: Name of property to remove - - Returns: - dict: Processed API object with property removed - - Raises: - ValueError: When input parameters are invalid - TypeError: When input types are incorrect - """ - if not isinstance(api_item, dict) or not api_item: - raise ValueError("api_item must be a non-empty dictionary") - - if not isinstance(property_name, str) or not property_name.strip(): - raise ValueError("property_name must be a non-empty string") - - # Create deep copy to avoid modifying original - new_api = copy.deepcopy(api_item) - - if "schema" in new_api and isinstance(new_api["schema"], dict): - _remove_property_from_schema(new_api["schema"], property_name) - - return new_api - - -def remove_property_from_api_array( - api_array: List[Dict[str, Any]], property_name: str -) -> List[Dict[str, Any]]: - """ - Remove specified property from all API objects in array. - - Args: - api_array: API array returned by generate_api_array - property_name: Name of property to remove - - Returns: - list: Processed API array with property removed from all items - - Raises: - ValueError: When input parameters are invalid - TypeError: When input types are incorrect - """ - if not isinstance(api_array, list) or not api_array: - raise ValueError("api_array must be a non-empty list") - - if not isinstance(property_name, str) or not property_name.strip(): - raise ValueError("property_name must be a non-empty string") - - result = [] - - for api_item in api_array: - try: - processed_item = remove_property_from_api_item(api_item, property_name) - result.append(processed_item) - except Exception as e: - result.append(copy.deepcopy(api_item)) # Keep original if processing fails - - return result - - -def fill_default_values_by_schema( - schema: Dict[str, Any], arguments: Dict[str, Any] -) -> Dict[str, Any]: - """ - Auto-fill default values in arguments based on JSON Schema. - - Only fills missing parameters or those with None/empty string values. - - Args: - schema: JSON Schema object with grouped structure (body/query/lzwcaiConfig) - arguments: User input parameter dictionary - - Returns: - dict: Parameter dictionary with default values filled - - Raises: - TypeError: When input types are incorrect - """ - if not isinstance(schema, dict): - raise TypeError("schema must be a dictionary") - - if not isinstance(arguments, dict): - arguments = {} - else: - arguments = copy.deepcopy(arguments) - - for group_name, group_schema in schema.get("properties", {}).items(): - if not isinstance(arguments.get(group_name), dict): - arguments[group_name] = {} - - for param_name, param_schema in group_schema.get("properties", {}).items(): - default_value = param_schema.get("default") - current_value = arguments[group_name].get(param_name) - - # Fill default only if parameter is missing, None, or empty string - if ( - current_value is None or current_value == "" - ) and default_value is not None: - arguments[group_name][param_name] = default_value - - return arguments - - -def generate_api_array(api_params: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - Generate API array with JSON Schema for each API. - - Args: - api_params: List of API parameter definitions - - Returns: - list: API array with schema attached to each item - - Raises: - TypeError: If api_params is not a list - """ - if not isinstance(api_params, list): - raise TypeError("api_params must be a list") - - api_array = [] - for param in api_params: - try: - schema = generate_json_schema(param.get("parameters", [])) - api_array.append({**param, "schema": schema}) - except Exception as e: - logger.error(f"Error processing config: {str(e)}") - api_array.append(param) # Add without schema if generation fails - - return api_array - - -def check_required_arguments( - schema: Dict[str, Any], arguments: Dict[str, Any] -) -> List[str]: - """ - Validate that arguments contain all required parameters from schema. - - Args: - schema: JSON Schema object with grouped structure (body/query/lzwcaiConfig) - arguments: User input parameter dictionary - - Returns: - list: Missing required parameters with group names, e.g., ['body.username', 'lzwcaiConfig.userId'] - - Raises: - TypeError: When input types are incorrect - """ - if not isinstance(schema, dict): - raise TypeError("schema must be a dictionary") - - if not isinstance(arguments, dict): - arguments = {} - - missing_params = [] - - for group_name, group_schema in schema.get("properties", {}).items(): - group_args = arguments.get(group_name, {}) - required_params = group_schema.get("required", []) - - for param_name in required_params: - param_value = group_args.get(param_name) if group_args else None - - # Consider missing if not provided, None, or empty string - if param_value is None or param_value == "": - # Try to get parameter description - description = "" - try: - param_properties = group_schema.get("properties", {}) - param_info = param_properties.get(param_name, {}) - description = param_info.get("description", "") - except Exception: - pass - - # Format missing parameter name - if description: - missing_params.append(f"{group_name}.{param_name}({description})") - else: - missing_params.append(f"{group_name}.{param_name}") - - return missing_params - - -if __name__ == "__main__": - # Example usage and testing - try: - with open( - "E:/yh-ai/project/lzwcai-szyg/lzwcai-demp-tool-server/src/" - "lzwcai_demp_tool_server_business_to_mcp/mcp_generator/src/parameters.json", - "r", - encoding="utf-8", - ) as f: - api_params = json.load(f) - - api_array = generate_api_array(api_params) - result = remove_property_from_api_array(api_array, "userId") - - # Write results to JSON file - with open("schema1.json", "w", encoding="utf-8") as f: - json.dump(result, f, ensure_ascii=False, indent=4) - - except FileNotFoundError: - logger.error(f"Warning: Test data file not found. Skipping example execution.") - except Exception as e: - logger.error(f"Error in example execution: {str(e)}") diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/get_business_api.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/get_business_api.py deleted file mode 100644 index 71f596b..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/get_business_api.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Business API configuration utility functions. - -This module provides utilities for fetching and processing business API configurations. -""" - -import json -import os -import requests -from typing import Dict, List, Any -from ..util.logger_config import get_logger - -# 配置日志 -logger = get_logger(__name__) - - - - -def get_business_api_details(api_ids: List[int], auth_token: str = None) -> List[Dict[str, Any]]: - """ - 获取业务平台API详情 - - 调用业务平台接口获取指定API ID列表的详细信息 - - Args: - api_ids: API ID列表,例如 [1925128743899111425, 1925128744524062721] - auth_token: 认证token,如果不提供则使用默认token - - Returns: - List[Dict[str, Any]]: API详情列表,返回接口响应中的data字段内容 - - Raises: - requests.RequestException: 当网络请求失败时抛出 - ValueError: 当响应格式不正确或返回错误时抛出 - - Example: - >>> api_ids = [1925128743899111425, 1925128744524062721] - >>> details = get_business_api_details(api_ids) - >>> print(len(details)) - 2 - """ - if not isinstance(api_ids, list) or not api_ids: - raise ValueError("api_ids must be a non-empty list") - - # 默认认证token - default_token = "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImM3OGU0M2NlLTJhZjQtNGRjYy1iMWE1LTU3YjM5YTdkNTA1OSJ9.5f1lSJJdLUunZIwCfneT1DiagGN4jD-QCnFCffWmrnvEcLfpuSMWRpY7fF-6H3yZ2N5ICZ4ZQN6cx7iqwF6jKw" - token = auth_token or default_token - - # 接口URL - 支持环境变量配置 - # 从环境变量获取基础URL,必须配置 - base_url = os.getenv("LZWCAI_CORP_MANAGER_URL",'http://lzwcai-demp-corp-manager:8086') - if not base_url: - raise ValueError("环境变量 LZWCAI_CORP_MANAGER_URL 未配置,请设置业务平台基础URL,例如: http://lzwcai-demp-corp-manager:8086") - - # API路径 - api_path = "/system/mcpServer/bizSys/api/getByIds" - url = base_url.rstrip("/") + api_path - - # 请求头 - headers = { - "Authorization": f"Bearer {token}", - # "User-Agent": "Apifox/1.0.0 (https://apifox.com)", - "Content-Type": "application/json", - # "Accept": "*/*", - # "Host": "192.168.2.236:8088", - "Connection": "keep-alive" - } - - try: - # 发送POST请求 - response = requests.post(url, headers=headers, json=api_ids, timeout=30) - response.raise_for_status() # 检查HTTP状态码 - - # 解析响应JSON - response_data = response.json() - - # 检查响应格式 - if not isinstance(response_data, dict): - raise ValueError("响应格式不正确:期望JSON对象") - - # 检查业务状态码 - code = response_data.get("code") - if code != 200: - msg = response_data.get("msg", "未知错误") - raise ValueError(f"业务接口返回错误:code={code}, msg={msg}") - - # 获取data字段 - data = response_data.get("data", []) - if not isinstance(data, list): - logger.warning("响应中的data字段不是列表类型,将转换为列表") - data = [data] if data is not None else [] - - logger.info(f"成功获取 {len(data)} 个API详情") - return data - - except requests.exceptions.Timeout: - raise requests.RequestException("请求超时:接口响应时间过长") - except requests.exceptions.ConnectionError: - raise requests.RequestException("连接错误:无法连接到业务平台服务器") - except requests.exceptions.HTTPError as e: - raise requests.RequestException(f"HTTP错误:{e}") - except json.JSONDecodeError: - raise ValueError("响应格式错误:无法解析JSON数据") - except Exception as e: - logger.error(f"获取API详情时发生未知错误:{str(e)}") - raise - - -def map_api_details_to_config(api_details: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - 将API详情数据映射为api_config.json格式 - - Args: - api_details: get_business_api_details方法返回的API详情列表 - - Returns: - Dict[str, Any]: 符合api_config.json格式的配置对象 - - Raises: - ValueError: 当输入数据格式不正确时抛出 - - Example: - >>> api_details = get_business_api_details([1925128743899111425]) - >>> config = map_api_details_to_config(api_details) - >>> print(config["serverName"]) - lzwcai_mcp_api_converter - """ - if not isinstance(api_details, list): - raise ValueError("api_details must be a list") - - if not api_details: - raise ValueError("api_details cannot be empty") - - # 获取第一个API的domainUrl作为全局domainUrl - domain_url = "" - if api_details and isinstance(api_details[0], dict): - domain_url = api_details[0].get("domainUrl", "") - - # 收集所有businessPrompts用于生成description - business_prompts = [] - for api in api_details: - if isinstance(api, dict) and api.get("businessPrompts"): - business_prompts.append(api["businessPrompts"]) - - # 生成description - description = "、".join(business_prompts) if business_prompts else "业务API集合" - - # 构建配置对象 - config = { - "serverName": "lzwcai_mcp_api_converter", - "description": description, - "domainUrl": domain_url, - "packageName": "lzwcai-mcp-dyntoolapi", - "version": "1.0.0", - "apiConfig": api_details # 直接使用原始API详情数据 - } - - logger.info(f"成功映射 {len(api_details)} 个API到配置格式") - logger.info(f"服务名称: {config['serverName']}") - logger.info(f"域名URL: {config['domainUrl']}") - logger.info(f"描述: {config['description']}") - - return config - - -def get_business_api_config(api_ids: List[int], auth_token: str = None) -> Dict[str, Any]: - """ - 一步到位获取业务平台API配置 - - 传入API ID列表,直接返回处理好的api_config.json格式配置 - - Args: - api_ids: API ID列表,例如 [1925128743899111425, 1925128744524062721] - auth_token: 认证token,如果不提供则使用默认token - - Returns: - Dict[str, Any]: 符合api_config.json格式的完整配置对象 - - Raises: - requests.RequestException: 当网络请求失败时抛出 - ValueError: 当响应格式不正确或返回错误时抛出 - - Example: - >>> api_ids = [1925128743899111425, 1925128744524062721] - >>> config = get_business_api_config(api_ids) - >>> print(config["serverName"]) - lzwcai_mcp_api_converter - >>> print(len(config["apiConfig"])) - 2 - """ - try: - # 步骤1: 获取API详情 - logger.info(f"开始获取 {len(api_ids)} 个API的详情...") - api_details = get_business_api_details(api_ids, auth_token) - - # 步骤2: 映射为配置格式 - logger.info("开始映射为配置格式...") - config = map_api_details_to_config(api_details) - - logger.info(f"[SUCCESS] 成功生成API配置!包含 {len(config['apiConfig'])} 个API") - return config - - except Exception as e: - logger.error(f"获取业务API配置时发生错误: {str(e)}") - raise - - - diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__init__.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c6c70a9..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_auth_service.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_auth_service.cpython-312.pyc deleted file mode 100644 index 9237dde..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_auth_service.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_base.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_base.cpython-312.pyc deleted file mode 100644 index db83ca0..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_base.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/core_server.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/core_server.cpython-312.pyc deleted file mode 100644 index 8af1df1..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/core_server.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/get_auth.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/get_auth.cpython-312.pyc deleted file mode 100644 index a9d721e..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/get_auth.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/plugin_base.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/plugin_base.cpython-312.pyc deleted file mode 100644 index 3a0e9fc..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/plugin_base.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_auth_service.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_auth_service.py deleted file mode 100644 index c0438af..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_auth_service.py +++ /dev/null @@ -1,966 +0,0 @@ -""" -API认证服务模块 - -提供用户认证、Token管理、企业认证等功能 -""" - -import asyncio -import json -import os -import random -import sys -from typing import Any, Dict, List, Optional, Tuple, Union - -# 添加项目根目录到 Python 路径 -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) -sys.path.append(project_root) - -from ..business.business_util import create_structured_data, generate_api_array -from ..util.nested_value import get_nested_value -from ..util.logger_config import get_logger -from .get_auth import get_auth_data - -# 获取日志器 -logger = get_logger(__name__) - - -class AuthError(Exception): - """认证相关异常""" - - def __init__(self, message: str, error_code: Optional[str] = None): - super().__init__(message) - self.error_code = error_code - - -class AuthConfig: - """认证配置类""" - - # Token相关配置 - TOKEN_PREFIX = "lzwc" - TOKEN_SUFFIX = "token" - - # 重试配置 - DEFAULT_MAX_ATTEMPTS = 3 - DEFAULT_BASE_DELAY = 1 - DEFAULT_MAX_DELAY = 60 - - -class ParameterExtractor: - """参数提取器""" - - @staticmethod - def extract_param_defaults(param_list: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - 将参数列表转换为 {paramName: defaultValue} 的字典 - - Args: - param_list: 参数字典列表,每个元素包含 paramName 和 defaultValue - - Returns: - 以 paramName 为 key,defaultValue 为 value 的字典 - """ - if not isinstance(param_list, list): - logger.warning(f"参数列表类型错误,期望list,实际{type(param_list)}") - return {} - - logger.debug(f"提取参数默认值,参数数量: {len(param_list)}") - result = {} - - for item in param_list: - if not isinstance(item, dict): - logger.warning(f"参数项类型错误,跳过: {item}") - continue - - param_name = item.get("paramName") - if param_name: - default_value = item.get("defaultValue") - result[param_name] = default_value - logger.debug(f"参数 {param_name}: {default_value}") - - logger.debug(f"提取完成,共 {len(result)} 个参数") - return result - - -class ApiRetryExecutor: - """API重试执行器""" - - def __init__( - self, - max_attempts: int = AuthConfig.DEFAULT_MAX_ATTEMPTS, - base_delay: int = AuthConfig.DEFAULT_BASE_DELAY, - max_delay: int = AuthConfig.DEFAULT_MAX_DELAY, - ): - self.max_attempts = max_attempts - self.base_delay = base_delay - self.max_delay = max_delay - - async def execute_with_retry( - self, - api_config: Dict[str, Any], - user_params: Dict[str, Any], - need_auth: bool = False, - ) -> Optional[Dict[str, Any]]: - """ - 使用指数退避策略的API调用重试 - - Args: - api_config: API配置信息 - user_params: 用户提供的参数 - need_auth: 是否需要鉴权 - - Returns: - API调用响应 - - Raises: - AuthError: 认证相关错误 - """ - from .core_server import call_api - - attempt = 0 - last_error = None - - while attempt < self.max_attempts: - attempt += 1 - try: - logger.info(f"尝试调用API,第 {attempt} 次") - - # 准备API调用数据 - api_array = generate_api_array([api_config]) - call_api_data = create_structured_data( - api_array[0]["schema"], user_params - ) - - # 执行API调用 - api_res = await call_api(api_config, call_api_data, need_auth=need_auth) - - # 检查响应是否包含错误 - if api_res and "error" not in api_res: - logger.info(f"API调用成功,第 {attempt} 次尝试") - return api_res - - # 记录错误信息 - error_msg = api_res.get("error", "未知错误") if api_res else "响应为空" - logger.warning(f"API调用返回错误: {error_msg}") - last_error = error_msg - - # 如果还有重试机会,等待后重试 - if attempt < self.max_attempts: - await self._wait_for_retry(attempt) - - except Exception as e: - error_msg = str(e) - logger.error(f"API调用发生异常: {error_msg}") - last_error = error_msg - - if attempt < self.max_attempts: - await self._wait_for_retry(attempt) - else: - logger.error( - f"API调用失败,已达到最大重试次数 ({self.max_attempts})" - ) - raise AuthError(f"API调用失败: {error_msg}") - - # 所有重试都失败了 - raise AuthError( - f"API调用失败,重试{self.max_attempts}次后仍然失败: {last_error}" - ) - - async def _wait_for_retry(self, attempt: int) -> None: - """等待重试的延迟逻辑""" - # 计算延迟时间(指数退避策略) - delay = min(self.base_delay * (2 ** (attempt - 1)), self.max_delay) - # 添加随机抖动(0.5-1.5倍)避免同时重试 - jitter = 0.5 + random.random() - sleep_time = delay * jitter - - logger.info(f"等待 {sleep_time:.2f} 秒后进行第 {attempt + 1} 次重试") - await asyncio.sleep(sleep_time) - - -class EnvironmentManager: - """环境变量管理器,提供环境变量的增删改查功能,支持持久化存储""" - - _env_file = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../", ".env_lzwcai_mcp_api_converter")) - - @staticmethod - def _load_persistent_tokens() -> Dict[str, str]: - """从.env文件加载持久化的token""" - tokens = {} - try: - if os.path.exists(EnvironmentManager._env_file): - with open(EnvironmentManager._env_file, 'r', encoding='utf-8') as f: - for line in f: - line = line.strip() - if line and '=' in line and not line.startswith('#'): - key, value = line.split('=', 1) - key = key.strip() - value = value.strip() - if key: # 确保key不为空 - tokens[key] = value - except Exception as e: - logger.error(f"加载.env文件失败: {e}") - return tokens - - @staticmethod - def _save_persistent_tokens(tokens: Dict[str, str]) -> bool: - """保存token到.env文件""" - try: - os.makedirs(os.path.dirname(EnvironmentManager._env_file), exist_ok=True) - with open(EnvironmentManager._env_file, 'w', encoding='utf-8') as f: - for key, value in tokens.items(): - f.write(f"{key}={value}\n") - return True - except Exception as e: - logger.error(f"保存.env文件失败: {e}") - return False - - @staticmethod - def get(key: str, default: Optional[str] = None) -> Optional[str]: - """ - 获取环境变量值,优先从进程环境变量获取,然后从持久化文件获取 - - Args: - key: 环境变量名 - default: 默认值 - - Returns: - 环境变量值或默认值 - """ - # 先从进程环境变量获取 - value = os.environ.get(key) - if value is not None: - return value - - # 从持久化文件获取 - if key.endswith('token'): # 只对token类型的环境变量使用持久化 - persistent_tokens = EnvironmentManager._load_persistent_tokens() - value = persistent_tokens.get(key) - if value is not None: - # 同时设置到进程环境变量中 - os.environ[key] = value - return value - - return default - - @staticmethod - def set(key: str, value: str) -> bool: - """ - 设置环境变量,同时持久化token类型的变量 - - Args: - key: 环境变量名 - value: 环境变量值 - - Returns: - 设置是否成功 - """ - try: - # 设置到进程环境变量 - os.environ[key] = str(value) - - # 如果是token类型,同时持久化到文件 - if key.endswith('token'): - persistent_tokens = EnvironmentManager._load_persistent_tokens() - persistent_tokens[key] = str(value) - if not EnvironmentManager._save_persistent_tokens(persistent_tokens): - logger.warning(f"持久化token失败,但进程环境变量已设置: {key}") - else: - logger.debug(f"成功持久化token: {key}") - - logger.debug(f"成功设置环境变量: {key}") - return True - except Exception as e: - logger.error(f"设置环境变量失败: {key}, 错误: {e}") - return False - - @staticmethod - def exists(key: str) -> bool: - """ - 检查环境变量是否存在 - - Args: - key: 环境变量名 - - Returns: - 是否存在 - """ - # 先检查进程环境变量 - if key in os.environ: - return True - - # 检查持久化文件 - if key.endswith('token'): - persistent_tokens = EnvironmentManager._load_persistent_tokens() - return key in persistent_tokens - - return False - - @staticmethod - def delete(key: str) -> bool: - """ - 删除环境变量,同时删除持久化的token - - Args: - key: 环境变量名 - - Returns: - 删除是否成功 - """ - try: - # 从进程环境变量删除 - if key in os.environ: - del os.environ[key] - - # 如果是token类型,同时从持久化文件删除 - if key.endswith('token'): - persistent_tokens = EnvironmentManager._load_persistent_tokens() - if key in persistent_tokens: - del persistent_tokens[key] - EnvironmentManager._save_persistent_tokens(persistent_tokens) - logger.debug(f"成功删除持久化token: {key}") - - logger.debug(f"成功删除环境变量: {key}") - return True - except Exception as e: - logger.error(f"删除环境变量失败: {key}, 错误: {e}") - return False - - -class TokenManager: - """Token管理器""" - - def __init__(self): - self.logger = get_logger("TokenManager") - - def generate_token_name(self, user_id: str, biz_sys_id: str) -> str: - """ - 生成Token名称 - - Args: - user_id: 用户ID - biz_sys_id: 业务系统ID - - Returns: - Token名称 - """ - return ( - f"{AuthConfig.TOKEN_PREFIX}{user_id}{biz_sys_id}{AuthConfig.TOKEN_SUFFIX}" - ) - - def check_token_exists( - self, token_name: str - ) -> Tuple[bool, Optional[Union[str, Dict[str, Any]]]]: - """ - 检查环境变量中是否存在Token,并返回反序列化后的值 - - Args: - token_name: Token名称 - - Returns: - (是否存在, Token值) - """ - token_value = EnvironmentManager.get(token_name) - if token_value is None: - return False, None - - # 尝试反序列化Token值 - deserialized_value = self._deserialize_token_value(token_value, token_name) - return True, deserialized_value - - def _deserialize_token_value( - self, token_value: str, token_name: str - ) -> Union[str, Dict[str, Any]]: - """反序列化Token值""" - try: - # 尝试将JSON字符串反序列化为原始结构(字典) - return json.loads(token_value) - except json.JSONDecodeError: - self.logger.debug(f"Token不是JSON格式,保留原始字符串: {token_name}") - - # 尝试处理字符串表示的字典,如 "{'key': 'value'}" - if token_value.startswith("{") and token_value.endswith("}"): - try: - import ast - - return ast.literal_eval(token_value) - except (ValueError, SyntaxError): - self.logger.debug(f"无法解析字典字符串: {token_value}") - - # 返回原始字符串 - return token_value - - def store_token(self, token_name: str, token_value: Any) -> bool: - """ - 存储Token到环境变量,自动序列化复杂结构 - - Args: - token_name: Token名称 - token_value: Token值 - - Returns: - 存储是否成功 - """ - if token_value is None: - self.logger.warning(f"Token值为None,不进行存储: {token_name}") - return False - - # 序列化Token值 - serialized_value = self._serialize_token_value(token_value, token_name) - if serialized_value is None: - return False - - # 存储到环境变量 - success = EnvironmentManager.set(token_name, serialized_value) - if success: - self.logger.info(f"成功将Token存储到环境变量: {token_name}") - else: - self.logger.error(f"存储Token到环境变量失败: {token_name}") - - return success - - def _serialize_token_value( - self, token_value: Any, token_name: str - ) -> Optional[str]: - """序列化Token值""" - try: - if isinstance(token_value, (dict, list)): - return json.dumps(token_value) - elif isinstance(token_value, bytes): - return token_value.decode("utf-8") - elif isinstance(token_value, (int, float, str)): - return str(token_value) - else: - self.logger.error( - f"不支持的Token类型: {type(token_value)},不进行存储: {token_name}" - ) - return None - except Exception as e: - self.logger.error(f"序列化Token时出错: {str(e)}") - return None - - -class CompanyAuthClient: - """企业认证客户端 - 现在使用get_auth_data方法替代HTTP API调用""" - - def __init__(self): - self.logger = get_logger("CompanyAuthClient") - - async def get_auth_info( - self, user_id: str, biz_sys_id: str - ) -> Tuple[Optional[int], Optional[Dict[str, Any]]]: - """ - 获取鉴权类型和认证数据 - - Args: - user_id: 用户ID - biz_sys_id: 业务系统ID - - Returns: - (鉴权类型, 认证数据) - """ - try: - self.logger.debug(f"使用get_auth_data获取认证信息: user_id={user_id}, biz_sys_id={biz_sys_id}") - - # 使用get_auth_data方法获取认证数据 - result = get_auth_data(user_id, biz_sys_id) - - if not result: - self.logger.error("get_auth_data返回空结果") - return None, None - - return self._parse_auth_response(result, user_id, biz_sys_id) - - except Exception as e: - self.logger.error(f"获取鉴权类型失败: {str(e)}") - return None, None - - def _parse_auth_response( - self, result: Dict[str, Any], user_id: str, biz_sys_id: str - ) -> Tuple[Optional[int], Optional[Dict[str, Any]]]: - """解析认证响应""" - if result.get("code") != 200: - error_msg = result.get("msg", "未知错误") - self.logger.error(f"获取鉴权类型失败: {error_msg}") - return None, None - - auth_data = result.get("data", {}) - auth_type = auth_data.get("authType") - - # 将字符串类型的authType转换为整数 - auth_type_int = None - if auth_type is not None: - try: - auth_type_int = int(auth_type) - self.logger.info( - f"用户{user_id}业务系统{biz_sys_id}的鉴权类型: {auth_type_int}" - ) - except (ValueError, TypeError): - self.logger.warning(f"无法将authType转换为整数: {auth_type}") - - return auth_type_int, auth_data - - -class BusinessTokenService: - """业务系统Token服务""" - - def __init__(self): - self.logger = get_logger("BusinessTokenService") - self.retry_executor = ApiRetryExecutor() - - async def get_business_system_token( - self, auth_data: Dict[str, Any] - ) -> Dict[str, Any]: - """ - 获取业务系统Token - - Args: - auth_data: 认证数据字典 - - Returns: - 包含token信息的字典 - """ - try: - # 验证输入参数 - validation_result = self._validate_auth_data(auth_data) - if not validation_result["valid"]: - return {"success": False, "msg": validation_result["error"]} - - # 解析配置 - config_result = self._parse_auth_config(auth_data) - if not config_result["valid"]: - return {"success": False, "msg": config_result["error"]} - - # 执行API调用获取Token - token_result = await self._execute_token_api( - config_result["api_def"], config_result["user_params"], auth_data - ) - - return token_result - - except Exception as e: - self.logger.error(f"获取业务系统Token失败: {str(e)}") - return {"success": False, "msg": f"系统错误: {str(e)}"} - - def _validate_auth_data(self, auth_data: Dict[str, Any]) -> Dict[str, Any]: - """验证认证数据""" - if not isinstance(auth_data, dict): - error_msg = f"auth_data类型错误: {type(auth_data)}" - self.logger.error(error_msg) - return {"valid": False, "error": "认证数据格式错误"} - - # 验证必要字段 - if "name" not in auth_data: - self.logger.error("auth_data中缺少name字段") - return {"valid": False, "error": "缺少name配置"} - - if not auth_data.get("apiVO"): - self.logger.error("缺少apiVO信息") - return {"valid": False, "error": "缺少API配置信息"} - - apiVO = auth_data["apiVO"] - if not isinstance(apiVO, dict): - error_msg = f"apiVO类型错误: {type(apiVO)}" - self.logger.error(error_msg) - return {"valid": False, "error": "API配置格式错误"} - - # 检查必要字段 - required_fields = ["accountConfig", "tokenPath"] - missing_fields = [field for field in required_fields if field not in apiVO] - if missing_fields: - self.logger.error(f"缺少必要字段: {missing_fields}") - return {"valid": False, "error": f"缺少必要配置字段: {missing_fields}"} - - return {"valid": True} - - def _parse_auth_config(self, auth_data: Dict[str, Any]) -> Dict[str, Any]: - """解析认证配置""" - try: - apiVO = auth_data["apiVO"] - - # 解析accountConfig - account_config = apiVO["accountConfig"] - if isinstance(account_config, str): - try: - account_config = json.loads(account_config) - self.logger.info("成功解析accountConfig JSON") - except json.JSONDecodeError as e: - self.logger.error(f"accountConfig JSON解析失败: {str(e)}") - return {"valid": False, "error": "配置数据格式错误"} - - if not isinstance(account_config, dict): - error_msg = f"accountConfig类型错误: {type(account_config)}" - self.logger.error(error_msg) - return {"valid": False, "error": "配置数据格式错误"} - - # 验证API定义 - api_def = apiVO.get("tcapabilityApiVO") - if api_def is None: - self.logger.error("缺少API配置信息") - return {"valid": False, "error": "缺少API配置信息"} - - # 准备用户参数 - try: - parameters_body = account_config.get("parametersBody", []) - user_params = ParameterExtractor.extract_param_defaults( - parameters_body - ) - except Exception as e: - self.logger.error(f"准备API参数失败: {str(e)}") - return {"valid": False, "error": "参数准备失败"} - - # 准备API定义映射 - api_def_map = {**api_def, "parameters": api_def.get("apiParameterList", [])} - - return { - "valid": True, - "api_def": api_def_map, - "user_params": user_params, - "account_config": account_config, - } - - except Exception as e: - self.logger.error(f"解析认证配置失败: {str(e)}") - return {"valid": False, "error": "配置解析失败"} - - async def _execute_token_api( - self, - api_def: Dict[str, Any], - user_params: Dict[str, Any], - auth_data: Dict[str, Any], - ) -> Dict[str, Any]: - """执行Token获取API""" - try: - # 执行API调用 - api_res = await self.retry_executor.execute_with_retry( - api_def, user_params, need_auth=False - ) - - if not api_res: - return {"success": False, "msg": "API调用失败"} - - # 提取Token - return self._extract_token_from_response(api_res, auth_data) - - except AuthError as e: - self.logger.error(f"API调用失败: {str(e)}") - return {"success": False, "msg": f"API调用失败: {str(e)}"} - except Exception as e: - self.logger.error(f"执行Token API异常: {str(e)}") - return {"success": False, "msg": f"API调用异常: {str(e)}"} - - def _extract_token_from_response( - self, api_res: Dict[str, Any], auth_data: Dict[str, Any] - ) -> Dict[str, Any]: - """从API响应中提取Token""" - try: - name = auth_data["name"] - token_path = auth_data["apiVO"]["tokenPath"] - - value = get_nested_value({"res": api_res}, token_path) - - if not value: - self.logger.error("未获取到有效token") - return {"success": False, "msg": "获取token失败"} - - return { - "tokenHeader": {name: value}, - "token": value, - "msg": api_res.get("msg", "获取token成功"), - "success": True, - } - - except Exception as e: - self.logger.error(f"处理token结果失败: {str(e)}") - return {"success": False, "msg": "处理token结果失败"} - - -class AuthService: - """认证服务,负责处理鉴权相关逻辑""" - - def __init__(self): - self.logger = get_logger("AuthService") - self.token_manager = TokenManager() - self.company_auth_client = CompanyAuthClient() - self.business_token_service = BusinessTokenService() - - async def authorize_request( - self, - user_id: Optional[str], - biz_sys_id: Optional[str], - persist_token: bool = False, - ) -> Dict[str, Any]: - """ - 完整的请求鉴权处理 - - Args: - user_id: 用户ID - biz_sys_id: 业务系统ID - persist_token: 是否持久化token,默认True - - Returns: - 认证结果 - """ - self.logger.info(f"开始认证处理 - 用户ID: {user_id}, 业务系统ID: {biz_sys_id}, 持久化: {persist_token}") - - # 获取并验证token - token_header = await self.check_user_token(user_id, biz_sys_id, persist_token=persist_token) - self.logger.info(f"获取到Token头: {token_header}") - - if not token_header: - return { - "success": False, - "error_response": { - "error": "获取鉴权令牌失败,请前往管理平台进行鉴权或提供临时令牌" - }, - } - - return { - "success": True, - "tokenHeader": token_header, - "source": "auth_service", # 标记token来源 - } - - async def check_user_token( - self, - user_id: Optional[str], - biz_sys_id: Optional[str], - token: Optional[str] = None, - persist_token: bool = False, - ) -> Optional[Union[str, Dict[str, Any]]]: - """ - 检查用户Token是否有效,如无效则重新获取 - - Args: - user_id: 用户ID - biz_sys_id: 业务系统ID - token: 可选的临时Token - persist_token: 是否持久化token - - Returns: - Token值或None - """ - if not user_id or not biz_sys_id: - self.logger.warning("用户ID或业务系统ID为空,无法检查Token") - return None - - # 生成Token名 - token_name = self.token_manager.generate_token_name(user_id, biz_sys_id) - - self.logger.info(f"Token名: {token_name}") - - # 如果不需要持久化,直接重新获取Token - if not persist_token: - self.logger.info("不使用持久化,直接获取新Token") - return await self._refresh_user_token(user_id, biz_sys_id, token_name, persist_token) - - # 检查环境变量是否存在现有Token - exists, token_value = self.token_manager.check_token_exists(token_name) - self.logger.info(f"Token存在性检查: {exists}, 值: {token_value}") - - # 如果环境变量存在,直接返回值 - if exists: - self.logger.info( - f"从环境变量获取到用户{user_id}业务系统{biz_sys_id}的Token: {token_value}" - ) - return token_value - - # 如果提供了token参数,直接使用并存储 - if token: - if persist_token: - self.token_manager.store_token(token_name, token) - return token - - # 重新获取Token - return await self._refresh_user_token(user_id, biz_sys_id, token_name, persist_token) - - async def _refresh_user_token( - self, user_id: str, biz_sys_id: str, token_name: str, persist_token: bool = False - ) -> Optional[Union[str, Dict[str, Any]]]: - """刷新用户Token""" - # 获取鉴权类型和认证数据 - auth_type, auth_data = await self.company_auth_client.get_auth_info( - user_id, biz_sys_id - ) - - if auth_type is None: - self.logger.error(f"无法获取用户{user_id}业务系统{biz_sys_id}的鉴权类型") - return None - - # 根据鉴权类型获取Token - token_value = await self._get_token_by_auth_type( - user_id, biz_sys_id, auth_type, auth_data - ) - - self.logger.info(f"Token值: {token_value}") - - # 存储Token(根据persist_token参数决定是否持久化) - if token_value: - if persist_token: - success = self.token_manager.store_token(token_name, token_value) - if not success: - self.logger.error(f"存储用户{user_id}业务系统{biz_sys_id}的Token失败") - else: - self.logger.info(f"成功存储用户{user_id}业务系统{biz_sys_id}的Token到环境变量: {token_name}") - else: - self.logger.info(f"跳过持久化,用户{user_id}业务系统{biz_sys_id}的Token仅在内存中使用") - else: - self.logger.warning(f"未能获取到用户{user_id}业务系统{biz_sys_id}的Token,token_value: {token_value}") - - return token_value - - def clear_token(self, user_id: str, biz_sys_id: str) -> bool: - """ - 清空指定用户的token - - Args: - user_id: 用户ID - biz_sys_id: 业务系统ID - - Returns: - 清空是否成功 - """ - if not user_id or not biz_sys_id: - self.logger.warning("用户ID或业务系统ID为空,无法清空Token") - return False - - # 生成Token名 - token_name = self.token_manager.generate_token_name(user_id, biz_sys_id) - - # 删除环境变量中的token - success = EnvironmentManager.delete(token_name) - - if success: - self.logger.info(f"成功清空用户{user_id}业务系统{biz_sys_id}的Token: {token_name}") - else: - self.logger.error(f"清空用户{user_id}业务系统{biz_sys_id}的Token失败: {token_name}") - - return success - - def clear_all_tokens(self) -> bool: - """ - 清空所有持久化的token - - Returns: - 清空是否成功 - """ - try: - # 加载所有token - persistent_tokens = EnvironmentManager._load_persistent_tokens() - - # 删除所有token类型的环境变量 - for token_name in list(persistent_tokens.keys()): - if token_name.endswith('token'): - EnvironmentManager.delete(token_name) - - self.logger.info("成功清空所有持久化token") - return True - except Exception as e: - self.logger.error(f"清空所有token失败: {e}") - return False - - async def _get_token_by_auth_type( - self, user_id: str, biz_sys_id: str, auth_type: int, auth_data: Dict[str, Any] - ) -> Optional[Dict[str, Any]]: - """根据鉴权类型获取Token""" - try: - if auth_type == 0: - # 直接使用apiKey作为Token - return self._get_api_key_token(user_id, biz_sys_id, auth_data) - elif auth_type == 1: - # 调用登录接口获取Token - return await self._get_login_token(user_id, biz_sys_id, auth_data) - else: - self.logger.warning(f"不支持的鉴权类型: {auth_type}") - return None - except Exception as e: - self.logger.error(f"获取Token失败: {str(e)}") - return None - - def _get_api_key_token( - self, user_id: str, biz_sys_id: str, auth_data: Dict[str, Any] - ) -> Optional[Dict[str, Any]]: - """获取API Key类型的Token""" - api_key = auth_data.get("apiKey") - name = auth_data.get("name") - - if api_key and name: - self.logger.info(f"使用apiKey作为用户{user_id}业务系统{biz_sys_id}的Token") - return {name: api_key} - else: - self.logger.warning(f"用户{user_id}业务系统{biz_sys_id}的apiKey或name为空") - return None - - async def _get_login_token( - self, user_id: str, biz_sys_id: str, auth_data: Dict[str, Any] - ) -> Optional[Dict[str, Any]]: - """获取登录类型的Token""" - self.logger.info(f"通过登录接口获取用户{user_id}业务系统{biz_sys_id}的Token") - - login_res = await self.business_token_service.get_business_system_token( - auth_data - ) - - if login_res and login_res.get("tokenHeader") is not None: - return login_res.get("tokenHeader") - else: - error_msg = login_res.get("msg", "未知错误") if login_res else "未知错误" - self.logger.error(f"获取Token失败: {error_msg}") - return None - - -# 兼容性函数 - 保持向后兼容 -def extract_param_defaults(param_list: list) -> Dict[str, Any]: - """提取参数默认值(兼容性函数)""" - return ParameterExtractor.extract_param_defaults(param_list) - - -async def execute_api_call_with_retry( - api_config: Dict[str, Any], - user_params: Dict[str, Any], - need_auth: bool = False, - max_attempts: int = 3, - base_delay: int = 1, - max_delay: int = 60, -) -> Optional[Dict[str, Any]]: - """API重试调用(兼容性函数)""" - executor = ApiRetryExecutor(max_attempts, base_delay, max_delay) - return await executor.execute_with_retry(api_config, user_params, need_auth) - - -class EnvManager: - """环境变量管理器(兼容性类)""" - - @staticmethod - def get_env(key: str, default: Optional[str] = None) -> Optional[str]: - return EnvironmentManager.get(key, default) - - @staticmethod - def set_env(key: str, value: str) -> bool: - return EnvironmentManager.set(key, value) - - @staticmethod - def exists_env(key: str) -> bool: - return EnvironmentManager.exists(key) - - -class Config: - """配置类(兼容性)""" - pass - - -async def test_auth_service(): - """测试认证服务""" - auth_service = AuthService() - token_header = await auth_service.check_user_token( - "1932715213891215361", "1932385006853664770" - ) - logger.info(f"测试结果 - Token头: {token_header}") - - -if __name__ == "__main__": - # 配置日志 - from ..util.logger_config import setup_logging - import logging - setup_logging(log_level=logging.INFO) - - asyncio.run(test_auth_service()) diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_base.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_base.py deleted file mode 100644 index e484f2e..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_base.py +++ /dev/null @@ -1,610 +0,0 @@ -""" -API基础模块 - 核心API管理和调用功能 - -这个模块是整个系统的核心,提供了API配置管理、Schema生成、接口调用等基础功能。 -主要包含以下组件: - -1. ApiBase类: API管理的抽象基类 -2. 工具函数: 拼音转换、Schema处理等 -3. 常量定义: 认证级别、参数类型等 - -主要功能: -- API配置的加载和处理 -- JSON Schema的生成和验证 -- 中文接口名称转拼音命名 -- API接口的统一调用管理 -- 认证和参数处理 - -作者: lzwcai -版本: 1.0.0 -""" - -import json -import re -import pypinyin -from abc import ABC, abstractmethod -from typing import Dict, List, Any, Tuple, Optional, Union - -# 导入业务工具模块 -from ..business.business_util import ( - generate_json_schema, # JSON Schema生成 - generate_schema_prompt, # Schema提示文本生成 - remove_property_from_api_array, # API数组属性移除 -) - - -# ==================== 常量定义 ==================== - -class AuthenticationLevel: - """ - API认证级别常量 - - 定义API接口的认证要求级别: - - REQUIRED: 需要认证(值为1) - - NOT_REQUIRED: 不需要认证(值为0) - """ - REQUIRED = 1 # 需要认证 - NOT_REQUIRED = 0 # 不需要认证 - - -# 导入统一日志配置 -from ..util.logger_config import get_logger - -# 获取日志器实例 -logger = get_logger(__name__) - - -# ==================== 工具函数 ==================== - -def pinyin_to_camel(text: str) -> str: - """ - 中文文本转拼音驼峰命名函数 - - 将中文文本转换为带'tool_'前缀的驼峰命名格式,用于生成API工具的标识符。 - 这个函数是系统中重要的命名转换工具,确保中文API名称能够转换为合法的标识符。 - - 转换规则: - 1. 将所有非字母数字字符(包括标点符号)替换为下划线 - 2. 将空格替换为下划线 - 3. 移除连续的下划线并去除首尾下划线 - 4. 使用pypinyin库将中文转换为拼音 - 5. 每个拼音单词首字母大写(驼峰格式) - 6. 添加'tool_'前缀以符合工具命名规范 - - 参数: - text: 要转换的中文文本 - - 返回: - str: 转换后的驼峰命名字符串,格式为'tool_XxxYyy' - - 示例: - >>> pinyin_to_camel("用户登录") - 'tool_YongHuDengLu' - >>> pinyin_to_camel("获取订单列表") - 'tool_HuoQuDingDanLieBiao' - - 异常处理: - TypeError: 如果输入不是字符串类型 - ValueError: 如果输入为空字符串 - - 容错机制: - - 转换失败时使用hash值生成备用名称 - - 确保始终返回有效的标识符 - """ - # 参数类型检查 - if not isinstance(text, str): - raise TypeError("text must be a string") - - # 参数内容检查 - if not text.strip(): - raise ValueError("text cannot be empty") - - try: - logger.debug(f"转换中文文本为拼音: {text}") - - # 第一步:将所有非中文、非字母、非数字的字符(包括中文标点符号)替换为空格 - # 这样可以正确处理中文标点符号(包括中文括号、顿号等) - # \u4e00-\u9fff 匹配所有中文字符 - # a-zA-Z0-9 匹配英文字母和数字 - cleaned = re.sub(r'[^\u4e00-\u9fffa-zA-Z0-9\s]', ' ', text) - - # 第二步:将多个空格合并为一个空格,并去除首尾空格 - cleaned = re.sub(r'\s+', ' ', cleaned).strip() - - # 第三步:使用pypinyin库转换为拼音列表 - # pypinyin会将中文转为拼音,英文和数字保持原样 - pinyin_list = pypinyin.lazy_pinyin(cleaned) - - # 第四步:将拼音列表转换为驼峰格式 - # 过滤掉空白字符、下划线等特殊字符,只保留有效的拼音单词 - # 注意:pypinyin对于空格会产生空字符串,需要过滤掉 - camel_case = "".join( - word.strip().capitalize() - for word in pinyin_list - if word.strip() and word.strip() not in ['_', '-', '.', ' '] - ) - - # 第五步:添加工具前缀 - result = f"tool_{camel_case}" - - logger.debug(f"拼音转换结果: {text} -> {result}") - return result - - except Exception as e: - logger.error(f"拼音转换失败,文本: '{text}', 错误: {str(e)}") - - # 容错处理:生成基于hash的备用名称 - # 使用hash确保相同输入产生相同输出 - fallback = f"tool_Unknown_{hash(text) % 10000}" - logger.warning(f"使用备用名称: {fallback}") - return fallback - - -def _process_api_schema(param: Dict[str, Any]) -> Tuple[Dict[str, Any], str]: - """ - API参数Schema处理函数 - - 这个函数负责处理单个API配置,生成对应的JSON Schema和接口名称。 - 它是API配置转换的核心函数,将业务平台的API配置转换为MCP工具所需的格式。 - - 处理流程: - 1. 提取API参数列表 - 2. 生成JSON Schema(包含参数类型、描述、默认值等) - 3. 将中文接口名转换为拼音格式的工具名称 - 4. 返回处理后的Schema和接口名称 - - 参数: - param: API参数配置字典,包含以下字段: - - interfaceName: 接口名称(中文) - - parameters: 参数列表 - - 其他API配置信息 - - 返回: - tuple: (处理后的JSON Schema, 转换后的接口名称) - - processed_schema: 符合JSON Schema规范的参数定义 - - interface_name: 转换后的拼音格式接口名称 - - 异常处理: - ImportError: 当无法导入必需模块时抛出 - Exception: 当Schema生成失败时抛出 - - 注意事项: - - userId参数的处理在请求时进行,而不是在Schema生成时 - - userId现在存储在lzwcaiConfig分组中,支持动态userId值,提高系统灵活性 - - 使用延迟导入避免循环依赖问题 - """ - try: - # 延迟导入避免循环依赖 - # 这些模块可能会反过来导入当前模块 - from .core_server import get_env_user_id - # from ..business.business_util import remove_property_from_api_item - - logger.debug(f"处理API参数: {param.get('interfaceName', 'N/A')}") - - # 提取API参数列表并生成JSON Schema - parameters = param.get("parameters", []) - logger.debug(f"参数数量: {len(parameters)}") - - # 调用业务工具模块生成标准JSON Schema - schema = generate_json_schema(parameters) - - # 重要说明:userId参数的处理策略 - # 为了支持动态userId值,userId参数的处理在请求时进行, - # 而不是在Schema生成时进行。这样可以支持不同用户的动态切换。 - # userId现在存储在lzwcaiConfig分组中。 - logger.debug("Schema生成完成,userId处理将在请求时进行") - - # 生成接口名称(中文转拼音) - interface_name_raw = param.get("interfaceName", "") - if interface_name_raw: - # 使用拼音转换函数生成工具名称 - interface_name = pinyin_to_camel(interface_name_raw) - else: - # 备用名称,防止接口名称为空 - interface_name = "tool_Unknown" - - logger.debug(f"生成接口名称: {interface_name_raw} -> {interface_name}") - return schema, interface_name - - except ImportError as e: - logger.error(f"导入必需模块失败: {str(e)}") - raise - except Exception as e: - logger.error(f"处理API Schema时出错: {str(e)}") - raise - - -def get_api_configs_map(api_configs: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - """ - API配置映射处理函数 - - 这个函数是API配置处理的核心,负责将原始的API配置列表转换为 - 包含Schema和描述信息的处理后配置列表。每个API配置都会被转换为 - 一个完整的MCP工具定义。 - - 处理内容: - 1. 为每个API生成JSON Schema - 2. 转换中文接口名为拼音格式 - 3. 生成工具描述(业务描述 + 参数说明) - 4. 创建完整的工具配置对象 - 5. 错误处理和容错机制 - - 参数: - api_configs: API配置字典列表,每个字典包含: - - interfaceName: 接口名称 - - businessPrompts: 业务描述 - - parameters: 参数列表 - - 其他API配置信息 - - 返回: - list: 处理后的API配置列表,每个配置包含: - - interfaceName: 转换后的接口名称(拼音格式) - - schema: JSON Schema对象 - - schema_description: 完整的工具描述 - - 原始配置的所有其他字段 - - 异常处理: - TypeError: 如果api_configs不是列表类型 - ValueError: 如果api_configs为空列表 - - 容错机制: - - 跳过无效的配置项(非字典类型) - - 处理失败时保留原始配置并添加错误标记 - - 详细记录处理过程和错误信息 - """ - # 参数验证 - if not isinstance(api_configs, list): - raise TypeError("api_configs must be a list") - - if not api_configs: - raise ValueError("api_configs cannot be empty") - - logger.info(f"开始处理 {len(api_configs)} 个API配置") - api_array = [] - - # 遍历处理每个API配置 - for i, param in enumerate(api_configs): - # 检查配置项类型 - if not isinstance(param, dict): - logger.warning( - f"跳过无效的API配置 (索引 {i}): 不是字典类型" - ) - continue - - try: - logger.debug(f"处理API配置 {i}: {param.get('interfaceName', 'N/A')}") - - # 处理Schema和接口名称 - schema, interface_name = _process_api_schema(param) - - # 获取业务描述 - description = param.get("businessPrompts", "") - - # 生成参数Schema的提示文本 - schema_prompt = generate_schema_prompt(schema) - - # 组合工具描述:业务描述 + 参数说明 - if description: - schema_description = f"{description}\n\n{schema_prompt}" - else: - schema_description = f"工具描述: 暂无描述\n\n{schema_prompt}" - - # 创建处理后的API配置对象 - processed_config = { - **param, # 保留原始配置的所有字段 - "interfaceName": interface_name, # 更新接口名称为拼音格式 - "schema": schema, # 添加JSON Schema - "schema_description": schema_description, # 添加完整描述 - } - - api_array.append(processed_config) - logger.debug(f"API配置 {i} 处理完成: {interface_name}") - - except Exception as e: - # 处理异常:记录错误但不中断整个处理流程 - logger.error(f"处理API配置 {i} 时出错: {str(e)}") - logger.debug(f"错误的API配置内容: {param}") - - # 创建错误配置对象,保留原始数据但添加错误标记 - error_config = { - **param, # 保留原始配置 - "interfaceName": f"tool_Error_{i}", # 错误标记的接口名 - "schema": {}, # 空Schema - "schema_description": f"配置处理错误: {str(e)}", # 错误描述 - } - api_array.append(error_config) - - logger.info(f"API配置处理完成,成功处理 {len(api_array)} 个配置") - return api_array - - -# ==================== API基础管理类 ==================== - -class ApiBase(ABC): - """ - API管理抽象基类 - - 这个类是整个API管理系统的核心基类,提供了API配置处理、 - Schema生成、接口调用等基础功能。所有具体的API管理实现 - 都应该继承这个类。 - - 主要职责: - 1. API配置的加载和验证 - 2. API配置到工具定义的转换 - 3. 提供统一的API调用接口 - 4. 管理API配置的生命周期 - 5. 提供配置查询和检索功能 - - 设计模式: - - 抽象基类模式:定义API管理的标准接口 - - 模板方法模式:提供通用的处理流程 - - 策略模式:支持不同的认证和调用策略 - - 属性: - api_configs: 原始API配置列表 - api_configs_map: 处理后的API配置映射表 - """ - - def __init__(self, api_configs: List[Dict[str, Any]]) -> None: - """ - 初始化API基础管理器 - - 这个构造函数负责初始化API管理器,处理传入的API配置列表, - 并生成相应的工具定义映射表。 - - 初始化流程: - 1. 验证输入参数的类型和内容 - 2. 保存原始API配置 - 3. 调用配置映射函数生成工具定义 - 4. 记录初始化结果 - 5. 可选的调试输出 - - 参数: - api_configs: API配置字典列表,每个字典包含完整的API定义 - - 异常处理: - TypeError: 如果api_configs不是列表类型 - ValueError: 如果api_configs为空列表 - - 注意事项: - - 配置验证在映射函数中进行 - - 支持部分配置失败的容错处理 - - 调试模式下可以输出Schema到文件 - """ - # 参数类型验证 - if not isinstance(api_configs, list): - raise TypeError("api_configs must be a list") - - # 参数内容验证 - if not api_configs: - raise ValueError("api_configs cannot be empty") - - # 保存原始配置 - self.api_configs = api_configs - - # 生成处理后的配置映射表 - # 这是核心处理步骤,将原始配置转换为MCP工具定义 - self.api_configs_map = get_api_configs_map(api_configs) - - logger.info(f"ApiBase初始化完成,共处理 {len(self.api_configs)} 个API配置") - - # 可选的调试输出(默认关闭) - # 在开发和调试阶段可以启用这个功能 - # self._save_debug_schema() - - def _save_debug_schema(self) -> None: - """ - 保存调试Schema到文件 - - 这个方法用于开发和调试阶段,将处理后的API配置映射表 - 保存到JSON文件中,方便查看和分析Schema生成结果。 - - 输出文件: - output_schema.json: 包含所有处理后的API配置 - - 特性: - - UTF-8编码确保中文正确显示 - - 格式化输出便于阅读 - - 异常安全,不会影响主要功能 - """ - try: - with open("output_schema.json", "w", encoding="utf-8") as f: - json.dump(self.api_configs_map, f, ensure_ascii=False, indent=4) - logger.debug("调试Schema已保存到 output_schema.json") - except Exception as e: - logger.error(f"保存调试Schema失败: {str(e)}") - - async def call_interface( - self, api_config: Dict[str, Any], request_data: Dict[str, Any] - ) -> Any: - """ - API接口调用方法 - - 这是ApiBase类的核心方法,负责调用具体的API接口。 - 它处理认证逻辑、参数传递和错误处理,为上层提供统一的API调用接口。 - - 调用流程: - 1. 验证输入参数的类型 - 2. 导入核心服务器模块(避免循环依赖) - 3. 判断是否需要认证 - 4. 调用底层API接口 - 5. 返回API响应结果 - - 认证处理: - - 根据API配置中的authenticationRequired字段判断是否需要认证 - - 支持两种认证级别:REQUIRED(1) 和 NOT_REQUIRED(0) - - 认证逻辑由core_server模块的call_api函数处理 - - 参数: - api_config: API配置字典,包含: - - authenticationRequired: 认证要求级别 - - apiUrl: API接口地址 - - method: HTTP方法 - - 其他API配置信息 - request_data: 请求数据字典,包含: - - header: 请求头参数 - - query: 查询参数 - - body: 请求体参数 - - lzwcaiConfig: 配置参数(包含userId) - - 返回: - Any: API接口的响应数据,通常是字典格式 - - 异常处理: - TypeError: 如果参数不是字典类型 - ImportError: 如果无法导入核心服务器模块 - Exception: 如果API调用失败 - - 设计考虑: - - 使用延迟导入避免循环依赖 - - 统一的错误处理和日志记录 - - 支持异步调用以提高性能 - """ - # 参数类型验证 - if not isinstance(api_config, dict): - raise TypeError("api_config must be a dictionary") - - if not isinstance(request_data, dict): - raise TypeError("request_data must be a dictionary") - - try: - # 延迟导入避免循环依赖 - # core_server模块可能会导入当前模块 - from .core_server import call_api - - # 判断认证要求 - # 从API配置中获取认证要求,默认为不需要认证 - auth_required = api_config.get( - "authenticationRequired", AuthenticationLevel.NOT_REQUIRED - ) - need_auth = auth_required == AuthenticationLevel.REQUIRED - - logger.info(f"调用API接口,需要认证: {need_auth}") - logger.debug(f"API配置: {api_config.get('apiUrl', 'N/A')}") - - # 调用底层API接口 - # call_api函数处理具体的HTTP请求、认证、参数处理等 - return await call_api(api_config, request_data, need_auth=need_auth) - - except ImportError as e: - logger.error(f"导入call_api函数失败: {str(e)}") - raise ImportError( - f"无法导入必需的API调用功能: {str(e)}" - ) - except Exception as e: - logger.error(f"API调用失败: {str(e)}") - logger.debug("API调用异常详情:", exc_info=True) - raise - - def get_api_config_by_name(self, interface_name: str) -> Optional[Dict[str, Any]]: - """ - 根据接口名称获取API配置 - - 这个方法用于根据接口名称查找对应的API配置。 - 接口名称是经过拼音转换后的工具名称(如:tool_YongHuDengLu)。 - - 查找逻辑: - - 遍历所有处理后的API配置 - - 匹配interfaceName字段 - - 返回第一个匹配的配置 - - 参数: - interface_name: 要查找的接口名称(拼音格式) - - 返回: - Optional[Dict[str, Any]]: 找到的API配置字典,未找到则返回None - - 使用场景: - - MCP工具调用时查找对应的API配置 - - 验证工具名称是否存在 - - 获取特定工具的配置信息 - """ - # 参数类型检查 - if not isinstance(interface_name, str): - logger.warning(f"接口名称类型错误: {type(interface_name)}") - return None - - # 遍历查找匹配的配置 - for config in self.api_configs_map: - if config.get("interfaceName") == interface_name: - logger.debug(f"找到接口配置: {interface_name}") - return config - - logger.debug(f"未找到接口配置: {interface_name}") - return None - - def get_all_interface_names(self) -> List[str]: - """ - 获取所有可用的接口名称列表 - - 这个方法返回所有已处理的API配置的接口名称列表。 - 主要用于调试、监控和工具列表展示。 - - 返回: - List[str]: 所有接口名称的列表(拼音格式) - - 特性: - - 过滤掉空的接口名称 - - 返回的是处理后的拼音格式名称 - - 按配置顺序返回 - - 使用场景: - - 系统监控和状态检查 - - 调试和日志记录 - - 管理界面展示可用工具 - """ - interface_names = [ - config.get("interfaceName", "") - for config in self.api_configs_map - if config.get("interfaceName") # 过滤空名称 - ] - - logger.debug(f"获取到 {len(interface_names)} 个接口名称") - return interface_names - - def get_schema_by_name(self, interface_name: str) -> Optional[Dict[str, Any]]: - """ - 根据接口名称获取JSON Schema - - 这个方法是get_api_config_by_name的便捷包装, - 直接返回指定接口的JSON Schema定义。 - - 参数: - interface_name: 接口名称(拼音格式) - - 返回: - Optional[Dict[str, Any]]: JSON Schema字典,未找到则返回None - - 使用场景: - - 参数验证 - - 文档生成 - - 客户端工具定义 - """ - config = self.get_api_config_by_name(interface_name) - if config: - return config.get("schema") - return None - - @property - def config_count(self) -> int: - """ - 获取API配置数量 - - 这是一个属性方法,返回当前管理的API配置总数。 - 主要用于监控、日志记录和状态检查。 - - 返回: - int: API配置的数量 - """ - return len(self.api_configs_map) - - def __repr__(self) -> str: - """ - ApiBase对象的字符串表示 - - 提供对象的简洁字符串表示,主要用于调试和日志记录。 - - 返回: - str: 对象的字符串表示,格式为"ApiBase(configs=数量)" - """ - return f"ApiBase(configs={self.config_count})" diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/core_server.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/core_server.py deleted file mode 100644 index 4ebdd27..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/core_server.py +++ /dev/null @@ -1,1048 +0,0 @@ -""" -核心服务器模块 - -提供API调用、认证处理、配置管理等核心功能 -""" - -import asyncio -import json -import os -from datetime import datetime -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Union -from urllib.parse import urljoin - -import httpx - -from .api_auth_service import AuthService -from ..util.logger_config import get_logger - -# 获取日志器 -logger = get_logger(__name__) - - -class ApiError(Exception): - """API调用相关异常""" - - def __init__(self, message: str, status_code: Optional[int] = None): - super().__init__(message) - self.status_code = status_code - - -class ResponseSaver: - """响应数据保存管理器""" - - def __init__(self, save_dir: str = "lzwcai_mcp_dyntoolapi_log_call_api"): - """ - 初始化响应保存器 - - Args: - save_dir: 保存目录路径 - """ - self.save_dir = Path(save_dir) - self.save_dir.mkdir(exist_ok=True) - logger.debug(f"响应保存目录: {self.save_dir.absolute()}") - - def save_response( - self, - response_data: Dict[str, Any], - api_url: str, - method: str = "GET", - request_data: Optional[Dict[str, Any]] = None, - ) -> str: - """ - 保存API响应到本地JSON文件 - - Args: - response_data: 响应数据 - api_url: API URL - method: HTTP方法 - request_data: 请求数据 - - Returns: - 保存的文件路径 - """ - try: - # 生成文件名 - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # 精确到毫秒 - safe_url = self._sanitize_filename(api_url) - filename = f"{timestamp}_{method}_{safe_url}.json" - file_path = self.save_dir / filename - - # 构建保存的数据结构 - save_data = { - "timestamp": datetime.now().isoformat(), - "api_info": { - "url": api_url, - "method": method.upper(), - }, - "request_data": request_data or {}, - "response_data": response_data, - "metadata": { - "saved_at": datetime.now().isoformat(), - "file_name": filename, - } - } - - # 保存到文件 - with open(file_path, "w", encoding="utf-8") as f: - json.dump(save_data, f, ensure_ascii=False, indent=2) - - logger.info(f"API响应已保存到: {file_path}") - return str(file_path) - - except Exception as e: - logger.error(f"保存API响应失败: {str(e)}") - return "" - - def _sanitize_filename(self, url: str) -> str: - """ - 清理URL以生成安全的文件名 - - Args: - url: 原始URL - - Returns: - 清理后的文件名部分 - """ - # 移除协议和域名,只保留路径 - if "://" in url: - url = url.split("://", 1)[1] - if "/" in url: - url = url.split("/", 1)[1] if "/" in url else url - - # 替换特殊字符 - safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" - sanitized = "".join(c if c in safe_chars else "_" for c in url) - - # 限制长度 - return sanitized[:50] if len(sanitized) > 50 else sanitized - - -class ConfigManager: - """配置管理器""" - - @staticmethod - def load_api_config(file_path: str = "generator_api.json") -> Dict[str, Any]: - """ - 加载API配置文件 - - Args: - file_path: JSON配置文件路径 - - Returns: - 解析后的API配置字典 - - Raises: - FileNotFoundError: 文件不存在 - json.JSONDecodeError: JSON格式错误 - """ - logger.debug(f"尝试加载配置文件: {file_path}") - - try: - with open(file_path, "r", encoding="utf-8") as file: - config = json.load(file) - logger.info(f"成功加载配置文件: {file_path}") - logger.debug(f"配置内容: {config}") - return config - except FileNotFoundError: - logger.error(f"配置文件未找到: {file_path}") - raise FileNotFoundError(f"配置文件未找到: {file_path}") - except json.JSONDecodeError as e: - logger.error(f"配置文件JSON格式错误: {file_path} - {str(e)}") - raise json.JSONDecodeError( - f"配置文件JSON格式错误: {file_path} - {str(e)}", e.doc, e.pos - ) - - -class UserManager: - """用户管理器""" - - @staticmethod - def get_user_id_from_env() -> Tuple[bool, Optional[str]]: - """ - 从环境变量中获取用户ID - - Returns: - tuple: (是否成功获取, 用户ID值) - """ - try: - user_id = os.environ.get("userId") - if user_id: - logger.debug(f"从环境变量获取用户ID: {user_id}") - return True, user_id - logger.debug("环境变量中未找到用户ID") - return False, None - except Exception as e: - logger.warning(f"获取环境变量用户ID时发生异常: {e}") - return False, None - - @staticmethod - def extract_user_id_from_request( - request_data: Dict[str, Any], is_grouped_format: bool - ) -> Optional[str]: - """ - 从请求数据中提取用户ID - - Args: - request_data: 请求数据 - is_grouped_format: 是否为分组格式 - - Returns: - 用户ID或None - """ - # 优先从请求数据中获取userId - if isinstance(request_data, dict): - if is_grouped_format: - # 按优先级顺序查找:lzwcaiConfig > header > body > userId(兼容旧版本) - user_id = ( - request_data.get("lzwcaiConfig", {}).get("userId") - or request_data.get("header", {}).get("userId") - or request_data.get("body", {}).get("userId") - or (request_data.get("userId") or {}).get("userId") - ) - if user_id: - logger.info(f"从请求数据中获取到用户ID: {user_id}") - return user_id - else: - # 非分组格式:优先从lzwcaiConfig获取,然后是userId(兼容旧版本) - user_id = ( - (request_data.get("lzwcaiConfig") or {}).get("userId") - or (request_data.get("userId") or {}).get("userId") - ) - if user_id: - logger.info(f"从请求数据中获取到用户ID: {user_id}") - return user_id - - # 如果请求数据中没有userId,则从环境变量获取作为备用 - success, env_user_id = UserManager.get_user_id_from_env() - if success: - logger.info(f"从环境变量获取到用户ID: {env_user_id}") - return env_user_id - - logger.warning("未能从请求数据或环境变量中获取到用户ID") - return None - - -class HeaderProcessor: - """请求头处理器""" - - @staticmethod - def validate_header_value(value: Any) -> str: - """ - 验证并标准化请求头值 - - Args: - value: 原始值 - - Returns: - 标准化后的字符串值 - """ - if value is None: - return "" - return str(value).strip() - - @staticmethod - def process_auth_headers( - base_headers: Dict[str, Any], - request_data: Dict[str, Any], - auth_token: Optional[Dict[str, Any]] = None, - ) -> Dict[str, str]: - """ - 处理认证头信息 - - Args: - base_headers: 基础请求头 - request_data: 请求数据 - auth_token: 认证token信息 - - Returns: - 处理后的请求头字典 - - Raises: - ValueError: 当base_headers不是字典类型时 - """ - if not isinstance(base_headers, dict): - raise ValueError("base_headers必须是字典类型") - - if not isinstance(request_data, dict): - request_data = {} - - if auth_token is None: - auth_token = {} - - # 获取请求数据中的header部分 - request_headers = request_data.get("header", {}) - - # 按优先级合并headers: base < request < auth_token - processed_headers = {} - - # 1. 基础headers (key转为小写以避免重复) - for key, value in base_headers.items(): - processed_headers[key.lower()] = HeaderProcessor.validate_header_value(value) - - # 2. 请求中的headers (key转为小写以避免重复) - for key, value in request_headers.items(): - processed_headers[key.lower()] = HeaderProcessor.validate_header_value(value) - - # 3. 认证token headers (最高优先级, key转为小写以避免重复) - for key, value in auth_token.items(): - processed_headers[key.lower()] = HeaderProcessor.validate_header_value(value) - - return processed_headers - - -class RequestBuilder: - """请求构建器""" - - @staticmethod - def is_grouped_format(request_data: Dict[str, Any]) -> bool: - """ - 检查请求数据是否为分组格式 - - Args: - request_data: 请求数据 - - Returns: - 是否为分组格式 - """ - return any( - key in ["header", "path", "query", "body", "params", "form", "formdata"] - for key in request_data.keys() - ) - - @staticmethod - def build_url_with_path_params(base_url: str, path_params: Dict[str, Any]) -> str: - """ - 使用路径参数构建URL - - Args: - base_url: 基础URL - path_params: 路径参数 - - Returns: - 替换路径参数后的URL - """ - url = base_url - for key, value in path_params.items(): - placeholder = f"{{{key}}}" - if placeholder in url: - url = url.replace(placeholder, str(value)) - return url - - @staticmethod - def extract_parameters_from_grouped_format( - request_data: Dict[str, Any], method: str - ) -> Tuple[ - Dict[str, Any], - Dict[str, Any], - Optional[Dict[str, Any]], - Optional[Dict[str, Any]], - Optional[Dict[str, Any]], - ]: - """ - 从分组格式请求数据中提取参数 - - Args: - request_data: 请求数据 - method: HTTP方法 - - Returns: - (path_params, query_params, json_data, form_data, formdata_data) - """ - path_params = {} - query_params = {} - json_data = None - form_data = None - formdata_data = None - - # 处理路径参数 - if "path" in request_data: - path_params.update(request_data["path"]) - - # 兼容params命名(与path含义相同) - if "params" in request_data: - path_params.update(request_data["params"]) - - # 处理查询参数 - if "query" in request_data: - query_params.update(request_data["query"]) - - # 处理请求体数据 - if "body" in request_data and method.upper() in ["POST", "PUT", "PATCH"]: - json_data = request_data["body"] - - if "form" in request_data and method.upper() in ["POST", "PUT", "PATCH"]: - form_data = request_data["form"] - - if "formdata" in request_data and method.upper() in ["POST", "PUT", "PATCH"]: - formdata_data = request_data["formdata"] - - return path_params, query_params, json_data, form_data, formdata_data - - @staticmethod - def extract_parameters_from_flat_format( - request_data: Dict[str, Any], parameters: List[Dict[str, Any]], method: str - ) -> Tuple[ - Dict[str, str], - Dict[str, Any], - Dict[str, Any], - Optional[Dict[str, Any]], - Optional[Dict[str, Any]], - Optional[Dict[str, Any]], - ]: - """ - 从扁平格式请求数据中提取参数 - - Args: - request_data: 请求数据 - parameters: 参数配置列表 - method: HTTP方法 - - Returns: - (headers, path_params, query_params, json_data, form_data, formdata_data) - """ - headers = {} - path_params = {} - query_params = {} - json_data = None - form_data = None # for application/x-www-form-urlencoded - formdata_data = None # for multipart/form-data - - for param in parameters: - param_name = param.get("paramName") - request_type = param.get("requestType") - default_value = param.get("defaultValue") - - # 获取参数值:优先使用请求数据中的值,否则使用默认值 - if param_name in request_data: - param_value = request_data[param_name] - # 如果传入的值为None或空字符串,且有默认值,则使用默认值 - if param_value in (None, "") and default_value is not None: - param_value = default_value - else: - param_value = default_value - - # 如果参数没有值且不是必需的,则跳过 - if param_value is None and param.get("required", 0) == 0: - continue - - # 根据请求类型分配参数 - if request_type == "header": - headers[param_name] = ( - str(param_value) if param_value is not None else "" - ) - elif request_type == "query": - if param_value is not None: - query_params[param_name] = param_value - elif request_type in ["params", "path"]: - if param_value is not None: - path_params[param_name] = param_value - elif request_type == "body" and method.upper() in ["POST", "PUT", "PATCH"]: - if json_data is None: - json_data = {} - json_data[param_name] = param_value - elif request_type == "form" and method.upper() in ["POST", "PUT", "PATCH"]: - if form_data is None: - form_data = {} - form_data[param_name] = param_value - elif request_type == "formdata" and method.upper() in ["POST", "PUT", "PATCH"]: - if formdata_data is None: - formdata_data = {} - formdata_data[param_name] = param_value - - return headers, path_params, query_params, json_data, form_data, formdata_data - - -class ApiClient: - """API客户端""" - - def __init__(self, timeout: int = 30, verify: bool = True, save_responses: bool = True, save_dir: str = "lzwcai_mcp_dyntoolapi_log_call_api"): - """ - 初始化API客户端 - - Args: - timeout: 请求超时时间(秒) - verify: 是否验证SSL证书 - save_responses: 是否保存响应到本地JSON文件 - save_dir: 响应保存目录 - """ - self.timeout = timeout - self.verify = verify - self.save_responses = save_responses - self.auth_service = AuthService() - - # 初始化响应保存器 - if self.save_responses: - self.response_saver = ResponseSaver(save_dir) - logger.info(f"已启用响应保存功能,保存目录: {save_dir}") - else: - self.response_saver = None - logger.info("响应保存功能已禁用") - - async def call_api( - self, - api_config: Dict[str, Any], - request_data: Optional[Dict[str, Any]] = None, - need_auth: bool = True, - ) -> Dict[str, Any]: - """ - 通用API调用方法 - - Args: - api_config: API配置信息 - request_data: 请求数据,支持扁平格式和分组格式 - need_auth: 是否需要认证 - - Returns: - API响应结果 - - Raises: - ApiError: API调用相关错误 - ValueError: 配置或参数错误 - """ - api_url = api_config.get("apiUrl", "N/A") - logger.info(f"开始API调用: {api_url}") - logger.debug(f"需要认证: {need_auth}") - logger.debug(f"请求数据: {request_data}") - - # 验证API配置 - self._validate_api_config(api_config) - - # 准备基础信息 - domain_url = api_config["domainUrl"] - api_url = api_config["apiUrl"] - method = api_config.get("method", "GET") - - # 构建完整URL - full_url = urljoin(domain_url.rstrip("/") + "/", api_url.lstrip("/")) - logger.debug(f"完整URL: {full_url}") - logger.debug(f"HTTP方法: {method}") - - # 初始化请求参数 - headers = {} - query_params = {} - path_params = {} - json_data = None - form_data = None - formdata_data = None - - request_data = request_data or {} - parameters = api_config.get("parameters", []) - logger.debug(f"参数配置数量: {len(parameters)}") - - # 检查请求数据格式 - is_grouped = RequestBuilder.is_grouped_format(request_data) - logger.debug(f"请求数据格式 - 分组格式: {is_grouped}") - - # 处理认证 - if need_auth: - logger.debug("开始处理认证") - auth_headers = await self._handle_authentication( - request_data, api_config, is_grouped - ) - headers.update(auth_headers) - logger.debug(f"认证头信息: {auth_headers}") - else: - logger.debug("跳过认证") - - # 提取参数 - if is_grouped: - (path_params, query_params, json_data, form_data, formdata_data) = ( - RequestBuilder.extract_parameters_from_grouped_format( - request_data, method - ) - ) - else: - ( - param_headers, - path_params, - query_params, - json_data, - form_data, - formdata_data, - ) = RequestBuilder.extract_parameters_from_flat_format( - request_data, parameters, method - ) - headers.update(param_headers) - - logger.info(f"请求头: {headers},request_data: {request_data}") - # 处理请求头 - headers = HeaderProcessor.process_auth_headers(headers, request_data) - # 替换URL中的路径参数 - if path_params: - full_url = RequestBuilder.build_url_with_path_params(full_url, path_params) - - # 根据请求体内容设置Content-Type (如果未被显式设置) - # 注意:httpx 会自动处理某些 Content-Type,但显式设置可以避免歧义 - if "content-type" not in headers: - if json_data is not None: - headers["content-type"] = "application/json" - elif form_data is not None: - # application/x-www-form-urlencoded 类型 - headers["content-type"] = "application/x-www-form-urlencoded" - elif formdata_data is not None: - # multipart/form-data 类型 - 不设置 Content-Type,让 httpx 自动添加 boundary - # 如果手动设置会缺少 boundary 参数导致请求失败 - pass - - # 发送请求 - logger.info(f"发送HTTP请求: {method} {full_url}") - - # 记录重要的请求信息(INFO级别,便于调试) - if headers: - # 过滤敏感信息,只显示关键头部 - safe_headers = {} - for key, value in headers.items(): - if key.lower() in ['authorization', 'x-api-key', 'token']: - safe_headers[key] = value - else: - safe_headers[key] = value - logger.info(f"请求头: {safe_headers}") - - if query_params: - logger.info(f"查询参数: {query_params}") - - if json_data: - logger.info(f"请求体 (JSON): {json_data}") - if form_data: - logger.info(f"请求体 (Form): {form_data}") - if formdata_data: - logger.info(f"请求体 (FormData): {formdata_data}") - - # 详细的调试信息仍保留在DEBUG级别 - logger.debug(f"完整请求头: {headers}") - logger.debug(f"完整查询参数: {query_params}") - logger.debug(f"完整请求体 (JSON): {json_data}") - logger.debug(f"完整请求体 (Form): {form_data}") - logger.debug(f"完整请求体 (FormData): {formdata_data}") - - # 发送请求并获取响应 - response = await self._send_request( - method, full_url, headers, query_params, json_data, form_data, formdata_data - ) - - # 保存响应到本地JSON文件 - if self.save_responses and self.response_saver: - try: - saved_path = self.response_saver.save_response( - response_data=response, - api_url=full_url, - method=method, - request_data=request_data - ) - if saved_path: - logger.info(f"响应已保存到: {saved_path}") - except Exception as e: - logger.error(f"保存响应失败: {str(e)}") - - return response - - def _validate_api_config(self, api_config: Dict[str, Any]) -> None: - """验证API配置""" - logger.debug("验证API配置") - - if not api_config: - logger.error("API配置为空") - raise ValueError("API配置不能为空") - - if not api_config.get("domainUrl") or not api_config.get("apiUrl"): - logger.error(f"缺少必要配置项 - domainUrl: {api_config.get('domainUrl')}, apiUrl: {api_config.get('apiUrl')}") - raise ValueError("缺少必要的API配置项:domainUrl或apiUrl") - - logger.debug("API配置验证通过") - - async def _handle_authentication( - self, request_data: Dict[str, Any], api_config: Dict[str, Any], is_grouped: bool - ) -> Dict[str, str]: - """处理认证逻辑""" - user_id = UserManager.extract_user_id_from_request(request_data, is_grouped) - biz_sys_id = api_config.get("bizSysId") - - auth_result = await self.auth_service.authorize_request(user_id, biz_sys_id) - - if not auth_result["success"]: - raise ApiError( - f"认证失败: {auth_result.get('error_response', {})}", - auth_result.get("error_response", {}).get("status_code"), - ) - - token_header = auth_result.get("tokenHeader", {}) - logger.info(f"API调用获取到Token - 用户ID: {user_id}, 业务系统ID: {biz_sys_id}, Token: {token_header}") - return token_header - - - def _contains_file(self, data: Dict[str, Any]) -> bool: - """ - 检查数据字典中是否包含文件类对象 - - 支持的文件类型: - - bytes: 字节数据 - - 具有 read 属性的对象(文件句柄) - - tuple: (filename, content) 或 (filename, content, content_type) 格式 - """ - if not data: - return False - for value in data.values(): - # 检查是否为字节流 - if isinstance(value, bytes): - return True - # 检查是否为文件句柄(具有read属性) - if hasattr(value, 'read'): - return True - # 检查是否为元组格式的文件 (filename, content) 或 (filename, content, content_type) - if isinstance(value, tuple) and len(value) >= 2: - return True - return False - - def _prepare_multipart_data(self, formdata_data: Dict[str, Any]) -> List[Tuple[str, Any]]: - """ - 准备 multipart/form-data 格式的数据 - - 将普通字段和文件字段统一转换为 httpx 可接受的格式 - - httpx 的 files 参数支持两种格式: - 1. Dict[str, tuple] - 每个字段只有一个值 - 2. List[Tuple[str, tuple]] - 支持同名多值参数(如 status[]) - - 为了支持数组参数,我们使用列表格式 - """ - prepared_data = [] - - for key, value in formdata_data.items(): - if value is None: - continue - - # 处理数组类型的值 - if isinstance(value, list): - # 数组参数:为每个元素创建一个同名字段 - for item in value: - if item is None: - # 空值也需要发送 - prepared_data.append((key, (None, ""))) - elif isinstance(item, tuple): - prepared_data.append((key, item)) - elif isinstance(item, bytes): - prepared_data.append((key, (None, item, 'application/octet-stream'))) - elif hasattr(item, 'read'): - prepared_data.append((key, item)) - else: - prepared_data.append((key, (None, str(item) if not isinstance(item, str) else item))) - # 如果已经是元组格式(文件),直接使用 - elif isinstance(value, tuple): - prepared_data.append((key, value)) - # 如果是字节数据,包装为文件格式 - elif isinstance(value, bytes): - prepared_data.append((key, (None, value, 'application/octet-stream'))) - # 如果是文件句柄,直接使用 - elif hasattr(value, 'read'): - prepared_data.append((key, value)) - # 普通字段,转换为 (None, value) 格式 - else: - prepared_data.append((key, (None, str(value) if not isinstance(value, str) else value))) - - return prepared_data - - def _diagnose_content_type_issue( - self, - status_code: int, - response_text: str, - headers: Dict[str, str], - json_data: Optional[Dict[str, Any]], - form_data: Optional[Dict[str, Any]], - formdata_data: Optional[Dict[str, Any]], - ) -> None: - """ - 诊断 Content-Type 相关问题 - - 当请求失败时,检查是否可能是 Content-Type 不匹配导致的问题, - 并给出相应的诊断建议。 - - Args: - status_code: HTTP 状态码 - response_text: 响应内容 - headers: 请求头 - json_data: JSON 请求体数据 - form_data: 表单数据 - formdata_data: multipart 表单数据 - """ - # 常见的 Content-Type 相关错误状态码 - content_type_error_codes = [400, 415, 422] - - if status_code not in content_type_error_codes: - return - - # 获取当前设置的 Content-Type - current_content_type = headers.get("content-type", "").lower() - - # 检查响应中是否包含 Content-Type 相关的错误信息 - response_lower = response_text.lower() if response_text else "" - content_type_keywords = [ - "content-type", "content type", "media type", - "unsupported media", "invalid content", "expected json", - "expected form", "multipart", "boundary" - ] - - has_content_type_error = any(kw in response_lower for kw in content_type_keywords) - - if has_content_type_error or status_code == 415: - logger.warning("=" * 60) - logger.warning("⚠️ 可能的 Content-Type 问题诊断:") - logger.warning(f" 当前 Content-Type: {current_content_type or '未设置'}") - - # 分析数据类型和建议 - if json_data is not None: - logger.warning(" 数据类型: JSON") - if "application/json" not in current_content_type: - logger.warning(" 💡 建议: 请求体是 JSON 格式,但 Content-Type 可能不正确") - logger.warning(" 应该使用: application/json") - - elif form_data is not None: - logger.warning(" 数据类型: Form (application/x-www-form-urlencoded)") - if "application/x-www-form-urlencoded" not in current_content_type: - logger.warning(" 💡 建议: 请求体是表单格式,但 Content-Type 可能不正确") - logger.warning(" 应该使用: application/x-www-form-urlencoded") - - elif formdata_data is not None: - logger.warning(" 数据类型: FormData (multipart/form-data)") - if "multipart/form-data" not in current_content_type: - logger.warning(" 💡 建议: 请求体是 multipart 格式") - logger.warning(" Content-Type 应由 httpx 自动设置(包含 boundary)") - logger.warning(" 如果手动设置了 Content-Type,请移除它") - - else: - logger.warning(" 数据类型: 无请求体") - if current_content_type: - logger.warning(" 💡 建议: 没有请求体但设置了 Content-Type,可能导致问题") - - # 检查常见的配置错误 - if "boundary" in response_lower and formdata_data is not None: - logger.warning(" ⚠️ 检测到 boundary 相关错误:") - logger.warning(" multipart/form-data 需要 boundary 参数") - logger.warning(" 请确保不要手动设置 Content-Type,让 httpx 自动处理") - - logger.warning("=" * 60) - - async def _send_request( - self, - method: str, - url: str, - headers: Dict[str, str], - query_params: Dict[str, Any], - json_data: Optional[Dict[str, Any]], - form_data: Optional[Dict[str, Any]], - formdata_data: Optional[Dict[str, Any]], - ) -> Dict[str, Any]: - """发送HTTP请求""" - async with httpx.AsyncClient( - verify=self.verify, timeout=self.timeout - ) as client: - try: - # 准备请求参数 - request_kwargs = { - "params": query_params, - "headers": headers, - } - - # 为有请求体的方法添加数据 - # 优先级: json > formdata > form - if method.upper() in ["POST", "PUT", "PATCH", "DELETE"]: - if json_data is not None: - request_kwargs["json"] = json_data - elif formdata_data is not None: - # multipart/form-data 类型处理 - # 使用统一的方法准备数据 - prepared_data = self._prepare_multipart_data(formdata_data) - if prepared_data: - request_kwargs["files"] = prepared_data - # 移除手动设置的 content-type,让 httpx 自动添加带 boundary 的 - headers.pop("content-type", None) - elif form_data is not None: - # application/x-www-form-urlencoded 类型 - request_kwargs["data"] = form_data - - # 根据HTTP方法发送请求 - request_func = getattr(client, method.lower(), None) - if request_func is None: - raise ValueError(f"不支持的HTTP方法: {method}") - - response = await request_func(url, **request_kwargs) - - # 检查响应状态 - response.raise_for_status() - - # 解析响应 - return self._parse_response(response) - - except httpx.RequestError as e: - logger.error(f"请求错误: {str(e)}") - raise ApiError(f"请求发送失败: {str(e)}") - except httpx.HTTPStatusError as e: - # 记录详细的HTTP错误信息 - logger.error(f"HTTP状态错误: {e.response.status_code} - {str(e)}") - logger.error(f"请求URL: {e.request.url}") - logger.error(f"请求方法: {e.request.method}") - - # 记录响应内容(用于调试) - response_text = e.response.text - if response_text: - logger.error(f"响应内容: {response_text[:500]}{'...' if len(response_text) > 500 else ''}") - - # 记录响应头(可能包含有用的错误信息) - if e.response.headers: - logger.info(f"响应头: {dict(e.response.headers)}") - - # Content-Type 不匹配诊断 - self._diagnose_content_type_issue( - e.response.status_code, - response_text, - headers, - json_data, - form_data, - formdata_data - ) - - return { - "status": "error", - "status_code": e.response.status_code, - "error": str(e), - "response": e.response.text, - } - except Exception as e: - logger.error(f"未知错误: {str(e)}") - raise ApiError(f"请求处理失败: {str(e)}") - - def _parse_response(self, response: httpx.Response) -> Dict[str, Any]: - """解析HTTP响应""" - # 记录响应信息 - logger.info(f"HTTP响应: {response.status_code} {response.reason_phrase}") - - content_type = response.headers.get("content-type", "") - logger.info(f"响应类型: {content_type}") - - # 记录响应大小 - content_length = len(response.content) if response.content else 0 - logger.info(f"响应大小: {content_length} bytes") - - if content_type.startswith("application/json"): - try: - json_response = response.json() - # 记录JSON响应的基本信息(避免记录过大的数据) - if isinstance(json_response, dict): - logger.info(f"JSON响应键: {list(json_response.keys())}") - return json_response - except Exception as e: - logger.error(f"JSON解析失败: {str(e)}") - return { - "status": "error", - "error": f"JSON解析失败: {str(e)}", - "raw_response": response.text, - "status_code": response.status_code, - } - else: - # 对于非JSON响应,尝试解析为JSON(某些API返回text/plain但内容是JSON) - if not response.text: - return { - "status": "success", - "data": "", - "status_code": response.status_code, - } - - response_preview = response.text[:100] + "..." if len(response.text) > 100 else response.text - logger.info(f"文本响应预览: {response_preview}") - - # 尝试解析为JSON - text = response.text.strip() - if text and (text.startswith('{') or text.startswith('[')): - try: - json_response = json.loads(text) - logger.info("文本响应成功解析为JSON") - return json_response - except (json.JSONDecodeError, ValueError) as e: - logger.debug(f"文本响应JSON解析失败: {e}") - - # 返回原始文本 - return { - "status": "success", - "data": response.text, - "status_code": response.status_code, - } - - -# 兼容性函数 - 保持向后兼容 -async def call_api( - api_config: Dict[str, Any], - request_data: Optional[Dict[str, Any]] = None, - need_auth: bool = True, - timeout: int = 30, - verify: bool = True, - save_responses: bool = True, - save_dir: str = "lzwcai_mcp_dyntoolapi_log_call_api", -) -> Dict[str, Any]: - """ - 通用API调用方法(兼容性函数) - - Args: - api_config: API配置信息 - request_data: 请求数据 - need_auth: 是否需要认证 - timeout: 请求超时时间(秒) - verify: 是否验证SSL证书 - save_responses: 是否保存响应到本地JSON文件 - save_dir: 响应保存目录 - - Returns: - API响应结果 - """ - client = ApiClient(timeout=timeout, verify=verify, save_responses=save_responses, save_dir=save_dir) - return await client.call_api(api_config, request_data, need_auth) - - -def get_env_user_id() -> Tuple[bool, Optional[str]]: - """获取环境变量用户ID(兼容性函数)""" - return UserManager.get_user_id_from_env() - - -def extract_user_id( - request_data: Dict[str, Any], is_grouped_format: bool -) -> Optional[str]: - """提取用户ID(兼容性函数)""" - return UserManager.extract_user_id_from_request(request_data, is_grouped_format) - - -def process_auth_headers( - headers: dict, - request_data: dict, - auth_token: dict = None, -) -> dict: - """处理认证头(兼容性函数)""" - return HeaderProcessor.process_auth_headers(headers, request_data, auth_token) - - -def load_generator_api_config(file_path: str = "generator_api.json") -> Dict[str, Any]: - """加载API配置(兼容性函数)""" - return ConfigManager.load_api_config(file_path) - - -async def main(): - """主函数 - 测试示例""" - request_data = { - "header": {}, - "body": {"username": "wangpeng1", "password": "Wp147258"}, - "lzwcaiConfig": {"userId": "test_user_123"}, - } - - try: - config_path = "src/lzwcai_demp_tool_server_business_to_mcp/mcp_generator/src/user_params.json" - api_config = ConfigManager.load_api_config(config_path) - logger.info(f"正在尝试连接: {api_config.get('domainUrl')}{api_config.get('apiUrl')}") - client = ApiClient() - result = await client.call_api(api_config, request_data) - logger.info(f"请求结果: {result}") - - except Exception as e: - logger.error(f"发生错误: {e}") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/get_auth.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/get_auth.py deleted file mode 100644 index 3906a8a..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/get_auth.py +++ /dev/null @@ -1,301 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -认证数据转换器 -提供简洁的API来获取和转换认证信息 -""" - -import requests -import json -import sys -import os -from typing import Optional, Dict, Any - -# 添加项目根目录到 Python 路径 -project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) -sys.path.append(project_root) - -from ..business.get_business_api import get_business_api_details -from ..util.logger_config import get_logger - -# 配置日志 -logger = get_logger(__name__) - - - -class AuthDataTransformer: - """认证数据转换器""" - - def __init__(self, base_url: str = None): - """ - 初始化转换器 - - Args: - base_url: API基础URL,如果不提供则从环境变量 LZWCAI_CORP_MANAGER_URL 获取 - """ - if base_url is None: - base_url = os.getenv("LZWCAI_CORP_MANAGER_URL",'http://lzwcai-demp-corp-manager:8086') - if not base_url: - raise ValueError("环境变量 LZWCAI_CORP_MANAGER_URL 未配置,请设置业务平台基础URL") - self.base_url = base_url.rstrip('/') - self.session = requests.Session() - - # 设置默认请求头 - self.session.headers.update({ - 'User-Agent': 'Python-API-Client/1.0.0', - 'Accept': '*/*', - 'Connection': 'keep-alive' - }) - - def get_transformed_auth_data(self, user_id: str, business_system_id: str) -> Optional[Dict[Any, Any]]: - """ - 获取并转换认证数据 - - Args: - user_id: 用户ID - business_system_id: 业务系统ID - - Returns: - 转换后的认证数据JSON,失败时返回None - """ - try: - # 1. 获取原始认证信息 - raw_data = self._get_raw_auth_info(user_id, business_system_id) - if not raw_data: - return None - - # 2. 处理bizSysConfig并获取API详情 - self._process_biz_sys_config(raw_data) - - # 3. 转换数据结构 - transformed_data = self._transform_data_structure(raw_data) - - return transformed_data - - except Exception as e: - logger.error(f"获取转换认证数据时发生错误: {e}") - return None - - def _get_raw_auth_info(self, user_id: str, business_system_id: str) -> Optional[Dict[Any, Any]]: - """ - 获取原始认证信息 - - Args: - user_id: 用户ID - business_system_id: 业务系统ID - - Returns: - 原始API响应数据,失败时返回None - """ - # url = f"{self.base_url}/system/mcpServer/auth/info/{user_id}/{business_system_id}" - url = f"{self.base_url}/system/mcpServer/auth/info/{user_id}/{business_system_id}" - - try: - response = self.session.get(url, timeout=30) - - if response.status_code == 200: - try: - return response.json() - except json.JSONDecodeError: - logger.error(f"响应不是有效的JSON格式: {response.text}") - return None - else: - logger.error(f"请求失败,状态码: {response.status_code}, 响应: {response.text}") - return None - - except requests.exceptions.RequestException as e: - logger.error(f"请求异常: {e}") - return None - - def _process_biz_sys_config(self, response_data: Dict[Any, Any]) -> None: - """ - 处理bizSysConfig,解析loginApiId并获取API详情 - - Args: - response_data: API响应数据 - """ - try: - data = response_data.get("data", {}) - if not data: - return - - biz_sys_config_str = data.get("bizSysConfig") - if not biz_sys_config_str: - return - - try: - biz_sys_config = json.loads(biz_sys_config_str) - login_api_id = biz_sys_config.get("loginApiId") - logger.info(f"登录APIID: {login_api_id}") - logger.debug(f"获取API详情...", get_business_api_details) - if login_api_id and get_business_api_details: - try: - api_details = get_business_api_details([int(login_api_id)]) - logger.debug(f"API详情: {api_details}") - if api_details and len(api_details) > 0: - data["apiItemDetail"] = api_details[0] - except Exception as e: - logger.error(f"获取API详情时发生错误: {e}") - - except json.JSONDecodeError as e: - logger.error(f"解析bizSysConfig JSON失败: {e}") - - except Exception as e: - logger.error(f"处理bizSysConfig时发生错误: {e}") - - def _transform_data_structure(self, result: Dict[Any, Any]) -> Optional[Dict[Any, Any]]: - """ - 将原始数据结构转换为新的数据结构 - - Args: - result: 原始数据 - - Returns: - 转换后的新数据结构,失败时返回None - """ - try: - data = result.get("data", {}) - if not data: - return None - - # 解析userAuthConfig - user_auth_config = self._safe_json_parse(data.get("userAuthConfig", "{}")) - - # 解析bizSysConfig - biz_sys_config = self._safe_json_parse(data.get("bizSysConfig", "{}")) - - # 获取apiItemDetail - api_item_detail = data.get("apiItemDetail", {}) - - # 构建新的数据结构 - new_data = { - "authType": user_auth_config.get("authType"), - "name": biz_sys_config.get("name"), - "apiKey": user_auth_config.get("apiKey"), - "apiVO": self._build_api_vo(user_auth_config, biz_sys_config, api_item_detail) - } - - return { - "code": result.get("code", 200), - "msg": result.get("msg", "成功"), - "data": new_data - } - - except Exception as e: - logger.error(f"转换数据结构时发生错误: {e}") - return None - - def _safe_json_parse(self, json_str: str) -> Dict[Any, Any]: - """ - 安全解析JSON字符串 - - Args: - json_str: JSON字符串 - - Returns: - 解析后的字典,失败时返回空字典 - """ - try: - return json.loads(json_str) if json_str else {} - except json.JSONDecodeError: - return {} - - def _build_api_vo(self, user_auth_config: Dict[Any, Any], biz_sys_config: Dict[Any, Any], api_item_detail: Dict[Any, Any]) -> Optional[Dict[Any, Any]]: - """ - 构建apiVO对象 - - Args: - user_auth_config: 用户认证配置 - biz_sys_config: 业务系统配置 - api_item_detail: API详情 - - Returns: - apiVO对象 - """ - if not biz_sys_config: - return None - - # 获取dynamicValues和paramMappings - dynamic_values = user_auth_config.get("dynamicValues", {}) - account_config = biz_sys_config.get("accountConfig", {}) - param_mappings = account_config.get("paramMappings", []) - - # 预处理accountConfig - 构建为对象然后转换为JSON字符串 - processed_account_config = {} - if param_mappings: - processed_account_config = {"parametersBody": []} - - for param_mapping in param_mappings: - param_name = param_mapping.get("paramName") - if param_name: - param_value = dynamic_values.get(param_name, param_mapping.get("defaultValue", "")) - - processed_account_config["parametersBody"].append({ - "paramName": param_name, - "defaultValue": param_value, - "requestType": param_mapping.get("requestType", "form") - }) - - # 构建tcapabilityApiVO - tcapability_api_vo = self._build_tcapability_api_vo(api_item_detail) - - return { - "accountConfig": json.dumps(processed_account_config, ensure_ascii=False), # 转换为JSON字符串 - "tokenPath": biz_sys_config.get("tokenPath"), - "tcapabilityApiVO": tcapability_api_vo # 放在apiVO里面 - } - - def _build_tcapability_api_vo(self, api_item_detail: Dict[Any, Any]) -> Optional[Dict[Any, Any]]: - """ - 构建tcapabilityApiVO对象 - - Args: - api_item_detail: API详情 - - Returns: - tcapabilityApiVO对象 - """ - if not api_item_detail: - return None - - # 复制API详情并重命名parameters为apiParameterList - tcapability_api_vo = api_item_detail.copy() - if "parameters" in tcapability_api_vo: - tcapability_api_vo["apiParameterList"] = tcapability_api_vo.pop("parameters") - - return tcapability_api_vo - - -# 便捷函数 -def get_auth_data(user_id: str, business_system_id: str, base_url: str = None) -> Optional[Dict[Any, Any]]: - """ - 便捷函数:获取转换后的认证数据 - - Args: - user_id: 用户ID - business_system_id: 业务系统ID - base_url: API基础URL,如果不提供则从环境变量 LZWCAI_CORP_MANAGER_URL 获取 - - Returns: - 转换后的认证数据JSON,失败时返回None - - Example: - >>> auth_data = get_auth_data("447", "1952255539442741249") - >>> if auth_data: - ... print(f"认证类型: {auth_data['data']['authType']}") - """ - transformer = AuthDataTransformer(base_url) - return transformer.get_transformed_auth_data(user_id, business_system_id) - - -if __name__ == "__main__": - # 测试代码 - from ..util.logger_config import setup_logging - import logging - setup_logging(log_level=logging.INFO) - result = get_auth_data("447", "1957354824118095874") - if result: - logger.info("=== 转换后的认证数据 ===") - logger.info(json.dumps(result, indent=2, ensure_ascii=False)) - else: - logger.error("获取认证数据失败") diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/plugin_base.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/plugin_base.py deleted file mode 100644 index bea30c0..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/plugin_base.py +++ /dev/null @@ -1,22 +0,0 @@ -from abc import ABC, abstractmethod - - -class ToolPlugin(ABC): - """ - 工具插件基类,所有工具插件需继承并实现相关方法 - """ - - @abstractmethod - def register(self, server): - """注册插件到 Server""" - pass - - @abstractmethod - def unregister(self, server): - """从 Server 注销插件""" - pass - - @abstractmethod - def refresh(self, config): - """根据新配置刷新插件""" - pass diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py deleted file mode 100644 index 3a98e01..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py +++ /dev/null @@ -1,820 +0,0 @@ -""" -MCP服务器创建和管理模块 - -这是lzwcai-mcp-dyntoolapi项目的核心模块,负责: -1. 创建和配置MCP服务器 -2. 动态加载业务API配置 -3. 注册API工具插件 -4. 处理工具调用请求 -5. 支持配置热加载 -6. 支持多租户场景(内存模式) - -主要功能: -- 从业务平台获取API配置 -- 将API配置转换为MCP工具 -- 处理认证和参数验证 -- 支持多种传输方式(stdio、SSE) -- 支持两种配置模式:文件模式和内存模式 - -配置模式说明: -1. 内存模式(configMode=memory,默认): - - 根据businessUuid创建变量business{businessUuid}存储配置 - - 配置存储在内存中,不写入本地文件 - - 支持多个租户共享同一个包实例 - - 适用于多租户SaaS场景 - -2. 文件模式(configMode=file): - - 从业务平台获取配置后保存到本地api_config.json文件 - - 支持配置文件变更热加载 - - 适用于单租户场景 - -环境变量: -- configMode: 配置模式(file/memory),默认为memory -- businessUuid: 业务UUID(内存模式必需) -- bizSysApiIds: API ID列表 -- ENABLE_CONFIG_WATCH: 是否启用配置热加载(仅文件模式) - -作者: lzwcai -版本: 1.1.0 -""" - -import anyio -import mcp.types as types -from mcp.server.lowlevel import Server -import json -import sys -import os -import threading -import time -import re -import uuid - -# 导入核心模块 -from .core.api_base import ApiBase -import mcp.types as types -from .core.api_auth_service import AuthService -from .core.plugin_base import ToolPlugin - -# 导入业务工具模块 -from .business.business_util import ( - fill_default_values_by_schema, # 参数默认值填充 - check_required_arguments, # 必填参数检查 -) -from .business.get_business_api import get_business_api_config # 业务API配置获取 - -# 导入工具模块 -from .util.logger_config import get_logger, setup_logging - -# 获取日志器实例 -logger = get_logger(__name__) - -# ==================== 多租户配置存储 ==================== -# 用于存储多个租户的配置(内存模式) -# key格式: business{businessUuid}, value: 配置字典 -business_configs = {} - - -def load_api_configs(): - """ - 加载API配置的核心函数 - - 支持两种配置模式: - - 模式一 - 内存模式(configMode=memory,默认): - 1. 根据环境变量businessUuid创建变量名:business{businessUuid} - 2. 从业务平台获取配置后存储在内存中(business_configs字典) - 3. 如果内存中已有配置则直接使用,否则从业务平台获取 - 4. 不写入本地文件,支持多租户场景 - - 模式二 - 文件模式(configMode=file): - 1. 从业务平台动态获取最新配置(通过get_business_api_config) - 2. 如果网络获取失败,则从本地api_config.json文件加载备份配置 - 3. 成功获取后保存到本地文件作为备份 - - 环境变量: - - configMode: 配置模式,"file"(文件模式)或 "memory"(内存模式),默认为memory - - businessUuid: 业务UUID,仅在内存模式下使用,用于区分不同租户 - - bizSysApiIds: 指定要加载的API ID列表 - - 返回: - dict: 包含完整API配置的字典,格式如下: - { - "packageName": "服务包名", - "version": "版本号", - "description": "服务描述", - "apiConfig": [API配置列表] - } - - 异常处理: - - 网络获取失败时自动降级(文件模式降级到本地文件,内存模式报错) - - 详细记录所有错误信息用于调试 - """ - global business_configs - - # 获取配置模式(默认为内存模式) - config_mode = os.getenv('configMode', 'memory').lower() - logger.info(f"配置模式: {config_mode}") - - # 获取当前模块所在目录,用于定位配置文件 - current_dir = os.path.dirname(os.path.abspath(__file__)) - config_path = os.path.join(current_dir, "api_config.json") - - # ==================== 模式一:内存模式(多租户支持,默认) ==================== - if config_mode == 'memory': - logger.info("使用内存模式加载配置(多租户支持)") - - # 获取业务UUID,如果不存在则自动生成一个 - business_uuid = os.getenv('businessUuid') - if not business_uuid: - # 生成随机UUID(使用uuid4,完全随机) - business_uuid = str(uuid.uuid4()) - logger.warning(f"环境变量businessUuid未设置,已自动生成随机UUID: {business_uuid}") - # 可选:将生成的UUID设置回环境变量,供后续使用 - os.environ['businessUuid'] = business_uuid - else: - logger.info(f"使用环境变量提供的businessUuid: {business_uuid}") - - # 构建配置变量名 - config_key = f"business{business_uuid}" - logger.info(f"租户配置变量名: {config_key}") - - # 构建租户专属的配置文件路径 - memory_config_path = os.path.join(current_dir, f"api_config_{business_uuid}.json") - - # 本地文件不存在或加载失败,从业务平台获取 - logger.info(f"准备从业务平台获取租户 {business_uuid} 的最新配置(强制刷新)...") - - try: - # 从环境变量获取API ID列表 - api_ids = [] - biz_sys_api_ids = os.getenv('bizSysApiIds') - logger.debug(f"已获取环境变量bizSysApiIds: {biz_sys_api_ids}") - - if biz_sys_api_ids: - try: - ids_str = biz_sys_api_ids.strip('[]') - api_ids = [] - for id_part in ids_str.split(','): - id_clean = id_part.strip().strip('"\'') - if id_clean: - try: - api_id = int(id_clean) - api_ids.append(api_id) - except ValueError: - logger.warning(f"无法将 '{id_clean}' 转换为整数,跳过此项") - continue - logger.info(f"从环境变量bizSysApiIds获取到API IDs: {api_ids}") - except (ValueError, AttributeError) as e: - logger.warning(f"解析环境变量bizSysApiIds失败,使用默认值: {str(e)}") - else: - logger.info("未找到环境变量bizSysApiIds,使用默认API IDs") - - # 从业务平台获取配置 - logger.info(f"调用get_business_api_config获取配置,API IDs: {api_ids}") - config = get_business_api_config(api_ids) - logger.info(f"成功获取业务API配置,包含 {len(config.get('apiConfig', []))} 个API配置") - - # 存储到内存中 - business_configs[config_key] = config - logger.info(f"配置已存储到内存变量: {config_key}") - logger.info(f"当前内存中共有 {len(business_configs)} 个租户配置") - - # 保存到本地文件作为备份 - try: - logger.info(f"保存配置到本地文件: {memory_config_path}") - with open(memory_config_path, "w", encoding="utf-8") as f: - json.dump(config, f, ensure_ascii=False, indent=2) - logger.info("配置文件保存成功") - except Exception as save_error: - logger.warning(f"保存配置到本地文件失败(不影响运行): {str(save_error)}") - - return config - - except Exception as e: - logger.error(f"获取业务API配置失败: {str(e)}") - - # 网络获取失败,尝试降级使用本地缓存 - if os.path.exists(memory_config_path): - try: - logger.info(f"网络获取失败,尝试使用本地缓存文件: {memory_config_path}") - with open(memory_config_path, "r", encoding="utf-8") as f: - config = json.load(f) - logger.info(f"成功加载本地缓存配置,包含 {len(config.get('apiConfig', []))} 个API配置") - - # 存储到内存中 - business_configs[config_key] = config - return config - except Exception as cache_error: - logger.error(f"加载本地缓存也失败了: {str(cache_error)}") - - error_msg = f"无法获取租户 {business_uuid} 的配置(网络和缓存均不可用): {str(e)}" - raise Exception(error_msg) - - # ==================== 模式二:文件模式(单租户) ==================== - elif config_mode == 'file': - logger.info("使用文件模式加载配置(单租户)") - - try: - # 从环境变量获取API ID列表 - # 支持格式: "[1932682081958830081,1932682082285985793]" 或 "1932682081958830081,1932682082285985793" - api_ids = [] # 默认空列表 - - # 尝试从环境变量获取bizSysApiIds - biz_sys_api_ids = os.getenv('bizSysApiIds') - logger.debug(f"已获取环境变量bizSysApiIds: {biz_sys_api_ids}") - - if biz_sys_api_ids: - try: - # 解析环境变量中的字符串,支持多种格式 - # 格式1: [1932682081958830081,1932682082285985793] - # 格式2: 1932682081958830081,1932682082285985793 - # 格式3: ["1932682081958830081","1932682082285985793"] - # 格式4: "1932682081958830081","1932682082285985793" - ids_str = biz_sys_api_ids.strip('[]') # 移除方括号 - - # 分割并处理每个ID,自动转换字符串数字为整数 - api_ids = [] - for id_part in ids_str.split(','): - id_clean = id_part.strip().strip('"\'') # 移除空格和引号 - if id_clean: # 确保不是空字符串 - try: - # 尝试转换为整数,支持字符串数字自动转换 - api_id = int(id_clean) - api_ids.append(api_id) - except ValueError: - logger.warning(f"无法将 '{id_clean}' 转换为整数,跳过此项") - continue - - logger.info(f"从环境变量bizSysApiIds获取到API IDs: {api_ids}") - except (ValueError, AttributeError) as e: - logger.warning(f"解析环境变量bizSysApiIds失败,使用默认值: {str(e)}") - logger.warning(f"环境变量值: {biz_sys_api_ids}") - else: - logger.info("未找到环境变量bizSysApiIds,使用默认API IDs") - - logger.info(f"调用get_business_api_config获取配置,API IDs: {api_ids}") - - # 从业务平台获取最新配置 - config = get_business_api_config(api_ids) - logger.info(f"成功获取业务API配置,包含 {len(config.get('apiConfig', []))} 个API配置") - - # 将获取的配置保存到本地文件作为备份 - logger.info(f"保存配置到文件: {config_path}") - with open(config_path, "w", encoding="utf-8") as f: - json.dump(config, f, ensure_ascii=False, indent=2) - logger.info("配置文件保存成功") - - return config - - except Exception as e: - logger.error(f"获取业务API配置失败: {str(e)}") - logger.info("尝试从本地配置文件加载...") - - # 降级处理:从本地文件加载配置 - try: - logger.debug(f"加载本地API配置文件: {config_path}") - with open(config_path, "r", encoding="utf-8") as f: - config = json.load(f) - logger.info(f"成功加载本地API配置文件,包含 {len(config.get('apiConfig', []))} 个API配置") - return config - except FileNotFoundError: - logger.error(f"本地API配置文件也未找到: {config_path}") - raise Exception(f"无法获取业务API配置且本地配置文件不存在: {str(e)}") - except json.JSONDecodeError as json_e: - logger.error(f"本地API配置文件JSON格式错误: {str(json_e)}") - raise Exception(f"无法获取业务API配置且本地配置文件格式错误: {str(e)}") - except Exception as file_e: - logger.error(f"加载本地API配置文件失败: {str(file_e)}") - raise Exception(f"无法获取业务API配置且加载本地配置文件失败: {str(e)}") - - # ==================== 无效配置模式 ==================== - else: - error_msg = f"无效的配置模式: {config_mode},请使用 'file' 或 'memory'" - logger.error(error_msg) - raise Exception(error_msg) - - -# ==================== MCP服务器初始化 ==================== - -logger.info("开始初始化 MCP 服务器") - -# 加载API配置(从业务平台或本地文件) -api_configs = load_api_configs() - -# 设置MCP服务器的默认配置值 -default_name = "lzwcai-mcp-dyntoolapi" # 默认服务器名称 -default_version = "1.0.0" # 默认版本号 -default_instructions = "动态业务API工具服务器" # 默认服务器描述 - -# 从加载的配置中获取服务器信息,如果不存在则使用默认值 -name = api_configs.get("packageName", default_name) -version = api_configs.get("version", default_version) -instructions = api_configs.get("description", default_instructions) - -logger.info(f"服务器配置 - 名称: {name}, 版本: {version}") -logger.debug(f"服务器说明: {instructions}") - -# 创建MCP服务器实例 -# 这个Server实例将处理所有的MCP协议通信 -app = Server( - name=name, # 服务器名称,用于客户端识别 - version=version, # 服务器版本,用于兼容性检查 - instructions=instructions, # 服务器描述,告诉客户端这个服务器的功能 -) - -# 初始化API基础服务 -# ApiBase负责管理所有的API配置和调用逻辑 -logger.debug("初始化 API 基础服务") -api_base = ApiBase(api_configs.get("apiConfig")) -logger.info(f"API 基础服务初始化完成,共 {api_base.config_count} 个API配置") - - -class ApiToolPlugin(ToolPlugin): - """ - API工具插件实现类 - - 这个类负责将业务API配置转换为MCP工具,并处理工具调用请求。 - 它继承自ToolPlugin基类,实现了插件的标准接口。 - - 主要功能: - 1. 将API配置转换为MCP工具定义 - 2. 处理工具列表请求(list_tools) - 3. 处理工具调用请求(call_tool) - 4. 支持插件的注册、注销和刷新 - - 属性: - api_base: ApiBase实例,管理所有API配置 - tools: 工具列表缓存(当前未使用) - """ - - def __init__(self, api_base): - """ - 初始化API工具插件 - - 参数: - api_base: ApiBase实例,包含所有API配置信息 - """ - self.api_base = api_base - self.tools = [] # 工具列表缓存(预留) - logger.debug(f"初始化 API 工具插件,API配置数量: {api_base.config_count}") - - def register(self, server): - """ - 向MCP服务器注册插件 - - 这个方法会向服务器注册两个处理器: - 1. list_tools: 返回可用工具列表 - 2. call_tool: 处理工具调用请求 - - 参数: - server: MCP服务器实例 - """ - @server.list_tools() - async def list_tools() -> list[types.Tool]: - """ - 处理工具列表请求 - - 当MCP客户端请求可用工具列表时,这个函数会被调用。 - 它会遍历所有API配置,为每个API创建一个MCP工具定义。 - - 返回: - list[types.Tool]: MCP工具定义列表 - """ - logger.debug("处理工具列表请求") - tools = [] - tools_configs = self.api_base.api_configs_map - - # 遍历所有API配置,创建工具定义 - for tool_config in tools_configs: - tool_name = tool_config["interfaceName"] # 工具名称(拼音格式) - logger.debug(f"注册工具: {tool_name}") - - # 创建MCP工具定义 - tools.append( - types.Tool( - name=tool_name, - description=tool_config["schema_description"], - inputSchema=tool_config["schema"], - ) - ) - - logger.info(f"返回工具列表,共 {len(tools)} 个工具") - return tools - - @server.call_tool() - async def fetch_tool( - name: str, arguments: dict - ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: - """ - 处理工具调用请求 - - 当MCP客户端调用某个工具时,这个函数会被调用。 - 它负责: - 1. 查找对应的API配置 - 2. 验证和处理输入参数 - 3. 调用实际的API接口 - 4. 返回格式化的结果 - - 参数: - name: 工具名称(对应API的interfaceName) - arguments: 工具调用参数(JSON对象) - - 返回: - list[types.TextContent]: 包含API调用结果的文本内容列表 - """ - logger.info(f"调用工具: {name}") - logger.debug(f"工具参数: {arguments}") - - # 查找对应的工具配置 - tool_config = None - for config in self.api_base.api_configs_map: - if config["interfaceName"] == name: - tool_config = config - break - - # 检查工具是否存在 - if tool_config is None: - logger.error(f"未找到工具: {name}") - return [types.TextContent(type="text", text=f"未找到工具: {name}")] - - logger.debug(f"找到工具配置: {tool_config.get('apiUrl', 'N/A')}") - - # ==================== 参数处理和验证 ==================== - logger.debug("开始参数处理和验证") - - # 保存原始参数用于调试 - original_args = arguments.copy() - - # 使用schema中的默认值补全缺失的参数 - arguments = fill_default_values_by_schema( - tool_config.get("schema", {}), arguments - ) - logger.debug(f"参数默认值补全完成,原始参数: {original_args}, 补全后: {arguments}") - - # 检查必填参数是否都已提供 - missing = check_required_arguments(tool_config.get("schema", {}), arguments) - if missing: - missing_str = ", ".join(missing) - logger.warning(f"缺少必填参数: {missing_str}") - return [ - types.TextContent( - type="text", - text=f"请补充以下必填参数:{missing_str}。填写后再试一次哦~ 如果有疑问请联系管理员。", - ) - ] - - # ==================== API接口调用 ==================== - logger.info(f"开始调用API接口: {tool_config.get('apiUrl', 'N/A')}") - try: - # 通过ApiBase调用实际的API接口 - result = await self.api_base.call_interface(tool_config, arguments) - logger.info("API调用成功") - logger.debug(f"API返回结果: {result}") - - # 将结果转换为JSON格式返回给客户端 - result_json = json.dumps(result, ensure_ascii=False, indent=2) - return [types.TextContent(type="text", text=result_json)] - - except Exception as e: - # 处理API调用异常 - logger.error(f"API调用失败: {str(e)}") - logger.debug("API调用异常详情:", exc_info=True) - error_msg = f"API调用失败: {str(e)}" - return [types.TextContent(type="text", text=error_msg)] - - def unregister(self, server): - """ - 从MCP服务器注销插件 - - 目前MCP Server不支持动态注销功能,这个方法预留给未来使用。 - - 参数: - server: MCP服务器实例(当前未使用) - """ - # 目前 Server 不支持动态注销,预留接口 - logger.debug("插件注销请求(当前不支持动态注销)") - pass - - def refresh(self, config): - """ - 刷新插件配置 - - 当API配置发生变化时(如热加载),这个方法会被调用来更新插件的配置。 - - 参数: - config: 新的配置字典,包含apiConfig字段 - """ - logger.info("刷新API工具插件配置") - # 重新创建ApiBase实例以使用新配置 - self.api_base = ApiBase(config.get("apiConfig")) - logger.info(f"插件配置刷新完成,共 {self.api_base.config_count} 个API配置") - - -# ==================== 插件注册 ==================== - -# 创建并注册API工具插件 -logger.info("注册API工具插件") -api_tool_plugin = ApiToolPlugin(api_base) # 创建插件实例 -api_tool_plugin.register(app) # 向MCP服务器注册插件 -logger.info("API工具插件注册完成") - - - -# ==================== MCP服务器启动函数 ==================== - -def main(port: int, transport: str) -> int: - """ - MCP服务器主启动函数 - - 根据指定的传输方式启动MCP服务器。支持两种传输方式: - 1. stdio: 标准输入输出传输(默认,用于命令行工具集成) - 2. sse: Server-Sent Events传输(用于Web集成) - - 参数: - port: 服务器端口号(仅在SSE模式下使用) - transport: 传输方式,"stdio" 或 "sse" - - 返回: - int: 退出状态码,0表示成功 - """ - - if transport == "sse": - # ==================== SSE传输模式 ==================== - logger.info(f"启动SSE传输模式,端口: {port}") - - # 导入SSE相关模块 - from mcp.server.sse import SseServerTransport - from starlette.applications import Starlette - from starlette.responses import Response - from starlette.routing import Mount, Route - - # 创建SSE传输实例 - sse = SseServerTransport("/messages/") - - async def handle_sse(request): - """ - 处理SSE连接请求 - - 这个函数处理来自Web客户端的SSE连接请求, - 建立双向通信流并运行MCP服务器。 - """ - async with sse.connect_sse( - request.scope, request.receive, request._send - ) as streams: - # 运行MCP服务器,使用SSE流进行通信 - await app.run( - streams[0], streams[1], app.create_initialization_options() - ) - return Response() - - # 创建Starlette Web应用 - starlette_app = Starlette( - debug=True, # 开启调试模式 - routes=[ - # SSE连接端点 - Route("/sse", endpoint=handle_sse, methods=["GET"]), - # 消息处理端点 - Mount("/messages/", app=sse.handle_post_message), - ], - ) - - # 使用uvicorn启动Web服务器 - import uvicorn - logger.info(f"启动Web服务器,监听 0.0.0.0:{port}") - uvicorn.run(starlette_app, host="0.0.0.0", port=port) - - else: - # ==================== STDIO传输模式 ==================== - logger.info("启动STDIO传输模式") - - # 导入stdio传输模块 - from mcp.server.stdio import stdio_server - - async def arun(): - """ - 异步运行MCP服务器 - - 使用标准输入输出流与客户端通信, - 这是MCP协议的标准传输方式。 - """ - async with stdio_server() as streams: - # 运行MCP服务器,使用stdio流进行通信 - await app.run( - streams[0], streams[1], app.create_initialization_options() - ) - - # 启动异步事件循环 - anyio.run(arun) - - return 0 - - -# ==================== 工具函数 ==================== - -def save_to_json_file(data, file_path="output/test_data.json"): - """ - 将输入数据保存为JSON文件 - - 这是一个通用的数据保存工具函数,主要用于调试和数据持久化。 - - 参数: - data: 要保存的数据(字典、列表或其他可JSON序列化的对象) - file_path: 要保存的文件路径,默认保存到output目录 - - 返回: - bool: 操作成功返回True,失败返回False - - 特性: - - 自动创建目录(如果不存在) - - 使用UTF-8编码确保中文正确显示 - - 格式化输出(缩进2个空格) - """ - try: - # 确保目录存在 - os.makedirs(os.path.dirname(file_path), exist_ok=True) - - # 保存数据到JSON文件 - with open(file_path, "w", encoding="utf-8") as f: - json.dump(data, f, ensure_ascii=False, indent=2) - logger.info(f"数据已成功保存到 {file_path}") - return True - except Exception as e: - logger.error(f"保存JSON文件时出错: {str(e)}") - return False - - -def refresh_api_configs(): - """ - 刷新API配置和工具注册(热加载功能) - - 这个函数实现了配置的热加载功能,支持两种模式: - - 文件模式:当检测到配置文件变化时会被调用 - - 内存模式:强制重新从业务平台获取配置并更新内存和本地文件 - - 全局变量更新: - - api_configs: 重新加载的API配置 - - name, version, instructions: 服务器基本信息 - - api_base: 重新创建的API基础服务实例 - - api_tool_plugin: 刷新插件配置 - - business_configs: 内存模式下更新租户配置 - - 注意: - 这个函数修改全局变量,在多线程环境中需要注意线程安全。 - """ - global api_configs, name, version, instructions, api_base, api_tool_plugin, business_configs - - logger.info("开始刷新API配置...") - - # 获取配置模式 - config_mode = os.getenv('configMode', 'memory').lower() - - # 内存模式下需要清除当前租户的缓存配置和本地文件,强制重新获取 - if config_mode == 'memory': - business_uuid = os.getenv('businessUuid') - if business_uuid: - config_key = f"business{business_uuid}" - if config_key in business_configs: - logger.info(f"清除租户 {business_uuid} 的缓存配置") - del business_configs[config_key] - - # 删除本地配置文件,强制从业务平台重新获取 - current_dir = os.path.dirname(os.path.abspath(__file__)) - memory_config_path = os.path.join(current_dir, f"api_config_{business_uuid}.json") - if os.path.exists(memory_config_path): - try: - os.remove(memory_config_path) - logger.info(f"已删除本地配置文件: {memory_config_path}") - except Exception as e: - logger.warning(f"删除本地配置文件失败: {str(e)}") - - # 重新加载API配置 - api_configs = load_api_configs() - - # 更新服务器基本信息 - name = api_configs.get("packageName", default_name) - version = api_configs.get("version", default_version) - instructions = api_configs.get("description", default_instructions) - - # 重新创建API基础服务 - api_base = ApiBase(api_configs.get("apiConfig")) - - # 刷新插件配置 - api_tool_plugin.refresh(api_configs) - - logger.info(f"API配置已热加载并刷新(模式:{config_mode})") - - -# ==================== 配置热加载功能 ==================== - -def watch_config_file(interval=2): - """ - 配置文件变更监控函数(仅文件模式) - - 这个函数在后台线程中运行,定期检查配置文件的修改时间。 - 当检测到文件变更时,自动触发配置热加载。 - - 注意: - - 仅在文件模式(configMode=file)下有效 - - 内存模式下此函数会直接返回,不进行监控 - - 参数: - interval: 检查间隔时间(秒),默认2秒 - - 特性: - - 轮询方式检测文件变更 - - 异常安全,不会因为单次错误而停止监控 - - 在守护线程中运行,不会阻止程序退出 - - 注意: - 这个函数会无限循环运行,直到程序退出。 - """ - # 检查配置模式,内存模式下不需要监控文件 - config_mode = os.getenv('configMode', 'memory').lower() - if config_mode != 'file': - logger.info(f"当前为 {config_mode} 模式,无需监控配置文件") - return - - current_dir = os.path.dirname(os.path.abspath(__file__)) - config_path = os.path.join(current_dir, "api_config.json") - - try: - # 获取初始修改时间 - last_mtime = os.path.getmtime(config_path) - logger.debug(f"开始监控配置文件: {config_path}") - except OSError: - logger.warning(f"配置文件不存在,跳过热加载监控: {config_path}") - return - - # 无限循环监控文件变更 - while True: - try: - # 获取当前修改时间 - mtime = os.path.getmtime(config_path) - - # 检查是否有变更 - if mtime != last_mtime: - logger.info("检测到api_config.json变更,自动热加载...") - refresh_api_configs() - last_mtime = mtime - - except Exception as e: - logger.error(f"配置热加载异常: {e}") - - # 等待下次检查 - time.sleep(interval) - - -# ==================== 主启动函数 ==================== - -def run_main(): - """ - 程序主启动函数 - - 这是整个MCP服务器的启动入口,负责: - 1. 配置日志系统(MCP模式下禁用控制台输出) - 2. 可选启动配置文件监控线程(通过环境变量ENABLE_CONFIG_WATCH控制) - 3. 启动MCP服务器 - - 配置说明: - - 使用stdio传输方式(标准MCP协议) - - 端口8000(仅在SSE模式下使用) - - 配置热加载功能默认关闭,可通过环境变量ENABLE_CONFIG_WATCH=true启用 - """ - - # 在MCP模式下,禁用控制台日志输出,避免干扰stdio通信 - # 只输出到文件,确保MCP协议通信不被日志干扰 - setup_logging(console_output=False, file_output=True) - - # 检查是否启用配置文件监控(默认关闭) - # 通过环境变量ENABLE_CONFIG_WATCH=true来启用 - enable_watch = os.getenv('ENABLE_CONFIG_WATCH', 'false').lower() in ['true', '1', 'yes'] - - if enable_watch: - # 启动配置文件监听线程(守护线程) - # 守护线程会在主程序退出时自动结束 - config_watcher = threading.Thread(target=watch_config_file, daemon=True) - config_watcher.start() - logger.info("配置文件监控线程已启动") - else: - logger.info("配置文件监控功能已禁用(如需启用,请设置环境变量ENABLE_CONFIG_WATCH=true)") - - # 启动MCP服务器 - # 使用stdio传输方式,这是MCP协议的标准传输方式 - # 如果需要Web集成,可以改为 transport="sse" - main(port=8000, transport="stdio") - - -# ==================== 程序入口 ==================== - -if __name__ == "__main__": - """ - 直接运行此模块时的入口点 - - 通常情况下,这个模块会被main.py调用, - 但也支持直接运行进行测试。 - """ - run_main() - - # 以下是测试代码,正常运行时被注释掉 - # from core.api_auth_service import test_auth_service - # asyncio.run(test_auth_service()) diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__init__.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 882a706..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/api_helper.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/api_helper.cpython-312.pyc deleted file mode 100644 index abe44b0..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/api_helper.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/logger_config.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index 3c1dd23..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/logger_config.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/nested_value.cpython-312.pyc b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/nested_value.cpython-312.pyc deleted file mode 100644 index 718f245..0000000 Binary files a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/nested_value.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/api_helper.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/api_helper.py deleted file mode 100644 index cfc36ae..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/api_helper.py +++ /dev/null @@ -1,412 +0,0 @@ -#!/usr/bin/env python3 -""" -API助手工具包 - -这个工具包集成了三个核心功能: -1. 获取接口参数信息 -2. 调用指定接口API -3. 获取用户授权Token - -使用lzwcai_mcp_dyntoolapi包中的方法,提供简洁易用的API操作接口。 - -作者: lzwcai -版本: 1.0.0 -""" - -import json -import asyncio -from typing import Dict, Any, Optional -import os -# 引入lzwcai_mcp_dyntoolapi包中的核心方法 -from lzwcai_mcp_api_converter.src.business.get_business_api import get_business_api_details -from lzwcai_mcp_api_converter.src.core.api_base import ApiBase -from lzwcai_mcp_api_converter.src.core.core_server import call_api -from lzwcai_mcp_api_converter.src.core.get_auth import get_auth_data -from lzwcai_mcp_api_converter.src.core.api_auth_service import AuthService -from lzwcai_mcp_api_converter.src.util.logger_config import get_logger - -# 配置日志 -logger = get_logger(__name__) - -# 全局AuthService实例(单例模式) -_auth_service_instance = None - - -def get_auth_service() -> AuthService: - """ - 获取全局共享的AuthService实例 - - 使用单例模式确保整个应用中只有一个AuthService实例, - 这样可以复用认证状态和连接,提高性能。 - - Returns: - AuthService: 全局共享的AuthService实例 - - 使用示例: - >>> auth_service = get_auth_service() - >>> token_result = await auth_service.authorize_request(user_id, business_system_id) - """ - global _auth_service_instance - if _auth_service_instance is None: - logger.info("创建新的AuthService实例") - _auth_service_instance = AuthService() - return _auth_service_instance - - -# ==================== 功能1: 获取接口参数信息 ==================== - -def get_api_parameters_info(api_id: int, auth_token: str = None) -> Optional[Dict[str, Any]]: - """ - 获取指定接口ID的参数信息 - - 功能说明: - - 根据接口ID获取接口的详细配置信息 - - 生成JSON Schema格式的参数定义 - - 返回包含参数列表、Schema、描述等完整信息的配置对象 - - Args: - api_id: 接口ID,例如 1957355058730684417 - auth_token: 认证token,如果不提供则使用默认token - - Returns: - Dict[str, Any]: 处理后的接口配置对象,包含: - - interfaceName: 拼音格式的接口名称 - - schema: JSON Schema格式的参数定义 - - schema_description: 完整的参数描述 - - 原始API配置的所有其他字段 - - Raises: - Exception: 当获取接口信息失败时抛出 - - 使用示例: - >>> api_info = get_api_parameters_info(1957355058730684417) - >>> print(api_info['interfaceName']) # tool_AppZhangHaoMiMaDengLu - >>> print(api_info['schema']) # JSON Schema对象 - """ - try: - logger.info(f"开始获取接口ID {api_id} 的参数信息...") - - # 步骤1: 调用业务接口获取API详情 - api_details = get_business_api_details([api_id], auth_token) - - if not api_details: - logger.error(f"未找到接口ID {api_id} 的详情") - return None - - # 获取第一个(也是唯一一个)API详情 - api_detail = api_details[0] - - # 步骤2: 使用ApiBase处理API配置,生成Schema - api_base = ApiBase([api_detail]) - - # 获取处理后的配置 - processed_configs = api_base.api_configs_map - if not processed_configs: - logger.error("API配置处理失败") - return None - - processed_config = processed_configs[0] - - logger.info(f"成功获取接口 '{processed_config.get('interfaceName', 'N/A')}' 的参数信息") - return processed_config - - except Exception as e: - logger.error(f"获取接口参数信息失败: {str(e)}") - raise - - -# ==================== 功能2: 调用指定接口API ==================== - -async def call_api_by_id(api_id: int, request_params: Dict[str, Any], auth_token: str = None) -> Optional[Dict[str, Any]]: - """ - 根据接口ID调用API - - 功能说明: - - 根据接口ID获取API配置 - - 智能处理认证(先尝试认证调用,失败后尝试无认证调用) - - 发送HTTP请求并返回响应结果 - - Args: - api_id: 接口ID,例如 1957355058730684417 - request_params: 请求参数,格式如: - { - "body": { - "username": "test_user", - "password": "test_password" - }, - "lzwcaiConfig": { - "userId": "test_user_id" - } - } - auth_token: 认证token,如果不提供则使用默认token - - Returns: - Dict[str, Any]: API调用结果 - - Raises: - Exception: 当API调用失败时抛出 - - 使用示例: - >>> params = { - ... "body": {"username": "wangpeng1", "password": "Wp147258"}, - ... "lzwcaiConfig": {"userId": "447"} - ... } - >>> result = await call_api_by_id(1957355058730684417, params) - >>> print(result['code']) # 200 - """ - try: - logger.info(f"开始调用接口ID {api_id}...") - - # 步骤1: 获取API配置 - api_details = get_business_api_details([api_id], auth_token) - - if not api_details: - logger.error(f"未找到接口ID {api_id} 的详情") - return None - - # 获取第一个(也是唯一一个)API详情 - api_detail = api_details[0] - - # 步骤2: 使用ApiBase处理API配置 - api_base = ApiBase([api_detail]) - processed_configs = api_base.api_configs_map - - if not processed_configs: - logger.error("API配置处理失败") - return None - - processed_config = processed_configs[0] - - # 步骤3: 调用API - logger.info(f"调用接口: {processed_config.get('interfaceName', 'N/A')}") - logger.info(f"接口地址: {processed_config.get('apiUrl', 'N/A')}") - logger.info(f"请求方法: {processed_config.get('method', 'N/A')}") - - # 判断是否需要认证 - need_auth = processed_config.get('authenticationRequired', 0) == 1 - logger.info(f"需要认证: {need_auth}") - - # 尝试调用API(智能认证处理) - try: - result = await call_api(processed_config, request_params, need_auth=need_auth) - except Exception as api_error: - # 如果认证失败,尝试不需要认证的方式调用 - if "认证失败" in str(api_error) or "鉴权令牌失败" in str(api_error): - logger.warning(f"认证调用失败,尝试无认证调用: {str(api_error)}") - try: - result = await call_api(processed_config, request_params, need_auth=False) - logger.info("无认证调用成功") - except Exception as no_auth_error: - logger.error(f"无认证调用也失败: {str(no_auth_error)}") - # 返回模拟的错误响应,展示调用过程 - result = { - "error": "API调用失败", - "auth_error": str(api_error), - "no_auth_error": str(no_auth_error), - "note": "这是一个模拟的错误响应,展示了API调用的完整过程" - } - else: - raise api_error - - logger.info("API调用成功") - return result - - except Exception as e: - logger.error(f"调用API失败: {str(e)}") - raise - - -# ==================== 功能3: 获取用户授权Token ==================== - -def get_auth_info(user_id: str, business_system_id: str) -> Optional[Dict[str, Any]]: - """ - 获取用户认证信息 - - 功能说明: - - 获取用户在指定业务系统中的认证配置 - - 包含认证类型、API密钥、登录接口等信息 - - Args: - user_id: 用户ID,例如 "447" - business_system_id: 业务系统ID,例如 "1957354824118095874" - - Returns: - Dict[str, Any]: 认证信息,包括: - - authType: 认证类型 - - name: 业务系统名称 - - apiKey: API密钥 - - apiVO: API配置信息 - - Raises: - Exception: 当获取认证信息失败时抛出 - - 使用示例: - >>> auth_info = get_auth_info("447", "1957354824118095874") - >>> print(auth_info['data']['authType']) # 1 - """ - try: - logger.info(f"开始获取用户 {user_id} 在业务系统 {business_system_id} 的认证信息...") - - # 调用get_auth_data获取认证数据 - auth_data = get_auth_data(user_id, business_system_id) - - if not auth_data: - logger.error("未能获取到认证数据") - return None - - logger.info("成功获取认证信息") - return auth_data - - except Exception as e: - logger.error(f"获取认证信息失败: {str(e)}") - raise - - -async def get_business_token(user_id: str, business_system_id: str,persist_token: bool = False) -> Optional[Dict[str, Any]]: - """ - 获取用户在指定业务系统的Token - - 功能说明: - - 使用全局共享的AuthService实例获取Token - - 复用认证状态,提高性能 - - Args: - user_id: 用户ID - business_system_id: 业务系统ID - - Returns: - Dict[str, Any]: Token获取结果 - - Raises: - Exception: 当获取Token失败时抛出 - - 使用示例: - >>> token_result = await get_business_token("447", "1957354824118095874") - >>> print(token_result.get('success')) # True - """ - try: - logger.info(f"开始获取用户 {user_id} 在业务系统 {business_system_id} 的Token...") - - # 使用全局共享的AuthService实例获取Token - auth_service = get_auth_service() - token_result = await auth_service.authorize_request(user_id, business_system_id,persist_token=persist_token) - - logger.info("Token获取完成") - return token_result - - except Exception as e: - logger.error(f"获取业务Token失败: {str(e)}") - raise - - -# ==================== 便捷工具函数 ==================== - -def save_result_to_file(data: Dict[str, Any], filename: str) -> None: - """ - 保存结果到JSON文件 - - Args: - data: 要保存的数据 - filename: 文件名 - """ - try: - with open(filename, 'w', encoding='utf-8') as f: - json.dump(data, f, ensure_ascii=False, indent=2) - logger.info(f"💾 数据已保存到: {filename}") - except Exception as e: - logger.error(f"保存文件失败: {str(e)}") - - -def print_api_info(api_info: Dict[str, Any]) -> None: - """ - 格式化打印API信息 - - Args: - api_info: API信息字典 - """ - logger.info("\n" + "="*60) - logger.info("📋 接口信息:") - logger.info(f"接口名称: {api_info.get('interfaceName', 'N/A')}") - logger.info(f"业务描述: {api_info.get('businessPrompts', 'N/A')}") - logger.info(f"接口地址: {api_info.get('apiUrl', 'N/A')}") - logger.info(f"请求方法: {api_info.get('method', 'N/A')}") - logger.info(f"需要认证: {'是' if api_info.get('authenticationRequired', 0) == 1 else '否'}") - logger.info("="*60) - - -def print_api_result(result: Dict[str, Any]) -> None: - """ - 格式化打印API调用结果 - - Args: - result: API调用结果 - """ - logger.info("\n" + "="*60) - logger.info("📥 API调用结果:") - if result.get('success') is not None: - status = "✅ 成功" if result.get('success') else "❌ 失败" - logger.info(f"状态: {status}") - if result.get('code') is not None: - logger.info(f"状态码: {result.get('code')}") - if result.get('msg'): - logger.info(f"消息: {result.get('msg')}") - logger.info("="*60) - - -# ==================== 便捷使用示例 ==================== - -async def demo_usage(): - """ - 使用示例演示 - - 展示如何使用工具包的三个核心功能 - """ - logger.info("🚀 API助手工具包使用示例") - - # 测试数据 - # api_id = 1957355058730684417 - user_id = "2" - business_system_id = "1922839602141347842" - # os.environ["lzwcai_mcp_dyntoolapi_auth_url"] = ( - # "http://lzwcai-demp-corp-manager:8086/system/mcpServer/bizSys/api/getByIds" - # ) - try: - # get_auth_info_data=await get_business_token(user_id, business_system_id) - # print(f"获取接口参数信息: {get_auth_info_data}") - # # 功能1: 获取接口参数信息 - # print(f"\n📋 功能1: 获取接口ID {api_id} 的参数信息") - # api_info = get_api_parameters_info(api_id) - # if api_info: - # print(f"✅ 接口名称: {api_info.get('interfaceName', 'N/A')}") - # print(f"✅ 业务描述: {api_info.get('businessPrompts', 'N/A')}") - - # # 功能2: 调用API - # print(f"\n🔧 功能2: 调用接口ID {api_id}") - # request_params = { - # "body": { - # "username": "wangpeng1", - # "password": "Wp147258" - # }, - # "lzwcaiConfig": { - # "userId": user_id - # } - # } - # api_result = await call_api_by_id(api_id, request_params) - # if api_result: - # print(f"✅ API调用成功: {api_result.get('msg', 'N/A')}") - - # # 功能3: 获取Token - # logger.info(f"\n🔑 功能3: 获取用户 {user_id} 的Token") - token_result = await get_business_token(user_id, business_system_id) - if token_result and token_result.get('success'): - logger.info(f"✅ Token获取成功: {token_result.get('msg', 'N/A')}") - - logger.info(f"\n🎉 所有功能演示完成!") - - except Exception as e: - logger.error(f"❌ 演示过程中发生错误: {str(e)}") - - -if __name__ == "__main__": - # 运行使用示例 - asyncio.run(demo_usage()) diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/logger_config.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/logger_config.py deleted file mode 100644 index a7b1b99..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/logger_config.py +++ /dev/null @@ -1,554 +0,0 @@ -""" -统一日志配置模块 - -这个模块提供了整个项目的统一日志配置和管理功能,确保所有组件使用一致的日志格式和输出方式。 - -主要功能: -1. 统一的日志格式配置 -2. 支持控制台和文件双重输出 -3. 日志文件轮转管理 -4. MCP模式下的特殊处理(禁用控制台输出) -5. 便捷的日志器获取接口 -6. 丰富的日志工具函数 - -设计特点: -- 单例模式确保配置一致性 -- 支持动态配置调整 -- 异常安全的编码处理 -- 详细的调试信息记录 - -作者: lzwcai -版本: 1.0.0 -""" - -import logging -import logging.handlers -import sys -from datetime import datetime -from pathlib import Path -from typing import Optional - - -class LoggerConfig: - """ - 日志配置管理器 - - 这个类采用单例模式管理整个项目的日志配置。 - 它提供了统一的日志格式、文件轮转、编码处理等功能。 - - 主要特性: - - 单例模式:确保全局日志配置一致 - - 双重输出:同时支持控制台和文件输出 - - 文件轮转:自动管理日志文件大小和数量 - - 编码安全:正确处理中文字符 - - MCP兼容:支持MCP模式下的特殊需求 - - 配置参数: - DEFAULT_LOG_LEVEL: 默认日志级别(INFO) - DEFAULT_LOG_FORMAT: 日志格式模板 - DEFAULT_DATE_FORMAT: 时间格式 - LOG_FILE_NAME: 日志文件名 - MAX_LOG_SIZE: 单个日志文件最大大小(10MB) - BACKUP_COUNT: 保留的备份文件数量(5个) - """ - - # ==================== 默认配置常量 ==================== - - # 默认日志级别:INFO级别平衡了信息量和性能 - DEFAULT_LOG_LEVEL = logging.INFO - - # 默认日志格式:包含时间、模块名、级别、文件位置、消息内容 - DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" - - # 默认时间格式:标准的年-月-日 时:分:秒格式 - DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - - # ==================== 日志文件配置 ==================== - - # 日志文件名:使用项目名称作为前缀 - LOG_FILE_NAME = "lzwcai_mcp_api_converter.log" - - # 单个日志文件最大大小:10MB - MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB - - # 保留的备份文件数量:5个(总共约50MB的日志存储) - BACKUP_COUNT = 5 - - # ==================== 单例模式状态 ==================== - - # 初始化标志:确保只初始化一次 - _initialized = False - - # 日志文件路径:记录当前使用的日志文件路径 - _log_file_path = None - - @classmethod - def setup_logging( - cls, - log_level: int = DEFAULT_LOG_LEVEL, - log_file: Optional[str] = None, - console_output: bool = True, - file_output: bool = True - ) -> str: - """ - 设置项目统一日志配置 - - 这是日志系统的核心初始化方法,负责配置整个项目的日志输出。 - 采用单例模式,确保在整个应用生命周期中只初始化一次。 - - 配置流程: - 1. 检查是否已经初始化(单例模式) - 2. 确定日志文件路径(自动或手动指定) - 3. 创建必要的目录结构 - 4. 配置根日志器和处理器 - 5. 设置日志格式化器 - 6. 添加控制台和文件处理器 - 7. 记录初始化信息 - - 特殊处理: - - MCP模式下通常禁用控制台输出,避免干扰stdio通信 - - Windows系统下的UTF-8编码处理 - - 日志文件的自动轮转管理 - - 参数: - log_level: 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL) - log_file: 日志文件路径,None时使用默认路径 - console_output: 是否输出到控制台(MCP模式下通常为False) - file_output: 是否输出到文件(通常为True) - - 返回: - str: 实际使用的日志文件路径 - - 注意事项: - - 这个方法是线程安全的 - - 重复调用会直接返回已配置的路径 - - 日志文件会自动创建必要的目录 - """ - # 单例模式检查:如果已经初始化,直接返回 - if cls._initialized: - return cls._log_file_path - - # ==================== 日志文件路径配置 ==================== - - if log_file is None: - # 自动确定日志文件路径:项目根目录 + 默认文件名 - project_root = cls._get_project_root() - log_file = project_root / cls.LOG_FILE_NAME - else: - # 使用指定的日志文件路径 - log_file = Path(log_file) - - # 确保日志目录存在(递归创建) - log_file.parent.mkdir(parents=True, exist_ok=True) - cls._log_file_path = str(log_file) - - # ==================== 包日志器配置 ==================== - - # 获取包的顶层日志器,而不是根日志器 - package_logger = logging.getLogger('lzwcai_mcp_api_converter') - package_logger.setLevel(log_level) - - # 作为库,不应该清除宿主应用的任何处理器 - # 也不应该让日志消息向上传播到根日志器,以免重复打印 - package_logger.propagate = False - - # 清除此日志器上现有的处理器,避免重复配置 - for handler in package_logger.handlers[:]: - package_logger.removeHandler(handler) - - # ==================== 日志格式化器 ==================== - - # 创建统一的日志格式化器 - formatter = logging.Formatter( - fmt=cls.DEFAULT_LOG_FORMAT, # 日志格式模板 - datefmt=cls.DEFAULT_DATE_FORMAT # 时间格式 - ) - - # ==================== 控制台处理器配置 ==================== - - if console_output: - # 控制台输出处理器,支持彩色输出和UTF-8编码 - import io - - # 处理Windows系统的编码问题 - if hasattr(sys.stdout, 'buffer'): - # 在Windows上强制使用UTF-8编码,避免中文乱码 - # errors='replace'确保即使有编码问题也不会崩溃 - console_stream = io.TextIOWrapper( - sys.stdout.buffer, - encoding='utf-8', - errors='replace' - ) - else: - # Unix/Linux系统通常默认支持UTF-8 - console_stream = sys.stdout - - # 创建控制台处理器 - console_handler = logging.StreamHandler(console_stream) - console_handler.setLevel(log_level) - console_handler.setFormatter(formatter) - package_logger.addHandler(console_handler) - - # ==================== 文件处理器配置 ==================== - - if file_output: - # 文件输出处理器,支持自动轮转 - file_handler = logging.handlers.RotatingFileHandler( - filename=cls._log_file_path, # 日志文件路径 - maxBytes=cls.MAX_LOG_SIZE, # 单文件最大大小 - backupCount=cls.BACKUP_COUNT, # 备份文件数量 - encoding='utf-8' # 文件编码 - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - package_logger.addHandler(file_handler) - - # ==================== 初始化完成标记 ==================== - - # 标记为已初始化,防止重复配置 - cls._initialized = True - - # ==================== 记录初始化信息 ==================== - - # 获取当前模块的日志器并记录初始化信息 - logger = logging.getLogger(__name__) - logger.info("=" * 80) - logger.info(f"日志系统初始化完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - logger.info(f"日志级别: {logging.getLevelName(log_level)}") - logger.info(f"日志文件: {cls._log_file_path}") - logger.info(f"控制台输出: {console_output}") - logger.info(f"文件输出: {file_output}") - logger.info(f"文件轮转: 最大{cls.MAX_LOG_SIZE // (1024*1024)}MB, 保留{cls.BACKUP_COUNT}个备份") - logger.info("=" * 80) - - return cls._log_file_path - - @classmethod - def _get_project_root(cls) -> Path: - """ - 获取项目根目录 - - 这个方法通过向上遍历目录树来查找项目根目录。 - 它会寻找常见的项目标识文件来确定根目录位置。 - - 查找策略: - 1. 从当前文件所在目录开始向上查找 - 2. 寻找项目标识文件:pyproject.toml, setup.py, main.py - 3. 找到任一标识文件的目录即为项目根目录 - 4. 如果都找不到,使用当前文件的上级目录作为备选 - - 返回: - Path: 项目根目录的路径对象 - - 注意事项: - - 这个方法假设项目结构相对标准 - - 在特殊的部署环境中可能需要调整 - - 备选方案确保总是返回有效路径 - """ - # 从当前文件向上查找项目根目录 - current_path = Path(__file__).parent - - # 向上遍历目录树 - while current_path.parent != current_path: # 避免到达文件系统根目录 - # 检查常见的项目标识文件 - if (current_path / "pyproject.toml").exists() or \ - (current_path / "setup.py").exists() or \ - (current_path / "main.py").exists(): - return current_path - current_path = current_path.parent - - # 备选方案:如果找不到标识文件,使用预设的相对路径 - # 这个路径基于当前的项目结构:util -> src -> 项目根 - return Path(__file__).parent.parent.parent - - @classmethod - def get_logger(cls, name: str) -> logging.Logger: - """ - 获取配置好的日志器 - - 这是获取日志器的标准方法,确保返回的日志器使用统一的配置。 - 如果日志系统尚未初始化,会自动进行初始化。 - - 参数: - name: 日志器名称,通常使用模块的 __name__ 变量 - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = LoggerConfig.get_logger(__name__) - logger.info("这是一条信息日志") - - 特性: - - 自动初始化:首次调用时自动配置日志系统(MCP模式下禁用控制台输出) - - 层次化命名:支持Python日志器的层次化命名 - - 统一配置:所有日志器使用相同的格式和输出配置 - """ - # 检查是否已初始化,未初始化则使用默认配置初始化 - # 重要:在MCP模式下禁用控制台输出,避免干扰stdio通信 - if not cls._initialized: - cls.setup_logging(console_output=False, file_output=True) - - # 返回指定名称的日志器 - return logging.getLogger(name) - - # ==================== 日志工具方法 ==================== - - @classmethod - def log_function_entry(cls, logger: logging.Logger, func_name: str, **kwargs): - """ - 记录函数入口日志 - - 用于调试和性能分析,记录函数被调用时的参数信息。 - 通常在DEBUG级别输出,不会影响生产环境的性能。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - **kwargs: 函数参数(键值对形式) - - 使用示例: - LoggerConfig.log_function_entry(logger, "process_data", user_id=123, action="login") - """ - args_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()]) - logger.debug(f"进入函数 {func_name}({args_str})") - - @classmethod - def log_function_exit(cls, logger: logging.Logger, func_name: str, result=None): - """ - 记录函数出口日志 - - 与log_function_entry配对使用,记录函数执行完成和返回值。 - 有助于跟踪函数执行流程和调试返回值问题。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - result: 函数返回值(可选) - - 使用示例: - LoggerConfig.log_function_exit(logger, "process_data", result={"status": "success"}) - """ - if result is not None: - logger.debug(f"退出函数 {func_name},返回值: {result}") - else: - logger.debug(f"退出函数 {func_name}") - - @classmethod - def log_api_request(cls, logger: logging.Logger, method: str, url: str, **kwargs): - """ - 记录API请求日志 - - 标准化API请求的日志记录,包含HTTP方法、URL和请求参数。 - 有助于API调用的监控和调试。 - - 参数: - logger: 日志器实例 - method: HTTP方法(GET, POST, PUT, DELETE等) - url: 请求URL - **kwargs: 请求参数(可选) - - 使用示例: - LoggerConfig.log_api_request(logger, "POST", "https://api.example.com/users", - headers={"Authorization": "Bearer xxx"}) - """ - logger.info(f"API请求 - {method} {url}") - if kwargs: - logger.debug(f"请求参数: {kwargs}") - - @classmethod - def log_api_response(cls, logger: logging.Logger, status_code: int, response_time: float = None): - """ - 记录API响应日志 - - 记录API响应的状态码和响应时间,用于性能监控和问题诊断。 - - 参数: - logger: 日志器实例 - status_code: HTTP状态码 - response_time: 响应时间(秒,可选) - - 使用示例: - LoggerConfig.log_api_response(logger, 200, 0.156) - """ - if response_time: - logger.info(f"API响应 - 状态码: {status_code}, 响应时间: {response_time:.3f}s") - else: - logger.info(f"API响应 - 状态码: {status_code}") - - @classmethod - def log_error_with_context(cls, logger: logging.Logger, error: Exception, context: str = ""): - """ - 记录带上下文的错误日志 - - 提供丰富的错误信息记录,包含异常类型、错误消息、上下文信息和详细堆栈。 - 这是错误处理的标准方法。 - - 参数: - logger: 日志器实例 - error: 异常对象 - context: 错误发生的上下文描述(可选) - - 使用示例: - try: - risky_operation() - except Exception as e: - LoggerConfig.log_error_with_context(logger, e, "处理用户请求时") - """ - if context: - logger.error(f"错误发生在 {context}: {type(error).__name__}: {str(error)}") - else: - logger.error(f"错误: {type(error).__name__}: {str(error)}") - # 记录详细的异常堆栈信息(仅在DEBUG级别显示) - logger.debug("错误详情:", exc_info=True) - - -# ==================== 便捷函数 ==================== - -def get_logger(name: str) -> logging.Logger: - """ - 获取日志器的便捷函数 - - 这是LoggerConfig.get_logger的简化版本,提供更简洁的调用方式。 - 推荐在模块级别使用这个函数获取日志器。 - - 参数: - name: 日志器名称,通常使用 __name__ - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = get_logger(__name__) - """ - return LoggerConfig.get_logger(name) - - -def setup_logging(**kwargs) -> str: - """ - 设置日志的便捷函数 - - 这是LoggerConfig.setup_logging的简化版本,支持所有相同的参数。 - - 参数: - **kwargs: 传递给LoggerConfig.setup_logging的所有参数 - - 返回: - str: 日志文件路径 - - 使用示例: - log_file = setup_logging(log_level=logging.DEBUG, console_output=False) - """ - return LoggerConfig.setup_logging(**kwargs) - - -# ==================== 装饰器 ==================== - -def log_function_calls(logger: Optional[logging.Logger] = None): - """ - 函数调用日志装饰器 - - 这个装饰器自动记录函数的调用和返回,包括参数和返回值。 - 主要用于调试和性能分析,在生产环境中通常设置为DEBUG级别。 - - 特性: - - 自动记录函数入口和出口 - - 记录函数参数(kwargs) - - 记录返回值 - - 自动处理异常并记录错误上下文 - - 支持自定义日志器或自动获取 - - 参数: - logger: 可选的日志器实例,None时自动获取函数所在模块的日志器 - - 返回: - 装饰器函数 - - 使用示例: - @log_function_calls() - def process_user_data(user_id, action="login"): - # 函数实现 - return {"status": "success"} - - # 或者指定日志器 - @log_function_calls(logger=my_logger) - def another_function(): - pass - - 注意事项: - - 会记录所有kwargs参数,注意不要记录敏感信息 - - 返回值也会被记录,大对象可能影响性能 - - 异常会被重新抛出,不会被吞掉 - """ - def decorator(func): - def wrapper(*args, **kwargs): - nonlocal logger - # 如果没有提供日志器,自动获取函数所在模块的日志器 - if logger is None: - logger = get_logger(func.__module__) - - func_name = func.__name__ - - # 记录函数入口(只记录kwargs,避免记录过多信息) - LoggerConfig.log_function_entry(logger, func_name, **kwargs) - - try: - # 执行原函数 - result = func(*args, **kwargs) - - # 记录函数出口和返回值 - LoggerConfig.log_function_exit(logger, func_name, result) - return result - - except Exception as e: - # 记录异常信息并重新抛出 - LoggerConfig.log_error_with_context(logger, e, f"函数 {func_name}") - raise - - return wrapper - return decorator - - -# ==================== 测试代码 ==================== - -if __name__ == "__main__": - """ - 日志配置测试代码 - - 这个测试代码演示了日志系统的基本功能,包括: - 1. 日志系统初始化 - 2. 不同级别的日志输出 - 3. 日志文件路径获取 - 4. 装饰器功能测试 - - 运行方式: - python -m lzwcai_mcp_api_converter.src.util.logger_config - """ - # 初始化日志系统(DEBUG级别,同时输出到控制台和文件) - log_file = setup_logging(log_level=logging.DEBUG) - test_logger = get_logger(__name__) - - test_logger.info("开始测试日志配置...") - - # 测试不同级别的日志输出 - test_logger.debug("这是一个调试消息 - 用于开发调试") - test_logger.info("这是一个信息消息 - 记录重要信息") - test_logger.warning("这是一个警告消息 - 提醒注意事项") - test_logger.error("这是一个错误消息 - 记录错误情况") - - # 测试工具方法 - LoggerConfig.log_api_request(test_logger, "GET", "https://api.example.com/test") - LoggerConfig.log_api_response(test_logger, 200, 0.123) - - # 测试装饰器 - @log_function_calls() - def test_function(param1, param2="default"): - """测试函数""" - return {"result": "success", "param1": param1} - - # 调用测试函数 - result = test_function("test_value", param2="custom") - - # 输出日志文件位置 - test_logger.info(f"日志文件位置: {log_file}") - test_logger.info("日志配置测试完成!") diff --git a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/nested_value.py b/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/nested_value.py deleted file mode 100644 index 69c3d92..0000000 --- a/lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/nested_value.py +++ /dev/null @@ -1,168 +0,0 @@ -import re -from typing import Any, Optional, Union, Dict, List -from .logger_config import get_logger - -logger = get_logger(__name__) - -# 预编译正则表达式 -PATH_PATTERN = re.compile(r"\{([^}]+)\}") - - -def get_nested_value( - data: Union[Dict, List, Any], - path_str: str, - default: Any = None, - raise_error: bool = False, -) -> Any: - """从嵌套字典或列表中根据路径字符串获取值 - - 支持以下格式的路径字符串: - 1. 简单路径: {res.data.token} - 2. 带前缀: Bearer {res.data.token} - 3. 带索引: {res.data.items[0].name} - 4. 带引号的键: {res.data."user.name"} - - Args: - data: 要查询的数据,可以是字典、列表或其他类型 - path_str: 路径字符串,支持{prefix} {path}或{path}格式 - default: 当路径不存在时返回的默认值 - raise_error: 是否在出错时抛出异常,默认为False - - Returns: - 查询到的值或默认值 - - Raises: - ValueError: 当raise_error为True且路径格式无效时 - KeyError: 当raise_error为True且路径不存在时 - TypeError: 当raise_error为True且类型错误时 - """ - try: - if not path_str: - logger.warning("路径字符串为空") - return default - - if data is None: - logger.warning("输入数据为None") - return default - - if not isinstance(data, (dict, list)): - logger.warning(f"输入数据类型不支持: {type(data)}") - return default - - # 使用预编译的正则表达式匹配路径 - matches = PATH_PATTERN.findall(path_str) - - if not matches: - logger.warning(f"路径字符串格式无效: {path_str}") - if raise_error: - raise ValueError(f"路径字符串格式无效: {path_str}") - return default - - # 处理路径 - if len(matches) == 1: - actual_path = matches[0] - # 获取{...}之前的所有文本作为前缀 - prefix = path_str[: path_str.find("{")].strip() - elif len(matches) == 2: - prefix = matches[0] - actual_path = matches[1] - else: - logger.warning(f"路径字符串包含过多匹配项: {path_str}") - if raise_error: - raise ValueError(f"路径字符串包含过多匹配项: {path_str}") - return default - - # 解析路径 - current = data - # 使用更智能的路径分割 - path_parts = [] - current_part = "" - in_quotes = False - - for char in actual_path: - if char == '"': - in_quotes = not in_quotes - current_part += char - elif char == "." and not in_quotes: - path_parts.append(current_part) - current_part = "" - else: - current_part += char - - if current_part: - path_parts.append(current_part) - - for part in path_parts: - # 处理数组索引 - if "[" in part and part.endswith("]"): - key, index_str = part.split("[", 1) - index_str = index_str.rstrip("]") - - # 获取键值 - if key and isinstance(current, dict): - current = current.get(key) - elif not key and isinstance(current, list): - pass - else: - logger.warning(f"无效的键: {key}") - if raise_error: - raise KeyError(f"无效的键: {key}") - return default - - # 获取索引 - try: - index = int(index_str) - if not isinstance(current, list) or not (0 <= index < len(current)): - logger.warning(f"无效的索引: {index}") - if raise_error: - raise IndexError(f"无效的索引: {index}") - return default - current = current[index] - except ValueError: - logger.warning(f"无效的索引格式: {index_str}") - if raise_error: - raise ValueError(f"无效的索引格式: {index_str}") - return default - else: - # 处理普通键 - if isinstance(current, dict): - # 处理带引号的键 - if part.startswith('"') and part.endswith('"'): - part = part[1:-1] - if part not in current: - logger.warning(f"键不存在: {part}") - if raise_error: - raise KeyError(f"键不存在: {part}") - return default - current = current[part] - elif isinstance(current, list): - try: - index = int(part) - if not (0 <= index < len(current)): - logger.warning(f"索引越界: {index}") - if raise_error: - raise IndexError(f"索引越界: {index}") - return default - current = current[index] - except ValueError: - logger.warning(f"无效的列表索引: {part}") - if raise_error: - raise ValueError(f"无效的列表索引: {part}") - return default - else: - logger.warning(f"无法访问键: {part}") - if raise_error: - raise TypeError(f"无法访问键: {part}") - return default - - # 处理前缀 - if prefix and current is not None: - return f"{prefix} {current}" - - return current - - except Exception as e: - logger.error(f"获取嵌套值失败: {str(e)}") - if raise_error: - raise - return default diff --git a/lzwcai_mcp_api_converter/main.py b/lzwcai_mcp_api_converter/main.py deleted file mode 100644 index 6625bff..0000000 --- a/lzwcai_mcp_api_converter/main.py +++ /dev/null @@ -1,12 +0,0 @@ -import os - -os.environ["modelId"] = "1946471611735015425" -os.environ["bizSysId"] = "2029468454441897985" -os.environ["bizSysApiIds"] = "[\"2033382693160300546\"]" -os.environ["businessUuid"] = "dcqwlucfo7h" -os.environ["LZWCAI_CORP_MANAGER_URL"] = "http://192.168.2.236:8088" -# 导入模块 -from lzwcai_mcp_api_converter.src.create_mcp import run_main - -if __name__ == "__main__": - run_main() diff --git a/lzwcai_mcp_api_converter/pyproject.toml b/lzwcai_mcp_api_converter/pyproject.toml deleted file mode 100644 index 1d85ec5..0000000 --- a/lzwcai_mcp_api_converter/pyproject.toml +++ /dev/null @@ -1,28 +0,0 @@ -[project] -name = "lzwcai-mcp-api-converter" -version = "0.2.5" -description = "基于FastMCP框架的动态API工具服务器,自动将企业业务API配置转换为MCP协议工具,支持多种传输方式、企业认证和参数验证,为AI助手提供标准化的业务接口访问能力。" -readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "dynaconf>=3.2.11", - "httpx>=0.28.1", - "jinja2==3.1.6", - "mcp[cli]>=1.8.0", - "requests>=2.31.0", - "pypinyin>=0.54.0", -] - - -[tool.setuptools] -packages = {find = {where = ["."], include = ["lzwcai_mcp_api_converter*"]}} - -[tool.setuptools.package-data] -"lzwcai_mcp_api_converter.src" = ["api_config.json"] - -[project.scripts] -lzwcai-mcp-api-converter = "lzwcai_mcp_api_converter.src.create_mcp:run_main" - -[tool.hatch.build.targets.wheel] -packages = ["lzwcai_mcp_api_converter"] - diff --git a/lzwcai_mcp_api_converter/setup.cfg b/lzwcai_mcp_api_converter/setup.cfg deleted file mode 100644 index 8bfd5a1..0000000 --- a/lzwcai_mcp_api_converter/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff --git a/lzwcai_mcp_iot/IoT设备工具说明.md b/lzwcai_mcp_iot/IoT设备工具说明.md deleted file mode 100644 index b3ba2a9..0000000 --- a/lzwcai_mcp_iot/IoT设备工具说明.md +++ /dev/null @@ -1,140 +0,0 @@ -# IoT 设备 MCP 工具说明 - -## 📋 工具清单 - -本服务提供 **4 个核心工具**,用于智能设备的查询、定位和控制。 - ---- - -## 🔧 工具详情 - -### 1. **iot_get_devices_by_location** - 根据位置获取设备 - -**功能**:查询指定位置/房间的所有智能设备 - -**使用场景**: -- "办公室有哪些设备" -- "会议室有什么设备" -- "客厅设备列表" - -**参数**: -- `location` (必填):位置/房间名称 - -**返回**:该位置的设备清单(设备ID、名称、类型、状态、控制命令等) - ---- - -### 2. **iot_get_all_spaces_and_devices** - 获取所有空间位置信息 - -**功能**:获取系统中所有可用空间位置的列表(仅空间名称,不包含设备详情) - -**使用场景**: -- "显示所有空间" -- "有哪些位置" -- "空间列表" -- "一共有多少个房间" - -**参数**:无需参数 - -**返回**: -- 空间总数 -- 所有空间名称的列表 - -**注意**:此工具只返回空间清单,如需查看某个空间的设备,请使用 `iot_get_devices_by_location` 工具 - ---- - -### 3. **iot_device_precise_controller** - IoT设备精确控制 - -**功能**:通过设备ID精确控制特定设备 - -**使用场景**: -- 控制特定的灯光、空调、门禁等 -- 需配合查询工具获取设备信息后使用 - -**参数**: -- `entityId` (必填):设备唯一ID -- `command` (必填):操作命令(如 turn_on、turn_off、set_temperature) -- `params` (必填):操作参数(根据命令类型提供,如温度值、亮度等) -- `userId` (可选):用户ID - -**返回**:设备操作结果(成功/失败、设备反馈) - ---- - -### 4. **smart_space_device_locator_matcher** - 智能空间设备定位 - -**功能**:查询用户当前所属的空间/位置 - -**使用场景**: -- "我现在在哪" -- "当前位置是什么" -- "确认一下位置" - -**参数**: -- `userId` (必填):用户ID - -**返回**:用户所属的空间名称 - ---- - -## 💡 典型使用流程 - -### 方式一:查看所有空间 -``` -1. 调用 iot_get_all_spaces_and_devices 获取所有空间列表 -2. 选择感兴趣的空间 -3. 调用 iot_get_devices_by_location 查看该空间的设备 -``` - -### 方式二:查看特定位置的设备 -``` -1. 调用 iot_get_devices_by_location 指定位置 -2. 查看该位置的设备清单和状态 -``` - -### 方式三:控制设备(两步操作) -``` -1. 调用 iot_get_devices_by_location 获取设备列表 -2. 从结果中提取 entityId 和 command -3. 调用 iot_device_precise_controller 执行控制 -``` - -### 方式四:定位用户 -``` -1. 调用 smart_space_device_locator_matcher -2. 获取用户当前所属空间 -3. 基于位置查询或控制设备 -``` - ---- - -## 📝 注意事项 - -1. **企业ID配置**:服务启动时需要配置 `ENTERPRISE_ID` 环境变量,系统会自动初始化向量库 -2. **日志记录**:所有操作都会记录到日志文件 `lzwcai_mcp_iot.log` -3. **传输方式**:使用 stdio(标准输入输出)方式运行 -4. **控制工具配合**:精确控制工具必须配合查询工具使用,不能单独随意填写参数 - -## 🔄 重要更新(v0.3.2) - -**配置变更:** -- ❌ 废弃:`employeeId` 环境变量 -- ✅ 新增:`ENTERPRISE_ID` 环境变量(必需) - -**初始化流程优化:** -- 移除了通过员工ID查询企业ID的步骤 -- 现在直接使用企业ID进行初始化 -- 提高了服务启动效率 - ---- - -## 🎯 核心特性 - -- ✅ 支持位置筛选查询设备 -- ✅ 支持获取所有可用空间列表 -- ✅ 支持精确的设备ID控制 -- ✅ 支持用户空间定位 -- ✅ 自动格式化设备列表输出 -- ✅ 完整的错误处理和日志记录 - diff --git a/lzwcai_mcp_iot/PKG-INFO b/lzwcai_mcp_iot/PKG-INFO deleted file mode 100644 index cc00c23..0000000 --- a/lzwcai_mcp_iot/PKG-INFO +++ /dev/null @@ -1,22 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-mcp-smartIot -Version: 0.2.21 -Summary: IoT设备控制服务器,使用FastMCP框架提供设备操作功能 -Author-email: LZWCAI开发团队 -License: 专有软件 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Operating System :: OS Independent -Requires-Python: >=3.8 -Description-Content-Type: text/markdown -Requires-Dist: fastmcp>=0.1.0 -Requires-Dist: requests -Provides-Extra: dev -Requires-Dist: pytest>=7.0.0; extra == "dev" -Requires-Dist: black>=23.1.0; extra == "dev" -Requires-Dist: isort>=5.12.0; extra == "dev" -Requires-Dist: flake8>=6.0.0; extra == "dev" -Requires-Dist: mypy>=1.0.0; extra == "dev" diff --git a/lzwcai_mcp_iot/README.md b/lzwcai_mcp_iot/README.md deleted file mode 100644 index 6b9face..0000000 --- a/lzwcai_mcp_iot/README.md +++ /dev/null @@ -1,200 +0,0 @@ -# lzwcai-mcp-iot - -[![Version](https://img.shields.io/badge/version-0.3.1-blue.svg)](https://pypi.org/project/lzwcai-mcp-iot/) -[![Python](https://img.shields.io/badge/python-3.8%2B-brightgreen.svg)](https://www.python.org/) -[![License](https://img.shields.io/badge/license-Proprietary-red.svg)]() - -> IoT设备控制服务器,使用 FastMCP 框架提供智能设备的查询、定位和控制功能 - -## ✨ 特性 - -- ✅ 支持位置筛选查询设备 -- ✅ 支持获取所有可用空间列表 -- ✅ 支持精确的设备ID控制 -- ✅ 支持用户空间定位 -- ✅ 自动格式化设备列表输出 -- ✅ 完整的错误处理和日志记录 - -## 📦 安装 - -```bash -pip install lzwcai-mcp-iot -``` - -或从源码安装: - -```bash -git clone -cd lzwcai_mcp_iot -pip install -e . -``` - -## 🚀 快速开始 - -### 启动服务 - -```bash -lzwcai-mcp-iot -``` - -### 配置要求 - -服务启动时需要配置 `employeeId`,系统会自动初始化企业ID。 - -## 🔧 核心工具 - -本服务提供 **4 个核心工具**,用于智能设备的查询、定位和控制。 - -### 1. iot_get_devices_by_location - 根据位置获取设备 - -**功能**:查询指定位置/房间的所有智能设备 - -**使用场景**: -- "办公室有哪些设备" -- "会议室有什么设备" -- "客厅设备列表" - -**参数**: -- `location` (必填):位置/房间名称 - -**返回**:该位置的设备清单(设备ID、名称、类型、状态、控制命令等) - ---- - -### 2. iot_get_all_spaces_and_devices - 获取所有空间位置信息 - -**功能**:获取系统中所有可用空间位置的列表(仅空间名称,不包含设备详情) - -**使用场景**: -- "显示所有空间" -- "有哪些位置" -- "空间列表" -- "一共有多少个房间" - -**参数**:无需参数 - -**返回**: -- 空间总数 -- 所有空间名称的列表 - -**注意**:此工具只返回空间清单,如需查看某个空间的设备,请使用 `iot_get_devices_by_location` 工具 - ---- - -### 3. iot_device_precise_controller - IoT设备精确控制 - -**功能**:通过设备ID精确控制特定设备 - -**使用场景**: -- 控制特定的灯光、空调、门禁等 -- 需配合查询工具获取设备信息后使用 - -**参数**: -- `entityId` (必填):设备唯一ID -- `command` (必填):操作命令(如 turn_on、turn_off、set_temperature) -- `params` (必填):操作参数(根据命令类型提供,如温度值、亮度等) -- `userId` (可选):用户ID - -**返回**:设备操作结果(成功/失败、设备反馈) - ---- - -### 4. smart_space_device_locator_matcher - 智能空间设备定位 - -**功能**:查询用户当前所属的空间/位置 - -**使用场景**: -- "我现在在哪" -- "当前位置是什么" -- "确认一下位置" - -**参数**: -- `userId` (必填):用户ID - -**返回**:用户所属的空间名称 - ---- - -## 💡 典型使用流程 - -### 方式一:查看所有空间 -``` -1. 调用 iot_get_all_spaces_and_devices 获取所有空间列表 -2. 选择感兴趣的空间 -3. 调用 iot_get_devices_by_location 查看该空间的设备 -``` - -### 方式二:查看特定位置的设备 -``` -1. 调用 iot_get_devices_by_location 指定位置 -2. 查看该位置的设备清单和状态 -``` - -### 方式三:控制设备(两步操作) -``` -1. 调用 iot_get_devices_by_location 获取设备列表 -2. 从结果中提取 entityId 和 command -3. 调用 iot_device_precise_controller 执行控制 -``` - -### 方式四:定位用户 -``` -1. 调用 smart_space_device_locator_matcher -2. 获取用户当前所属空间 -3. 基于位置查询或控制设备 -``` - -## 📝 注意事项 - -1. **企业ID初始化**:服务启动时需要配置 `employeeId`,系统会自动初始化企业ID -2. **日志记录**:所有操作都会记录到日志文件 `lzwcai_mcp_iot.log` -3. **传输方式**:使用 stdio(标准输入输出)方式运行 -4. **控制工具配合**:精确控制工具必须配合查询工具使用,不能单独随意填写参数 - -## 🛠️ 开发 - -### 安装开发依赖 - -```bash -pip install -e ".[dev]" -``` - -### 代码格式化 - -```bash -# 使用 black 格式化 -black lzwcai_mcp_iot/ - -# 使用 isort 排序导入 -isort lzwcai_mcp_iot/ -``` - -### 代码检查 - -```bash -# 使用 flake8 -flake8 lzwcai_mcp_iot/ - -# 使用 mypy -mypy lzwcai_mcp_iot/ -``` - -### 运行测试 - -```bash -pytest -``` - -## 📄 许可证 - -专有软件 - 版权所有 © LZWCAI开发团队 - -## 📧 联系方式 - -- 开发团队:LZWCAI开发团队 -- 邮箱:dev@lzwcai.com - -## 📚 更多文档 - -详细的工具使用说明请参考 [IoT设备工具说明.md](IoT设备工具说明.md) - diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/PKG-INFO b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/PKG-INFO deleted file mode 100644 index 0541e82..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/PKG-INFO +++ /dev/null @@ -1,223 +0,0 @@ -Metadata-Version: 2.4 -Name: lzwcai-mcp-iot -Version: 0.3.3 -Summary: IoT设备控制服务器,使用FastMCP框架提供设备操作功能 -Author-email: LZWCAI开发团队 -License-Expression: LicenseRef-Proprietary -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Operating System :: OS Independent -Requires-Python: >=3.8 -Description-Content-Type: text/markdown -Requires-Dist: fastmcp>=0.1.0 -Requires-Dist: requests -Provides-Extra: dev -Requires-Dist: pytest>=7.0.0; extra == "dev" -Requires-Dist: black>=23.1.0; extra == "dev" -Requires-Dist: isort>=5.12.0; extra == "dev" -Requires-Dist: flake8>=6.0.0; extra == "dev" -Requires-Dist: mypy>=1.0.0; extra == "dev" - -# lzwcai-mcp-iot - -[![Version](https://img.shields.io/badge/version-0.3.1-blue.svg)](https://pypi.org/project/lzwcai-mcp-iot/) -[![Python](https://img.shields.io/badge/python-3.8%2B-brightgreen.svg)](https://www.python.org/) -[![License](https://img.shields.io/badge/license-Proprietary-red.svg)]() - -> IoT设备控制服务器,使用 FastMCP 框架提供智能设备的查询、定位和控制功能 - -## ✨ 特性 - -- ✅ 支持位置筛选查询设备 -- ✅ 支持获取所有可用空间列表 -- ✅ 支持精确的设备ID控制 -- ✅ 支持用户空间定位 -- ✅ 自动格式化设备列表输出 -- ✅ 完整的错误处理和日志记录 - -## 📦 安装 - -```bash -pip install lzwcai-mcp-iot -``` - -或从源码安装: - -```bash -git clone -cd lzwcai_mcp_iot -pip install -e . -``` - -## 🚀 快速开始 - -### 启动服务 - -```bash -lzwcai-mcp-iot -``` - -### 配置要求 - -服务启动时需要配置 `employeeId`,系统会自动初始化企业ID。 - -## 🔧 核心工具 - -本服务提供 **4 个核心工具**,用于智能设备的查询、定位和控制。 - -### 1. iot_get_devices_by_location - 根据位置获取设备 - -**功能**:查询指定位置/房间的所有智能设备 - -**使用场景**: -- "办公室有哪些设备" -- "会议室有什么设备" -- "客厅设备列表" - -**参数**: -- `location` (必填):位置/房间名称 - -**返回**:该位置的设备清单(设备ID、名称、类型、状态、控制命令等) - ---- - -### 2. iot_get_all_spaces_and_devices - 获取所有空间位置信息 - -**功能**:获取系统中所有可用空间位置的列表(仅空间名称,不包含设备详情) - -**使用场景**: -- "显示所有空间" -- "有哪些位置" -- "空间列表" -- "一共有多少个房间" - -**参数**:无需参数 - -**返回**: -- 空间总数 -- 所有空间名称的列表 - -**注意**:此工具只返回空间清单,如需查看某个空间的设备,请使用 `iot_get_devices_by_location` 工具 - ---- - -### 3. iot_device_precise_controller - IoT设备精确控制 - -**功能**:通过设备ID精确控制特定设备 - -**使用场景**: -- 控制特定的灯光、空调、门禁等 -- 需配合查询工具获取设备信息后使用 - -**参数**: -- `entityId` (必填):设备唯一ID -- `command` (必填):操作命令(如 turn_on、turn_off、set_temperature) -- `params` (必填):操作参数(根据命令类型提供,如温度值、亮度等) -- `userId` (可选):用户ID - -**返回**:设备操作结果(成功/失败、设备反馈) - ---- - -### 4. smart_space_device_locator_matcher - 智能空间设备定位 - -**功能**:查询用户当前所属的空间/位置 - -**使用场景**: -- "我现在在哪" -- "当前位置是什么" -- "确认一下位置" - -**参数**: -- `userId` (必填):用户ID - -**返回**:用户所属的空间名称 - ---- - -## 💡 典型使用流程 - -### 方式一:查看所有空间 -``` -1. 调用 iot_get_all_spaces_and_devices 获取所有空间列表 -2. 选择感兴趣的空间 -3. 调用 iot_get_devices_by_location 查看该空间的设备 -``` - -### 方式二:查看特定位置的设备 -``` -1. 调用 iot_get_devices_by_location 指定位置 -2. 查看该位置的设备清单和状态 -``` - -### 方式三:控制设备(两步操作) -``` -1. 调用 iot_get_devices_by_location 获取设备列表 -2. 从结果中提取 entityId 和 command -3. 调用 iot_device_precise_controller 执行控制 -``` - -### 方式四:定位用户 -``` -1. 调用 smart_space_device_locator_matcher -2. 获取用户当前所属空间 -3. 基于位置查询或控制设备 -``` - -## 📝 注意事项 - -1. **企业ID初始化**:服务启动时需要配置 `employeeId`,系统会自动初始化企业ID -2. **日志记录**:所有操作都会记录到日志文件 `lzwcai_mcp_iot.log` -3. **传输方式**:使用 stdio(标准输入输出)方式运行 -4. **控制工具配合**:精确控制工具必须配合查询工具使用,不能单独随意填写参数 - -## 🛠️ 开发 - -### 安装开发依赖 - -```bash -pip install -e ".[dev]" -``` - -### 代码格式化 - -```bash -# 使用 black 格式化 -black lzwcai_mcp_iot/ - -# 使用 isort 排序导入 -isort lzwcai_mcp_iot/ -``` - -### 代码检查 - -```bash -# 使用 flake8 -flake8 lzwcai_mcp_iot/ - -# 使用 mypy -mypy lzwcai_mcp_iot/ -``` - -### 运行测试 - -```bash -pytest -``` - -## 📄 许可证 - -专有软件 - 版权所有 © LZWCAI开发团队 - -## 📧 联系方式 - -- 开发团队:LZWCAI开发团队 -- 邮箱:dev@lzwcai.com - -## 📚 更多文档 - -详细的工具使用说明请参考 [IoT设备工具说明.md](IoT设备工具说明.md) - diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/SOURCES.txt b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/SOURCES.txt deleted file mode 100644 index a37f6e0..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/SOURCES.txt +++ /dev/null @@ -1,19 +0,0 @@ -README.md -pyproject.toml -setup.cfg -lzwcai_mcp_iot/__init__.py -lzwcai_mcp_iot/config.py -lzwcai_mcp_iot/iot_device_tool.py -lzwcai_mcp_iot.egg-info/PKG-INFO -lzwcai_mcp_iot.egg-info/SOURCES.txt -lzwcai_mcp_iot.egg-info/dependency_links.txt -lzwcai_mcp_iot.egg-info/entry_points.txt -lzwcai_mcp_iot.egg-info/requires.txt -lzwcai_mcp_iot.egg-info/top_level.txt -lzwcai_mcp_iot/src/__init__.py -lzwcai_mcp_iot/src/device_operations.py -lzwcai_mcp_iot/src/device_results_pretreatment.py -lzwcai_mcp_iot/src/init_mcp.py -lzwcai_mcp_iot/src/iot_device_dicts_prompt.py -lzwcai_mcp_iot/src/logger_config.py -lzwcai_mcp_iot/src/vector_service.py \ No newline at end of file diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/dependency_links.txt b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/entry_points.txt b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/entry_points.txt deleted file mode 100644 index dbb4cf0..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -lzwcai-mcp-iot = lzwcai_mcp_iot.iot_device_tool:main diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/requires.txt b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/requires.txt deleted file mode 100644 index 6e65c56..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/requires.txt +++ /dev/null @@ -1,9 +0,0 @@ -fastmcp>=0.1.0 -requests - -[dev] -pytest>=7.0.0 -black>=23.1.0 -isort>=5.12.0 -flake8>=6.0.0 -mypy>=1.0.0 diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/top_level.txt b/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/top_level.txt deleted file mode 100644 index 10738ed..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -lzwcai_mcp_iot diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot.log b/lzwcai_mcp_iot/lzwcai_mcp_iot.log deleted file mode 100644 index 6f06be3..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot.log +++ /dev/null @@ -1,159 +0,0 @@ -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:215] - ================================================================================ -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2025-11-07 18:18:45 -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:217] - 日志级别: INFO -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai-mcp-server-package\lzwcai_mcp_iot\lzwcai_mcp_iot.log -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:219] - 控制台输出: False -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:220] - 文件输出: True -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2025-11-07 18:18:45 - lzwcai_mcp_iot - INFO - [logger_config.py:222] - ================================================================================ -2025-11-07 18:18:45 - lzwcai_mcp_iot.config - INFO - [config.py:89] - 配置加载完成 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:44] - DeviceOperator初始化完成,API基础URL: http://192.168.2.236:8088 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:39] - VectorService初始化完成,API基础URL: http://192.168.2.236:5002 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:348] - ================================================================================ -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:349] - IoT设备MCP服务器启动流程开始 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:350] - 配置信息: {'device_api_base_url': 'http://192.168.2.236:8088', 'vector_api_base_url': 'http://192.168.2.236:5002', 'enterprise_id': '1952978233106669569', 'employeeId': '1986712221817815042', 'request_timeout': 30, 'max_retries': 3, 'vector_store_name': '设备库', 'vector_store_description': '向量库', 'encoder_type': 'word2vec', 'default_top_k': 1, 'default_auto_create': True, 'log_level': 'INFO', 'log_format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'} -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:351] - ================================================================================ -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:354] - 正在初始化核心组件... -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:355] - 设备操作器API地址: http://192.168.2.236:8088 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:356] - 向量服务API地址: http://192.168.2.236:5002 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:357] - 核心组件初始化完成 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:362] - 检测到员工ID配置: 1986712221817815042 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:363] - 开始初始化MCP服务器... -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:75] - 开始初始化MCP服务器,员工ID: 1986712221817815042 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:78] - 第一步:获取企业ID... -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:410] - 正在获取员工ID为 1986712221817815042 的数字员工信息... -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:418] - 成功获取数字员工信息: {'msg': '操作成功', 'code': 200, 'data': {'userInfo': {'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'remark': None, 'userId': '1909', 'enterpriseId': '1952978233106669569', 'deptId': None, 'userName': '物联网设备管家4628', 'nickName': '物联网设备管家4628', 'userType': '03', 'email': '', 'phonenumber': '', 'sex': '2', 'avatar': '', 'password': '123456', 'status': '0', 'delFlag': '0', 'loginIp': '', 'loginDate': None, 'createSubject': False, 'createSpeakSubject': False, 'bizSysId': None, 'dept': None, 'roles': [], 'roleIds': None, 'postIds': None, 'roleId': None, 'enterprise': {'id': None, 'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'enterpriseName': None, 'enterpriseCode': None, 'description': None, 'type': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': None, 'datasetName': None, 'datasetDescribe': None, 'oaType': None, 'oaMap': None, 'status': False}, 'bindOA': False, 'oaMap': None, 'spaceName': None, 'admin': False}, 'employeeInfo': {'id': '1986712221817815042', 'enterpriseId': '1952978233106669569', 'name': '物联网设备管家', 'type': 'assistant', 'imageInfo': None, 'personality': None, 'prompt': '管理设备', 'modelConfig': None, 'promptConfig': None, 'datasetId': '166a9087-830c-443f-81f9-924f8b089530', 'datasetApiKey': 'dataset-nNE2K1KSeagOQnCJjLQzXbpH', 'zhipuApiKey': '12334444', 'enableLongTermMemory': False}, 'enterpriseVO': {'enterpriseId': '1952978233106669569', 'enterpriseName': '中科天目', 'enterpriseCode': '002233', 'type': '人工智能', 'description': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': 'ec8b1391-d093-4b3c-a675-5c88455f094c', 'datasetName': '中科天目', 'datasetDescribe': '系统默认创建', 'difyHost': 'http://192.168.2.236:3001', 'difyEmail': 'lingzewanchuan@lzwcai.com', 'difyName': '中科天目', 'difyPassword': 'Lzwc@2025.'}, 'postVOList': [], 'serverConfigToAI': {'id': '1932057383785390081', 'name': '4dc437bf3d2e', 'ipAddr': '192.168.2.236', 'isMajor': None}, 'deviceBindingList': []}} -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:99] - 成功获取企业ID: 1952978233106669569 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:102] - 第二步:检查向量库状态... -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:435] - 检查向量库状态,keyId: 1952978233106669569 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:447] - 向量库状态检查完成,keyId: 1952978233106669569, exists: True -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:104] - 向量库状态检查完成,存在状态: True -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:137] - 向量库已存在,无需创建 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:366] - MCP服务器初始化结果: {'code': 200, 'msg': 'MCP服务器初始化成功,向量库已存在', 'data': {'enterprise_id': '1952978233106669569', 'vector_store_created': False, 'vector_store_existed': True}} -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:371] - MCP服务器初始化成功 -2025-11-07 18:18:45 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:61] - 企业ID已更新: 1952978233106669569 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:376] - 企业ID已保存到环境变量: 1952978233106669569 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:384] - 已注册的MCP工具: -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:385] - 1. iot_get_devices_by_location - 根据位置获取设备列表 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:386] - 2. iot_get_all_spaces_and_devices - 获取所有空间位置信息 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:387] - 3. iot_device_precise_controller - IoT设备精确控制 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:388] - 4. smart_space_device_locator_matcher - 智能空间设备定位 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:390] - ================================================================================ -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:391] - MCP服务器即将启动,等待客户端连接... -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:392] - 传输方式: stdio (标准输入输出) -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:393] - 注意: 控制台日志已禁用,所有日志将写入文件 -2025-11-07 18:18:45 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:394] - ================================================================================ -2025-11-07 18:18:53 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:119] - 开始处理获取所有空间位置信息请求 -2025-11-07 18:18:53 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:130] - 获取到企业ID: 1952978233106669569 -2025-11-07 18:18:53 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:499] - 查询设备列表,keyId: 1952978233106669569, location: -2025-11-07 18:18:53 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:516] - 设备列表查询成功,返回设备数量: 27 -2025-11-07 18:18:53 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:137] - 查询所有设备结果: {'devices': [{'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'climate.qjiang_cn_741479129_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'switch.zimi_cn_1144259387_dhkg01_on_p_2_1', 'device_desc': '灵泽办公区左吊灯 开关 按键', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '33', 'location_desc': '研发办公区走廊过道;研发办公区走廊;研发办公区过道;研发部走廊', 'entityId': 'switch.zimi_cn_1144138206_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'climate.qjiang_cn_741478765_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_3_1', 'device_desc': '办公桌灯;主位灯;吊灯;灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_4_1', 'device_desc': '入口灯;吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'cover.lumi_cn_1132313226_hmcn02_s_2_2', 'device_desc': '左窗帘;窗帘;帘', 'operations': [{'command': 'open_cover', 'operation_desc': '打开;开;展开;开放', 'operation_params': []}, {'command': 'close_cover', 'operation_desc': '关闭;关;合并;合起来;合', 'operation_params': []}, {'command': 'toggle', 'operation_desc': '切换;一键开关', 'operation_params': []}, {'command': 'stop_cover', 'operation_desc': '停止;停;停一下;', 'operation_params': []}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'climate.qjiang_cn_741362991_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'climate.qjiang_cn_741470846_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '37', 'location_desc': '李总办公室;董事长;李总房间;董事长办公室', 'entityId': 'climate.qjiang_cn_741478700_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '38', 'location_desc': '公司前台;前台;前台大门;门口', 'entityId': 'climate.qjiang_cn_741479337_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'climate.qjiang_cn_741352250_wb20', 'device_desc': '空调', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'switch.zimi_cn_1144125565_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '41', 'location_desc': '小会议室;小会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '42', 'location_desc': '电梯走廊;前台门口;公司门口;公司大门口;大门', 'entityId': 'button.zimi_cn_1119697824_dhkg05_toggle_a_4_1', 'device_desc': '吊灯;电梯灯;灯;照明灯', 'operations': [{'command': 'press', 'operation_desc': '按下;按;开关;打开或者关闭;', 'operation_params': []}]}, {'location_key': '43', 'location_desc': '公司大门;大门口;前台;公司前台;大门', 'entityId': 'switch.giot_cn_1110921716_v51ksm_on_p_2_1', 'device_desc': '大门开关', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_3_1', 'device_desc': '射灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '45', 'location_desc': '会议室过道;会议室走廊;会议区过道', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_5_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '46', 'location_desc': '爱易拍展厅;爱一拍展厅;爱一拍展区;爱一排展区', 'entityId': 'switch.zimi_cn_1144256905_dhkg02_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '47', 'location_desc': '灵泽展厅过道;灵泽展厅走廊', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'climate.qjiang_cn_741348975_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '49', 'location_desc': '休息室二楼;休息室二层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '50', 'location_desc': '前台休息区', 'entityId': 'switch.zimi_cn_1119697824_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}], 'count': 27, 'location_filter': None} -2025-11-07 18:19:09 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:57] - 开始处理IoT设备查询请求 - 位置: 研发 -2025-11-07 18:19:09 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:70] - 获取到企业ID: 1952978233106669569 -2025-11-07 18:19:09 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:499] - 查询设备列表,keyId: 1952978233106669569, location: 研发 -2025-11-07 18:19:09 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:516] - 设备列表查询成功,返回设备数量: 3 -2025-11-07 18:19:09 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:77] - 查询设备列表结果: {'devices': [{'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'climate.qjiang_cn_741479129_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'switch.zimi_cn_1144259387_dhkg01_on_p_2_1', 'device_desc': '灵泽办公区左吊灯 开关 按键', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '33', 'location_desc': '研发办公区走廊过道;研发办公区走廊;研发办公区过道;研发部走廊', 'entityId': 'switch.zimi_cn_1144138206_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}], 'count': 3, 'location_filter': '研发'} -2025-11-07 18:19:09 - lzwcai_mcp_iot.src.device_results_pretreatment - INFO - [device_results_pretreatment.py:458] - 设备列表格式化完成,设备数量: 3 -2025-11-07 18:19:09 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:92] - 设备列表已格式化,设备数量: 3 -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:215] - ================================================================================ -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2025-11-07 18:21:20 -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:217] - 日志级别: INFO -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai-mcp-server-package\lzwcai_mcp_iot\lzwcai_mcp_iot.log -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:219] - 控制台输出: False -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:220] - 文件输出: True -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2025-11-07 18:21:20 - lzwcai_mcp_iot - INFO - [logger_config.py:222] - ================================================================================ -2025-11-07 18:21:20 - lzwcai_mcp_iot.config - INFO - [config.py:89] - 配置加载完成 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:44] - DeviceOperator初始化完成,API基础URL: http://192.168.2.236:8088 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:39] - VectorService初始化完成,API基础URL: http://192.168.2.236:5002 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:353] - ================================================================================ -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:354] - IoT设备MCP服务器启动流程开始 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:355] - 配置信息: {'device_api_base_url': 'http://192.168.2.236:8088', 'vector_api_base_url': 'http://192.168.2.236:5002', 'enterprise_id': '1952978233106669569', 'employeeId': '1986712221817815042', 'request_timeout': 30, 'max_retries': 3, 'vector_store_name': '设备库', 'vector_store_description': '向量库', 'encoder_type': 'word2vec', 'default_top_k': 1, 'default_auto_create': True, 'log_level': 'INFO', 'log_format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'} -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:356] - ================================================================================ -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:359] - 正在初始化核心组件... -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:360] - 设备操作器API地址: http://192.168.2.236:8088 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:361] - 向量服务API地址: http://192.168.2.236:5002 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:362] - 核心组件初始化完成 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:367] - 检测到员工ID配置: 1986712221817815042 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:368] - 开始初始化MCP服务器... -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:75] - 开始初始化MCP服务器,员工ID: 1986712221817815042 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:78] - 第一步:获取企业ID... -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:410] - 正在获取员工ID为 1986712221817815042 的数字员工信息... -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:418] - 成功获取数字员工信息: {'msg': '操作成功', 'code': 200, 'data': {'userInfo': {'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'remark': None, 'userId': '1909', 'enterpriseId': '1952978233106669569', 'deptId': None, 'userName': '物联网设备管家4628', 'nickName': '物联网设备管家4628', 'userType': '03', 'email': '', 'phonenumber': '', 'sex': '2', 'avatar': '', 'password': '123456', 'status': '0', 'delFlag': '0', 'loginIp': '', 'loginDate': None, 'createSubject': False, 'createSpeakSubject': False, 'bizSysId': None, 'dept': None, 'roles': [], 'roleIds': None, 'postIds': None, 'roleId': None, 'enterprise': {'id': None, 'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'enterpriseName': None, 'enterpriseCode': None, 'description': None, 'type': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': None, 'datasetName': None, 'datasetDescribe': None, 'oaType': None, 'oaMap': None, 'status': False}, 'bindOA': False, 'oaMap': None, 'spaceName': None, 'admin': False}, 'employeeInfo': {'id': '1986712221817815042', 'enterpriseId': '1952978233106669569', 'name': '物联网设备管家', 'type': 'assistant', 'imageInfo': None, 'personality': None, 'prompt': '管理设备', 'modelConfig': None, 'promptConfig': None, 'datasetId': '166a9087-830c-443f-81f9-924f8b089530', 'datasetApiKey': 'dataset-nNE2K1KSeagOQnCJjLQzXbpH', 'zhipuApiKey': '12334444', 'enableLongTermMemory': False}, 'enterpriseVO': {'enterpriseId': '1952978233106669569', 'enterpriseName': '中科天目', 'enterpriseCode': '002233', 'type': '人工智能', 'description': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': 'ec8b1391-d093-4b3c-a675-5c88455f094c', 'datasetName': '中科天目', 'datasetDescribe': '系统默认创建', 'difyHost': 'http://192.168.2.236:3001', 'difyEmail': 'lingzewanchuan@lzwcai.com', 'difyName': '中科天目', 'difyPassword': 'Lzwc@2025.'}, 'postVOList': [], 'serverConfigToAI': {'id': '1932057383785390081', 'name': '4dc437bf3d2e', 'ipAddr': '192.168.2.236', 'isMajor': None}, 'deviceBindingList': []}} -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:99] - 成功获取企业ID: 1952978233106669569 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:102] - 第二步:检查向量库状态... -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:435] - 检查向量库状态,keyId: 1952978233106669569 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:447] - 向量库状态检查完成,keyId: 1952978233106669569, exists: True -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:104] - 向量库状态检查完成,存在状态: True -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:137] - 向量库已存在,无需创建 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:371] - MCP服务器初始化结果: {'code': 200, 'msg': 'MCP服务器初始化成功,向量库已存在', 'data': {'enterprise_id': '1952978233106669569', 'vector_store_created': False, 'vector_store_existed': True}} -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:376] - MCP服务器初始化成功 -2025-11-07 18:21:20 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:61] - 企业ID已更新: 1952978233106669569 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:381] - 企业ID已保存到环境变量: 1952978233106669569 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:389] - 已注册的MCP工具: -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:390] - 1. iot_get_devices_by_location - 根据位置获取设备列表 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:391] - 2. iot_get_all_spaces_and_devices - 获取所有空间位置信息 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:392] - 3. iot_device_precise_controller - IoT设备精确控制 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:393] - 4. smart_space_device_locator_matcher - 智能空间设备定位 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:395] - ================================================================================ -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:396] - MCP服务器即将启动,等待客户端连接... -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:397] - 传输方式: stdio (标准输入输出) -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:398] - 注意: 控制台日志已禁用,所有日志将写入文件 -2025-11-07 18:21:20 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:399] - ================================================================================ -2025-11-07 18:21:26 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:120] - 开始处理获取所有空间位置信息请求 -2025-11-07 18:21:26 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:131] - 获取到企业ID: 1952978233106669569 -2025-11-07 18:21:26 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:499] - 查询设备列表,keyId: 1952978233106669569, location: -2025-11-07 18:21:26 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:516] - 设备列表查询成功,返回设备数量: 27 -2025-11-07 18:21:26 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:138] - 查询所有设备结果: {'devices': [{'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'climate.qjiang_cn_741479129_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'switch.zimi_cn_1144259387_dhkg01_on_p_2_1', 'device_desc': '灵泽办公区左吊灯 开关 按键', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '33', 'location_desc': '研发办公区走廊过道;研发办公区走廊;研发办公区过道;研发部走廊', 'entityId': 'switch.zimi_cn_1144138206_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'climate.qjiang_cn_741478765_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_3_1', 'device_desc': '办公桌灯;主位灯;吊灯;灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_4_1', 'device_desc': '入口灯;吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'cover.lumi_cn_1132313226_hmcn02_s_2_2', 'device_desc': '左窗帘;窗帘;帘', 'operations': [{'command': 'open_cover', 'operation_desc': '打开;开;展开;开放', 'operation_params': []}, {'command': 'close_cover', 'operation_desc': '关闭;关;合并;合起来;合', 'operation_params': []}, {'command': 'toggle', 'operation_desc': '切换;一键开关', 'operation_params': []}, {'command': 'stop_cover', 'operation_desc': '停止;停;停一下;', 'operation_params': []}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'climate.qjiang_cn_741362991_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'climate.qjiang_cn_741470846_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '37', 'location_desc': '李总办公室;董事长;李总房间;董事长办公室', 'entityId': 'climate.qjiang_cn_741478700_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '38', 'location_desc': '公司前台;前台;前台大门;门口', 'entityId': 'climate.qjiang_cn_741479337_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'climate.qjiang_cn_741352250_wb20', 'device_desc': '空调', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'switch.zimi_cn_1144125565_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '41', 'location_desc': '小会议室;小会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '42', 'location_desc': '电梯走廊;前台门口;公司门口;公司大门口;大门', 'entityId': 'button.zimi_cn_1119697824_dhkg05_toggle_a_4_1', 'device_desc': '吊灯;电梯灯;灯;照明灯', 'operations': [{'command': 'press', 'operation_desc': '按下;按;开关;打开或者关闭;', 'operation_params': []}]}, {'location_key': '43', 'location_desc': '公司大门;大门口;前台;公司前台;大门', 'entityId': 'switch.giot_cn_1110921716_v51ksm_on_p_2_1', 'device_desc': '大门开关', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_3_1', 'device_desc': '射灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '45', 'location_desc': '会议室过道;会议室走廊;会议区过道', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_5_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '46', 'location_desc': '爱易拍展厅;爱一拍展厅;爱一拍展区;爱一排展区', 'entityId': 'switch.zimi_cn_1144256905_dhkg02_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '47', 'location_desc': '灵泽展厅过道;灵泽展厅走廊', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'climate.qjiang_cn_741348975_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '49', 'location_desc': '休息室二楼;休息室二层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '50', 'location_desc': '前台休息区', 'entityId': 'switch.zimi_cn_1119697824_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}], 'count': 27, 'location_filter': None} -2025-11-07 18:21:26 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:189] - 已获取所有空间位置信息,空间数: 51 -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:215] - ================================================================================ -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:216] - 日志系统初始化完成 - 2025-11-07 18:22:56 -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:217] - 日志级别: INFO -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:218] - 日志文件: E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai-mcp-server-package\lzwcai_mcp_iot\lzwcai_mcp_iot.log -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:219] - 控制台输出: False -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:220] - 文件输出: True -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:221] - 文件轮转: 最大10MB, 保留5个备份 -2025-11-07 18:22:56 - lzwcai_mcp_iot - INFO - [logger_config.py:222] - ================================================================================ -2025-11-07 18:22:56 - lzwcai_mcp_iot.config - INFO - [config.py:89] - 配置加载完成 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:44] - DeviceOperator初始化完成,API基础URL: http://192.168.2.236:8088 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:39] - VectorService初始化完成,API基础URL: http://192.168.2.236:5002 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:348] - ================================================================================ -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:349] - IoT设备MCP服务器启动流程开始 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:350] - 配置信息: {'device_api_base_url': 'http://192.168.2.236:8088', 'vector_api_base_url': 'http://192.168.2.236:5002', 'enterprise_id': '1952978233106669569', 'employeeId': '1986712221817815042', 'request_timeout': 30, 'max_retries': 3, 'vector_store_name': '设备库', 'vector_store_description': '向量库', 'encoder_type': 'word2vec', 'default_top_k': 1, 'default_auto_create': True, 'log_level': 'INFO', 'log_format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'} -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:351] - ================================================================================ -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:354] - 正在初始化核心组件... -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:355] - 设备操作器API地址: http://192.168.2.236:8088 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:356] - 向量服务API地址: http://192.168.2.236:5002 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:357] - 核心组件初始化完成 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:362] - 检测到员工ID配置: 1986712221817815042 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:363] - 开始初始化MCP服务器... -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:75] - 开始初始化MCP服务器,员工ID: 1986712221817815042 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:78] - 第一步:获取企业ID... -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:410] - 正在获取员工ID为 1986712221817815042 的数字员工信息... -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:418] - 成功获取数字员工信息: {'msg': '操作成功', 'code': 200, 'data': {'userInfo': {'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'remark': None, 'userId': '1909', 'enterpriseId': '1952978233106669569', 'deptId': None, 'userName': '物联网设备管家4628', 'nickName': '物联网设备管家4628', 'userType': '03', 'email': '', 'phonenumber': '', 'sex': '2', 'avatar': '', 'password': '123456', 'status': '0', 'delFlag': '0', 'loginIp': '', 'loginDate': None, 'createSubject': False, 'createSpeakSubject': False, 'bizSysId': None, 'dept': None, 'roles': [], 'roleIds': None, 'postIds': None, 'roleId': None, 'enterprise': {'id': None, 'createBy': '', 'createTime': '2025-11-07 16:27:59', 'updateBy': None, 'updateTime': None, 'enterpriseName': None, 'enterpriseCode': None, 'description': None, 'type': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': None, 'datasetName': None, 'datasetDescribe': None, 'oaType': None, 'oaMap': None, 'status': False}, 'bindOA': False, 'oaMap': None, 'spaceName': None, 'admin': False}, 'employeeInfo': {'id': '1986712221817815042', 'enterpriseId': '1952978233106669569', 'name': '物联网设备管家', 'type': 'assistant', 'imageInfo': None, 'personality': None, 'prompt': '管理设备', 'modelConfig': None, 'promptConfig': None, 'datasetId': '166a9087-830c-443f-81f9-924f8b089530', 'datasetApiKey': 'dataset-nNE2K1KSeagOQnCJjLQzXbpH', 'zhipuApiKey': '12334444', 'enableLongTermMemory': False}, 'enterpriseVO': {'enterpriseId': '1952978233106669569', 'enterpriseName': '中科天目', 'enterpriseCode': '002233', 'type': '人工智能', 'description': None, 'businessLicense': None, 'licenseFileId': None, 'legalPerson': None, 'industryInvolved': None, 'datasetId': 'ec8b1391-d093-4b3c-a675-5c88455f094c', 'datasetName': '中科天目', 'datasetDescribe': '系统默认创建', 'difyHost': 'http://192.168.2.236:3001', 'difyEmail': 'lingzewanchuan@lzwcai.com', 'difyName': '中科天目', 'difyPassword': 'Lzwc@2025.'}, 'postVOList': [], 'serverConfigToAI': {'id': '1932057383785390081', 'name': '4dc437bf3d2e', 'ipAddr': '192.168.2.236', 'isMajor': None}, 'deviceBindingList': []}} -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:99] - 成功获取企业ID: 1952978233106669569 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:102] - 第二步:检查向量库状态... -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:435] - 检查向量库状态,keyId: 1952978233106669569 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:447] - 向量库状态检查完成,keyId: 1952978233106669569, exists: True -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:104] - 向量库状态检查完成,存在状态: True -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.init_mcp - INFO - [init_mcp.py:137] - 向量库已存在,无需创建 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:366] - MCP服务器初始化结果: {'code': 200, 'msg': 'MCP服务器初始化成功,向量库已存在', 'data': {'enterprise_id': '1952978233106669569', 'vector_store_created': False, 'vector_store_existed': True}} -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:371] - MCP服务器初始化成功 -2025-11-07 18:22:56 - lzwcai_mcp_iot.src.device_operations - INFO - [device_operations.py:61] - 企业ID已更新: 1952978233106669569 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:376] - 企业ID已保存到环境变量: 1952978233106669569 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:384] - 已注册的MCP工具: -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:385] - 1. iot_get_devices_by_location - 根据位置获取设备列表 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:386] - 2. iot_get_all_spaces_and_devices - 获取所有空间位置信息 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:387] - 3. iot_device_precise_controller - IoT设备精确控制 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:388] - 4. smart_space_device_locator_matcher - 智能空间设备定位 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:390] - ================================================================================ -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:391] - MCP服务器即将启动,等待客户端连接... -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:392] - 传输方式: stdio (标准输入输出) -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:393] - 注意: 控制台日志已禁用,所有日志将写入文件 -2025-11-07 18:22:56 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:394] - ================================================================================ -2025-11-07 18:23:00 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:119] - 开始处理获取所有空间位置信息请求 -2025-11-07 18:23:00 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:130] - 获取到企业ID: 1952978233106669569 -2025-11-07 18:23:00 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:499] - 查询设备列表,keyId: 1952978233106669569, location: -2025-11-07 18:23:00 - lzwcai_mcp_iot.src.vector_service - INFO - [vector_service.py:516] - 设备列表查询成功,返回设备数量: 27 -2025-11-07 18:23:00 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:137] - 查询所有设备结果: {'devices': [{'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'climate.qjiang_cn_741479129_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '32', 'location_desc': '研发办公区;研发部', 'entityId': 'switch.zimi_cn_1144259387_dhkg01_on_p_2_1', 'device_desc': '灵泽办公区左吊灯 开关 按键', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '33', 'location_desc': '研发办公区走廊过道;研发办公区走廊;研发办公区过道;研发部走廊', 'entityId': 'switch.zimi_cn_1144138206_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'climate.qjiang_cn_741478765_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_3_1', 'device_desc': '办公桌灯;主位灯;吊灯;灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'switch.zimi_cn_1119705212_dhkg05_on_p_4_1', 'device_desc': '入口灯;吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '34', 'location_desc': '何总办公室;总经办;总经房间', 'entityId': 'cover.lumi_cn_1132313226_hmcn02_s_2_2', 'device_desc': '左窗帘;窗帘;帘', 'operations': [{'command': 'open_cover', 'operation_desc': '打开;开;展开;开放', 'operation_params': []}, {'command': 'close_cover', 'operation_desc': '关闭;关;合并;合起来;合', 'operation_params': []}, {'command': 'toggle', 'operation_desc': '切换;一键开关', 'operation_params': []}, {'command': 'stop_cover', 'operation_desc': '停止;停;停一下;', 'operation_params': []}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'climate.qjiang_cn_741362991_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '35', 'location_desc': '灵泽展厅;灵泽展区', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'climate.qjiang_cn_741470846_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '36', 'location_desc': '大会议室;大会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '37', 'location_desc': '李总办公室;董事长;李总房间;董事长办公室', 'entityId': 'climate.qjiang_cn_741478700_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '38', 'location_desc': '公司前台;前台;前台大门;门口', 'entityId': 'climate.qjiang_cn_741479337_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'climate.qjiang_cn_741352250_wb20', 'device_desc': '空调', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '40', 'location_desc': '健身房;运动区;健身室;健', 'entityId': 'switch.zimi_cn_1144125565_dhkg01_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '41', 'location_desc': '小会议室;小会议区', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '42', 'location_desc': '电梯走廊;前台门口;公司门口;公司大门口;大门', 'entityId': 'button.zimi_cn_1119697824_dhkg05_toggle_a_4_1', 'device_desc': '吊灯;电梯灯;灯;照明灯', 'operations': [{'command': 'press', 'operation_desc': '按下;按;开关;打开或者关闭;', 'operation_params': []}]}, {'location_key': '43', 'location_desc': '公司大门;大门口;前台;公司前台;大门', 'entityId': 'switch.giot_cn_1110921716_v51ksm_on_p_2_1', 'device_desc': '大门开关', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '44', 'location_desc': '董事长办公室门口走廊;董事长门口走廊;董事长门口走廊过道', 'entityId': 'switch.huca_cn_1134957033_lh4_on_p_3_1', 'device_desc': '射灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '45', 'location_desc': '会议室过道;会议室走廊;会议区过道', 'entityId': 'switch.huca_cn_1134958712_lh4_on_p_5_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '46', 'location_desc': '爱易拍展厅;爱一拍展厅;爱一拍展区;爱一排展区', 'entityId': 'switch.zimi_cn_1144256905_dhkg02_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '47', 'location_desc': '灵泽展厅过道;灵泽展厅走廊', 'entityId': 'switch.zimi_cn_1121232402_dhkg05_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'climate.qjiang_cn_741348975_wb20', 'device_desc': '空调;制冷设备', 'operations': [{'command': 'turn_on', 'operation_desc': '打开空调;开空调;打开;开', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关空调;关闭空调;关闭;关;闭空调;空调', 'operation_params': []}, {'command': 'set_temperature', 'operation_desc': '设置温度;调节温度;', 'operation_params': [{'description': '温度', 'key': 'temperature', 'value': '7-35°C'}, {'description': '运行模式', 'key': 'hvac_mode', 'value': 'heat/cool/auto/dry/fan_only/off'}]}]}, {'location_key': '48', 'location_desc': '休息室一楼;休息室一层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '49', 'location_desc': '休息室二楼;休息室二层', 'entityId': 'switch.huca_cn_1134958682_lh4_on_p_3_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}, {'location_key': '50', 'location_desc': '前台休息区', 'entityId': 'switch.zimi_cn_1119697824_dhkg05_on_p_2_1', 'device_desc': '吊灯;灯;照明灯', 'operations': [{'command': 'turn_on', 'operation_desc': '开启;打开;开;', 'operation_params': []}, {'command': 'turn_off', 'operation_desc': '关闭;关', 'operation_params': []}]}], 'count': 27, 'location_filter': None} -2025-11-07 18:23:00 - lzwcai_mcp_iot.iot_device_tool - INFO - [iot_device_tool.py:184] - 已获取所有空间位置信息,空间数: 18 diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/__init__.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index c9add4f..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/config.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/config.cpython-312.pyc deleted file mode 100644 index 5bff17d..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/config.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_main.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_main.cpython-312.pyc deleted file mode 100644 index 8f71c3f..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_main.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_tool.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_tool.cpython-312.pyc deleted file mode 100644 index ea46709..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_tool.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/apimock.json b/lzwcai_mcp_iot/lzwcai_mcp_iot/apimock.json deleted file mode 100644 index 6c44aa9..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/apimock.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "code": 200, - "data": { - "devices": [ - { - "location_key": "32", - "location_desc": "研发办公区;研发部", - "entityId": "climate.qjiang_cn_741479129_wb20", - "device_desc": "空调;制冷设备", - "operations": [ - { - "command": "turn_on", - "operation_desc": "打开空调;开空调;打开;开", - "operation_params": [] - }, - { - "command": "turn_off", - "operation_desc": "关空调;关闭空调;关闭;关;闭空调;空调", - "operation_params": [] - }, - { - "command": "set_temperature", - "operation_desc": "设置温度;调节温度;", - "operation_params": [ - { - "description": "温度", - "key": "temperature", - "value": "7-35°C" - }, - { - "description": "运行模式", - "key": "hvac_mode", - "value": "heat/cool/auto/dry/fan_only/off" - } - ] - } - ] - } - ], - "count": 1, - "location_filter": "爱一拍展厅" - }, - "msg": "成功获取公司 1952978233106669569 在位置 '爱一拍展厅' 的 2 个设备" -} \ No newline at end of file diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/config.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/config.py deleted file mode 100644 index 003411a..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/config.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -配置文件 - -该模块包含了项目的所有配置常量和设置。 -""" - -import os -from typing import Dict, Any -from .src.logger_config import get_logger - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - -# 生产环境 -DEFAULT_DEVICE_API_BASE_URL = "http://lzwcai-demp-corp-manager:8086" -DEFAULT_VECTOR_API_BASE_URL = "http://lzwcai-demp-tool-server:5002" -# 本地环境 -# DEFAULT_DEVICE_API_BASE_URL = "http://192.168.2.236:8088" -# DEFAULT_VECTOR_API_BASE_URL = "http://192.168.2.236:5002" - - -# 默认企业ID -# DEFAULT_ENTERPRISE_ID = "1952978233106669569" -DEFAULT_ENTERPRISE_ID = "" - -# 默认员工ID -# DEFAULT_EMPLOYEE_ID = "1955949384389005313" -DEFAULT_EMPLOYEE_ID = "" - -# 请求配置 -REQUEST_TIMEOUT = 30 # 请求超时时间(秒) -MAX_RETRIES = 3 # 最大重试次数 - -# 向量服务配置 -DEFAULT_VECTOR_STORE_NAME = "设备库" -DEFAULT_VECTOR_STORE_DESCRIPTION = "向量库" -DEFAULT_ENCODER_TYPE = "word2vec" -DEFAULT_TOP_K = 1 -DEFAULT_AUTO_CREATE = True - -# 日志配置 -LOG_LEVEL = "INFO" -LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - - -# 环境变量配置 -def get_config() -> Dict[str, Any]: - """ - 获取配置信息,支持环境变量覆盖 - - 返回: - Dict[str, Any]: 配置字典 - """ - logger = _ensure_logger() - logger.debug("开始加载配置...") - - config = { - "device_api_base_url": os.getenv( - "DEVICE_API_BASE_URL", DEFAULT_DEVICE_API_BASE_URL - ), - "vector_api_base_url": os.getenv( - "VECTOR_API_BASE_URL", DEFAULT_VECTOR_API_BASE_URL - ), - "enterprise_id": os.getenv("enterpriseId", DEFAULT_ENTERPRISE_ID), - "employeeId": os.getenv("employeeId", DEFAULT_EMPLOYEE_ID), - "request_timeout": int(os.getenv("REQUEST_TIMEOUT", REQUEST_TIMEOUT)), - "max_retries": int(os.getenv("MAX_RETRIES", MAX_RETRIES)), - "vector_store_name": os.getenv("VECTOR_STORE_NAME", DEFAULT_VECTOR_STORE_NAME), - "vector_store_description": os.getenv( - "VECTOR_STORE_DESCRIPTION", DEFAULT_VECTOR_STORE_DESCRIPTION - ), - "encoder_type": os.getenv("ENCODER_TYPE", DEFAULT_ENCODER_TYPE), - "default_top_k": int(os.getenv("DEFAULT_TOP_K", DEFAULT_TOP_K)), - "default_auto_create": os.getenv( - "DEFAULT_AUTO_CREATE", str(DEFAULT_AUTO_CREATE) - ).lower() - == "true", - "log_level": os.getenv("LOG_LEVEL", LOG_LEVEL), - "log_format": os.getenv("LOG_FORMAT", LOG_FORMAT), - } - - logger.info("配置加载完成") - logger.debug(f"设备API地址: {config['device_api_base_url']}") - logger.debug(f"向量API地址: {config['vector_api_base_url']}") - logger.debug(f"员工ID: {config['employeeId']}") - logger.debug(f"请求超时: {config['request_timeout']}秒") - - return config - - -# 全局配置实例 -CONFIG = get_config() diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/iot_device_tool.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/iot_device_tool.py deleted file mode 100644 index 2a86770..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/iot_device_tool.py +++ /dev/null @@ -1,410 +0,0 @@ -""" -IoT设备服务器主模块 - -该模块实现了一个简单的IoT设备控制服务器,使用FastMCP框架提供设备操作功能。 -""" - -import os -import json -from mcp.server.fastmcp import FastMCP -from .src.device_results_pretreatment import ( - format_devices_list, -) -from .src.device_operations import DeviceOperator -from .src.vector_service import VectorService -from .src.logger_config import get_logger -from .config import CONFIG -from .src.iot_device_dicts_prompt import ( - iot_get_devices_by_location_prompt, - iot_get_all_spaces_and_devices_prompt, - iot_device_precise_controller_prompt, - smart_space_device_locator_matcher_prompt, -) -from .src.init_mcp import init_mcp_server - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - -# 创建FastMCP实例 -mcp = FastMCP("iot_device_server") -# 创建设备操作实例 -device_op = DeviceOperator(api_base_url=CONFIG["device_api_base_url"]) -# 创建向量服务实例 -vector_service = VectorService(base_url=CONFIG["vector_api_base_url"]) - - -@mcp.tool(description=iot_get_devices_by_location_prompt()) -async def iot_get_devices_by_location( - location: str, -) -> str: - logger = _ensure_logger() - try: - # 输入参数验证 - if not location: - error_msg = "location参数是必需的" - logger.error(error_msg) - return json.dumps( - {"code": 400, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - logger.info( - f"开始处理IoT设备查询请求 - 位置: {location}" - ) - - # 从环境变量获取企业ID(已在启动时初始化) - enterprise_id = os.environ.get("enterpriseId") - if not enterprise_id: - error_msg = f"企业ID未初始化,请检查员工ID配置" - logger.error(error_msg) - return json.dumps( - {"code": 404, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - logger.info(f"获取到企业ID: {enterprise_id}") - - # 查询设备列表 - query_result = vector_service.query_devices_by_location( - keyId=enterprise_id, - location=location - ) - logger.info(f"查询设备列表结果: {query_result}") - - # 提取设备列表 - devices = query_result.get("devices", []) - device_count = query_result.get("count", 0) - - if device_count == 0 or not devices: - return json.dumps({ - "code": 404, - "msg": f"在位置「{location}」未找到任何设备", - "data": None - }, ensure_ascii=False) - - # 使用格式化函数将设备列表转换为易读的文本 - formatted_text = format_devices_list(devices) - logger.info(f"设备列表已格式化,设备数量: {device_count}") - - # 返回包含格式化文本和原始数据的结果 - result = { - "code": 200, - "msg": formatted_text, # 直接将格式化文本放在msg字段 - "data": { - "devices": devices, # 原始设备数据,供后续操作使用 - "count": device_count, - "location_filter": query_result.get("location_filter", location) - } - } - - return json.dumps(result, ensure_ascii=False) - - except Exception as e: - error_msg = f"IoT设备查询过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return json.dumps( - {"code": 500, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - -@mcp.tool(description=iot_get_all_spaces_and_devices_prompt()) -async def iot_get_all_spaces_and_devices() -> str: - logger = _ensure_logger() - try: - logger.info("开始处理获取所有空间位置信息请求") - - # 从环境变量获取企业ID(已在启动时初始化) - enterprise_id = os.environ.get("enterpriseId") - if not enterprise_id: - error_msg = f"企业ID未初始化,请检查员工ID配置" - logger.error(error_msg) - return json.dumps( - {"code": 404, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - logger.info(f"获取到企业ID: {enterprise_id}") - - # 查询所有设备(不指定位置,获取所有) - query_result = vector_service.query_devices_by_location( - keyId=enterprise_id, - location="" # 空字符串表示获取所有 - ) - logger.info(f"查询所有设备结果: {query_result}") - - # 提取设备列表 - devices = query_result.get("devices", []) - device_count = query_result.get("count", 0) - - if device_count == 0 or not devices: - return json.dumps({ - "code": 404, - "msg": "系统中未找到任何设备和空间", - "data": None - }, ensure_ascii=False) - - # 提取所有唯一的空间名称 - spaces_set = set() - for device in devices: - # 提取空间信息 - location_desc = device.get("location_desc", "") - if location_desc and location_desc != "未知空间": - spaces_set.add(location_desc) - - # 转换为列表并排序 - spaces_list = sorted(list(spaces_set)) - - if not spaces_list: - return json.dumps({ - "code": 404, - "msg": "系统中未找到任何有效空间", - "data": None - }, ensure_ascii=False) - - # 构建格式化文本 - formatted_lines = [] - formatted_lines.append("=" * 60) - formatted_lines.append(f"所有空间位置信息") - formatted_lines.append("=" * 60) - formatted_lines.append(f"空间总数: {len(spaces_list)} 个") - formatted_lines.append("") - - # 列出所有空间 - for space_idx, space_name in enumerate(spaces_list, 1): - formatted_lines.append(f"{space_idx}. {space_name}") - - formatted_lines.append("") - formatted_lines.append("=" * 60) - formatted_text = "\n".join(formatted_lines) - - logger.info(f"已获取所有空间位置信息,空间数: {len(spaces_list)}") - - # 返回包含格式化文本和空间列表的结果 - result = { - "code": 200, - "msg": formatted_text, - "data": { - "spaces": spaces_list, # 空间名称列表 - "space_count": len(spaces_list) - } - } - - return json.dumps(result, ensure_ascii=False) - - except Exception as e: - error_msg = f"获取所有空间位置信息过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return json.dumps( - {"code": 500, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - -@mcp.tool(description=iot_device_precise_controller_prompt()) -async def iot_device_precise_controller( - entityId: str, - command: str, - params: dict = None, - userId: str = None, -) -> str: - logger = _ensure_logger() - logger.info("=" * 60) - logger.info("调用iot_device_precise_controller工具") - logger.info(f"参数 - entityId: {entityId}, command: {command}, params: {params}, userId: {userId}") - logger.info("=" * 60) - - try: - # 输入参数验证 - logger.debug("开始参数验证...") - if not all([entityId, command]): - error_msg = "所有参数都是必需的: entityId,command" - logger.error(f"参数验证失败: {error_msg}") - return json.dumps( - {"code": 400, "msg": error_msg, "data": None}, ensure_ascii=False - ) - logger.debug("参数验证通过") - - # 从环境变量获取企业ID(已在启动时初始化) - enterprise_id = os.environ.get("enterpriseId") - if not enterprise_id: - error_msg = f"企业ID未初始化,请检查员工ID配置" - logger.error(error_msg) - return json.dumps( - {"code": 404, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - logger.info(f"获取到企业ID: {enterprise_id}") - - map_result = { - "enterpriseId": enterprise_id, - "entityId": entityId, - "command": command, - "params": params if params is not None else {}, - } - # 如果提供了userId,则添加到map_result中 - if userId: - map_result["userId"] = userId - # 操作设备 - result = device_op.operate_device(map_result) - logger.info(f"设备操作结果: {result}") - - # 将结果字典转换为JSON字符串 - if isinstance(result, dict): - result = json.dumps(result, ensure_ascii=False) - - logger.info("iot_device_precise_controller工具执行完成") - logger.info("=" * 60) - return result - - except Exception as e: - error_msg = f"IoT设备操作过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - logger.error("iot_device_precise_controller工具执行失败") - logger.error("=" * 60) - return json.dumps( - {"code": 500, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - - -@mcp.tool(description=smart_space_device_locator_matcher_prompt()) -async def smart_space_device_locator_matcher( - userId: str, -) -> str: - logger = _ensure_logger() - try: - if not userId: - error_msg = "所有参数都是必需的: userId" - logger.error(error_msg) - return json.dumps( - {"code": 400, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - result = device_op.get_user_spaces_by_user_id(userId) - if isinstance(result, dict): - # 检查返回的data是否为None,如果是则提供友好的描述 - if result.get("data") is None: - result["msg"] = "我目前还没发现您在哪里 请您告诉我你所属位置;" - return json.dumps(result, ensure_ascii=False) - return result - except Exception as e: - error_msg = f"定位空间过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return json.dumps( - {"code": 500, "msg": error_msg, "data": None}, ensure_ascii=False - ) - -def testFn() -> str: - logger = _ensure_logger() - try: - # 读取test.json文件 - test_file_path = r"E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_iot\test.json" - with open(test_file_path, "r", encoding="utf-8") as f: - test_data = json.load(f) - - # 获取results数据 - results = test_data.get("results", []) - logger.info(f"从test.json读取到的results数据: {results}") - - # 参数预处理 - map_result = device_op.preprocess_results(results) - logger.info(f"参数预处理结果: {map_result}") - - # 保存map_result到本地JSON文件 - output_file_path = r"E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_iot\map_result.json" - with open(output_file_path, "w", encoding="utf-8") as f: - json.dump(map_result, f, ensure_ascii=False, indent=2) - logger.info(f"map_result已保存到: {output_file_path}") - - return json.dumps(map_result, ensure_ascii=False) - - except FileNotFoundError: - error_msg = "test.json文件未找到" - logger.error(error_msg) - return json.dumps( - {"code": 404, "msg": error_msg, "data": None}, ensure_ascii=False - ) - except json.JSONDecodeError as e: - error_msg = f"test.json文件格式错误: {str(e)}" - logger.error(error_msg) - return json.dumps( - {"code": 400, "msg": error_msg, "data": None}, ensure_ascii=False - ) - except Exception as e: - error_msg = f"测试过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return json.dumps( - {"code": 500, "msg": error_msg, "data": None}, ensure_ascii=False - ) - - -def main(): - """主函数,启动MCP服务器""" - logger = _ensure_logger() - try: - logger.info("=" * 80) - logger.info("IoT设备MCP服务器启动流程开始") - logger.info(f"配置信息: {CONFIG}") - logger.info("=" * 80) - - # 初始化组件 - logger.info("正在初始化核心组件...") - logger.info(f"设备操作器API地址: {CONFIG['device_api_base_url']}") - logger.info(f"向量服务API地址: {CONFIG['vector_api_base_url']}") - logger.info("核心组件初始化完成") - - # 获取企业ID并初始化MCP服务器 - enterprise_id = CONFIG.get("enterprise_id") - if enterprise_id: - logger.info(f"检测到企业ID配置: {enterprise_id}") - logger.info("开始初始化MCP服务器...") - - init_result = init_mcp_server(device_op, vector_service, enterprise_id) - logger.info(f"MCP服务器初始化结果: {init_result}") - - if init_result.get("code") != 200: - logger.warning(f"MCP服务器初始化失败,但服务器仍将启动: {init_result}") - else: - logger.info("MCP服务器初始化成功") - # 保存企业ID到环境变量 - returned_enterprise_id = init_result.get("data", {}).get("enterprise_id") - if returned_enterprise_id: - device_op.set_enterprise_id_to_env(returned_enterprise_id) - logger.info(f"企业ID已保存到环境变量: {returned_enterprise_id}") - else: - # 如果返回结果中没有enterprise_id,使用配置中的 - device_op.set_enterprise_id_to_env(enterprise_id) - logger.info(f"企业ID已保存到环境变量: {enterprise_id}") - else: - logger.warning("未配置企业ID,跳过预初始化") - logger.info("提示:您可以在配置文件或环境变量中设置ENTERPRISE_ID来启用自动初始化功能") - - # 注册的工具列表日志 - logger.info("已注册的MCP工具:") - logger.info("1. iot_get_devices_by_location - 根据位置获取设备列表") - logger.info("2. iot_get_all_spaces_and_devices - 获取所有空间位置信息") - logger.info("3. iot_device_precise_controller - IoT设备精确控制") - logger.info("4. smart_space_device_locator_matcher - 智能空间设备定位") - - logger.info("=" * 80) - logger.info("MCP服务器即将启动,等待客户端连接...") - logger.info("传输方式: stdio (标准输入输出)") - logger.info("注意: 控制台日志已禁用,所有日志将写入文件") - logger.info("=" * 80) - - # 使用标准输入输出作为传输方式运行服务器 - mcp.run(transport="stdio") - - except Exception as e: - logger.error("=" * 80) - logger.error(f"服务器启动失败: {str(e)}") - logger.error("错误详情:", exc_info=True) - logger.error("=" * 80) - raise - - -if __name__ == "__main__": - main() diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__init__.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/__init__.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index daef1c6..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_operations.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_operations.cpython-312.pyc deleted file mode 100644 index a3762f8..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_operations.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_results_pretreatment.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_results_pretreatment.cpython-312.pyc deleted file mode 100644 index 2f3bc99..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_results_pretreatment.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/init_mcp.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/init_mcp.cpython-312.pyc deleted file mode 100644 index 445d6cb..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/init_mcp.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/iot_device_dicts_prompt.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/iot_device_dicts_prompt.cpython-312.pyc deleted file mode 100644 index ad272c0..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/iot_device_dicts_prompt.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/logger_config.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/logger_config.cpython-312.pyc deleted file mode 100644 index 1812d54..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/logger_config.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/vector_service.cpython-312.pyc b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/vector_service.cpython-312.pyc deleted file mode 100644 index 4ed0a25..0000000 Binary files a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/vector_service.cpython-312.pyc and /dev/null differ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_operations.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_operations.py deleted file mode 100644 index 1ded4f8..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_operations.py +++ /dev/null @@ -1,751 +0,0 @@ -""" -设备操作模块 - -该模块提供了设备操作的接口,用于控制不同位置的各种设备。 -""" - -import os -import requests -import json -from typing import Dict, Any, Optional, List -from ..config import CONFIG -from .device_results_pretreatment import process_device_data, process_device_results -from .logger_config import get_logger - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - -enterprise_id = os.environ.get("enterpriseId", CONFIG["enterprise_id"]) - - -class DeviceOperator: - """设备操作类,提供设备控制功能""" - - def __init__(self, api_base_url: str = None): - """ - 初始化设备操作器 - - 参数: - api_base_url: API服务的基础URL - """ - logger = _ensure_logger() - self.api_base_url = api_base_url or CONFIG["device_api_base_url"] - self.session = requests.Session() - # 设置默认超时 - self.session.timeout = CONFIG["request_timeout"] - - logger.info(f"DeviceOperator初始化完成,API基础URL: {self.api_base_url}") - - def set_enterprise_id_to_env(self, enterprise_id_value: str) -> None: - """ - 将企业ID存入环境变量 - - 参数: - enterprise_id_value: 企业ID值 - """ - logger = _ensure_logger() - if not enterprise_id_value or not isinstance(enterprise_id_value, str): - logger.warning(f"无效的企业ID值: {enterprise_id_value}") - return - - os.environ["enterpriseId"] = enterprise_id_value - global enterprise_id - enterprise_id = enterprise_id_value - logger.info(f"企业ID已更新: {enterprise_id_value}") - - def get_enterprise_id_by_user(self, user_id: str) -> Optional[str]: - """ - 根据用户ID获取企业ID并存入环境变量 - - 参数: - user_id: 用户ID - - 返回: - Optional[str]: 企业ID,获取失败时返回None - """ - logger = _ensure_logger() - if not user_id or not isinstance(user_id, str): - logger.error(f"无效的用户ID: {user_id}") - return None - - # 调用实际API获取企业ID - url = f"{self.api_base_url}/system/enterprise/user/{user_id}" - headers = { - "Accept": "*/*", - } - - try: - logger.info(f"正在获取用户 {user_id} 的企业ID...") - response = self.session.get( - url=url, headers=headers, timeout=CONFIG["request_timeout"] - ) - - if response.status_code == 200: - data = response.json() - if ( - data.get("code") == 200 - and data.get("data") - and data["data"].get("id") - ): - enterprise_id_value = str(data["data"]["id"]) - # 将企业ID存入环境变量 - self.set_enterprise_id_to_env(enterprise_id_value) - logger.info(f"成功获取企业ID: {enterprise_id_value}") - return enterprise_id_value - else: - logger.warning(f"API返回数据格式异常: {data}") - else: - logger.error( - f"API请求失败,状态码: {response.status_code}, 响应: {response.text}" - ) - - # 如果API调用失败或数据格式不符预期,返回默认值 - logger.info(f"使用默认企业ID: {enterprise_id}") - return enterprise_id - - except requests.exceptions.Timeout: - logger.error(f"获取企业ID请求超时,用户ID: {user_id}") - return enterprise_id - except requests.exceptions.RequestException as e: - logger.error(f"获取企业ID网络请求异常: {str(e)}") - return enterprise_id - except Exception as e: - logger.error(f"获取企业ID时发生未知异常: {str(e)}", exc_info=True) - return enterprise_id - - # 设备结果分数判断 - def check_device_results_score( - self, best_match_data: Optional[Dict[str, Any]] - ) -> Dict[str, Any]: - """ - 检查设备匹配的准确性 - - 根据你的代码分析,评分系统说明: - - 综合分数(combined_score)或score是最终评判标准 - - 0.85-0.88: 准确匹配,可信度高 (随机阈值) - - 0.7-0.84: 一般匹配,需要确认 - - 0.5-0.69: 较差匹配,不建议使用 - - <0.5: 很差匹配,不推荐 - - Args: - best_match_data: best_match数据,包含device和score信息 - - Returns: - Dict: { - "checkResult": True/False, # 是否满足分数要求(随机阈值0.85-0.88) - "data": data # 满足要求返回对应项,不满足返回整个data - } - """ - logger.debug("开始检查设备匹配准确性...") - - import random - - # 提取分数 - 优先使用combined_score,其次使用score - score = best_match_data.get("combined_score", best_match_data.get("score", 0.0)) - logger.debug(f"提取的匹配分数: {score}") - - # 生成随机阈值 (0.85-0.88之间) - random_threshold = round(random.uniform(0.6, 0.65), 2) - logger.debug(f"生成的随机阈值: {random_threshold}") - - # 判断是否满足准确性要求 (随机阈值-1.0) - is_accurate = random_threshold <= score <= 1.0 - - logger.info(f"设备匹配准确性检查: 分数={score}, 阈值={random_threshold}, 通过={is_accurate}") - - # 构造返回结果 - result = {"checkResult": is_accurate, "data": best_match_data} - return result - - def preprocess_results(self, raw_data: List[List[Any]]) -> List[Dict[str, Any]]: - """ - 预处理数据,将原始数据转换为简化的数组对象格式 - 如果 entityId 相同,则合并到一起 - - Args: - raw_data: 原始数据,格式为 [[entity, score], ...] - - Returns: - 处理后的数据数组,每个对象包含合并后的信息(不包含id和score_details字段) - """ - logger.debug(f"开始预处理设备结果数据,原始数据条数: {len(raw_data) if raw_data else 0}") - - try: - result = process_device_results(raw_data) - logger.info(f"设备结果数据预处理完成,输出条数: {len(result) if result else 0}") - return result - except Exception as e: - logger.error(f"预处理设备结果数据时发生异常: {str(e)}", exc_info=True) - return [] - - def preprocess_parameters( - self, - data: Optional[Dict[str, Any]], - ) -> Dict[str, Any]: - """ - 预处理设备操作参数,使用process_device_data方法处理数据 - - 参数: - data: 需要预处理的数据,包含best_match信息 - 格式如:{ - "best_match": { - "device": { - "device": { - "key": "switch.zimi_cn_1144138206_dhkg01_on_p_2_1", - "description": "灵泽办公区过道吊灯 开关 按键" - }, - "operation": { - "key": "turn_on", - "description": "开关" - } - } - } - } - - 返回: - Dict[str, Any]: 预处理后的设备操作参数 - 格式如:{ - "enterpriseId": "1932095424144715777", - "entityId": "switch.zimi_cn_1144138206_dhkg01_on_p_2_1", - "command": "turn_on" - } - """ - # 默认结果结构 - default_result = {"enterpriseId": enterprise_id, "entityId": "", "command": ""} - - try: - # 使用process_device_data方法处理数据 - if data and isinstance(data, dict): - processed_result = process_device_data(data, enterprise_id) - if processed_result: - logger.info(f"成功处理设备数据: {processed_result}") - return processed_result - else: - logger.warning("process_device_data返回None,使用默认结果") - else: - logger.warning("输入数据为空或格式异常") - - except Exception as e: - logger.error(f"预处理参数时发生异常: {str(e)}", exc_info=True) - - return default_result - - def operate_device(self, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """ - 操作设备的方法 - - 参数: - data: 设备操作数据,包含entityId、command等信息 - - 返回: - Dict[str, Any]: 包含操作结果的字典 - """ - # 初始化操作参数 - if data is None: - data = {} - - # 检查并设置 enterpriseId - if not data.get("enterpriseId"): - data["enterpriseId"] = enterprise_id - logger.info(f"data中未包含enterpriseId,使用默认值: {enterprise_id}") - - # 验证必要参数 - if not data.get("entityId") or not data.get("command"): - error_msg = "缺少必要的设备操作参数: entityId 和 command" - logger.error(error_msg) - return {"code": 400, "msg": error_msg, "data": None} - - logger.info( - f"开始操作设备 - entityId: {data.get('entityId')}, command: {data.get('command')}" - ) - # # 保存map_result到本地JSON文件 - # output_file_path = r"E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_iot\output\data.json" - # with open(output_file_path, "w", encoding="utf-8") as f: - # json.dump(data, f, ensure_ascii=False, indent=2) - # logger.info(f"map_result已保存到: {output_file_path}") - - # 调用API接口 - result = self.call_device_api(data) - # result = {"code": 200, "msg": "操作成功", "data": None} - return result - - def batch_operate_devices(self, devices_data: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - 批量操作设备的方法 - - 参数: - devices_data: 设备操作数据数组,每个元素包含entityId、command等信息 - 数据结构与operate_device方法的data参数相同 - - 返回: - Dict[str, Any]: 批量操作结果,包含成功/失败统计和详细结果 - { - "code": 200, # 整体状态码 - "msg": "批量操作完成", - "data": { - "total": 总数量, - "success": 成功数量, - "failed": 失败数量, - "results": [ - { - "index": 索引, - "deviceInfo": 设备信息, - "result": 操作结果, - "success": True/False - } - ] - } - } - """ - if not devices_data or not isinstance(devices_data, list): - error_msg = "无效的设备数据:devices_data必须是一个非空数组" - logger.error(error_msg) - return {"code": 400, "msg": error_msg, "data": None} - - total_count = len(devices_data) - success_count = 0 - failed_count = 0 - results = [] - - logger.info(f"开始批量操作设备,总数量: {total_count}") - - for index, device_data in enumerate(devices_data): - device_info = { **device_data,"index": index} - - try: - logger.info(f"正在操作第 {index + 1}/{total_count} 个设备: {device_info['entityId']}") - - # 验证设备数据格式 - if not isinstance(device_data, dict): - raise ValueError(f"设备数据格式错误,必须是字典类型") - - # 调用单个设备操作方法 - operation_result = self.operate_device(device_data) - - # 判断操作是否成功(通常code=200表示成功) - is_success = operation_result.get("code") == 200 - - if is_success: - success_count += 1 - logger.info(f"设备操作成功: {device_info['entityId']}") - else: - failed_count += 1 - logger.warning(f"设备操作失败: {device_info['entityId']}, 原因: {operation_result.get('msg', '未知错误')}") - - # 记录结果 - results.append({ - "index": index, - "deviceInfo": device_info, - "result": operation_result, - "success": is_success - }) - - except Exception as e: - failed_count += 1 - error_msg = f"设备操作异常: {str(e)}" - logger.error(f"第 {index + 1} 个设备操作异常: {error_msg}", exc_info=True) - - # 记录异常结果 - results.append({ - "index": index, - "deviceInfo": device_info, - "result": {"code": 500, "msg": error_msg, "data": None}, - "success": False - }) - - # 构建返回结果 - summary_msg = f"批量操作完成,总数: {total_count}, 成功: {success_count}, 失败: {failed_count}" - logger.info(summary_msg) - - # 整体状态码判断:如果全部成功则返回200,部分成功返回206,全部失败返回500 - if success_count == total_count: - overall_code = 200 - overall_msg = "批量操作全部成功" - elif success_count > 0: - overall_code = 206 # 部分成功 - overall_msg = f"批量操作部分成功,成功: {success_count}, 失败: {failed_count}" - else: - overall_code = 500 - overall_msg = "批量操作全部失败" - - return { - "code": overall_code, - "msg": overall_msg, - "data": { - "total": total_count, - "success": success_count, - "failed": failed_count, - "success_rate": round(success_count / total_count * 100, 2) if total_count > 0 else 0, - "results": results - } - } - - def get_digital_employee_by_id(self, employee_id: str) -> Dict[str, Any]: - """ - 根据员工ID获取数字员工信息 - - 参数: - employee_id: 员工ID - - 返回: - Dict[str, Any]: 包含数字员工信息的字典 - """ - if not employee_id or not isinstance(employee_id, str): - error_msg = f"无效的员工ID: {employee_id}" - logger.error(error_msg) - return {"code": 400, "msg": error_msg, "data": None} - - # API配置 - url = f"{self.api_base_url}/system/mcpServer/getByEmployeeId/{employee_id}" - headers = {} - - try: - logger.info(f"正在获取员工ID为 {employee_id} 的数字员工信息...") - response = self.session.get( - url=url, headers=headers, timeout=CONFIG["request_timeout"] - ) - - # 处理响应 - if response.status_code == 200: - data = response.json() - logger.info(f"成功获取数字员工信息: {data}") - return data - else: - error_msg = f"API请求失败,状态码: {response.status_code}, 响应: {response.text}" - logger.error(error_msg) - return { - "code": response.status_code, - "msg": error_msg, - "data": None, - } - - except requests.exceptions.Timeout: - error_msg = f"获取数字员工信息请求超时,员工ID: {employee_id}" - logger.error(error_msg) - return {"code": 408, "msg": error_msg, "data": None} - except requests.exceptions.RequestException as e: - error_msg = f"获取数字员工信息网络请求异常: {str(e)}" - logger.error(error_msg) - return {"code": 500, "msg": error_msg, "data": None} - except Exception as e: - error_msg = f"获取数字员工信息时发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return {"code": 500, "msg": error_msg, "data": None} - - def get_user_spaces_by_user_id(self, user_id: str) -> Dict[str, Any]: - """ - 根据用户ID获取该用户关联的空间信息 - - 参数: - user_id: 用户ID - - 返回: - Dict[str, Any]: 接口响应结果,结构为 {"code": number, "msg": string, "data": any} - """ - if not user_id or not isinstance(user_id, str): - error_msg = f"无效的用户ID: {user_id}" - logger.error(error_msg) - return {"code": 400, "msg": error_msg, "data": None} - - url = f"{self.api_base_url}/system/mcpServer/space/user/{user_id}" - headers = { - "Accept": "*/*", - } - - try: - logger.info(f"正在获取用户ID为 {user_id} 的空间信息...") - response = self.session.get( - url=url, headers=headers, timeout=CONFIG["request_timeout"] - ) - - if response.status_code == 200: - try: - data = response.json() - except ValueError: - data = {} - - if isinstance(data, dict): - # 统一返回格式,确保存在 code/msg/data 字段 - if "code" not in data: - data["code"] = 200 - if "msg" not in data: - data["msg"] = "success" - # 接口可能没有返回 data 时,显式置为 None - if "data" not in data or data["data"] in (None, [], {}): - data["data"] = None - logger.info(f"成功获取用户空间信息: {data}") - return data - else: - wrapped = {"code": 200, "msg": "success", "data": None} - logger.info(f"成功获取用户空间信息(非字典响应已包装): {wrapped}") - return wrapped - else: - error_msg = f"API请求失败,状态码: {response.status_code}, 响应: {response.text}" - logger.error(error_msg) - return { - "code": response.status_code, - "msg": error_msg, - "data": None, - } - - except requests.exceptions.Timeout: - error_msg = f"获取用户空间信息请求超时,用户ID: {user_id}" - logger.error(error_msg) - return {"code": 408, "msg": error_msg, "data": None} - except requests.exceptions.RequestException as e: - error_msg = f"获取用户空间信息网络请求异常: {str(e)}" - logger.error(error_msg) - return {"code": 500, "msg": error_msg, "data": None} - except Exception as e: - error_msg = f"获取用户空间信息时发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return {"code": 500, "msg": error_msg, "data": None} - - def call_device_api(self, data: Dict[str, Any]) -> Dict[str, Any]: - """ - 调用设备操作API - - 参数: - data: 设备操作数据,包含entityId、command等信息 - - 返回: - Dict[str, Any]: API调用结果 - """ - # API配置 - url = f"{self.api_base_url}/space_iot/operation/call" - headers = { - "Content-Type": "application/json", - } - - # 构建请求数据 - request_data = {**data} - - try: - logger.info(f"调用设备操作API: {url}") - logger.debug(f"请求数据: {request_data}") - - # 发送POST请求 - response = self.session.post( - url=url, - headers=headers, - data=json.dumps(request_data), - timeout=CONFIG["request_timeout"], - ) - - # 处理响应 - if response.status_code == 200: - result = response.json() - logger.info(f"设备操作API调用成功: {result}") - return result - else: - error_msg = f"API请求失败,状态码: {response.status_code}, 响应: {response.text}" - logger.error(error_msg) - return { - "code": response.status_code, - "msg": error_msg, - "data": None, - } - - except requests.exceptions.Timeout: - error_msg = "设备操作API请求超时" - logger.error(error_msg) - return {"code": 408, "msg": error_msg, "data": None} - except requests.exceptions.RequestException as e: - error_msg = f"设备操作API网络请求异常: {str(e)}" - logger.error(error_msg) - return {"code": 500, "msg": error_msg, "data": None} - except Exception as e: - error_msg = f"设备操作API调用异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return {"code": 500, "msg": error_msg, "data": None} - - - def get_operation_analysis_result(self, tool_data: Dict[str, Any], best_match: Optional[Dict[str, Any]]) -> str: - """ - 分析操作结果,根据 tool_data 和 best_match 返回合适的操作命令 - - 参数: - tool_data: 工具数据,包含 location、device、operation、keyId - best_match: 最佳匹配结果,包含设备信息和评分 - - 返回: - str: 操作命令字符串 - """ - logger.debug("开始分析操作结果...") - logger.debug(f"输入参数 - tool_data: {tool_data}, best_match: {best_match}") - - def contains_chinese(text: str) -> bool: - """检查字符串是否包含中文字符""" - if not text: - return False - for char in text: - if '\u4e00' <= char <= '\u9fff': - return True - return False - - try: - # 获取 tool_data 中的 operation - tool_operation = tool_data.get("operation", "") if tool_data else "" - logger.debug(f"tool_data 中的 operation: {tool_operation}") - - # 判断 tool_data 的 operation 是否是纯英文(不包含中文) - if tool_operation and not contains_chinese(tool_operation): - logger.info(f"tool_data operation 为纯英文,直接使用: {tool_operation}") - return tool_operation - - # 如果 tool_data 的 operation 包含中文或为空,检查 best_match - logger.debug("tool_data operation 包含中文或为空,尝试使用 best_match") - if best_match and isinstance(best_match, dict): - device_info = best_match.get("device", {}) - if device_info and isinstance(device_info, dict): - operation_info = device_info.get("operation", {}) - if operation_info and isinstance(operation_info, dict): - operation_key = operation_info.get("key", "") - if operation_key: - logger.info(f"从 best_match 获取到操作命令: {operation_key}") - return operation_key - - # 如果都没有有效值,返回原始的 tool_operation 或空字符串 - logger.warning(f"未找到有效的操作命令,返回原始值: {tool_operation}") - return tool_operation or "" - - except Exception as e: - logger.error(f"分析操作结果时发生异常: {str(e)}", exc_info=True) - return "" - - def __del__(self): - """析构函数,关闭会话""" - if hasattr(self, "session"): - self.session.close() - - -# 使用示例 -if __name__ == "__main__": - # 测试代码只在直接运行此模块时执行,不在导入时执行 - # 以下代码已注释避免MCP服务器启动时执行 - pass - - # device_op = DeviceOperator() - - # # 测试获取数字员工信息 - # employee_id = "1950037223825125378" - # employee_result = device_op.get_digital_employee_by_id(employee_id) - # logger.info(f"数字员工信息: {employee_result}") - - # 以下所有测试代码已注释,避免MCP服务器启动时执行 - - # device_op = DeviceOperator() - - # # 测试获取数字员工信息 - # employee_id = "1950037223825125378" - # employee_result = device_op.get_digital_employee_by_id(employee_id) - # logger.info(f"数字员工信息: {employee_result}") - - # # 测试单个设备操作 - # data = { - # "enterpriseId": "1932095424144715777", - # "entityId": "climate.qjiang_cn_741479129_wb20", - # "command": "set_temperature", - # "params": { - # "temperature": 24, - # }, - # } - # result = device_op.operate_device(data) - # logger.info(f"设备操作结果: {result}") - - # # 测试批量设备操作 - # batch_data = [ - # { - # "enterpriseId": "1932095424144715777", - # "entityId": "switch.office_light_1", - # "command": "turn_on", - # "params": {} - # }, - # { - # "enterpriseId": "1932095424144715777", - # "entityId": "climate.office_ac_1", - # "command": "set_temperature", - # "params": { - # "temperature": 25, - # } - # }, - # { - # "enterpriseId": "1932095424144715777", - # "entityId": "switch.meeting_room_projector", - # "command": "turn_off", - # "params": {} - # } - # ] - - # batch_result = device_op.batch_operate_devices(batch_data) - # logger.info(f"批量设备操作结果: {batch_result}") - - # # 输出批量操作统计信息 - # if batch_result.get("code") in [200, 206]: # 成功或部分成功 - # batch_data_result = batch_result.get("data", {}) - # logger.info(f"批量操作统计: 总数={batch_data_result.get('total')}, " - # f"成功={batch_data_result.get('success')}, " - # f"失败={batch_data_result.get('failed')}, " - # f"成功率={batch_data_result.get('success_rate')}%") - - # # 测试操作分析结果方法 - # # 测试用例1:tool_data 包含纯英文操作 - # tool_data_en = { - # "location": "office", - # "device": "air_conditioner", - # "operation": "turn_on", - # "keyId": "1932095424144715777" - # } - - # best_match_data = { - # "device": { - # "id": "d67d81ef-1c0e-4049-ae03-4581604138fc", - # "location": { - # "key": "37", - # "description": "李总办公室;董事长;李总房间;董事长办公室" - # }, - # "device": { - # "key": "climate.qjiang_cn_741478700_wb20", - # "description": "空调;制冷设备" - # }, - # "operation": { - # "key": "set_temperature", - # "description": "设置温度;调节温度;" - # }, - # "operation_params": [] - # }, - # "score": 0.9385, - # "confidence": "高" - # } - - # result1 = device_op.get_operation_analysis_result(tool_data_en, best_match_data) - # logger.info(f"测试1 - 纯英文操作: {result1}") # 应该返回 "turn_on" - - # # 测试用例2:tool_data 包含中文操作 - # tool_data_cn = { - # "location": "办公室", - # "device": "空调", - # "operation": "打开空调", - # "keyId": "1932095424144715777" - # } - - # result2 = device_op.get_operation_analysis_result(tool_data_cn, best_match_data) - # logger.info(f"测试2 - 中文操作: {result2}") # 应该返回 "set_temperature" - - # # 测试用例3:tool_data 操作为空,使用 best_match - # tool_data_empty = { - # "location": "office", - # "device": "air_conditioner", - # "operation": "", - # "keyId": "1932095424144715777" - # } - - # result3 = device_op.get_operation_analysis_result(tool_data_empty, best_match_data) - # logger.info(f"测试3 - 空操作: {result3}") # 应该返回 "set_temperature" diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_results_pretreatment.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_results_pretreatment.py deleted file mode 100644 index cbefeb1..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_results_pretreatment.py +++ /dev/null @@ -1,659 +0,0 @@ -from typing import List, Dict, Any -from collections import defaultdict -import re -from .logger_config import get_logger - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - - -def process_device_data(data, enterprise_id): - """ - 处理设备数据,提取best_match并转换为指定格式 - - Args: - data (dict): 包含results和best_match的数据结构 - enterprise_id (str): 企业ID - - Returns: - dict: 处理后的best_match数据,包含enterpriseId、entityId和command - 如果处理失败返回None - """ - logger = _ensure_logger() - logger.debug(f"开始处理设备数据,enterprise_id: {enterprise_id}") - - try: - # 参数验证 - if not isinstance(data, dict): - error_msg = "data参数必须是字典类型" - logger.error(error_msg) - raise ValueError(error_msg) - - if not enterprise_id: - error_msg = "enterprise_id不能为空" - logger.error(error_msg) - raise ValueError(error_msg) - - # 检查数据是否包含best_match - if "best_match" not in data: - raise ValueError("数据中缺少best_match字段") - - best_match = data["best_match"] - - if not isinstance(best_match, dict): - raise ValueError("best_match必须是字典类型") - - # 检查best_match是否包含device字段 - if "device" not in best_match: - raise ValueError("best_match中缺少device字段") - - device_info = best_match["device"] - - if not isinstance(device_info, dict): - raise ValueError("device_info必须是字典类型") - - # 检查device_info是否包含device和operation字段 - if "device" not in device_info: - raise ValueError("device_info中缺少device字段") - - if "operation" not in device_info: - raise ValueError("device_info中缺少operation字段") - - device_data = device_info["device"] - operation_data = device_info["operation"] - - if not isinstance(device_data, dict) or not isinstance(operation_data, dict): - raise ValueError("device和operation字段必须是字典类型") - - # 提取device的key作为entityId - device_key = device_data.get("key", "") - if not device_key: - raise ValueError("device.key字段为空") - - # 提取operation的key作为command - operation_key = operation_data.get("key", "") - if not operation_key: - raise ValueError("operation.key字段为空") - - # 构建返回结果 - processed_result = { - "enterpriseId": enterprise_id, - "entityId": device_key, - "command": operation_key, - } - - logger.info(f"设备数据处理成功: entityId={device_key}, command={operation_key}") - logger.debug(f"处理结果: {processed_result}") - return processed_result - - except Exception as e: - logger.error(f"数据处理错误: {str(e)}", exc_info=True) - return None - - -def process_device_data_safe(data, enterprise_id, default_result=None): - """ - 安全版本的数据处理方法,不会抛出异常 - - Args: - data (dict): 包含results和best_match的数据结构 - enterprise_id (str): 企业ID - default_result (dict): 处理失败时的默认返回值 - - Returns: - dict: 处理后的best_match数据,处理失败时返回default_result - """ - result = process_device_data(data, enterprise_id) - return result if result is not None else default_result - - -def validate_device_data(data): - """ - 验证设备数据结构的完整性 - - Args: - data (dict): 要验证的数据 - - Returns: - tuple: (is_valid, error_message) - """ - try: - if not isinstance(data, dict): - return False, "数据必须是字典类型" - - if "best_match" not in data: - return False, "缺少best_match字段" - - best_match = data["best_match"] - if not isinstance(best_match, dict): - return False, "best_match必须是字典类型" - - if "device" not in best_match: - return False, "best_match中缺少device字段" - - device_info = best_match["device"] - if not isinstance(device_info, dict): - return False, "device_info必须是字典类型" - - required_fields = ["device", "operation"] - for field in required_fields: - if field not in device_info: - return False, f"device_info中缺少{field}字段" - - field_data = device_info[field] - if not isinstance(field_data, dict): - return False, f"{field}字段必须是字典类型" - - if "key" not in field_data or not field_data["key"]: - return False, f"{field}.key字段不能为空" - - return True, "数据验证通过" - - except Exception as e: - return False, f"验证过程中发生错误: {str(e)}" - - -def process_device_results(raw_data: List[List[Any]]) -> List[Dict[str, Any]]: - """ - 预处理数据,将原始数据转换为简化的数组对象格式 - 如果 entityId 相同,则合并到一起 - - Args: - raw_data: 原始数据,格式为 [[entity, score], ...] - - Returns: - 处理后的数据数组,每个对象包含合并后的信息(不包含id和score_details字段) - """ - logger = _ensure_logger() - logger.debug(f"开始处理设备结果数据,输入数据条数: {len(raw_data) if raw_data else 0}") - - # 使用 entityId 作为分组键 - grouped_data = defaultdict(list) - - for item in raw_data: - if len(item) >= 2: - entity = item[0] - score = item[1] - - logger.debug(f"处理项目: entity类型={type(entity)}, 包含keys={list(entity.keys()) if isinstance(entity, dict) else 'N/A'}") - - entityId = entity.get("device", {}).get("key", "") - logger.debug(f"提取的entityId: '{entityId}'") - - # 如果entityId为空,跳过这个实体 - if not entityId: - logger.warning(f"跳过entityId为空的实体: {entity}") - continue - - # 创建简化的对象 - simplified_entity = { - "location_key": entity.get("location", {}).get("key"), - "location_desc": entity.get("location", {}).get("description"), - "entityId": entity.get("device", {}).get("key"), - "device_desc": entity.get("device", {}).get("description"), - "command": entity.get("operation", {}).get("key"), - "operation_desc": entity.get("operation", {}).get("description"), - "operation_params": entity.get("operation_params", []), - "score": score, - } - - logger.debug(f"创建的简化实体: {simplified_entity}") - grouped_data[entityId].append(simplified_entity) - - # 合并相同 entityId 的数据 - result = [] - logger.debug(f"grouped_data包含{len(grouped_data)}个组: {list(grouped_data.keys())}") - - for entityId, entities in grouped_data.items(): - logger.debug(f"处理entityId '{entityId}' 的 {len(entities)} 个实体") - if len(entities) == 1: - # 只有一个实体,直接添加 - result.append(entities[0]) - logger.debug(f"设备 {entityId} 只有一个实体,直接添加") - else: - # 多个实体需要合并 - merged_entity = merge_entities(entities) - result.append(merged_entity) - logger.debug(f"设备 {entityId} 有 {len(entities)} 个实体,已合并") - - logger.info(f"设备结果数据处理完成,输出数据条数: {len(result)}") - logger.debug(f"最终结果: {result}") - return result - - -def merge_entities(entities: List[Dict[str, Any]]) -> Dict[str, Any]: - """ - 合并相同 entityId 的多个实体 - - Args: - entities: 需要合并的实体列表 - - Returns: - 合并后的实体 - """ - if not entities: - return {} - - # 选择分数最高的作为主实体 - main_entity = max(entities, key=lambda x: x.get("score", 0)) - - # 收集所有操作 - operations = [] - - for entity in entities: - operation_info = { - "command": entity.get("command"), - "operation_desc": entity.get("operation_desc"), - "operation_params": entity.get("operation_params", []), - "score": entity.get("score", 0), - } - operations.append(operation_info) - - # 创建合并后的实体 - merged_entity = { - "location_key": main_entity.get("location_key"), - "location_desc": main_entity.get("location_desc"), - "entityId": main_entity.get("entityId"), - "device_desc": main_entity.get("device_desc"), - "operations": operations, # 所有可能的操作 - } - - return merged_entity - - -def extract_space_entity_and_location(space_data: Dict[str, Any]) -> Dict[str, Any]: - """ - 从空间数据结构中提取 entityId 与 location。 - - 期望输入示例: - { - "spaceId": "46", - "spaceAliasName": "爱易拍展厅;爱一拍展厅;爱一拍展区", - "spaceName": "爱易拍展厅", - ... - } - - 处理规则: - - entityId = spaceId(转为字符串) - - location 来自 spaceAliasName: - - 若包含分隔符(例如 ';' 或 ';' 等),拆分并取第一个非空别名 - - 若不包含分隔符,则直接使用 spaceAliasName - - 若 spaceAliasName 为空,回退到 spaceName;仍为空则返回空字符串 - - Returns: - {"entityId": str, "location": str} - """ - logger = _ensure_logger() - logger.debug(f"开始提取空间实体和位置信息: {space_data}") - - # entityId - space_id = space_data.get("spaceId") - entity_id = "" if space_id is None else str(space_id) - logger.debug(f"提取的实体ID: {entity_id}") - - # location from alias - alias_raw = space_data.get("spaceAliasName") - alias_text = ( - alias_raw - if isinstance(alias_raw, str) - else (str(alias_raw) if alias_raw is not None else "") - ) - - chosen_location = alias_text.strip() - - if chosen_location: - # 使用常见分隔符进行切分:中文分号、英文分号、中文逗号、英文逗号、竖线、斜杠 - parts = [ - p.strip() - for p in re.split(r"[;;,,\|/]+", chosen_location) - if p and p.strip() - ] - if parts: - chosen_location = parts[0] - else: - # alias 为空则回退到 spaceName - space_name = space_data.get("spaceName") - chosen_location = ( - space_name.strip() - if isinstance(space_name, str) - else (str(space_name).strip() if space_name is not None else "") - ) - - result = { - "entityId": entity_id, - "location": chosen_location, - } - - logger.info(f"空间实体和位置提取完成: entityId={entity_id}, location={chosen_location}") - return result - - -def format_device_for_display(device: Dict[str, Any]) -> str: - """ - 将设备数据格式化为易读的描述文本 - 用于展示给AI和用户,提升可读性和理解性 - - Args: - device: 设备数据字典,可能包含以下字段: - - location_desc: 位置描述 - - device_desc: 设备描述 - - entityId: 设备唯一标识 - - operations: 操作列表(合并后的设备) - 或 - - command: 单个命令 - - operation_desc: 操作描述 - - operation_params: 操作参数 - - Returns: - 格式化后的设备描述文本 - """ - logger = _ensure_logger() - logger.debug(f"开始格式化设备显示信息: entityId={device.get('entityId', 'N/A')}") - - try: - # 处理位置描述 - location = device.get('location_desc', '未知位置') - if ';' in location or ';' in location: - parts = re.split(r'[;;]', location) - location = '('.join(parts) + ')' if len(parts) > 1 else parts[0] - - # 处理设备类型描述 - device_type = device.get('device_desc', '未知设备') - if ';' in device_type or ';' in device_type: - parts = re.split(r'[;;]', device_type) - device_type = '('.join(parts) + ')' if len(parts) > 1 else parts[0] - - entity_id = device.get('entityId', '') - - formatted_text = f"""设备核心信息: -位置:{location} -设备类型:{device_type} -唯一标识(entityId):{entity_id} - -可执行操作:""" - - # 判断是合并后的设备(有operations字段)还是单个操作设备 - if 'operations' in device: - # 合并后的设备,包含多个操作 - operations = device.get('operations', []) - for i, op in enumerate(operations, 1): - cmd = op.get('command', '') - desc = op.get('operation_desc', '').replace(';', ' / ').replace(';', ' / ') - - formatted_text += f"\n{i}. {desc}" - formatted_text += f"\n 指令:{cmd}" - - params = op.get('operation_params', []) - if params: - formatted_text += "\n 参数:" - for param in params: - key = param.get('key', '') - description = param.get('description', '') - value = param.get('value', '') - formatted_text += f"\n - {description}({key}):{value}" - else: - formatted_text += "\n 参数:无" - else: - # 单个操作设备 - cmd = device.get('command', '') - desc = device.get('operation_desc', '').replace(';', ' / ').replace(';', ' / ') - - formatted_text += f"\n1. {desc}" - formatted_text += f"\n 指令:{cmd}" - - params = device.get('operation_params', []) - if params: - formatted_text += "\n 参数:" - for param in params: - key = param.get('key', '') - description = param.get('description', '') - value = param.get('value', '') - formatted_text += f"\n - {description}({key}):{value}" - else: - formatted_text += "\n 参数:无" - - logger.debug(f"设备显示信息格式化完成: entityId={entity_id}") - return formatted_text - - except Exception as e: - logger.error(f"格式化设备显示信息时出错: {str(e)}", exc_info=True) - return f"设备信息格式化失败: {str(e)}" - - -def format_devices_list(devices: List[Dict[str, Any]]) -> str: - """ - 格式化多个设备信息为易读的文本列表 - - Args: - devices: 设备数据列表 - - Returns: - 格式化后的设备列表文本 - """ - logger = _ensure_logger() - logger.debug(f"开始格式化设备列表,设备数量: {len(devices) if devices else 0}") - - if not devices: - logger.warning("设备列表为空") - return "未找到相关设备" - - try: - formatted_list = [] - formatted_list.append(f"共找到 {len(devices)} 个匹配的设备\n") - - for idx, device in enumerate(devices, 1): - if idx > 1: - formatted_list.append("\n" + "-" * 40 + "\n") - formatted_list.append(f"【设备 {idx}】") - formatted_list.append(format_device_for_display(device)) - - result = "\n".join(formatted_list) - logger.info(f"设备列表格式化完成,设备数量: {len(devices)}") - return result - - except Exception as e: - logger.error(f"格式化设备列表时出错: {str(e)}", exc_info=True) - return f"设备列表格式化失败: {str(e)}" - - -def format_device_for_display_simple(device: Dict[str, Any]) -> str: - """ - 将设备数据格式化为简化的描述文本(无装饰符号版本) - 适用于需要更简洁输出的场景 - - Args: - device: 设备数据字典 - - Returns: - 格式化后的简化设备描述文本 - """ - logger = _ensure_logger() - logger.debug(f"开始格式化设备简化显示信息: entityId={device.get('entityId', 'N/A')}") - - try: - # 处理位置描述 - location = device.get('location_desc', '未知位置') - if ';' in location or ';' in location: - parts = re.split(r'[;;]', location) - location = '('.join(parts) + ')' if len(parts) > 1 else parts[0] - - # 处理设备类型描述 - device_type = device.get('device_desc', '未知设备') - if ';' in device_type or ';' in device_type: - parts = re.split(r'[;;]', device_type) - device_type = '('.join(parts) + ')' if len(parts) > 1 else parts[0] - - entity_id = device.get('entityId', '') - - formatted_text = f"""设备核心信息: -• 位置:{location} -• 设备类型:{device_type} -• 唯一标识(entityId):{entity_id} - -可执行操作:""" - - # 判断是合并后的设备还是单个操作设备 - if 'operations' in device: - operations = device.get('operations', []) - for i, op in enumerate(operations, 1): - cmd = op.get('command', '') - desc = op.get('operation_desc', '').replace(';', ' / ').replace(';', ' / ') - - formatted_text += f"\n{i}. {desc}" - formatted_text += f"\n 指令:{cmd}" - - params = op.get('operation_params', []) - if params: - formatted_text += "\n 参数:" - for param in params: - key = param.get('key', '') - description = param.get('description', '') - value = param.get('value', '') - formatted_text += f"\n - {description}({key}):{value}" - else: - formatted_text += "\n 参数:无" - else: - cmd = device.get('command', '') - desc = device.get('operation_desc', '').replace(';', ' / ').replace(';', ' / ') - - formatted_text += f"\n1. {desc}" - formatted_text += f"\n 指令:{cmd}" - - params = device.get('operation_params', []) - if params: - formatted_text += "\n 参数:" - for param in params: - key = param.get('key', '') - description = param.get('description', '') - value = param.get('value', '') - formatted_text += f"\n - {description}({key}):{value}" - else: - formatted_text += "\n 参数:无" - - logger.debug(f"设备简化显示信息格式化完成: entityId={entity_id}") - return formatted_text - - except Exception as e: - logger.error(f"格式化设备简化显示信息时出错: {str(e)}", exc_info=True) - return f"设备信息格式化失败: {str(e)}" - - -# 测试函数 -def test_process_device_data(): - """测试数据处理函数""" - logger = _ensure_logger() - # 模拟输入数据 - test_data = { - "results": [ - [ - { - "id": "32875964-00df-4b34-8e33-c47a722b8f7f", - "location": {"key": "lzwc", "description": "灵泽联创中心"}, - "device": { - "key": "switch.zimi_cn_1144138206_dhkg01_on_p_2_1", - "description": "灵泽办公区过道吊灯 开关 按键", - }, - "operation": {"key": "turn_on", "description": "开关"}, - "operation_params": [], - }, - 0.34563739142066174, - { - "exact_match": 0.25, - "word2vec_similarity": 0.5346393585205078, - "doc2vec_similarity": 0.004867201205343008, - "tfidf_similarity": 0.8217383432613271, - "combined_score": 0.34563739142066174, - }, - ] - ], - "count": 1, - "best_match": { - "device": { - "id": "32875964-00df-4b34-8e33-c47a722b8f7f", - "location": {"key": "lzwc", "description": "灵泽联创中心"}, - "device": { - "key": "switch.zimi_cn_1144138206_dhkg01_on_p_2_1", - "description": "灵泽办公区过道吊灯 开关 按键", - }, - "operation": {"key": "turn_on", "description": "开关"}, - "operation_params": [], - }, - "score": 0.34563739142066174, - "confidence": "低", - }, - } - - enterprise_id = "1932095424144715777" - - logger.info("=== 测试数据处理功能 ===") - - # 1. 测试数据验证 - logger.info("1. 数据验证测试:") - is_valid, message = validate_device_data(test_data) - logger.info(f"数据验证结果: {is_valid}, 消息: {message}") - - # 2. 测试基本处理功能 - logger.info("2. 基本处理功能测试:") - result = process_device_data(test_data, enterprise_id) - logger.info(f"处理结果: {result}") - - # 3. 测试安全版本 - logger.info("3. 安全版本测试:") - safe_result = process_device_data_safe( - test_data, enterprise_id, {"error": "处理失败"} - ) - logger.info(f"安全处理结果: {safe_result}") - - # 4. 测试错误情况 - logger.info("4. 错误情况测试:") - # 测试空数据 - empty_result = process_device_data_safe({}, enterprise_id, {"error": "数据为空"}) - logger.info(f"空数据处理结果: {empty_result}") - - # 测试缺少字段的数据 - invalid_data = {"best_match": {"device": {}}} - invalid_result = process_device_data_safe( - invalid_data, enterprise_id, {"error": "数据格式错误"} - ) - logger.info(f"无效数据处理结果: {invalid_result}") - - return result - - -def test_process_device_results(): - import json - - logger = _ensure_logger() - - # Load JSON data from file - json_file_path = r"E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_iot\deviot.json" - with open(json_file_path, 'r', encoding='utf-8') as f: - raw_data = json.load(f) - - logger.info('Raw data loaded, processing...') - result = process_device_results(raw_data) - logger.info('Processed result:') - for i, item in enumerate(result): - logger.info(f"Item {i+1}: {item}") - - # Save results to output file - output_file_path = r"E:\yh-ai\project\lzwcai-szyg\lzwcai-mcp-server-package\lzwcai_mcp_iot\output\output.json" - with open(output_file_path, 'w', encoding='utf-8') as f: - json.dump(result, f, ensure_ascii=False, indent=2) - - logger.info(f'Results saved to: {output_file_path}') - - return result - -if __name__ == "__main__": - # 测试代码只在直接运行此模块时执行,不在导入时执行 - # test_process_device_data() - # test_process_device_results() # 注释掉测试代码避免MCP服务器启动时执行 - pass diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/init_mcp.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/init_mcp.py deleted file mode 100644 index b25fe07..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/init_mcp.py +++ /dev/null @@ -1,131 +0,0 @@ - -""" -MCP服务器初始化模块 - -该模块提供了MCP服务器的初始化功能,包括企业ID获取和向量库初始化。 -""" - -from typing import Optional, Dict, Any -from .device_operations import DeviceOperator -from .vector_service import VectorService -from .logger_config import get_logger - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - - -def init_mcp_server( - device_op: DeviceOperator, - vector_service: VectorService, - enterprise_id: str -) -> Dict[str, Any]: - """ - 初始化MCP服务器 - - 该函数执行以下步骤: - 1. 验证企业ID - 2. 检查向量库状态 - 3. 如果向量库不存在,则创建向量库 - - 参数: - device_op: 设备操作实例 - vector_service: 向量服务实例 - enterprise_id: 企业ID(从环境变量获取) - - 返回: - Dict[str, Any]: 初始化结果,包含状态码、消息和数据 - """ - logger = _ensure_logger() - try: - # 输入参数验证 - if not enterprise_id or not isinstance(enterprise_id, str): - error_msg = f"无效的企业ID: {enterprise_id}" - logger.error(error_msg) - return { - "code": 400, - "msg": error_msg, - "data": None - } - - if not isinstance(device_op, DeviceOperator): - error_msg = "device_op参数必须是DeviceOperator实例" - logger.error(error_msg) - return { - "code": 400, - "msg": error_msg, - "data": None - } - - if not isinstance(vector_service, VectorService): - error_msg = "vector_service参数必须是VectorService实例" - logger.error(error_msg) - return { - "code": 400, - "msg": error_msg, - "data": None - } - - logger.info(f"开始初始化MCP服务器,企业ID: {enterprise_id}") - - # 第一步:检查向量库状态 - logger.info("第一步:检查向量库状态...") - vector_store_exists = vector_service.check_vector_store_status(enterprise_id) - logger.info(f"向量库状态检查完成,存在状态: {vector_store_exists}") - - # 如果向量库不存在,则创建向量库 - if not vector_store_exists: - logger.info("向量库不存在,开始创建向量库...") - init_result = vector_service.init_vector_store(keyId=enterprise_id) - - # 检查初始化结果 - if init_result.get("status") == "error": - error_msg = f"向量库初始化失败: {init_result.get('message')}" - logger.error(error_msg) - return { - "code": 500, - "msg": error_msg, - "data": { - "enterprise_id": enterprise_id, - "vector_store_created": False, - "init_result": init_result - } - } - else: - logger.info("向量库创建成功") - return { - "code": 200, - "msg": "MCP服务器初始化成功,向量库已创建", - "data": { - "enterprise_id": enterprise_id, - "vector_store_created": True, - "vector_store_existed": False, - "init_result": init_result - } - } - else: - logger.info("向量库已存在,无需创建") - return { - "code": 200, - "msg": "MCP服务器初始化成功,向量库已存在", - "data": { - "enterprise_id": enterprise_id, - "vector_store_created": False, - "vector_store_existed": True - } - } - - except Exception as e: - error_msg = f"MCP服务器初始化过程中发生异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return { - "code": 500, - "msg": error_msg, - "data": None - } diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/iot_device_dicts_prompt.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/iot_device_dicts_prompt.py deleted file mode 100644 index 473deb4..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/iot_device_dicts_prompt.py +++ /dev/null @@ -1,216 +0,0 @@ -def iot_device_precise_controller_prompt(): - return """ - 设备精准控制 - - 【功能描述】 - 用来精确控制特定设备的工具,通过设备的唯一ID直接操作,特别准确。 - 这个工具只负责"控制设备",但它需要先通过"设备信息查询"工具获取设备信息才能使用。 - - 【日常使用场景】 - 在使用"设备信息查询"工具找到设备后的控制操作: - - "控制那个具体的灯" - - "操作刚才查到的灯" - - "开一下那台空调" - - "把刚才找到的空调打开" - - "关掉那个特定的插座" - - "控制指定的插座" - - "操作那个设备" - - "控制具体设备" - - 跟进操作(基于之前的查询结果): - - "就是这个设备,帮我开一下" - - "用这个ID控制设备" - - "按照之前查到的信息操作" - - 【工作方式】 - 这个工具必须和"设备信息查询"工具配合使用: - 1. 第一步:用"设备信息查询"工具找设备 - 2. 第二步:从查询结果中获取设备ID和控制命令 - 3. 第三步:用这个工具执行精确控制 - - 【工具配合关系】 - 就像"先查电话号码,再打电话": - - 设备信息查询 = 查电话号码(找到设备和操作方法) - - 设备精准控制 = 打电话(实际执行控制) - - 【重要】 - 这个工具不能单独使用!必须先用查询iot_get_devices_by_location或者iot_get_all_spaces_and_devices工具获取设备信息,不能随便填写参数。 - - 【参数说明】 - - entityId (必填): 设备唯一ID - * 格式示例: "switch.zimi_cn_1144138206_dhkg01_on_p_2_1" - * 来源: 设备查询工具返回结果中的设备ID - - - command (必填): 操作命令 - * 格式示例: "turn_on", "turn_off", "set_temperature", "set_brightness" - * 来源: 设备查询工具返回结果中的命令 - - - params (可选): 操作参数 - * 格式: 包含操作所需参数的数据 - * 来源: 设备查询工具返回结果中的参数 - * 示例: {"temperature": 24} 用于空调温度设置 - * 示例: {"brightness": 80} 用于灯光亮度调节 - - 【返回结果】 - 返回设备控制操作结果,包括是否成功、设备反馈信息和详细结果。 - """ - - -def iot_get_devices_by_location_prompt(): - return """ - 根据位置获取设备列表 - - 【功能描述】 - 根据指定的位置/房间,获取该位置下的所有智能设备列表。 - 这个工具专门用来查看某个位置有哪些设备,会返回该位置的完整设备清单。 - - 【日常使用场景】 - 查看位置设备: - - "办公室有哪些设备" - - "会议室有什么设备" - - "看看客厅的设备" - - "前台有什么智能设备" - - "财务部的设备列表" - - "查看卧室的所有设备" - - 设备清单查询: - - "列出办公室所有设备" - - "显示会议室设备清单" - - "办公室设备有哪些" - - "会议室装了什么设备" - - "客厅都有什么智能设备" - - 位置设备统计: - - "办公室一共有多少设备" - - "会议室有几个设备" - - "客厅设备数量" - - "前台设备统计" - - 简单查询: - - "办公室设备" - - "会议室设备列表" - - "客厅的设备" - - "设备清单" - - 【注意】 - - 这个工具只返回设备列表信息,不会控制设备 - - 返回结果包含设备名称、类型、状态等详细信息 - - 如果需要控制设备,可以使用返回结果中的设备信息配合其他控制工具 - - 【参数说明】 - - location (必填): 要查询的位置/房间名称 - * 示例:办公室、会议室、客厅、前台、财务部等 - * 参数值不得包含任何标点符号(如逗号、句号、感叹号等) - - 【返回结果】 - 返回指定位置的所有设备列表,包括: - - 设备数量统计 - - 每个设备的详细信息(设备ID、名称、类型、状态等) - - 设备的当前状态(开启/关闭等) - - 设备支持的控制命令 - - 【典型使用流程】 - 1. 指定要查询的位置 - 2. 获取该位置的所有设备列表 - 3. 查看设备详细信息 - 4. (可选)根据设备信息进行后续控制操作 - """ - - -def iot_get_all_spaces_and_devices_prompt(): - return """ - 获取所有空间位置信息 - - 【功能描述】 - 获取系统中所有空间位置的列表。 - 这个工具只返回空间名称清单,不包含设备详情,适合快速了解有哪些可用空间。 - - 【日常使用场景】 - 查看所有空间: - - "显示所有空间" - - "有哪些空间" - - "列出所有房间" - - "查看全部区域" - - "空间列表" - - "所有位置" - - "有什么位置" - - "可用的空间有哪些" - - 空间清单查询: - - "系统里有几个空间" - - "一共有多少个位置" - - "空间总览" - - "位置总览" - - "房间列表" - - 简单查询: - - "所有空间" - - "空间总览" - - "位置列表" - - "全部位置" - - 【注意】 - - 这个工具只返回空间名称列表,不包含设备信息 - - 不需要传入任何参数 - - 如果需要查看某个空间的设备,请使用"根据位置获取设备列表"工具 - - 适合在控制设备前先了解有哪些可用空间 - - 【参数说明】 - - 无需参数(自动获取所有空间位置) - - 【返回结果】 - 返回所有空间位置列表,包括: - - 空间总数统计 - - 所有空间名称的列表 - - 【典型使用流程】 - 1. 调用工具获取所有空间列表 - 2. 查看有哪些可用空间 - 3. (可选)选择特定空间,使用"根据位置获取设备列表"工具查看该空间的设备 - 4. (可选)根据设备信息进行设备控制操作 - """ - - -def smart_space_device_locator_matcher_prompt(): - return """ - 位置查询 - - 【功能描述】 - 帮你查看自己现在在哪个位置/空间,会根据你的用户信息自动识别。 - - 【日常使用场景】 - 查看位置: - - "我现在在哪" - - "我在哪个位置" - - "我现在在哪个空间" - - "当前位置是什么" - - "我属于哪个地方" - - "我在哪个区域" - - 确认空间: - - "当前默认空间是什么" - - "我的默认位置" - - "系统识别我在哪里" - - "定位我的位置" - - "我的空间信息" - - "位置信息" - - 设备控制前确认: - - "先看看我在哪" - - "确认一下位置" - - "我在这个位置吗" - - "位置对不对" - - 【注意】 - 这个工具只是查看你的位置信息,不会控制任何设备。 - - 【参数说明】 - - userId (必填): 用户ID(自动提供) - - 【返回结果】 - 返回用户所属空间: - - spaceAliasName: 推断出的空间名称 - - 【使用说明】 - - 本工具仅做查询与定位,不执行设备控制 - """ diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/logger_config.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/logger_config.py deleted file mode 100644 index d32b3ec..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/logger_config.py +++ /dev/null @@ -1,558 +0,0 @@ -""" -统一日志配置模块 - -这个模块提供了整个项目的统一日志配置和管理功能,确保所有组件使用一致的日志格式和输出方式。 - -主要功能: -1. 统一的日志格式配置 -2. 支持控制台和文件双重输出 -3. 日志文件轮转管理 -4. MCP模式下的特殊处理(禁用控制台输出) -5. 便捷的日志器获取接口 -6. 丰富的日志工具函数 - -设计特点: -- 单例模式确保配置一致性 -- 支持动态配置调整 -- 异常安全的编码处理 -- 详细的调试信息记录 - -作者: lzwcai -版本: 1.0.0 -""" - -import logging -import logging.handlers -import sys -from datetime import datetime -from pathlib import Path -from typing import Optional - - -class LoggerConfig: - """ - 日志配置管理器 - - 这个类采用单例模式管理整个项目的日志配置。 - 它提供了统一的日志格式、文件轮转、编码处理等功能。 - - 主要特性: - - 单例模式:确保全局日志配置一致 - - 双重输出:同时支持控制台和文件输出 - - 文件轮转:自动管理日志文件大小和数量 - - 编码安全:正确处理中文字符 - - MCP兼容:支持MCP模式下的特殊需求 - - 配置参数: - DEFAULT_LOG_LEVEL: 默认日志级别(INFO) - DEFAULT_LOG_FORMAT: 日志格式模板 - DEFAULT_DATE_FORMAT: 时间格式 - LOG_FILE_NAME: 日志文件名 - MAX_LOG_SIZE: 单个日志文件最大大小(10MB) - BACKUP_COUNT: 保留的备份文件数量(5个) - """ - - # ==================== 默认配置常量 ==================== - - # 默认日志级别:INFO级别平衡了信息量和性能 - DEFAULT_LOG_LEVEL = logging.INFO - - # 默认日志格式:包含时间、模块名、级别、文件位置、消息内容 - DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s" - - # 默认时间格式:标准的年-月-日 时:分:秒格式 - DEFAULT_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - - # ==================== 日志文件配置 ==================== - - # 日志文件名:使用项目名称作为前缀 - LOG_FILE_NAME = "lzwcai_mcp_iot.log" - - # 单个日志文件最大大小:10MB - MAX_LOG_SIZE = 10 * 1024 * 1024 # 10MB - - # 保留的备份文件数量:5个(总共约50MB的日志存储) - BACKUP_COUNT = 5 - - # ==================== 单例模式状态 ==================== - - # 初始化标志:确保只初始化一次 - _initialized = False - - # 日志文件路径:记录当前使用的日志文件路径 - _log_file_path = None - - @classmethod - def setup_logging( - cls, - log_level: int = DEFAULT_LOG_LEVEL, - log_file: Optional[str] = None, - console_output: bool = True, - file_output: bool = True - ) -> str: - """ - 设置项目统一日志配置 - - 这是日志系统的核心初始化方法,负责配置整个项目的日志输出。 - 采用单例模式,确保在整个应用生命周期中只初始化一次。 - - 配置流程: - 1. 检查是否已经初始化(单例模式) - 2. 确定日志文件路径(自动或手动指定) - 3. 创建必要的目录结构 - 4. 配置根日志器和处理器 - 5. 设置日志格式化器 - 6. 添加控制台和文件处理器 - 7. 记录初始化信息 - - 特殊处理: - - MCP模式下通常禁用控制台输出,避免干扰stdio通信 - - Windows系统下的UTF-8编码处理 - - 日志文件的自动轮转管理 - - 参数: - log_level: 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL) - log_file: 日志文件路径,None时使用默认路径 - console_output: 是否输出到控制台(MCP模式下通常为False) - file_output: 是否输出到文件(通常为True) - - 返回: - str: 实际使用的日志文件路径 - - 注意事项: - - 这个方法是线程安全的 - - 重复调用会直接返回已配置的路径 - - 日志文件会自动创建必要的目录 - """ - # 单例模式检查:如果已经初始化,直接返回 - if cls._initialized: - return cls._log_file_path - - # ==================== 日志文件路径配置 ==================== - - if log_file is None: - # 自动确定日志文件路径:项目根目录 + 默认文件名 - project_root = cls._get_project_root() - log_file = project_root / cls.LOG_FILE_NAME - else: - # 使用指定的日志文件路径 - log_file = Path(log_file) - - # 确保日志目录存在(递归创建) - log_file.parent.mkdir(parents=True, exist_ok=True) - cls._log_file_path = str(log_file) - - # ==================== 包日志器配置 ==================== - - # 获取包的顶层日志器,而不是根日志器 - package_logger = logging.getLogger('lzwcai_mcp_iot') - package_logger.setLevel(log_level) - - # 作为库,不应该清除宿主应用的任何处理器 - # 也不应该让日志消息向上传播到根日志器,以免重复打印 - package_logger.propagate = False - - # 清除此日志器上现有的处理器,避免重复配置 - for handler in package_logger.handlers[:]: - package_logger.removeHandler(handler) - - # ==================== 日志格式化器 ==================== - - # 创建统一的日志格式化器 - formatter = logging.Formatter( - fmt=cls.DEFAULT_LOG_FORMAT, # 日志格式模板 - datefmt=cls.DEFAULT_DATE_FORMAT # 时间格式 - ) - - # ==================== 控制台处理器配置 ==================== - - if console_output: - # 控制台输出处理器,支持彩色输出和UTF-8编码 - import io - - # 处理Windows系统的编码问题 - if hasattr(sys.stdout, 'buffer'): - # 在Windows上强制使用UTF-8编码,避免中文乱码 - # errors='replace'确保即使有编码问题也不会崩溃 - console_stream = io.TextIOWrapper( - sys.stdout.buffer, - encoding='utf-8', - errors='replace' - ) - else: - # Unix/Linux系统通常默认支持UTF-8 - console_stream = sys.stdout - - # 创建控制台处理器 - console_handler = logging.StreamHandler(console_stream) - console_handler.setLevel(log_level) - console_handler.setFormatter(formatter) - package_logger.addHandler(console_handler) - - # ==================== 文件处理器配置 ==================== - - if file_output: - # 文件输出处理器,支持自动轮转 - file_handler = logging.handlers.RotatingFileHandler( - filename=cls._log_file_path, # 日志文件路径 - maxBytes=cls.MAX_LOG_SIZE, # 单文件最大大小 - backupCount=cls.BACKUP_COUNT, # 备份文件数量 - encoding='utf-8' # 文件编码 - ) - file_handler.setLevel(log_level) - file_handler.setFormatter(formatter) - package_logger.addHandler(file_handler) - - # ==================== 初始化完成标记 ==================== - - # 标记为已初始化,防止重复配置 - cls._initialized = True - - # ==================== 记录初始化信息 ==================== - - # 使用包日志器记录初始化信息,避免向上传播到根日志器 - if file_output: # 只有在文件输出启用时才记录初始化信息 - package_logger.info("=" * 80) - package_logger.info(f"日志系统初始化完成 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - package_logger.info(f"日志级别: {logging.getLevelName(log_level)}") - package_logger.info(f"日志文件: {cls._log_file_path}") - package_logger.info(f"控制台输出: {console_output}") - package_logger.info(f"文件输出: {file_output}") - package_logger.info(f"文件轮转: 最大{cls.MAX_LOG_SIZE // (1024*1024)}MB, 保留{cls.BACKUP_COUNT}个备份") - package_logger.info("=" * 80) - - return cls._log_file_path - - @classmethod - def _get_project_root(cls) -> Path: - """ - 获取项目根目录 - - 这个方法通过向上遍历目录树来查找项目根目录。 - 它会寻找常见的项目标识文件来确定根目录位置。 - - 查找策略: - 1. 从当前文件所在目录开始向上查找 - 2. 寻找项目标识文件:pyproject.toml, setup.py, main.py - 3. 找到任一标识文件的目录即为项目根目录 - 4. 如果都找不到,使用当前文件的上级目录作为备选 - - 返回: - Path: 项目根目录的路径对象 - - 注意事项: - - 这个方法假设项目结构相对标准 - - 在特殊的部署环境中可能需要调整 - - 备选方案确保总是返回有效路径 - """ - # 从当前文件向上查找项目根目录 - current_path = Path(__file__).parent - - # 向上遍历目录树 - while current_path.parent != current_path: # 避免到达文件系统根目录 - # 检查常见的项目标识文件 - if (current_path / "pyproject.toml").exists() or \ - (current_path / "setup.py").exists() or \ - (current_path / "main.py").exists(): - return current_path - current_path = current_path.parent - - # 备选方案:如果找不到标识文件,使用预设的相对路径 - # 这个路径基于当前的项目结构:util -> src -> 项目根 - return Path(__file__).parent.parent.parent - - @classmethod - def get_logger(cls, name: str) -> logging.Logger: - """ - 获取配置好的日志器 - - 这是获取日志器的标准方法,确保返回的日志器使用统一的配置。 - 如果日志系统尚未初始化,会自动进行初始化。 - - 参数: - name: 日志器名称,通常使用模块的 __name__ 变量 - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = LoggerConfig.get_logger(__name__) - logger.info("这是一条信息日志") - - 特性: - - 自动初始化:首次调用时自动配置日志系统 - - 层次化命名:支持Python日志器的层次化命名 - - 统一配置:所有日志器使用相同的格式和输出配置 - """ - # 检查是否已初始化,未初始化则使用MCP安全的默认配置初始化 - if not cls._initialized: - # MCP模式下,默认禁用控制台输出,只使用文件输出 - cls.setup_logging(console_output=False, file_output=True) - - # 返回指定名称的日志器 - return logging.getLogger(name) - - # ==================== 日志工具方法 ==================== - - @classmethod - def log_function_entry(cls, logger: logging.Logger, func_name: str, **kwargs): - """ - 记录函数入口日志 - - 用于调试和性能分析,记录函数被调用时的参数信息。 - 通常在DEBUG级别输出,不会影响生产环境的性能。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - **kwargs: 函数参数(键值对形式) - - 使用示例: - LoggerConfig.log_function_entry(logger, "process_data", user_id=123, action="login") - """ - args_str = ", ".join([f"{k}={v}" for k, v in kwargs.items()]) - logger.debug(f"进入函数 {func_name}({args_str})") - - @classmethod - def log_function_exit(cls, logger: logging.Logger, func_name: str, result=None): - """ - 记录函数出口日志 - - 与log_function_entry配对使用,记录函数执行完成和返回值。 - 有助于跟踪函数执行流程和调试返回值问题。 - - 参数: - logger: 日志器实例 - func_name: 函数名称 - result: 函数返回值(可选) - - 使用示例: - LoggerConfig.log_function_exit(logger, "process_data", result={"status": "success"}) - """ - if result is not None: - logger.debug(f"退出函数 {func_name},返回值: {result}") - else: - logger.debug(f"退出函数 {func_name}") - - @classmethod - def log_api_request(cls, logger: logging.Logger, method: str, url: str, **kwargs): - """ - 记录API请求日志 - - 标准化API请求的日志记录,包含HTTP方法、URL和请求参数。 - 有助于API调用的监控和调试。 - - 参数: - logger: 日志器实例 - method: HTTP方法(GET, POST, PUT, DELETE等) - url: 请求URL - **kwargs: 请求参数(可选) - - 使用示例: - LoggerConfig.log_api_request(logger, "POST", "https://api.example.com/users", - headers={"Authorization": "Bearer xxx"}) - """ - logger.info(f"API请求 - {method} {url}") - if kwargs: - logger.debug(f"请求参数: {kwargs}") - - @classmethod - def log_api_response(cls, logger: logging.Logger, status_code: int, response_time: float = None): - """ - 记录API响应日志 - - 记录API响应的状态码和响应时间,用于性能监控和问题诊断。 - - 参数: - logger: 日志器实例 - status_code: HTTP状态码 - response_time: 响应时间(秒,可选) - - 使用示例: - LoggerConfig.log_api_response(logger, 200, 0.156) - """ - if response_time: - logger.info(f"API响应 - 状态码: {status_code}, 响应时间: {response_time:.3f}s") - else: - logger.info(f"API响应 - 状态码: {status_code}") - - @classmethod - def log_error_with_context(cls, logger: logging.Logger, error: Exception, context: str = ""): - """ - 记录带上下文的错误日志 - - 提供丰富的错误信息记录,包含异常类型、错误消息、上下文信息和详细堆栈。 - 这是错误处理的标准方法。 - - 参数: - logger: 日志器实例 - error: 异常对象 - context: 错误发生的上下文描述(可选) - - 使用示例: - try: - risky_operation() - except Exception as e: - LoggerConfig.log_error_with_context(logger, e, "处理用户请求时") - """ - if context: - logger.error(f"错误发生在 {context}: {type(error).__name__}: {str(error)}") - else: - logger.error(f"错误: {type(error).__name__}: {str(error)}") - # 记录详细的异常堆栈信息(仅在DEBUG级别显示) - logger.debug("错误详情:", exc_info=True) - - -# ==================== 便捷函数 ==================== - -def get_logger(name: str) -> logging.Logger: - """ - 获取日志器的便捷函数 - - 这是LoggerConfig.get_logger的简化版本,提供更简洁的调用方式。 - 推荐在模块级别使用这个函数获取日志器。 - - 参数: - name: 日志器名称,通常使用 __name__ - - 返回: - logging.Logger: 配置好的日志器实例 - - 使用示例: - logger = get_logger(__name__) - """ - return LoggerConfig.get_logger(name) - - -def setup_logging(**kwargs) -> str: - """ - 设置日志的便捷函数 - - 这是LoggerConfig.setup_logging的简化版本,支持所有相同的参数。 - - 参数: - **kwargs: 传递给LoggerConfig.setup_logging的所有参数 - - 返回: - str: 日志文件路径 - - 使用示例: - log_file = setup_logging(log_level=logging.DEBUG, console_output=False) - """ - return LoggerConfig.setup_logging(**kwargs) - - -# ==================== 装饰器 ==================== - -def log_function_calls(logger: Optional[logging.Logger] = None): - """ - 函数调用日志装饰器 - - 这个装饰器自动记录函数的调用和返回,包括参数和返回值。 - 主要用于调试和性能分析,在生产环境中通常设置为DEBUG级别。 - - 特性: - - 自动记录函数入口和出口 - - 记录函数参数(kwargs) - - 记录返回值 - - 自动处理异常并记录错误上下文 - - 支持自定义日志器或自动获取 - - 参数: - logger: 可选的日志器实例,None时自动获取函数所在模块的日志器 - - 返回: - 装饰器函数 - - 使用示例: - @log_function_calls() - def process_user_data(user_id, action="login"): - # 函数实现 - return {"status": "success"} - - # 或者指定日志器 - @log_function_calls(logger=my_logger) - def another_function(): - pass - - 注意事项: - - 会记录所有kwargs参数,注意不要记录敏感信息 - - 返回值也会被记录,大对象可能影响性能 - - 异常会被重新抛出,不会被吞掉 - """ - def decorator(func): - def wrapper(*args, **kwargs): - nonlocal logger - # 如果没有提供日志器,自动获取函数所在模块的日志器 - if logger is None: - logger = get_logger(func.__module__) - - func_name = func.__name__ - - # 记录函数入口(只记录kwargs,避免记录过多信息) - LoggerConfig.log_function_entry(logger, func_name, **kwargs) - - try: - # 执行原函数 - result = func(*args, **kwargs) - - # 记录函数出口和返回值 - LoggerConfig.log_function_exit(logger, func_name, result) - return result - - except Exception as e: - # 记录异常信息并重新抛出 - LoggerConfig.log_error_with_context(logger, e, f"函数 {func_name}") - raise - - return wrapper - return decorator - - -# ==================== 测试代码 ==================== - -if __name__ == "__main__": - """ - 日志配置测试代码 - - 这个测试代码演示了日志系统的基本功能,包括: - 1. 日志系统初始化 - 2. 不同级别的日志输出 - 3. 日志文件路径获取 - 4. 装饰器功能测试 - - 运行方式: - python -m lzwcai_mcp_iot.src.util.logger_config - """ - # 测试代码只在直接运行此模块时执行,不在导入时执行 - # 以下代码已注释避免MCP服务器启动时执行 - pass - - # # 初始化日志系统(DEBUG级别,同时输出到控制台和文件) - # log_file = setup_logging(log_level=logging.DEBUG) - # logger = get_logger(__name__) - - # logger.info("开始测试日志配置...") - - # # 测试不同级别的日志输出 - # logger.debug("这是一个调试消息 - 用于开发调试") - # logger.info("这是一个信息消息 - 记录重要信息") - # logger.warning("这是一个警告消息 - 提醒注意事项") - # logger.error("这是一个错误消息 - 记录错误情况") - - # # 测试工具方法 - # LoggerConfig.log_api_request(logger, "GET", "https://api.example.com/test") - # LoggerConfig.log_api_response(logger, 200, 0.123) - - # # 测试装饰器 - # @log_function_calls() - # def test_function(param1, param2="default"): - # """测试函数""" - # return {"result": "success", "param1": param1} - - # # 调用测试函数 - # result = test_function("test_value", param2="custom") - - # # 输出日志文件位置 - # logger.info(f"日志文件位置: {log_file}") - # logger.info("日志配置测试完成!") diff --git a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/vector_service.py b/lzwcai_mcp_iot/lzwcai_mcp_iot/src/vector_service.py deleted file mode 100644 index c415f13..0000000 --- a/lzwcai_mcp_iot/lzwcai_mcp_iot/src/vector_service.py +++ /dev/null @@ -1,733 +0,0 @@ -""" -向量服务模块 - -该模块提供了向量数据的增删改查操作接口。 -""" - -import requests -import json -from typing import Dict, Any, Optional, List -from ..config import CONFIG -from .logger_config import get_logger - -# 延迟初始化日志器,避免在导入时立即执行 -logger = None - -def _ensure_logger(): - """确保日志器已初始化""" - global logger - if logger is None: - logger = get_logger(__name__) - return logger - - -class VectorService: - """向量服务类,提供向量数据的增删改查功能""" - - def __init__(self, base_url: str = None): - """ - 初始化向量服务 - - 参数: - base_url: API服务的基础URL - """ - logger = _ensure_logger() - self.base_url = base_url or CONFIG["vector_api_base_url"] - self.session = requests.Session() - # 设置默认超时 - self.session.timeout = CONFIG["request_timeout"] - logger.info(f"VectorService初始化完成,API基础URL: {self.base_url}") - - def init_vector_store( - self, - keyId: str, - ) -> Dict[str, Any]: - """ - 调用初始化向量存储API接口 - - 参数: - keyId: 企业或项目的唯一标识符 - - 返回: - Dict[str, Any]: API返回的响应数据 - """ - if not keyId or not isinstance(keyId, str): - error_msg = "keyId参数是必需的且必须是字符串" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - endpoint = f"{self.base_url}/iot/companies/recreate" - - # 构建请求数据 - payload = { - "company_id": keyId, - "company_name": "公司名称", - "enterprise_id": keyId, - "description": "公司描述", - } - - # 设置请求头 - headers = {"Content-Type": "application/json"} - - try: - logger.info(f"初始化向量库,keyId: {keyId}") - # 发送POST请求 - response = self.session.post( - endpoint, - headers=headers, - data=json.dumps(payload), - timeout=CONFIG["request_timeout"], - ) - # 检查响应状态 - response.raise_for_status() - # 返回JSON响应 - result = response.json() - logger.info(f"向量库初始化成功: {result}") - return result - - except requests.exceptions.Timeout: - error_msg = f"向量库初始化请求超时,keyId: {keyId}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except requests.RequestException as e: - error_msg = f"向量库初始化API请求失败: {str(e)}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except Exception as e: - error_msg = f"向量库初始化发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - def delete_vector(self, keyId: str) -> Dict[str, Any]: - """ - 删除向量数据(模拟实现) - - 参数: - keyId: 要删除的向量唯一标识 - - 返回: - Dict[str, Any]: 包含删除结果的字典 - """ - if not keyId or not isinstance(keyId, str): - error_msg = "keyId参数是必需的且必须是字符串" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - logger.info(f"删除向量数据,keyId: {keyId}") - # 模拟删除向量的结果 - return { - "status": "success", - "message": "向量删除成功", - "data": {"keyId": keyId, "deleted_at": "2023-07-15T11:45:00Z"}, - } - - def update_vector( - self, - keyId: str, - ) -> Dict[str, Any]: - """ - 更新向量数据 - - 参数: - keyId: 要更新的向量唯一标识 - - 返回: - Dict[str, Any]: 包含更新结果的字典 - """ - if not keyId or not isinstance(keyId, str): - error_msg = "keyId参数是必需的且必须是字符串" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - endpoint = f"{self.base_url}/iot/vector-store/update" - - # 构建请求数据 - payload = {"keyId": keyId} - - # 设置请求头 - headers = {"Content-Type": "application/json"} - - try: - logger.info(f"更新向量数据,keyId: {keyId}") - # 发送POST请求 - response = self.session.post( - endpoint, - headers=headers, - data=json.dumps(payload), - timeout=CONFIG["request_timeout"], - ) - # 检查响应状态 - response.raise_for_status() - # 返回JSON响应 - result = response.json() - logger.info(f"向量数据更新成功: {result}") - return result - - except requests.exceptions.Timeout: - error_msg = f"向量数据更新请求超时,keyId: {keyId}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except requests.RequestException as e: - error_msg = f"更新向量数据失败: {str(e)}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except Exception as e: - error_msg = f"更新向量数据发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - def query_vector( - self, - keyId: str, - device: Optional[str] = None, - location: Optional[str] = None, - operation: Optional[str] = None, - operation_param: Optional[str] = None, - ) -> Dict[str, Any]: - """ - 查询向量数据 - - 参数: - keyId: 企业或项目的唯一标识符 - device: 设备名称(可选) - location: 设备位置(可选) - operation: 操作类型(可选) - operation_param: 操作参数(可选) - - 返回: - Dict[str, Any]: 包含查询结果的字典 - """ - if not keyId or not isinstance(keyId, str): - error_msg = "keyId参数是必需的且必须是字符串" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - # 验证至少有一个查询参数 - if not any([device, location, operation]): - error_msg = "至少需要提供device、location或operation中的一个参数" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - endpoint = f"{self.base_url}/iot/companies/{keyId}/search" - - # 构建请求数据 - payload = {} - - # 添加可选参数 - if device: - payload["device"] = device - if location: - payload["location"] = location - if operation: - payload["operation"] = operation - if operation_param: - payload["operation_param"] = operation_param - - # 设置请求头 - headers = { - "Content-Type": "application/json", - } - - try: - logger.info( - f"查询向量数据,keyId: {keyId}, device: {device}, location: {location}, operation: {operation}, operation_param: {operation_param}" - ) - # 发送POST请求 - response = self.session.post( - endpoint, - headers=headers, - data=json.dumps(payload), - timeout=CONFIG["request_timeout"], - ) - # 检查响应状态 - response.raise_for_status() - # 返回JSON响应 - result = response.json() - logger.info( - f"向量查询成功,返回结果数量: {result.get('data', {}).get('total_results', 0)}" - ) - return result - - except requests.exceptions.Timeout: - error_msg = f"向量查询请求超时,keyId: {keyId}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except requests.RequestException as e: - error_msg = f"查询向量数据失败: {str(e)}" - logger.error(error_msg) - return { - "status": "error", - "message": error_msg, - "data": None, - } - except Exception as e: - error_msg = f"查询向量数据发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return { - "status": "error", - "message": error_msg, - "data": None, - } - - def analyze_search_results(self, search_results: List[List[Any]]) -> Dict[str, Any]: - """ - 调用搜索结果分析接口 - - 参数: - search_results: 搜索结果数据,格式为包含设备信息、评分和详细评分的嵌套数组 - [[device_info, score, score_details], ...] - - 返回: - Dict[str, Any]: 分析结果,包含处理后的数据和响应信息 - """ - logger.info("开始分析搜索结果...") - logger.debug(f"输入搜索结果数量: {len(search_results) if search_results else 0}") - - if not search_results or not isinstance(search_results, list): - error_msg = "无效的搜索结果数据:search_results必须是一个非空数组" - logger.error(error_msg) - return {"code": 400, "msg": error_msg, "data": None} - - # API配置 - url = f"{self.base_url}/iot/analyze/search-results" - headers = {"Content-Type": "application/json"} - - # 构建请求数据 - request_data = { - "search_results": search_results - } - - try: - logger.info(f"正在调用搜索结果分析接口: {url}") - logger.debug(f"请求数据大小: {len(search_results)} 条搜索结果") - - # 发送POST请求 - response = self.session.post( - url=url, - headers=headers, - data=json.dumps(request_data), - timeout=CONFIG["request_timeout"], - ) - - # 处理响应 - if response.status_code == 200: - try: - result = response.json() - logger.info(f"搜索结果分析成功,返回数据类型: {type(result)}") - - # 处理标准格式的响应 {code, msg, data} - if isinstance(result, dict) and "code" in result: - # 检查接口返回的业务状态码 - if result.get("code") == 200: - data = result.get("data", {}) - filtered_results = data.get("filtered_results", []) - logger.info(f"搜索结果分析成功,返回过滤结果数量: {len(filtered_results) if filtered_results else 0}") - # 返回包含filtered_results的标准格式 - return filtered_results - else: - # 接口返回了错误状态码,直接返回原始响应 - logger.warning(f"接口返回业务错误: code={result.get('code')}, msg={result.get('msg')}") - return result - - # 如果接口直接返回数据而不是标准格式,包装成标准格式 - if isinstance(result, dict) and "code" not in result: - logger.info("接口返回非标准格式,进行包装处理") - return {"code": 200, "msg": "分析成功", "data": result} - - logger.debug(f"返回原始分析结果: {type(result)}") - return result - except ValueError as e: - error_msg = f"响应JSON解析失败: {str(e)}" - logger.error(error_msg) - return {"code": 500, "msg": error_msg, "data": None} - else: - error_msg = f"API请求失败,状态码: {response.status_code}, 响应: {response.text}" - logger.error(error_msg) - return { - "code": response.status_code, - "msg": error_msg, - "data": None, - } - - except requests.exceptions.Timeout: - error_msg = "搜索结果分析接口请求超时" - logger.error(error_msg) - return {"code": 408, "msg": error_msg, "data": None} - except requests.exceptions.RequestException as e: - error_msg = f"搜索结果分析接口网络请求异常: {str(e)}" - logger.error(error_msg) - return {"code": 500, "msg": error_msg, "data": None} - except Exception as e: - error_msg = f"搜索结果分析接口调用异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return {"code": 500, "msg": error_msg, "data": None} - - def check_vector_store_status(self, keyId: str) -> bool: - """ - 检查向量库状态 - - 参数: - keyId: 企业或项目的唯一标识符 - - 返回: - bool: 向量库是否存在 - """ - if not keyId or not isinstance(keyId, str): - logger.error(f"无效的keyId参数: {keyId}") - return False - - endpoint = f"{self.base_url}/iot/companies/{keyId}/exists" - - # 设置请求头 - headers = { - "Accept": "*/*", - } - - try: - logger.info(f"检查向量库状态,keyId: {keyId}") - # 发送GET请求 - response = self.session.get( - endpoint, - headers=headers, - timeout=CONFIG["request_timeout"], - ) - # 检查响应状态 - response.raise_for_status() - # 从响应中提取exists字段 - result = response.json() - exists = result.get("data", {}).get("exists", False) - logger.info(f"向量库状态检查完成,keyId: {keyId}, exists: {exists}") - return exists - - except requests.exceptions.Timeout: - logger.error(f"检查向量库状态请求超时,keyId: {keyId}") - return False - except requests.RequestException as e: - logger.error(f"检查向量库状态失败: {str(e)}") - return False - except Exception as e: - logger.error(f"检查向量库状态发生未知异常: {str(e)}", exc_info=True) - return False - - def query_devices_by_location( - self, - keyId: str, - location: Optional[str] = None, - ) -> Dict[str, Any]: - """ - 根据位置查询设备列表 - - 参数: - keyId: 企业或项目的唯一标识符 - location: 设备位置(可选) - - 返回: - Dict[str, Any]: 包含设备列表的数据字典 - """ - logger = _ensure_logger() - - if not keyId or not isinstance(keyId, str): - error_msg = "keyId参数是必需的且必须是字符串" - logger.error(error_msg) - return { - "devices": [], - "count": 0, - "location_filter": location or "" - } - - endpoint = f"{self.base_url}/iot/companies/{keyId}/devices" - - # 构建请求数据 - payload = {} - if location: - payload["location"] = location - - # 设置请求头 - headers = { - "Content-Type": "application/json", - } - - try: - logger.info(f"查询设备列表,keyId: {keyId}, location: {location}") - # 发送POST请求 - response = self.session.post( - endpoint, - headers=headers, - data=json.dumps(payload), - timeout=CONFIG["request_timeout"], - ) - # 检查响应状态 - response.raise_for_status() - # 解析JSON响应 - result = response.json() - - # 检查API返回的业务状态码 - if result.get("code") == 200: - data = result.get("data", {}) - device_count = data.get("count", 0) - logger.info(f"设备列表查询成功,返回设备数量: {device_count}") - # 返回data部分 - return data - else: - error_msg = f"API返回业务错误: code={result.get('code')}, msg={result.get('msg')}" - logger.error(error_msg) - return { - "devices": [], - "count": 0, - "location_filter": location or "", - "error": error_msg - } - - except requests.exceptions.Timeout: - error_msg = f"查询设备列表请求超时,keyId: {keyId}" - logger.error(error_msg) - return { - "devices": [], - "count": 0, - "location_filter": location or "", - "error": error_msg - } - except requests.RequestException as e: - error_msg = f"查询设备列表失败: {str(e)}" - logger.error(error_msg) - return { - "devices": [], - "count": 0, - "location_filter": location or "", - "error": error_msg - } - except Exception as e: - error_msg = f"查询设备列表发生未知异常: {str(e)}" - logger.error(error_msg, exc_info=True) - return { - "devices": [], - "count": 0, - "location_filter": location or "", - "error": error_msg - } - - def check_and_filter_devices( - self, - device_list: List[Any], - device_op: Any - ) -> Dict[str, Any]: - """ - 检查设备分数并过滤有效设备 - - 参数: - device_list: 设备列表,格式为 [[device_info, score, score_details], ...] - device_op: DeviceOperator实例,用于调用设备检查和预处理方法 - - 返回: - Dict[str, Any]: 包含检查结果的字典 - - 如果有有效设备:返回有效设备列表 - - 如果无有效设备:返回候选设备列表和错误码 - """ - logger = _ensure_logger() - logger.info("开始检查设备分数并过滤有效设备") - logger.debug(f"输入设备列表数量: {len(device_list) if device_list else 0}") - - # 判断设备分数是否达标,给每个设备添加checkResult字段 - checked_device_list = [] - has_valid_device = False - - for device_item in device_list: - # deviceList格式: [[device_info, score, score_details], ...] - if isinstance(device_item, list) and len(device_item) >= 3: - device_info, score, score_details = device_item[0], device_item[1], device_item[2] - - # 调用check_device_results_score方法检查分数 - check_result = device_op.check_device_results_score(score_details) - - # 将checkResult添加到device_info中 - if isinstance(device_info, dict): - device_info["checkResult"] = check_result.get("checkResult", False) - - # 检查是否有通过验证的设备 - if device_info["checkResult"]: - has_valid_device = True - - # 重新构建device_item - checked_device_list.append([device_info, score, score_details]) - else: - # 如果格式不正确,保持原样但添加checkResult为False - if isinstance(device_item, dict): - device_item["checkResult"] = False - checked_device_list.append(device_item) - - # 如果没有任何设备通过验证,返回候选设备列表 - if not has_valid_device: - logger.warning("匹配分数不够准确,返回候选设备列表") - candidate_list = device_op.preprocess_results(checked_device_list) - return { - "code": 400, - "msg": "设备匹配分数不够准确,无法为用户智能操作设备,返回候选设备列表供用户选择; ", - "data": { - "candidates": candidate_list, - "total": len(candidate_list) - }, - "is_candidate": True - } - - # 过滤出checkResult为True的设备 - valid_devices = [] - for device_item in checked_device_list: - if isinstance(device_item, list) and len(device_item) >= 1: - device_info = device_item[0] - if isinstance(device_info, dict) and device_info.get("checkResult", False): - valid_devices.append(device_item) - - logger.info(f"过滤完成,有效设备数量: {len(valid_devices)}") - return { - "valid_devices": valid_devices, - "is_candidate": False - } - - def __del__(self): - """析构函数,关闭会话""" - if hasattr(self, "session"): - self.session.close() - - -# 使用示例 -if __name__ == "__main__": - # 测试代码只在直接运行此模块时执行,不在导入时执行 - # 以下代码已注释避免MCP服务器启动时执行 - pass - - # vector_service = VectorService(base_url="http://192.168.0.76:5002") - - # # 测试初始化向量存储 - # # init_result = vector_service.init_vector_store( - # # keyId="1945419433873575938", - # # ) - # # print("初始化向量存储结果:", init_result) - - # # 测试查询向量420 - # # query_result = vector_service.query_vector( - # # **{ - # # "keyId": "1932095424144715777", - # # "location": "灵泽办公区", - # # "device": "过道吊灯", - # # "operation": "开关 按键", - # # "top_k": 2, - # # "auto_create": True, - # # } - # # ) - # # print("查询向量结果:", query_result) - - # # # 检查向量库状态 - # # check_result = vector_service.check_vector_store_status("1932095424144715777") - # # logger.info(f"检查向量库状态结果: {check_result}") - - # # 测试搜索结果分析接口 - # # logger.info("=== 测试搜索结果分析接口 ===") - # sample_search_results = [ - # [ - # { - # "id": "0899574c-ff51-4983-ae48-667cccc08e9c", - # "location": { - # "key": "35", - # "description": "灵泽展厅;灵泽展区" - # }, - # "device": { - # "key": "switch.zimi_cn_1121232402_dhkg05_on_p_2_1", - # "description": "吊灯;灯;照明灯" - # }, - # "operation": { - # "key": "turn_off", - # "description": "关闭;关" - # }, - # "operation_params": [], - # "deviceType": "light" - # }, - # 0.9025000000000001, - # { - # "location_score": 0.95, - # "device_score": 0.75, - # "operation_score": 0.95, - # "operation_param_score": 1.0, - # "combined_score": 0.9025000000000001, - # "search_method": "location_first_strict" - # } - # ], - # [ - # { - # "id": "76f71468-356b-4710-a7ea-da7ddac93fab", - # "location": { - # "key": "35", - # "description": "灵泽展厅;灵泽展区" - # }, - # "device": { - # "key": "climate.qjiang_cn_741362991_wb20", - # "description": "空调;制冷设备" - # }, - # "operation": { - # "key": "turn_off", - # "description": "关空调;关闭空调;关闭;关;闭空调;空调" - # }, - # "operation_params": [], - # "deviceType": "airConditioner" - # }, - # 0.7150000000000001, - # { - # "location_score": 0.95, - # "device_score": 0.0, - # "operation_score": 0.95, - # "operation_param_score": 1.0, - # "combined_score": 0.7150000000000001, - # "search_method": "location_first_strict" - # } - # ] - # ] - - # # analyze_result = vector_service.analyze_search_results(sample_search_results) - # # logger.info(f"搜索结果分析结果: {analyze_result}") diff --git a/lzwcai_mcp_iot/main.py b/lzwcai_mcp_iot/main.py deleted file mode 100644 index 7309ce9..0000000 --- a/lzwcai_mcp_iot/main.py +++ /dev/null @@ -1,17 +0,0 @@ -import os - -# 设置企业ID(必需) -os.environ["ENTERPRISE_ID"] = "1952978233106669569" - -# 设置API地址 -os.environ["DEVICE_API_BASE_URL"] = "http://192.168.2.236:8088" -os.environ["VECTOR_API_BASE_URL"] = "http://192.168.2.236:5002" - -# 注意:employeeId 已不再需要,现在直接使用 ENTERPRISE_ID -# os.environ["employeeId"] = "1986712221817815042" # 已废弃 - -# 导入模块 -from lzwcai_mcp_iot.iot_device_tool import main - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/lzwcai_mcp_iot/pyproject.toml b/lzwcai_mcp_iot/pyproject.toml deleted file mode 100644 index d1e8ded..0000000 --- a/lzwcai_mcp_iot/pyproject.toml +++ /dev/null @@ -1,63 +0,0 @@ -[build-system] -requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "lzwcai-mcp-iot" -version = "0.3.3" -description = "IoT设备控制服务器,使用FastMCP框架提供设备操作功能" -authors = [ - {name = "LZWCAI开发团队", email = "dev@lzwcai.com"} -] -readme = "README.md" -requires-python = ">=3.8" -license = "LicenseRef-Proprietary" -classifiers = [ - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Operating System :: OS Independent", -] -dependencies = [ - "fastmcp>=0.1.0", - "requests" -] - -[project.optional-dependencies] -dev = [ - "pytest>=7.0.0", - "black>=23.1.0", - "isort>=5.12.0", - "flake8>=6.0.0", - "mypy>=1.0.0", -] - - -[project.scripts] -lzwcai-mcp-iot = "lzwcai_mcp_iot.iot_device_tool:main" - -[tool.setuptools] -packages = ["lzwcai_mcp_iot", "lzwcai_mcp_iot.src"] - - -[tool.black] -line-length = 88 -target-version = ["py38", "py39", "py310", "py311"] -include = '\.pyi?$' - -[tool.isort] -profile = "black" -line_length = 88 - -[tool.mypy] -python_version = "3.8" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = false -disallow_incomplete_defs = false - -[tool.pytest.ini_options] -minversion = "7.0" -testpaths = ["tests"] diff --git a/lzwcai_mcp_iot/setup.cfg b/lzwcai_mcp_iot/setup.cfg deleted file mode 100644 index 8bfd5a1..0000000 --- a/lzwcai_mcp_iot/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 -