Spaces:
Running
on
Zero
Running
on
Zero
Commit
Β·
abad335
1
Parent(s):
610037b
Upd MCP tools logs
Browse files
agent.py
CHANGED
|
@@ -32,15 +32,16 @@ except ImportError:
|
|
| 32 |
sys.exit(1)
|
| 33 |
|
| 34 |
# Configure logging
|
| 35 |
-
|
|
|
|
| 36 |
logger = logging.getLogger(__name__)
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
#
|
| 40 |
mcp_logger = logging.getLogger("mcp")
|
| 41 |
-
mcp_logger.setLevel(logging.
|
| 42 |
root_logger = logging.getLogger("root")
|
| 43 |
-
root_logger.setLevel(logging.
|
| 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 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
"
|
| 130 |
-
|
| 131 |
-
"
|
| 132 |
-
|
| 133 |
-
"
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
"
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
"
|
| 142 |
-
|
| 143 |
-
"
|
| 144 |
-
"
|
| 145 |
-
"
|
| 146 |
-
"
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
| 148 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
}
|
| 150 |
},
|
| 151 |
-
"
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 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 |
-
#
|
| 283 |
-
# These are expected during the MCP initialization handshake
|
| 284 |
original_root_level = logging.getLogger("root").level
|
| 285 |
-
logging.getLogger("root").setLevel(logging.
|
| 286 |
|
| 287 |
try:
|
|
|
|
| 288 |
async with stdio_server() as streams:
|
| 289 |
-
|
| 290 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 307 |
# Fallback: try without NotificationOptions
|
| 308 |
try:
|
| 309 |
server_capabilities = app.get_capabilities()
|
| 310 |
-
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
| 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 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 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.
|
|
|
|
|
|
|
| 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 |
-
|
| 237 |
try:
|
| 238 |
if global_mcp_stdio_ctx is not None:
|
| 239 |
await global_mcp_stdio_ctx.__aexit__(None, None, None)
|
| 240 |
-
except:
|
| 241 |
-
|
| 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 |
-
|
| 278 |
-
|
| 279 |
|
| 280 |
-
#
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
|
|
|
|
|
|
|
|
|
| 285 |
try:
|
| 286 |
-
#
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
except Exception as e:
|
| 291 |
-
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
|
| 294 |
-
# Wait
|
| 295 |
-
|
| 296 |
-
await asyncio.sleep(
|
| 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 |
-
|
|
|
|
| 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
|
| 317 |
if init_attempt == 0:
|
| 318 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
|
| 320 |
-
#
|
| 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)
|
| 324 |
-
logger.debug(f"Server still initializing
|
| 325 |
await asyncio.sleep(wait_time)
|
| 326 |
continue
|
| 327 |
elif "invalid request" in error_str or "invalid request parameters" in error_str:
|
| 328 |
-
# This
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
if init_attempt < max_init_retries - 1:
|
| 330 |
-
wait_time = 0
|
| 331 |
-
logger.debug(f"
|
| 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"
|
| 337 |
await asyncio.sleep(wait_time)
|
| 338 |
else:
|
| 339 |
-
logger.error(f"β Could not list tools after {max_init_retries} attempts
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|