fix: Docker path translation for review_changes and code deduplication

- Fixed review_changes tool to properly translate host paths to container paths in Docker
- Prevents "No such file or directory" errors when running in Docker containers
- Added proper error handling with clear messages when paths are inaccessible

refactor: Centralized token limit validation across all tools
- Added _validate_token_limit method to BaseTool to eliminate code duplication
- Reduced ~25 lines of duplicated code across 5 tools (analyze, chat, debug_issue, review_code, think_deeper)
- Maintains exact same error messages and behavior

feat: Enhanced large prompt handling
- Added support for prompts >50K chars by requesting file-based input
- Preserves MCP's ~25K token capacity for responses
- All tools now check prompt size before processing

test: Added comprehensive Docker path integration tests
- Tests for path translation, security validation, and error handling
- Tests for review_changes tool specifically with Docker paths
- Fixed failing think_deeper test (updated default from "max" to "high")

chore: Code quality improvements
- Applied black formatting across all files
- Fixed import sorting with isort
- All tests passing (96 tests)
- Standardized error handling follows MCP TextContent format

The changes ensure consistent behavior across all environments while reducing code duplication and improving maintainability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Fahad
2025-06-10 07:20:24 +04:00
parent ab007fb4b2
commit 7ea790ef88
36 changed files with 1540 additions and 176 deletions

View File

@@ -13,17 +13,20 @@ Key responsibilities:
- Support for clarification requests when more information is needed
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Literal
import os
import json
import os
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Literal, Optional
from google import genai
from google.genai import types
from mcp.types import TextContent
from pydantic import BaseModel, Field
from .models import ToolOutput, ClarificationRequest
from config import MCP_PROMPT_SIZE_LIMIT
from utils.file_utils import read_file_content
from .models import ClarificationRequest, ToolOutput
class ToolRequest(BaseModel):
@@ -194,6 +197,80 @@ class BaseTool(ABC):
return None
def check_prompt_size(self, text: str) -> Optional[Dict[str, Any]]:
"""
Check if a text field is too large for MCP's token limits.
The MCP protocol has a combined request+response limit of ~25K tokens.
To ensure adequate space for responses, we limit prompt input to a
configurable character limit (default 50K chars ~= 10-12K tokens).
Larger prompts are handled by having Claude save them to a file,
bypassing MCP's token constraints while preserving response capacity.
Args:
text: The text to check
Returns:
Optional[Dict[str, Any]]: Response asking for file handling if too large, None otherwise
"""
if text and len(text) > MCP_PROMPT_SIZE_LIMIT:
return {
"status": "requires_file_prompt",
"content": (
f"The prompt is too large for MCP's token limits (>{MCP_PROMPT_SIZE_LIMIT:,} characters). "
"Please save the prompt text to a temporary file named 'prompt.txt' and "
"resend the request with an empty prompt string and the absolute file path included "
"in the files parameter, along with any other files you wish to share as context."
),
"content_type": "text",
"metadata": {
"prompt_size": len(text),
"limit": MCP_PROMPT_SIZE_LIMIT,
"instructions": "Save prompt to 'prompt.txt' and include absolute path in files parameter",
},
}
return None
def handle_prompt_file(
self, files: Optional[List[str]]
) -> tuple[Optional[str], Optional[List[str]]]:
"""
Check for and handle prompt.txt in the files list.
If prompt.txt is found, reads its content and removes it from the files list.
This file is treated specially as the main prompt, not as an embedded file.
This mechanism allows us to work around MCP's ~25K token limit by having
Claude save large prompts to a file, effectively using the file transfer
mechanism to bypass token constraints while preserving response capacity.
Args:
files: List of file paths
Returns:
tuple: (prompt_content, updated_files_list)
"""
if not files:
return None, files
prompt_content = None
updated_files = []
for file_path in files:
# Check if the filename is exactly "prompt.txt"
# This ensures we don't match files like "myprompt.txt" or "prompt.txt.bak"
if os.path.basename(file_path) == "prompt.txt":
try:
prompt_content = read_file_content(file_path)
except Exception:
# If we can't read the file, we'll just skip it
# The error will be handled elsewhere
pass
else:
updated_files.append(file_path)
return prompt_content, updated_files if updated_files else None
async def execute(self, arguments: Dict[str, Any]) -> List[TextContent]:
"""
Execute the tool with the provided arguments.
@@ -378,6 +455,30 @@ class BaseTool(ABC):
"""
return response
def _validate_token_limit(self, text: str, context_type: str = "Context") -> None:
"""
Validate token limit and raise ValueError if exceeded.
This centralizes the token limit check that was previously duplicated
in all prepare_prompt methods across tools.
Args:
text: The text to check
context_type: Description of what's being checked (for error message)
Raises:
ValueError: If text exceeds MAX_CONTEXT_TOKENS
"""
from config import MAX_CONTEXT_TOKENS
from utils import check_token_limit
within_limit, estimated_tokens = check_token_limit(text)
if not within_limit:
raise ValueError(
f"{context_type} too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
)
def create_model(
self, model_name: str, temperature: float, thinking_mode: str = "medium"
):