From 18c5ec913d744d6ccf88527615e7096c59346687 Mon Sep 17 00:00:00 2001 From: Fahad Date: Sun, 8 Jun 2025 19:39:06 +0400 Subject: [PATCH] Initial commit: Gemini MCP Server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MCP server implementation for Google Gemini models - Support for multiple Gemini models including 1.5 Pro and 2.5 Pro preview - Chat tool with configurable parameters (temperature, max_tokens, model) - List models tool to view available Gemini models - System prompt support - Comprehensive error handling for blocked responses - Test suite included - Documentation and examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 157 +++++++++++++++++++++++++++++++ README.md | 92 ++++++++++++++++++ claude_config_example.json | 11 +++ gemini_server.py | 186 +++++++++++++++++++++++++++++++++++++ requirements.txt | 3 + test_server.py | 70 ++++++++++++++ 6 files changed, 519 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 claude_config_example.json create mode 100755 gemini_server.py create mode 100644 requirements.txt create mode 100644 test_server.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04cd3c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,157 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# pdm +.pdm.toml +.pdm-python +pdm.lock + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# VS Code +.vscode/ + +# macOS +.DS_Store + +# API Keys and secrets +*.key +*.pem +.env.local +.env.*.local + +# Test outputs +test_output/ +*.test.log \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1954524 --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# Gemini MCP Server + +A Model Context Protocol (MCP) server that enables integration with Google's Gemini models, including Gemini 1.5 Pro and Gemini 2.5 Pro preview. + +## Features + +- **Chat with Gemini**: Send prompts to any available Gemini model +- **List Models**: View all available Gemini models +- **Configurable Parameters**: Adjust temperature, max tokens, and model selection +- **System Prompts**: Support for system prompts to set context + +## Installation + +1. Clone this repository +2. Create a virtual environment: + ```bash + python3 -m venv venv + source venv/bin/activate + ``` +3. Install dependencies: + ```bash + pip install -r requirements.txt + ``` + +## Configuration + +Set your Gemini API key as an environment variable: +```bash +export GEMINI_API_KEY="your-api-key-here" +``` + +## Usage + +### For Claude Desktop + +Add this configuration to your Claude Desktop config file: + +```json +{ + "mcpServers": { + "gemini": { + "command": "/path/to/venv/bin/python", + "args": ["/path/to/gemini_server.py"], + "env": { + "GEMINI_API_KEY": "your-api-key-here" + } + } + } +} +``` + +### Direct Usage + +Run the server: +```bash +source venv/bin/activate +export GEMINI_API_KEY="your-api-key-here" +python gemini_server.py +``` + +## Available Tools + +### chat +Send a prompt to Gemini and receive a response. + +Parameters: +- `prompt` (required): The prompt to send to Gemini +- `system_prompt` (optional): System prompt for context +- `max_tokens` (optional): Maximum tokens in response (default: 4096) +- `temperature` (optional): Temperature for randomness 0-1 (default: 0.7) +- `model` (optional): Model to use (default: gemini-1.5-pro-latest) + +Available models include: +- `gemini-1.5-pro-latest` - Latest stable Gemini 1.5 Pro +- `gemini-1.5-flash` - Fast Gemini 1.5 Flash model +- `gemini-2.5-pro-preview-06-05` - Gemini 2.5 Pro preview (may have restrictions) +- `gemini-2.0-flash` - Gemini 2.0 Flash +- And many more (use `list_models` to see all available) + +### list_models +List all available Gemini models that support content generation. + +## Requirements + +- Python 3.8+ +- Valid Google Gemini API key + +## Notes + +- The Gemini 2.5 Pro preview models may have safety restrictions that block certain prompts +- If a model returns a blocked response, the server will indicate the finish reason +- For most reliable results, use `gemini-1.5-pro-latest` or `gemini-1.5-flash` \ No newline at end of file diff --git a/claude_config_example.json b/claude_config_example.json new file mode 100644 index 0000000..b35d28a --- /dev/null +++ b/claude_config_example.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "gemini": { + "command": "/Users/fahad/Developer/gemini-mcp-server/venv/bin/python", + "args": ["/Users/fahad/Developer/gemini-mcp-server/gemini_server.py"], + "env": { + "GEMINI_API_KEY": "your-gemini-api-key-here" + } + } + } +} \ No newline at end of file diff --git a/gemini_server.py b/gemini_server.py new file mode 100755 index 0000000..1342f18 --- /dev/null +++ b/gemini_server.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +""" +Gemini MCP Server - Model Context Protocol server for Google Gemini +""" + +import os +import json +import asyncio +from typing import Optional, Dict, Any, List +from mcp.server.models import InitializationOptions +from mcp.server import Server, NotificationOptions +from mcp.server.stdio import stdio_server +from mcp.types import TextContent, Tool +from pydantic import BaseModel, Field +import google.generativeai as genai + + +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(4096, description="Maximum number of tokens in response") + temperature: Optional[float] = Field(0.7, description="Temperature for response randomness (0-1)") + model: Optional[str] = Field("gemini-1.5-pro-latest", description="Model to use (defaults to gemini-1.5-pro-latest)") + + +# Create the MCP server instance +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) + + +@server.list_tools() +async def handle_list_tools() -> List[Tool]: + """List all available tools""" + return [ + Tool( + name="chat", + description="Chat with Gemini Pro 2.5 model", + 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": 4096 + }, + "temperature": { + "type": "number", + "description": "Temperature for response randomness (0-1)", + "default": 0.7, + "minimum": 0, + "maximum": 1 + }, + "model": { + "type": "string", + "description": "Model to use (e.g., gemini-1.5-pro-latest, gemini-2.5-pro-preview-06-05)", + "default": "gemini-1.5-pro-latest" + } + }, + "required": ["prompt"] + } + ), + Tool( + name="list_models", + description="List available Gemini models", + inputSchema={ + "type": "object", + "properties": {} + } + ) + ] + + +@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 or default to 1.5 Pro + model = genai.GenerativeModel( + model_name=request.model, + generation_config={ + "temperature": request.temperature, + "max_output_tokens": request.max_tokens, + } + ) + + # Prepare the prompt + full_prompt = request.prompt + if request.system_prompt: + full_prompt = f"{request.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 == "list_models": + try: + # List available models + models = [] + for model in genai.list_models(): + if 'generateContent' in model.supported_generation_methods: + models.append({ + "name": model.name, + "display_name": model.display_name, + "description": model.description + }) + + 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)}" + )] + + 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="1.0.0", + capabilities={ + "tools": {} + } + ) + ) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7a7a6b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +mcp>=1.0.0 +google-generativeai>=0.8.0 +python-dotenv>=1.0.0 \ No newline at end of file diff --git a/test_server.py b/test_server.py new file mode 100644 index 0000000..d5bac07 --- /dev/null +++ b/test_server.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Test script for Gemini MCP Server +""" + +import os +import asyncio +import json +from gemini_server import configure_gemini, handle_call_tool, handle_list_tools + + +async def test_server(): + """Test the server functionality""" + print("Testing Gemini MCP Server...") + print("-" * 50) + + # Test configuration + try: + configure_gemini() + print("✓ Gemini API configured successfully") + except Exception as e: + print(f"✗ Failed to configure Gemini API: {e}") + return + + # Test listing tools + print("\n1. Testing list_tools...") + tools = await handle_list_tools() + print(f"✓ Found {len(tools)} tools:") + for tool in tools: + print(f" - {tool.name}: {tool.description}") + + # Test list_models + print("\n2. Testing list_models tool...") + models_result = await handle_call_tool("list_models", {}) + print("✓ Available models:") + print(models_result[0].text) + + # Test chat + print("\n3. Testing chat tool...") + chat_result = await handle_call_tool("chat", { + "prompt": "What is the capital of France?", + "temperature": 0.3, + "max_tokens": 50 + }) + print("✓ Chat response:") + print(chat_result[0].text) + + # Test chat with system prompt + print("\n4. Testing chat with system prompt...") + chat_result = await handle_call_tool("chat", { + "prompt": "What's 2+2?", + "system_prompt": "You are a helpful math tutor. Always explain your reasoning step by step.", + "temperature": 0.3, + "max_tokens": 200 + }) + print("✓ Chat response with system prompt:") + print(chat_result[0].text) + + print("\n" + "-" * 50) + print("All tests completed!") + + +if __name__ == "__main__": + # Check for API key + if not os.getenv("GEMINI_API_KEY"): + print("Error: GEMINI_API_KEY environment variable is not set") + print("Please set it with: export GEMINI_API_KEY='your-api-key'") + exit(1) + + asyncio.run(test_server()) \ No newline at end of file