Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions QWEN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Reference `CLAUDE.md` for project conventions.
2 changes: 2 additions & 0 deletions src/mcpm/clients/client_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from mcpm.clients.managers.fiveire import FiveireManager
from mcpm.clients.managers.gemini_cli import GeminiCliManager
from mcpm.clients.managers.goose import GooseClientManager
from mcpm.clients.managers.qwen_cli import QwenCliManager
from mcpm.clients.managers.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
from mcpm.clients.managers.windsurf import WindsurfManager
Expand Down Expand Up @@ -50,6 +51,7 @@ class ClientRegistry:
"vscode": VSCodeManager,
"gemini-cli": GeminiCliManager,
"codex-cli": CodexCliManager,
"qwen-cli": QwenCliManager,
}

@classmethod
Expand Down
2 changes: 2 additions & 0 deletions src/mcpm/clients/managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from mcpm.clients.managers.fiveire import FiveireManager
from mcpm.clients.managers.gemini_cli import GeminiCliManager
from mcpm.clients.managers.goose import GooseClientManager
from mcpm.clients.managers.qwen_cli import QwenCliManager
from mcpm.clients.managers.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
from mcpm.clients.managers.windsurf import WindsurfManager
Expand All @@ -26,6 +27,7 @@
"ContinueManager",
"FiveireManager",
"GooseClientManager",
"QwenCliManager",
"TraeManager",
"VSCodeManager",
"GeminiCliManager",
Expand Down
61 changes: 61 additions & 0 deletions src/mcpm/clients/managers/qwen_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Qwen CLI integration utilities for MCP
"""

import logging
import os
import shutil
from typing import Any, Dict

from mcpm.clients.base import JSONClientManager

logger = logging.getLogger(__name__)


class QwenCliManager(JSONClientManager):
"""Manages Qwen CLI MCP server configurations"""

# Client information
client_key = "qwen-cli"
display_name = "Qwen CLI"
download_url = "https://github.com/QwenLM/qwen-code"

def __init__(self, config_path_override: str | None = None):
"""Initialize the Qwen CLI client manager

Args:
config_path_override: Optional path to override the default config file location
"""
# Qwen CLI stores its settings in ~/.qwen/settings.json
self.config_path = os.path.expanduser("~/.qwen/settings.json")
super().__init__(config_path_override=config_path_override)

def _get_empty_config(self) -> Dict[str, Any]:
"""Get empty config structure for Qwen CLI"""
return {
"mcpServers": {},
# Include other default settings that Qwen CLI expects
"theme": "Qwen Dark",
"selectedAuthType": "openai",
}

def is_client_installed(self) -> bool:
"""Check if Qwen CLI is installed
Returns:
bool: True if qwen command is available, False otherwise
"""
qwen_executable = "qwen.exe" if self._system == "Windows" else "qwen"
return shutil.which(qwen_executable) is not None

def get_client_info(self) -> Dict[str, str]:
"""Get information about this client

Returns:
Dict: Information about the client including display name, download URL, and config path
"""
return {
"name": self.display_name,
"download_url": self.download_url,
"config_file": self.config_path,
"description": "Alibaba's Qwen CLI tool",
}
91 changes: 91 additions & 0 deletions tests/test_clients/test_qwen_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Test for Qwen CLI manager
"""

import os
import tempfile
from unittest.mock import patch

from mcpm.clients.managers.qwen_cli import QwenCliManager


def test_qwen_cli_manager_initialization():
"""Test QwenCliManager initialization"""
# Test with default config path
manager = QwenCliManager()
assert manager.client_key == "qwen-cli"
assert manager.display_name == "Qwen CLI"
assert manager.download_url == "https://github.com/QwenLM/qwen-code"
assert manager.config_path == os.path.expanduser("~/.qwen/settings.json")

# Test with custom config path
custom_path = "/tmp/custom_settings.json"
manager = QwenCliManager(config_path_override=custom_path)
assert manager.config_path == custom_path


def test_qwen_cli_manager_get_empty_config():
"""Test QwenCliManager _get_empty_config method"""
manager = QwenCliManager()
config = manager._get_empty_config()
assert "mcpServers" in config
assert "theme" in config
assert "selectedAuthType" in config
assert config["mcpServers"] == {}


def test_qwen_cli_manager_is_client_installed():
"""Test QwenCliManager is_client_installed method"""
manager = QwenCliManager()

# Mock shutil.which to return a path (simulating installed client)
with patch("shutil.which", return_value="/usr/local/bin/qwen") as mock_which:
assert manager.is_client_installed() is True
mock_which.assert_called_with("qwen")

# Mock shutil.which to return None (simulating uninstalled client)
with patch("shutil.which", return_value=None) as mock_which:
assert manager.is_client_installed() is False
mock_which.assert_called_with("qwen")


def test_qwen_cli_manager_is_client_installed_windows():
"""Test QwenCliManager is_client_installed method on Windows"""
manager = QwenCliManager()

with patch.object(manager, "_system", "Windows"):
# Mock shutil.which to return a path (simulating installed client)
with patch("shutil.which", return_value="C:\\Program Files\\qwen\\qwen.exe") as mock_which:
assert manager.is_client_installed() is True
mock_which.assert_called_with("qwen.exe")

# Mock shutil.which to return None (simulating uninstalled client)
with patch("shutil.which", return_value=None) as mock_which:
assert manager.is_client_installed() is False
mock_which.assert_called_with("qwen.exe")


def test_qwen_cli_manager_get_empty_config_structure():
"""Test QwenCliManager _get_empty_config method returns expected structure"""
manager = QwenCliManager()
config = manager._get_empty_config()

# Check that required keys are present
assert "mcpServers" in config
assert "theme" in config
assert "selectedAuthType" in config

# Check default values
assert config["mcpServers"] == {}
assert config["theme"] == "Qwen Dark"
assert config["selectedAuthType"] == "openai"


def test_qwen_cli_manager_get_client_info():
"""Test QwenCliManager get_client_info method"""
manager = QwenCliManager()
info = manager.get_client_info()
assert info["name"] == "Qwen CLI"
assert info["download_url"] == "https://github.com/QwenLM/qwen-code"
assert info["config_file"] == os.path.expanduser("~/.qwen/settings.json")
assert info["description"] == "Alibaba's Qwen CLI tool"