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'}...")
|
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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Binary file not shown.
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user