From 453bc885c436682d215241ee947d7d6c21d55553 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Fri, 1 May 2026 13:59:12 -0500 Subject: [PATCH 1/2] fix List[] return values --- azure/functions/decorators/function_app.py | 74 +++++++++++++++++++--- tests/decorators/test_mcp.py | 40 ++++++++++++ 2 files changed, 104 insertions(+), 10 deletions(-) diff --git a/azure/functions/decorators/function_app.py b/azure/functions/decorators/function_app.py index a1a91e24..99c4f600 100644 --- a/azure/functions/decorators/function_app.py +++ b/azure/functions/decorators/function_app.py @@ -1663,16 +1663,44 @@ def decorator(fb: FunctionBuilder) -> FunctionBuilder: args = typing.get_args(return_annotation) # Check for official MCP SDK types in lists - try: - if isinstance(args[0], type): - # Check if the type is from the mcp.types module - if hasattr(args[0], '__module__'): - module = args[0].__module__ - if module and (module.startswith('mcp.types') - or module == 'mcp.types'): - is_mcp_sdk_type = True - except (ImportError, TypeError, AttributeError): - pass + if origin in (list, List): + # For List[T], check if T is an MCP type + try: + if len(args) > 0: + list_item_type = args[0] + # Check if it's a direct MCP type + if isinstance(list_item_type, type): + if hasattr(list_item_type, '__module__'): + module = list_item_type.__module__ + if (module and + (module.startswith('mcp.types') + or module == 'mcp.types')): + is_mcp_sdk_type = True + # Check if it's a Union of MCP types + elif hasattr(list_item_type, '__origin__'): + union_origin = typing.get_origin( + list_item_type) + if union_origin is Union: + union_args = typing.get_args( + list_item_type) + for union_arg in union_args: + if (isinstance(union_arg, type) + and union_arg is not + type(None)): + if hasattr(union_arg, + '__module__'): + module = ( + union_arg.__module__) + if (module and + (module.startswith( + 'mcp.types') + or module == + 'mcp.types')): + is_mcp_sdk_type = True + break + except (ImportError, TypeError, AttributeError, + IndexError): + pass # Check for Optional[T] where T is an MCP type if origin is Union: @@ -1801,6 +1829,32 @@ async def wrapper(context: str, *args, **kwargs): "structuredContent": structured_content_json })) + # Handle lists of MCP SDK content blocks + # Wrap them in a CallToolResult structure + elif isinstance(result, list) and len(result) > 0: + first_item = result[0] + if _is_mcp_sdk_type(first_item): + # Serialize all blocks in the list + from ..mcp import _serialize_content_block + blocks_list = [_serialize_content_block(block) + for block in result] + + # Create a CallToolResult-like structure + # containing the blocks + call_tool_result = { + "content": blocks_list + } + full_result_json = json.dumps(call_tool_result) + + # Return in CallToolResult format + # (list of blocks doesn't have separate + # structuredContent) + return str(json.dumps({ + "type": "call_tool_result", + "content": full_result_json, + "structuredContent": None + })) + # Handle all other MCP SDK types # (TextContent, ImageContent, ResourceLink, etc.) elif _is_mcp_sdk_type(result): diff --git a/tests/decorators/test_mcp.py b/tests/decorators/test_mcp.py index 5533069a..5a036221 100644 --- a/tests/decorators/test_mcp.py +++ b/tests/decorators/test_mcp.py @@ -675,6 +675,18 @@ def get_texts() -> List[TextContent]: trigger = get_texts._function._bindings[0] self.assertTrue(trigger.use_result_schema) + def test_auto_detect_list_union_mcp_types(self): + """Test auto-detection of List[Union[MCP types]] return type""" + from typing import Union + + @self.app.mcp_tool() + def get_mixed_content() -> List[Union[TextContent, ImageContent]]: + """Returns mixed content blocks""" + return [TextContent(type="text", text="test")] + + trigger = get_mixed_content._function._bindings[0] + self.assertTrue(trigger.use_result_schema) + def test_auto_detect_optional_mcp_image_content(self): """Test auto-detection of Optional[ImageContent] return type""" @self.app.mcp_tool() @@ -907,6 +919,34 @@ def test_func() -> str: self.assertIn("content", result_obj) self.assertIn("structuredContent", result_obj) + def test_structured_content_in_list_of_mcp_types(self): + """Test that List[MCP SDK types] includes structuredContent""" + @self.app.mcp_tool() + def test_func() -> List[TextContent]: + """Test function""" + return [ + TextContent(type="text", text="First item"), + TextContent(type="text", text="Second item") + ] + + wrapper = test_func._function._func + context = json.dumps({"arguments": {}}) + result = asyncio.run(wrapper(context)) + result_obj = json.loads(result) + + # List of content blocks is wrapped as CallToolResult + self.assertIn("type", result_obj) + self.assertEqual(result_obj["type"], "call_tool_result") + self.assertIn("content", result_obj) + self.assertIn("structuredContent", result_obj) + + # Content contains the CallToolResult structure with the blocks + content_obj = json.loads(result_obj["content"]) + self.assertIn("content", content_obj) + self.assertEqual(len(content_obj["content"]), 2) + self.assertEqual(content_obj["content"][0]["text"], "First item") + self.assertEqual(content_obj["content"][1]["text"], "Second item") + class TestMCPPackageNotInstalled(unittest.TestCase): """Tests for graceful degradation when mcp package is not installed""" From 1ebfee6fc31b00a978189b93212e70cb4ef37b91 Mon Sep 17 00:00:00 2001 From: Victoria Hall Date: Mon, 4 May 2026 09:50:57 -0500 Subject: [PATCH 2/2] lint --- azure/functions/decorators/function_app.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/azure/functions/decorators/function_app.py b/azure/functions/decorators/function_app.py index 99c4f600..629e7279 100644 --- a/azure/functions/decorators/function_app.py +++ b/azure/functions/decorators/function_app.py @@ -1672,9 +1672,9 @@ def decorator(fb: FunctionBuilder) -> FunctionBuilder: if isinstance(list_item_type, type): if hasattr(list_item_type, '__module__'): module = list_item_type.__module__ - if (module and - (module.startswith('mcp.types') - or module == 'mcp.types')): + if (module + and (module.startswith('mcp.types') + or module == 'mcp.types')): is_mcp_sdk_type = True # Check if it's a Union of MCP types elif hasattr(list_item_type, '__origin__'): @@ -1691,11 +1691,11 @@ def decorator(fb: FunctionBuilder) -> FunctionBuilder: '__module__'): module = ( union_arg.__module__) - if (module and - (module.startswith( + if (module + and (module.startswith( 'mcp.types') - or module == - 'mcp.types')): + or module + == 'mcp.types')): is_mcp_sdk_type = True break except (ImportError, TypeError, AttributeError,