forked from OpenHands/OpenHands
-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathtest_cli_runtime_mcp.py
More file actions
156 lines (123 loc) Β· 5.82 KB
/
test_cli_runtime_mcp.py
File metadata and controls
156 lines (123 loc) Β· 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""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.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()
self.runtime = CLIRuntime(
config=self.config, event_stream=self.event_stream, sid='test-session'
)
@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