mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Graham Neubig <neubig@gmail.com> Co-authored-by: Engel Nyst <enyst@users.noreply.github.com>
162 lines
6.0 KiB
Python
162 lines
6.0 KiB
Python
"""Tests for CLI Runtime MCP functionality."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from openhands.core.config import OpenHandsConfig
|
|
from openhands.core.config.mcp_config import (
|
|
MCPConfig,
|
|
MCPSSEServerConfig,
|
|
MCPStdioServerConfig,
|
|
)
|
|
from openhands.events.action.mcp import MCPAction
|
|
from openhands.events.observation import ErrorObservation
|
|
from openhands.events.observation.mcp import MCPObservation
|
|
from openhands.llm.llm_registry import LLMRegistry
|
|
from openhands.runtime.impl.cli.cli_runtime import CLIRuntime
|
|
|
|
|
|
class TestCLIRuntimeMCP:
|
|
"""Test MCP functionality in CLI Runtime."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures."""
|
|
self.config = OpenHandsConfig()
|
|
self.event_stream = MagicMock()
|
|
llm_registry = LLMRegistry(config=OpenHandsConfig())
|
|
self.runtime = CLIRuntime(
|
|
config=self.config,
|
|
event_stream=self.event_stream,
|
|
sid='test-session',
|
|
llm_registry=llm_registry,
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_call_tool_mcp_no_servers_configured(self):
|
|
"""Test MCP call with no servers configured."""
|
|
# Set up empty MCP config
|
|
self.runtime.config.mcp = MCPConfig()
|
|
|
|
action = MCPAction(name='test_tool', arguments={'arg1': 'value1'})
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = await self.runtime.call_tool_mcp(action)
|
|
|
|
assert isinstance(result, ErrorObservation)
|
|
assert 'No MCP servers configured' in result.content
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('openhands.mcp.utils.create_mcp_clients')
|
|
async def test_call_tool_mcp_no_clients_created(self, mock_create_clients):
|
|
"""Test MCP call when no clients can be created."""
|
|
# Set up MCP config with servers
|
|
self.runtime.config.mcp = MCPConfig(
|
|
sse_servers=[MCPSSEServerConfig(url='http://test.com')]
|
|
)
|
|
|
|
# Mock create_mcp_clients to return empty list
|
|
mock_create_clients.return_value = []
|
|
|
|
action = MCPAction(name='test_tool', arguments={'arg1': 'value1'})
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = await self.runtime.call_tool_mcp(action)
|
|
|
|
assert isinstance(result, ErrorObservation)
|
|
assert 'No MCP clients could be created' in result.content
|
|
mock_create_clients.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('openhands.mcp.utils.create_mcp_clients')
|
|
@patch('openhands.mcp.utils.call_tool_mcp')
|
|
async def test_call_tool_mcp_success(self, mock_call_tool, mock_create_clients):
|
|
"""Test successful MCP tool call."""
|
|
# Set up MCP config with servers
|
|
self.runtime.config.mcp = MCPConfig(
|
|
sse_servers=[MCPSSEServerConfig(url='http://test.com')],
|
|
stdio_servers=[MCPStdioServerConfig(name='test-stdio', command='python')],
|
|
)
|
|
|
|
# Mock successful client creation
|
|
mock_client = MagicMock()
|
|
mock_create_clients.return_value = [mock_client]
|
|
|
|
# Mock successful tool call
|
|
expected_observation = MCPObservation(
|
|
content='{"result": "success"}',
|
|
name='test_tool',
|
|
arguments={'arg1': 'value1'},
|
|
)
|
|
mock_call_tool.return_value = expected_observation
|
|
|
|
action = MCPAction(name='test_tool', arguments={'arg1': 'value1'})
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = await self.runtime.call_tool_mcp(action)
|
|
|
|
assert result == expected_observation
|
|
mock_create_clients.assert_called_once_with(
|
|
self.runtime.config.mcp.sse_servers,
|
|
self.runtime.config.mcp.shttp_servers,
|
|
self.runtime.sid,
|
|
self.runtime.config.mcp.stdio_servers,
|
|
)
|
|
mock_call_tool.assert_called_once_with([mock_client], action)
|
|
|
|
@pytest.mark.asyncio
|
|
@patch('openhands.mcp.utils.create_mcp_clients')
|
|
async def test_call_tool_mcp_exception_handling(self, mock_create_clients):
|
|
"""Test exception handling in MCP tool call."""
|
|
# Set up MCP config with servers
|
|
self.runtime.config.mcp = MCPConfig(
|
|
sse_servers=[MCPSSEServerConfig(url='http://test.com')]
|
|
)
|
|
|
|
# Mock create_mcp_clients to raise an exception
|
|
mock_create_clients.side_effect = Exception('Connection error')
|
|
|
|
action = MCPAction(name='test_tool', arguments={'arg1': 'value1'})
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = await self.runtime.call_tool_mcp(action)
|
|
|
|
assert isinstance(result, ErrorObservation)
|
|
assert 'Error executing MCP tool test_tool' in result.content
|
|
assert 'Connection error' in result.content
|
|
|
|
def test_get_mcp_config_basic(self):
|
|
"""Test basic MCP config retrieval."""
|
|
# Set up MCP config
|
|
expected_config = MCPConfig(
|
|
sse_servers=[MCPSSEServerConfig(url='http://test.com')],
|
|
stdio_servers=[MCPStdioServerConfig(name='test-stdio', command='python')],
|
|
)
|
|
self.runtime.config.mcp = expected_config
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = self.runtime.get_mcp_config()
|
|
|
|
assert result == expected_config
|
|
|
|
def test_get_mcp_config_with_extra_stdio_servers(self):
|
|
"""Test MCP config with extra stdio servers."""
|
|
# Set up initial MCP config
|
|
initial_stdio_server = MCPStdioServerConfig(name='initial', command='python')
|
|
self.runtime.config.mcp = MCPConfig(stdio_servers=[initial_stdio_server])
|
|
|
|
# Add extra stdio servers
|
|
extra_servers = [
|
|
MCPStdioServerConfig(name='extra1', command='node'),
|
|
MCPStdioServerConfig(name='extra2', command='java'),
|
|
]
|
|
|
|
with patch('sys.platform', 'linux'):
|
|
result = self.runtime.get_mcp_config(extra_stdio_servers=extra_servers)
|
|
|
|
# Should have all three servers
|
|
assert len(result.stdio_servers) == 3
|
|
assert initial_stdio_server in result.stdio_servers
|
|
assert extra_servers[0] in result.stdio_servers
|
|
assert extra_servers[1] in result.stdio_servers
|