feat(schema-converter): 新增文件上传类型支持并优化schema转换逻辑

- 新增 file 和 fileList 类型用于单文件和多文件上传功能
- 添加文件配置信息处理,包括 accept、typeCategories、uploadMode 等参数
- 为文件类型添加 x-file-type、x-accept、x-type-categories 等扩展属性
- 优化默认值处理逻辑,排除文件类型设置默认值的情况

refactor(main): 重构工具配置处理优先级

- 将 schema 获取优先级调整为先从 sqlParams 转换,再使用 inputJsonSchema
- 增强错误处理机制,当 sqlParams 转换失败时自动回退到 inputJsonSchema
- 当两种方式都失败时提供空 schema 作为兜底方案
- 改进日志记录,增加调试信息和警告提示

chore: 更新项目版本号和测试配置

- 更新 pyproject.toml 中的版本号从 0.1.3 到 0.1.5
- 修改测试用的 workflowId 和 workflowExecuteKey 环境变量
This commit is contained in:
2026-02-07 20:17:10 +08:00
parent e18c661368
commit a50aa307ab
7 changed files with 90 additions and 24 deletions

View File

@@ -177,27 +177,47 @@ async def handle_list_tools() -> list[types.Tool]:
logger.debug(f"处理工具配置: name={name}, description={description[:50] if description else 'None'}...") logger.debug(f"处理工具配置: name={name}, description={description[:50] if description else 'None'}...")
# 优先使用 inputJsonSchema如果不存在则从 sqlParams 转换 # 优先从 sqlParams 转换,因为它包含更详细的文件配置信息
sql_params = query.get("sqlParams")
if sql_params:
try:
input_schema = convert_sql_params_to_input_schema(sql_params)
logger.debug(f"从 sqlParams 转换的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}")
except Exception as e:
logger.warning(f"从 sqlParams 转换失败: {e},尝试使用 inputJsonSchema")
# 回退到 inputJsonSchema
input_json_schema = query.get("inputJsonSchema")
if input_json_schema:
try:
if isinstance(input_json_schema, str):
input_schema = json.loads(input_json_schema)
else:
input_schema = input_json_schema
input_schema = sanitize_json_schema(input_schema)
logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}")
except (json.JSONDecodeError, TypeError) as e2:
logger.error(f"解析 inputJsonSchema 也失败: {e2},使用空 schema")
input_schema = {"type": "object", "properties": {}, "required": []}
else:
logger.warning("sqlParams 和 inputJsonSchema 都不可用,使用空 schema")
input_schema = {"type": "object", "properties": {}, "required": []}
else:
# 如果没有 sqlParams尝试使用 inputJsonSchema
input_json_schema = query.get("inputJsonSchema") input_json_schema = query.get("inputJsonSchema")
if input_json_schema: if input_json_schema:
try: try:
# inputJsonSchema 是 JSON 字符串,需要解析
if isinstance(input_json_schema, str): if isinstance(input_json_schema, str):
input_schema = json.loads(input_json_schema) input_schema = json.loads(input_json_schema)
else: else:
input_schema = input_json_schema input_schema = input_json_schema
# 清理 schema确保 enum 只包含字符串
input_schema = sanitize_json_schema(input_schema) input_schema = sanitize_json_schema(input_schema)
logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}") logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}")
except (json.JSONDecodeError, TypeError) as e: except (json.JSONDecodeError, TypeError) as e:
logger.warning(f"解析 inputJsonSchema 失败: {e}回退到 sqlParams") logger.error(f"解析 inputJsonSchema 失败: {e}使用空 schema")
sql_params = query.get("sqlParams", "[]") input_schema = {"type": "object", "properties": {}, "required": []}
input_schema = convert_sql_params_to_input_schema(sql_params)
else: else:
# 从 sqlParams 转换 logger.warning("sqlParams 和 inputJsonSchema 都不存在,使用空 schema")
sql_params = query.get("sqlParams", "[]") input_schema = {"type": "object", "properties": {}, "required": []}
input_schema = convert_sql_params_to_input_schema(sql_params)
logger.debug(f"从 sqlParams 转换的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}")
tools.append( tools.append(
types.Tool( types.Tool(

View File

@@ -7,6 +7,8 @@ Schema 转换器
- paragraph: 段落/多行文本 - paragraph: 段落/多行文本
- select: 下拉选项 - select: 下拉选项
- number: 数字输入 - number: 数字输入
- file: 单文件上传
- fileList: 多文件上传
""" """
import json import json
from typing import Any from typing import Any
@@ -60,7 +62,13 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
"maxLength": 200, "maxLength": 200,
"defaultValue": "", "defaultValue": "",
"required": true, "required": true,
"options": ["选项1", "选项2"] # 仅 select 类型 "options": ["选项1", "选项2"], # 仅 select 类型
"fileConfig": { # 仅 file/fileList 类型
"uploadMode": "both",
"typeCategories": ["image"],
"customAccept": "",
"accept": ".jpg,.jpeg,.png"
}
} }
Returns: Returns:
@@ -73,6 +81,7 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
max_length = param.get("maxLength") max_length = param.get("maxLength")
is_required = param.get("required", False) is_required = param.get("required", False)
options = param.get("options", []) options = param.get("options", [])
file_config = param.get("fileConfig", {})
property_schema = { property_schema = {
"description": display_name "description": display_name
@@ -112,12 +121,49 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
elif param_type == "number": elif param_type == "number":
property_schema["type"] = "number" property_schema["type"] = "number"
elif param_type == "file":
# 单文件上传
property_schema["type"] = "string"
property_schema["format"] = "url"
property_schema["x-file-type"] = "single"
# 添加文件配置信息
if file_config:
if file_config.get("accept"):
property_schema["x-accept"] = file_config["accept"]
if file_config.get("typeCategories"):
property_schema["x-type-categories"] = file_config["typeCategories"]
if file_config.get("uploadMode"):
property_schema["x-upload-mode"] = file_config["uploadMode"]
# 保留完整配置
property_schema["x-file-config"] = file_config
elif param_type == "fileList":
# 多文件上传
property_schema["type"] = "array"
property_schema["items"] = {
"type": "string",
"format": "url"
}
property_schema["x-file-type"] = "multiple"
# 添加文件配置信息
if file_config:
if file_config.get("accept"):
property_schema["x-accept"] = file_config["accept"]
if file_config.get("typeCategories"):
property_schema["x-type-categories"] = file_config["typeCategories"]
if file_config.get("uploadMode"):
property_schema["x-upload-mode"] = file_config["uploadMode"]
# 保留完整配置
property_schema["x-file-config"] = file_config
else: else:
# 默认当作 string 处理 # 默认当作 string 处理
property_schema["type"] = "string" property_schema["type"] = "string"
# 添加默认值 # 添加默认值(文件类型不需要默认值)
if default_value not in (None, ""): if default_value not in (None, "") and param_type not in ("file", "fileList"):
if param_type == "number": if param_type == "number":
try: try:
property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value) property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value)

View File

@@ -7,8 +7,8 @@ import os
if __name__ == "__main__": if __name__ == "__main__":
# 设置环境变量 # 设置环境变量
os.environ["workflowId"] = "2019984063311556609" os.environ["workflowId"] = "2020107946473074690"
os.environ["workflowExecuteKey"] = "wf_45bd630402664485a2ef5cd3ede5aa53" os.environ["workflowExecuteKey"] = "wf_20170f33487d41459c180c6a277991a4"
os.environ["backendBaseUrl"] = "http://192.168.2.236:8088" os.environ["backendBaseUrl"] = "http://192.168.2.236:8088"
# Import and run the actual MCP server # Import and run the actual MCP server

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "lzwcai-workflow-to-mcp" name = "lzwcai-workflow-to-mcp"
version = "0.1.3" version = "0.1.5"
description = "MCP server for executing business SQL queries with dynamic tool generation" description = "MCP server for executing business SQL queries with dynamic tool generation"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"