Skip to content

Commit 17ec81e

Browse files
calmininiechenclaude
authored
feat: support more cli-client (#210)
Co-authored-by: User <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent b8e57b9 commit 17ec81e

File tree

6 files changed

+240
-0
lines changed

6 files changed

+240
-0
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ dependencies = [
3535
"deprecated>=1.2.18",
3636
"inquirerpy>=0.3.4",
3737
"rich-gradient>=0.3.2",
38+
"tomli>=2.2.1",
39+
"tomli-w>=1.2.0",
3840
]
3941

4042
[project.urls]

src/mcpm/clients/client_registry.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
from mcpm.clients.managers.claude_code import ClaudeCodeManager
1414
from mcpm.clients.managers.claude_desktop import ClaudeDesktopManager
1515
from mcpm.clients.managers.cline import ClineManager, RooCodeManager
16+
from mcpm.clients.managers.codex_cli import CodexCliManager
1617
from mcpm.clients.managers.continue_extension import ContinueManager
1718
from mcpm.clients.managers.cursor import CursorManager
1819
from mcpm.clients.managers.fiveire import FiveireManager
20+
from mcpm.clients.managers.gemini_cli import GeminiCliManager
1921
from mcpm.clients.managers.goose import GooseClientManager
2022
from mcpm.clients.managers.trae import TraeManager
2123
from mcpm.clients.managers.vscode import VSCodeManager
@@ -46,6 +48,8 @@ class ClientRegistry:
4648
"roo-code": RooCodeManager,
4749
"trae": TraeManager,
4850
"vscode": VSCodeManager,
51+
"gemini-cli": GeminiCliManager,
52+
"codex-cli": CodexCliManager,
4953
}
5054

5155
@classmethod

src/mcpm/clients/managers/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77
from mcpm.clients.managers.claude_code import ClaudeCodeManager
88
from mcpm.clients.managers.claude_desktop import ClaudeDesktopManager
99
from mcpm.clients.managers.cline import ClineManager
10+
from mcpm.clients.managers.codex_cli import CodexCliManager
1011
from mcpm.clients.managers.continue_extension import ContinueManager
1112
from mcpm.clients.managers.cursor import CursorManager
1213
from mcpm.clients.managers.fiveire import FiveireManager
14+
from mcpm.clients.managers.gemini_cli import GeminiCliManager
1315
from mcpm.clients.managers.goose import GooseClientManager
1416
from mcpm.clients.managers.trae import TraeManager
17+
from mcpm.clients.managers.vscode import VSCodeManager
1518
from mcpm.clients.managers.windsurf import WindsurfManager
1619

1720
__all__ = [
@@ -24,4 +27,7 @@
2427
"FiveireManager",
2528
"GooseClientManager",
2629
"TraeManager",
30+
"VSCodeManager",
31+
"GeminiCliManager",
32+
"CodexCliManager",
2733
]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Codex CLI integration utilities for MCP
3+
"""
4+
5+
import logging
6+
import os
7+
import shutil
8+
from typing import Any, Dict
9+
10+
import tomli
11+
import tomli_w
12+
13+
from mcpm.clients.base import JSONClientManager
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class CodexCliManager(JSONClientManager):
19+
"""Manages Codex CLI MCP server configurations"""
20+
21+
# Client information
22+
client_key = "codex-cli"
23+
display_name = "Codex CLI"
24+
download_url = "https://github.com/openai/codex"
25+
configure_key_name = "mcp_servers" # Codex uses mcp_servers instead of mcpServers
26+
27+
def __init__(self, config_path_override: str | None = None):
28+
"""Initialize the Codex CLI client manager
29+
30+
Args:
31+
config_path_override: Optional path to override the default config file location
32+
"""
33+
super().__init__(config_path_override=config_path_override)
34+
35+
if config_path_override:
36+
self.config_path = config_path_override
37+
else:
38+
# Codex CLI stores its settings in ~/.codex/config.toml
39+
self.config_path = os.path.expanduser("~/.codex/config.toml")
40+
41+
def _get_empty_config(self) -> Dict[str, Any]:
42+
"""Get empty config structure for Codex CLI"""
43+
return {"mcp_servers": {}}
44+
45+
def is_client_installed(self) -> bool:
46+
"""Check if Codex CLI is installed
47+
Returns:
48+
bool: True if codex command is available, False otherwise
49+
"""
50+
codex_executable = "codex.exe" if self._system == "Windows" else "codex"
51+
return shutil.which(codex_executable) is not None
52+
53+
def get_client_info(self) -> Dict[str, str]:
54+
"""Get information about this client
55+
56+
Returns:
57+
Dict: Information about the client including display name, download URL, and config path
58+
"""
59+
return {
60+
"name": self.display_name,
61+
"download_url": self.download_url,
62+
"config_file": self.config_path,
63+
"description": "OpenAI's Codex CLI tool",
64+
}
65+
66+
def _load_config(self) -> Dict[str, Any]:
67+
"""Load client configuration file
68+
69+
Returns:
70+
Dict containing the client configuration with at least {"mcp_servers": {}}
71+
"""
72+
try:
73+
# Check if config file exists
74+
if not os.path.exists(self.config_path):
75+
# Create empty config
76+
return self._get_empty_config()
77+
78+
# Codex uses TOML format instead of JSON
79+
with open(self.config_path, "rb") as f:
80+
config = tomli.load(f)
81+
82+
# Ensure mcp_servers key exists
83+
if self.configure_key_name not in config:
84+
config[self.configure_key_name] = {}
85+
86+
return config
87+
except Exception as e:
88+
logger.error(f"Error loading Codex config: {e}")
89+
return self._get_empty_config()
90+
91+
def _save_config(self, config: Dict[str, Any]) -> bool:
92+
"""Save configuration to client config file
93+
94+
Args:
95+
config: Configuration to save
96+
97+
Returns:
98+
bool: Success or failure
99+
"""
100+
try:
101+
# Create directory if it doesn't exist
102+
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)
103+
104+
# Codex uses TOML format instead of JSON
105+
with open(self.config_path, "wb") as f:
106+
tomli_w.dump(config, f)
107+
return True
108+
except Exception as e:
109+
logger.error(f"Error saving Codex config: {e}")
110+
return False
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
Gemini 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 GeminiCliManager(JSONClientManager):
16+
"""Manages Gemini CLI MCP server configurations"""
17+
18+
# Client information
19+
client_key = "gemini-cli"
20+
display_name = "Gemini CLI"
21+
download_url = "https://github.com/google-gemini/gemini-cli"
22+
23+
def __init__(self, config_path_override: str | None = None):
24+
"""Initialize the Gemini CLI client manager
25+
26+
Args:
27+
config_path_override: Optional path to override the default config file location
28+
"""
29+
super().__init__(config_path_override=config_path_override)
30+
31+
if config_path_override:
32+
self.config_path = config_path_override
33+
else:
34+
# Gemini CLI stores its settings in ~/.gemini/settings.json
35+
self.config_path = os.path.expanduser("~/.gemini/settings.json")
36+
37+
def _get_empty_config(self) -> Dict[str, Any]:
38+
"""Get empty config structure for Gemini CLI"""
39+
return {
40+
"mcpServers": {},
41+
# Include other default settings that Gemini CLI expects
42+
"contextFileName": "GEMINI.md",
43+
"autoAccept": False,
44+
"theme": "Default"
45+
}
46+
47+
def is_client_installed(self) -> bool:
48+
"""Check if Gemini CLI is installed
49+
Returns:
50+
bool: True if gemini command is available, False otherwise
51+
"""
52+
gemini_executable = "gemini.exe" if self._system == "Windows" else "gemini"
53+
return shutil.which(gemini_executable) is not None
54+
55+
def get_client_info(self) -> Dict[str, str]:
56+
"""Get information about this client
57+
58+
Returns:
59+
Dict: Information about the client including display name, download URL, and config path
60+
"""
61+
return {
62+
"name": self.display_name,
63+
"download_url": self.download_url,
64+
"config_file": self.config_path,
65+
"description": "Google's Gemini CLI tool",
66+
}

uv.lock

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)