Skip to content
Merged
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
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ dependencies = [
"deprecated>=1.2.18",
"inquirerpy>=0.3.4",
"rich-gradient>=0.3.2",
"tomli>=2.2.1",
"tomli-w>=1.2.0",
]

[project.urls]
Expand Down
4 changes: 4 additions & 0 deletions src/mcpm/clients/client_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
from mcpm.clients.managers.claude_code import ClaudeCodeManager
from mcpm.clients.managers.claude_desktop import ClaudeDesktopManager
from mcpm.clients.managers.cline import ClineManager, RooCodeManager
from mcpm.clients.managers.codex_cli import CodexCliManager
from mcpm.clients.managers.continue_extension import ContinueManager
from mcpm.clients.managers.cursor import CursorManager
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.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
Expand Down Expand Up @@ -46,6 +48,8 @@ class ClientRegistry:
"roo-code": RooCodeManager,
"trae": TraeManager,
"vscode": VSCodeManager,
"gemini-cli": GeminiCliManager,
"codex-cli": CodexCliManager,
}

@classmethod
Expand Down
6 changes: 6 additions & 0 deletions src/mcpm/clients/managers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
from mcpm.clients.managers.claude_code import ClaudeCodeManager
from mcpm.clients.managers.claude_desktop import ClaudeDesktopManager
from mcpm.clients.managers.cline import ClineManager
from mcpm.clients.managers.codex_cli import CodexCliManager
from mcpm.clients.managers.continue_extension import ContinueManager
from mcpm.clients.managers.cursor import CursorManager
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.trae import TraeManager
from mcpm.clients.managers.vscode import VSCodeManager
from mcpm.clients.managers.windsurf import WindsurfManager

__all__ = [
Expand All @@ -24,4 +27,7 @@
"FiveireManager",
"GooseClientManager",
"TraeManager",
"VSCodeManager",
"GeminiCliManager",
"CodexCliManager",
]
110 changes: 110 additions & 0 deletions src/mcpm/clients/managers/codex_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
Codex CLI integration utilities for MCP
"""

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

import tomli
import tomli_w

from mcpm.clients.base import JSONClientManager

logger = logging.getLogger(__name__)


class CodexCliManager(JSONClientManager):
"""Manages Codex CLI MCP server configurations"""

# Client information
client_key = "codex-cli"
display_name = "Codex CLI"
download_url = "https://github.com/openai/codex"
configure_key_name = "mcp_servers" # Codex uses mcp_servers instead of mcpServers

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

Args:
config_path_override: Optional path to override the default config file location
"""
super().__init__(config_path_override=config_path_override)

if config_path_override:
self.config_path = config_path_override
else:
# Codex CLI stores its settings in ~/.codex/config.toml
self.config_path = os.path.expanduser("~/.codex/config.toml")

def _get_empty_config(self) -> Dict[str, Any]:
"""Get empty config structure for Codex CLI"""
return {"mcp_servers": {}}

def is_client_installed(self) -> bool:
"""Check if Codex CLI is installed
Returns:
bool: True if codex command is available, False otherwise
"""
codex_executable = "codex.exe" if self._system == "Windows" else "codex"
return shutil.which(codex_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": "OpenAI's Codex CLI tool",
}

def _load_config(self) -> Dict[str, Any]:
"""Load client configuration file

Returns:
Dict containing the client configuration with at least {"mcp_servers": {}}
"""
try:
# Check if config file exists
if not os.path.exists(self.config_path):
# Create empty config
return self._get_empty_config()

# Codex uses TOML format instead of JSON
with open(self.config_path, "rb") as f:
config = tomli.load(f)

# Ensure mcp_servers key exists
if self.configure_key_name not in config:
config[self.configure_key_name] = {}

return config
except Exception as e:
logger.error(f"Error loading Codex config: {e}")
return self._get_empty_config()

def _save_config(self, config: Dict[str, Any]) -> bool:
"""Save configuration to client config file

Args:
config: Configuration to save

Returns:
bool: Success or failure
"""
try:
# Create directory if it doesn't exist
os.makedirs(os.path.dirname(self.config_path), exist_ok=True)

# Codex uses TOML format instead of JSON
with open(self.config_path, "wb") as f:
tomli_w.dump(config, f)
return True
except Exception as e:
logger.error(f"Error saving Codex config: {e}")
return False
66 changes: 66 additions & 0 deletions src/mcpm/clients/managers/gemini_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""
Gemini 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 GeminiCliManager(JSONClientManager):
"""Manages Gemini CLI MCP server configurations"""

# Client information
client_key = "gemini-cli"
display_name = "Gemini CLI"
download_url = "https://github.com/google-gemini/gemini-cli"

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

Args:
config_path_override: Optional path to override the default config file location
"""
super().__init__(config_path_override=config_path_override)

if config_path_override:
self.config_path = config_path_override
else:
# Gemini CLI stores its settings in ~/.gemini/settings.json
self.config_path = os.path.expanduser("~/.gemini/settings.json")

def _get_empty_config(self) -> Dict[str, Any]:
"""Get empty config structure for Gemini CLI"""
return {
"mcpServers": {},
# Include other default settings that Gemini CLI expects
"contextFileName": "GEMINI.md",
"autoAccept": False,
"theme": "Default"
}

def is_client_installed(self) -> bool:
"""Check if Gemini CLI is installed
Returns:
bool: True if gemini command is available, False otherwise
"""
gemini_executable = "gemini.exe" if self._system == "Windows" else "gemini"
return shutil.which(gemini_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": "Google's Gemini CLI tool",
}
52 changes: 52 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.