From 0d48341b738af27e6b92ab4d3c785e93dee8473d Mon Sep 17 00:00:00 2001 From: yuanzhipeng <2501363769@qq.com> Date: Thu, 11 Jun 2026 18:55:33 +0800 Subject: [PATCH] =?UTF-8?q?docs(agile-db):=20=E7=A7=BB=E9=99=A4=E8=BF=87?= =?UTF-8?q?=E6=97=B6=E7=9A=84=20AgileDB=20=E6=8A=80=E8=83=BD=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E8=AE=A1=E5=88=92=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除 .kilo/plans/lzwcai-agile-db-skill.md 文件,该文件包含 AgileDB 数据库操作技能的详细实现计划,包括数据源管理、 数据库与表管理、表数据操作和 SQL 执行等场景化工作流指导。 同时移除对应的技能压缩包文件。 --- .kilo/plans/lzwcai-agile-db-skill.md | 451 ------- .kilo/skills/lzwcai-agile-db.zip | Bin 9699 -> 0 bytes .kilo/skills/lzwcai-agile-db/SKILL.md | 893 -------------- .kilo/skills/mcp-tool-testing.zip | Bin 5992 -> 0 bytes .kilo/skills/mcp-tool-testing/SKILL.md | 356 ------ .vscode/settings.json | 2 - .../PKG-INFO | 12 - .../README.md | 37 - .../PKG-INFO | 12 - .../SOURCES.txt | 23 - .../dependency_links.txt | 1 - .../entry_points.txt | 2 - .../requires.txt | 7 - .../top_level.txt | 1 - .../main.py | 61 - .../pyproject.toml | 32 - .../setup.cfg | 4 - .../src/__init__.py | 0 .../src/__pycache__/__init__.cpython-312.pyc | Bin 265 -> 0 bytes .../__pycache__/create_mcp.cpython-312.pyc | Bin 10218 -> 0 bytes .../src/chat/__init__.py | 0 .../chat/__pycache__/__init__.cpython-312.pyc | Bin 270 -> 0 bytes .../__pycache__/chat_server.cpython-312.pyc | Bin 843 -> 0 bytes .../src/chat/chat_server.py | 7 - .../completion_server.cpython-312.pyc | Bin 8246 -> 0 bytes .../src/completion/completion_server.py | 212 ---- .../src/completion/test.py | 104 -- .../src/core/__init__.py | 0 .../src/core/core_server.py | 172 --- .../src/create_mcp.py | 318 ----- .../src/create_mcp_util.py | 373 ------ .../__pycache__/task_instance.cpython-312.pyc | Bin 2038 -> 0 bytes .../src/difyTaskCall/task_instance.py | 53 - .../tool_translation.cpython-312.pyc | Bin 5394 -> 0 bytes .../__pycache__/translator.cpython-312.pyc | Bin 2582 -> 0 bytes .../src/utils/tool_translation.py | 153 --- .../src/utils/translator.py | 64 - .../src/workflow/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 274 -> 0 bytes .../workflow_server.cpython-312.pyc | Bin 7404 -> 0 bytes .../src/workflow/workflow_server.py | 175 --- .../PKG-INFO | 12 - .../README.md | 0 .../dify-api.md | 267 ----- .../logs/.gitkeep | 2 - ...wcai_demp_tool_server_dify_to_mcp_test.log | 156 --- .../PKG-INFO | 12 - .../SOURCES.txt | 27 - .../dependency_links.txt | 1 - .../entry_points.txt | 2 - .../requires.txt | 7 - .../top_level.txt | 1 - .../main.py | 86 -- .../pyproject.toml | 32 - .../setup.cfg | 4 - .../src/__init__.py | 0 .../src/__pycache__/__init__.cpython-312.pyc | Bin 282 -> 0 bytes .../__pycache__/create_mcp.cpython-312.pyc | Bin 10393 -> 0 bytes .../create_mcp_utils.cpython-312.pyc | Bin 13039 -> 0 bytes .../src/chat/__init__.py | 0 .../chat/__pycache__/__init__.cpython-312.pyc | Bin 287 -> 0 bytes .../__pycache__/chat_server.cpython-312.pyc | Bin 860 -> 0 bytes .../src/chat/chat_server.py | 7 - .../completion_server.cpython-312.pyc | Bin 7924 -> 0 bytes .../src/completion/completion_server.py | 203 ---- .../src/completion/test.py | 104 -- .../src/core/__init__.py | 0 .../src/core/core_server.py | 213 ---- .../src/create_mcp.py | 253 ---- .../src/create_mcp_update.py | 320 ----- .../src/create_mcp_utils.py | 342 ------ .../__pycache__/task_instance.cpython-312.pyc | Bin 2055 -> 0 bytes .../src/difyTaskCall/task_instance.py | 53 - .../dify_workflow_schema.cpython-312.pyc | Bin 8871 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 19670 -> 0 bytes .../__pycache__/upload_file.cpython-312.pyc | Bin 19696 -> 0 bytes .../src/utils/dify_workflow_schema.py | 400 ------- .../src/utils/logger_config.py | 552 --------- .../src/utils/tool_translation.py | 153 --- .../src/utils/translator.py | 64 - .../src/utils/upload_file.py | 461 -------- .../src/workflow/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 291 -> 0 bytes .../workflow_server.cpython-312.pyc | Bin 15245 -> 0 bytes .../src/workflow/workflow_server.py | 352 ------ lzwcai_mcp_agile_db/README.md | 85 -- .../lzwcai_mcp_agile_db/.python-version | 1 - .../lzwcai_mcp_agile_db/README.md | 0 .../lzwcai_mcp_agile_db/__init__.py | 5 - .../__pycache__/__init__.cpython-312.pyc | Bin 323 -> 0 bytes .../__pycache__/server.cpython-312.pyc | Bin 5979 -> 0 bytes .../logs/lzwcai_mcp_agile_db.log | 202 ---- .../logs/lzwcai_mcp_agile_db_error.log | 15 - .../lzwcai_mcp_agile_db/server.py | 138 --- .../lzwcai_mcp_agile_db/tools/__init__.py | 18 - .../__pycache__/__init__.cpython-312.pyc | Bin 833 -> 0 bytes .../tools/__pycache__/_base.cpython-312.pyc | Bin 4053 -> 0 bytes .../__pycache__/api_keys.cpython-312.pyc | Bin 5420 -> 0 bytes .../__pycache__/data_import.cpython-312.pyc | Bin 3259 -> 0 bytes .../database_tables.cpython-312.pyc | Bin 6876 -> 0 bytes .../__pycache__/datasources.cpython-312.pyc | Bin 8404 -> 0 bytes .../tools/__pycache__/skills.cpython-312.pyc | Bin 6027 -> 0 bytes .../__pycache__/sql_execution.cpython-312.pyc | Bin 1386 -> 0 bytes .../__pycache__/subscriptions.cpython-312.pyc | Bin 1293 -> 0 bytes .../__pycache__/table_data.cpython-312.pyc | Bin 6518 -> 0 bytes .../lzwcai_mcp_agile_db/tools/_base.py | 116 -- .../lzwcai_mcp_agile_db/tools/api_keys.py | 121 -- .../lzwcai_mcp_agile_db/tools/data_import.py | 72 -- .../tools/database_tables.py | 151 --- .../lzwcai_mcp_agile_db/tools/datasources.py | 173 --- .../lzwcai_mcp_agile_db/tools/skills.py | 137 --- .../tools/sql_execution.py | 38 - .../tools/subscriptions.py | 29 - .../lzwcai_mcp_agile_db/tools/table_data.py | 137 --- .../lzwcai_mcp_agile_db/utils/__init__.py | 13 - .../__pycache__/__init__.cpython-312.pyc | Bin 531 -> 0 bytes .../__pycache__/api_client.cpython-312.pyc | Bin 11555 -> 0 bytes .../__pycache__/env_config.cpython-312.pyc | Bin 2150 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 5588 -> 0 bytes .../lzwcai_mcp_agile_db/utils/api_client.py | 200 ---- .../lzwcai_mcp_agile_db/utils/env_config.py | 60 - .../utils/logger_config.py | 121 -- .../数据库管理平台-MCP工具设计方案.md | 621 ---------- .../数据库管理平台-功能总览.md | 303 ----- lzwcai_mcp_agile_db/main.py | 12 - lzwcai_mcp_agile_db/pyproject.toml | 34 - lzwcai_mcp_api_converter/PKG-INFO | 12 - .../PKG-INFO | 12 - .../SOURCES.txt | 25 - .../dependency_links.txt | 1 - .../entry_points.txt | 2 - .../requires.txt | 6 - .../top_level.txt | 1 - .../lzwcai_mcp_api_converter.log | 96 -- .../lzwcai_mcp_api_converter/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 213 -> 0 bytes .../lzwcai_mcp_api_converter/src/__init__.py | 0 .../src/__pycache__/__init__.cpython-312.pyc | Bin 243 -> 0 bytes .../__pycache__/create_mcp.cpython-312.pyc | Bin 34346 -> 0 bytes .../src/api_config.json | 1 - .../src/api_config_dcqwlucfo7h.json | 555 --------- .../src/api_config_w8kgb73ib3.json | 219 ---- .../src/business/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 252 -> 0 bytes .../__pycache__/business_util.cpython-312.pyc | Bin 25065 -> 0 bytes .../get_business_api.cpython-312.pyc | Bin 8473 -> 0 bytes .../src/business/business_util.py | 716 ----------- .../src/business/get_business_api.py | 208 ---- .../src/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-312.pyc | Bin 248 -> 0 bytes .../api_auth_service.cpython-312.pyc | Bin 42777 -> 0 bytes .../core/__pycache__/api_base.cpython-312.pyc | Bin 22656 -> 0 bytes .../__pycache__/core_server.cpython-312.pyc | Bin 42421 -> 0 bytes .../core/__pycache__/get_auth.cpython-312.pyc | Bin 12391 -> 0 bytes .../__pycache__/plugin_base.cpython-312.pyc | Bin 1133 -> 0 bytes .../src/core/api_auth_service.py | 966 --------------- .../src/core/api_base.py | 610 ---------- .../src/core/core_server.py | 1048 ----------------- .../src/core/get_auth.py | 301 ----- .../src/core/plugin_base.py | 22 - .../src/create_mcp.py | 820 ------------- .../src/util/__init__.py | 0 .../util/__pycache__/__init__.cpython-312.pyc | Bin 248 -> 0 bytes .../__pycache__/api_helper.cpython-312.pyc | Bin 15667 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 19573 -> 0 bytes .../__pycache__/nested_value.cpython-312.pyc | Bin 5863 -> 0 bytes .../src/util/api_helper.py | 412 ------- .../src/util/logger_config.py | 554 --------- .../src/util/nested_value.py | 168 --- lzwcai_mcp_api_converter/main.py | 12 - lzwcai_mcp_api_converter/pyproject.toml | 28 - lzwcai_mcp_api_converter/setup.cfg | 4 - lzwcai_mcp_iot/IoT设备工具说明.md | 140 --- lzwcai_mcp_iot/PKG-INFO | 22 - lzwcai_mcp_iot/README.md | 200 ---- .../lzwcai_mcp_iot.egg-info/PKG-INFO | 223 ---- .../lzwcai_mcp_iot.egg-info/SOURCES.txt | 19 - .../dependency_links.txt | 1 - .../lzwcai_mcp_iot.egg-info/entry_points.txt | 2 - .../lzwcai_mcp_iot.egg-info/requires.txt | 9 - .../lzwcai_mcp_iot.egg-info/top_level.txt | 1 - lzwcai_mcp_iot/lzwcai_mcp_iot.log | 159 --- lzwcai_mcp_iot/lzwcai_mcp_iot/__init__.py | 0 .../__pycache__/__init__.cpython-312.pyc | Bin 219 -> 0 bytes .../__pycache__/config.cpython-312.pyc | Bin 3664 -> 0 bytes .../iot_device_main.cpython-312.pyc | Bin 34766 -> 0 bytes .../iot_device_tool.cpython-312.pyc | Bin 17127 -> 0 bytes lzwcai_mcp_iot/lzwcai_mcp_iot/apimock.json | 44 - lzwcai_mcp_iot/lzwcai_mcp_iot/config.py | 99 -- .../lzwcai_mcp_iot/iot_device_tool.py | 410 ------- lzwcai_mcp_iot/lzwcai_mcp_iot/src/__init__.py | 0 .../src/__pycache__/__init__.cpython-312.pyc | Bin 223 -> 0 bytes .../device_operations.cpython-312.pyc | Bin 26507 -> 0 bytes ...evice_results_pretreatment.cpython-312.pyc | Bin 24910 -> 0 bytes .../src/__pycache__/init_mcp.cpython-312.pyc | Bin 4668 -> 0 bytes .../iot_device_dicts_prompt.cpython-312.pyc | Bin 25831 -> 0 bytes .../__pycache__/logger_config.cpython-312.pyc | Bin 18212 -> 0 bytes .../vector_service.cpython-312.pyc | Bin 22178 -> 0 bytes .../lzwcai_mcp_iot/src/device_operations.py | 751 ------------ .../src/device_results_pretreatment.py | 659 ----------- lzwcai_mcp_iot/lzwcai_mcp_iot/src/init_mcp.py | 131 --- .../src/iot_device_dicts_prompt.py | 216 ---- .../lzwcai_mcp_iot/src/logger_config.py | 558 --------- .../lzwcai_mcp_iot/src/vector_service.py | 733 ------------ lzwcai_mcp_iot/main.py | 17 - lzwcai_mcp_iot/pyproject.toml | 63 - lzwcai_mcp_iot/setup.cfg | 4 - 207 files changed, 21673 deletions(-) delete mode 100644 .kilo/plans/lzwcai-agile-db-skill.md delete mode 100644 .kilo/skills/lzwcai-agile-db.zip delete mode 100644 .kilo/skills/lzwcai-agile-db/SKILL.md delete mode 100644 .kilo/skills/mcp-tool-testing.zip delete mode 100644 .kilo/skills/mcp-tool-testing/SKILL.md delete mode 100644 .vscode/settings.json delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/PKG-INFO delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/README.md delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/main.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/pyproject.toml delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/setup.cfg delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/__pycache__/create_mcp.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/__pycache__/chat_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/chat/chat_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/__pycache__/completion_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/completion_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/completion/test.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/core/core_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/create_mcp_util.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/difyTaskCall/task_instance.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/tool_translation.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/__pycache__/translator.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/tool_translation.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/utils/translator.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/__pycache__/workflow_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp/lzwcai_demp_tool_server_dify_to_mcp/src/workflow/workflow_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/PKG-INFO delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/README.md delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/dify-api.md delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/logs/.gitkeep delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 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 delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/main.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/pyproject.toml delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/setup.cfg delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/__pycache__/create_mcp_utils.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/__pycache__/chat_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/chat/chat_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/__pycache__/completion_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/completion_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/completion/test.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/core/core_server.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_update.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/create_mcp_utils.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/__pycache__/task_instance.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/difyTaskCall/task_instance.py delete mode 100644 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 delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/__pycache__/upload_file.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/dify_workflow_schema.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/logger_config.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/tool_translation.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/translator.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/utils/upload_file.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__init__.py delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/__pycache__/workflow_server.cpython-312.pyc delete mode 100644 lzwcai_demp_tool_server_dify_to_mcp_test/lzwcai_demp_tool_server_dify_to_mcp_test/src/workflow/workflow_server.py delete mode 100644 lzwcai_mcp_agile_db/README.md delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/.python-version delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/README.md delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__init__.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/__pycache__/server.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db.log delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/logs/lzwcai_mcp_agile_db_error.log delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/server.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__init__.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/_base.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/api_keys.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/data_import.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/database_tables.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/datasources.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/skills.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/sql_execution.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/subscriptions.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/__pycache__/table_data.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/_base.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/api_keys.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/data_import.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/database_tables.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/datasources.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/skills.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/sql_execution.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/subscriptions.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/tools/table_data.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__init__.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/api_client.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/env_config.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/api_client.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/env_config.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/utils/logger_config.py delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-MCP工具设计方案.md delete mode 100644 lzwcai_mcp_agile_db/lzwcai_mcp_agile_db/数据库管理平台-功能总览.md delete mode 100644 lzwcai_mcp_agile_db/main.py delete mode 100644 lzwcai_mcp_agile_db/pyproject.toml delete mode 100644 lzwcai_mcp_api_converter/PKG-INFO delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/PKG-INFO delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/SOURCES.txt delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/dependency_links.txt delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/entry_points.txt delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/requires.txt delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.egg-info/top_level.txt delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter.log delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__init__.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__init__.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/__pycache__/create_mcp.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config.json delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_dcqwlucfo7h.json delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/api_config_w8kgb73ib3.json delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__init__.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/business_util.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/__pycache__/get_business_api.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/business_util.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/business/get_business_api.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__init__.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_auth_service.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/api_base.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/core_server.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/get_auth.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/__pycache__/plugin_base.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_auth_service.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/api_base.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/core_server.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/get_auth.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/core/plugin_base.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/create_mcp.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__init__.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/api_helper.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/__pycache__/nested_value.cpython-312.pyc delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/api_helper.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/logger_config.py delete mode 100644 lzwcai_mcp_api_converter/lzwcai_mcp_api_converter/src/util/nested_value.py delete mode 100644 lzwcai_mcp_api_converter/main.py delete mode 100644 lzwcai_mcp_api_converter/pyproject.toml delete mode 100644 lzwcai_mcp_api_converter/setup.cfg delete mode 100644 lzwcai_mcp_iot/IoT设备工具说明.md delete mode 100644 lzwcai_mcp_iot/PKG-INFO delete mode 100644 lzwcai_mcp_iot/README.md delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/PKG-INFO delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/SOURCES.txt delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/dependency_links.txt delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/entry_points.txt delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/requires.txt delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.egg-info/top_level.txt delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot.log delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/__init__.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/config.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_main.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/__pycache__/iot_device_tool.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/apimock.json delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/config.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/iot_device_tool.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__init__.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/__init__.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_operations.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/device_results_pretreatment.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/init_mcp.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/iot_device_dicts_prompt.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/logger_config.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/__pycache__/vector_service.cpython-312.pyc delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_operations.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/device_results_pretreatment.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/init_mcp.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/iot_device_dicts_prompt.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/logger_config.py delete mode 100644 lzwcai_mcp_iot/lzwcai_mcp_iot/src/vector_service.py delete mode 100644 lzwcai_mcp_iot/main.py delete mode 100644 lzwcai_mcp_iot/pyproject.toml delete mode 100644 lzwcai_mcp_iot/setup.cfg 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 72157c8aed1d1dd638a194d2d851c1449b954a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9699 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 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 5303d592a1209602192f3405e24cad07b1b248e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5992 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 PQ2trJ{@G(l{!ae^Xf`D> 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 c0b589d82385d341baaca8fc0a2f6b01f3657868..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 265 zcmX@j%ge<81dHv{GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!TH$IHQ<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKjPf5)!h%d>{&xwcV zj8Dl-s{{(f0}X@f#;>NhC^;rRJ~J<~BtBlRpz;@oO>TZlX-=wL5i8K)j6hrrVtiy~ KWMnL22C@Jc^;0td 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 e46ec7fa30fff71ec32f2dbed799df9e63e9f76a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10218 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 ZMi|0bQ~g)i@=rPIE1g-Ea20g9{|`llh1~!E 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 ab380630858e7ca7f3d35eeadf260027f864bf64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmX@j%ge<81dHv{GC=fW5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!TI*^RQ<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKjPf5)!h%d>{&xwcV zj8Dl-s{{(f0}X@f#;>NhC^;rMBe5hVK0Y%qvm`!Vub}c5hfQvNN@-52T@fqL^^8DV P3}Sp_W@Kb6Vg|AR=Nwfu 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 b9ab6033682ee792a1f9ea0f8922069193d5a981..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 843 zcmah{v5wO~5S?8+iMdMv2@t6ubhyZU06Kxtfe=kZ9YtC#-c4fST+FV+$x#Z56jwl< z_9PlkH2en@-N`LK5Md;wtC+E42n|f}=FQB`?Ci{bX|?tM<-?=l_$%sfWK`mAN!~`X z1`f|a!3q@n#LGw@QtmrJb)NL^ocn03K|`M@=yS!iqqug4u5wmx-&HPhu4>3@Y29yR z`$uD$JPC*C;}_2}@1?X_%uSRvWE=}Sv4OL53EfV1-a%Nyk}W|XD`m;iVwIJ`IqUh{ zP+O*k;5(UXbu|3ru~e$l>0Ew(`e2ZbyD}WaX7)-4$sk&s1TyT}MLMeZG>E%an>X5Y zV;M~3NdGq?l%B>Snav_mW(tMxM2Em)+w6azu_hP2sy^in}F4~<__g(Aa_MKDreKSWNWQ|1Hi3nA&-6lB;*+D3B zG!e4;7jtsTLyJ6Jk13 z^N4s;h}Ux&Z6l6NjG^yp=uze>0p%C>8(L!lw0wIHVFeq`Yj@9XYyj0qo0Hmjtkya^ r`T?lQY)b6;tSs<%(M#{J?B$`kg{O+j*N|)+#@J6@XRYsmP}KPaJ0H() 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 93fb769d5fbee13f148aa8918dd6c0c50f13f204..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8246 zcmb7JYj6`+mcIRxx+Ph%EraE^`~agE+et`x8WI~5LjqL&6xfSI_G^ISspDwCK%zdx)5j6d2;@a7 zM>CEQ4&4p>(eEG>s=H^qJ2y=2fH#P{E*hY90)_H4FR9Y+%6Nh&Wu#3VAs*73g5Jc> z?oBW4?bR-B$@)C09T-;DYXmQ=)e0r7nmb2?!=BM6Xc<1tsb6?CyC$685U;q83g?B%^HvQ#|3zz4=n3_NR`}tGvsvWQW z`#bX=zqQ%^N~_<~y4K!0>lgWt}7IXWf(rcocV|Tc&A;B*k3yRyqO+k-;%;U!^xLrpDUq_cO;CA_> zZ5{bO)o^5~60+;)r*ZUMZQs-rll#v(&p2o6I+C@0f01aQy;WTcf~Sz8pit7tVTF-G zq9>@Z!LjTDiqYo^k2!N6v^U`KJ19k$`_{XCg3B-PiZQnxpGOKw_(_2wMH7^YB}dUV zD&iesMwecLY#iM-BTLzPgC_=04aBUm!IZguyziR1RBbshxi8k5GCxvid0}#UtTJVG z%$hrs=FTZ9WqxA3@3szEYi7$=C(BpIMpNaT*ENet+q z4-2IpN)AdEN&}P}ltw5GP@14L3MRhfcwU4DO2%{!i&C~d|JuF4E8TyyOJRb+_3PIw z>|vKAI7dXEVp6%t6%0D1Ba$1xmzH!;Q2}vW1w@&UkyE1qV8B;ibYu#?@-R7sA~gIn z@UN3mG5G97hYo}I>1EoRhp+t9Ae>&V@TJvc{l(mRT1g<8xzG3_FjsWE?mpv7v{egb zvGFH)8p$-AJ%0+Bfv-Fe%W!^Y{wy-xi{4}1kkb>IpfW;p%&swnL-U!#L?OWaDVAZdH_?VXM=% zYj9bWuCPVvliXZYP1KyOu8msKYRy_XYi&tdTW%qW+Z^4VHdW7>8k45RSj%kFx@6P3 zsqLwzjWecAX;aOtsVQk{iajygygAvtd3s%{`KcMx)5S-8Z`zd6!;BdNYG}vn*Q6Ud za9NwKUz`1Y{OLc{cgO#B^0nB?_s71g?~c;nvo(K#iiG9r>(!>7EwsV`6bi1R9{*6k zJI_Ku&Wqk82o=^=UT9pN-Uk!v12Uq=*24Sf1CpnB`YdK462y_=nX^=clWCCw2IY7i z)_pR?>yN-oh`$SA!B&%ZsmSNrWWCJ781@o(F-MIUctgY}8;XgGrY%9J0dFHF*(4Ds zb+W0L5c5V&cq1ipNl|OWr1gMV92}BM_!1sfBX1Fr)K-p|Lzdjuk+)Q<6-wEx%?UTc zd94MCR^#(Mc#C>NCcaeWUPmG;8{sBOv5UQ1QJ*>(j9$|;1UJVey0>L$mempu)Wdgl>+_? zq*Bs75*iMOp0L^)Hf<01LxMllzJDwzV7>402S-AZQX&dcFyIF*bu_>WDx4@5cfb!O zlcbJxdU&w|HoPAO>4yaubdnSrFQVwOVkKgc3Ns_2!S;<|hIfTrn{QxMtT4jsu%e>R zZ5UwFFO3S~hz*Df6S0Laeslekj~0G=>e+pJ`@tYxc=xl-_6wxA0m$M;$Q)+T4u!&s z!fPV}m?H`u3`ilx-~nuP`aFI?QgpoFhK(x*DdZAE(x@jiEMnGFCr}t)z{N`n1<0&e z@G`w>?;WBT02KEHhK2ym^`kD)57?kofhFmN%XMb=>BJK|4}&#!#8Dix^Aj_swdI9VYYQ^vUO{!^{L67S7F%Z^PA(tFl@3n-OzmQ*qLK-eX8Nnsa@YS zY`wDPTRz$I{G7cl9-7*D;f1OqC3;IE$8-}+4KJ1`wjW&b`7$(%r>k`Hmtja2-x=3+NN{; zXZjQEJFfVy{36k||4+3qWGGCzBVnxk<5S&iZw>iPP1_ER`A?RByttwG4Rn5pwgTUl zr?UDA5R~ER0y@gzpx;yPGXd=xT;#_izE99A*OL1kfKH>USP=FnxafewHr zCO(CmpN32+6BLin>+Y4TC1mV`NIuBGiYi-?7pvHwg!%C(m9{p;hT=oj_+?CWFiLIc(;u9o1Hof?*h8}5DnyE=$4fy6_$ zi~;se^-XP>-jOi(+y`7)R91QK)A!C;yC3%Od22M2M#XE-m4q z9netVA*C9kI~^k-k5B4QV??3C6e9TANDYcO36qCk8&ZDlNI!@u#>9Cup zShPt&*Q+chT?pZpEzBfSaPG7bdZd!z^iT?3%$I^7kc;xEov zpS+vb=CT^_AK#3c=GpGK3j1tDOR}ORb|6);Hp>0CvEo{Nd)$+%-x%GSZhkzvdvayc zWY5@8X>Ee7$)eIy=FEFu7_T5q4RK!VS7BB-3)*Oi@DT8X`hLZNEi3R@!C1whepEF& zv9ZR0Nhpqu;K`G@mw|NZ=D|Ax&}_H~!&aUBNr!T4N<1z*;H^oRm~0f8dH zmYk{y)Tg;C#wE4@E!=XiU~9KpaP7}RPYHL2yEWCN%c|n~>5jyKfn?1qiPD1^inN8B6OSbOhg`+IdOSjzc?9$u#zQabL~vUFul_{Uu;{?AWQPXl%h4TA|TpM%(S(el@=PV!iJ7ZcF+i zZAR@SdDB@C!|V&X$(Ioiq1ZQ=eYtX!(9Yo~=@+-c##A?1DTCiIjtu$&qhLb%J<>2l z-1UKxkm`%;Kr4*+LLg3CaDQxygsE3weN{2?g5-u^KKAcLth`j(h#hM4|8o4tQ*ZzH z>8}^gN3VVL(T}lT&VMkmF!8(jUwwJw*Jn1{;inTuP~Ba2(B<GVsJ!*MdT zqAmJNhD9|U)7+Jv-wr0WzmV8)AX)k%AaavAVX6n$wRGjI#h$d-V=HDXt?_3*-+N*2 z^p2UeJsHweRyEh`_@wK6*A$a#UO(I1m2B=xHE%n$`>MU=6T^8!yennjcxo4Tu(nlk zE?N0lde4D$t>Y#|_7l|!+avMElD78vVAA&FlqYF>`id=Sdp2W66{{1bRSD}V$bY)c zAzSTiWm~ec?G_?o!3x`)O385W_1D|5zp7iPgfm|?mA}`2qCZi$^|Jd)<5z>zFC}W8 znKAXj>FOVwvo&5_wI*Kn(Sh`uwV$&W*eToPb^QnfmN7j<%$h;Coa$Xv%gkC-TAp6nn6}lXEi2RJ@rY6v}njEn@6e^f;UZmWk?Xh-PRK zag0Xcz;@WJMw&!yiixTq;h)abZ7(~|9kwfbaofaSK^LrSBrIbPCm@I)kmY}n^#^4B u0U3Wl9DL4k)zQjXt~$w8Pi{ '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 12f81e61c9ac53f9ea2cdadbd0b1cec555ebd056..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2038 zcmbVN%WoS+7@vJ5_S#OI7c^Cxt|CEgN^)p|S{XtKX+de#f&v1`iZpC@H;J8fc00Rn z#jI+23QF;~=i$Df4rGgk0rYJyz*MUsW0hv*#RiyC)EdLo$~7>^Mm(31C&7q%lW1Y?8s8lNTf~tn)#}a$OJ&1;&wTdF9A> z0*bm0jVZ%wt#UCCl^|=ehN6xmTpEB}WOL)qAb144cJH4KjU^^~#mJbBw5TGP zW9saTBu09T*~ElH$0WViP|zg>_3ENDB~B>+se!T*)1j$pDlB_K8=R>@9IWE~?=}XK z#)uE8ZW+NtqvmJYj-RFqyn|9)xUIw`XLYkfT<+=iC_9s_o zu4f?4{_Hwj!b)H@mLtnjwbEGHfPeNYjpEn9y+g?~>75i!5t>oy7?WabnpN2tmtrY^ z-;PZ(GWP|O;*uchCBgTvX!CxaaH|zjlDkBqklYY$skcdT(YAW22c3(u8Cc zR_+z9->Prm-+oxgFD>sZt>y18?0h~q^yWzZ*7ElE*Z8XE5SlPTRgT1}JZDJQUSH2& z{c7Y?&3IHX<7oN``*M!_?R)>2V^oY7$`fAih-zFpjgW>yLjJo?>Y*1FR|=n|3ya?t zt}f+2xSxM;CBL>-1&T;1@R(_SS3^_dsy1^9_d32F7PSm(FF0&GfTM4(T`w%$F3i7=3s(;>B*fdX(H{@yt42F_*YnHQLPF4I zb>Y8XpOd3XRQ}7zj~y+6nAFnlAssW)$yU28=nYP#| zC1F^6t+utn6?7po=}{Be2mnOFBNE8+hvYL&!u4~9tWP1b}mbXjJtTd3?3f@?jdT+_1czepy&dffn59H+`*#{8`-D% zw5Qkvy#B1GC*$eKdIA|wVAJ#5oue7gV6MC8%gASuo1SdoL?&=zGZ5PBelh*VFRfj< z)?;@Dvi)Z={bx3go!vMKH{R1WpuXY1SYqh5rXrwyO+_Ab2rFz>IGz!XXNAFxFt{n4 z%(TCfc0Y6he`n6?|LEL@=kSsB9?EzRl{m)F7Xjnukq@IjA8a6#b6Sik5L!Hh_+t@Q z3GIdOVq8?q8brpI#TX_+0;awJwzFL`M82!`M#^cB$utN4q#5poB7cPAZ?-)G_$b>G zQ}WGX#0wH4^R(>z450`bJK(PCr80<@(Ge^o%1 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 2085820845da82518bef0892fb50ed6124b2facd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5394 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 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 2a681ce5b4ca5310270c646e63c4111ae497c10b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2582 zcmahLTW}NC_3rBZ@B<_aj*C!;V_ONbuqiS2gi_g2i3c!*M+STBY?j@%B_m79-4(_f zxv^UUB9LGQf`{M)cZey819qmJ)Wr}o%@3V^bX89WcS*mhm%n04I@6&)J-aKp?v%E7 zM(5sh?s=bckM?&(MP>x+>W*&zArnG>k&(jE=MRr=!C?%sh$VukL*0oELcz%n5_U4E z>C<*-2}B}JJCzqxFx0E)FQ`;^R>NwiNcG&I2bm{x=~(?TmjSqp|CQ6g8Vj6LL|(6n zHJv~m<^jTGUX)2~E<&;d!!%2=VSx)BlZ?&Y13v1|{@t-UzaT{TCXa`uc|XsDBU~*V z3=Bj=3}4HH`#kWA&_duykaPEiSzRxr=N7y55_Y2$Vx72UhT2F7@BCXo9_a)zM>Y#7WKHq-t!9)9fG0o=M1}2{h z#c>@#4g4P6hCs$p9APp}@H&==Lz>6Q14vNVWB^o6foejmmeu|1?lSt2WcB0ZAjuly z#AU=9;|OTlK@DqS&2bHD87F%PVc9Zk9oGzM1=+f?dJDxb5MURK1qAdJeMrX1xc0N+ zDcM_CgSxoxiN!8CT7wYY47Wup=So;;mO*`7&z6?M6}w7-s}*RIVQK7)kS1+GejD+W#MT47Cr5;qlEOU|t-!;tNW zT{md@u?g%edq2^Sj%(UcKjA8m7DEZmPFUHybGhl!%*9ig@z2sH-%8*7bviMVy?iq> zaVj@`BX@nstgtmjXiWDxnQ#aiRA_SMt_`b_*$+;rVk_AM8uxGl6{JHwQM!j~a;{YP z?d`MaYd@*xLe(|7GoOPI*CuCm%wJuTn!N#j$XG93U6UD`%-?lmz<%+*jAd_}R_$O; zh~f|^R@_gIyu0j&sy_44c>e0+&vU<=OeZeo?p{b={wV$K6|j+h@9g4*OS$Rk)ZL$D zhNl&k(WfDuYABh+#gz`HX5Y#E^iFE_R`!!yE>hBQ7>BV`B49ei2Wf$0DSRDFVf?h9%%>nH zN6CW@gP)^W4)c;$4ten&xC$Em;Q+(&Sbp%5A+HU~4-&)JN71JMtgIyjk$wA%%+PRl zVz|i}m52ByXZrj*>01{7UBAqoyL&2o{R}i!`rK&t!WFgPP0lu#MKa`Pqhwhz@FXMV z-iUI%0PaP?ynwZ!O0w_*1jJJe1dHW2DCyuT5RtTU(Iu1bIKxHc_#_R`U1r5`XpVn> z0eF=xsgGb7*Mg*FX@Qo_@e#-iC+Xo74GK!QQjyYLR6Y+9!SQm0@>5mU;~8I5=YZc$ z2RcDrFUJU-!B{^-2i$yYpr>%|VhphZC>F{`3E^;%QY9%i&^-VI z3Y6ug|2Gehna-#X2=bkUw-Ls*kpan6Ksj6uvB)!%Z-xOhgdUb6tL>6!q5MVhmBY!B zcG1}WH$9LR%3H+4RI=oC(fIn3cB76Wmh@=7XJJFt$NdulasB3pI*rYoFf3V--E;j- zv3AE2LO�NZ4K?cgb4|xm(^=?;#WVB_k?xE^Mrsc{K@^)@}c$qUxbRTWU^dzbi)e z?~QFA-TqC<+C;Co&Lvu(o7wqQ<#!s=ZcZ4N%*blLZ`qKvY?#>c2TRq$n)PE{qh05z z#E%vnl@skrhi8UJI_hT{laB4Pfu!T*`;NAxqix=?FR^#Q{_Mn?qOC!+Hh}jw_w~c~ z-QJ|zD{gB3>bZM|f8X`nu6ujM(#|CfDtZby9M8}8Cv7`L>(0NYEcU-50>WOX*mm1C z`^sF|tWhlYim!GhOJ4h<@wI;}Y2@3-d?n=aqIXAg1Nu#wQ-%$umI}?+sI;X_^K~g9 zpUZRrq3OvmkJkaa+QoYHQv=I?k&+&!HOwZZ<8hfx(RIW3h=AA1{Bq4cL5~0;Tm|5< z@kMz0(IEE&Tni`^67duO@QENEAkza>@&H->j2sX3h_Ie0g3zpM67}aAPdAQqh)*A! zw;UQWE|_hJjptmaU822GtlBki-aTaaQ?YHNb)QogHw{@H>FWrJ Iz;3YdPXZiLWB>pF 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 bfa3884ff75ad8f7df52402d2fc93898fce86796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 274 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 Swf1kZOv|Ove-g~K>Hiz3g1Ha? 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 12ecc7cb483d4eded41610bcff6a7f924178217c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 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 Dgk9B+ 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 93da5927c398f619d3ffc222c093804b80d719ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7924 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
  • mDEy z7A$hksx4^~l>P#Lg5;MzKzSzHZENBCT|eteRPMatyIJ!^&*afW`AgH*PB>lF##v|0 z?bU1J1s@+ttzG{adzGDV-q&wyfoL_g+0ZRB0fk#umD(I_tEpQxB(A9^aDFv~^J`7*ChFG4*1UF_ z`nO#K6uzVxpkP$t|Bu`@1;WeNT=94mvj+#12EonpC{~Z>jX|$pYY~T_D-L{M9)hfR z2$OD1PGEwq8qtLb#(c$!eK^4f_}&-fM9e4RDNL}Jqwx+7t%;c6D(N_obLc)X9}Bfv5pNe2eV=jU3-1+qcM~=9~V< '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 018363aa1140bf84792da17a4dd1058815f4715b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2055 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 WT+M18{j>bQyn>RaF~5N4^?w25BwtAY 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 ae002379f647928fc5c5de300f24e95e043ab229..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 291 zcmX@j%ge<81TTE2WPs?$AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdb;#8!rZPh}F*By1 zC_gJTxg;j1sysO{Q@6OPG9AXwO)k(aPAw`+Ez&JWOwLYBPbDZ5pOTte5MPp?pA!$! z8K07wRtXe{2O1V%l3EP2h_FIk13d#hL(`bzqU4zJ{G#l%oc!{b`1s7c%#!$cy@JYL g95%W6DWy57c15f}FEIjfF^KVznURsPh#ANN06yVeG5`Po 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 7c4f86ecf2b3a49ccbef1ef74b8bd04ab54aca47..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15245 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 X98TqJSz{6PRZ(8!8tGT7X^8(5r$wJ& 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 a0a9bb2e4250e40c02dcf9e60bea8a1a8fad546f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 323 zcmX@j%ge<81SOL-v%-P&V-N=hn4pZ$Qb5LZh7^V#wg}W z7ERVtC7^PO4oIC~QEEE*1h3AD9^#8E-OZJY|r0z%AIJ(a2uJ0Tc!R1ZZAS 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 9caadc5e8ebea95e814769bc51fcd379e3010511..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5979 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 -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 a9dadb2cb065569b7706334a93489ba5e6621fd6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 833 zcmZuv&rcIU6rR}*-F8c{7(ok?G$tH+*y35ySbrS67>{OCGg-Q0*|MdZ*{!4yl3uDo z8^sVq4U%XKS|m{t4|u@8zzYqUl0_2}-BM11+&DQ)yY}Kc%=_kh?|bvjWIhLj0$|~r zi>7-y0Dd|fH#%D2;2Q(;Kmig^pu%ZTf=8K?IF>z{Cxave0S5>o9V@V@N_VDO@KCI6a>d$AI+GhXn^-|-_Yc?TriIS9Rn1o_S6=}RG?k=s`&noo~ z75iRE*mYdoh(Zh9l+95KVhhHi$1EOWC7Z<9@~as=OHEB3v3Pwfl`~b10oKYG${qz+ zK8)|E8ezR&U8ZE*w1QZdlVfsFJ?@$WzPd4+~+(H9S8t+ zZ-cJy;MBesbob2On(L~C2CJdLLU7Lu`eNmiwXj$Xi_6C9h0US(M(pxt_{txE;BXc7 z6yk-c+4vTJyv7ey`GIos3x8(I7y5zv3)gF?zl!?H@{2%udgHWMnfi*(@B5jKo?s6C E0$J__od5s; 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 282110bf1de4e8c8ac4b47dd84620edfe8d0191b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4053 zcmbVPZ)_CD6`#G^`{TRwAHaqpmvm!W#V5?6wp39qNCSo@4vC8bZQ3i%cG+E5f(tEZ)vBuMU7tU3)JSdfsqf9+t?$lA zl{(VR&dhr=^WK~He(&8M%FBHO+IP_d$naZXICPK`FGGyLAUNGSA)sU7G0%ZYtKl zu`UY6;3Yv5)I>@VWwkD7vx-qcRk>GyVaF03E!vnfYjEjUJQ{W3wsOmlv`<7XNmfNA zlGGxJI5KBWUN;8Mnr9}>6IaaXA){|NGj_SIXh$TTOliC-9*|=KZ25U86gphQP&5(L zuDA%dv~%(ySxjkiqbf(+n>az~P)h_xaTHlgDRE9XC`2?4IYT0Snox(VHP&qsl95et zUtJTI6ciz*Zl%Z23u(6vPF=DBTA(@iF2>M_`SHm|GkwPCt8-InbM(E;)nkul`suc! zCuiZMSK*T%gYFD7Of=gedLV)(&?TTLl|7_0H?~%+zMpyXIiPy_nI6Iy?9}jF+TLcA znx$E%C%?Y*?I9Dnoeb$A=w@vS27Zv$JqH9;&@_e4^%ZQs;s;%D;tx7_@>Yy z=O{c-cmYF(m-at(4MSMn$L|+ZIh5?u%OqJ$D1w$yy5U_5!aTGknh=Gk`h3Xdr~?Vr zt1%*Y7$6*K-g_yJB08~x(qDk7 zx;wESBMtMB)|HfXAB0{~RRv^DD-}^>Ne>iiBo?9yj_m|AOnMMiaxOqf7Xu0C zFidUB7=6Dq#*Z5VXD$6Lp>3)?D8@-Nsjc_!R#5e#*>`<@;_Cm_8Mn4UE_GY`0d-hJ zZG{7&EZE6~o|oq%@rcIr-Bmf~4CPn0BQI1lBu%mvWX-xh_Jp5CpUxt@>uxJ4EHldy z*s>UaZWm!79=n)Nod?k%e}*MnQ?q|OGd7TO?)1&f?Nc_%9xS&8-kbNmLLb8Xas4xM zbZp@_1Nmo16JWECdyVF>f;@y&EJl6;6-pBa1<6mFIZLkjo9~+!MxfX+CXdbC8kw7# zE+}v^cTO80UNF)#_Q`EZHRuNL_AGpqdgzL5?8&9r?#jH0a|^qW6D^!Un9HsvW##`- zF(19_V3}pfkGk8-Km~Uro$xJfPfvWXYNxoR9TGF*q;*n}n`UnP);w{sSY0bVtpK() zwg*o&ssu)DrWV84QbVXVST?;dl12!iVQ-%hbXr*fkyrQAfU`iOkf1yR)F49*tYQw% zxvyVK(EQ3nCDXksZ{C8%9|5nr9y+jZ6{%S}+Rh$YWRFP_;;GY7;jU8rK1s_QHI(9+q(3_|rG(9S7aEjd~_^Ize*GnEd z0xoa%t7OHh^p3vzBW(}YtWED4T6K?I`><*A7u)ad{o!o=>*-g98X#r23>`WZy2rl0 z$a-AsvMY(ZI_vPc%10YU)yplThsHu#0@KO8Q}t8oRO7Wb{)y8?XPz(n6~D`OZtp0h z{n5q`-grV_YDq^0YXEsf_}LBpl=>1L_UAA~%=zKC|+d zoii|f(XyBF(r%gaVVRCGjEOUIapLKz5l)1&=mCg}xJRh~8A{BqS3_;Ptd3K@37qZl zqs0l-CKV)5b-(J_d;Yq+Y#lXAkumH%(FA+I7|>g-6HQdo@DQ9)&>7wc{a`AEE@z{9aV)2mWiB&vqsj{BB{ggAk`-BZEmL#Oxk%viWXop8*C#$9@X5v*4^w|mS|spUtY(>KsDl3m DScuZl 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 bf3dd892d3f243771264f7326d0a27e87d242cbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5420 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 BcccIS 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 8f85dc86f6f2b5e0dafd4ee52e1cef21a760a4b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6876 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 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 0e71be68fcbed473f9782d7f0f8478122dcbcd30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8404 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 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 8a0ffae0c0308abf3c22d6cf7a19186dafa0c51a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6027 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 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 c327e76325e4275df3503a93da151ba736291b67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1386 zcmZux-ESL35a0Xo+4V;pnktR@kytdXFvW?W@PbO8QlY9L5=v>nia1?vH_nB#@3MPG zaT=tk1gSxk*mVVcfUQpce%c>{%Cgo@x6J! z3rr6-uGPO-sV`o6c;n80jP->}@Lv7)wFf_g!!yrpgeS4#8O-siNzP!N3My=j*#y|} z1QhxlDY~}d5uP0LnN=c;4H-qkRE*G7$sA_MG5Ar zK?{s)lz2WZnKE2#EfqGO!LK=PXORRwoI#T<>vK36j@ENWFqkvaPx4wAZy4QDE=XBr zX!_ND!?pUK(d!2?LFb^lgHEB}Bg_MqBbGNYY)+7(k(KzKN#>Mv;RH?5Oxq$QkE^c3 zJkcaZ>~Y!wk2CRFCiB!k{pM(SB4=2mz*zKn)SkXz8di=?mkX_Z(R6c+(2ohtxrX_X zQ6Qm32a9eLESuYJaIaBMoI^Jf`a_cWc zPwM)VZ`h$l6-X1{#gkUa^*x<6i^o-LnI1g|h9A)`NJI|^5p4(cDIfym!?<F^V| VT_V!r$SxANt01|%(L8F#{1@4Lv8w<8 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 f88c8275884faaa8371d0ee83c532e9f140700e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1293 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 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 af8ee54dda0ffe524fbbee1cdd6aa936b61daea0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 531 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 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 0a4b06100b7e0bc9203449ef637ed868db7e4b2f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2150 zcmbVN|4&<06u<9Fzt93J5i=v`r2TU5ts2jeOK4I(L%;w^cW@7d6>bRL&+DarJuO>@ol(|5& zFLTy(>=X8!L4p>nrI4nS8Gk6LF~yH@1boc<(?aXJXU_V?ciKb-^M7eigSxgV9S`XYw3`D|xq2RG-+;GFqL@62)FDh|-2<}0EpuGVkfu7W( zsza%cWJjtq*}2xxv~cOx<&{^~E8kA|AGkdatLxT|w5?aSuQ}WQa(fbIcfbif16YzH z1#BBI{}-@DpaK-x*c}K&J9GdP&8f*;X33mgHg6=TEhW!^#R3e3eqE3HjvrSe0ZHwT zXu7ZYMDvN~L}=mc0#I+>&CMr-4=$W7K&qw$skYWGzX)&*Q9XPbA<&>&R9T(!C4H%uWXoDTG-%y@a*aQUJ)jQ2S6ZYC zv=hgGflidhip*pzUMG6GGS)7$vn*7di+v0NnuJyLWFkA8EIft6$fHV1{_}94&w#bmz8>tF-44XS34?21npt1=fM`(OIg}>!3-a5$50#JAzJu zU7P`2jssJu5FIP!V*-ZU#=S9fb^#`lnVi`CYS^}Wn3|coxyEl_ z$|GQx8+XXI{i=&;o@vY@*jCzi$d<2nG4>gr@b-MYlQ~M%u+0VNwje(tzv%h@0)k9{ AxBvhE 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 6c581fde0576b17395c69d27397b969184836176..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5588 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 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 1d712163423cec82d83d0a763e15c8a4c04868eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 213 zcmX@j%ge<81j1`gGeGoX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!Dsi=nsm#z#%#0~0 z%FjwoE{VyhDo;+#)Ge;6Ooy>^lM8fE-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kMDWEXa&c&d&qt zD@iSaYQZ8=T$CIWAD@|*SrQ+wS5Wzj!zMRBr8Fniu80-rL`EPk1~EP|Gcqz3F#}lu DKZQx4 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 b7f6256de60fd0788f8906e2fce7aecfa1cbfc96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34346 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 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 7f75b3b542e0cd2aed210d4ea1dc21b95661486c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 252 zcmX@j%ge<81j1`gGeGoX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!n(b;8Q<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kMDWEXa&c&d&qt zD@iSaYQZ8=T$CJ>R9c*wms(sL6Ca2KczG$)vkyY=vGD`E(S3^ MGBYwV7BK@^06Jw&aR2}S 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 a6a7be83a289b615357f797c61c3e11f990bb4fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25065 zcmd6Pdvp`mx$j6CNh4W)zl{yTHrO(@c^DFKz=?SnOu$eeP2z<52#)|eew~q&#ID>L z5`x?~L9}Uc5)w>H5+x9DZqvqr(2%aP&Z6h8b(d6`YjtkUy4-s#WB#guoSWYCpZk4# z&x}U09MW^|TDRlnt(iUh{rJA$_t^8FQ&Y_hTxt7TIzF^B%)jA-d|9NMdoSu4<`ly- ztqiZ>wOyK44f)l!YT;McrR!!}*=~KSzT41h=r*<*ySY|Q!|*Jx?=p3pTg@6q%Q(!A zl=B)|Vyi{VY-M=Eiwti(ua)9bNgM}pCM7P7#F-&3MTtvyq;^`RmZ^RmX-Q*KJ4;oG zq+3XvEH<^X{BKCRG-=vov8kPvNz*2aP3^2onl?#nhoLo(wnL;>4r&V_8UFK`>X?HppY5R7Oh{+oiy&P|Ldp1sLsn{X#r@HW1RUv{DTypFE0 zHB4(I#A`bD&}Vh%s8n`+6|coVc_$O9u*6ZNyk7=olsT617CA+0HL0zJuT5H8tGXU~ z@2GZE^2_-Z7gnBUX$;Vzgj)Nx{n|2SCF9gQr`0e|G8*_BX?kAwB=ejmF}`0b$2)4v z86ii*I4qrQbL-_-$-Tf5`?Zc0kh;8ewj<3AgD>y)*|+PJ$pa^mRB&&MO@UcBXd zDe}{i?R$5%SogLcc68fke)h`r)zfD4?B}mfPkcc0%$)E||L)D{%P+-pddF3HX0KkD zdF7Y?Hgtp*d;5ct*-J;@5t83N_R6iR7ipolJ{+DIn}~e=g4C*?oSA*$>c0&gHAgPK zFf)2-_Vc%AUVVqYQ*uJ%c5d2DI}o|}#`LGdx88Vh=IlE&zk~{onxTVNX5T#$Aw|`j zSJtXJ=RGxh)=!%@J#kspT=S}0tGcC7$L%Ylvlq`rhCZW1Vy>%|hvS6r*15|wzc@Z~ z`ogW(&dhjy=GCMUsbfDMzjftBY0TC@v7aA{oIOtqpZV3$%$ax0X4+>+Hhtx{_2wF> z>RTUOiM;)CWMbm>@e{OT@JiK4T3O_EzmkIvD~(YPD;*q|v5UW(y?8eA>Y0f5b*x9K z;o@-QnE%h8y)ixU&dk_nFnTnewhp>ZM+j@V`04G}UY{9y4-!W%jzoU(A|#G{b`;u3 zM`h;p<%svUq&=5rhTo5T^mCec=Cw;RBS&U_F^=iU!jh_naV1M(_PrOOO^|VB?0Dq( zOOcUZKoz$xyc_X(RXv7Ovm-CSlwLhOJ9Mnx+BNW8yS>AFYuF28UvFJmyP|f5`8MvT z7MosVH+QtVM0Q7q(L2TYo7)H%Y#Pyg$l+@1>OFMGA+)Qv zql7S+d&pc*F%AZ>3WGSMRR_C?<#nxko^3Vo`c@-vXytfgtEr!}nMBiWyI}9$*Wc%G z{~U6ft#B#(LOrWy@`}!_wBkSgb>zf&EHSL);h~w+!*l>?UR>2!B<{bEw&)%5* z<@uS>vB)?K5}bIVrI(DK;Mja&AJ#^#oNTejlO#$CI1 zY-wx}^^JS>G(IUBc5U3gWz)VEn_1-A+IsBWjMX$b9qCminjr57*c`p6(NRJ00z=r|F#5I0yO< z$@ksueKk&p@T^0q>9e;#V?X4O-n7A+HhW)3TYGO0ymdK*`CmAN_NNcHogF<6r&GCU zbGtgaYWwe`qTWEZIX_Anr($kT2&< zzH|@;-hb)z8*QAJw%ys=LrE-IJ0~G4ndmu_NNIw4^{A52O)@?4AxuBrP4P2->ox!M z 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 K{-s_I_x}eQeg|^^ 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 eb2aa08f68b16f98d62ed8b2fa0b2b279ae72e3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8473 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 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 c6c70a9b7559e2ab6872cea94fab5d1cf5a176a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmX@j%ge<81j1`gGeGoX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!n(k^9Q<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kMDWEXa&c&d&qt zD@iSaYQZ8=T$CJ>oL`h06Ca2KczG$)vkyY=uSo;E(S3^GBYwV I7BK@^0N*7`ssI20 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 9237dde2e991552ce7205827975fac7ad8672a10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42777 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 SG)~(7+)xVDxFMH8&i@Zm%Opks 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 db83ca09728698425f61ce5fa8630b072c3ed5c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22656 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 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 8af1df126bdd4541ef9480af29a5c2dab2018438..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42421 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 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 a9d721efae1a6e57ff01f60b55fac2b4e095a52a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12391 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 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 3a0e9fcb1529ef8fbceb5a948cac9d0d8eb7ac71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1133 zcma)6&ui0A9Dgrq)2yr$*QubFf~N@GA0Q&?5N}n~Qz$&LL!@|J3~E)e7Ez)d?kIvIUcNHr zjA_$$g=u=FNGo~E(1XIj3Zc`x1YiYGq+$c9f`L_$M)>UYut1H1FAn$;_>loWR*Nc< z6TfX)<_)u&E0n6&p*5a@z&v9 z=I(C08?V|+o9&mM+_^1pdDq)|?oZPDd_z`fpuI^VwQHtrfCmX5FEBC2Q6n=vpCd*K4^xF6!lU z(3UQ1`duwYhh_-$5UpGwx>W)O70SYveF(OkiX)_U?# z+R1)!gs&e1(?DGbjU;*i(NK|2LD61oDHJ9JdUc*P0=+U!h9u*2Lv`3FU?*NJ_3x~o z7$9;O9^-w!IRxMhO19*^!A~61OI1&grQ^*}*6k~LT zQ9EBBAD|P)l1I2?6xoppDQQJYh!ZEIXc<+L<55EHRW&or#0W7goe;*~CzjymI5!jA z__FZLd_9=N5jVWno&qzAehWfW?#f7-;3&}(0d+b0kC@-V{P}!pMu|A0Hmx)Meg}N~ o8;`O$kS_$QTx6%A4*nj<=487f#`p(1yDyF7#N*^21P_1qUsJe6cK`qY 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 882a7067ab01bdb3a053a5048e682933c4929b1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmX@j%ge<81j1`gGeGoX5P=Rpvj9b=GgLBYGWxA#C}INgK7-W!n(k^9Q<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kMDWEXa&c&d&qt zD@iSaYQZ8=T$CJBT9TO)6Ca2KczG$)vkyY=uSo;E(S3^GBYwV I7BK@^0OL1HzW@LL 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 abe44b08cf345b5fc85433a638e7babd50f97f7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15667 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 bsee?(wk$&H;DF(v@e5VPkeX4Yk%jwTmTX!^ 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 3c1dd2394ca45eafeb49e2fd277c178257294952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19573 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 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 c9add4f2b12c6d91f4caab45f413afd352aa8511..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219 zcmX@j%ge<81Yuh@XMpI(AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdRpn|GQ<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kME>%!e5V=f=lp p=4F<|$LkeT{^GF7%}*)KNwq6t1v-Thh>JmtkIamWj77{q7672sKw|&^ 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 5bff17d7b4e28924d0e4252297e16ff153a53bf7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3664 zcma)9|8o<^6+cO5Sw35`W%--14KlXDnu#68d?_dljj+!|^%uFa%+yyhH;QiU2qc}l zIzdd6R-_OhxL_wKwu>E`OepD)Lfh#8rBMEW{vt;DL9^pdI}=W}f2yhTQ-131NhfRy zv3KTHZ{NQ6?%lr6+xPSjqmiNT*eMtT-6a(D4Oysz&JnAZF)>LA6h{dfK^xR?8vLsZ zYC}3s7os^@LkYA{5G)AkIX$MezCuBNmf{RTAy*_AI7TSqit$q-Fq}~+=1f8fS1K5} zGQq@|1)3`tN;!*A##IPru2LumI#31ZVXYOvbyzD{&eB}9mIBqca_(?7qgs1K%!QZC z^0{9xKDwQ`dj9F(?lR2Mog102uFJEN^4K|f{MOS4A1z=1>*BTBi@!aaxfsvPT+E8g z_wO&CzaoD!wsiaNSo@p!hs-ju-DlS#x-;OHkj@z%UDjZhU4xhrAn`$QWCTDzky3{a z6XaRlkEcmWQu$Q8R3dz3Na`6DvqW-ea8{q*r{X7Yyocv7DJO1}4J z`NFJx^O8Jv)viPO?AxIpOCkf$hkYTy^S`H7iM{XMV;#pv-);2;jzyq&9QdVU!PBSw zzCdgA^yr96hy0P&D1avcY>oK*CwwDd!yO*$@BvXZmtXqD@Ni(HEi#HsJP1c)5adn= z8nHVj{6uT;6sNw`QwCG~7YpXvnYWVW<_r3TVspF~7URv+P47xl#PRy;IadV`inRJg z7-w*V?`dxFMg3AB1nf~;tF5_(oNJB@f|DS)W*q|2DE7-9 zMS2LN7!3RIoARR!QwkKyakw!Qa-pUu6|x^t?^DO~pvddtbW&lVp`-$pf1CvRg&}Aq zgUY(4%~Bg^=p-eziR>mdXbE2d*|ABD0*M4%8F>w}%Gtm_y;QDBs=DfFNSn1$C{?`D z#`mA6L8^LDa|3^iB!X*YsQzxC!AM*FPh+!RRxH)MsGC2X@BgwAX`8Ak*;Go^RXso6 zsO~)fRnaOKPqa`Q;n5^DshXP&Q-yK!bC`uI@bl70jy7npb5{`TH8d zLdwK7YF4|UbOn~K+_ZERmM++|bTyW)$w~{>S=}06H29L=5({7_850GLK73DLj5!< z!PjiKO1Aal;z05-|;ea>DM)vRN6vI89+SHIWg?n5PPUx&Ms_3+-Ieij)!*)Hc` z4>s)X=evos*`;&P>*hN=tkcUPT7ofvihA5f_#XBs+k+TFcDX&hPOsgFECM(g@B`i# z3GnauqJWRVAhO_FUlQSZrV)fC03$Gfg#rR3xs6I8_*o1@C7x6TF(#qnkncSn-}o>9 zqR5;x6UDZGQiV~?I+g&Uei(>I0Wpk9K-ez|0P@mk1R$dThJCRhHY`T?6U58x(if9N z-VcFK!rz3;Jg*cU#1L{=gdv|4BbQ_@#7Z#b+hdsv<1uoNWJ?{6r9AUTc{c8_L6X(V zR<`b~-6H84BNaiOn3C_^aCJItF>-k?-MN?f(}zoU&t)d>VhbckW296q-u%Q~snm2- zAb@vbBS*VoYU|PQvbZ z=uU3yN!Io*RBxGSpF1#pVE*mDbSG=yOjbJ=>bK1eO%Ek@I#LG*ll4azns(fDU3aBR zcBER~!Y^+oFSpjt6-^h-x24*PVP7Q#8Br*0^J8c>dUvt?h~W-3jxa6umd6 zGk$1(2R@f4+uuku9K4V1y_uq&|IRm{b}K66c|r8^JlsXXsK;p<2#-L5e2BX^3YnwO z-1Y0;b2Gqj{#KHS}r;rD6U>)%}yQujnbp z6xaWkF3^;(lu_GQO@gXTZ|%S{URrAtROO0+ssu0(HQm^j zpls>th6GiSE;GlC>8dSRQKKbZls0z6nH7UwQ$F4~Wts9%)lBrQ;(ssY&o~XmR9vb% IqgUMiA0OkZz5oCK 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 8f71c3f8514a5ff56bce3ef07468829ef604eed2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34766 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 a#e%ENUHd;#<$S8vKVCboqSU$M3HZN<&QyZ{ 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 ea46709c4dfb08716560efbebe332b8da3355597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17127 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 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 daef1c6e3e5b9bf5c5f3f93e2564b3b12fe6e26c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmX@j%ge<81Yuh@XMpI(AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxdRp)9IQ<E-0 zl%JKFToRL0Ri2!fsasrCnGR#;CKu=yrxul^7U>owCTAz6rxKKj2kME>%!e5V=N1JmtkIamWj77{q768b1LJI%@ 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 a3762f882804bf76a1d9b1f5cf9afa4b043cca88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26507 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 NRZ$fs%f`+5e*hpDCxrk2 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 445d6cb60c3e34cd7c93254f1c6be52aa5a03ea3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4668 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 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 ad272c0bdeb0aa029611a4da2f5b7e9027e50000..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25831 zcmds9-EUmSbth%Vk?j~oTNG)7J}ih9sDse*S3WGX1q#Ro3hobF&;Jnp)@9qKx9K>&V>hGN2%-p$m zcPYx23?zcRym#)*nKNh3oR2wY=6_yz;khRK+xMR@bpKaJQ`2YiV*l+h4`2Kg4>L`j zO`Xj>O+Vw`=ASip?l{`?vmKpJ;rFS|r}6uA=Qr^Cjm~HA`%LFI@%zoro%r3^`7C~) z?R*Zu&vibJ-{%iCzxb`HfPWjE#{WHq*Ds#N&rB0)?Rfj(`14Io&!zCMnw>45TB@u( z2(F9-8`)s!b~v>djNW}tzkZgT4#wvzNA8EYTxE0hv+QxNQF-=gc&><#wXK7DSA(H@ zpKnZrr}E*%U}=55bpLeu)_QR97%QV~FkdV$WKEwZCrkI|KFc0f@TJ1Z@b*%vI8|QB z29u|yE-;(Uf0i8te$|;}c^DlDCqE=|=%-Ye4DXHwxp4=+dgE-lxESOPho@I7t4Av1 zhXuSHRW*B9hCOj4IDf~EDjds(b7REVM=LQ-^cGAEg~x7|?hm8e>bYrP8cZK8FHV+E zeG;5o0(Rx0#qjJD9;!1-RD&o^cj|JGCs@GBMY-!WGOSe6I#Ew(~9K3>|Gt*oq|b@`(rC=iaFL@QT8 zXL>9cn+$TRGQ!-6pt$J1hU2G$`?pH#*HNuh91f!R?Cp}2%aGF7PRHKT-RHZ)Tbe^iV={@Ae8hla?hbo9Du_c}fj*0hp8*ZpCBq3fZq5El&@} z)-P(tR(WvkaCmW!%b_}#ty~-AG7SqK1;uMvGNs}QAHt=DU?YdM8EvDVbV{;tehovFVIHiv?R8!zro353ymZ|yNlsVCjh zKG2=+^J)-j$ctcZ3A8ESU)cp-tj^pCrYFmbtJ*M?`fiFt3_o5FxuOyjQn7UZB->yF z<@I@4#v7M{>_$s)|0I?pzj2BbhDtDehpY)6%s})6(>Ewk!%HLO!)xW6*Mb~m?I?Py zW@qpW+6B3*d+b0ChEzVk0&pByVie&NV9l^gR-hyY zy%RM_;Kz+?`aoAEIRl3El%cpV4AG&nsw0zNCNQjPXzYIy2ysj!Ul&o>4QP&c8zwj?)$?vd0V%wv8*eF{@NU?o8Olk|4wiq>sS z4IIq$wWa%Zrzn<5SDD0(tN>71E%P{?55`5K$bhKk@9q@V zCUj*4dFZyZatd^CE?oYQCE?VVc_8G$xw??k`}Xy80j{cE!wKVe!|b5ot%U+bg#up_ z$T>1nx|zkQa~LNMCa$loBmG`i#?pr^&`+rPMf$mQ8eJuNXiatWr2D&UTMpK53tfP= znBvMOc14hILc0Ee^a0mY$-}0Y0azyn`&7z2fYV^5ghzAWG)yvxNv0~TUxi+-o;p{_ z=asdnw55X<8CXl|e}_+`!sW~5g%!3)wHR(_Nrj_lSc`bH$cQhkg_A4(!jpAOtN~I7 z+!*HGy#}a9m~BAqA85}EG=|z|mp7!W@`yn z8M%%*tY*i;V;==pS8)9X$L~X{I=is4c`7)6vAli~swq-=QpO1&cT>R-0PA{2p{N7! z80!nODT7mF1}7%MvBk>5L|NC3HF!U2%7}6Pg&6I>pL}!oJBRkSws*gCAd~)iSI59R zJs-T^(cazK|G}Ys_PMv?Kr8eXR!HlC_Ksh)@9TQ#mo~I%>rPv6?zKPD@eYdHs1n*b zyE_K@+t79Ifq|C}9Qrined~X}d!S=KShH^+lkPEB#w)}L<|NFYqUB;{`}`dm9=V zR&{OU>7lH1fzG+x$BCrY!nr0wepeWsATjmUQIR1Oh<+4xhvt?rmM==1VpacL}3*w z70!U#gT}kyB-W7I6~hfk&`KPG%TrPBm#2c&T(Zt;&aVT50IWfnuAz}C0*Z8079xo~ zI)Z{?;}wpP#awf~)_P^o*>!6|nSlXZb7-aIQLk+ZJkfX34~qI$&~`?9>ebb(1A(ozxcE z6751ZCFs_FbxWRr>sa#I(;u1*Z`Zi&<-|P^Y9lHkZ~^!^xU#~*i~O_u46Y=xcOZ2} zhvB)f0@gxApF*LsI9M)Tg2e;KhM;rB3MOZC+5vWl_%>~kV)CR3TOeUQWy^yJiadY;Cw+SeS0DhuVqhmCZOy;*xTLNJ#dIL zUfbk0hM}>2rPc$WVEhs&=7AW9Ia*S~^QruLaUYUgL?1F*cj`b!uj)P4cun*_BBwC| zg*_vcDuM-FBKL|7ow;HHl)>&v_(ldal*z+go~Z5D6F)2EsD+!`9cnFFxGlY3#p+$B%}qIV@T?Z*FSPIEUTOF>DjI z?C)wHX!%u3M>^euKkWwx()iPvIfP%4Yw5loc}!amSdepYJk(s7hgfNSh`VlIs+UNr zx&hdt`h6JOd(!U%;oh#!?t{H}*x$Wxe{ul6oOQsX!usITeFn_0THvq_ba%81!S<&! z-5MN4ft|MFI^@1Rh;gr{2Zhhkoal;mt%2$fza}Qt`!C}H~c6!>ZJYj!T3xX^U(Fw0CF3 z`G;!5et4HY`x^nzqT;{df433PyZw!TJ!q^{LJT7qk{A;Fok!dVU?TwVcL7Wc;`|*f zC|TjFw-CP&&jbCOm_R3Z*q&qWtmN8ImUZ!56<`3kk*DJf?7@7(2z@C>~8Ut)m4?t};9H~ZyM|6ZcpXrL!hvYmCu>CSb?7Gwu7qMoGKKl2)hJ zgejsZ!9#82jjLTg#Y1pWI7S>~tNU&IjpzvchsED0J_NpcR`Q^MxCH!|b4cinCP*Lk zqHunD!v?w!5xkc{0R4Xo+EeUoj^_D!})n?pvCSlV0& zA@Sy~R`MS@+qGWySWrOR^AakCV-H}0VN<)Zc$o=Qem)tL7D0j_Ft9~?%uho*GSdQz zt+bvkt&OsCEEXe}VMS?djY?4&D5+KN%vbJQ#+LL0HbFQPQ90Q0tHJaXm6YrT>cp@H zDp0#thv4dtwwSIyeh`cdvtA4JE91Gomfa#wqy(@XxXdolh2esjEvcI>&d_k5Kn3~$ z!Ssy#im?zvWH!dFQCBY3!N{fZ`5eTEYhIo^hX0J=hK7lhAvNfvUuJE2{rXs$oEMu# z=72{@P;*Pc$?L`r!kEz|e5~q7&NsfS)l57w_Z+N)X8a!=1=6})`9lT>F4ZOg=$g=8 zmhvmJ!Rqm3I-F+5d99SMh_)5JZU)Vaz{R+LoQ-H+KxE^3T*!(==6yn*i1`G=ZS$-= z!|0nm8Z%DO9hR0yAL&2N7?}FxRK}+dL-3c48$Q`E7@dIyyiMa2ND?Eqb!!z?D(GLH zn@5U+zlg9Z5{nB1gq7+RmmS$FFCleBw!i&#%LU4=l=!cr)-W6HzYJ;0Y5O(CS9Hq>C$eSee~LV4n!l-S!fUal2bdvKl)>6R{l<WrCAl^zjT&$1D)E~U+8prI9#5nRlPA$+>F4hRz@ zk(mfh8%&(x%QUP60(Uh8O0MvvA!|UiabO+9UI$RA*L`ysG>o#u&d7 z(mHG~M_Kp9AVrbGPFTJasLxCB7GKfH=-Fv5O4|3P37le3vsK9nfDGAx0 z6zrZEq*y_)8{+d&f^rq?L~bS& zHq9XmRxVc04RMCGab4SAH*QUC*S{a^${cEI@9S*q?`qE=lZ$CynHS&z{8_WNar=|) zdjbD}tS{}omVF{MRp$%|*Z}$&+=etrGA%MykZW$h*c~ge4zjbAO_Q1o@&>2!(7bQ$ zA?sSPH6g7>Z$L}{o*3Y=T73fooLMo3zh~Kt?T>(ZuyTY<1Fg^Q4Drcs6ne(S_vh4n z9+Q1JYegJ(!)|Q*Yu>n=_h{UWZ}7k`!N5r+J78;dw6M5(__A=jW-g$vZv`3|FbWYo ziIKq1NmPIw1w|!k_3&jm9g(mpv6;-a>LW>8tw2HREJ%^DQ)5JfGbt!1PL)t4OpIWj z>wHyNMF`{q2i!H_F5_wO2=A!x}oK$fAZgLMk!ho;c88C1;FFHKzm8 zt}$cyHmfhrZr;QshRXw;HUuuRdNH1_DUJN2WNU0d$cYbcAhzY`0Mw@wW&c&>$&?q<_e zwoaz6A@dMBkK&)uhRjV?uZ@!ZvE(cHYvuJ}o8*63QLbw{{f(?JV>==Hprc!lmT2;O z55nKZdO`|&QccboFexo5=|_OR2FT$5;h;htdiU zw05^VNLkm4`?gwf>2yuL;-A7^yXbFwy}A8uug`GBi_(xsgejpO(Q;#T0)RfJZzKB< zdT@~nmWu~=iahYn6Buva;pDGs!m%4?rx6>cwRzk+`ijjTy~xB>P&;zj>43ynG?#N~ z?jn-f=1fymnC#0z_H8+WO_&^!Ij7CVtnzO9a9@doMNH@HVMd+lPLG;L_-Fvb#KjXt zU{0eQg^vcYa@L(i!`Mi?j)ViR`w47ekcr>kQd)zLv&m11ftydRp-zF>f!ZR=F)<=+ z#>7kNA{nQx8B|?_9ja=1#%hwA9i4sVjyBG=G8g5TZ*ncECfs82@JzqIc&M($@{pT* zzQXMwcKm-#w}U7zY{zaC6K;HoBoLXc)}VWpboty}yy8=Ja77Zm3up0MF5cqpNRe8K zhvV=SmLbhT2MrKp0Ts?B%7e*!iNXl%*JdjcOS41s_1TK-wy~`U>$K6;8VPfe3tpB` z{04p9)>Hv%7acZVQj?>h-+593+T z7@R}Q!$-rgiSM0$)$GWK-V|_Z_BA}f=26qDZnq`tFpA7FiOz4CK#YQ{W^WFbeXvVxPewNr-3Z7_68CQh?Y6C-lSNQIG0F73x*OH9mI zG>bO+qz)E8vHzGWP)p%BWgHxb13@LGwGq!4l$6CA+42asCQdzZy)bg43(*f;0ukE! zgl)v9FBcgbGkEGIr&ySp^Ck~AJDgD|1PQ&b&G$tc904EDE^Eh6(mHL)M#1u24|0422To`5Cm%^iKVJDYSa^_uR1aztyC2z29?mH(3OeaO{dxQ%HpU)m36{+ zA!%Yqs;tHRvl^`Gl6{MgEKFmukj@rQ%fs;gMz>fZ57C~y zSApYzY~z(fjf92BY)fU|aIjt=%lt{@+&BU!b&{KE2zWzE z_^DhzT0}^P;*>Kh9{%jB zx_;JutlJ~V$2wMW8gWV;K{~o2LuEL8XmjEdASBwdkYnepxK^xb3l)E#dnMyohvN?f z$Jo@Tc4#T4Chw) z_k8VCxpY?bm_tQ?TK8j21qzClU*^^S=I#Cf9mOO?|-|!7dOv^$c;L)jNJfP zMJj+OU>!Y-ua=1CgmX73O>xCiFlY`tl?qej3DSZI%l_mljq4h|FZTE1revny+EhVx z15<=wjRyMNQPlX zqSYDMUdxCfoe?pY5s*a7-PH5@oqvx@#t!y${bS~@@fBNfP5nQ{&u^b$H-0~8eyw@u N7d_42ZRX{^{}1)7%NGCu 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 1812d548b7f4ccc7e36688603e475e95ed329f59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18212 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 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 4ed0a250d8a76bca3509b90654fbb3f475b7f8fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22178 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 t6G=nw)BdE^C^G-A#cfa&UuDo8-C$+ZY 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 -