LiamKhoaLe commited on
Commit
40374f9
·
1 Parent(s): 413918e

Upd gemini mcp server script

Browse files
Files changed (4) hide show
  1. README.md +4 -4
  2. app.py +9 -11
  3. gemini_mcp.py +264 -0
  4. requirements.txt +1 -1
README.md CHANGED
@@ -91,7 +91,7 @@ tags:
91
  - **RAG Framework**: LlamaIndex with hierarchical node parsing
92
  - **Web Search**: Model Context Protocol (MCP) tools with automatic fallback to DuckDuckGo
93
  - **MCP Client**: Python MCP SDK for standardized tool integration
94
- - **Gemini MCP Server**: aistudio-mcp-server via MCP protocol
95
 
96
  ## 📋 Requirements
97
 
@@ -112,7 +112,7 @@ export GEMINI_API_KEY="your-gemini-api-key"
112
 
113
  # Gemini MCP Server Configuration
114
  export MCP_SERVER_COMMAND="python"
115
- export MCP_SERVER_ARGS="-m aistudio_mcp_server"
116
 
117
  # Optional Gemini Configuration
118
  export GEMINI_MODEL="gemini-2.5-flash" # For harder tasks (default)
@@ -146,7 +146,7 @@ export GEMINI_TEMPERATURE=0.2 # Temperature for generation 0-2 (default: 0.2)
146
  2. **Install Gemini MCP Server**:
147
  ```bash
148
  # Install Python package
149
- pip install aistudio-mcp-server
150
  ```
151
 
152
  3. **Get Gemini API Key**:
@@ -157,7 +157,7 @@ export GEMINI_TEMPERATURE=0.2 # Temperature for generation 0-2 (default: 0.2)
157
  ```bash
158
  export GEMINI_API_KEY="your-gemini-api-key"
159
  export MCP_SERVER_COMMAND="python"
160
- export MCP_SERVER_ARGS="-m aistudio_mcp_server"
161
  ```
162
 
163
  **Note**: The application requires Gemini MCP for translation, document parsing, transcription, and summarization. Web search functionality still supports fallback to direct library calls if MCP is not configured.
 
91
  - **RAG Framework**: LlamaIndex with hierarchical node parsing
92
  - **Web Search**: Model Context Protocol (MCP) tools with automatic fallback to DuckDuckGo
93
  - **MCP Client**: Python MCP SDK for standardized tool integration
94
+ - **Gemini MCP Server**: mcp-server via MCP protocol
95
 
96
  ## 📋 Requirements
97
 
 
112
 
113
  # Gemini MCP Server Configuration
114
  export MCP_SERVER_COMMAND="python"
115
+ export MCP_SERVER_ARGS="-m mcp_server"
116
 
117
  # Optional Gemini Configuration
118
  export GEMINI_MODEL="gemini-2.5-flash" # For harder tasks (default)
 
146
  2. **Install Gemini MCP Server**:
147
  ```bash
148
  # Install Python package
149
+ pip install mcp-server
150
  ```
151
 
152
  3. **Get Gemini API Key**:
 
157
  ```bash
158
  export GEMINI_API_KEY="your-gemini-api-key"
159
  export MCP_SERVER_COMMAND="python"
160
+ export MCP_SERVER_ARGS="-m mcp_server"
161
  ```
162
 
163
  **Note**: The application requires Gemini MCP for translation, document parsing, transcription, and summarization. Web search functionality still supports fallback to direct library calls if MCP is not configured.
app.py CHANGED
@@ -200,20 +200,18 @@ global_mcp_session = None
200
  global_mcp_stdio_ctx = None # Store stdio context to keep it alive
201
  global_mcp_lock = threading.Lock() # Lock for thread-safe session access
202
  # MCP server configuration via environment variables
203
- # Gemini MCP server options:
204
- # 1. Node.js version (recommended): npx @rlabs/gemini-mcp
205
- # 2. Python version: If you have a Python Gemini MCP server package installed
206
  # Make sure GEMINI_API_KEY is set in environment variables
207
  #
208
- # To use Node.js version (default):
209
- # export MCP_SERVER_COMMAND="npx"
210
- # export MCP_SERVER_ARGS="@rlabs/gemini-mcp"
211
- #
212
- # To use Python version (if available):
213
  # export MCP_SERVER_COMMAND="python"
214
- # export MCP_SERVER_ARGS="-m your_gemini_mcp_package"
215
- MCP_SERVER_COMMAND = os.environ.get("MCP_SERVER_COMMAND", "npx")
216
- MCP_SERVER_ARGS = os.environ.get("MCP_SERVER_ARGS", "@rlabs/gemini-mcp").split() if os.environ.get("MCP_SERVER_ARGS") else ["@rlabs/gemini-mcp"]
 
 
217
 
218
  async def get_mcp_session():
219
  """Get or create MCP client session with proper context management"""
 
200
  global_mcp_stdio_ctx = None # Store stdio context to keep it alive
201
  global_mcp_lock = threading.Lock() # Lock for thread-safe session access
202
  # MCP server configuration via environment variables
203
+ # Gemini MCP server: Python-based server (gemini_mcp_server.py)
204
+ # This works on Hugging Face Spaces without requiring npm/Node.js
 
205
  # Make sure GEMINI_API_KEY is set in environment variables
206
  #
207
+ # Default configuration uses the bundled gemini_mcp_server.py script
208
+ # To override:
 
 
 
209
  # export MCP_SERVER_COMMAND="python"
210
+ # export MCP_SERVER_ARGS="/path/to/gemini_mcp_server.py"
211
+ script_dir = os.path.dirname(os.path.abspath(__file__))
212
+ gemini_mcp_server_path = os.path.join(script_dir, "gemini_mcp_server.py")
213
+ MCP_SERVER_COMMAND = os.environ.get("MCP_SERVER_COMMAND", "python")
214
+ MCP_SERVER_ARGS = os.environ.get("MCP_SERVER_ARGS", gemini_mcp_server_path).split() if os.environ.get("MCP_SERVER_ARGS") else [gemini_mcp_server_path]
215
 
216
  async def get_mcp_session():
217
  """Get or create MCP client session with proper context management"""
gemini_mcp.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Gemini MCP Server
4
+ A Python-based MCP server that provides Gemini AI capabilities via Model Context Protocol.
5
+ This server implements the generate_content tool for translation, summarization, document parsing, and transcription.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import json
11
+ import base64
12
+ import asyncio
13
+ import logging
14
+ from typing import Any, Sequence
15
+ from pathlib import Path
16
+
17
+ # MCP imports
18
+ try:
19
+ from mcp.server import Server
20
+ from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
21
+ except ImportError:
22
+ print("Error: MCP SDK not installed. Install with: pip install mcp", file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+ # Gemini imports
26
+ try:
27
+ import google.generativeai as genai
28
+ except ImportError:
29
+ print("Error: google-generativeai not installed. Install with: pip install google-generativeai", file=sys.stderr)
30
+ sys.exit(1)
31
+
32
+ # Configure logging
33
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
34
+ logger = logging.getLogger(__name__)
35
+
36
+ # Initialize Gemini
37
+ GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
38
+ if not GEMINI_API_KEY:
39
+ logger.error("GEMINI_API_KEY not set in environment variables")
40
+ sys.exit(1)
41
+
42
+ genai.configure(api_key=GEMINI_API_KEY)
43
+
44
+ # Configuration from environment
45
+ GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.5-flash")
46
+ GEMINI_MODEL_LITE = os.environ.get("GEMINI_MODEL_LITE", "gemini-2.5-flash-lite")
47
+ GEMINI_TIMEOUT = int(os.environ.get("GEMINI_TIMEOUT", "300000")) # milliseconds
48
+ GEMINI_MAX_OUTPUT_TOKENS = int(os.environ.get("GEMINI_MAX_OUTPUT_TOKENS", "8192"))
49
+ GEMINI_MAX_FILES = int(os.environ.get("GEMINI_MAX_FILES", "10"))
50
+ GEMINI_MAX_TOTAL_FILE_SIZE = int(os.environ.get("GEMINI_MAX_TOTAL_FILE_SIZE", "50")) # MB
51
+ GEMINI_TEMPERATURE = float(os.environ.get("GEMINI_TEMPERATURE", "0.2"))
52
+
53
+ # Create MCP server
54
+ app = Server("gemini-mcp-server")
55
+
56
+ def decode_base64_file(content: str, mime_type: str = None) -> bytes:
57
+ """Decode base64 encoded file content"""
58
+ try:
59
+ return base64.b64decode(content)
60
+ except Exception as e:
61
+ logger.error(f"Error decoding base64 content: {e}")
62
+ raise
63
+
64
+ def prepare_gemini_files(files: list) -> list:
65
+ """Prepare files for Gemini API"""
66
+ gemini_parts = []
67
+
68
+ for file_obj in files:
69
+ try:
70
+ # Handle file with path
71
+ if "path" in file_obj:
72
+ file_path = file_obj["path"]
73
+ mime_type = file_obj.get("type")
74
+
75
+ if not os.path.exists(file_path):
76
+ logger.warning(f"File not found: {file_path}")
77
+ continue
78
+
79
+ # Read file
80
+ with open(file_path, 'rb') as f:
81
+ file_data = f.read()
82
+
83
+ # Auto-detect MIME type if not provided
84
+ if not mime_type:
85
+ from mimetypes import guess_type
86
+ mime_type, _ = guess_type(file_path)
87
+ if not mime_type:
88
+ mime_type = "application/octet-stream"
89
+
90
+ # Handle file with base64 content
91
+ elif "content" in file_obj:
92
+ file_data = decode_base64_file(file_obj["content"])
93
+ mime_type = file_obj.get("type", "application/octet-stream")
94
+ else:
95
+ logger.warning("File object must have either 'path' or 'content'")
96
+ continue
97
+
98
+ # Add to Gemini parts
99
+ gemini_parts.append({
100
+ "mime_type": mime_type,
101
+ "data": file_data
102
+ })
103
+
104
+ except Exception as e:
105
+ logger.error(f"Error processing file: {e}")
106
+ continue
107
+
108
+ return gemini_parts
109
+
110
+ @app.list_tools()
111
+ async def list_tools() -> list[Tool]:
112
+ """List available tools"""
113
+ return [
114
+ Tool(
115
+ name="generate_content",
116
+ description="Generate content using Gemini AI. Supports text generation, translation, summarization, document parsing, and audio transcription.",
117
+ inputSchema={
118
+ "type": "object",
119
+ "properties": {
120
+ "user_prompt": {
121
+ "type": "string",
122
+ "description": "User prompt for generation (required)"
123
+ },
124
+ "system_prompt": {
125
+ "type": "string",
126
+ "description": "System prompt to guide AI behavior (optional)"
127
+ },
128
+ "files": {
129
+ "type": "array",
130
+ "description": "Array of files to include in generation (optional)",
131
+ "items": {
132
+ "type": "object",
133
+ "properties": {
134
+ "path": {"type": "string", "description": "Path to file"},
135
+ "content": {"type": "string", "description": "Base64 encoded file content"},
136
+ "type": {"type": "string", "description": "MIME type (auto-detected from file extension)"}
137
+ }
138
+ }
139
+ },
140
+ "model": {
141
+ "type": "string",
142
+ "description": f"Gemini model to use (default: {GEMINI_MODEL})"
143
+ },
144
+ "temperature": {
145
+ "type": "number",
146
+ "description": f"Temperature for generation 0-2 (default: {GEMINI_TEMPERATURE})"
147
+ }
148
+ },
149
+ "required": ["user_prompt"]
150
+ }
151
+ )
152
+ ]
153
+
154
+ @app.call_tool()
155
+ async def call_tool(name: str, arguments: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
156
+ """Handle tool calls"""
157
+ if name == "generate_content":
158
+ try:
159
+ user_prompt = arguments.get("user_prompt")
160
+ if not user_prompt:
161
+ return [TextContent(type="text", text="Error: user_prompt is required")]
162
+
163
+ system_prompt = arguments.get("system_prompt")
164
+ files = arguments.get("files", [])
165
+ model = arguments.get("model", GEMINI_MODEL)
166
+ temperature = float(arguments.get("temperature", GEMINI_TEMPERATURE))
167
+
168
+ # Prepare model
169
+ try:
170
+ gemini_model = genai.GenerativeModel(model)
171
+ except Exception as e:
172
+ logger.error(f"Error loading model {model}: {e}")
173
+ return [TextContent(type="text", text=f"Error: Failed to load model {model}")]
174
+
175
+ # Prepare content parts
176
+ parts = []
177
+
178
+ # Add system instruction if provided
179
+ if system_prompt:
180
+ # Gemini models use system_instruction parameter
181
+ generation_config = genai.types.GenerationConfig(
182
+ temperature=temperature,
183
+ max_output_tokens=GEMINI_MAX_OUTPUT_TOKENS
184
+ )
185
+ else:
186
+ generation_config = genai.types.GenerationConfig(
187
+ temperature=temperature,
188
+ max_output_tokens=GEMINI_MAX_OUTPUT_TOKENS
189
+ )
190
+
191
+ # Prepare content parts for Gemini
192
+ # Gemini API accepts a list where each part can be:
193
+ # - A string (for text)
194
+ # - A dict with "mime_type" and "data" keys (for binary data)
195
+ content_parts = []
196
+
197
+ # Prepare files if provided
198
+ if files:
199
+ gemini_files = prepare_gemini_files(files)
200
+ for file_part in gemini_files:
201
+ # Use genai.types.Part or dict format
202
+ content_parts.append({
203
+ "mime_type": file_part["mime_type"],
204
+ "data": file_part["data"]
205
+ })
206
+
207
+ # Add text prompt (as string)
208
+ content_parts.append(user_prompt)
209
+
210
+ # Generate content
211
+ try:
212
+ if system_prompt:
213
+ # Use system_instruction for models that support it
214
+ response = await asyncio.to_thread(
215
+ gemini_model.generate_content,
216
+ content_parts,
217
+ generation_config=generation_config,
218
+ system_instruction=system_prompt
219
+ )
220
+ else:
221
+ response = await asyncio.to_thread(
222
+ gemini_model.generate_content,
223
+ content_parts,
224
+ generation_config=generation_config
225
+ )
226
+
227
+ # Extract text from response
228
+ if response and response.text:
229
+ return [TextContent(type="text", text=response.text)]
230
+ else:
231
+ return [TextContent(type="text", text="Error: No response from Gemini")]
232
+
233
+ except Exception as e:
234
+ logger.error(f"Error generating content: {e}")
235
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
236
+
237
+ except Exception as e:
238
+ logger.error(f"Error in generate_content: {e}")
239
+ import traceback
240
+ logger.debug(traceback.format_exc())
241
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
242
+ else:
243
+ return [TextContent(type="text", text=f"Unknown tool: {name}")]
244
+
245
+ async def main():
246
+ """Main entry point"""
247
+ logger.info("Starting Gemini MCP Server...")
248
+ logger.info(f"Gemini API Key: {'Set' if GEMINI_API_KEY else 'Not Set'}")
249
+ logger.info(f"Default Model: {GEMINI_MODEL}")
250
+ logger.info(f"Default Lite Model: {GEMINI_MODEL_LITE}")
251
+
252
+ # Use stdio_server from mcp.server.stdio
253
+ from mcp.server.stdio import stdio_server
254
+
255
+ async with stdio_server() as streams:
256
+ await app.run(
257
+ streams[0], # read_stream
258
+ streams[1], # write_stream
259
+ app.create_initialization_options()
260
+ )
261
+
262
+ if __name__ == "__main__":
263
+ asyncio.run(main())
264
+
requirements.txt CHANGED
@@ -15,7 +15,7 @@ gradio
15
  # MCP dependencies (required for Gemini MCP)
16
  mcp
17
  nest-asyncio
18
- mcp-server
19
  # Fallback dependencies (used if MCP is not available)
20
  requests
21
  beautifulsoup4
 
15
  # MCP dependencies (required for Gemini MCP)
16
  mcp
17
  nest-asyncio
18
+ google-generativeai
19
  # Fallback dependencies (used if MCP is not available)
20
  requests
21
  beautifulsoup4