From 14ccbede43edb0d577bd8a78731bcd85e404fb5a Mon Sep 17 00:00:00 2001 From: Fahad Date: Wed, 11 Jun 2025 07:09:28 +0400 Subject: [PATCH] Fixes https://github.com/BeehiveInnovations/gemini-mcp-server/issues/6 --- docker-compose.yml | 4 +++- setup-docker.sh | 8 ++++++-- tests/test_collaboration.py | 6 +++--- tests/test_conversation_memory.py | 8 +++++--- tests/test_thinking_modes.py | 6 +++--- utils/file_utils.py | 27 ++++++++++++++++++++++----- 6 files changed, 42 insertions(+), 17 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d58b53d..38c80af 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,9 @@ services: environment: - GEMINI_API_KEY=${GEMINI_API_KEY} - REDIS_URL=redis://redis:6379/0 - - WORKSPACE_ROOT=${WORKSPACE_ROOT:-/workspace} + # Use HOME not PWD: Claude needs access to any absolute file path, not just current project, + # and Claude Code could be running from multiple locations at the same time + - WORKSPACE_ROOT=${WORKSPACE_ROOT:-${HOME}} volumes: - ${HOME:-/tmp}:/workspace:ro stdin_open: true diff --git a/setup-docker.sh b/setup-docker.sh index 31e33e3..5d7e654 100755 --- a/setup-docker.sh +++ b/setup-docker.sh @@ -37,8 +37,12 @@ GEMINI_API_KEY=$API_KEY_VALUE # Redis configuration (automatically set for Docker Compose) REDIS_URL=redis://redis:6379/0 -# Workspace root (automatically set for Docker Compose) -WORKSPACE_ROOT=/workspace +# Workspace root - host path that maps to /workspace in container +# This should be the host directory path that contains all files Claude might reference +# We use $HOME (not $PWD) because Claude needs access to ANY absolute file path, +# not just files within the current project directory. Additionally, Claude Code +# could be running from multiple locations at the same time. +WORKSPACE_ROOT=$HOME EOF echo "✅ Created .env file with Redis configuration" echo "" diff --git a/tests/test_collaboration.py b/tests/test_collaboration.py index 8d653c9..6e7fe93 100644 --- a/tests/test_collaboration.py +++ b/tests/test_collaboration.py @@ -235,9 +235,9 @@ class TestCollaborationWorkflow: ) response = json.loads(result[0].text) - assert ( - response["status"] == "requires_clarification" - ), "Should request clarification when asked about dependencies without package files" + assert response["status"] == "requires_clarification", ( + "Should request clarification when asked about dependencies without package files" + ) clarification = json.loads(response["content"]) assert "package.json" in str(clarification["files_needed"]), "Should specifically request package.json" diff --git a/tests/test_conversation_memory.py b/tests/test_conversation_memory.py index 6890127..ff49aeb 100644 --- a/tests/test_conversation_memory.py +++ b/tests/test_conversation_memory.py @@ -300,7 +300,7 @@ class TestConversationFlow: # REQUEST 6: Try to exceed MAX_CONVERSATION_TURNS limit - should fail turns_at_limit = [ ConversationTurn( - role="assistant" if i % 2 == 0 else "user", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:30Z" + role="assistant" if i % 2 == 0 else "user", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:30Z" ) for i in range(MAX_CONVERSATION_TURNS) ] @@ -423,7 +423,9 @@ class TestConversationFlow: # Mock context with current turns turns = [ ConversationTurn( - role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z" + role="user" if i % 2 == 0 else "assistant", + content=f"Turn {i + 1}", + timestamp="2023-01-01T00:00:00Z", ) for i in range(turn_num) ] @@ -445,7 +447,7 @@ class TestConversationFlow: # Now we should be at the limit - create final context final_turns = [ ConversationTurn( - role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z" + role="user" if i % 2 == 0 else "assistant", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:00Z" ) for i in range(MAX_CONVERSATION_TURNS) ] diff --git a/tests/test_thinking_modes.py b/tests/test_thinking_modes.py index c8d441e..135072a 100644 --- a/tests/test_thinking_modes.py +++ b/tests/test_thinking_modes.py @@ -32,9 +32,9 @@ class TestThinkingModes: ] for tool, expected_default in tools: - assert ( - tool.get_default_thinking_mode() == expected_default - ), f"{tool.__class__.__name__} should default to {expected_default}" + assert tool.get_default_thinking_mode() == expected_default, ( + f"{tool.__class__.__name__} should default to {expected_default}" + ) @pytest.mark.asyncio @patch("tools.base.BaseTool.create_model") diff --git a/utils/file_utils.py b/utils/file_utils.py index 46f79b7..4bc7928 100644 --- a/utils/file_utils.py +++ b/utils/file_utils.py @@ -28,8 +28,9 @@ from .token_utils import MAX_CONTEXT_TOKENS, estimate_tokens logger = logging.getLogger(__name__) # Get workspace root for Docker path translation -# When running in Docker with a mounted workspace, WORKSPACE_ROOT contains -# the host path that corresponds to /workspace in the container +# IMPORTANT: WORKSPACE_ROOT should contain the HOST path (e.g., /Users/john/project) +# that gets mounted to /workspace in the Docker container. This enables proper +# path translation between host absolute paths and container workspace paths. WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT") CONTAINER_WORKSPACE = Path("/workspace") @@ -43,6 +44,7 @@ DANGEROUS_WORKSPACE_PATHS = { "/var", "/root", "/home", + "/workspace", # Container path - WORKSPACE_ROOT should be host path "C:\\", "C:\\Windows", "C:\\Program Files", @@ -54,7 +56,17 @@ if WORKSPACE_ROOT: # Resolve to canonical path for comparison resolved_workspace = Path(WORKSPACE_ROOT).resolve() - # Check against dangerous paths + # Special check for /workspace - common configuration mistake + if str(resolved_workspace) == "/workspace": + raise RuntimeError( + f"Configuration Error: WORKSPACE_ROOT should be set to the HOST path, not the container path. " + f"Found: WORKSPACE_ROOT={WORKSPACE_ROOT} " + f"Expected: WORKSPACE_ROOT should be set to your host directory path (e.g., $HOME) " + f"that contains all files Claude might reference. " + f"This path gets mounted to /workspace inside the Docker container." + ) + + # Check against other dangerous paths if str(resolved_workspace) in DANGEROUS_WORKSPACE_PATHS: raise RuntimeError( f"Security Error: WORKSPACE_ROOT '{WORKSPACE_ROOT}' is set to a dangerous system directory. " @@ -181,12 +193,17 @@ def translate_path_for_environment(path_str: str) -> str: This is the unified path translation function that should be used by all tools and utilities throughout the codebase. It handles: - 1. Docker host-to-container path translation + 1. Docker host-to-container path translation (host paths -> /workspace/...) 2. Direct mode (no translation needed) 3. Security validation and error handling + Docker Path Translation Logic: + - Input: /Users/john/project/src/file.py (host path from Claude) + - WORKSPACE_ROOT: /Users/john/project (host path in env var) + - Output: /workspace/src/file.py (container path for file operations) + Args: - path_str: Original path string from the client + path_str: Original path string from the client (absolute host path) Returns: Translated path appropriate for the current environment