LiamKhoaLe commited on
Commit
abad335
Β·
1 Parent(s): 610037b

Upd MCP tools logs

Browse files
Files changed (2) hide show
  1. agent.py +89 -59
  2. app.py +142 -41
agent.py CHANGED
@@ -32,15 +32,16 @@ except ImportError:
32
  sys.exit(1)
33
 
34
  # Configure logging
35
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
 
36
  logger = logging.getLogger(__name__)
37
 
38
- # Suppress warnings from MCP SDK during initialization
39
- # These warnings are expected during the initialization handshake
40
  mcp_logger = logging.getLogger("mcp")
41
- mcp_logger.setLevel(logging.ERROR) # Only show errors, suppress warnings during init
42
  root_logger = logging.getLogger("root")
43
- root_logger.setLevel(logging.ERROR) # Suppress root logger warnings during init
44
 
45
  # Initialize Gemini
46
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
@@ -121,48 +122,56 @@ def prepare_gemini_files(files: list) -> list:
121
  async def list_tools() -> list[Tool]:
122
  """List available tools"""
123
  logger.info("πŸ“‹ MCP server received list_tools request")
124
- tools = [
125
- Tool(
126
- name="generate_content",
127
- description="Generate content using Gemini AI. Supports text generation, translation, summarization, document parsing, and audio transcription.",
128
- inputSchema={
129
- "type": "object",
130
- "properties": {
131
- "user_prompt": {
132
- "type": "string",
133
- "description": "User prompt for generation (required)"
134
- },
135
- "system_prompt": {
136
- "type": "string",
137
- "description": "System prompt to guide AI behavior (optional)"
138
- },
139
- "files": {
140
- "type": "array",
141
- "description": "Array of files to include in generation (optional)",
142
- "items": {
143
- "type": "object",
144
- "properties": {
145
- "path": {"type": "string", "description": "Path to file"},
146
- "content": {"type": "string", "description": "Base64 encoded file content"},
147
- "type": {"type": "string", "description": "MIME type (auto-detected from file extension)"}
 
 
 
148
  }
 
 
 
 
 
 
 
 
149
  }
150
  },
151
- "model": {
152
- "type": "string",
153
- "description": f"Gemini model to use (default: {GEMINI_MODEL})"
154
- },
155
- "temperature": {
156
- "type": "number",
157
- "description": f"Temperature for generation 0-2 (default: {GEMINI_TEMPERATURE})"
158
- }
159
- },
160
- "required": ["user_prompt"]
161
- }
162
- )
163
- ]
164
- logger.info(f"βœ… MCP server returning {len(tools)} tools")
165
- return tools
166
 
167
  @app.call_tool()
168
  async def call_tool(name: str, arguments: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
@@ -271,28 +280,30 @@ async def call_tool(name: str, arguments: dict) -> Sequence[TextContent | ImageC
271
 
272
  async def main():
273
  """Main entry point"""
 
274
  logger.info("Starting Gemini MCP Server...")
275
  logger.info(f"Gemini API Key: {'Set' if GEMINI_API_KEY else 'Not Set'}")
276
  logger.info(f"Default Model: {GEMINI_MODEL}")
277
  logger.info(f"Default Lite Model: {GEMINI_MODEL_LITE}")
 
278
 
279
  # Use stdio_server from mcp.server.stdio
280
  from mcp.server.stdio import stdio_server
281
 
282
- # Suppress root logger warnings during initialization
283
- # These are expected during the MCP initialization handshake
284
  original_root_level = logging.getLogger("root").level
285
- logging.getLogger("root").setLevel(logging.ERROR)
286
 
287
  try:
 
288
  async with stdio_server() as streams:
289
- # Restore logging after initialization
290
- logging.getLogger("root").setLevel(original_root_level)
291
- logger.info("βœ… MCP server stdio streams ready, starting server...")
292
 
293
  # Create initialization options
294
  # The Server class will automatically provide its capabilities based on
295
  # the registered @app.list_tools() and @app.call_tool() handlers
 
296
  try:
297
  # Try to get capabilities from the server if the method exists
298
  if hasattr(app, 'get_capabilities'):
@@ -303,39 +314,58 @@ async def main():
303
  notification_options=NotificationOptions(),
304
  experimental_capabilities={}
305
  )
306
- except (ImportError, AttributeError, TypeError):
 
 
307
  # Fallback: try without NotificationOptions
308
  try:
309
  server_capabilities = app.get_capabilities()
310
- except:
 
 
311
  # If get_capabilities doesn't work, create minimal capabilities
312
  server_capabilities = {}
313
  else:
 
314
  # Server will provide capabilities automatically, use empty dict
315
  server_capabilities = {}
316
  except Exception as e:
317
- logger.debug(f"Could not get server capabilities: {e}, server will provide defaults")
 
 
318
  # Server will handle capabilities automatically
319
  server_capabilities = {}
320
 
321
  # Create initialization options
322
  # The server_name and server_version are required
323
  # Capabilities will be automatically determined by the Server from registered handlers
 
324
  init_options = InitializationOptions(
325
  server_name="gemini-mcp-server",
326
  server_version="1.0.0",
327
  capabilities=server_capabilities
328
  )
 
329
 
330
  # Run the server with initialization options
331
- await app.run(
332
- read_stream=streams[0],
333
- write_stream=streams[1],
334
- initialization_options=init_options
335
- )
 
 
 
 
 
 
 
 
336
  except Exception as e:
337
  logging.getLogger("root").setLevel(original_root_level)
338
- logger.error(f"❌ MCP server error: {e}")
 
 
339
  raise
340
 
341
  if __name__ == "__main__":
 
32
  sys.exit(1)
33
 
34
  # Configure logging
35
+ # Use DEBUG level to see all MCP protocol messages
36
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
37
  logger = logging.getLogger(__name__)
38
 
39
+ # Enable detailed logging for MCP protocol debugging
40
+ # We want to see what requests the server receives
41
  mcp_logger = logging.getLogger("mcp")
42
+ mcp_logger.setLevel(logging.DEBUG) # Show all MCP protocol messages for debugging
43
  root_logger = logging.getLogger("root")
44
+ root_logger.setLevel(logging.INFO) # Show info level for root logger
45
 
46
  # Initialize Gemini
47
  GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY")
 
122
  async def list_tools() -> list[Tool]:
123
  """List available tools"""
124
  logger.info("πŸ“‹ MCP server received list_tools request")
125
+ logger.debug("list_tools() called - preparing tool list")
126
+ try:
127
+ tools = [
128
+ Tool(
129
+ name="generate_content",
130
+ description="Generate content using Gemini AI. Supports text generation, translation, summarization, document parsing, and audio transcription.",
131
+ inputSchema={
132
+ "type": "object",
133
+ "properties": {
134
+ "user_prompt": {
135
+ "type": "string",
136
+ "description": "User prompt for generation (required)"
137
+ },
138
+ "system_prompt": {
139
+ "type": "string",
140
+ "description": "System prompt to guide AI behavior (optional)"
141
+ },
142
+ "files": {
143
+ "type": "array",
144
+ "description": "Array of files to include in generation (optional)",
145
+ "items": {
146
+ "type": "object",
147
+ "properties": {
148
+ "path": {"type": "string", "description": "Path to file"},
149
+ "content": {"type": "string", "description": "Base64 encoded file content"},
150
+ "type": {"type": "string", "description": "MIME type (auto-detected from file extension)"}
151
+ }
152
  }
153
+ },
154
+ "model": {
155
+ "type": "string",
156
+ "description": f"Gemini model to use (default: {GEMINI_MODEL})"
157
+ },
158
+ "temperature": {
159
+ "type": "number",
160
+ "description": f"Temperature for generation 0-2 (default: {GEMINI_TEMPERATURE})"
161
  }
162
  },
163
+ "required": ["user_prompt"]
164
+ }
165
+ )
166
+ ]
167
+ logger.info(f"βœ… MCP server returning {len(tools)} tools: {[t.name for t in tools]}")
168
+ logger.debug(f"Tool details: {tools[0].name} - {tools[0].description[:50]}...")
169
+ return tools
170
+ except Exception as e:
171
+ logger.error(f"❌ Error in list_tools(): {e}")
172
+ import traceback
173
+ logger.debug(f"list_tools error traceback:\n{traceback.format_exc()}")
174
+ raise
 
 
 
175
 
176
  @app.call_tool()
177
  async def call_tool(name: str, arguments: dict) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
 
280
 
281
  async def main():
282
  """Main entry point"""
283
+ logger.info("=" * 60)
284
  logger.info("Starting Gemini MCP Server...")
285
  logger.info(f"Gemini API Key: {'Set' if GEMINI_API_KEY else 'Not Set'}")
286
  logger.info(f"Default Model: {GEMINI_MODEL}")
287
  logger.info(f"Default Lite Model: {GEMINI_MODEL_LITE}")
288
+ logger.info("=" * 60)
289
 
290
  # Use stdio_server from mcp.server.stdio
291
  from mcp.server.stdio import stdio_server
292
 
293
+ # Keep logging enabled for debugging
 
294
  original_root_level = logging.getLogger("root").level
295
+ logging.getLogger("root").setLevel(logging.INFO)
296
 
297
  try:
298
+ logger.info("πŸ”„ Setting up stdio_server...")
299
  async with stdio_server() as streams:
300
+ logger.info("βœ… MCP server stdio streams ready")
301
+ logger.debug(f"Streams: read={type(streams[0])}, write={type(streams[1])}")
 
302
 
303
  # Create initialization options
304
  # The Server class will automatically provide its capabilities based on
305
  # the registered @app.list_tools() and @app.call_tool() handlers
306
+ logger.debug("Preparing server capabilities...")
307
  try:
308
  # Try to get capabilities from the server if the method exists
309
  if hasattr(app, 'get_capabilities'):
 
314
  notification_options=NotificationOptions(),
315
  experimental_capabilities={}
316
  )
317
+ logger.debug(f"Got capabilities with NotificationOptions: {server_capabilities}")
318
+ except (ImportError, AttributeError, TypeError) as e:
319
+ logger.debug(f"NotificationOptions not available: {e}, trying without...")
320
  # Fallback: try without NotificationOptions
321
  try:
322
  server_capabilities = app.get_capabilities()
323
+ logger.debug(f"Got capabilities: {server_capabilities}")
324
+ except Exception as e2:
325
+ logger.debug(f"get_capabilities() failed: {e2}, using empty dict")
326
  # If get_capabilities doesn't work, create minimal capabilities
327
  server_capabilities = {}
328
  else:
329
+ logger.debug("Server doesn't have get_capabilities method, using empty dict")
330
  # Server will provide capabilities automatically, use empty dict
331
  server_capabilities = {}
332
  except Exception as e:
333
+ logger.warning(f"Could not get server capabilities: {e}, server will provide defaults")
334
+ import traceback
335
+ logger.debug(f"Capabilities error traceback:\n{traceback.format_exc()}")
336
  # Server will handle capabilities automatically
337
  server_capabilities = {}
338
 
339
  # Create initialization options
340
  # The server_name and server_version are required
341
  # Capabilities will be automatically determined by the Server from registered handlers
342
+ logger.info("πŸ“‹ Creating initialization options...")
343
  init_options = InitializationOptions(
344
  server_name="gemini-mcp-server",
345
  server_version="1.0.0",
346
  capabilities=server_capabilities
347
  )
348
+ logger.debug(f"Initialization options: {init_options}")
349
 
350
  # Run the server with initialization options
351
+ logger.info("πŸš€ Starting MCP server run loop...")
352
+ logger.info(" Server is ready to accept requests")
353
+ try:
354
+ await app.run(
355
+ read_stream=streams[0],
356
+ write_stream=streams[1],
357
+ initialization_options=init_options
358
+ )
359
+ except Exception as run_error:
360
+ logger.error(f"❌ Error in app.run(): {run_error}")
361
+ import traceback
362
+ logger.debug(f"app.run() error traceback:\n{traceback.format_exc()}")
363
+ raise
364
  except Exception as e:
365
  logging.getLogger("root").setLevel(original_root_level)
366
+ logger.error(f"❌ MCP server fatal error: {type(e).__name__}: {e}")
367
+ import traceback
368
+ logger.debug(f"Server error traceback:\n{traceback.format_exc()}")
369
  raise
370
 
371
  if __name__ == "__main__":
app.py CHANGED
@@ -33,6 +33,16 @@ from llama_index.llms.huggingface import HuggingFaceLLM
33
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
34
  from tqdm import tqdm
35
  from langdetect import detect, LangDetectException
 
 
 
 
 
 
 
 
 
 
36
  # MCP imports
37
  try:
38
  from mcp import ClientSession, StdioServerParameters
@@ -43,8 +53,18 @@ try:
43
  nest_asyncio.apply() # Allow nested event loops
44
  except ImportError:
45
  pass # nest_asyncio is optional
 
 
 
 
 
 
 
 
 
46
  MCP_AVAILABLE = True
47
- except ImportError:
 
48
  MCP_AVAILABLE = False
49
  # Fallback imports if MCP is not available
50
  from ddgs import DDGS
@@ -60,11 +80,6 @@ import numpy as np
60
  import soundfile as sf
61
  import tempfile
62
 
63
- os.environ["TOKENIZERS_PARALLELISM"] = "false"
64
- logging.basicConfig(level=logging.INFO)
65
- logger = logging.getLogger(__name__)
66
- hf_logging.set_verbosity_error()
67
-
68
  # Model configurations
69
  MEDSWIN_MODELS = {
70
  "MedSwin SFT": "MedSwin/MedSwin-7B-SFT",
@@ -218,27 +233,32 @@ async def get_mcp_session():
218
  global global_mcp_session, global_mcp_stdio_ctx
219
 
220
  if not MCP_AVAILABLE:
 
221
  return None
222
 
223
  # Check if session exists and is still valid
224
  if global_mcp_session is not None:
225
  try:
226
  # Test if session is still alive by listing tools
 
227
  await global_mcp_session.list_tools()
 
228
  return global_mcp_session
229
  except Exception as e:
230
- logger.debug(f"Existing MCP session invalid, recreating: {e}")
 
 
231
  # Clean up old session
232
  try:
233
  if global_mcp_session is not None:
234
  await global_mcp_session.__aexit__(None, None, None)
235
- except:
236
- pass
237
  try:
238
  if global_mcp_stdio_ctx is not None:
239
  await global_mcp_stdio_ctx.__aexit__(None, None, None)
240
- except:
241
- pass
242
  global_mcp_session = None
243
  global_mcp_stdio_ctx = None
244
 
@@ -248,12 +268,14 @@ async def get_mcp_session():
248
  mcp_env = os.environ.copy()
249
  if GEMINI_API_KEY:
250
  mcp_env["GEMINI_API_KEY"] = GEMINI_API_KEY
 
251
  else:
252
  logger.warning("GEMINI_API_KEY not set in environment. Gemini MCP features may not work.")
253
 
254
  # Add other Gemini MCP configuration if set
255
  if os.environ.get("GEMINI_MODEL"):
256
  mcp_env["GEMINI_MODEL"] = os.environ.get("GEMINI_MODEL")
 
257
  if os.environ.get("GEMINI_TIMEOUT"):
258
  mcp_env["GEMINI_TIMEOUT"] = os.environ.get("GEMINI_TIMEOUT")
259
  if os.environ.get("GEMINI_MAX_OUTPUT_TOKENS"):
@@ -262,81 +284,153 @@ async def get_mcp_session():
262
  mcp_env["GEMINI_TEMPERATURE"] = os.environ.get("GEMINI_TEMPERATURE")
263
 
264
  logger.info(f"Creating MCP client session with command: {MCP_SERVER_COMMAND} {MCP_SERVER_ARGS}")
 
 
265
  server_params = StdioServerParameters(
266
  command=MCP_SERVER_COMMAND,
267
  args=MCP_SERVER_ARGS,
268
  env=mcp_env
269
  )
270
 
 
271
  # Correct MCP SDK usage: stdio_client is an async context manager
272
  # that yields (read, write) streams
273
  stdio_ctx = stdio_client(server_params)
 
274
  read, write = await stdio_ctx.__aenter__()
 
 
 
 
 
275
 
276
  # Create ClientSession from the streams
277
- # The __aenter__() method automatically handles the initialization handshake
278
- session = ClientSession(read, write)
279
 
280
- # Wait longer for the server process to fully start
281
- # The server needs time to: start Python, import modules, initialize Gemini client, start MCP server
282
- logger.info("⏳ Waiting for MCP server process to start...")
283
- await asyncio.sleep(3.0) # Increased wait for server process startup
 
 
 
 
 
 
284
 
 
 
 
285
  try:
286
- # Initialize the session (this sends initialize request and waits for response)
287
- logger.info("πŸ”„ Initializing MCP session...")
288
- await session.__aenter__()
289
- logger.info("βœ… MCP session initialized, verifying tools...")
 
 
 
 
 
 
290
  except Exception as e:
291
- logger.warning(f"MCP session initialization had an issue (may be expected): {e}")
292
- # Continue anyway - the session might still work
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
- # Wait longer for the server to be fully ready after initialization
295
- # The server needs time to process the initialization and be ready for requests
296
- await asyncio.sleep(2.0) # Wait after initialization
297
 
298
  # Verify the session works by listing tools with retries
299
  # This confirms the server is ready to handle requests
 
300
  max_init_retries = 5
301
  tools_listed = False
302
  tools = None
303
  last_error = None
304
  for init_attempt in range(max_init_retries):
305
  try:
 
306
  tools = await session.list_tools()
 
 
307
  if tools and hasattr(tools, 'tools') and len(tools.tools) > 0:
308
- logger.info(f"βœ… MCP server ready with {len(tools.tools)} tools: {[t.name for t in tools.tools]}")
 
309
  tools_listed = True
310
  break
 
 
 
 
 
311
  except Exception as e:
312
  last_error = e
313
  error_str = str(e).lower()
314
  error_msg = str(e)
 
 
 
315
 
316
- # Log the actual error for debugging
317
  if init_attempt == 0:
318
- logger.debug(f"First list_tools attempt failed: {error_msg}")
 
 
 
 
 
319
 
320
- # Ignore initialization-related errors during the handshake phase
321
  if "initialization" in error_str or "before initialization" in error_str or "not initialized" in error_str:
322
  if init_attempt < max_init_retries - 1:
323
- wait_time = 0.5 * (init_attempt + 1) # Progressive wait: 0.5s, 1s, 1.5s...
324
- logger.debug(f"Server still initializing (attempt {init_attempt + 1}/{max_init_retries}), waiting {wait_time}s...")
325
  await asyncio.sleep(wait_time)
326
  continue
327
  elif "invalid request" in error_str or "invalid request parameters" in error_str:
328
- # This might be a timing issue - wait and retry
 
 
 
 
329
  if init_attempt < max_init_retries - 1:
330
- wait_time = 0.8 * (init_attempt + 1) # Longer wait for invalid request errors
331
- logger.debug(f"Invalid request error (attempt {init_attempt + 1}/{max_init_retries}), waiting {wait_time}s...")
332
  await asyncio.sleep(wait_time)
333
  continue
334
  elif init_attempt < max_init_retries - 1:
335
  wait_time = 0.5 * (init_attempt + 1)
336
- logger.debug(f"Tool listing attempt {init_attempt + 1}/{max_init_retries} failed: {error_msg}, waiting {wait_time}s...")
337
  await asyncio.sleep(wait_time)
338
  else:
339
- logger.error(f"❌ Could not list tools after {max_init_retries} attempts. Last error: {error_msg}")
340
  # Don't continue - if we can't list tools, the session is not usable
341
  try:
342
  await session.__aexit__(None, None, None)
@@ -350,7 +444,12 @@ async def get_mcp_session():
350
 
351
  if not tools_listed:
352
  error_msg = str(last_error) if last_error else "Unknown error"
353
- logger.error(f"MCP server failed to initialize - tools could not be listed. Last error: {error_msg}")
 
 
 
 
 
354
  try:
355
  await session.__aexit__(None, None, None)
356
  except:
@@ -364,12 +463,14 @@ async def get_mcp_session():
364
  # Store both the session and stdio context to keep them alive
365
  global_mcp_session = session
366
  global_mcp_stdio_ctx = stdio_ctx
367
- logger.info("MCP client session created successfully")
368
  return session
369
  except Exception as e:
370
- logger.error(f"Failed to create MCP client session: {e}")
 
 
371
  import traceback
372
- logger.debug(traceback.format_exc())
373
  global_mcp_session = None
374
  global_mcp_stdio_ctx = None
375
  return None
 
33
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
34
  from tqdm import tqdm
35
  from langdetect import detect, LangDetectException
36
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
37
+ # Enable DEBUG logging for MCP troubleshooting
38
+ # Set to logging.INFO in production for less verbose output
39
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
40
+ logger = logging.getLogger(__name__)
41
+ # Enable MCP client debugging
42
+ mcp_client_logger = logging.getLogger("mcp.client")
43
+ mcp_client_logger.setLevel(logging.DEBUG)
44
+ hf_logging.set_verbosity_error()
45
+
46
  # MCP imports
47
  try:
48
  from mcp import ClientSession, StdioServerParameters
 
53
  nest_asyncio.apply() # Allow nested event loops
54
  except ImportError:
55
  pass # nest_asyncio is optional
56
+
57
+ # Check MCP SDK version for debugging
58
+ try:
59
+ import mcp
60
+ mcp_version = getattr(mcp, '__version__', 'unknown')
61
+ logger.debug(f"MCP SDK version: {mcp_version}")
62
+ except:
63
+ logger.debug("Could not determine MCP SDK version")
64
+
65
  MCP_AVAILABLE = True
66
+ except ImportError as e:
67
+ logger.warning(f"MCP SDK not available: {e}")
68
  MCP_AVAILABLE = False
69
  # Fallback imports if MCP is not available
70
  from ddgs import DDGS
 
80
  import soundfile as sf
81
  import tempfile
82
 
 
 
 
 
 
83
  # Model configurations
84
  MEDSWIN_MODELS = {
85
  "MedSwin SFT": "MedSwin/MedSwin-7B-SFT",
 
233
  global global_mcp_session, global_mcp_stdio_ctx
234
 
235
  if not MCP_AVAILABLE:
236
+ logger.warning("MCP not available - SDK not installed")
237
  return None
238
 
239
  # Check if session exists and is still valid
240
  if global_mcp_session is not None:
241
  try:
242
  # Test if session is still alive by listing tools
243
+ logger.debug("Testing existing MCP session...")
244
  await global_mcp_session.list_tools()
245
+ logger.debug("Existing MCP session is valid")
246
  return global_mcp_session
247
  except Exception as e:
248
+ logger.warning(f"Existing MCP session invalid, recreating: {e}")
249
+ import traceback
250
+ logger.debug(f"Session validation error traceback: {traceback.format_exc()}")
251
  # Clean up old session
252
  try:
253
  if global_mcp_session is not None:
254
  await global_mcp_session.__aexit__(None, None, None)
255
+ except Exception as cleanup_error:
256
+ logger.debug(f"Error cleaning up session: {cleanup_error}")
257
  try:
258
  if global_mcp_stdio_ctx is not None:
259
  await global_mcp_stdio_ctx.__aexit__(None, None, None)
260
+ except Exception as cleanup_error:
261
+ logger.debug(f"Error cleaning up stdio context: {cleanup_error}")
262
  global_mcp_session = None
263
  global_mcp_stdio_ctx = None
264
 
 
268
  mcp_env = os.environ.copy()
269
  if GEMINI_API_KEY:
270
  mcp_env["GEMINI_API_KEY"] = GEMINI_API_KEY
271
+ logger.debug("GEMINI_API_KEY set in MCP server environment")
272
  else:
273
  logger.warning("GEMINI_API_KEY not set in environment. Gemini MCP features may not work.")
274
 
275
  # Add other Gemini MCP configuration if set
276
  if os.environ.get("GEMINI_MODEL"):
277
  mcp_env["GEMINI_MODEL"] = os.environ.get("GEMINI_MODEL")
278
+ logger.debug(f"GEMINI_MODEL set to: {mcp_env['GEMINI_MODEL']}")
279
  if os.environ.get("GEMINI_TIMEOUT"):
280
  mcp_env["GEMINI_TIMEOUT"] = os.environ.get("GEMINI_TIMEOUT")
281
  if os.environ.get("GEMINI_MAX_OUTPUT_TOKENS"):
 
284
  mcp_env["GEMINI_TEMPERATURE"] = os.environ.get("GEMINI_TEMPERATURE")
285
 
286
  logger.info(f"Creating MCP client session with command: {MCP_SERVER_COMMAND} {MCP_SERVER_ARGS}")
287
+ logger.debug(f"MCP server args type: {type(MCP_SERVER_ARGS)}, value: {MCP_SERVER_ARGS}")
288
+
289
  server_params = StdioServerParameters(
290
  command=MCP_SERVER_COMMAND,
291
  args=MCP_SERVER_ARGS,
292
  env=mcp_env
293
  )
294
 
295
+ logger.debug("Creating stdio_client context manager...")
296
  # Correct MCP SDK usage: stdio_client is an async context manager
297
  # that yields (read, write) streams
298
  stdio_ctx = stdio_client(server_params)
299
+ logger.debug("Entering stdio_client context...")
300
  read, write = await stdio_ctx.__aenter__()
301
+ logger.debug("βœ… stdio_client context entered, streams obtained")
302
+
303
+ # Wait for the server process to start and be ready
304
+ logger.info("⏳ Waiting for MCP server process to start...")
305
+ await asyncio.sleep(2.0) # Initial wait for server startup
306
 
307
  # Create ClientSession from the streams
308
+ logger.debug("Creating ClientSession from streams...")
309
+ logger.debug(f"Read stream type: {type(read)}, Write stream type: {type(write)}")
310
 
311
+ # Check if ClientSession has any required initialization parameters
312
+ try:
313
+ import inspect
314
+ session_init_sig = inspect.signature(ClientSession.__init__)
315
+ logger.debug(f"ClientSession.__init__ signature: {session_init_sig}")
316
+ except:
317
+ pass
318
+
319
+ session = ClientSession(read, write)
320
+ logger.debug(f"ClientSession object created: {type(session)}")
321
 
322
+ # Initialize the session (this sends initialize request and waits for response)
323
+ logger.info("πŸ”„ Initializing MCP session (sending initialize request)...")
324
+ logger.debug("About to call session.__aenter__() for initialization handshake...")
325
  try:
326
+ # The __aenter__() method handles the initialization handshake
327
+ # It sends the initialize request and waits for the initialize response
328
+ # According to MCP protocol, this should send:
329
+ # - initialize request with client info
330
+ # - wait for initialize response from server
331
+ init_result = await session.__aenter__()
332
+ logger.info(f"βœ… MCP session initialized successfully")
333
+ logger.debug(f"Initialization result: {init_result}")
334
+ if hasattr(init_result, '__dict__'):
335
+ logger.debug(f"Init result attributes: {init_result.__dict__}")
336
  except Exception as e:
337
+ error_msg = str(e)
338
+ error_type = type(e).__name__
339
+ logger.error(f"❌ MCP session initialization failed: {error_type}: {error_msg}")
340
+ import traceback
341
+ full_traceback = traceback.format_exc()
342
+ logger.debug(f"Initialization error traceback:\n{full_traceback}")
343
+
344
+ # Try to get more details about the error
345
+ if hasattr(e, 'args') and e.args:
346
+ logger.debug(f"Error args: {e.args}")
347
+ if hasattr(e, '__dict__'):
348
+ logger.debug(f"Error dict: {e.__dict__}")
349
+
350
+ # Check if this is a protocol error
351
+ if "invalid" in error_msg.lower() or "parameter" in error_msg.lower():
352
+ logger.error("⚠️ This appears to be a protocol-level error.")
353
+ logger.error(" Possible causes:")
354
+ logger.error(" 1. Server not ready to receive initialize request")
355
+ logger.error(" 2. Protocol version mismatch between client and server")
356
+ logger.error(" 3. Malformed initialization request")
357
+ logger.error(" 4. Server rejected initialization parameters")
358
+
359
+ # Clean up and return None
360
+ try:
361
+ await stdio_ctx.__aexit__(None, None, None)
362
+ except Exception as cleanup_error:
363
+ logger.debug(f"Error during cleanup: {cleanup_error}")
364
+ return None
365
 
366
+ # Wait for the server to be fully ready after initialization
367
+ logger.debug("Waiting for server to be ready after initialization...")
368
+ await asyncio.sleep(1.0) # Wait after initialization
369
 
370
  # Verify the session works by listing tools with retries
371
  # This confirms the server is ready to handle requests
372
+ logger.info("πŸ” Verifying MCP session by listing tools...")
373
  max_init_retries = 5
374
  tools_listed = False
375
  tools = None
376
  last_error = None
377
  for init_attempt in range(max_init_retries):
378
  try:
379
+ logger.debug(f"Attempting to list tools (attempt {init_attempt + 1}/{max_init_retries})...")
380
  tools = await session.list_tools()
381
+ logger.debug(f"list_tools() returned: type={type(tools)}, value={tools}")
382
+
383
  if tools and hasattr(tools, 'tools') and len(tools.tools) > 0:
384
+ tool_names = [t.name for t in tools.tools]
385
+ logger.info(f"βœ… MCP server ready with {len(tools.tools)} tools: {tool_names}")
386
  tools_listed = True
387
  break
388
+ else:
389
+ logger.warning(f"list_tools() returned empty or invalid result: {tools}")
390
+ if init_attempt < max_init_retries - 1:
391
+ await asyncio.sleep(0.5 * (init_attempt + 1))
392
+ continue
393
  except Exception as e:
394
  last_error = e
395
  error_str = str(e).lower()
396
  error_msg = str(e)
397
+ error_type = type(e).__name__
398
+
399
+ logger.error(f"❌ list_tools() failed (attempt {init_attempt + 1}/{max_init_retries}): {error_type}: {error_msg}")
400
 
401
+ # Log detailed error information
402
  if init_attempt == 0:
403
+ import traceback
404
+ logger.debug(f"First list_tools attempt error traceback:\n{traceback.format_exc()}")
405
+ if hasattr(e, 'args') and e.args:
406
+ logger.debug(f"Error args: {e.args}")
407
+ if hasattr(e, '__dict__'):
408
+ logger.debug(f"Error dict: {e.__dict__}")
409
 
410
+ # Handle different error types
411
  if "initialization" in error_str or "before initialization" in error_str or "not initialized" in error_str:
412
  if init_attempt < max_init_retries - 1:
413
+ wait_time = 0.5 * (init_attempt + 1)
414
+ logger.debug(f"Server still initializing, waiting {wait_time}s...")
415
  await asyncio.sleep(wait_time)
416
  continue
417
  elif "invalid request" in error_str or "invalid request parameters" in error_str:
418
+ # This is the key error we're seeing - log more details
419
+ logger.error(f"⚠️ Invalid request parameters error detected. This may indicate:")
420
+ logger.error(f" 1. Server not ready to accept requests yet")
421
+ logger.error(f" 2. Initialization handshake incomplete")
422
+ logger.error(f" 3. Protocol version mismatch")
423
  if init_attempt < max_init_retries - 1:
424
+ wait_time = 1.0 * (init_attempt + 1) # Longer wait for invalid request errors
425
+ logger.debug(f"Waiting {wait_time}s before retry...")
426
  await asyncio.sleep(wait_time)
427
  continue
428
  elif init_attempt < max_init_retries - 1:
429
  wait_time = 0.5 * (init_attempt + 1)
430
+ logger.debug(f"Waiting {wait_time}s before retry...")
431
  await asyncio.sleep(wait_time)
432
  else:
433
+ logger.error(f"❌ Could not list tools after {max_init_retries} attempts")
434
  # Don't continue - if we can't list tools, the session is not usable
435
  try:
436
  await session.__aexit__(None, None, None)
 
444
 
445
  if not tools_listed:
446
  error_msg = str(last_error) if last_error else "Unknown error"
447
+ error_type = type(last_error).__name__ if last_error else "UnknownError"
448
+ logger.error(f"❌ MCP server failed to initialize - tools could not be listed")
449
+ logger.error(f" Last error: {error_type}: {error_msg}")
450
+ if last_error:
451
+ import traceback
452
+ logger.debug(f"Final error traceback:\n{traceback.format_exc()}")
453
  try:
454
  await session.__aexit__(None, None, None)
455
  except:
 
463
  # Store both the session and stdio context to keep them alive
464
  global_mcp_session = session
465
  global_mcp_stdio_ctx = stdio_ctx
466
+ logger.info("βœ… MCP client session created and verified successfully")
467
  return session
468
  except Exception as e:
469
+ error_type = type(e).__name__
470
+ error_msg = str(e)
471
+ logger.error(f"❌ Failed to create MCP client session: {error_type}: {error_msg}")
472
  import traceback
473
+ logger.debug(f"Session creation error traceback:\n{traceback.format_exc()}")
474
  global_mcp_session = None
475
  global_mcp_stdio_ctx = None
476
  return None