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:
Binary file not shown.
Binary file not shown.
@@ -177,27 +177,47 @@ async def handle_list_tools() -> list[types.Tool]:
|
||||
|
||||
logger.debug(f"处理工具配置: name={name}, description={description[:50] if description else 'None'}...")
|
||||
|
||||
# 优先使用 inputJsonSchema,如果不存在则从 sqlParams 转换
|
||||
input_json_schema = query.get("inputJsonSchema")
|
||||
if input_json_schema:
|
||||
# 优先从 sqlParams 转换,因为它包含更详细的文件配置信息
|
||||
sql_params = query.get("sqlParams")
|
||||
if sql_params:
|
||||
try:
|
||||
# inputJsonSchema 是 JSON 字符串,需要解析
|
||||
if isinstance(input_json_schema, str):
|
||||
input_schema = json.loads(input_json_schema)
|
||||
else:
|
||||
input_schema = input_json_schema
|
||||
# 清理 schema,确保 enum 只包含字符串
|
||||
input_schema = sanitize_json_schema(input_schema)
|
||||
logger.debug(f"使用 inputJsonSchema (已清理): {json.dumps(input_schema, ensure_ascii=False)}")
|
||||
except (json.JSONDecodeError, TypeError) as e:
|
||||
logger.warning(f"解析 inputJsonSchema 失败: {e},回退到 sqlParams")
|
||||
sql_params = query.get("sqlParams", "[]")
|
||||
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 转换
|
||||
sql_params = query.get("sqlParams", "[]")
|
||||
input_schema = convert_sql_params_to_input_schema(sql_params)
|
||||
logger.debug(f"从 sqlParams 转换的 inputSchema: {json.dumps(input_schema, ensure_ascii=False)}")
|
||||
# 如果没有 sqlParams,尝试使用 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 e:
|
||||
logger.error(f"解析 inputJsonSchema 失败: {e},使用空 schema")
|
||||
input_schema = {"type": "object", "properties": {}, "required": []}
|
||||
else:
|
||||
logger.warning("sqlParams 和 inputJsonSchema 都不存在,使用空 schema")
|
||||
input_schema = {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
tools.append(
|
||||
types.Tool(
|
||||
|
||||
@@ -7,6 +7,8 @@ Schema 转换器
|
||||
- paragraph: 段落/多行文本
|
||||
- select: 下拉选项
|
||||
- number: 数字输入
|
||||
- file: 单文件上传
|
||||
- fileList: 多文件上传
|
||||
"""
|
||||
import json
|
||||
from typing import Any
|
||||
@@ -60,7 +62,13 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
|
||||
"maxLength": 200,
|
||||
"defaultValue": "",
|
||||
"required": true,
|
||||
"options": ["选项1", "选项2"] # 仅 select 类型
|
||||
"options": ["选项1", "选项2"], # 仅 select 类型
|
||||
"fileConfig": { # 仅 file/fileList 类型
|
||||
"uploadMode": "both",
|
||||
"typeCategories": ["image"],
|
||||
"customAccept": "",
|
||||
"accept": ".jpg,.jpeg,.png"
|
||||
}
|
||||
}
|
||||
|
||||
Returns:
|
||||
@@ -73,6 +81,7 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
|
||||
max_length = param.get("maxLength")
|
||||
is_required = param.get("required", False)
|
||||
options = param.get("options", [])
|
||||
file_config = param.get("fileConfig", {})
|
||||
|
||||
property_schema = {
|
||||
"description": display_name
|
||||
@@ -112,12 +121,49 @@ def convert_param_to_schema_property(param: dict) -> tuple[str, dict, bool]:
|
||||
elif param_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:
|
||||
# 默认当作 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":
|
||||
try:
|
||||
property_schema["default"] = int(default_value) if str(default_value).isdigit() else float(default_value)
|
||||
|
||||
Binary file not shown.
@@ -7,8 +7,8 @@ import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 设置环境变量
|
||||
os.environ["workflowId"] = "2019984063311556609"
|
||||
os.environ["workflowExecuteKey"] = "wf_45bd630402664485a2ef5cd3ede5aa53"
|
||||
os.environ["workflowId"] = "2020107946473074690"
|
||||
os.environ["workflowExecuteKey"] = "wf_20170f33487d41459c180c6a277991a4"
|
||||
os.environ["backendBaseUrl"] = "http://192.168.2.236:8088"
|
||||
|
||||
# Import and run the actual MCP server
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
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"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
Reference in New Issue
Block a user