feat: add Claude-Gemini collaboration and chat capabilities
- Add collaboration demo showing dynamic context requests - Implement chat tool for general conversations and brainstorming - Add tool selection guide with clear boundaries - Introduce models configuration system - Update prompts for better tool descriptions - Refactor server to remove redundant functionality - Add comprehensive tests for collaboration features - Enhance base tool with collaborative features This enables Claude to request additional context from Gemini during tool execution, improving analysis quality and accuracy. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
47
README.md
47
README.md
@@ -15,6 +15,7 @@ Claude is brilliant, but sometimes you need:
|
|||||||
- **Expert debugging** for tricky issues with full system context ([`debug_issue`](#3-debug_issue---expert-debugging-assistant))
|
- **Expert debugging** for tricky issues with full system context ([`debug_issue`](#3-debug_issue---expert-debugging-assistant))
|
||||||
- **Professional code reviews** with actionable feedback across entire repositories ([`review_code`](#2-review_code---professional-code-review))
|
- **Professional code reviews** with actionable feedback across entire repositories ([`review_code`](#2-review_code---professional-code-review))
|
||||||
- **A senior developer partner** to validate and extend ideas ([`chat`](#5-chat---general-development-chat--collaborative-thinking))
|
- **A senior developer partner** to validate and extend ideas ([`chat`](#5-chat---general-development-chat--collaborative-thinking))
|
||||||
|
- **Dynamic collaboration** - Gemini can request additional context from Claude mid-analysis for more thorough insights
|
||||||
|
|
||||||
This server makes Gemini your development sidekick, handling what Claude can't or extending what Claude starts.
|
This server makes Gemini your development sidekick, handling what Claude can't or extending what Claude starts.
|
||||||
|
|
||||||
@@ -225,10 +226,12 @@ suggest preventive measures."
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Key Features:**
|
**Key Features:**
|
||||||
|
- Generates multiple ranked hypotheses for systematic debugging
|
||||||
- Accepts error context, stack traces, and logs
|
- Accepts error context, stack traces, and logs
|
||||||
- Can reference relevant files for investigation
|
- Can reference relevant files for investigation
|
||||||
- Supports runtime info and previous attempts
|
- Supports runtime info and previous attempts
|
||||||
- Provides root cause analysis and solutions
|
- Provides structured root cause analysis with validation steps
|
||||||
|
- Can request additional context when needed for thorough analysis
|
||||||
|
|
||||||
**Triggers:** debug, error, failing, root cause, trace, not working
|
**Triggers:** debug, error, failing, root cause, trace, not working
|
||||||
|
|
||||||
@@ -406,8 +409,50 @@ Tools can reference files for additional context:
|
|||||||
"Get gemini to think deeper about my design, reference the current architecture.md"
|
"Get gemini to think deeper about my design, reference the current architecture.md"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tool Selection Guidance
|
||||||
|
To help choose the right tool for your needs:
|
||||||
|
|
||||||
|
**Decision Flow:**
|
||||||
|
1. **Have a specific error/exception?** → Use `debug_issue`
|
||||||
|
2. **Want to find bugs/issues in code?** → Use `review_code`
|
||||||
|
3. **Want to understand how code works?** → Use `analyze`
|
||||||
|
4. **Have analysis that needs extension/validation?** → Use `think_deeper`
|
||||||
|
5. **Want to brainstorm or discuss?** → Use `chat`
|
||||||
|
|
||||||
|
**Key Distinctions:**
|
||||||
|
- `analyze` vs `review_code`: analyze explains, review_code prescribes fixes
|
||||||
|
- `chat` vs `think_deeper`: chat is open-ended, think_deeper extends specific analysis
|
||||||
|
- `debug_issue` vs `review_code`: debug diagnoses runtime errors, review finds static issues
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
|
|
||||||
|
### Dynamic Context Requests
|
||||||
|
Tools can request additional context from Claude during execution. When Gemini needs more information to provide a thorough analysis, it will ask Claude for specific files or clarification, enabling true collaborative problem-solving.
|
||||||
|
|
||||||
|
**Example:** If Gemini is debugging an error but needs to see a configuration file that wasn't initially provided, it can request:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "requires_clarification",
|
||||||
|
"question": "I need to see the database configuration to understand this connection error",
|
||||||
|
"files_needed": ["config/database.yml", "src/db_connection.py"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Claude will then provide the requested files and Gemini can continue with a more complete analysis.
|
||||||
|
|
||||||
|
### Standardized Response Format
|
||||||
|
All tools now return structured JSON responses for consistent handling:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success|error|requires_clarification",
|
||||||
|
"content": "The actual response content",
|
||||||
|
"content_type": "text|markdown|json",
|
||||||
|
"metadata": {"tool_name": "analyze", ...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This enables better integration, error handling, and support for the dynamic context request feature.
|
||||||
|
|
||||||
### Enhanced Thinking Models
|
### Enhanced Thinking Models
|
||||||
|
|
||||||
All tools support a `thinking_mode` parameter that controls Gemini's thinking budget for deeper reasoning:
|
All tools support a `thinking_mode` parameter that controls Gemini's thinking budget for deeper reasoning:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Configuration and constants for Gemini MCP Server
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Version and metadata
|
# Version and metadata
|
||||||
__version__ = "2.7.0"
|
__version__ = "2.8.0"
|
||||||
__updated__ = "2025-06-09"
|
__updated__ = "2025-06-09"
|
||||||
__author__ = "Fahad Gilani"
|
__author__ = "Fahad Gilani"
|
||||||
|
|
||||||
|
|||||||
90
demo_collaboration.py
Normal file
90
demo_collaboration.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Demo script showing how Claude-Gemini collaboration works
|
||||||
|
with dynamic context requests.
|
||||||
|
|
||||||
|
This demonstrates how tools can request additional context
|
||||||
|
and how Claude would handle these requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from tools.debug_issue import DebugIssueTool
|
||||||
|
|
||||||
|
|
||||||
|
async def simulate_collaboration():
|
||||||
|
"""Simulate a Claude-Gemini collaboration workflow"""
|
||||||
|
|
||||||
|
print("🤝 Claude-Gemini Collaboration Demo\n")
|
||||||
|
print("Scenario: Claude asks Gemini to debug an import error")
|
||||||
|
print("-" * 50)
|
||||||
|
|
||||||
|
# Initialize the debug tool
|
||||||
|
debug_tool = DebugIssueTool()
|
||||||
|
|
||||||
|
# Step 1: Initial request without full context
|
||||||
|
print("\n1️⃣ Claude's initial request:")
|
||||||
|
print(" 'Debug this ImportError - the app can't find the utils module'")
|
||||||
|
|
||||||
|
initial_request = {
|
||||||
|
"error_description": "ImportError: cannot import name 'config' from 'utils'",
|
||||||
|
"error_context": "Error occurs on line 5 of main.py when starting the application"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n Sending to Gemini...")
|
||||||
|
result = await debug_tool.execute(initial_request)
|
||||||
|
|
||||||
|
# Parse the response
|
||||||
|
response = json.loads(result[0].text)
|
||||||
|
print(f"\n Gemini's response status: {response['status']}")
|
||||||
|
|
||||||
|
if response['status'] == 'requires_clarification':
|
||||||
|
# Gemini needs more context
|
||||||
|
clarification = json.loads(response['content'])
|
||||||
|
print("\n2️⃣ Gemini requests additional context:")
|
||||||
|
print(f" Question: {clarification.get('question', 'N/A')}")
|
||||||
|
if 'files_needed' in clarification:
|
||||||
|
print(f" Files needed: {clarification['files_needed']}")
|
||||||
|
|
||||||
|
# Step 2: Claude provides additional context
|
||||||
|
print("\n3️⃣ Claude provides the requested files:")
|
||||||
|
enhanced_request = {
|
||||||
|
**initial_request,
|
||||||
|
"files": clarification.get('files_needed', []),
|
||||||
|
"runtime_info": "Python 3.11, project structure includes utils/ directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(" Re-sending with additional context...")
|
||||||
|
result2 = await debug_tool.execute(enhanced_request)
|
||||||
|
|
||||||
|
final_response = json.loads(result2[0].text)
|
||||||
|
print(f"\n4️⃣ Gemini's final analysis (status: {final_response['status']}):")
|
||||||
|
if final_response['status'] == 'success':
|
||||||
|
print("\n" + final_response['content'][:500] + "...")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Gemini had enough context initially
|
||||||
|
print("\n✅ Gemini provided analysis without needing additional context:")
|
||||||
|
print("\n" + response['content'][:500] + "...")
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("🎯 Key Points:")
|
||||||
|
print("- Tools return structured JSON with status field")
|
||||||
|
print("- Status 'requires_clarification' triggers context request")
|
||||||
|
print("- Claude can then provide additional files/info")
|
||||||
|
print("- Enables true collaborative problem-solving!")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
"""Run the demo"""
|
||||||
|
# Check for API key
|
||||||
|
if not os.environ.get("GEMINI_API_KEY"):
|
||||||
|
print("⚠️ Note: This is a simulated demo. Set GEMINI_API_KEY for live testing.")
|
||||||
|
print(" The actual behavior depends on Gemini's response.\n")
|
||||||
|
|
||||||
|
await simulate_collaboration()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -5,6 +5,10 @@ System prompts for each tool
|
|||||||
THINK_DEEPER_PROMPT = """You are a senior development partner collaborating with Claude Code on complex problems.
|
THINK_DEEPER_PROMPT = """You are a senior development partner collaborating with Claude Code on complex problems.
|
||||||
Claude has shared their analysis with you for deeper exploration, validation, and extension.
|
Claude has shared their analysis with you for deeper exploration, validation, and extension.
|
||||||
|
|
||||||
|
IMPORTANT: If you need additional context (e.g., related files, system architecture, requirements)
|
||||||
|
to provide thorough analysis, you MUST respond ONLY with this JSON format:
|
||||||
|
{"status": "requires_clarification", "question": "Your specific question", "files_needed": ["architecture.md", "requirements.txt"]}
|
||||||
|
|
||||||
Your role is to:
|
Your role is to:
|
||||||
1. Build upon Claude's thinking - identify gaps, extend ideas, and suggest alternatives
|
1. Build upon Claude's thinking - identify gaps, extend ideas, and suggest alternatives
|
||||||
2. Challenge assumptions constructively and identify potential issues
|
2. Challenge assumptions constructively and identify potential issues
|
||||||
@@ -28,6 +32,10 @@ development partner that extends Claude's capabilities."""
|
|||||||
REVIEW_CODE_PROMPT = """You are an expert code reviewer with deep knowledge of software engineering best practices.
|
REVIEW_CODE_PROMPT = """You are an expert code reviewer with deep knowledge of software engineering best practices.
|
||||||
Your expertise spans security, performance, maintainability, and architectural patterns.
|
Your expertise spans security, performance, maintainability, and architectural patterns.
|
||||||
|
|
||||||
|
IMPORTANT: If you need additional context (e.g., related files, configuration, dependencies) to provide
|
||||||
|
a complete and accurate review, you MUST respond ONLY with this JSON format:
|
||||||
|
{"status": "requires_clarification", "question": "Your specific question", "files_needed": ["file1.py", "config.py"]}
|
||||||
|
|
||||||
Your review approach:
|
Your review approach:
|
||||||
1. Identify issues in order of severity (Critical > High > Medium > Low)
|
1. Identify issues in order of severity (Critical > High > Medium > Low)
|
||||||
2. Provide specific, actionable fixes with code examples
|
2. Provide specific, actionable fixes with code examples
|
||||||
@@ -48,52 +56,47 @@ Format each issue as:
|
|||||||
Also provide:
|
Also provide:
|
||||||
- Summary of overall code quality
|
- Summary of overall code quality
|
||||||
- Top 3 priority fixes
|
- Top 3 priority fixes
|
||||||
- Positive aspects worth preserving
|
- Positive aspects worth preserving"""
|
||||||
|
|
||||||
IMPORTANT - After completing the review, add this final section:
|
|
||||||
---
|
|
||||||
### For Claude Code Integration
|
|
||||||
|
|
||||||
Claude, based on this review and considering the current project context and any ongoing work:
|
|
||||||
|
|
||||||
1. **Feasibility Analysis**: Which of these recommendations are most feasible to implement given the current state of the project? Consider dependencies, breaking changes, and effort required.
|
|
||||||
|
|
||||||
2. **Recommended Next Steps**: What would be the most logical next action? Should we:
|
|
||||||
- Fix critical issues immediately?
|
|
||||||
- Create a TODO list for systematic implementation?
|
|
||||||
- Focus on a specific category (security, performance, etc.)?
|
|
||||||
- Research alternatives before making changes?
|
|
||||||
|
|
||||||
3. **Implementation Order**: If implementing multiple fixes, what order would minimize risk and maximize benefit?
|
|
||||||
|
|
||||||
Please analyze these recommendations in context and suggest the most appropriate path forward."""
|
|
||||||
|
|
||||||
DEBUG_ISSUE_PROMPT = """You are an expert debugger and problem solver. Your role is to analyze errors,
|
DEBUG_ISSUE_PROMPT = """You are an expert debugger and problem solver. Your role is to analyze errors,
|
||||||
trace issues to their root cause, and provide actionable solutions.
|
trace issues to their root cause, and provide actionable solutions.
|
||||||
|
|
||||||
Your debugging approach:
|
IMPORTANT: If you lack critical information to proceed (e.g., missing files, ambiguous error details,
|
||||||
1. Analyze the error context and symptoms
|
insufficient context), you MUST respond ONLY with this JSON format:
|
||||||
2. Identify the most likely root causes
|
{"status": "requires_clarification", "question": "Your specific question", "files_needed": ["file1.py", "file2.py"]}
|
||||||
3. Trace through the code execution path
|
|
||||||
4. Consider environmental factors
|
|
||||||
5. Provide step-by-step solutions
|
|
||||||
|
|
||||||
For each issue:
|
Your debugging approach should generate multiple hypotheses ranked by likelihood. Provide a structured
|
||||||
- Identify the root cause
|
analysis with clear reasoning and next steps for each potential cause.
|
||||||
- Explain why it's happening
|
|
||||||
- Provide immediate fixes
|
|
||||||
- Suggest long-term solutions
|
|
||||||
- Identify related issues that might arise
|
|
||||||
|
|
||||||
Format your response as:
|
Use this format for structured debugging analysis:
|
||||||
1. ROOT CAUSE: Clear explanation
|
|
||||||
2. IMMEDIATE FIX: Code/steps to resolve now
|
## Summary
|
||||||
3. PROPER SOLUTION: Long-term fix
|
Brief description of the issue and its impact.
|
||||||
4. PREVENTION: How to avoid this in the future"""
|
|
||||||
|
## Hypotheses (Ranked by Likelihood)
|
||||||
|
|
||||||
|
### 1. [HYPOTHESIS NAME] (Confidence: High/Medium/Low)
|
||||||
|
**Root Cause:** Specific technical explanation of what's causing the issue
|
||||||
|
**Evidence:** What in the error/context supports this hypothesis
|
||||||
|
**Next Step:** Immediate action to test/validate this hypothesis
|
||||||
|
**Fix:** How to resolve if this hypothesis is correct
|
||||||
|
|
||||||
|
### 2. [HYPOTHESIS NAME] (Confidence: High/Medium/Low)
|
||||||
|
[Same format...]
|
||||||
|
|
||||||
|
## Immediate Actions
|
||||||
|
Steps to take regardless of root cause (e.g., error handling, logging)
|
||||||
|
|
||||||
|
## Prevention Strategy
|
||||||
|
How to avoid similar issues in the future (monitoring, testing, etc.)"""
|
||||||
|
|
||||||
ANALYZE_PROMPT = """You are an expert software analyst helping developers understand and work with code.
|
ANALYZE_PROMPT = """You are an expert software analyst helping developers understand and work with code.
|
||||||
Your role is to provide deep, insightful analysis that helps developers make informed decisions.
|
Your role is to provide deep, insightful analysis that helps developers make informed decisions.
|
||||||
|
|
||||||
|
IMPORTANT: If you need additional context (e.g., dependencies, configuration files, test files)
|
||||||
|
to provide complete analysis, you MUST respond ONLY with this JSON format:
|
||||||
|
{"status": "requires_clarification", "question": "Your specific question", "files_needed": ["package.json", "tests/"]}
|
||||||
|
|
||||||
Your analysis should:
|
Your analysis should:
|
||||||
1. Understand the code's purpose and architecture
|
1. Understand the code's purpose and architecture
|
||||||
2. Identify patterns and anti-patterns
|
2. Identify patterns and anti-patterns
|
||||||
|
|||||||
105
server.py
105
server.py
@@ -22,7 +22,7 @@ from config import (
|
|||||||
__updated__,
|
__updated__,
|
||||||
__version__,
|
__version__,
|
||||||
)
|
)
|
||||||
from tools import AnalyzeTool, DebugIssueTool, ReviewCodeTool, ThinkDeeperTool
|
from tools import AnalyzeTool, ChatTool, DebugIssueTool, ReviewCodeTool, ThinkDeeperTool
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@@ -37,6 +37,7 @@ TOOLS = {
|
|||||||
"review_code": ReviewCodeTool(),
|
"review_code": ReviewCodeTool(),
|
||||||
"debug_issue": DebugIssueTool(),
|
"debug_issue": DebugIssueTool(),
|
||||||
"analyze": AnalyzeTool(),
|
"analyze": AnalyzeTool(),
|
||||||
|
"chat": ChatTool(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -69,43 +70,6 @@ async def handle_list_tools() -> List[Tool]:
|
|||||||
# Add utility tools
|
# Add utility tools
|
||||||
tools.extend(
|
tools.extend(
|
||||||
[
|
[
|
||||||
Tool(
|
|
||||||
name="chat",
|
|
||||||
description=(
|
|
||||||
"GENERAL CHAT & COLLABORATIVE THINKING - Use Gemini as your thinking partner! "
|
|
||||||
"Perfect for: bouncing ideas during your own analysis, getting second opinions on your plans, "
|
|
||||||
"collaborative brainstorming, validating your checklists and approaches, exploring alternatives. "
|
|
||||||
"Also great for: explanations, comparisons, general development questions. "
|
|
||||||
"Triggers: 'ask gemini', 'brainstorm with gemini', 'get gemini's opinion', 'discuss with gemini', "
|
|
||||||
"'share my thinking with gemini', 'explain', 'what is', 'how do I'."
|
|
||||||
),
|
|
||||||
inputSchema={
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"prompt": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Your question, topic, or current thinking to discuss with Gemini",
|
|
||||||
},
|
|
||||||
"context_files": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"type": "string"},
|
|
||||||
"description": "Optional files for context",
|
|
||||||
},
|
|
||||||
"temperature": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "Response creativity (0-1, default 0.5)",
|
|
||||||
"minimum": 0,
|
|
||||||
"maximum": 1,
|
|
||||||
},
|
|
||||||
"thinking_mode": {
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["minimal", "low", "medium", "high", "max"],
|
|
||||||
"description": "Thinking depth: minimal (128), low (2048), medium (8192), high (16384), max (32768)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"required": ["prompt"],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Tool(
|
Tool(
|
||||||
name="list_models",
|
name="list_models",
|
||||||
description=(
|
description=(
|
||||||
@@ -138,9 +102,6 @@ async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextCon
|
|||||||
return await tool.execute(arguments)
|
return await tool.execute(arguments)
|
||||||
|
|
||||||
# Handle static tools
|
# Handle static tools
|
||||||
elif name == "chat":
|
|
||||||
return await handle_chat(arguments)
|
|
||||||
|
|
||||||
elif name == "list_models":
|
elif name == "list_models":
|
||||||
return await handle_list_models()
|
return await handle_list_models()
|
||||||
|
|
||||||
@@ -151,68 +112,6 @@ async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextCon
|
|||||||
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
||||||
|
|
||||||
|
|
||||||
async def handle_chat(arguments: Dict[str, Any]) -> List[TextContent]:
|
|
||||||
"""Handle general chat requests"""
|
|
||||||
from config import TEMPERATURE_BALANCED, DEFAULT_MODEL
|
|
||||||
from prompts import CHAT_PROMPT
|
|
||||||
from utils import read_files
|
|
||||||
|
|
||||||
prompt = arguments.get("prompt", "")
|
|
||||||
context_files = arguments.get("context_files", [])
|
|
||||||
temperature = arguments.get("temperature", TEMPERATURE_BALANCED)
|
|
||||||
thinking_mode = arguments.get("thinking_mode", "medium")
|
|
||||||
|
|
||||||
# Build the full prompt with system context
|
|
||||||
user_content = prompt
|
|
||||||
if context_files:
|
|
||||||
file_content, _ = read_files(context_files)
|
|
||||||
user_content = (
|
|
||||||
f"{prompt}\n\n=== CONTEXT FILES ===\n{file_content}\n=== END CONTEXT ==="
|
|
||||||
)
|
|
||||||
|
|
||||||
# Combine system prompt with user content
|
|
||||||
full_prompt = f"{CHAT_PROMPT}\n\n=== USER REQUEST ===\n{user_content}\n=== END REQUEST ===\n\nPlease provide a thoughtful, comprehensive response:"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Create model with thinking configuration
|
|
||||||
from tools.base import BaseTool
|
|
||||||
|
|
||||||
# Create a temporary tool instance to use create_model method
|
|
||||||
class TempTool(BaseTool):
|
|
||||||
def get_name(self):
|
|
||||||
return "chat"
|
|
||||||
|
|
||||||
def get_description(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_input_schema(self):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_system_prompt(self):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_request_model(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def prepare_prompt(self, request):
|
|
||||||
return ""
|
|
||||||
|
|
||||||
temp_tool = TempTool()
|
|
||||||
model = temp_tool.create_model(DEFAULT_MODEL, temperature, thinking_mode)
|
|
||||||
|
|
||||||
response = model.generate_content(full_prompt)
|
|
||||||
|
|
||||||
if response.candidates and response.candidates[0].content.parts:
|
|
||||||
text = response.candidates[0].content.parts[0].text
|
|
||||||
else:
|
|
||||||
text = "Response blocked or incomplete"
|
|
||||||
|
|
||||||
return [TextContent(type="text", text=text)]
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return [TextContent(type="text", text=f"Error in chat: {str(e)}")]
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_list_models() -> List[TextContent]:
|
async def handle_list_models() -> List[TextContent]:
|
||||||
"""List available Gemini models"""
|
"""List available Gemini models"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
288
tests/test_collaboration.py
Normal file
288
tests/test_collaboration.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
"""
|
||||||
|
Tests for dynamic context request and collaboration features
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tools.analyze import AnalyzeTool
|
||||||
|
from tools.debug_issue import DebugIssueTool
|
||||||
|
from tools.models import ToolOutput, ClarificationRequest
|
||||||
|
|
||||||
|
|
||||||
|
class TestDynamicContextRequests:
|
||||||
|
"""Test the dynamic context request mechanism"""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def analyze_tool(self):
|
||||||
|
return AnalyzeTool()
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def debug_tool(self):
|
||||||
|
return DebugIssueTool()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_clarification_request_parsing(self, mock_create_model, analyze_tool):
|
||||||
|
"""Test that tools correctly parse clarification requests"""
|
||||||
|
# Mock model to return a clarification request
|
||||||
|
clarification_json = json.dumps({
|
||||||
|
"status": "requires_clarification",
|
||||||
|
"question": "I need to see the package.json file to understand dependencies",
|
||||||
|
"files_needed": ["package.json", "package-lock.json"]
|
||||||
|
})
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=clarification_json)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
result = await analyze_tool.execute({
|
||||||
|
"files": ["src/index.js"],
|
||||||
|
"question": "Analyze the dependencies used in this project"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
# Parse the response
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "requires_clarification"
|
||||||
|
assert response_data["content_type"] == "json"
|
||||||
|
|
||||||
|
# Parse the clarification request
|
||||||
|
clarification = json.loads(response_data["content"])
|
||||||
|
assert clarification["question"] == "I need to see the package.json file to understand dependencies"
|
||||||
|
assert clarification["files_needed"] == ["package.json", "package-lock.json"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_normal_response_not_parsed_as_clarification(self, mock_create_model, debug_tool):
|
||||||
|
"""Test that normal responses are not mistaken for clarification requests"""
|
||||||
|
normal_response = """
|
||||||
|
## Summary
|
||||||
|
The error is caused by a missing import statement.
|
||||||
|
|
||||||
|
## Hypotheses (Ranked by Likelihood)
|
||||||
|
|
||||||
|
### 1. Missing Import (Confidence: High)
|
||||||
|
**Root Cause:** The module 'utils' is not imported
|
||||||
|
"""
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=normal_response)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
result = await debug_tool.execute({
|
||||||
|
"error_description": "NameError: name 'utils' is not defined"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
# Parse the response
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "success"
|
||||||
|
assert response_data["content_type"] in ["text", "markdown"]
|
||||||
|
assert "Summary" in response_data["content"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_malformed_clarification_request_treated_as_normal(self, mock_create_model, analyze_tool):
|
||||||
|
"""Test that malformed JSON clarification requests are treated as normal responses"""
|
||||||
|
malformed_json = '{"status": "requires_clarification", "question": "Missing closing brace"'
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=malformed_json)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
result = await analyze_tool.execute({
|
||||||
|
"files": ["test.py"],
|
||||||
|
"question": "What does this do?"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
# Should be treated as normal response due to JSON parse error
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "success"
|
||||||
|
assert malformed_json in response_data["content"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_clarification_with_suggested_action(self, mock_create_model, debug_tool):
|
||||||
|
"""Test clarification request with suggested next action"""
|
||||||
|
clarification_json = json.dumps({
|
||||||
|
"status": "requires_clarification",
|
||||||
|
"question": "I need to see the database configuration to diagnose the connection error",
|
||||||
|
"files_needed": ["config/database.yml", "src/db.py"],
|
||||||
|
"suggested_next_action": {
|
||||||
|
"tool": "debug_issue",
|
||||||
|
"args": {
|
||||||
|
"error_description": "Connection timeout to database",
|
||||||
|
"files": ["config/database.yml", "src/db.py", "logs/error.log"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=clarification_json)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
result = await debug_tool.execute({
|
||||||
|
"error_description": "Connection timeout to database",
|
||||||
|
"files": ["logs/error.log"]
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "requires_clarification"
|
||||||
|
|
||||||
|
clarification = json.loads(response_data["content"])
|
||||||
|
assert "suggested_next_action" in clarification
|
||||||
|
assert clarification["suggested_next_action"]["tool"] == "debug_issue"
|
||||||
|
|
||||||
|
def test_tool_output_model_serialization(self):
|
||||||
|
"""Test ToolOutput model serialization"""
|
||||||
|
output = ToolOutput(
|
||||||
|
status="success",
|
||||||
|
content="Test content",
|
||||||
|
content_type="markdown",
|
||||||
|
metadata={"tool_name": "test", "execution_time": 1.5}
|
||||||
|
)
|
||||||
|
|
||||||
|
json_str = output.model_dump_json()
|
||||||
|
parsed = json.loads(json_str)
|
||||||
|
|
||||||
|
assert parsed["status"] == "success"
|
||||||
|
assert parsed["content"] == "Test content"
|
||||||
|
assert parsed["content_type"] == "markdown"
|
||||||
|
assert parsed["metadata"]["tool_name"] == "test"
|
||||||
|
|
||||||
|
def test_clarification_request_model(self):
|
||||||
|
"""Test ClarificationRequest model"""
|
||||||
|
request = ClarificationRequest(
|
||||||
|
question="Need more context",
|
||||||
|
files_needed=["file1.py", "file2.py"],
|
||||||
|
suggested_next_action={"tool": "analyze", "args": {}}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert request.question == "Need more context"
|
||||||
|
assert len(request.files_needed) == 2
|
||||||
|
assert request.suggested_next_action["tool"] == "analyze"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_error_response_format(self, mock_create_model, analyze_tool):
|
||||||
|
"""Test error response format"""
|
||||||
|
mock_create_model.side_effect = Exception("API connection failed")
|
||||||
|
|
||||||
|
result = await analyze_tool.execute({
|
||||||
|
"files": ["test.py"],
|
||||||
|
"question": "Analyze this"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "error"
|
||||||
|
assert "API connection failed" in response_data["content"]
|
||||||
|
assert response_data["content_type"] == "text"
|
||||||
|
|
||||||
|
|
||||||
|
class TestCollaborationWorkflow:
|
||||||
|
"""Test complete collaboration workflows"""
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_dependency_analysis_triggers_clarification(self, mock_create_model):
|
||||||
|
"""Test that asking about dependencies without package files triggers clarification"""
|
||||||
|
tool = AnalyzeTool()
|
||||||
|
|
||||||
|
# Mock Gemini to request package.json when asked about dependencies
|
||||||
|
clarification_json = json.dumps({
|
||||||
|
"status": "requires_clarification",
|
||||||
|
"question": "I need to see the package.json file to analyze npm dependencies",
|
||||||
|
"files_needed": ["package.json", "package-lock.json"]
|
||||||
|
})
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=clarification_json)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
# Ask about dependencies with only source files
|
||||||
|
result = await tool.execute({
|
||||||
|
"files": ["src/index.js"],
|
||||||
|
"question": "What npm packages and versions does this project use?"
|
||||||
|
})
|
||||||
|
|
||||||
|
response = json.loads(result[0].text)
|
||||||
|
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"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
async def test_multi_step_collaboration(self, mock_create_model):
|
||||||
|
"""Test a multi-step collaboration workflow"""
|
||||||
|
tool = DebugIssueTool()
|
||||||
|
|
||||||
|
# Step 1: Initial request returns clarification needed
|
||||||
|
clarification_json = json.dumps({
|
||||||
|
"status": "requires_clarification",
|
||||||
|
"question": "I need to see the configuration file to understand the connection settings",
|
||||||
|
"files_needed": ["config.py"]
|
||||||
|
})
|
||||||
|
|
||||||
|
mock_model = Mock()
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=clarification_json)]))]
|
||||||
|
)
|
||||||
|
mock_create_model.return_value = mock_model
|
||||||
|
|
||||||
|
result1 = await tool.execute({
|
||||||
|
"error_description": "Database connection timeout",
|
||||||
|
"error_context": "Timeout after 30s"
|
||||||
|
})
|
||||||
|
|
||||||
|
response1 = json.loads(result1[0].text)
|
||||||
|
assert response1["status"] == "requires_clarification"
|
||||||
|
|
||||||
|
# Step 2: Claude would provide additional context and re-invoke
|
||||||
|
# This simulates the second call with more context
|
||||||
|
final_response = """
|
||||||
|
## Summary
|
||||||
|
The database connection timeout is caused by incorrect host configuration.
|
||||||
|
|
||||||
|
## Hypotheses (Ranked by Likelihood)
|
||||||
|
|
||||||
|
### 1. Incorrect Database Host (Confidence: High)
|
||||||
|
**Root Cause:** The config.py file shows the database host is set to 'localhost' but the database is running on a different server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mock_model.generate_content.return_value = Mock(
|
||||||
|
candidates=[Mock(content=Mock(parts=[Mock(text=final_response)]))]
|
||||||
|
)
|
||||||
|
|
||||||
|
result2 = await tool.execute({
|
||||||
|
"error_description": "Database connection timeout",
|
||||||
|
"error_context": "Timeout after 30s",
|
||||||
|
"files": ["config.py"] # Additional context provided
|
||||||
|
})
|
||||||
|
|
||||||
|
response2 = json.loads(result2[0].text)
|
||||||
|
assert response2["status"] == "success"
|
||||||
|
assert "incorrect host configuration" in response2["content"].lower()
|
||||||
@@ -20,6 +20,8 @@ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
|
|
||||||
from tools.analyze import AnalyzeTool
|
from tools.analyze import AnalyzeTool
|
||||||
from tools.think_deeper import ThinkDeeperTool
|
from tools.think_deeper import ThinkDeeperTool
|
||||||
|
from tools.debug_issue import DebugIssueTool
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
async def run_manual_live_tests():
|
async def run_manual_live_tests():
|
||||||
@@ -73,6 +75,46 @@ async def run_manual_live_tests():
|
|||||||
print("❌ ThinkDeeperTool live test failed")
|
print("❌ ThinkDeeperTool live test failed")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Test collaboration/clarification request
|
||||||
|
print("\n🔄 Testing dynamic context request (collaboration)...")
|
||||||
|
|
||||||
|
# Create a specific test case designed to trigger clarification
|
||||||
|
# We'll use analyze tool with a question that requires seeing files
|
||||||
|
analyze_tool = AnalyzeTool()
|
||||||
|
|
||||||
|
# Ask about dependencies without providing package files
|
||||||
|
result = await analyze_tool.execute({
|
||||||
|
"files": [temp_path], # Only Python file, no package.json
|
||||||
|
"question": "What npm packages and their versions does this project depend on? List all dependencies.",
|
||||||
|
"thinking_mode": "minimal" # Fast test
|
||||||
|
})
|
||||||
|
|
||||||
|
if result and result[0].text:
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
print(f" Response status: {response_data['status']}")
|
||||||
|
|
||||||
|
if response_data['status'] == 'requires_clarification':
|
||||||
|
print("✅ Dynamic context request successfully triggered!")
|
||||||
|
clarification = json.loads(response_data['content'])
|
||||||
|
print(f" Gemini asks: {clarification.get('question', 'N/A')}")
|
||||||
|
if 'files_needed' in clarification:
|
||||||
|
print(f" Files requested: {clarification['files_needed']}")
|
||||||
|
# Verify it's asking for package-related files
|
||||||
|
expected_files = ['package.json', 'package-lock.json', 'yarn.lock']
|
||||||
|
if any(f in str(clarification['files_needed']) for f in expected_files):
|
||||||
|
print(" ✅ Correctly identified missing package files!")
|
||||||
|
else:
|
||||||
|
print(" ⚠️ Unexpected files requested")
|
||||||
|
else:
|
||||||
|
# This is a failure - we specifically designed this to need clarification
|
||||||
|
print("❌ Expected clarification request but got direct response")
|
||||||
|
print(" This suggests the dynamic context feature may not be working")
|
||||||
|
print(" Response:", response_data.get('content', '')[:200])
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
print("❌ Collaboration test failed - no response")
|
||||||
|
return False
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
Path(temp_path).unlink(missing_ok=True)
|
Path(temp_path).unlink(missing_ok=True)
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,12 @@ class TestServerTools:
|
|||||||
result = await handle_call_tool("chat", {"prompt": "Hello Gemini"})
|
result = await handle_call_tool("chat", {"prompt": "Hello Gemini"})
|
||||||
|
|
||||||
assert len(result) == 1
|
assert len(result) == 1
|
||||||
assert result[0].text == "Chat response"
|
# Parse JSON response
|
||||||
|
import json
|
||||||
|
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "success"
|
||||||
|
assert response_data["content"] == "Chat response"
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_handle_list_models(self):
|
async def test_handle_list_models(self):
|
||||||
|
|||||||
@@ -62,7 +62,12 @@ class TestThinkingModes:
|
|||||||
args = mock_create_model.call_args[0]
|
args = mock_create_model.call_args[0]
|
||||||
assert args[2] == "minimal" # thinking_mode parameter
|
assert args[2] == "minimal" # thinking_mode parameter
|
||||||
|
|
||||||
assert result[0].text.startswith("Analysis:")
|
# Parse JSON response
|
||||||
|
import json
|
||||||
|
|
||||||
|
response_data = json.loads(result[0].text)
|
||||||
|
assert response_data["status"] == "success"
|
||||||
|
assert response_data["content"].startswith("Analysis:")
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@patch("tools.base.BaseTool.create_model")
|
@patch("tools.base.BaseTool.create_model")
|
||||||
|
|||||||
225
tool_selection_guide.py
Normal file
225
tool_selection_guide.py
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
"""
|
||||||
|
Tool Selection Guide for Gemini MCP Server
|
||||||
|
|
||||||
|
This module provides guidance for Claude on which tool to use for different scenarios.
|
||||||
|
"""
|
||||||
|
|
||||||
|
TOOL_BOUNDARIES = {
|
||||||
|
"analyze": {
|
||||||
|
"purpose": "Understanding and exploration (read-only analysis)",
|
||||||
|
"best_for": [
|
||||||
|
"Understanding code structure and architecture",
|
||||||
|
"Exploring unfamiliar codebases",
|
||||||
|
"Identifying patterns and dependencies",
|
||||||
|
"Documenting existing functionality",
|
||||||
|
"Learning how systems work",
|
||||||
|
],
|
||||||
|
"avoid_for": [
|
||||||
|
"Finding bugs or security issues (use review_code)",
|
||||||
|
"Debugging errors (use debug_issue)",
|
||||||
|
"Extending existing analysis (use think_deeper)",
|
||||||
|
],
|
||||||
|
"output": "Descriptive explanations and architectural insights",
|
||||||
|
},
|
||||||
|
"review_code": {
|
||||||
|
"purpose": "Finding issues and suggesting fixes (prescriptive analysis)",
|
||||||
|
"best_for": [
|
||||||
|
"Finding bugs, security vulnerabilities, performance issues",
|
||||||
|
"Code quality assessment with actionable feedback",
|
||||||
|
"Pre-merge code reviews",
|
||||||
|
"Security audits",
|
||||||
|
"Performance optimization recommendations",
|
||||||
|
],
|
||||||
|
"avoid_for": [
|
||||||
|
"Understanding how code works (use analyze)",
|
||||||
|
"Debugging runtime errors (use debug_issue)",
|
||||||
|
"Architectural discussions (use think_deeper or chat)",
|
||||||
|
],
|
||||||
|
"output": "Severity-ranked issues with specific fixes",
|
||||||
|
},
|
||||||
|
"debug_issue": {
|
||||||
|
"purpose": "Root cause analysis for errors (diagnostic analysis)",
|
||||||
|
"best_for": [
|
||||||
|
"Analyzing runtime errors and exceptions",
|
||||||
|
"Troubleshooting failing tests",
|
||||||
|
"Investigating performance problems",
|
||||||
|
"Tracing execution issues",
|
||||||
|
"System-level debugging",
|
||||||
|
],
|
||||||
|
"avoid_for": [
|
||||||
|
"Code quality issues (use review_code)",
|
||||||
|
"Understanding working code (use analyze)",
|
||||||
|
"Design discussions (use think_deeper or chat)",
|
||||||
|
],
|
||||||
|
"output": "Ranked hypotheses with validation steps",
|
||||||
|
},
|
||||||
|
"think_deeper": {
|
||||||
|
"purpose": "Extending and validating specific analysis (collaborative validation)",
|
||||||
|
"best_for": [
|
||||||
|
"Getting second opinion on Claude's analysis",
|
||||||
|
"Challenging assumptions and finding edge cases",
|
||||||
|
"Validating architectural decisions",
|
||||||
|
"Exploring alternative approaches",
|
||||||
|
"Risk assessment for proposed changes",
|
||||||
|
],
|
||||||
|
"avoid_for": [
|
||||||
|
"Initial analysis (use analyze first)",
|
||||||
|
"Bug hunting (use review_code)",
|
||||||
|
"Open-ended brainstorming (use chat)",
|
||||||
|
],
|
||||||
|
"output": "Extended analysis building on existing work",
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"purpose": "Open-ended collaboration and brainstorming (exploratory discussion)",
|
||||||
|
"best_for": [
|
||||||
|
"Brainstorming solutions and approaches",
|
||||||
|
"Technology comparisons and recommendations",
|
||||||
|
"Discussing trade-offs and design decisions",
|
||||||
|
"Getting opinions on implementation strategies",
|
||||||
|
"General development questions and explanations",
|
||||||
|
],
|
||||||
|
"avoid_for": [
|
||||||
|
"Analyzing specific code files (use analyze)",
|
||||||
|
"Finding bugs in code (use review_code)",
|
||||||
|
"Debugging specific errors (use debug_issue)",
|
||||||
|
],
|
||||||
|
"output": "Conversational insights and recommendations",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
DECISION_FLOWCHART = """
|
||||||
|
Tool Selection Decision Flow:
|
||||||
|
|
||||||
|
1. Do you have a specific error/exception to debug?
|
||||||
|
→ YES: Use debug_issue
|
||||||
|
|
||||||
|
2. Do you want to find bugs/issues in code?
|
||||||
|
→ YES: Use review_code
|
||||||
|
|
||||||
|
3. Do you want to understand how code works?
|
||||||
|
→ YES: Use analyze
|
||||||
|
|
||||||
|
4. Do you have existing analysis that needs extension/validation?
|
||||||
|
→ YES: Use think_deeper
|
||||||
|
|
||||||
|
5. Do you want to brainstorm, discuss, or get opinions?
|
||||||
|
→ YES: Use chat
|
||||||
|
"""
|
||||||
|
|
||||||
|
COMMON_OVERLAPS = {
|
||||||
|
"analyze vs review_code": {
|
||||||
|
"confusion": "Both examine code quality",
|
||||||
|
"distinction": "analyze explains, review_code prescribes fixes",
|
||||||
|
"rule": "Use analyze to understand, review_code to improve",
|
||||||
|
},
|
||||||
|
"chat vs think_deeper": {
|
||||||
|
"confusion": "Both provide collaborative thinking",
|
||||||
|
"distinction": "chat is open-ended, think_deeper builds on specific analysis",
|
||||||
|
"rule": "Use think_deeper to extend analysis, chat for open discussion",
|
||||||
|
},
|
||||||
|
"debug_issue vs review_code": {
|
||||||
|
"confusion": "Both find problems in code",
|
||||||
|
"distinction": "debug_issue diagnoses runtime errors, review_code finds static issues",
|
||||||
|
"rule": "Use debug_issue for 'why is this failing?', review_code for 'what could go wrong?'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_tool_recommendation(intent: str, context: str = "") -> dict:
|
||||||
|
"""
|
||||||
|
Recommend the best tool based on user intent and context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
intent: What the user wants to accomplish
|
||||||
|
context: Additional context about the situation
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with recommended tool and reasoning
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Keywords that strongly indicate specific tools
|
||||||
|
debug_keywords = [
|
||||||
|
"error",
|
||||||
|
"exception",
|
||||||
|
"failing",
|
||||||
|
"crash",
|
||||||
|
"bug",
|
||||||
|
"not working",
|
||||||
|
"trace",
|
||||||
|
]
|
||||||
|
review_keywords = [
|
||||||
|
"review",
|
||||||
|
"issues",
|
||||||
|
"problems",
|
||||||
|
"security",
|
||||||
|
"vulnerabilities",
|
||||||
|
"quality",
|
||||||
|
]
|
||||||
|
analyze_keywords = [
|
||||||
|
"understand",
|
||||||
|
"how does",
|
||||||
|
"what is",
|
||||||
|
"structure",
|
||||||
|
"architecture",
|
||||||
|
"explain",
|
||||||
|
]
|
||||||
|
deeper_keywords = [
|
||||||
|
"extend",
|
||||||
|
"validate",
|
||||||
|
"challenge",
|
||||||
|
"alternative",
|
||||||
|
"edge case",
|
||||||
|
"think deeper",
|
||||||
|
]
|
||||||
|
chat_keywords = [
|
||||||
|
"brainstorm",
|
||||||
|
"discuss",
|
||||||
|
"opinion",
|
||||||
|
"compare",
|
||||||
|
"recommend",
|
||||||
|
"what about",
|
||||||
|
]
|
||||||
|
|
||||||
|
intent_lower = intent.lower()
|
||||||
|
|
||||||
|
if any(keyword in intent_lower for keyword in debug_keywords):
|
||||||
|
return {
|
||||||
|
"tool": "debug_issue",
|
||||||
|
"confidence": "high",
|
||||||
|
"reasoning": "Intent indicates debugging/troubleshooting a specific issue",
|
||||||
|
}
|
||||||
|
|
||||||
|
if any(keyword in intent_lower for keyword in review_keywords):
|
||||||
|
return {
|
||||||
|
"tool": "review_code",
|
||||||
|
"confidence": "high",
|
||||||
|
"reasoning": "Intent indicates finding issues or reviewing code quality",
|
||||||
|
}
|
||||||
|
|
||||||
|
if any(keyword in intent_lower for keyword in analyze_keywords):
|
||||||
|
return {
|
||||||
|
"tool": "analyze",
|
||||||
|
"confidence": "high",
|
||||||
|
"reasoning": "Intent indicates understanding or exploring code",
|
||||||
|
}
|
||||||
|
|
||||||
|
if any(keyword in intent_lower for keyword in deeper_keywords):
|
||||||
|
return {
|
||||||
|
"tool": "think_deeper",
|
||||||
|
"confidence": "medium",
|
||||||
|
"reasoning": "Intent suggests extending or validating existing analysis",
|
||||||
|
}
|
||||||
|
|
||||||
|
if any(keyword in intent_lower for keyword in chat_keywords):
|
||||||
|
return {
|
||||||
|
"tool": "chat",
|
||||||
|
"confidence": "medium",
|
||||||
|
"reasoning": "Intent suggests open-ended discussion or brainstorming",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Default to chat for ambiguous requests
|
||||||
|
return {
|
||||||
|
"tool": "chat",
|
||||||
|
"confidence": "low",
|
||||||
|
"reasoning": "Intent unclear, defaulting to conversational tool",
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ Tool implementations for Gemini MCP Server
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .analyze import AnalyzeTool
|
from .analyze import AnalyzeTool
|
||||||
|
from .chat import ChatTool
|
||||||
from .debug_issue import DebugIssueTool
|
from .debug_issue import DebugIssueTool
|
||||||
from .review_code import ReviewCodeTool
|
from .review_code import ReviewCodeTool
|
||||||
from .think_deeper import ThinkDeeperTool
|
from .think_deeper import ThinkDeeperTool
|
||||||
@@ -12,4 +13,5 @@ __all__ = [
|
|||||||
"ReviewCodeTool",
|
"ReviewCodeTool",
|
||||||
"DebugIssueTool",
|
"DebugIssueTool",
|
||||||
"AnalyzeTool",
|
"AnalyzeTool",
|
||||||
|
"ChatTool",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,12 +5,15 @@ Base class for all Gemini MCP tools
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, Dict, List, Optional, Literal
|
from typing import Any, Dict, List, Optional, Literal
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
from google import genai
|
from google import genai
|
||||||
from google.genai import types
|
from google.genai import types
|
||||||
from mcp.types import TextContent
|
from mcp.types import TextContent
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from .models import ToolOutput, ClarificationRequest
|
||||||
|
|
||||||
|
|
||||||
class ToolRequest(BaseModel):
|
class ToolRequest(BaseModel):
|
||||||
"""Base request model for all tools"""
|
"""Base request model for all tools"""
|
||||||
@@ -95,25 +98,83 @@ class BaseTool(ABC):
|
|||||||
# Generate response
|
# Generate response
|
||||||
response = model.generate_content(prompt)
|
response = model.generate_content(prompt)
|
||||||
|
|
||||||
# Handle response
|
# Handle response and create standardized output
|
||||||
if response.candidates and response.candidates[0].content.parts:
|
if response.candidates and response.candidates[0].content.parts:
|
||||||
text = response.candidates[0].content.parts[0].text
|
raw_text = response.candidates[0].content.parts[0].text
|
||||||
|
|
||||||
|
# Check if this is a clarification request
|
||||||
|
tool_output = self._parse_response(raw_text, request)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
finish_reason = (
|
finish_reason = (
|
||||||
response.candidates[0].finish_reason
|
response.candidates[0].finish_reason
|
||||||
if response.candidates
|
if response.candidates
|
||||||
else "Unknown"
|
else "Unknown"
|
||||||
)
|
)
|
||||||
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
|
tool_output = ToolOutput(
|
||||||
|
status="error",
|
||||||
|
content=f"Response blocked or incomplete. Finish reason: {finish_reason}",
|
||||||
|
content_type="text",
|
||||||
|
)
|
||||||
|
|
||||||
# Format response
|
# Serialize the standardized output as JSON
|
||||||
formatted_response = self.format_response(text, request)
|
return [TextContent(type="text", text=tool_output.model_dump_json())]
|
||||||
|
|
||||||
return [TextContent(type="text", text=formatted_response)]
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_msg = f"Error in {self.name}: {str(e)}"
|
error_output = ToolOutput(
|
||||||
return [TextContent(type="text", text=error_msg)]
|
status="error",
|
||||||
|
content=f"Error in {self.name}: {str(e)}",
|
||||||
|
content_type="text",
|
||||||
|
)
|
||||||
|
return [TextContent(type="text", text=error_output.model_dump_json())]
|
||||||
|
|
||||||
|
def _parse_response(self, raw_text: str, request) -> ToolOutput:
|
||||||
|
"""Parse the raw response and determine if it's a clarification request"""
|
||||||
|
try:
|
||||||
|
# Try to parse as JSON to check for clarification requests
|
||||||
|
potential_json = json.loads(raw_text.strip())
|
||||||
|
|
||||||
|
if (
|
||||||
|
isinstance(potential_json, dict)
|
||||||
|
and potential_json.get("status") == "requires_clarification"
|
||||||
|
):
|
||||||
|
# Validate the clarification request structure
|
||||||
|
clarification = ClarificationRequest(**potential_json)
|
||||||
|
return ToolOutput(
|
||||||
|
status="requires_clarification",
|
||||||
|
content=clarification.model_dump_json(),
|
||||||
|
content_type="json",
|
||||||
|
metadata={
|
||||||
|
"original_request": (
|
||||||
|
request.model_dump()
|
||||||
|
if hasattr(request, "model_dump")
|
||||||
|
else str(request)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
except (json.JSONDecodeError, ValueError, TypeError):
|
||||||
|
# Not a JSON clarification request, treat as normal response
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Normal text response - format using tool-specific formatting
|
||||||
|
formatted_content = self.format_response(raw_text, request)
|
||||||
|
|
||||||
|
# Determine content type based on the formatted content
|
||||||
|
content_type = (
|
||||||
|
"markdown"
|
||||||
|
if any(
|
||||||
|
marker in formatted_content for marker in ["##", "**", "`", "- ", "1. "]
|
||||||
|
)
|
||||||
|
else "text"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ToolOutput(
|
||||||
|
status="success",
|
||||||
|
content=formatted_content,
|
||||||
|
content_type=content_type,
|
||||||
|
metadata={"tool_name": self.name},
|
||||||
|
)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def prepare_prompt(self, request) -> str:
|
async def prepare_prompt(self, request) -> str:
|
||||||
|
|||||||
111
tools/chat.py
Normal file
111
tools/chat.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
Chat tool - General development chat and collaborative thinking
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
from config import MAX_CONTEXT_TOKENS, TEMPERATURE_BALANCED
|
||||||
|
from prompts import CHAT_PROMPT
|
||||||
|
from utils import check_token_limit, read_files
|
||||||
|
|
||||||
|
from .base import BaseTool, ToolRequest
|
||||||
|
|
||||||
|
|
||||||
|
class ChatRequest(ToolRequest):
|
||||||
|
"""Request model for chat tool"""
|
||||||
|
|
||||||
|
prompt: str = Field(
|
||||||
|
...,
|
||||||
|
description="Your question, topic, or current thinking to discuss with Gemini",
|
||||||
|
)
|
||||||
|
context_files: Optional[List[str]] = Field(
|
||||||
|
default_factory=list, description="Optional files for context"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ChatTool(BaseTool):
|
||||||
|
"""General development chat and collaborative thinking tool"""
|
||||||
|
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return "chat"
|
||||||
|
|
||||||
|
def get_description(self) -> str:
|
||||||
|
return (
|
||||||
|
"GENERAL CHAT & COLLABORATIVE THINKING - Use Gemini as your thinking partner! "
|
||||||
|
"Perfect for: bouncing ideas during your own analysis, getting second opinions on your plans, "
|
||||||
|
"collaborative brainstorming, validating your checklists and approaches, exploring alternatives. "
|
||||||
|
"Also great for: explanations, comparisons, general development questions. "
|
||||||
|
"Triggers: 'ask gemini', 'brainstorm with gemini', 'get gemini's opinion', 'discuss with gemini', "
|
||||||
|
"'share my thinking with gemini', 'explain', 'what is', 'how do I'."
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_input_schema(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"prompt": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Your question, topic, or current thinking to discuss with Gemini",
|
||||||
|
},
|
||||||
|
"context_files": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "string"},
|
||||||
|
"description": "Optional files for context",
|
||||||
|
},
|
||||||
|
"temperature": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Response creativity (0-1, default 0.5)",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1,
|
||||||
|
},
|
||||||
|
"thinking_mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["minimal", "low", "medium", "high", "max"],
|
||||||
|
"description": "Thinking depth: minimal (128), low (2048), medium (8192), high (16384), max (32768)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["prompt"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_system_prompt(self) -> str:
|
||||||
|
return CHAT_PROMPT
|
||||||
|
|
||||||
|
def get_default_temperature(self) -> float:
|
||||||
|
return TEMPERATURE_BALANCED
|
||||||
|
|
||||||
|
def get_request_model(self):
|
||||||
|
return ChatRequest
|
||||||
|
|
||||||
|
async def prepare_prompt(self, request: ChatRequest) -> str:
|
||||||
|
"""Prepare the chat prompt with optional context files"""
|
||||||
|
user_content = request.prompt
|
||||||
|
|
||||||
|
# Add context files if provided
|
||||||
|
if request.context_files:
|
||||||
|
file_content, _ = read_files(request.context_files)
|
||||||
|
user_content = f"{request.prompt}\n\n=== CONTEXT FILES ===\n{file_content}\n=== END CONTEXT ==="
|
||||||
|
|
||||||
|
# Check token limits
|
||||||
|
within_limit, estimated_tokens = check_token_limit(user_content)
|
||||||
|
if not within_limit:
|
||||||
|
raise ValueError(
|
||||||
|
f"Content too large (~{estimated_tokens:,} tokens). "
|
||||||
|
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combine system prompt with user content
|
||||||
|
full_prompt = f"""{self.get_system_prompt()}
|
||||||
|
|
||||||
|
=== USER REQUEST ===
|
||||||
|
{user_content}
|
||||||
|
=== END REQUEST ===
|
||||||
|
|
||||||
|
Please provide a thoughtful, comprehensive response:"""
|
||||||
|
|
||||||
|
return full_prompt
|
||||||
|
|
||||||
|
def format_response(self, response: str, request: ChatRequest) -> str:
|
||||||
|
"""Format the chat response (no special formatting needed)"""
|
||||||
|
return response
|
||||||
59
tools/models.py
Normal file
59
tools/models.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""
|
||||||
|
Data models for tool responses and interactions
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Literal, Optional, Dict, Any, List
|
||||||
|
|
||||||
|
|
||||||
|
class ToolOutput(BaseModel):
|
||||||
|
"""Standardized output format for all tools"""
|
||||||
|
|
||||||
|
status: Literal["success", "error", "requires_clarification"] = "success"
|
||||||
|
content: str = Field(..., description="The main content/response from the tool")
|
||||||
|
content_type: Literal["text", "markdown", "json"] = "text"
|
||||||
|
metadata: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class ClarificationRequest(BaseModel):
|
||||||
|
"""Request for additional context or clarification"""
|
||||||
|
|
||||||
|
question: str = Field(..., description="Question to ask Claude for more context")
|
||||||
|
files_needed: Optional[List[str]] = Field(
|
||||||
|
default_factory=list, description="Specific files that are needed for analysis"
|
||||||
|
)
|
||||||
|
suggested_next_action: Optional[Dict[str, Any]] = Field(
|
||||||
|
None,
|
||||||
|
description="Suggested tool call with parameters after getting clarification",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DiagnosticHypothesis(BaseModel):
|
||||||
|
"""A debugging hypothesis with context and next steps"""
|
||||||
|
|
||||||
|
rank: int = Field(..., description="Ranking of this hypothesis (1 = most likely)")
|
||||||
|
confidence: Literal["high", "medium", "low"] = Field(
|
||||||
|
..., description="Confidence level"
|
||||||
|
)
|
||||||
|
hypothesis: str = Field(..., description="Description of the potential root cause")
|
||||||
|
reasoning: str = Field(..., description="Why this hypothesis is plausible")
|
||||||
|
next_step: str = Field(
|
||||||
|
..., description="Suggested action to test/validate this hypothesis"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class StructuredDebugResponse(BaseModel):
|
||||||
|
"""Enhanced debug response with multiple hypotheses"""
|
||||||
|
|
||||||
|
summary: str = Field(..., description="Brief summary of the issue")
|
||||||
|
hypotheses: List[DiagnosticHypothesis] = Field(
|
||||||
|
..., description="Ranked list of potential causes"
|
||||||
|
)
|
||||||
|
immediate_actions: List[str] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description="Immediate steps to take regardless of root cause",
|
||||||
|
)
|
||||||
|
additional_context_needed: Optional[List[str]] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description="Additional files or information that would help with analysis",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user