feat: complete redesign to v2.4.0 - Claude's ultimate development partner

Major redesign of Gemini MCP Server with modular architecture:

- Removed all emoji characters from tool outputs for clean terminal display
- Kept review category emojis (🔴🟠🟡🟢) per user preference
- Added 4 specialized tools:
  - think_deeper: Extended reasoning and problem-solving (temp 0.7)
  - review_code: Professional code review with severity levels (temp 0.2)
  - debug_issue: Root cause analysis and debugging (temp 0.2)
  - analyze: General-purpose file analysis (temp 0.2)
- Modular architecture with base tool class and Pydantic models
- Verbose tool descriptions with natural language triggers
- Updated README with comprehensive examples and real-world use cases
- All 25 tests passing, type checking clean, critical linting clean

BREAKING CHANGE: Removed analyze_code tool in favor of specialized tools

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Fahad
2025-06-08 22:30:45 +04:00
parent 8754f3c544
commit 1aa19548d1
25 changed files with 2059 additions and 1828 deletions

705
README.md
View File

@@ -1,58 +1,36 @@
# Gemini MCP Server for Claude Code
# Gemini MCP Server
A specialized Model Context Protocol (MCP) server that extends Claude Code's capabilities with Google's Gemini 2.5 Pro Preview, featuring a massive 1M token context window for handling large codebases and complex analysis tasks.
The ultimate development partner for Claude - a Model Context Protocol server that gives Claude access to Google's Gemini 2.5 Pro for extended thinking, code analysis, and problem-solving.
## Purpose
## Why This Server?
This server acts as a developer assistant that augments Claude Code when you need:
- Analysis of files too large for Claude's context window
- Deep architectural reviews across multiple files
- Extended thinking and complex problem solving
- Performance analysis of large codebases
- Security audits requiring full codebase context
Claude is brilliant, but sometimes you need:
- **Extended thinking** on complex architectural decisions
- **Deep code analysis** across massive codebases
- **Expert debugging** for tricky issues
- **Professional code reviews** with actionable feedback
- **A senior developer partner** to validate and extend ideas
## Prerequisites
This server makes Gemini your development sidekick, handling what Claude can't or extending what Claude starts.
Before you begin, ensure you have the following:
## 🚀 Quickstart (5 minutes)
1. **Python:** Python 3.10 or newer. Check your version with `python3 --version`
2. **Claude Desktop:** A working installation of Claude Desktop and the `claude` command-line tool
3. **Gemini API Key:** An active API key from [Google AI Studio](https://aistudio.google.com/app/apikey)
- Ensure your key is enabled for the `gemini-2.5-pro-preview` model
4. **Git:** The `git` command-line tool for cloning the repository
### 1. Get a Gemini API Key
Visit [Google AI Studio](https://makersuite.google.com/app/apikey) and generate a free API key.
## Quick Start for Claude Code
### 2. Install via Claude Desktop Config
### 1. Clone the Repository
Add to your `claude_desktop_config.json`:
First, clone this repository to your local machine:
```bash
git clone https://github.com/BeehiveInnovations/gemini-mcp-server.git
cd gemini-mcp-server
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
# macOS/Linux only: Make the script executable
chmod +x run_gemini.sh
```
Note the full path to this directory - you'll need it for the configuration.
### 2. Configure in Claude Desktop
You can access the configuration file in two ways:
- **Through Claude Desktop**: Open Claude Desktop → Settings → Developer → Edit Config
- **Direct file access**:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
Add the following configuration, replacing the path with your actual directory path:
**macOS**:
```json
{
"mcpServers": {
"gemini": {
"command": "/path/to/gemini-mcp-server/run_gemini.sh",
"command": "python",
"args": ["/absolute/path/to/gemini-mcp-server/server.py"],
"env": {
"GEMINI_API_KEY": "your-gemini-api-key-here"
}
@@ -61,156 +39,310 @@ Add the following configuration, replacing the path with your actual directory p
}
```
**Windows**:
```json
{
"mcpServers": {
"gemini": {
"command": "C:\\path\\to\\gemini-mcp-server\\run_gemini.bat",
"env": {
"GEMINI_API_KEY": "your-gemini-api-key-here"
}
}
}
}
```
**Linux**:
```json
{
"mcpServers": {
"gemini": {
"command": "/path/to/gemini-mcp-server/run_gemini.sh",
"env": {
"GEMINI_API_KEY": "your-gemini-api-key-here"
}
}
}
}
```
**Important**: Replace the path with the actual absolute path where you cloned the repository:
- **macOS example**: `/Users/yourname/projects/gemini-mcp-server/run_gemini.sh`
- **Windows example**: `C:\\Users\\yourname\\projects\\gemini-mcp-server\\run_gemini.bat`
- **Linux example**: `/home/yourname/projects/gemini-mcp-server/run_gemini.sh`
### 3. Restart Claude Desktop
After adding the configuration, restart Claude Desktop. You'll see "gemini" in the MCP servers list.
### 4. Start Using It!
### 4. Add to Claude Code
Just ask Claude naturally:
- "Think deeper about this architecture design"
- "Review this code for security issues"
- "Debug why this test is failing"
- "Analyze these files to understand the data flow"
To make the server available in Claude Code, run:
```bash
# This command reads your Claude Desktop configuration and makes
# the "gemini" server available in your terminal
claude mcp add-from-claude-desktop -s user
## 🧠 Available Tools
### `think_deeper` - Extended Reasoning Partner
**When Claude needs to go deeper on complex problems**
#### Example Prompts:
```
### 5. Start Using Natural Language
Just talk to Claude naturally:
- "Use gemini analyze_file on main.py to find bugs"
- "Share your analysis with gemini extended_think for deeper insights"
- "Ask gemini to review the architecture using analyze_file"
**Key tools:**
- `analyze_file` - Clean file analysis without terminal clutter
- `extended_think` - Collaborative deep thinking with Claude's analysis
- `chat` - General conversations
- `analyze_code` - Legacy tool (prefer analyze_file for files)
## How It Works
This server acts as a local proxy between Claude Code and the Google Gemini API, following the Model Context Protocol (MCP):
1. You issue a command to Claude (e.g., "Ask Gemini to...")
2. Claude Code sends a request to the local MCP server defined in your configuration
3. This server receives the request, formats it for the Gemini API, and includes any file contents
4. The request is sent to the Google Gemini API using your API key
5. The server receives the response from Gemini
6. The response is formatted and streamed back to Claude, who presents it to you
All processing and API communication happens locally from your machine. Your API key is never exposed to Anthropic.
## Developer-Optimized Features
### Automatic Developer Context
When no custom system prompt is provided, Gemini automatically operates with deep developer expertise, focusing on:
- Clean code principles
- Performance optimization
- Security best practices
- Architectural patterns
- Testing strategies
- Modern development practices
### Optimized Temperature Settings
- **General chat**: 0.5 (balanced accuracy with some creativity)
- **Code analysis**: 0.2 (high precision for code review)
### Large Context Window
- Handles up to 1M tokens (~4M characters)
- Perfect for analyzing entire codebases
- Maintains context across multiple large files
## Available Tools
### `chat`
General-purpose developer conversations with Gemini.
**Example uses:**
```
"Ask Gemini about the best approach for implementing a distributed cache"
"Use Gemini to explain the tradeoffs between different authentication strategies"
```
### `analyze_code` (Legacy)
Analyzes code files or snippets. For better terminal output, use `analyze_file` instead.
### `analyze_file` (Recommended for Files)
Clean file analysis - always uses file paths, never shows content in terminal.
**Example uses:**
```
"Use gemini analyze_file on README.md to find issues"
"Ask gemini to analyze_file main.py for performance problems"
"Have gemini analyze_file on auth.py, users.py, and permissions.py together"
```
**Benefits:**
- Terminal always stays clean - only shows "Analyzing N file(s)"
- Server reads files directly and sends to Gemini
- No need to worry about prompt phrasing
- Supports multiple files in one request
### `extended_think`
Collaborate with Gemini on complex problems by sharing Claude's analysis for deeper insights.
**Example uses:**
```
"Share your analysis with gemini extended_think for deeper insights"
"Use gemini extended_think to validate and extend your architectural design"
"Ask gemini to extend your thinking on this security analysis"
```
**Advanced usage with focus areas:**
```
"Use gemini extended_think with focus='performance' to drill into scaling issues"
"Share your design with gemini extended_think focusing on security vulnerabilities"
"Get gemini to extend your analysis with focus on edge cases"
"Think deeper about my authentication design"
"Ultrathink on this distributed system architecture"
"Extend my analysis of this performance issue"
"Challenge my assumptions about this approach"
"Explore alternative solutions for this caching strategy"
"Validate my microservices communication approach"
```
**Features:**
- Takes Claude's thoughts, plans, or analysis as input
- Optional file context for reference
- Configurable focus areas (architecture, bugs, performance, security)
- Higher temperature (0.7) for creative problem-solving
- Designed for collaborative thinking, not just code review
- Extends Claude's analysis with alternative approaches
- Finds edge cases and failure modes
- Validates architectural decisions
- Suggests concrete implementations
- Temperature: 0.7 (creative problem-solving)
### `list_models`
Lists available Gemini models (defaults to 2.5 Pro Preview).
**Key Capabilities:**
- Challenge assumptions constructively
- Identify overlooked edge cases
- Suggest alternative design patterns
- Evaluate scalability implications
- Consider security vulnerabilities
- Assess technical debt impact
## Installation
**Triggers:** think deeper, ultrathink, extend my analysis, explore alternatives, validate my approach
### `review_code` - Professional Code Review
**Comprehensive code analysis with prioritized feedback**
#### Example Prompts:
```
"Review this code for issues"
"Security audit of auth.py"
"Quick review of my changes"
"Check this code against PEP8 standards"
"Review the authentication module focusing on OWASP top 10"
"Performance review of the database queries in models.py"
"Review api/ directory for REST API best practices"
```
**Review Types:**
- `full` - Complete review (default)
- `security` - Security-focused analysis
- `performance` - Performance optimization
- `quick` - Critical issues only
**Output includes:**
- Issues by severity with color coding:
- 🔴 CRITICAL: Security vulnerabilities, data loss risks
- 🟠 HIGH: Bugs, performance issues, bad practices
- 🟡 MEDIUM: Code smells, maintainability issues
- 🟢 LOW: Style issues, minor improvements
- Specific fixes with code examples
- Overall quality assessment
- Top 3 priority improvements
- Positive aspects worth preserving
**Customization Options:**
- `focus_on`: Specific aspects to emphasize
- `standards`: Coding standards to enforce (PEP8, ESLint, etc.)
- `severity_filter`: Minimum severity to report
**Triggers:** review code, check for issues, find bugs, security check, code audit
### `debug_issue` - Expert Debugging Assistant
**Root cause analysis for complex problems**
#### Example Prompts:
```
"Debug this TypeError in my async function"
"Why is this test failing intermittently?"
"Trace the root cause of this memory leak"
"Debug this race condition"
"Help me understand why the API returns 500 errors under load"
"Debug why my WebSocket connections are dropping"
"Find the root cause of this deadlock in my threading code"
```
**Provides:**
- Root cause identification
- Step-by-step debugging approach
- Immediate fixes
- Long-term solutions
- Prevention strategies
**Input Options:**
- `error_description`: The error or symptom
- `error_context`: Stack traces, logs, error messages
- `relevant_files`: Files that might be involved
- `runtime_info`: Environment, versions, configuration
- `previous_attempts`: What you've already tried
**Triggers:** debug, error, failing, root cause, trace, not working, why is
### `analyze` - Smart File Analysis
**General-purpose code understanding and exploration**
#### Example Prompts:
```
"Analyze main.py to understand the architecture"
"Examine these files for circular dependencies"
"Look for performance bottlenecks in this module"
"Understand how these components interact"
"Analyze the data flow through the pipeline modules"
"Check if this module follows SOLID principles"
"Analyze the API endpoints to create documentation"
"Examine the test coverage and suggest missing tests"
```
**Analysis Types:**
- `architecture` - Design patterns, structure, dependencies
- `performance` - Bottlenecks, optimization opportunities
- `security` - Vulnerability assessment, security patterns
- `quality` - Code metrics, maintainability, test coverage
- `general` - Comprehensive analysis (default)
**Output Formats:**
- `detailed` - Comprehensive analysis (default)
- `summary` - High-level overview
- `actionable` - Focused on specific improvements
**Special Features:**
- Always uses file paths (not content) = clean terminal output!
- Can analyze multiple files to understand relationships
- Identifies patterns and anti-patterns
- Suggests refactoring opportunities
**Triggers:** analyze, examine, look at, understand, inspect, check
### `chat` - General Development Chat
**For everything else**
#### Example Prompts:
```
"Ask Gemini about the best caching strategy"
"Explain how async generators work"
"What's the difference between these design patterns?"
"Compare Redis vs Memcached for my use case"
"Explain the tradeoffs of microservices vs monolith"
"Best practices for handling timezone data in Python"
```
### Additional Utility Tools
#### `list_models` - See Available Gemini Models
```
"List available Gemini models"
"Show me what models I can use"
```
#### `get_version` - Server Information
```
"Get Gemini server version"
"Show server configuration"
```
## 📚 Real-World Examples
### Example 1: Comprehensive Security Review
```
You: "Review the auth/ directory for security issues, focus on JWT handling"
Gemini:
🔴 CRITICAL auth/jwt.py:45 - Secret key hardcoded
→ Fix: Use environment variable
SECRET_KEY = os.environ.get('JWT_SECRET')
🟠 HIGH auth/validate.py:23 - No token expiration check
→ Fix: Add expiration validation
if decoded['exp'] < time.time():
raise TokenExpiredError()
```
### Example 2: Performance Debugging
```
You: "Debug why the API endpoint /users/search is slow"
Gemini:
ROOT CAUSE: N+1 query problem in user search
- Each user triggers separate query for roles
- 100 users = 101 database queries
IMMEDIATE FIX:
Add eager loading: User.query.options(joinedload('roles'))
LONG-TERM SOLUTION:
Implement query result caching with Redis
```
### Example 3: Architecture Analysis
```
You: "Analyze src/ to understand the architecture and identify improvements"
Gemini:
ARCHITECTURE OVERVIEW:
- Layered architecture: Controllers → Services → Repositories
- Good separation of concerns
- Dependency injection pattern used
ISSUES FOUND:
1. Circular dependency: UserService ↔ AuthService
2. Missing abstraction for external APIs
3. Business logic leaked into controllers
RECOMMENDATIONS:
1. Extract shared logic to UserAuthService
2. Add adapter pattern for external APIs
3. Move validation to service layer
```
## 💡 Power User Workflows
### 1. **Claude + Gemini Deep Thinking**
```
You: "Design a real-time collaborative editor"
Claude: [provides initial design]
You: "Think deeper about the conflict resolution"
Gemini: [explores CRDTs, operational transforms, edge cases]
You: "Update the design based on Gemini's insights"
Claude: [refines with deeper understanding]
```
### 2. **Comprehensive Code Review**
```
You: "Review api/auth.py focusing on security"
Gemini: [identifies SQL injection risk, suggests prepared statements]
You: "Fix the critical issues Gemini found"
Claude: [implements secure solution]
```
### 3. **Complex Debugging**
```
Claude: "I see the error but the root cause isn't clear..."
You: "Debug this with the error context and relevant files"
Gemini: [traces execution, identifies race condition]
You: "Implement Gemini's suggested fix"
```
### 4. **Architecture Validation**
```
You: "I've designed a microservices architecture [details]"
You: "Think deeper about scalability and failure modes"
Gemini: [analyzes bottlenecks, suggests circuit breakers, identifies edge cases]
```
## 🎯 Pro Tips
### Natural Language Triggers
The server recognizes natural phrases. Just talk normally:
- ❌ "Use the think_deeper tool with current_analysis parameter..."
- ✅ "Think deeper about this approach"
### Automatic Tool Selection
Claude will automatically pick the right tool based on your request:
- "review" → `review_code`
- "debug" → `debug_issue`
- "analyze" → `analyze`
- "think deeper" → `think_deeper`
### Clean Terminal Output
All file operations use paths, not content, so your terminal stays readable even with large files.
### Context Awareness
Tools can reference files for additional context:
```
"Debug this error with context from app.py and config.py"
"Think deeper about my design, reference the current architecture.md"
```
## 🏗️ Architecture
```
gemini-mcp-server/
├── server.py # Main server
├── config.py # Configuration
├── tools/ # Tool implementations
│ ├── think_deeper.py
│ ├── review_code.py
│ ├── debug_issue.py
│ └── analyze.py
├── prompts/ # System prompts
└── utils/ # Utilities
```
**Extensible Design:**
- Each tool is a self-contained module
- Easy to add new tools
- Consistent interface
- Type-safe with Pydantic
## 🔧 Installation
1. Clone the repository:
```bash
@@ -234,210 +366,21 @@ Lists available Gemini models (defaults to 2.5 Pro Preview).
export GEMINI_API_KEY="your-api-key-here"
```
## Advanced Configuration
## 🤝 Contributing
### Custom System Prompts
Override the default developer prompt when needed:
```python
{
"prompt": "Review this code",
"system_prompt": "You are a security expert. Focus only on vulnerabilities."
}
```
We welcome contributions! The modular architecture makes it easy to add new tools:
### Temperature Control
Adjust for your use case:
- `0.1-0.3`: Maximum precision (debugging, security analysis)
- `0.4-0.6`: Balanced (general development tasks)
- `0.7-0.9`: Creative solutions (architecture design, brainstorming)
1. Create a new tool in `tools/`
2. Inherit from `BaseTool`
3. Implement required methods
4. Add to `TOOLS` in `server.py`
### Model Selection
While defaulting to `gemini-2.5-pro-preview-06-05`, you can specify other models:
- `gemini-1.5-pro-latest`: Stable alternative
- `gemini-1.5-flash`: Faster responses
- Use `list_models` to see all available options
See existing tools for examples.
## Claude Code Integration Examples
## 📝 License
### When Claude hits token limits:
```
Claude: "This file is too large for me to analyze fully..."
You: "Use Gemini to analyze the entire file and identify the main components"
```
MIT License - see LICENSE file for details.
### For architecture reviews:
```
You: "Use Gemini to analyze all files in /src/core/ and create an architecture diagram"
```
## 🙏 Acknowledgments
### For performance optimization:
```
You: "Have Gemini profile this codebase and suggest the top 5 performance improvements"
```
## Practical Usage Tips
### Effective Commands
Be specific about what you want from Gemini:
- Good: "Ask Gemini to identify memory leaks in this code"
- Bad: "Ask Gemini about this"
### Clean Terminal Output
When analyzing files, explicitly mention the files parameter:
- "Use gemini analyze_code with files=['app.py'] to find bugs"
- "Analyze package.json using gemini's files parameter"
This prevents Claude from displaying the entire file content in your terminal.
### Common Workflows
#### 1. **Extended Thinking Partnership**
```
You: "Design a distributed task queue system"
Claude: [provides detailed architecture and implementation plan]
You: "Use gemini extended_think to validate and extend this design"
Gemini: [identifies gaps, suggests alternatives, finds edge cases]
You: "Address the issues Gemini found"
Claude: [updates design with improvements]
```
#### 2. **Clean File Analysis (No Terminal Clutter)**
```
"Use gemini analyze_file on engine.py to find performance issues"
"Ask gemini to analyze_file database.py and suggest optimizations"
"Have gemini analyze_file on all files in /src/core/"
```
#### 3. **Multi-File Architecture Review**
```
"Use gemini analyze_file on auth.py, users.py, permissions.py to map dependencies"
"Ask gemini to analyze_file the entire /src/api/ directory for security issues"
"Have gemini analyze_file all model files to check for N+1 queries"
```
#### 4. **Deep Collaborative Analysis**
```
Claude: "Here's my analysis of the memory leak: [detailed investigation]"
You: "Share this with gemini extended_think focusing on root causes"
Claude: "I've designed this caching strategy: [detailed design]"
You: "Use gemini extended_think with focus='performance' to stress-test this design"
Claude: "Here's my security assessment: [findings]"
You: "Get gemini to extended_think on this with files=['auth.py', 'crypto.py'] for context"
```
#### 4. **Claude-Driven Design with Gemini Validation**
```
Claude: "I've designed a caching strategy using Redis with TTL-based expiration..."
You: "Share my caching design with Gemini and ask for edge cases I might have missed"
Claude: "Here's my implementation plan for the authentication system: [detailed plan]"
You: "Use Gemini to analyze this plan and identify security vulnerabilities or scalability issues"
Claude: "I'm thinking of using this approach for the data pipeline: [approach details]"
You: "Have Gemini review my approach and check these 10 files for compatibility issues"
```
#### 5. **Security & Performance Audits**
```
"Use Gemini to security audit this authentication flow"
"Have Gemini identify performance bottlenecks in this codebase"
"Ask Gemini to check for common security vulnerabilities"
```
### Best Practices
- Let Claude do the primary thinking and design work
- Use Gemini as a validation layer for edge cases and extended context
- Share Claude's complete thoughts with Gemini for comprehensive review
- Have Gemini analyze files that are too large for Claude
- Use the feedback loop: Claude designs → Gemini validates → Claude refines
### Real-World Example Flow
```
1. You: "Create a microservices architecture for our e-commerce platform"
2. Claude: [Designs comprehensive architecture with service boundaries, APIs, data flow]
3. You: "Take my complete architecture design and have Gemini analyze it for:
- Potential bottlenecks
- Missing error handling
- Security vulnerabilities
- Scalability concerns"
4. Gemini: [Provides detailed analysis with specific concerns]
5. You: "Based on Gemini's analysis, update the architecture"
6. Claude: [Refines design addressing all concerns]
```
## Notes
- Gemini 2.5 Pro Preview may occasionally block certain prompts due to safety filters
- If a prompt is blocked by Google's safety filters, the server will return a clear error message to Claude explaining why the request could not be completed
- Token estimation: ~4 characters per token
- All file paths should be absolute paths
## Troubleshooting
### Server Not Appearing in Claude
- **Check JSON validity:** Ensure your `claude_desktop_config.json` file is valid JSON (no trailing commas, proper quotes)
- **Verify absolute paths:** The `command` path must be an absolute path to `run_gemini.sh` or `run_gemini.bat`
- **Restart Claude Desktop:** Always restart Claude Desktop completely after any configuration change
### Gemini Commands Fail
- **"API Key not valid" errors:** Verify your `GEMINI_API_KEY` is correct and active in [Google AI Studio](https://aistudio.google.com/app/apikey)
- **"Permission denied" errors:**
- Ensure your API key is enabled for the `gemini-2.5-pro-preview` model
- On macOS/Linux, check that `run_gemini.sh` has execute permissions (`chmod +x run_gemini.sh`)
- **Network errors:** If behind a corporate firewall, ensure requests to `https://generativelanguage.googleapis.com` are allowed
### Common Setup Issues
- **"Module not found" errors:** The virtual environment may not be activated. See the Installation section
- **`chmod: command not found` (Windows):** The `chmod +x` command is for macOS/Linux only. Windows users can skip this step
- **Path not found errors:** Use absolute paths in all configurations, not relative paths like `./run_gemini.sh`
## Testing
### Running Tests Locally
```bash
# Install development dependencies
pip install -r requirements.txt
# Run tests with coverage
pytest
# Run tests with verbose output
pytest -v
# Run specific test file
pytest tests/test_gemini_server.py
# Generate HTML coverage report
pytest --cov-report=html
open htmlcov/index.html # View coverage report
```
### Continuous Integration
This project uses GitHub Actions for automated testing:
- Tests run on every push and pull request
- Supports Python 3.8 - 3.12
- Tests on Ubuntu, macOS, and Windows
- Includes linting with flake8, black, isort, and mypy
- Maintains 80%+ code coverage
## Contributing
This server is designed specifically for Claude Code users. Contributions that enhance the developer experience are welcome!
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Write tests for your changes
4. Ensure all tests pass (`pytest`)
5. Commit your changes (`git commit -m 'Add amazing feature'`)
6. Push to the branch (`git push origin feature/amazing-feature`)
7. Open a Pull Request
## License
MIT License - feel free to customize for your development workflow.
Built with [MCP](https://modelcontextprotocol.com) by Anthropic and powered by Google's Gemini API.

67
config.py Normal file
View File

@@ -0,0 +1,67 @@
"""
Configuration and constants for Gemini MCP Server
"""
# Version and metadata
__version__ = "2.4.0"
__updated__ = "2025-06-08"
__author__ = "Fahad Gilani"
# Model configuration
DEFAULT_MODEL = "gemini-2.5-pro-preview-06-05"
MAX_CONTEXT_TOKENS = 1_000_000 # 1M tokens for Gemini Pro
# Temperature defaults for different tool types
TEMPERATURE_ANALYTICAL = 0.2 # For code review, debugging
TEMPERATURE_BALANCED = 0.5 # For general chat
TEMPERATURE_CREATIVE = 0.7 # For architecture, deep thinking
# Tool trigger phrases for natural language matching
TOOL_TRIGGERS = {
"think_deeper": [
"think deeper",
"ultrathink",
"extend my analysis",
"reason through",
"explore alternatives",
"challenge my thinking",
"deep think",
"extended thinking",
"validate my approach",
"find edge cases",
],
"review_code": [
"review",
"check for issues",
"find bugs",
"security check",
"code quality",
"audit",
"code review",
"check this code",
"review for",
"find vulnerabilities",
],
"debug_issue": [
"debug",
"error",
"failing",
"root cause",
"trace",
"why doesn't",
"not working",
"diagnose",
"troubleshoot",
"investigate this error",
],
"analyze": [
"analyze",
"examine",
"look at",
"check",
"inspect",
"understand",
"analyze file",
"analyze these files",
],
}

View File

@@ -1,858 +1,11 @@
#!/usr/bin/env python3
"""
Gemini MCP Server - Model Context Protocol server for Google Gemini
Enhanced for large-scale code analysis with 1M token context window
Gemini MCP Server - Entry point for backward compatibility
This file exists to maintain compatibility with existing configurations.
The main implementation is now in server.py
"""
from server import main
import asyncio
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
import google.generativeai as genai
from mcp.server import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from pydantic import BaseModel, Field
# Version and metadata
__version__ = "2.3.0"
__updated__ = "2025-06-08"
__author__ = "Fahad Gilani"
# Default to Gemini 2.5 Pro Preview with maximum context
DEFAULT_MODEL = "gemini-2.5-pro-preview-06-05"
MAX_CONTEXT_TOKENS = 1000000 # 1M tokens
# Developer-focused system prompt for Claude Code usage
DEVELOPER_SYSTEM_PROMPT = """You are an expert software developer assistant working alongside Claude Code. \
Your role is to extend Claude's capabilities when handling large codebases or complex analysis tasks.
Core competencies:
- Deep understanding of software architecture and design patterns
- Expert-level debugging and root cause analysis
- Performance optimization and scalability considerations
- Security best practices and vulnerability identification
- Clean code principles and refactoring strategies
- Comprehensive testing approaches (unit, integration, e2e)
- Modern development practices (CI/CD, DevOps, cloud-native)
- Cross-platform and cross-language expertise
Your approach:
- Be precise and technical, avoiding unnecessary explanations
- Provide actionable, concrete solutions with code examples
- Consider edge cases and potential issues proactively
- Focus on maintainability, readability, and long-term sustainability
- Suggest modern, idiomatic solutions for the given language/framework
- When reviewing code, prioritize critical issues first
- Always validate your suggestions against best practices
Remember: You're augmenting Claude Code's capabilities, especially for tasks requiring \
extensive context or deep analysis that might exceed Claude's token limits."""
# Extended thinking system prompt for collaborative analysis
EXTENDED_THINKING_PROMPT = """You are a senior development partner collaborating with Claude Code on complex problems. \
Claude has shared their analysis with you for deeper exploration and validation.
Your role is to:
1. Build upon Claude's thinking - identify gaps, extend ideas, and suggest alternatives
2. Challenge assumptions constructively and identify potential issues
3. Provide concrete, actionable insights that complement Claude's analysis
4. Focus on aspects Claude might have missed or couldn't fully explore
5. Suggest implementation strategies and architectural improvements
Key areas to consider:
- Edge cases and failure modes Claude might have overlooked
- Performance implications at scale
- Security vulnerabilities or attack vectors
- Maintainability and technical debt considerations
- Alternative approaches or design patterns
- Integration challenges with existing systems
- Testing strategies for complex scenarios
Be direct and technical. Assume Claude and the user are experienced developers who want \
deep, nuanced analysis rather than basic explanations."""
class GeminiChatRequest(BaseModel):
"""Request model for Gemini chat"""
prompt: str = Field(..., description="The prompt to send to Gemini")
system_prompt: Optional[str] = Field(
None, description="Optional system prompt for context"
)
max_tokens: Optional[int] = Field(
8192, description="Maximum number of tokens in response"
)
temperature: Optional[float] = Field(
0.5,
description="Temperature for response randomness (0-1, default 0.5 for balanced accuracy/creativity)",
)
model: Optional[str] = Field(
DEFAULT_MODEL, description=f"Model to use (defaults to {DEFAULT_MODEL})"
)
class CodeAnalysisRequest(BaseModel):
"""Request model for code analysis"""
files: Optional[List[str]] = Field(
None, description="List of file paths to analyze"
)
code: Optional[str] = Field(None, description="Direct code content to analyze")
question: str = Field(
..., description="Question or analysis request about the code"
)
system_prompt: Optional[str] = Field(
None, description="Optional system prompt for context"
)
max_tokens: Optional[int] = Field(
8192, description="Maximum number of tokens in response"
)
temperature: Optional[float] = Field(
0.2,
description="Temperature for code analysis (0-1, default 0.2 for high accuracy)",
)
model: Optional[str] = Field(
DEFAULT_MODEL, description=f"Model to use (defaults to {DEFAULT_MODEL})"
)
verbose_output: Optional[bool] = Field(
False, description="Show file contents in terminal output"
)
class FileAnalysisRequest(BaseModel):
"""Request model for file analysis"""
files: List[str] = Field(..., description="List of file paths to analyze")
question: str = Field(
..., description="Question or analysis request about the files"
)
system_prompt: Optional[str] = Field(
None, description="Optional system prompt for context"
)
max_tokens: Optional[int] = Field(
8192, description="Maximum number of tokens in response"
)
temperature: Optional[float] = Field(
0.2,
description="Temperature for analysis (0-1, default 0.2 for high accuracy)",
)
model: Optional[str] = Field(
DEFAULT_MODEL, description=f"Model to use (defaults to {DEFAULT_MODEL})"
)
class ExtendedThinkRequest(BaseModel):
"""Request model for extended thinking with Gemini"""
thought_process: str = Field(
..., description="Claude's analysis, thoughts, plans, or outlines to extend"
)
context: Optional[str] = Field(
None, description="Additional context about the problem or goal"
)
files: Optional[List[str]] = Field(
None, description="Optional file paths for additional context"
)
focus: Optional[str] = Field(
None,
description="Specific focus area: architecture, bugs, performance, security, etc.",
)
system_prompt: Optional[str] = Field(
None, description="Optional system prompt for context"
)
max_tokens: Optional[int] = Field(
8192, description="Maximum number of tokens in response"
)
temperature: Optional[float] = Field(
0.7,
description="Temperature for creative thinking (0-1, default 0.7 for balanced creativity)",
)
model: Optional[str] = Field(
DEFAULT_MODEL, description=f"Model to use (defaults to {DEFAULT_MODEL})"
)
# Create the MCP server instance
server: Server = Server("gemini-server")
# Configure Gemini API
def configure_gemini():
"""Configure the Gemini API with API key from environment"""
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError("GEMINI_API_KEY environment variable is not set")
genai.configure(api_key=api_key)
def read_file_content(file_path: str) -> str:
"""Read content from a file with error handling - for backward compatibility"""
return read_file_content_for_gemini(file_path)
def read_file_content_for_gemini(file_path: str) -> str:
"""Read content from a file with proper formatting for Gemini"""
try:
path = Path(file_path)
if not path.exists():
return f"\n--- FILE NOT FOUND: {file_path} ---\nError: File does not exist\n--- END FILE ---\n"
if not path.is_file():
return f"\n--- NOT A FILE: {file_path} ---\nError: Path is not a file\n--- END FILE ---\n"
# Read the file
with open(path, "r", encoding="utf-8") as f:
content = f.read()
# Format with clear delimiters for Gemini
return f"\n--- BEGIN FILE: {file_path} ---\n{content}\n--- END FILE: {file_path} ---\n"
except Exception as e:
return f"\n--- ERROR READING FILE: {file_path} ---\nError: {str(e)}\n--- END FILE ---\n"
def prepare_code_context(
files: Optional[List[str]], code: Optional[str]
) -> Tuple[str, str]:
"""Prepare code context from files and/or direct code
Returns: (context_for_gemini, summary_for_terminal)
"""
context_parts = []
summary_parts = []
# Add file contents
if files:
summary_parts.append(f"Analyzing {len(files)} file(s):")
for file_path in files:
# Get file content for Gemini
file_content = read_file_content_for_gemini(file_path)
context_parts.append(file_content)
# Create summary with small excerpt for terminal
path = Path(file_path)
if path.exists() and path.is_file():
size = path.stat().st_size
try:
with open(path, "r", encoding="utf-8") as f:
# Read first few lines for preview
preview_lines = []
for i, line in enumerate(f):
if i >= 3: # Show max 3 lines
break
preview_lines.append(line.rstrip())
preview = "\n".join(preview_lines)
if len(preview) > 100:
preview = preview[:100] + "..."
summary_parts.append(f" {file_path} ({size:,} bytes)")
if preview.strip():
summary_parts.append(f" Preview: {preview[:50]}...")
except Exception:
summary_parts.append(f" {file_path} ({size:,} bytes)")
else:
summary_parts.append(f" {file_path} (not found)")
# Add direct code
if code:
formatted_code = (
f"\n--- BEGIN DIRECT CODE ---\n{code}\n--- END DIRECT CODE ---\n"
)
context_parts.append(formatted_code)
preview = code[:100] + "..." if len(code) > 100 else code
summary_parts.append(f"Direct code provided ({len(code):,} characters)")
summary_parts.append(f" Preview: {preview}")
full_context = "\n\n".join(context_parts)
summary = "\n".join(summary_parts)
return full_context, summary
@server.list_tools()
async def handle_list_tools() -> List[Tool]:
"""List all available tools"""
return [
Tool(
name="chat",
description="Chat with Gemini (optimized for 2.5 Pro with 1M context)",
inputSchema={
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "The prompt to send to Gemini",
},
"system_prompt": {
"type": "string",
"description": "Optional system prompt for context",
},
"max_tokens": {
"type": "integer",
"description": "Maximum number of tokens in response",
"default": 8192,
},
"temperature": {
"type": "number",
"description": "Temperature for response randomness (0-1, default 0.5 for "
"balanced accuracy/creativity)",
"default": 0.5,
"minimum": 0,
"maximum": 1,
},
"model": {
"type": "string",
"description": f"Model to use (defaults to {DEFAULT_MODEL})",
"default": DEFAULT_MODEL,
},
},
"required": ["prompt"],
},
),
Tool(
name="analyze_code",
description="Analyze code files or snippets with Gemini's 1M context window. "
"For large content, use file paths to avoid terminal clutter.",
inputSchema={
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {"type": "string"},
"description": "List of file paths to analyze",
},
"code": {
"type": "string",
"description": "Direct code content to analyze "
"(use for small snippets only; prefer files for large content)",
},
"question": {
"type": "string",
"description": "Question or analysis request about the code",
},
"system_prompt": {
"type": "string",
"description": "Optional system prompt for context",
},
"max_tokens": {
"type": "integer",
"description": "Maximum number of tokens in response",
"default": 8192,
},
"temperature": {
"type": "number",
"description": "Temperature for code analysis (0-1, default 0.2 for high accuracy)",
"default": 0.2,
"minimum": 0,
"maximum": 1,
},
"model": {
"type": "string",
"description": f"Model to use (defaults to {DEFAULT_MODEL})",
"default": DEFAULT_MODEL,
},
},
"required": ["question"],
},
),
Tool(
name="list_models",
description="List available Gemini models",
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="get_version",
description="Get the version and metadata of the Gemini MCP Server",
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="analyze_file",
description="Analyze files with Gemini - always uses file paths for clean terminal output",
inputSchema={
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {"type": "string"},
"description": "List of file paths to analyze",
},
"question": {
"type": "string",
"description": "Question or analysis request about the files",
},
"system_prompt": {
"type": "string",
"description": "Optional system prompt for context",
},
"max_tokens": {
"type": "integer",
"description": "Maximum number of tokens in response",
"default": 8192,
},
"temperature": {
"type": "number",
"description": "Temperature for analysis (0-1, default 0.2 for high accuracy)",
"default": 0.2,
"minimum": 0,
"maximum": 1,
},
"model": {
"type": "string",
"description": f"Model to use (defaults to {DEFAULT_MODEL})",
"default": DEFAULT_MODEL,
},
},
"required": ["files", "question"],
},
),
Tool(
name="extended_think",
description="Collaborate with Gemini on complex problems - share Claude's analysis for deeper insights",
inputSchema={
"type": "object",
"properties": {
"thought_process": {
"type": "string",
"description": "Claude's analysis, thoughts, plans, or outlines to extend",
},
"context": {
"type": "string",
"description": "Additional context about the problem or goal",
},
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Optional file paths for additional context",
},
"focus": {
"type": "string",
"description": "Specific focus area: architecture, bugs, performance, security, etc.",
},
"system_prompt": {
"type": "string",
"description": "Optional system prompt for context",
},
"max_tokens": {
"type": "integer",
"description": "Maximum number of tokens in response",
"default": 8192,
},
"temperature": {
"type": "number",
"description": "Temperature for creative thinking (0-1, default 0.7)",
"default": 0.7,
"minimum": 0,
"maximum": 1,
},
"model": {
"type": "string",
"description": f"Model to use (defaults to {DEFAULT_MODEL})",
"default": DEFAULT_MODEL,
},
},
"required": ["thought_process"],
},
),
]
@server.call_tool()
async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]:
"""Handle tool execution requests"""
if name == "chat":
# Validate request
request = GeminiChatRequest(**arguments)
try:
# Use the specified model with optimized settings
model_name = request.model or DEFAULT_MODEL
temperature = (
request.temperature if request.temperature is not None else 0.5
)
max_tokens = request.max_tokens if request.max_tokens is not None else 8192
model = genai.GenerativeModel(
model_name=model_name,
generation_config={
"temperature": temperature,
"max_output_tokens": max_tokens,
"candidate_count": 1,
},
)
# Prepare the prompt with automatic developer context if no system prompt provided
if request.system_prompt:
full_prompt = f"{request.system_prompt}\n\n{request.prompt}"
else:
# Auto-inject developer system prompt for better Claude Code integration
full_prompt = f"{DEVELOPER_SYSTEM_PROMPT}\n\n{request.prompt}"
# Generate response
response = model.generate_content(full_prompt)
# Handle response based on finish reason
if response.candidates and response.candidates[0].content.parts:
text = response.candidates[0].content.parts[0].text
else:
# Handle safety filters or other issues
finish_reason = (
response.candidates[0].finish_reason
if response.candidates
else "Unknown"
)
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
return [TextContent(type="text", text=text)]
except Exception as e:
return [
TextContent(type="text", text=f"Error calling Gemini API: {str(e)}")
]
elif name == "analyze_code":
# Validate request
request_analysis = CodeAnalysisRequest(**arguments)
# Check that we have either files or code
if not request_analysis.files and not request_analysis.code:
return [
TextContent(
type="text",
text="Error: Must provide either 'files' or 'code' parameter",
)
]
try:
# Prepare code context - always use non-verbose mode for Claude Code compatibility
code_context, summary = prepare_code_context(
request_analysis.files, request_analysis.code
)
# Count approximate tokens (rough estimate: 1 token ≈ 4 characters)
estimated_tokens = len(code_context) // 4
if estimated_tokens > MAX_CONTEXT_TOKENS:
return [
TextContent(
type="text",
text=f"Error: Code context too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens.",
)
]
# Use the specified model with optimized settings for code analysis
model_name = request_analysis.model or DEFAULT_MODEL
temperature = (
request_analysis.temperature
if request_analysis.temperature is not None
else 0.2
)
max_tokens = (
request_analysis.max_tokens
if request_analysis.max_tokens is not None
else 8192
)
model = genai.GenerativeModel(
model_name=model_name,
generation_config={
"temperature": temperature,
"max_output_tokens": max_tokens,
"candidate_count": 1,
},
)
# Prepare the full prompt with enhanced developer context and clear structure
system_prompt = request_analysis.system_prompt or DEVELOPER_SYSTEM_PROMPT
full_prompt = f"""{system_prompt}
=== USER REQUEST ===
{request_analysis.question}
=== END USER REQUEST ===
=== CODE TO ANALYZE ===
{code_context}
=== END CODE TO ANALYZE ===
Please analyze the code above and respond to the user's request. The code files are clearly \
marked with their paths and content boundaries."""
# Generate response
response = model.generate_content(full_prompt)
# Handle response
if response.candidates and response.candidates[0].content.parts:
text = response.candidates[0].content.parts[0].text
else:
finish_reason = (
response.candidates[0].finish_reason
if response.candidates
else "Unknown"
)
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
# Create a brief summary for terminal display
if request_analysis.files or request_analysis.code:
# Create a very brief summary for terminal
brief_summary_parts = []
if request_analysis.files:
brief_summary_parts.append(
f"Analyzing {len(request_analysis.files)} file(s)"
)
if request_analysis.code:
code_preview = (
request_analysis.code[:20] + "..."
if len(request_analysis.code) > 20
else request_analysis.code
)
brief_summary_parts.append(f"Direct code: {code_preview}")
brief_summary = " | ".join(brief_summary_parts)
response_text = f"{brief_summary}\n\nGemini's Analysis:\n{text}"
else:
response_text = text
return [TextContent(type="text", text=response_text)]
except Exception as e:
return [TextContent(type="text", text=f"Error analyzing code: {str(e)}")]
elif name == "list_models":
try:
# List available models
models = []
for model_info in genai.list_models():
if (
hasattr(model_info, "supported_generation_methods")
and "generateContent" in model_info.supported_generation_methods
):
models.append(
{
"name": model_info.name,
"display_name": getattr(
model_info, "display_name", "Unknown"
),
"description": getattr(
model_info, "description", "No description"
),
"is_default": model_info.name.endswith(DEFAULT_MODEL),
}
)
return [TextContent(type="text", text=json.dumps(models, indent=2))]
except Exception as e:
return [TextContent(type="text", text=f"Error listing models: {str(e)}")]
elif name == "get_version":
# Return version and metadata information
version_info = {
"version": __version__,
"updated": __updated__,
"author": __author__,
"default_model": DEFAULT_MODEL,
"max_context_tokens": f"{MAX_CONTEXT_TOKENS:,}",
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"server_started": datetime.now().isoformat(),
}
return [
TextContent(
type="text",
text=f"""Gemini MCP Server v{__version__}
Updated: {__updated__}
Author: {__author__}
Configuration:
- Default Model: {DEFAULT_MODEL}
- Max Context: {MAX_CONTEXT_TOKENS:,} tokens
- Python: {version_info['python_version']}
- Started: {version_info['server_started']}
For updates, visit: https://github.com/BeehiveInnovations/gemini-mcp-server""",
)
]
elif name == "analyze_file":
# Validate request
request_file = FileAnalysisRequest(**arguments)
try:
# Prepare code context from files
code_context, summary = prepare_code_context(request_file.files, None)
# Count approximate tokens
estimated_tokens = len(code_context) // 4
if estimated_tokens > MAX_CONTEXT_TOKENS:
return [
TextContent(
type="text",
text=f"Error: File content too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens.",
)
]
# Use the specified model with optimized settings
model_name = request_file.model or DEFAULT_MODEL
temperature = (
request_file.temperature if request_file.temperature is not None else 0.2
)
max_tokens = request_file.max_tokens if request_file.max_tokens is not None else 8192
model = genai.GenerativeModel(
model_name=model_name,
generation_config={
"temperature": temperature,
"max_output_tokens": max_tokens,
"candidate_count": 1,
},
)
# Prepare prompt
system_prompt = request_file.system_prompt or DEVELOPER_SYSTEM_PROMPT
full_prompt = f"""{system_prompt}
=== USER REQUEST ===
{request_file.question}
=== END USER REQUEST ===
=== FILES TO ANALYZE ===
{code_context}
=== END FILES ===
Please analyze the files above and respond to the user's request."""
# Generate response
response = model.generate_content(full_prompt)
# Handle response
if response.candidates and response.candidates[0].content.parts:
text = response.candidates[0].content.parts[0].text
else:
finish_reason = (
response.candidates[0].finish_reason
if response.candidates
else "Unknown"
)
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
# Create a brief summary for terminal
brief_summary = f"Analyzing {len(request_file.files)} file(s)"
response_text = f"{brief_summary}\n\nGemini's Analysis:\n{text}"
return [TextContent(type="text", text=response_text)]
except Exception as e:
return [TextContent(type="text", text=f"Error analyzing files: {str(e)}")]
elif name == "extended_think":
# Validate request
request_think = ExtendedThinkRequest(**arguments)
try:
# Prepare context parts
context_parts = [
f"=== CLAUDE'S ANALYSIS ===\n{request_think.thought_process}\n=== END CLAUDE'S ANALYSIS ==="
]
if request_think.context:
context_parts.append(
f"\n=== ADDITIONAL CONTEXT ===\n{request_think.context}\n=== END CONTEXT ==="
)
# Add file contents if provided
if request_think.files:
file_context, _ = prepare_code_context(request_think.files, None)
context_parts.append(
f"\n=== REFERENCE FILES ===\n{file_context}\n=== END FILES ==="
)
full_context = "\n".join(context_parts)
# Check token limits
estimated_tokens = len(full_context) // 4
if estimated_tokens > MAX_CONTEXT_TOKENS:
return [
TextContent(
type="text",
text=f"Error: Context too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens.",
)
]
# Use the specified model with creative settings
model_name = request_think.model or DEFAULT_MODEL
temperature = (
request_think.temperature if request_think.temperature is not None else 0.7
)
max_tokens = request_think.max_tokens if request_think.max_tokens is not None else 8192
model = genai.GenerativeModel(
model_name=model_name,
generation_config={
"temperature": temperature,
"max_output_tokens": max_tokens,
"candidate_count": 1,
},
)
# Prepare prompt with focus area if specified
system_prompt = request_think.system_prompt or EXTENDED_THINKING_PROMPT
focus_instruction = ""
if request_think.focus:
focus_instruction = f"\n\nFOCUS AREA: Please pay special attention to {request_think.focus} aspects."
full_prompt = f"""{system_prompt}{focus_instruction}
{full_context}
Build upon Claude's analysis with deeper insights, alternative approaches, and critical evaluation."""
# Generate response
response = model.generate_content(full_prompt)
# Handle response
if response.candidates and response.candidates[0].content.parts:
text = response.candidates[0].content.parts[0].text
else:
finish_reason = (
response.candidates[0].finish_reason
if response.candidates
else "Unknown"
)
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
# Create response with clear attribution
response_text = f"Extended Analysis by Gemini:\n\n{text}"
return [TextContent(type="text", text=response_text)]
except Exception as e:
return [
TextContent(type="text", text=f"Error in extended thinking: {str(e)}")
]
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
"""Main entry point for the server"""
# Configure Gemini API
configure_gemini()
# Run the server using stdio transport
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="gemini", server_version="2.0.0", capabilities={"tools": {}}
),
)
if __name__ == "__main__":
asyncio.run(main())

17
prompts/__init__.py Normal file
View File

@@ -0,0 +1,17 @@
"""
System prompts for Gemini tools
"""
from .tool_prompts import (
THINK_DEEPER_PROMPT,
REVIEW_CODE_PROMPT,
DEBUG_ISSUE_PROMPT,
ANALYZE_PROMPT,
)
__all__ = [
"THINK_DEEPER_PROMPT",
"REVIEW_CODE_PROMPT",
"DEBUG_ISSUE_PROMPT",
"ANALYZE_PROMPT",
]

95
prompts/tool_prompts.py Normal file
View File

@@ -0,0 +1,95 @@
"""
System prompts for each tool
"""
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.
Your role is to:
1. Build upon Claude's thinking - identify gaps, extend ideas, and suggest alternatives
2. Challenge assumptions constructively and identify potential issues
3. Provide concrete, actionable insights that complement Claude's analysis
4. Focus on aspects Claude might have missed or couldn't fully explore
5. Suggest implementation strategies and architectural improvements
Key areas to consider:
- Edge cases and failure modes Claude might have overlooked
- Performance implications at scale
- Security vulnerabilities or attack vectors
- Maintainability and technical debt considerations
- Alternative approaches or design patterns
- Integration challenges with existing systems
- Testing strategies for complex scenarios
Be direct and technical. Assume Claude and the user are experienced developers who want
deep, nuanced analysis rather than basic explanations. Your goal is to be the perfect
development partner that extends Claude's capabilities."""
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 review approach:
1. Identify issues in order of severity (Critical > High > Medium > Low)
2. Provide specific, actionable fixes with code examples
3. Consider security vulnerabilities, performance issues, and maintainability
4. Acknowledge good practices when you see them
5. Be constructive but thorough - don't sugarcoat serious issues
Review categories:
- 🔴 CRITICAL: Security vulnerabilities, data loss risks, crashes
- 🟠 HIGH: Bugs, performance issues, bad practices
- 🟡 MEDIUM: Code smells, maintainability issues
- 🟢 LOW: Style issues, minor improvements
Format each issue as:
[SEVERITY] File:Line - Issue description
→ Fix: Specific solution with code example
Also provide:
- Summary of overall code quality
- Top 3 priority fixes
- Positive aspects worth preserving"""
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.
Your debugging approach:
1. Analyze the error context and symptoms
2. Identify the most likely root causes
3. Trace through the code execution path
4. Consider environmental factors
5. Provide step-by-step solutions
For each issue:
- Identify the root cause
- Explain why it's happening
- Provide immediate fixes
- Suggest long-term solutions
- Identify related issues that might arise
Format your response as:
1. ROOT CAUSE: Clear explanation
2. IMMEDIATE FIX: Code/steps to resolve now
3. PROPER SOLUTION: Long-term fix
4. PREVENTION: How to avoid this in the future"""
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 analysis should:
1. Understand the code's purpose and architecture
2. Identify patterns and anti-patterns
3. Assess code quality and maintainability
4. Find potential issues or improvements
5. Provide actionable insights
Focus on:
- Code structure and organization
- Design patterns and architectural decisions
- Performance characteristics
- Security considerations
- Testing coverage and quality
- Documentation completeness
Be thorough but concise. Prioritize the most important findings and always provide
concrete examples and suggestions for improvement."""

View File

@@ -1,6 +1,7 @@
mcp>=1.0.0
google-generativeai>=0.8.0
python-dotenv>=1.0.0
pydantic>=2.0.0
# Development dependencies
pytest>=7.4.0

271
server.py Normal file
View File

@@ -0,0 +1,271 @@
"""
Gemini MCP Server - Main server implementation
"""
import os
import sys
import asyncio
import logging
from datetime import datetime
from typing import List, Dict, Any
import google.generativeai as genai
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from mcp.server.models import InitializationOptions
from config import (
__version__,
__updated__,
__author__,
DEFAULT_MODEL,
MAX_CONTEXT_TOKENS,
)
from tools import ThinkDeeperTool, ReviewCodeTool, DebugIssueTool, AnalyzeTool
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Create the MCP server instance
server: Server = Server("gemini-server")
# Initialize tools
TOOLS = {
"think_deeper": ThinkDeeperTool(),
"review_code": ReviewCodeTool(),
"debug_issue": DebugIssueTool(),
"analyze": AnalyzeTool(),
}
def configure_gemini():
"""Configure Gemini API with the provided API key"""
api_key = os.getenv("GEMINI_API_KEY")
if not api_key:
raise ValueError(
"GEMINI_API_KEY environment variable is required. "
"Please set it with your Gemini API key."
)
genai.configure(api_key=api_key)
logger.info("Gemini API configured successfully")
@server.list_tools()
async def handle_list_tools() -> List[Tool]:
"""List all available tools with verbose descriptions"""
tools = []
for tool in TOOLS.values():
tools.append(
Tool(
name=tool.name,
description=tool.description,
inputSchema=tool.get_input_schema(),
)
)
# Add utility tools
tools.extend(
[
Tool(
name="chat",
description=(
"GENERAL CHAT - Have a conversation with Gemini about any development topic. "
"Use for explanations, brainstorming, or general questions. "
"Triggers: 'ask gemini', 'explain', 'what is', 'how do I'."
),
inputSchema={
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "Your question or topic",
},
"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,
},
},
"required": ["prompt"],
},
),
Tool(
name="list_models",
description=(
"LIST AVAILABLE MODELS - Show all Gemini models you can use. "
"Lists model names, descriptions, and which one is the default."
),
inputSchema={"type": "object", "properties": {}},
),
Tool(
name="get_version",
description=(
"VERSION & CONFIGURATION - Get server version, configuration details, "
"and list of available tools. Useful for debugging and understanding capabilities."
),
inputSchema={"type": "object", "properties": {}},
),
]
)
return tools
@server.call_tool()
async def handle_call_tool(
name: str, arguments: Dict[str, Any]
) -> List[TextContent]:
"""Handle tool execution requests"""
# Handle dynamic tools
if name in TOOLS:
tool = TOOLS[name]
return await tool.execute(arguments)
# Handle static tools
elif name == "chat":
return await handle_chat(arguments)
elif name == "list_models":
return await handle_list_models()
elif name == "get_version":
return await handle_get_version()
else:
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def handle_chat(arguments: Dict[str, Any]) -> List[TextContent]:
"""Handle general chat requests"""
from utils import read_files
from config import TEMPERATURE_BALANCED
prompt = arguments.get("prompt", "")
context_files = arguments.get("context_files", [])
temperature = arguments.get("temperature", TEMPERATURE_BALANCED)
# Build context if files provided
full_prompt = prompt
if context_files:
file_content, _ = read_files(context_files)
full_prompt = f"{prompt}\n\n=== CONTEXT FILES ===\n{file_content}\n=== END CONTEXT ==="
try:
model = genai.GenerativeModel(
model_name=DEFAULT_MODEL,
generation_config={
"temperature": temperature,
"max_output_tokens": 8192,
"candidate_count": 1,
},
)
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]:
"""List available Gemini models"""
try:
import json
models = []
for model_info in genai.list_models():
if (
hasattr(model_info, "supported_generation_methods")
and "generateContent"
in model_info.supported_generation_methods
):
models.append(
{
"name": model_info.name,
"display_name": getattr(
model_info, "display_name", "Unknown"
),
"description": getattr(
model_info, "description", "No description"
),
"is_default": model_info.name.endswith(DEFAULT_MODEL),
}
)
return [TextContent(type="text", text=json.dumps(models, indent=2))]
except Exception as e:
return [
TextContent(type="text", text=f"Error listing models: {str(e)}")
]
async def handle_get_version() -> List[TextContent]:
"""Get version and configuration information"""
version_info = {
"version": __version__,
"updated": __updated__,
"author": __author__,
"default_model": DEFAULT_MODEL,
"max_context_tokens": f"{MAX_CONTEXT_TOKENS:,}",
"python_version": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
"server_started": datetime.now().isoformat(),
"available_tools": list(TOOLS.keys())
+ ["chat", "list_models", "get_version"],
}
text = f"""Gemini MCP Server v{__version__}
Updated: {__updated__}
Author: {__author__}
Configuration:
- Default Model: {DEFAULT_MODEL}
- Max Context: {MAX_CONTEXT_TOKENS:,} tokens
- Python: {version_info['python_version']}
- Started: {version_info['server_started']}
Available Tools:
{chr(10).join(f" - {tool}" for tool in version_info['available_tools'])}
For updates, visit: https://github.com/BeehiveInnovations/gemini-mcp-server"""
return [TextContent(type="text", text=text)]
async def main():
"""Main entry point for the server"""
# Configure Gemini API
configure_gemini()
# Run the server using stdio transport
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="gemini",
server_version=__version__,
capabilities={"tools": {}},
),
)
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -2,7 +2,7 @@
Setup configuration for Gemini MCP Server
"""
from setuptools import setup, find_packages
from setuptools import setup
from pathlib import Path
# Read README for long description
@@ -46,4 +46,4 @@ setup(
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
],
)
)

49
tests/test_config.py Normal file
View File

@@ -0,0 +1,49 @@
"""
Tests for configuration
"""
from config import (
__version__,
__updated__,
__author__,
DEFAULT_MODEL,
MAX_CONTEXT_TOKENS,
TEMPERATURE_ANALYTICAL,
TEMPERATURE_BALANCED,
TEMPERATURE_CREATIVE,
TOOL_TRIGGERS,
)
class TestConfig:
"""Test configuration values"""
def test_version_info(self):
"""Test version information"""
assert __version__ == "2.4.0"
assert __author__ == "Fahad Gilani"
assert __updated__ == "2025-06-08"
def test_model_config(self):
"""Test model configuration"""
assert DEFAULT_MODEL == "gemini-2.5-pro-preview-06-05"
assert MAX_CONTEXT_TOKENS == 1_000_000
def test_temperature_defaults(self):
"""Test temperature constants"""
assert TEMPERATURE_ANALYTICAL == 0.2
assert TEMPERATURE_BALANCED == 0.5
assert TEMPERATURE_CREATIVE == 0.7
def test_tool_triggers(self):
"""Test tool trigger phrases"""
assert "think_deeper" in TOOL_TRIGGERS
assert "review_code" in TOOL_TRIGGERS
assert "debug_issue" in TOOL_TRIGGERS
assert "analyze" in TOOL_TRIGGERS
# Check some specific triggers
assert "ultrathink" in TOOL_TRIGGERS["think_deeper"]
assert "extended thinking" in TOOL_TRIGGERS["think_deeper"]
assert "find bugs" in TOOL_TRIGGERS["review_code"]
assert "root cause" in TOOL_TRIGGERS["debug_issue"]

View File

@@ -1,352 +0,0 @@
"""
Unit tests for Gemini MCP Server
"""
import pytest
import json
from unittest.mock import Mock, patch, AsyncMock
from pathlib import Path
import sys
import os
# Add parent directory to path for imports in a cross-platform way
parent_dir = Path(__file__).resolve().parent.parent
if str(parent_dir) not in sys.path:
sys.path.insert(0, str(parent_dir))
from gemini_server import (
GeminiChatRequest,
CodeAnalysisRequest,
read_file_content,
prepare_code_context,
handle_list_tools,
handle_call_tool,
DEVELOPER_SYSTEM_PROMPT,
DEFAULT_MODEL,
)
class TestModels:
"""Test request models"""
def test_gemini_chat_request_defaults(self):
"""Test GeminiChatRequest with default values"""
request = GeminiChatRequest(prompt="Test prompt")
assert request.prompt == "Test prompt"
assert request.system_prompt is None
assert request.max_tokens == 8192
assert request.temperature == 0.5
assert request.model == DEFAULT_MODEL
def test_gemini_chat_request_custom(self):
"""Test GeminiChatRequest with custom values"""
request = GeminiChatRequest(
prompt="Test prompt",
system_prompt="Custom system",
max_tokens=4096,
temperature=0.8,
model="custom-model",
)
assert request.system_prompt == "Custom system"
assert request.max_tokens == 4096
assert request.temperature == 0.8
assert request.model == "custom-model"
def test_code_analysis_request_defaults(self):
"""Test CodeAnalysisRequest with default values"""
request = CodeAnalysisRequest(question="Analyze this")
assert request.question == "Analyze this"
assert request.files is None
assert request.code is None
assert request.max_tokens == 8192
assert request.temperature == 0.2
assert request.model == DEFAULT_MODEL
class TestFileOperations:
"""Test file reading and context preparation"""
def test_read_file_content_success(self, tmp_path):
"""Test successful file reading"""
test_file = tmp_path / "test.py"
test_file.write_text("def hello():\n return 'world'", encoding="utf-8")
content = read_file_content(str(test_file))
assert "--- BEGIN FILE:" in content
assert "--- END FILE:" in content
assert "def hello():" in content
assert "return 'world'" in content
def test_read_file_content_not_found(self):
"""Test reading non-existent file"""
# Use a path that's guaranteed not to exist on any platform
nonexistent_path = os.path.join(
os.path.sep, "nonexistent_dir_12345", "nonexistent_file.py"
)
content = read_file_content(nonexistent_path)
assert "--- FILE NOT FOUND:" in content
assert "Error: File does not exist" in content
def test_read_file_content_directory(self, tmp_path):
"""Test reading a directory instead of file"""
content = read_file_content(str(tmp_path))
assert "--- NOT A FILE:" in content
assert "Error: Path is not a file" in content
def test_prepare_code_context_with_files(self, tmp_path):
"""Test preparing context from files"""
file1 = tmp_path / "file1.py"
file1.write_text("print('file1')", encoding="utf-8")
file2 = tmp_path / "file2.py"
file2.write_text("print('file2')", encoding="utf-8")
context, summary = prepare_code_context([str(file1), str(file2)], None)
assert "--- BEGIN FILE:" in context
assert "file1.py" in context
assert "file2.py" in context
assert "print('file1')" in context
assert "print('file2')" in context
assert "--- END FILE:" in context
assert "Analyzing 2 file(s)" in summary
assert "bytes)" in summary
def test_prepare_code_context_with_code(self):
"""Test preparing context from direct code"""
code = "def test():\n pass"
context, summary = prepare_code_context(None, code)
assert "--- BEGIN DIRECT CODE ---" in context
assert "--- END DIRECT CODE ---" in context
assert code in context
assert "Direct code provided" in summary
def test_prepare_code_context_mixed(self, tmp_path):
"""Test preparing context from both files and code"""
test_file = tmp_path / "test.py"
test_file.write_text("# From file", encoding="utf-8")
code = "# Direct code"
context, summary = prepare_code_context([str(test_file)], code)
assert "# From file" in context
assert "# Direct code" in context
assert "Analyzing 1 file(s)" in summary
assert "Direct code provided" in summary
class TestToolHandlers:
"""Test MCP tool handlers"""
@pytest.mark.asyncio
async def test_handle_list_tools(self):
"""Test listing available tools"""
tools = await handle_list_tools()
assert len(tools) == 6
tool_names = [tool.name for tool in tools]
assert "chat" in tool_names
assert "analyze_code" in tool_names
assert "list_models" in tool_names
assert "get_version" in tool_names
assert "analyze_file" in tool_names
assert "extended_think" in tool_names
@pytest.mark.asyncio
async def test_handle_call_tool_unknown(self):
"""Test calling unknown tool"""
result = await handle_call_tool("unknown_tool", {})
assert len(result) == 1
assert "Unknown tool" in result[0].text
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_chat_success(self, mock_model):
"""Test successful chat tool call"""
# Mock the response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [Mock(text="Test response")]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool(
"chat", {"prompt": "Test prompt", "temperature": 0.5}
)
assert len(result) == 1
assert result[0].text == "Test response"
# Verify model was called with correct parameters
mock_model.assert_called_once()
call_args = mock_model.call_args[1]
assert call_args["model_name"] == DEFAULT_MODEL
assert call_args["generation_config"]["temperature"] == 0.5
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_chat_with_developer_prompt(self, mock_model):
"""Test chat tool uses developer prompt when no system prompt provided"""
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [Mock(text="Response")]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
await handle_call_tool("chat", {"prompt": "Test"})
# Check that developer prompt was included
call_args = mock_instance.generate_content.call_args[0][0]
assert DEVELOPER_SYSTEM_PROMPT in call_args
@pytest.mark.asyncio
async def test_handle_call_tool_analyze_code_no_input(self):
"""Test analyze_code with no files or code"""
result = await handle_call_tool("analyze_code", {"question": "Analyze what?"})
assert len(result) == 1
assert "Must provide either 'files' or 'code'" in result[0].text
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_analyze_code_success(self, mock_model, tmp_path):
"""Test successful code analysis"""
# Create test file
test_file = tmp_path / "test.py"
test_file.write_text("def hello(): pass", encoding="utf-8")
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [Mock(text="Analysis result")]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool(
"analyze_code", {"files": [str(test_file)], "question": "Analyze this"}
)
assert len(result) == 1
# Check that the response contains both summary and Gemini's response
response_text = result[0].text
assert "Analyzing 1 file(s)" in response_text
assert "Gemini's Analysis:" in response_text
assert "Analysis result" in response_text
@pytest.mark.asyncio
@patch("google.generativeai.list_models")
async def test_handle_call_tool_list_models(self, mock_list_models):
"""Test listing models"""
# Mock model data
mock_model = Mock()
mock_model.name = "test-model"
mock_model.display_name = "Test Model"
mock_model.description = "A test model"
mock_model.supported_generation_methods = ["generateContent"]
mock_list_models.return_value = [mock_model]
result = await handle_call_tool("list_models", {})
assert len(result) == 1
models = json.loads(result[0].text)
assert len(models) == 1
assert models[0]["name"] == "test-model"
assert models[0]["is_default"] == False
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_analyze_file_success(self, mock_model, tmp_path):
"""Test successful file analysis with analyze_file tool"""
# Create test file
test_file = tmp_path / "test.py"
test_file.write_text("def hello(): pass", encoding="utf-8")
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [Mock(text="File analysis result")]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool(
"analyze_file", {"files": [str(test_file)], "question": "Analyze this file"}
)
assert len(result) == 1
response_text = result[0].text
assert "Analyzing 1 file(s)" in response_text
assert "Gemini's Analysis:" in response_text
assert "File analysis result" in response_text
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_extended_think_success(self, mock_model):
"""Test successful extended thinking"""
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Extended thinking result")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool(
"extended_think",
{
"thought_process": "Claude's analysis of the problem...",
"context": "Building a distributed system",
"focus": "performance",
},
)
assert len(result) == 1
response_text = result[0].text
assert "Extended Analysis by Gemini:" in response_text
assert "Extended thinking result" in response_text
class TestErrorHandling:
"""Test error handling scenarios"""
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_chat_api_error(self, mock_model):
"""Test handling API errors in chat"""
mock_instance = Mock()
mock_instance.generate_content.side_effect = Exception("API Error")
mock_model.return_value = mock_instance
result = await handle_call_tool("chat", {"prompt": "Test"})
assert len(result) == 1
assert "Error calling Gemini API" in result[0].text
assert "API Error" in result[0].text
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_call_tool_chat_blocked_response(self, mock_model):
"""Test handling blocked responses"""
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = []
mock_response.candidates[0].finish_reason = 2
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool("chat", {"prompt": "Test"})
assert len(result) == 1
assert "Response blocked or incomplete" in result[0].text
assert "Finish reason: 2" in result[0].text
if __name__ == "__main__":
pytest.main([__file__, "-v"])

View File

@@ -1,48 +0,0 @@
"""
Test that imports work correctly when package is installed
This helps verify CI setup is correct
"""
import pytest
def test_direct_import():
"""Test that gemini_server can be imported directly"""
try:
import gemini_server
assert hasattr(gemini_server, "GeminiChatRequest")
assert hasattr(gemini_server, "CodeAnalysisRequest")
assert hasattr(gemini_server, "handle_list_tools")
assert hasattr(gemini_server, "handle_call_tool")
except ImportError as e:
pytest.fail(f"Failed to import gemini_server: {e}")
def test_from_import():
"""Test that specific items can be imported from gemini_server"""
try:
from gemini_server import (
GeminiChatRequest,
CodeAnalysisRequest,
DEFAULT_MODEL,
DEVELOPER_SYSTEM_PROMPT,
)
assert GeminiChatRequest is not None
assert CodeAnalysisRequest is not None
assert isinstance(DEFAULT_MODEL, str)
assert isinstance(DEVELOPER_SYSTEM_PROMPT, str)
except ImportError as e:
pytest.fail(f"Failed to import from gemini_server: {e}")
def test_google_generativeai_import():
"""Test that google.generativeai can be imported"""
try:
import google.generativeai as genai
assert hasattr(genai, "GenerativeModel")
assert hasattr(genai, "configure")
except ImportError as e:
pytest.fail(f"Failed to import google.generativeai: {e}")

96
tests/test_server.py Normal file
View File

@@ -0,0 +1,96 @@
"""
Tests for the main server functionality
"""
import pytest
import json
from unittest.mock import Mock, patch
from server import handle_list_tools, handle_call_tool
class TestServerTools:
"""Test server tool handling"""
@pytest.mark.asyncio
async def test_handle_list_tools(self):
"""Test listing all available tools"""
tools = await handle_list_tools()
tool_names = [tool.name for tool in tools]
# Check all core tools are present
assert "think_deeper" in tool_names
assert "review_code" in tool_names
assert "debug_issue" in tool_names
assert "analyze" in tool_names
assert "chat" in tool_names
assert "list_models" in tool_names
assert "get_version" in tool_names
# Should have exactly 7 tools
assert len(tools) == 7
# Check descriptions are verbose
for tool in tools:
assert (
len(tool.description) > 50
) # All should have detailed descriptions
@pytest.mark.asyncio
async def test_handle_call_tool_unknown(self):
"""Test calling an unknown tool"""
result = await handle_call_tool("unknown_tool", {})
assert len(result) == 1
assert "Unknown tool: unknown_tool" in result[0].text
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_handle_chat(self, mock_model):
"""Test chat functionality"""
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Chat response")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await handle_call_tool("chat", {"prompt": "Hello Gemini"})
assert len(result) == 1
assert result[0].text == "Chat response"
@pytest.mark.asyncio
@patch("google.generativeai.list_models")
async def test_handle_list_models(self, mock_list_models):
"""Test listing models"""
# Mock model data
mock_model = Mock()
mock_model.name = "models/gemini-2.5-pro-preview-06-05"
mock_model.display_name = "Gemini 2.5 Pro"
mock_model.description = "Latest Gemini model"
mock_model.supported_generation_methods = ["generateContent"]
mock_list_models.return_value = [mock_model]
result = await handle_call_tool("list_models", {})
assert len(result) == 1
models = json.loads(result[0].text)
assert len(models) == 1
assert models[0]["name"] == "models/gemini-2.5-pro-preview-06-05"
assert models[0]["is_default"] is True
@pytest.mark.asyncio
async def test_handle_get_version(self):
"""Test getting version info"""
result = await handle_call_tool("get_version", {})
assert len(result) == 1
response = result[0].text
assert "Gemini MCP Server v2.4.0" in response
assert "Available Tools:" in response
assert "think_deeper" in response

202
tests/test_tools.py Normal file
View File

@@ -0,0 +1,202 @@
"""
Tests for individual tool implementations
"""
import pytest
from unittest.mock import Mock, patch
from tools import ThinkDeeperTool, ReviewCodeTool, DebugIssueTool, AnalyzeTool
class TestThinkDeeperTool:
"""Test the think_deeper tool"""
@pytest.fixture
def tool(self):
return ThinkDeeperTool()
def test_tool_metadata(self, tool):
"""Test tool metadata"""
assert tool.get_name() == "think_deeper"
assert "EXTENDED THINKING" in tool.get_description()
assert tool.get_default_temperature() == 0.7
schema = tool.get_input_schema()
assert "current_analysis" in schema["properties"]
assert schema["required"] == ["current_analysis"]
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_execute_success(self, mock_model, tool):
"""Test successful execution"""
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Extended analysis")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await tool.execute(
{
"current_analysis": "Initial analysis",
"problem_context": "Building a cache",
"focus_areas": ["performance", "scalability"],
}
)
assert len(result) == 1
assert "Extended Analysis by Gemini:" in result[0].text
assert "Extended analysis" in result[0].text
class TestReviewCodeTool:
"""Test the review_code tool"""
@pytest.fixture
def tool(self):
return ReviewCodeTool()
def test_tool_metadata(self, tool):
"""Test tool metadata"""
assert tool.get_name() == "review_code"
assert "PROFESSIONAL CODE REVIEW" in tool.get_description()
assert tool.get_default_temperature() == 0.2
schema = tool.get_input_schema()
assert "files" in schema["properties"]
assert schema["required"] == ["files"]
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_execute_with_review_type(self, mock_model, tool, tmp_path):
"""Test execution with specific review type"""
# Create test file
test_file = tmp_path / "test.py"
test_file.write_text("def insecure(): pass", encoding="utf-8")
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Security issues found")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await tool.execute(
{
"files": [str(test_file)],
"review_type": "security",
"focus_on": "authentication",
}
)
assert len(result) == 1
assert "Code Review (SECURITY)" in result[0].text
assert "Focus: authentication" in result[0].text
assert "Security issues found" in result[0].text
class TestDebugIssueTool:
"""Test the debug_issue tool"""
@pytest.fixture
def tool(self):
return DebugIssueTool()
def test_tool_metadata(self, tool):
"""Test tool metadata"""
assert tool.get_name() == "debug_issue"
assert "DEBUG & ROOT CAUSE ANALYSIS" in tool.get_description()
assert tool.get_default_temperature() == 0.2
schema = tool.get_input_schema()
assert "error_description" in schema["properties"]
assert schema["required"] == ["error_description"]
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_execute_with_context(self, mock_model, tool):
"""Test execution with error context"""
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Root cause: race condition")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await tool.execute(
{
"error_description": "Test fails intermittently",
"error_context": "AssertionError in test_async",
"previous_attempts": "Added sleep, still fails",
}
)
assert len(result) == 1
assert "Debug Analysis" in result[0].text
assert "Root cause: race condition" in result[0].text
class TestAnalyzeTool:
"""Test the analyze tool"""
@pytest.fixture
def tool(self):
return AnalyzeTool()
def test_tool_metadata(self, tool):
"""Test tool metadata"""
assert tool.get_name() == "analyze"
assert "ANALYZE FILES & CODE" in tool.get_description()
assert tool.get_default_temperature() == 0.2
schema = tool.get_input_schema()
assert "files" in schema["properties"]
assert "question" in schema["properties"]
assert set(schema["required"]) == {"files", "question"}
@pytest.mark.asyncio
@patch("google.generativeai.GenerativeModel")
async def test_execute_with_analysis_type(
self, mock_model, tool, tmp_path
):
"""Test execution with specific analysis type"""
# Create test file
test_file = tmp_path / "module.py"
test_file.write_text("class Service: pass", encoding="utf-8")
# Mock response
mock_response = Mock()
mock_response.candidates = [Mock()]
mock_response.candidates[0].content.parts = [
Mock(text="Architecture analysis")
]
mock_instance = Mock()
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = await tool.execute(
{
"files": [str(test_file)],
"question": "What's the structure?",
"analysis_type": "architecture",
"output_format": "summary",
}
)
assert len(result) == 1
assert "ARCHITECTURE Analysis" in result[0].text
assert "Analyzed 1 file(s)" in result[0].text
assert "Architecture analysis" in result[0].text

91
tests/test_utils.py Normal file
View File

@@ -0,0 +1,91 @@
"""
Tests for utility functions
"""
from utils import (
read_file_content,
read_files,
estimate_tokens,
check_token_limit,
)
class TestFileUtils:
"""Test file reading utilities"""
def test_read_file_content_success(self, tmp_path):
"""Test successful file reading"""
test_file = tmp_path / "test.py"
test_file.write_text(
"def hello():\n return 'world'", encoding="utf-8"
)
content = read_file_content(str(test_file))
assert "--- BEGIN FILE:" in content
assert "--- END FILE:" in content
assert "def hello():" in content
assert "return 'world'" in content
def test_read_file_content_not_found(self):
"""Test reading non-existent file"""
content = read_file_content("/nonexistent/file.py")
assert "--- FILE NOT FOUND:" in content
assert "Error: File does not exist" in content
def test_read_file_content_directory(self, tmp_path):
"""Test reading a directory"""
content = read_file_content(str(tmp_path))
assert "--- NOT A FILE:" in content
assert "Error: Path is not a file" in content
def test_read_files_multiple(self, tmp_path):
"""Test reading multiple files"""
file1 = tmp_path / "file1.py"
file1.write_text("print('file1')", encoding="utf-8")
file2 = tmp_path / "file2.py"
file2.write_text("print('file2')", encoding="utf-8")
content, summary = read_files([str(file1), str(file2)])
assert "--- BEGIN FILE:" in content
assert "file1.py" in content
assert "file2.py" in content
assert "print('file1')" in content
assert "print('file2')" in content
assert "Reading 2 file(s)" in summary
def test_read_files_with_code(self):
"""Test reading with direct code"""
code = "def test():\n pass"
content, summary = read_files([], code)
assert "--- BEGIN DIRECT CODE ---" in content
assert "--- END DIRECT CODE ---" in content
assert code in content
assert "Direct code:" in summary
class TestTokenUtils:
"""Test token counting utilities"""
def test_estimate_tokens(self):
"""Test token estimation"""
# Rough estimate: 1 token ≈ 4 characters
text = "a" * 400 # 400 characters
assert estimate_tokens(text) == 100
def test_check_token_limit_within(self):
"""Test token limit check - within limit"""
text = "a" * 4000 # 1000 tokens
within_limit, tokens = check_token_limit(text)
assert within_limit is True
assert tokens == 1000
def test_check_token_limit_exceeded(self):
"""Test token limit check - exceeded"""
text = "a" * 5_000_000 # 1.25M tokens
within_limit, tokens = check_token_limit(text)
assert within_limit is False
assert tokens == 1_250_000

View File

@@ -1,105 +0,0 @@
"""
Test verbose output functionality
"""
import pytest
from pathlib import Path
import sys
# Add parent directory to path for imports
parent_dir = Path(__file__).resolve().parent.parent
if str(parent_dir) not in sys.path:
sys.path.insert(0, str(parent_dir))
from gemini_server import prepare_code_context
class TestNewFormattingBehavior:
"""Test the improved formatting behavior"""
def test_file_formatting_for_gemini(self, tmp_path):
"""Test that files are properly formatted for Gemini"""
test_file = tmp_path / "test.py"
content = "def hello():\n return 'world'"
test_file.write_text(content, encoding="utf-8")
context, summary = prepare_code_context([str(test_file)], None)
# Context should have clear markers for Gemini
assert "--- BEGIN FILE:" in context
assert "--- END FILE:" in context
assert str(test_file) in context
assert content in context
# Summary should be concise for terminal
assert "Analyzing 1 file(s)" in summary
assert "bytes)" in summary
assert len(summary) < len(context) # Summary much smaller than full context
def test_terminal_summary_shows_preview(self, tmp_path):
"""Test that terminal summary shows small preview"""
test_file = tmp_path / "large_file.py"
content = "# This is a large file\n" + "x = 1\n" * 1000
test_file.write_text(content, encoding="utf-8")
context, summary = prepare_code_context([str(test_file)], None)
# Summary should show preview but not full content
assert "Analyzing 1 file(s)" in summary
assert str(test_file) in summary
assert "bytes)" in summary
assert "Preview:" in summary
# Full content should not be in summary
assert "x = 1" not in summary or summary.count("x = 1") < 5
def test_multiple_files_summary(self, tmp_path):
"""Test summary with multiple files"""
files = []
for i in range(3):
file = tmp_path / f"file{i}.py"
file.write_text(f"# File {i}\nprint({i})", encoding="utf-8")
files.append(str(file))
context, summary = prepare_code_context(files, None)
assert "Analyzing 3 file(s)" in summary
for file in files:
assert file in summary
assert "bytes)" in summary
# Should have clear delimiters in context
assert context.count("--- BEGIN FILE:") == 3
assert context.count("--- END FILE:") == 3
def test_direct_code_formatting(self):
"""Test direct code formatting"""
direct_code = "# Direct code\nprint('hello')"
context, summary = prepare_code_context(None, direct_code)
# Context should have clear markers
assert "--- BEGIN DIRECT CODE ---" in context
assert "--- END DIRECT CODE ---" in context
assert direct_code in context
# Summary should show preview
assert "Direct code provided" in summary
assert f"({len(direct_code)} characters)" in summary
assert "Preview:" in summary
def test_mixed_content_formatting(self, tmp_path):
"""Test formatting with both files and direct code"""
test_file = tmp_path / "test.py"
test_file.write_text("# Test file", encoding="utf-8")
direct_code = "# Direct code\nprint('hello')"
context, summary = prepare_code_context([str(test_file)], direct_code)
# Context should have both with clear separation
assert "--- BEGIN FILE:" in context
assert "--- END FILE:" in context
assert "--- BEGIN DIRECT CODE ---" in context
assert "--- END DIRECT CODE ---" in context
# Summary should mention both
assert "Analyzing 1 file(s)" in summary
assert "Direct code provided" in summary

View File

@@ -1,89 +0,0 @@
"""
Test version functionality
"""
import pytest
import json
from pathlib import Path
import sys
# Add parent directory to path for imports
parent_dir = Path(__file__).resolve().parent.parent
if str(parent_dir) not in sys.path:
sys.path.insert(0, str(parent_dir))
from gemini_server import (
__version__,
__updated__,
__author__,
handle_list_tools,
handle_call_tool,
)
class TestVersionFunctionality:
"""Test version-related functionality"""
@pytest.mark.asyncio
async def test_version_constants_exist(self):
"""Test that version constants are defined"""
assert __version__ is not None
assert isinstance(__version__, str)
assert __updated__ is not None
assert isinstance(__updated__, str)
assert __author__ is not None
assert isinstance(__author__, str)
@pytest.mark.asyncio
async def test_version_tool_in_list(self):
"""Test that get_version tool appears in tool list"""
tools = await handle_list_tools()
tool_names = [tool.name for tool in tools]
assert "get_version" in tool_names
# Find the version tool
version_tool = next(t for t in tools if t.name == "get_version")
assert (
version_tool.description
== "Get the version and metadata of the Gemini MCP Server"
)
@pytest.mark.asyncio
async def test_get_version_tool_execution(self):
"""Test executing the get_version tool"""
result = await handle_call_tool("get_version", {})
assert len(result) == 1
assert result[0].type == "text"
# Check the response contains expected information
response_text = result[0].text
assert __version__ in response_text
assert __updated__ in response_text
assert __author__ in response_text
assert "Gemini MCP Server" in response_text
assert "Default Model:" in response_text
assert "Max Context:" in response_text
assert "Python:" in response_text
assert "Started:" in response_text
assert "github.com/BeehiveInnovations/gemini-mcp-server" in response_text
@pytest.mark.asyncio
async def test_version_format(self):
"""Test that version follows semantic versioning"""
parts = __version__.split(".")
assert len(parts) == 3 # Major.Minor.Patch
for part in parts:
assert part.isdigit() # Each part should be numeric
@pytest.mark.asyncio
async def test_date_format(self):
"""Test that updated date follows expected format"""
# Expected format: YYYY-MM-DD
parts = __updated__.split("-")
assert len(parts) == 3
assert len(parts[0]) == 4 # Year
assert len(parts[1]) == 2 # Month
assert len(parts[2]) == 2 # Day
for part in parts:
assert part.isdigit()

15
tools/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
"""
Tool implementations for Gemini MCP Server
"""
from .think_deeper import ThinkDeeperTool
from .review_code import ReviewCodeTool
from .debug_issue import DebugIssueTool
from .analyze import AnalyzeTool
__all__ = [
"ThinkDeeperTool",
"ReviewCodeTool",
"DebugIssueTool",
"AnalyzeTool",
]

151
tools/analyze.py Normal file
View File

@@ -0,0 +1,151 @@
"""
Analyze tool - General-purpose code and file analysis
"""
from typing import Dict, Any, List, Optional
from pydantic import Field
from .base import BaseTool, ToolRequest
from prompts import ANALYZE_PROMPT
from utils import read_files, check_token_limit
from config import TEMPERATURE_ANALYTICAL, MAX_CONTEXT_TOKENS
class AnalyzeRequest(ToolRequest):
"""Request model for analyze tool"""
files: List[str] = Field(..., description="Files to analyze")
question: str = Field(..., description="What to analyze or look for")
analysis_type: Optional[str] = Field(
None,
description="Type of analysis: architecture|performance|security|quality|general",
)
output_format: Optional[str] = Field(
"detailed", description="Output format: summary|detailed|actionable"
)
class AnalyzeTool(BaseTool):
"""General-purpose file and code analysis tool"""
def get_name(self) -> str:
return "analyze"
def get_description(self) -> str:
return (
"ANALYZE FILES & CODE - General-purpose analysis for understanding code. "
"Use this for examining files, understanding architecture, or investigating specific aspects. "
"Triggers: 'analyze these files', 'examine this code', 'understand this'. "
"Perfect for: codebase exploration, dependency analysis, pattern detection. "
"Always uses file paths for clean terminal output."
)
def get_input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Files to analyze",
},
"question": {
"type": "string",
"description": "What to analyze or look for",
},
"analysis_type": {
"type": "string",
"enum": [
"architecture",
"performance",
"security",
"quality",
"general",
],
"description": "Type of analysis to perform",
},
"output_format": {
"type": "string",
"enum": ["summary", "detailed", "actionable"],
"default": "detailed",
"description": "How to format the output",
},
"temperature": {
"type": "number",
"description": "Temperature (0-1, default 0.2)",
"minimum": 0,
"maximum": 1,
},
},
"required": ["files", "question"],
}
def get_system_prompt(self) -> str:
return ANALYZE_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_ANALYTICAL
def get_request_model(self):
return AnalyzeRequest
async def prepare_prompt(self, request: AnalyzeRequest) -> str:
"""Prepare the analysis prompt"""
# Read all files
file_content, summary = read_files(request.files)
# Check token limits
within_limit, estimated_tokens = check_token_limit(file_content)
if not within_limit:
raise ValueError(
f"Files too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
)
# Build analysis instructions
analysis_focus = []
if request.analysis_type:
type_focus = {
"architecture": "Focus on architectural patterns, structure, and design decisions",
"performance": "Focus on performance characteristics and optimization opportunities",
"security": "Focus on security implications and potential vulnerabilities",
"quality": "Focus on code quality, maintainability, and best practices",
"general": "Provide a comprehensive general analysis",
}
analysis_focus.append(type_focus.get(request.analysis_type, ""))
if request.output_format == "summary":
analysis_focus.append("Provide a concise summary of key findings")
elif request.output_format == "actionable":
analysis_focus.append(
"Focus on actionable insights and specific recommendations"
)
focus_instruction = "\n".join(analysis_focus) if analysis_focus else ""
# Combine everything
full_prompt = f"""{self.get_system_prompt()}
{focus_instruction}
=== USER QUESTION ===
{request.question}
=== END QUESTION ===
=== FILES TO ANALYZE ===
{file_content}
=== END FILES ===
Please analyze these files to answer the user's question."""
return full_prompt
def format_response(self, response: str, request: AnalyzeRequest) -> str:
"""Format the analysis response"""
header = f"Analysis: {request.question[:50]}..."
if request.analysis_type:
header = f"{request.analysis_type.upper()} Analysis"
summary_text = f"Analyzed {len(request.files)} file(s)"
return f"{header}\n{summary_text}\n{'=' * 50}\n\n{response}"

128
tools/base.py Normal file
View File

@@ -0,0 +1,128 @@
"""
Base class for all Gemini MCP tools
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional
from pydantic import BaseModel, Field
import google.generativeai as genai
from mcp.types import TextContent
class ToolRequest(BaseModel):
"""Base request model for all tools"""
model: Optional[str] = Field(
None, description="Model to use (defaults to Gemini 2.5 Pro)"
)
max_tokens: Optional[int] = Field(
8192, description="Maximum number of tokens in response"
)
temperature: Optional[float] = Field(
None, description="Temperature for response (tool-specific defaults)"
)
class BaseTool(ABC):
"""Base class for all Gemini tools"""
def __init__(self):
self.name = self.get_name()
self.description = self.get_description()
self.default_temperature = self.get_default_temperature()
@abstractmethod
def get_name(self) -> str:
"""Return the tool name"""
pass
@abstractmethod
def get_description(self) -> str:
"""Return the verbose tool description for Claude"""
pass
@abstractmethod
def get_input_schema(self) -> Dict[str, Any]:
"""Return the JSON schema for tool inputs"""
pass
@abstractmethod
def get_system_prompt(self) -> str:
"""Return the system prompt for this tool"""
pass
def get_default_temperature(self) -> float:
"""Return default temperature for this tool"""
return 0.5
@abstractmethod
def get_request_model(self):
"""Return the Pydantic model for request validation"""
pass
async def execute(self, arguments: Dict[str, Any]) -> List[TextContent]:
"""Execute the tool with given arguments"""
try:
# Validate request
request_model = self.get_request_model()
request = request_model(**arguments)
# Prepare the prompt
prompt = await self.prepare_prompt(request)
# Get model configuration
from config import DEFAULT_MODEL
model_name = getattr(request, "model", None) or DEFAULT_MODEL
temperature = getattr(request, "temperature", None)
if temperature is None:
temperature = self.get_default_temperature()
max_tokens = getattr(request, "max_tokens", 8192)
# Create and configure model
model = self.create_model(model_name, temperature, max_tokens)
# Generate response
response = model.generate_content(prompt)
# Handle response
if response.candidates and response.candidates[0].content.parts:
text = response.candidates[0].content.parts[0].text
else:
finish_reason = (
response.candidates[0].finish_reason
if response.candidates
else "Unknown"
)
text = f"Response blocked or incomplete. Finish reason: {finish_reason}"
# Format response
formatted_response = self.format_response(text, request)
return [TextContent(type="text", text=formatted_response)]
except Exception as e:
error_msg = f"Error in {self.name}: {str(e)}"
return [TextContent(type="text", text=error_msg)]
@abstractmethod
async def prepare_prompt(self, request) -> str:
"""Prepare the full prompt for Gemini"""
pass
def format_response(self, response: str, request) -> str:
"""Format the response for display (can be overridden)"""
return response
def create_model(
self, model_name: str, temperature: float, max_tokens: int
) -> genai.GenerativeModel:
"""Create a configured Gemini model"""
return genai.GenerativeModel(
model_name=model_name,
generation_config={
"temperature": temperature,
"max_output_tokens": max_tokens,
"candidate_count": 1,
},
)

145
tools/debug_issue.py Normal file
View File

@@ -0,0 +1,145 @@
"""
Debug Issue tool - Root cause analysis and debugging assistance
"""
from typing import Dict, Any, List, Optional
from pydantic import Field
from .base import BaseTool, ToolRequest
from prompts import DEBUG_ISSUE_PROMPT
from utils import read_files, check_token_limit
from config import TEMPERATURE_ANALYTICAL, MAX_CONTEXT_TOKENS
class DebugIssueRequest(ToolRequest):
"""Request model for debug_issue tool"""
error_description: str = Field(
..., description="Error message, symptoms, or issue description"
)
error_context: Optional[str] = Field(
None, description="Stack trace, logs, or additional error context"
)
relevant_files: Optional[List[str]] = Field(
None, description="Files that might be related to the issue"
)
runtime_info: Optional[str] = Field(
None, description="Environment, versions, or runtime information"
)
previous_attempts: Optional[str] = Field(
None, description="What has been tried already"
)
class DebugIssueTool(BaseTool):
"""Advanced debugging and root cause analysis tool"""
def get_name(self) -> str:
return "debug_issue"
def get_description(self) -> str:
return (
"DEBUG & ROOT CAUSE ANALYSIS - Expert debugging for complex issues. "
"Use this when you need help tracking down bugs or understanding errors. "
"Triggers: 'debug this', 'why is this failing', 'root cause', 'trace error'. "
"I'll analyze the issue, find root causes, and provide step-by-step solutions. "
"Include error messages, stack traces, and relevant code for best results."
)
def get_input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"error_description": {
"type": "string",
"description": "Error message, symptoms, or issue description",
},
"error_context": {
"type": "string",
"description": "Stack trace, logs, or additional error context",
},
"relevant_files": {
"type": "array",
"items": {"type": "string"},
"description": "Files that might be related to the issue",
},
"runtime_info": {
"type": "string",
"description": "Environment, versions, or runtime information",
},
"previous_attempts": {
"type": "string",
"description": "What has been tried already",
},
"temperature": {
"type": "number",
"description": "Temperature (0-1, default 0.2 for accuracy)",
"minimum": 0,
"maximum": 1,
},
},
"required": ["error_description"],
}
def get_system_prompt(self) -> str:
return DEBUG_ISSUE_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_ANALYTICAL
def get_request_model(self):
return DebugIssueRequest
async def prepare_prompt(self, request: DebugIssueRequest) -> str:
"""Prepare the debugging prompt"""
# Build context sections
context_parts = [
f"=== ISSUE DESCRIPTION ===\n{request.error_description}\n=== END DESCRIPTION ==="
]
if request.error_context:
context_parts.append(
f"\n=== ERROR CONTEXT/STACK TRACE ===\n{request.error_context}\n=== END CONTEXT ==="
)
if request.runtime_info:
context_parts.append(
f"\n=== RUNTIME INFORMATION ===\n{request.runtime_info}\n=== END RUNTIME ==="
)
if request.previous_attempts:
context_parts.append(
f"\n=== PREVIOUS ATTEMPTS ===\n{request.previous_attempts}\n=== END ATTEMPTS ==="
)
# Add relevant files if provided
if request.relevant_files:
file_content, _ = read_files(request.relevant_files)
context_parts.append(
f"\n=== RELEVANT CODE ===\n{file_content}\n=== END CODE ==="
)
full_context = "\n".join(context_parts)
# Check token limits
within_limit, estimated_tokens = check_token_limit(full_context)
if not within_limit:
raise ValueError(
f"Context too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
)
# Combine everything
full_prompt = f"""{self.get_system_prompt()}
{full_context}
Please debug this issue following the structured format in the system prompt.
Focus on finding the root cause and providing actionable solutions."""
return full_prompt
def format_response(
self, response: str, request: DebugIssueRequest
) -> str:
"""Format the debugging response"""
return f"Debug Analysis\n{'=' * 50}\n\n{response}"

160
tools/review_code.py Normal file
View File

@@ -0,0 +1,160 @@
"""
Code Review tool - Comprehensive code analysis and review
"""
from typing import Dict, Any, List, Optional
from pydantic import Field
from .base import BaseTool, ToolRequest
from prompts import REVIEW_CODE_PROMPT
from utils import read_files, check_token_limit
from config import TEMPERATURE_ANALYTICAL, MAX_CONTEXT_TOKENS
class ReviewCodeRequest(ToolRequest):
"""Request model for review_code tool"""
files: List[str] = Field(..., description="Code files to review")
review_type: str = Field(
"full", description="Type of review: full|security|performance|quick"
)
focus_on: Optional[str] = Field(
None, description="Specific aspects to focus on during review"
)
standards: Optional[str] = Field(
None, description="Coding standards or guidelines to enforce"
)
severity_filter: str = Field(
"all",
description="Minimum severity to report: critical|high|medium|all",
)
class ReviewCodeTool(BaseTool):
"""Professional code review tool"""
def get_name(self) -> str:
return "review_code"
def get_description(self) -> str:
return (
"PROFESSIONAL CODE REVIEW - Comprehensive analysis for bugs, security, and quality. "
"Use this for thorough code review with actionable feedback. "
"Triggers: 'review this code', 'check for issues', 'find bugs', 'security audit'. "
"I'll identify issues by severity (Critical→High→Medium→Low) with specific fixes. "
"Supports focused reviews: security, performance, or quick checks."
)
def get_input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Code files to review",
},
"review_type": {
"type": "string",
"enum": ["full", "security", "performance", "quick"],
"default": "full",
"description": "Type of review to perform",
},
"focus_on": {
"type": "string",
"description": "Specific aspects to focus on",
},
"standards": {
"type": "string",
"description": "Coding standards to enforce",
},
"severity_filter": {
"type": "string",
"enum": ["critical", "high", "medium", "all"],
"default": "all",
"description": "Minimum severity level to report",
},
"temperature": {
"type": "number",
"description": "Temperature (0-1, default 0.2 for consistency)",
"minimum": 0,
"maximum": 1,
},
},
"required": ["files"],
}
def get_system_prompt(self) -> str:
return REVIEW_CODE_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_ANALYTICAL
def get_request_model(self):
return ReviewCodeRequest
async def prepare_prompt(self, request: ReviewCodeRequest) -> str:
"""Prepare the code review prompt"""
# Read all files
file_content, summary = read_files(request.files)
# Check token limits
within_limit, estimated_tokens = check_token_limit(file_content)
if not within_limit:
raise ValueError(
f"Code too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
)
# Build review instructions
review_focus = []
if request.review_type == "security":
review_focus.append(
"Focus on security vulnerabilities and authentication issues"
)
elif request.review_type == "performance":
review_focus.append(
"Focus on performance bottlenecks and optimization opportunities"
)
elif request.review_type == "quick":
review_focus.append(
"Provide a quick review focusing on critical issues only"
)
if request.focus_on:
review_focus.append(
f"Pay special attention to: {request.focus_on}"
)
if request.standards:
review_focus.append(
f"Enforce these standards: {request.standards}"
)
if request.severity_filter != "all":
review_focus.append(
f"Only report issues of {request.severity_filter} severity or higher"
)
focus_instruction = "\n".join(review_focus) if review_focus else ""
# Combine everything
full_prompt = f"""{self.get_system_prompt()}
{focus_instruction}
=== CODE TO REVIEW ===
{file_content}
=== END CODE ===
Please provide a comprehensive code review following the format specified in the system prompt."""
return full_prompt
def format_response(
self, response: str, request: ReviewCodeRequest
) -> str:
"""Format the review response"""
header = f"Code Review ({request.review_type.upper()})"
if request.focus_on:
header += f" - Focus: {request.focus_on}"
return f"{header}\n{'=' * 50}\n\n{response}"

145
tools/think_deeper.py Normal file
View File

@@ -0,0 +1,145 @@
"""
Think Deeper tool - Extended reasoning and problem-solving
"""
from typing import Dict, Any, List, Optional
from pydantic import Field
from .base import BaseTool, ToolRequest
from prompts import THINK_DEEPER_PROMPT
from utils import read_files, check_token_limit
from config import TEMPERATURE_CREATIVE, MAX_CONTEXT_TOKENS
class ThinkDeeperRequest(ToolRequest):
"""Request model for think_deeper tool"""
current_analysis: str = Field(
..., description="Claude's current thinking/analysis to extend"
)
problem_context: Optional[str] = Field(
None, description="Additional context about the problem or goal"
)
focus_areas: Optional[List[str]] = Field(
None,
description="Specific aspects to focus on (architecture, performance, security, etc.)",
)
reference_files: Optional[List[str]] = Field(
None, description="Optional file paths for additional context"
)
class ThinkDeeperTool(BaseTool):
"""Extended thinking and reasoning tool"""
def get_name(self) -> str:
return "think_deeper"
def get_description(self) -> str:
return (
"EXTENDED THINKING & REASONING - Your deep thinking partner for complex problems. "
"Use this when you need to extend your analysis, explore alternatives, or validate approaches. "
"Perfect for: architecture decisions, complex bugs, performance challenges, security analysis. "
"Triggers: 'think deeper', 'ultrathink', 'extend my analysis', 'explore alternatives'. "
"I'll challenge assumptions, find edge cases, and provide alternative solutions."
)
def get_input_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"current_analysis": {
"type": "string",
"description": "Your current thinking/analysis to extend and validate",
},
"problem_context": {
"type": "string",
"description": "Additional context about the problem or goal",
},
"focus_areas": {
"type": "array",
"items": {"type": "string"},
"description": "Specific aspects to focus on (architecture, performance, security, etc.)",
},
"reference_files": {
"type": "array",
"items": {"type": "string"},
"description": "Optional file paths for additional context",
},
"temperature": {
"type": "number",
"description": "Temperature for creative thinking (0-1, default 0.7)",
"minimum": 0,
"maximum": 1,
},
"max_tokens": {
"type": "integer",
"description": "Maximum tokens in response",
"default": 8192,
},
},
"required": ["current_analysis"],
}
def get_system_prompt(self) -> str:
return THINK_DEEPER_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_CREATIVE
def get_request_model(self):
return ThinkDeeperRequest
async def prepare_prompt(self, request: ThinkDeeperRequest) -> str:
"""Prepare the full prompt for extended thinking"""
# Build context parts
context_parts = [
f"=== CLAUDE'S CURRENT ANALYSIS ===\n{request.current_analysis}\n=== END ANALYSIS ==="
]
if request.problem_context:
context_parts.append(
f"\n=== PROBLEM CONTEXT ===\n{request.problem_context}\n=== END CONTEXT ==="
)
# Add reference files if provided
if request.reference_files:
file_content, _ = read_files(request.reference_files)
context_parts.append(
f"\n=== REFERENCE FILES ===\n{file_content}\n=== END FILES ==="
)
full_context = "\n".join(context_parts)
# Check token limits
within_limit, estimated_tokens = check_token_limit(full_context)
if not within_limit:
raise ValueError(
f"Context too large (~{estimated_tokens:,} tokens). "
f"Maximum is {MAX_CONTEXT_TOKENS:,} tokens."
)
# Add focus areas instruction if specified
focus_instruction = ""
if request.focus_areas:
areas = ", ".join(request.focus_areas)
focus_instruction = f"\n\nFOCUS AREAS: Please pay special attention to {areas} aspects."
# Combine system prompt with context
full_prompt = f"""{self.get_system_prompt()}{focus_instruction}
{full_context}
Please provide deep analysis that extends Claude's thinking with:
1. Alternative approaches and solutions
2. Edge cases and potential failure modes
3. Critical evaluation of assumptions
4. Concrete implementation suggestions
5. Risk assessment and mitigation strategies"""
return full_prompt
def format_response(
self, response: str, request: ThinkDeeperRequest
) -> str:
"""Format the response with clear attribution"""
return f"Extended Analysis by Gemini:\n\n{response}"

13
utils/__init__.py Normal file
View File

@@ -0,0 +1,13 @@
"""
Utility functions for Gemini MCP Server
"""
from .file_utils import read_files, read_file_content
from .token_utils import estimate_tokens, check_token_limit
__all__ = [
"read_files",
"read_file_content",
"estimate_tokens",
"check_token_limit",
]

63
utils/file_utils.py Normal file
View File

@@ -0,0 +1,63 @@
"""
File reading utilities
"""
from pathlib import Path
from typing import List, Tuple, Optional
def read_file_content(file_path: str) -> str:
"""Read a single file and format it for Gemini"""
path = Path(file_path)
try:
# Check if path exists and is a file
if not path.exists():
return f"\n--- FILE NOT FOUND: {file_path} ---\nError: File does not exist\n--- END FILE ---\n"
if not path.is_file():
return f"\n--- NOT A FILE: {file_path} ---\nError: Path is not a file\n--- END FILE ---\n"
# Read the file
with open(path, "r", encoding="utf-8") as f:
content = f.read()
# Format with clear delimiters for Gemini
return f"\n--- BEGIN FILE: {file_path} ---\n{content}\n--- END FILE: {file_path} ---\n"
except Exception as e:
return f"\n--- ERROR READING FILE: {file_path} ---\nError: {str(e)}\n--- END FILE ---\n"
def read_files(
file_paths: List[str], code: Optional[str] = None
) -> Tuple[str, str]:
"""
Read multiple files and optional direct code.
Returns: (full_content, brief_summary)
"""
content_parts = []
summary_parts = []
# Process files
if file_paths:
summary_parts.append(f"Reading {len(file_paths)} file(s)")
for file_path in file_paths:
content = read_file_content(file_path)
content_parts.append(content)
# Add direct code if provided
if code:
formatted_code = (
f"\n--- BEGIN DIRECT CODE ---\n{code}\n--- END DIRECT CODE ---\n"
)
content_parts.append(formatted_code)
code_preview = code[:50] + "..." if len(code) > 50 else code
summary_parts.append(f"Direct code: {code_preview}")
full_content = "\n\n".join(content_parts)
summary = (
" | ".join(summary_parts) if summary_parts else "No input provided"
)
return full_content, summary

20
utils/token_utils.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Token counting utilities
"""
from typing import Tuple
from config import MAX_CONTEXT_TOKENS
def estimate_tokens(text: str) -> int:
"""Estimate token count (rough: 1 token ≈ 4 characters)"""
return len(text) // 4
def check_token_limit(text: str) -> Tuple[bool, int]:
"""
Check if text exceeds token limit.
Returns: (is_within_limit, estimated_tokens)
"""
estimated = estimate_tokens(text)
return estimated <= MAX_CONTEXT_TOKENS, estimated