Skip to content

Commit 7b3c6d1

Browse files
RYABOV EvgenyRYABOV Evgeny
authored andcommitted
feat: add Qwen CLI client manager support
1 parent bf2dc92 commit 7b3c6d1

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed

QWEN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reference `CLAUDE.md` for project conventions.

src/mcpm/clients/client_registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from mcpm.clients.managers.fiveire import FiveireManager
2020
from mcpm.clients.managers.gemini_cli import GeminiCliManager
2121
from mcpm.clients.managers.goose import GooseClientManager
22+
from mcpm.clients.managers.qwen_cli import QwenCliManager
2223
from mcpm.clients.managers.trae import TraeManager
2324
from mcpm.clients.managers.vscode import VSCodeManager
2425
from mcpm.clients.managers.windsurf import WindsurfManager
@@ -50,6 +51,7 @@ class ClientRegistry:
5051
"vscode": VSCodeManager,
5152
"gemini-cli": GeminiCliManager,
5253
"codex-cli": CodexCliManager,
54+
"qwen-cli": QwenCliManager,
5355
}
5456

5557
@classmethod

src/mcpm/clients/managers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from mcpm.clients.managers.fiveire import FiveireManager
1414
from mcpm.clients.managers.gemini_cli import GeminiCliManager
1515
from mcpm.clients.managers.goose import GooseClientManager
16+
from mcpm.clients.managers.qwen_cli import QwenCliManager
1617
from mcpm.clients.managers.trae import TraeManager
1718
from mcpm.clients.managers.vscode import VSCodeManager
1819
from mcpm.clients.managers.windsurf import WindsurfManager
@@ -26,6 +27,7 @@
2627
"ContinueManager",
2728
"FiveireManager",
2829
"GooseClientManager",
30+
"QwenCliManager",
2931
"TraeManager",
3032
"VSCodeManager",
3133
"GeminiCliManager",
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Qwen CLI integration utilities for MCP
3+
"""
4+
5+
import logging
6+
import os
7+
import shutil
8+
from typing import Any, Dict
9+
10+
from mcpm.clients.base import JSONClientManager
11+
12+
logger = logging.getLogger(__name__)
13+
14+
15+
class QwenCliManager(JSONClientManager):
16+
"""Manages Qwen CLI MCP server configurations"""
17+
18+
# Client information
19+
client_key = "qwen-cli"
20+
display_name = "Qwen CLI"
21+
download_url = "https://github.com/QwenLM/qwen-code"
22+
23+
def __init__(self, config_path_override: str | None = None):
24+
"""Initialize the Qwen CLI client manager
25+
26+
Args:
27+
config_path_override: Optional path to override the default config file location
28+
"""
29+
# Qwen CLI stores its settings in ~/.qwen/settings.json
30+
self.config_path = os.path.expanduser("~/.qwen/settings.json")
31+
super().__init__(config_path_override=config_path_override)
32+
33+
def _get_empty_config(self) -> Dict[str, Any]:
34+
"""Get empty config structure for Qwen CLI"""
35+
return {
36+
"mcpServers": {},
37+
# Include other default settings that Qwen CLI expects
38+
"theme": "Qwen Dark",
39+
"selectedAuthType": "openai",
40+
}
41+
42+
def is_client_installed(self) -> bool:
43+
"""Check if Qwen CLI is installed
44+
Returns:
45+
bool: True if qwen command is available, False otherwise
46+
"""
47+
qwen_executable = "qwen.exe" if self._system == "Windows" else "qwen"
48+
return shutil.which(qwen_executable) is not None
49+
50+
def get_client_info(self) -> Dict[str, str]:
51+
"""Get information about this client
52+
53+
Returns:
54+
Dict: Information about the client including display name, download URL, and config path
55+
"""
56+
return {
57+
"name": self.display_name,
58+
"download_url": self.download_url,
59+
"config_file": self.config_path,
60+
"description": "Alibaba's Qwen CLI tool",
61+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""
2+
Test for Qwen CLI manager
3+
"""
4+
5+
import os
6+
import tempfile
7+
from unittest.mock import patch
8+
9+
from mcpm.clients.managers.qwen_cli import QwenCliManager
10+
11+
12+
def test_qwen_cli_manager_initialization():
13+
"""Test QwenCliManager initialization"""
14+
# Test with default config path
15+
manager = QwenCliManager()
16+
assert manager.client_key == "qwen-cli"
17+
assert manager.display_name == "Qwen CLI"
18+
assert manager.download_url == "https://github.com/QwenLM/qwen-code"
19+
assert manager.config_path == os.path.expanduser("~/.qwen/settings.json")
20+
21+
# Test with custom config path
22+
custom_path = "/tmp/custom_settings.json"
23+
manager = QwenCliManager(config_path_override=custom_path)
24+
assert manager.config_path == custom_path
25+
26+
27+
def test_qwen_cli_manager_get_empty_config():
28+
"""Test QwenCliManager _get_empty_config method"""
29+
manager = QwenCliManager()
30+
config = manager._get_empty_config()
31+
assert "mcpServers" in config
32+
assert "theme" in config
33+
assert "selectedAuthType" in config
34+
assert config["mcpServers"] == {}
35+
36+
37+
def test_qwen_cli_manager_is_client_installed():
38+
"""Test QwenCliManager is_client_installed method"""
39+
manager = QwenCliManager()
40+
41+
# Mock shutil.which to return a path (simulating installed client)
42+
with patch("shutil.which", return_value="/usr/local/bin/qwen") as mock_which:
43+
assert manager.is_client_installed() is True
44+
mock_which.assert_called_with("qwen")
45+
46+
# Mock shutil.which to return None (simulating uninstalled client)
47+
with patch("shutil.which", return_value=None) as mock_which:
48+
assert manager.is_client_installed() is False
49+
mock_which.assert_called_with("qwen")
50+
51+
52+
def test_qwen_cli_manager_is_client_installed_windows():
53+
"""Test QwenCliManager is_client_installed method on Windows"""
54+
manager = QwenCliManager()
55+
56+
with patch.object(manager, "_system", "Windows"):
57+
# Mock shutil.which to return a path (simulating installed client)
58+
with patch("shutil.which", return_value="C:\\Program Files\\qwen\\qwen.exe") as mock_which:
59+
assert manager.is_client_installed() is True
60+
mock_which.assert_called_with("qwen.exe")
61+
62+
# Mock shutil.which to return None (simulating uninstalled client)
63+
with patch("shutil.which", return_value=None) as mock_which:
64+
assert manager.is_client_installed() is False
65+
mock_which.assert_called_with("qwen.exe")
66+
67+
68+
def test_qwen_cli_manager_get_empty_config_structure():
69+
"""Test QwenCliManager _get_empty_config method returns expected structure"""
70+
manager = QwenCliManager()
71+
config = manager._get_empty_config()
72+
73+
# Check that required keys are present
74+
assert "mcpServers" in config
75+
assert "theme" in config
76+
assert "selectedAuthType" in config
77+
78+
# Check default values
79+
assert config["mcpServers"] == {}
80+
assert config["theme"] == "Qwen Dark"
81+
assert config["selectedAuthType"] == "openai"
82+
83+
84+
def test_qwen_cli_manager_get_client_info():
85+
"""Test QwenCliManager get_client_info method"""
86+
manager = QwenCliManager()
87+
info = manager.get_client_info()
88+
assert info["name"] == "Qwen CLI"
89+
assert info["download_url"] == "https://github.com/QwenLM/qwen-code"
90+
assert info["config_file"] == os.path.expanduser("~/.qwen/settings.json")
91+
assert info["description"] == "Alibaba's Qwen CLI tool"

0 commit comments

Comments
 (0)