diff --git a/libraries/microsoft-agents-a365-tooling-extensions-agentframework/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py b/libraries/microsoft-agents-a365-tooling-extensions-agentframework/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py index c4b1ec88..e895db06 100644 --- a/libraries/microsoft-agents-a365-tooling-extensions-agentframework/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py +++ b/libraries/microsoft-agents-a365-tooling-extensions-agentframework/microsoft_agents_a365/tooling/extensions/agentframework/services/mcp_tool_registration_service.py @@ -225,6 +225,8 @@ async def send_chat_history_messages( Args: chat_messages: Sequence of Agent Framework ChatMessage objects to send. + Can be empty - the request will still be sent to register + the user message from turn_context.activity.text. turn_context: TurnContext from the Agents SDK containing conversation info. tool_options: Optional configuration for the request. Defaults to AgentFramework-specific options if not provided. @@ -235,6 +237,12 @@ async def send_chat_history_messages( Raises: ValueError: If chat_messages or turn_context is None. + Note: + Even if chat_messages is empty or all messages are filtered during + conversion, the request will still be sent to the MCP platform. This + ensures the user message from turn_context.activity.text is registered + correctly for real-time threat protection. + Example: >>> service = McpToolRegistrationService() >>> messages = [ChatMessage(role=Role.USER, text="Hello")] @@ -249,11 +257,6 @@ async def send_chat_history_messages( if turn_context is None: raise ValueError("turn_context cannot be None") - # Handle empty messages - return success with warning - if len(chat_messages) == 0: - self._logger.warning("Empty message list provided to send_chat_history_messages") - return OperationResult.success() - self._logger.info(f"Send chat history initiated with {len(chat_messages)} messages") # Use default options if not provided @@ -263,10 +266,13 @@ async def send_chat_history_messages( # Convert messages to ChatHistoryMessage format history_messages = self._convert_chat_messages_to_history(chat_messages) - # Check if all messages were filtered out during conversion + # Call core service even with empty history_messages to register + # the user message from turn_context.activity.text in the MCP platform. if len(history_messages) == 0: - self._logger.warning("All messages were filtered out during conversion (empty content)") - return OperationResult.success() + self._logger.info( + "Empty history messages (either no input or all filtered), " + "still sending to register user message" + ) # Delegate to core service result = await self._mcp_server_configuration_service.send_chat_history( diff --git a/libraries/microsoft-agents-a365-tooling-extensions-openai/microsoft_agents_a365/tooling/extensions/openai/mcp_tool_registration_service.py b/libraries/microsoft-agents-a365-tooling-extensions-openai/microsoft_agents_a365/tooling/extensions/openai/mcp_tool_registration_service.py index 7708b4d2..dc4893fb 100644 --- a/libraries/microsoft-agents-a365-tooling-extensions-openai/microsoft_agents_a365/tooling/extensions/openai/mcp_tool_registration_service.py +++ b/libraries/microsoft-agents-a365-tooling-extensions-openai/microsoft_agents_a365/tooling/extensions/openai/mcp_tool_registration_service.py @@ -347,7 +347,8 @@ async def send_chat_history_messages( and activity.text. messages: List of OpenAI TResponseInputItem messages to send. Supports UserMessage, AssistantMessage, SystemMessage, and other OpenAI - message types. + message types. Can be empty - the request will still be sent to + register the user message from turn_context.activity.text. options: Optional ToolOptions for customization. If not provided, uses default options with orchestrator_name="OpenAI". @@ -359,6 +360,12 @@ async def send_chat_history_messages( Raises: ValueError: If turn_context is None or messages is None. + Note: + Even if messages is empty or all messages are filtered during conversion, + the request will still be sent to the MCP platform. This ensures the user + message from turn_context.activity.text is registered correctly for + real-time threat protection. + Example: >>> from microsoft_agents_a365.tooling.extensions.openai import ( ... McpToolRegistrationService @@ -382,11 +389,6 @@ async def send_chat_history_messages( if messages is None: raise ValueError("messages cannot be None") - # Handle empty list as no-op - if len(messages) == 0: - self._logger.info("Empty message list provided, returning success") - return OperationResult.success() - self._logger.info(f"Sending {len(messages)} OpenAI messages as chat history") # Set default options @@ -399,9 +401,13 @@ async def send_chat_history_messages( # Convert OpenAI messages to ChatHistoryMessage format chat_history_messages = self._convert_openai_messages_to_chat_history(messages) + # Call core service even with empty chat_history_messages to register + # the user message from turn_context.activity.text in the MCP platform. if len(chat_history_messages) == 0: - self._logger.warning("No messages could be converted to chat history format") - return OperationResult.success() + self._logger.info( + "Empty chat history messages (either no input or all filtered), " + "still sending to register user message" + ) self._logger.debug( f"Converted {len(chat_history_messages)} messages to ChatHistoryMessage format" diff --git a/libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py b/libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py index edc9c4db..4e93f830 100644 --- a/libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py +++ b/libraries/microsoft-agents-a365-tooling/microsoft_agents_a365/tooling/services/mcp_tool_server_configuration_service.py @@ -583,6 +583,11 @@ async def send_chat_history( turn_context.activity is None, or any of the required fields (conversation.id, activity.id, activity.text) are missing or empty. + Note: + Even if chat_history_messages is empty, the request will still be sent to + the MCP platform. This ensures the user message from turn_context.activity.text + is registered correctly for real-time threat protection. + Example: >>> from datetime import datetime, timezone >>> from microsoft_agents_a365.tooling.models import ChatHistoryMessage diff --git a/tests/tooling/extensions/agentframework/services/test_send_chat_history.py b/tests/tooling/extensions/agentframework/services/test_send_chat_history.py index f3a780a3..30dd612e 100644 --- a/tests/tooling/extensions/agentframework/services/test_send_chat_history.py +++ b/tests/tooling/extensions/agentframework/services/test_send_chat_history.py @@ -120,17 +120,20 @@ async def test_send_chat_history_from_store_validates_turn_context_none( @pytest.mark.asyncio @pytest.mark.unit - async def test_send_chat_history_messages_empty_messages_returns_success( + async def test_send_chat_history_messages_empty_messages_calls_core_service( self, service, mock_turn_context ): - """Test that empty message list returns success with warning log.""" + """Test that empty message list still calls core service to register user message.""" # Act result = await service.send_chat_history_messages([], mock_turn_context) # Assert assert result.succeeded is True - # Core service should not be called for empty messages - service._mcp_server_configuration_service.send_chat_history.assert_not_called() + # Core service SHOULD be called even for empty messages to register the user message + service._mcp_server_configuration_service.send_chat_history.assert_called_once() + # Verify empty list was passed + call_args = service._mcp_server_configuration_service.send_chat_history.call_args + assert call_args.kwargs["chat_history_messages"] == [] @pytest.mark.asyncio @pytest.mark.unit @@ -492,10 +495,10 @@ async def test_send_chat_history_messages_skips_messages_with_none_role( @pytest.mark.asyncio @pytest.mark.unit - async def test_send_chat_history_messages_all_filtered_returns_success( + async def test_send_chat_history_messages_all_filtered_still_calls_core( self, service, mock_turn_context, mock_role ): - """Test that all messages filtered out returns success without calling core (CRM-006).""" + """Test that all messages filtered out still calls core service to register user message.""" # Arrange - all messages have empty content msg1 = Mock() msg1.message_id = "msg-1" @@ -517,8 +520,11 @@ async def test_send_chat_history_messages_all_filtered_returns_success( # Assert assert result.succeeded is True - # Core service should not be called when all messages are filtered out - service._mcp_server_configuration_service.send_chat_history.assert_not_called() + # Core service SHOULD be called even when all messages are filtered out to register user message + service._mcp_server_configuration_service.send_chat_history.assert_called_once() + # Verify empty list was passed (all messages filtered) + call_args = service._mcp_server_configuration_service.send_chat_history.call_args + assert call_args.kwargs["chat_history_messages"] == [] @pytest.mark.asyncio @pytest.mark.unit diff --git a/tests/tooling/extensions/openai/test_send_chat_history.py b/tests/tooling/extensions/openai/test_send_chat_history.py index 9d7b9f12..9222139a 100644 --- a/tests/tooling/extensions/openai/test_send_chat_history.py +++ b/tests/tooling/extensions/openai/test_send_chat_history.py @@ -45,14 +45,26 @@ async def test_send_chat_history_messages_validates_messages_none( # UV-03 @pytest.mark.asyncio @pytest.mark.unit - async def test_send_chat_history_messages_empty_list_returns_success( + async def test_send_chat_history_messages_empty_list_calls_core_service( self, service, mock_turn_context ): - """Test that empty message list returns success (no-op).""" - result = await service.send_chat_history_messages(mock_turn_context, []) + """Test that empty message list still calls core service to register user message.""" + with patch.object( + service.config_service, + "send_chat_history", + new_callable=AsyncMock, + ) as mock_send: + mock_send.return_value = OperationResult.success() - assert result.succeeded is True - assert len(result.errors) == 0 + result = await service.send_chat_history_messages(mock_turn_context, []) + + assert result.succeeded is True + assert len(result.errors) == 0 + # Core service SHOULD be called even for empty messages to register the user message + mock_send.assert_called_once() + # Verify empty list was passed + call_args = mock_send.call_args + assert call_args.kwargs["chat_history_messages"] == [] # UV-04 @pytest.mark.asyncio