From 9e1eb986e74dd3deafebae52427712107ba272bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arribas=20Lopera?= Date: Tue, 9 Sep 2025 10:06:22 +0200 Subject: [PATCH 1/4] fix: resolve Windows compatibility issue in MCP server execution Fixes issue #1 where Windows users encountered "Error executing Codex CLI" when using the codex-bridge MCP server. Changes: - Add platform detection utilities (_is_windows(), _get_codex_command()) - Enhanced CLI detection to handle Windows executable extensions (.exe, .bat, .cmd) - Created _run_codex_command() helper with Windows-specific subprocess handling - Remove start_new_session=True parameter on Windows (not supported) - Updated all three MCP tool functions to use new helper Maintains backward compatibility with macOS/Linux while adding proper Windows support. Resolves: #1 --- src/mcp_server.py | 99 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/src/mcp_server.py b/src/mcp_server.py index 80c2820..c35f55c 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -14,9 +14,11 @@ import json import os +import platform import re import subprocess import shutil +import sys import time from typing import Dict, List, Optional, Union @@ -25,6 +27,30 @@ mcp = FastMCP("codex-assistant") +def _is_windows() -> bool: + """Check if the current platform is Windows.""" + return platform.system().lower() == "windows" + + +def _get_codex_command() -> Optional[str]: + """Get the codex command path with Windows compatibility. + + Returns: + Path to codex executable or None if not found + """ + # First try the standard shutil.which approach + codex_path = shutil.which("codex") + if codex_path: + return codex_path + + # On Windows, explicitly check for common executable extensions + if _is_windows(): + for ext in ['.exe', '.bat', '.cmd']: + codex_path = shutil.which(f"codex{ext}") + if codex_path: + return codex_path + + return None def _get_timeout() -> int: @@ -51,6 +77,43 @@ def _should_skip_git_check() -> bool: +def _run_codex_command(cmd: List[str], directory: str, timeout_value: int, input_text: str) -> subprocess.CompletedProcess: + """Execute codex command with platform-specific handling. + + Args: + cmd: Command list to execute + directory: Working directory + timeout_value: Timeout in seconds + input_text: Input text to pipe to the command + + Returns: + CompletedProcess result + """ + # Windows-specific handling + if _is_windows(): + # On Windows, don't use start_new_session as it's not supported + return subprocess.run( + cmd, + cwd=directory, + capture_output=True, + text=True, + timeout=timeout_value, + input=input_text, + shell=False + ) + else: + # Unix/macOS handling (original behavior) + return subprocess.run( + cmd, + cwd=directory, + capture_output=True, + text=True, + timeout=timeout_value, + input=input_text, + start_new_session=True + ) + + def _clean_codex_output(output: str) -> str: """Clean irrelevant messages from Codex CLI output.""" if not output: @@ -201,7 +264,7 @@ def consult_codex( Formatted response based on format parameter """ # Check if codex CLI is available - if not shutil.which("codex"): + if not _get_codex_command(): error_response = "Error: Codex CLI not found. Install from OpenAI" if format == "json": return json.dumps({"status": "error", "error": error_response}, indent=2) @@ -235,15 +298,7 @@ def consult_codex( # Execute with timing start_time = time.time() try: - result = subprocess.run( - cmd, - cwd=directory, - capture_output=True, - text=True, - timeout=timeout_value, - input=processed_query, - start_new_session=True - ) + result = _run_codex_command(cmd, directory, timeout_value, processed_query) execution_time = time.time() - start_time if result.returncode == 0: @@ -316,7 +371,7 @@ def consult_codex_with_stdin( Formatted response based on format parameter """ # Check if codex CLI is available - if not shutil.which("codex"): + if not _get_codex_command(): error_response = "Error: Codex CLI not found. Install from OpenAI" if format == "json": return json.dumps({"status": "error", "error": error_response}, indent=2) @@ -353,15 +408,7 @@ def consult_codex_with_stdin( # Execute with timing start_time = time.time() try: - result = subprocess.run( - cmd, - cwd=directory, - capture_output=True, - text=True, - timeout=timeout_value, - input=processed_query, - start_new_session=True - ) + result = _run_codex_command(cmd, directory, timeout_value, processed_query) execution_time = time.time() - start_time if result.returncode == 0: @@ -430,7 +477,7 @@ def consult_codex_batch( JSON array with all results """ # Check if codex CLI is available - if not shutil.which("codex"): + if not _get_codex_command(): return json.dumps({ "status": "error", "error": "Codex CLI not found. Install from OpenAI" @@ -479,15 +526,7 @@ def consult_codex_batch( start_time = time.time() try: - result = subprocess.run( - cmd, - cwd=directory, - capture_output=True, - text=True, - timeout=query_timeout, - input=processed_query, - start_new_session=True - ) + result = _run_codex_command(cmd, directory, query_timeout, processed_query) execution_time = time.time() - start_time if result.returncode == 0: From f5ba2579f88a34ca38466f2f9366cb0b92db6a1d Mon Sep 17 00:00:00 2001 From: Shelakh Date: Tue, 9 Sep 2025 10:10:23 +0200 Subject: [PATCH 2/4] Update src/mcp_server.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mcp_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mcp_server.py b/src/mcp_server.py index c35f55c..aa881f1 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -18,7 +18,6 @@ import re import subprocess import shutil -import sys import time from typing import Dict, List, Optional, Union From cd5e00bf1e662e263b24042697ed0120d802981e Mon Sep 17 00:00:00 2001 From: Shelakh Date: Tue, 9 Sep 2025 10:10:34 +0200 Subject: [PATCH 3/4] Update src/mcp_server.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/mcp_server.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mcp_server.py b/src/mcp_server.py index aa881f1..cdbabaf 100644 --- a/src/mcp_server.py +++ b/src/mcp_server.py @@ -73,9 +73,6 @@ def _should_skip_git_check() -> bool: return skip_check in ("true", "1", "yes") - - - def _run_codex_command(cmd: List[str], directory: str, timeout_value: int, input_text: str) -> subprocess.CompletedProcess: """Execute codex command with platform-specific handling. From 3fc5211ef21695f554413d8801a5e0f65b27dcf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n=20Arribas=20Lopera?= Date: Tue, 9 Sep 2025 10:11:03 +0200 Subject: [PATCH 4/4] build: update PyPI publish action to use Trusted Publishing Configure publishing settings to eliminate the need for a password. --- .github/workflows/release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c213432..bb412af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -98,4 +98,6 @@ jobs: - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: - password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file + # Use Trusted Publishing (no password needed) + # Configure at: https://pypi.org/manage/project/codex-bridge/settings/publishing/ + attestations: true \ No newline at end of file