From bc14ad6a8f6476f205dabd5f382051b7bd410152 Mon Sep 17 00:00:00 2001 From: lif <1835304752@qq.com> Date: Mon, 30 Mar 2026 23:05:57 +0800 Subject: [PATCH] fix: map checkbox and json_object types in MCP schema publishing (#34226) Signed-off-by: majiayu000 <1835304752@qq.com> Co-authored-by: Asuka Minato --- api/controllers/mcp/mcp.py | 1 + api/core/mcp/server/streamable_http.py | 8 ++ .../core/mcp/server/test_streamable_http.py | 80 +++++++++++++++++-- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/api/controllers/mcp/mcp.py b/api/controllers/mcp/mcp.py index 3d00f77e79f..58ec76243b9 100644 --- a/api/controllers/mcp/mcp.py +++ b/api/controllers/mcp/mcp.py @@ -174,6 +174,7 @@ class MCPAppApi(Resource): required=variable.get("required", False), max_length=variable.get("max_length"), options=variable.get("options") or [], + json_schema=variable.get("json_schema"), ) def _parse_mcp_request(self, args: dict) -> mcp_types.ClientRequest | mcp_types.ClientNotification: diff --git a/api/core/mcp/server/streamable_http.py b/api/core/mcp/server/streamable_http.py index 27000c947c1..278add8cc90 100644 --- a/api/core/mcp/server/streamable_http.py +++ b/api/core/mcp/server/streamable_http.py @@ -260,4 +260,12 @@ def convert_input_form_to_parameters( parameters[item.variable]["enum"] = item.options elif item.type == VariableEntityType.NUMBER: parameters[item.variable]["type"] = "number" + elif item.type == VariableEntityType.CHECKBOX: + parameters[item.variable]["type"] = "boolean" + elif item.type == VariableEntityType.JSON_OBJECT: + parameters[item.variable]["type"] = "object" + if item.json_schema: + for key in ("properties", "required", "additionalProperties"): + if key in item.json_schema: + parameters[item.variable][key] = item.json_schema[key] return parameters, required diff --git a/api/tests/unit_tests/core/mcp/server/test_streamable_http.py b/api/tests/unit_tests/core/mcp/server/test_streamable_http.py index 313d18c695d..9a815fb94d0 100644 --- a/api/tests/unit_tests/core/mcp/server/test_streamable_http.py +++ b/api/tests/unit_tests/core/mcp/server/test_streamable_http.py @@ -415,12 +415,44 @@ class TestUtilityFunctions: label="Upload", required=False, ), + VariableEntity( + type=VariableEntityType.CHECKBOX, + variable="enabled", + description="Enable flag", + label="Enabled", + required=False, + ), + VariableEntity( + type=VariableEntityType.JSON_OBJECT, + variable="config", + description="Config object", + label="Config", + required=True, + ), + VariableEntity( + type=VariableEntityType.JSON_OBJECT, + variable="schema_config", + description="Config with schema", + label="Schema Config", + required=False, + json_schema={ + "properties": { + "host": {"type": "string"}, + "port": {"type": "number"}, + }, + "required": ["host"], + "additionalProperties": False, + }, + ), ] parameters_dict: dict[str, str] = { "name": "Enter your name", "category": "Select category", "count": "Enter count", + "enabled": "Enable flag", + "config": "Config object", + "schema_config": "Config with schema", } parameters, required = convert_input_form_to_parameters(user_input_form, parameters_dict) @@ -437,20 +469,35 @@ class TestUtilityFunctions: assert "count" in parameters assert parameters["count"]["type"] == "number" - # FILE type should be skipped - it creates empty dict but gets filtered later - # Check that it doesn't have any meaningful content - if "upload" in parameters: - assert parameters["upload"] == {} + # FILE type is skipped entirely via `continue` — key should not exist + assert "upload" not in parameters + + # CHECKBOX maps to boolean + assert parameters["enabled"]["type"] == "boolean" + + # JSON_OBJECT without json_schema maps to object + assert parameters["config"]["type"] == "object" + assert "properties" not in parameters["config"] + + # JSON_OBJECT with json_schema forwards schema keys + assert parameters["schema_config"]["type"] == "object" + assert parameters["schema_config"]["properties"] == { + "host": {"type": "string"}, + "port": {"type": "number"}, + } + assert parameters["schema_config"]["required"] == ["host"] + assert parameters["schema_config"]["additionalProperties"] is False # Check required fields assert "name" in required assert "count" in required + assert "config" in required assert "category" not in required # Note: _get_request_id function has been removed as request_id is now passed as parameter def test_convert_input_form_to_parameters_jsonschema_validation_ok(self): - """Current schema uses 'number' for numeric fields; it should be a valid JSON Schema.""" + """Generated schema with all supported types should be valid JSON Schema.""" user_input_form = [ VariableEntity( type=VariableEntityType.NUMBER, @@ -466,11 +513,27 @@ class TestUtilityFunctions: label="Name", required=False, ), + VariableEntity( + type=VariableEntityType.CHECKBOX, + variable="enabled", + description="Toggle", + label="Enabled", + required=False, + ), + VariableEntity( + type=VariableEntityType.JSON_OBJECT, + variable="metadata", + description="Metadata", + label="Metadata", + required=False, + ), ] parameters_dict = { "count": "Enter count", "name": "Enter your name", + "enabled": "Toggle flag", + "metadata": "Metadata object", } parameters, required = convert_input_form_to_parameters(user_input_form, parameters_dict) @@ -485,9 +548,12 @@ class TestUtilityFunctions: # 1) The schema itself must be valid jsonschema.Draft202012Validator.check_schema(schema) - # 2) Both float and integer instances should pass validation + # 2) Validate instances with all types jsonschema.validate(instance={"count": 3.14, "name": "alice"}, schema=schema) - jsonschema.validate(instance={"count": 2, "name": "bob"}, schema=schema) + jsonschema.validate( + instance={"count": 2, "enabled": True, "metadata": {"key": "val"}}, + schema=schema, + ) def test_legacy_float_type_schema_is_invalid(self): """Legacy/buggy behavior: using 'float' should produce an invalid JSON Schema."""