#
tokens: 30799/50000 1/207 files (page 34/45)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 34 of 45. Use http://codebase.md/dicklesworthstone/llm_gateway_mcp_server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .cursorignore
├── .env.example
├── .envrc
├── .gitignore
├── additional_features.md
├── check_api_keys.py
├── completion_support.py
├── comprehensive_test.py
├── docker-compose.yml
├── Dockerfile
├── empirically_measured_model_speeds.json
├── error_handling.py
├── example_structured_tool.py
├── examples
│   ├── __init__.py
│   ├── advanced_agent_flows_using_unified_memory_system_demo.py
│   ├── advanced_extraction_demo.py
│   ├── advanced_unified_memory_system_demo.py
│   ├── advanced_vector_search_demo.py
│   ├── analytics_reporting_demo.py
│   ├── audio_transcription_demo.py
│   ├── basic_completion_demo.py
│   ├── cache_demo.py
│   ├── claude_integration_demo.py
│   ├── compare_synthesize_demo.py
│   ├── cost_optimization.py
│   ├── data
│   │   ├── sample_event.txt
│   │   ├── Steve_Jobs_Introducing_The_iPhone_compressed.md
│   │   └── Steve_Jobs_Introducing_The_iPhone_compressed.mp3
│   ├── docstring_refiner_demo.py
│   ├── document_conversion_and_processing_demo.py
│   ├── entity_relation_graph_demo.py
│   ├── filesystem_operations_demo.py
│   ├── grok_integration_demo.py
│   ├── local_text_tools_demo.py
│   ├── marqo_fused_search_demo.py
│   ├── measure_model_speeds.py
│   ├── meta_api_demo.py
│   ├── multi_provider_demo.py
│   ├── ollama_integration_demo.py
│   ├── prompt_templates_demo.py
│   ├── python_sandbox_demo.py
│   ├── rag_example.py
│   ├── research_workflow_demo.py
│   ├── sample
│   │   ├── article.txt
│   │   ├── backprop_paper.pdf
│   │   ├── buffett.pdf
│   │   ├── contract_link.txt
│   │   ├── legal_contract.txt
│   │   ├── medical_case.txt
│   │   ├── northwind.db
│   │   ├── research_paper.txt
│   │   ├── sample_data.json
│   │   └── text_classification_samples
│   │       ├── email_classification.txt
│   │       ├── news_samples.txt
│   │       ├── product_reviews.txt
│   │       └── support_tickets.txt
│   ├── sample_docs
│   │   └── downloaded
│   │       └── attention_is_all_you_need.pdf
│   ├── sentiment_analysis_demo.py
│   ├── simple_completion_demo.py
│   ├── single_shot_synthesis_demo.py
│   ├── smart_browser_demo.py
│   ├── sql_database_demo.py
│   ├── sse_client_demo.py
│   ├── test_code_extraction.py
│   ├── test_content_detection.py
│   ├── test_ollama.py
│   ├── text_classification_demo.py
│   ├── text_redline_demo.py
│   ├── tool_composition_examples.py
│   ├── tournament_code_demo.py
│   ├── tournament_text_demo.py
│   ├── unified_memory_system_demo.py
│   ├── vector_search_demo.py
│   ├── web_automation_instruction_packs.py
│   └── workflow_delegation_demo.py
├── LICENSE
├── list_models.py
├── marqo_index_config.json.example
├── mcp_protocol_schema_2025-03-25_version.json
├── mcp_python_lib_docs.md
├── mcp_tool_context_estimator.py
├── model_preferences.py
├── pyproject.toml
├── quick_test.py
├── README.md
├── resource_annotations.py
├── run_all_demo_scripts_and_check_for_errors.py
├── storage
│   └── smart_browser_internal
│       ├── locator_cache.db
│       ├── readability.js
│       └── storage_state.enc
├── test_client.py
├── test_connection.py
├── TEST_README.md
├── test_sse_client.py
├── test_stdio_client.py
├── tests
│   ├── __init__.py
│   ├── conftest.py
│   ├── integration
│   │   ├── __init__.py
│   │   └── test_server.py
│   ├── manual
│   │   ├── test_extraction_advanced.py
│   │   └── test_extraction.py
│   └── unit
│       ├── __init__.py
│       ├── test_cache.py
│       ├── test_providers.py
│       └── test_tools.py
├── TODO.md
├── tool_annotations.py
├── tools_list.json
├── ultimate_mcp_banner.webp
├── ultimate_mcp_logo.webp
├── ultimate_mcp_server
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli
│   │   ├── __init__.py
│   │   ├── __main__.py
│   │   ├── commands.py
│   │   ├── helpers.py
│   │   └── typer_cli.py
│   ├── clients
│   │   ├── __init__.py
│   │   ├── completion_client.py
│   │   └── rag_client.py
│   ├── config
│   │   └── examples
│   │       └── filesystem_config.yaml
│   ├── config.py
│   ├── constants.py
│   ├── core
│   │   ├── __init__.py
│   │   ├── evaluation
│   │   │   ├── base.py
│   │   │   └── evaluators.py
│   │   ├── providers
│   │   │   ├── __init__.py
│   │   │   ├── anthropic.py
│   │   │   ├── base.py
│   │   │   ├── deepseek.py
│   │   │   ├── gemini.py
│   │   │   ├── grok.py
│   │   │   ├── ollama.py
│   │   │   ├── openai.py
│   │   │   └── openrouter.py
│   │   ├── server.py
│   │   ├── state_store.py
│   │   ├── tournaments
│   │   │   ├── manager.py
│   │   │   ├── tasks.py
│   │   │   └── utils.py
│   │   └── ums_api
│   │       ├── __init__.py
│   │       ├── ums_database.py
│   │       ├── ums_endpoints.py
│   │       ├── ums_models.py
│   │       └── ums_services.py
│   ├── exceptions.py
│   ├── graceful_shutdown.py
│   ├── services
│   │   ├── __init__.py
│   │   ├── analytics
│   │   │   ├── __init__.py
│   │   │   ├── metrics.py
│   │   │   └── reporting.py
│   │   ├── cache
│   │   │   ├── __init__.py
│   │   │   ├── cache_service.py
│   │   │   ├── persistence.py
│   │   │   ├── strategies.py
│   │   │   └── utils.py
│   │   ├── cache.py
│   │   ├── document.py
│   │   ├── knowledge_base
│   │   │   ├── __init__.py
│   │   │   ├── feedback.py
│   │   │   ├── manager.py
│   │   │   ├── rag_engine.py
│   │   │   ├── retriever.py
│   │   │   └── utils.py
│   │   ├── prompts
│   │   │   ├── __init__.py
│   │   │   ├── repository.py
│   │   │   └── templates.py
│   │   ├── prompts.py
│   │   └── vector
│   │       ├── __init__.py
│   │       ├── embeddings.py
│   │       └── vector_service.py
│   ├── tool_token_counter.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── audio_transcription.py
│   │   ├── base.py
│   │   ├── completion.py
│   │   ├── docstring_refiner.py
│   │   ├── document_conversion_and_processing.py
│   │   ├── enhanced-ums-lookbook.html
│   │   ├── entity_relation_graph.py
│   │   ├── excel_spreadsheet_automation.py
│   │   ├── extraction.py
│   │   ├── filesystem.py
│   │   ├── html_to_markdown.py
│   │   ├── local_text_tools.py
│   │   ├── marqo_fused_search.py
│   │   ├── meta_api_tool.py
│   │   ├── ocr_tools.py
│   │   ├── optimization.py
│   │   ├── provider.py
│   │   ├── pyodide_boot_template.html
│   │   ├── python_sandbox.py
│   │   ├── rag.py
│   │   ├── redline-compiled.css
│   │   ├── sentiment_analysis.py
│   │   ├── single_shot_synthesis.py
│   │   ├── smart_browser.py
│   │   ├── sql_databases.py
│   │   ├── text_classification.py
│   │   ├── text_redline_tools.py
│   │   ├── tournament.py
│   │   ├── ums_explorer.html
│   │   └── unified_memory_system.py
│   ├── utils
│   │   ├── __init__.py
│   │   ├── async_utils.py
│   │   ├── display.py
│   │   ├── logging
│   │   │   ├── __init__.py
│   │   │   ├── console.py
│   │   │   ├── emojis.py
│   │   │   ├── formatter.py
│   │   │   ├── logger.py
│   │   │   ├── panels.py
│   │   │   ├── progress.py
│   │   │   └── themes.py
│   │   ├── parse_yaml.py
│   │   ├── parsing.py
│   │   ├── security.py
│   │   └── text.py
│   └── working_memory_api.py
├── unified_memory_system_technical_analysis.md
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/ultimate_mcp_server/core/server.py:
--------------------------------------------------------------------------------

```python
   1 | """Main server implementation for Ultimate MCP Server."""
   2 | 
   3 | import asyncio
   4 | import logging
   5 | import logging.config
   6 | import os
   7 | import sys
   8 | import time
   9 | from contextlib import asynccontextmanager
  10 | from dataclasses import dataclass
  11 | from functools import wraps
  12 | from typing import Any, Dict, List, Optional
  13 | 
  14 | from fastapi import FastAPI
  15 | from fastapi.middleware.cors import CORSMiddleware
  16 | from fastmcp import Context, FastMCP
  17 | 
  18 | import ultimate_mcp_server
  19 | 
  20 | # Import core specifically to set the global instance
  21 | import ultimate_mcp_server.core
  22 | from ultimate_mcp_server.config import get_config, load_config
  23 | from ultimate_mcp_server.constants import Provider
  24 | from ultimate_mcp_server.core.state_store import StateStore
  25 | 
  26 | # Import UMS API utilities and database functions
  27 | from ultimate_mcp_server.core.ums_api import (
  28 |     setup_ums_api,
  29 | )
  30 | from ultimate_mcp_server.graceful_shutdown import (
  31 |     create_quiet_server,
  32 |     enable_quiet_shutdown,
  33 |     register_shutdown_handler,
  34 | )
  35 | from ultimate_mcp_server.tools.smart_browser import (
  36 |     _ensure_initialized as smart_browser_ensure_initialized,
  37 | )
  38 | from ultimate_mcp_server.tools.smart_browser import (
  39 |     shutdown as smart_browser_shutdown,
  40 | )
  41 | from ultimate_mcp_server.tools.sql_databases import initialize_sql_tools, shutdown_sql_tools
  42 | 
  43 | # --- Import the trigger function directly instead of the whole module---
  44 | from ultimate_mcp_server.utils import get_logger
  45 | from ultimate_mcp_server.utils.logging import logger
  46 | 
  47 | # --- Define Logging Configuration Dictionary ---
  48 | 
  49 | LOG_FILE_PATH = "logs/ultimate_mcp_server.log"
  50 | 
  51 | # Ensure log directory exists before config is used
  52 | log_dir = os.path.dirname(LOG_FILE_PATH)
  53 | if log_dir:
  54 |     os.makedirs(log_dir, exist_ok=True)
  55 | 
  56 | LOGGING_CONFIG = {
  57 |     "version": 1,
  58 |     "disable_existing_loggers": False,  # Let Uvicorn's loggers pass through if needed
  59 |     "formatters": {
  60 |         "default": {
  61 |             "()": "uvicorn.logging.DefaultFormatter",
  62 |             "fmt": "%(levelprefix)s %(message)s",
  63 |             "use_colors": None,
  64 |         },
  65 |         "access": {
  66 |             "()": "uvicorn.logging.AccessFormatter",
  67 |             "fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
  68 |         },
  69 |         "file": {  # Formatter for file output
  70 |             "format": "%(asctime)s - %(name)s:%(lineno)d - %(levelname)s - %(message)s",
  71 |             "datefmt": "%Y-%m-%d %H:%M:%S",
  72 |         },
  73 |     },
  74 |     "handlers": {
  75 |         "default": {  # Console handler - redirect to stderr
  76 |             "formatter": "default",
  77 |             "class": "logging.StreamHandler",
  78 |             "stream": "ext://sys.stderr",  # Changed from stdout to stderr
  79 |         },
  80 |         "access": {  # Access log handler - redirect to stderr
  81 |             "formatter": "access",
  82 |             "class": "logging.StreamHandler",
  83 |             "stream": "ext://sys.stderr",  # Changed from stdout to stderr
  84 |         },
  85 |         "rich_console": {  # Rich console handler
  86 |             "()": "ultimate_mcp_server.utils.logging.formatter.create_rich_console_handler",
  87 |             "stderr": True,  # Add this parameter to use stderr
  88 |         },
  89 |         "file": {  # File handler
  90 |             "formatter": "file",
  91 |             "class": "logging.handlers.RotatingFileHandler",
  92 |             "filename": LOG_FILE_PATH,
  93 |             "maxBytes": 2 * 1024 * 1024,  # 2 MB
  94 |             "backupCount": 5,
  95 |             "encoding": "utf-8",
  96 |         },
  97 |         "tools_file": {  # Tools log file handler
  98 |             "formatter": "file",
  99 |             "class": "logging.FileHandler",
 100 |             "filename": "logs/direct_tools.log",
 101 |             "encoding": "utf-8",
 102 |         },
 103 |         "completions_file": {  # Completions log file handler
 104 |             "formatter": "file",
 105 |             "class": "logging.FileHandler",
 106 |             "filename": "logs/direct_completions.log",
 107 |             "encoding": "utf-8",
 108 |         },
 109 |     },
 110 |     "loggers": {
 111 |         "uvicorn": {"handlers": ["rich_console"], "level": "INFO", "propagate": False},
 112 |         "uvicorn.error": {"level": "INFO", "propagate": True},  # Propagate errors to root
 113 |         "uvicorn.access": {"handlers": ["access", "file"], "level": "INFO", "propagate": False},
 114 |         "ultimate_mcp_server": {  # Our application's logger namespace
 115 |             "handlers": ["rich_console", "file"],
 116 |             "level": "DEBUG",
 117 |             "propagate": False,
 118 |         },
 119 |         "ultimate_mcp_server.tools": {  # Tools-specific logger
 120 |             "handlers": ["tools_file"],
 121 |             "level": "DEBUG",
 122 |             "propagate": True,  # Propagate to parent for console display
 123 |         },
 124 |         "ultimate_mcp_server.completions": {  # Completions-specific logger
 125 |             "handlers": ["completions_file"],
 126 |             "level": "DEBUG",
 127 |             "propagate": True,  # Propagate to parent for console display
 128 |         },
 129 |     },
 130 |     "root": {  # Root logger configuration
 131 |         "level": "INFO",
 132 |         "handlers": ["rich_console", "file"],  # Root catches logs not handled by specific loggers
 133 |     },
 134 | }
 135 | 
 136 | # DO NOT apply the config here - it will be applied by Uvicorn through log_config parameter
 137 | 
 138 | # Global server instance
 139 | _server_app = None
 140 | _gateway_instance = None
 141 | 
 142 | # Get loggers
 143 | tools_logger = get_logger("ultimate_mcp_server.tools")
 144 | completions_logger = get_logger("ultimate_mcp_server.completions")
 145 | 
 146 | 
 147 | @dataclass
 148 | class ProviderStatus:
 149 |     """
 150 |     Structured representation of an LLM provider's configuration and availability status.
 151 | 
 152 |     This dataclass encapsulates all essential status information about a language model
 153 |     provider in the Ultimate MCP Server. It's used to track the state of each provider,
 154 |     including whether it's properly configured, successfully initialized, and what models
 155 |     it offers. This information is vital for:
 156 | 
 157 |     1. Displaying provider status to clients via API endpoints
 158 |     2. Making runtime decisions about provider availability
 159 |     3. Debugging provider configuration and connectivity issues
 160 |     4. Resource listings and capability discovery
 161 | 
 162 |     The status is typically maintained in the Gateway's provider_status dictionary,
 163 |     with provider names as keys and ProviderStatus instances as values.
 164 | 
 165 |     Attributes:
 166 |         enabled: Whether the provider is enabled in the configuration.
 167 |                 This reflects the user's intent, not actual availability.
 168 |         available: Whether the provider is successfully initialized and ready for use.
 169 |                   This is determined by runtime checks during server initialization.
 170 |         api_key_configured: Whether a valid API key was found for this provider.
 171 |                            A provider might be enabled but have no API key configured.
 172 |         models: List of available models from this provider, with each model represented
 173 |                as a dictionary containing model ID, name, and capabilities.
 174 |         error: Error message explaining why a provider is unavailable, or None if
 175 |               the provider initialized successfully or hasn't been initialized yet.
 176 |     """
 177 | 
 178 |     enabled: bool
 179 |     available: bool
 180 |     api_key_configured: bool
 181 |     models: List[Dict[str, Any]]
 182 |     error: Optional[str] = None
 183 | 
 184 | 
 185 | class Gateway:
 186 |     """
 187 |     Main Ultimate MCP Server implementation and central orchestrator.
 188 | 
 189 |     The Gateway class serves as the core of the Ultimate MCP Server, providing a unified
 190 |     interface to multiple LLM providers (OpenAI, Anthropic, etc.) and implementing the
 191 |     Model Control Protocol (MCP). It manages provider connections, tool registration,
 192 |     state persistence, and request handling.
 193 | 
 194 |     Key responsibilities:
 195 |     - Initializing and managing connections to LLM providers
 196 |     - Registering and exposing tools for model interaction
 197 |     - Providing consistent error handling and logging
 198 |     - Managing state persistence across requests
 199 |     - Exposing resources (guides, examples, reference info) for models
 200 |     - Implementing the MCP protocol for standardized model interaction
 201 | 
 202 |     The Gateway is designed to be instantiated once per server instance and serves
 203 |     as the central hub for all model interactions. It can be accessed globally through
 204 |     the ultimate_mcp_server.core._gateway_instance reference.
 205 |     """
 206 | 
 207 |     def __init__(
 208 |         self,
 209 |         name: str = "main",
 210 |         register_tools: bool = True,
 211 |         provider_exclusions: List[str] = None,
 212 |         load_all_tools: bool = False,  # Remove result_serialization_mode
 213 |     ):
 214 |         """
 215 |         Initialize the MCP Gateway with configured providers and tools.
 216 | 
 217 |         This constructor sets up the complete MCP Gateway environment, including:
 218 |         - Loading configuration from environment variables and config files
 219 |         - Setting up logging infrastructure
 220 |         - Initializing the MCP server framework
 221 |         - Creating a state store for persistence
 222 |         - Registering tools and resources based on configuration
 223 | 
 224 |         The initialization process is designed to be flexible, allowing for customization
 225 |         through the provided parameters and the configuration system. Provider initialization
 226 |         is deferred until server startup to ensure proper async handling.
 227 | 
 228 |         Args:
 229 |             name: Server instance name, used for logging and identification purposes.
 230 |                  Default is "main".
 231 |             register_tools: Whether to register standard MCP tools with the server.
 232 |                            If False, only the minimal core functionality will be available.
 233 |                            Default is True.
 234 |             provider_exclusions: List of provider names to exclude from initialization.
 235 |                                 This allows selectively disabling specific providers
 236 |                                 regardless of their configuration status.
 237 |                                 Default is None (no exclusions).
 238 |             load_all_tools: If True, load all available tools. If False (default),
 239 |                            load only the defined 'Base Toolset'.
 240 |         """
 241 |         self.name = name
 242 |         self.providers = {}
 243 |         self.provider_status = {}
 244 |         self.logger = get_logger(f"ultimate_mcp_server.{name}")
 245 |         self.event_handlers = {}
 246 |         self.provider_exclusions = provider_exclusions or []
 247 |         self.api_meta_tool = None  # Initialize api_meta_tool attribute
 248 |         self.load_all_tools = load_all_tools  # Store the flag
 249 | 
 250 |         # Load configuration if not already loaded
 251 |         if get_config() is None:
 252 |             self.logger.info("Initializing Gateway: Loading configuration...")
 253 |             load_config()
 254 | 
 255 |         # Initialize logger
 256 |         self.logger.info(f"Initializing {self.name}...")
 257 | 
 258 |         # Set MCP protocol version to 2025-03-25
 259 |         import os
 260 | 
 261 |         os.environ["MCP_PROTOCOL_VERSION"] = "2025-03-25"
 262 | 
 263 |         # Create MCP server with modern FastMCP constructor
 264 |         self.mcp = FastMCP(
 265 |             name=self.name,
 266 |             lifespan=self._server_lifespan,
 267 |             instructions=self.system_instructions,
 268 |         )
 269 | 
 270 |         # Initialize the state store
 271 |         persistence_dir = None
 272 |         if (
 273 |             get_config()
 274 |             and hasattr(get_config(), "state_persistence")
 275 |             and hasattr(get_config().state_persistence, "dir")
 276 |         ):
 277 |             persistence_dir = get_config().state_persistence.dir
 278 |         self.state_store = StateStore(persistence_dir)
 279 | 
 280 |         # Register tools if requested
 281 |         if register_tools:
 282 |             self._register_tools(load_all=self.load_all_tools)
 283 |             self._register_resources()
 284 | 
 285 |         self.logger.info(f"Ultimate MCP Server '{self.name}' initialized")
 286 | 
 287 |     def log_tool_calls(self, func):
 288 |         """
 289 |         Decorator to log MCP tool calls with detailed timing and result information.
 290 | 
 291 |         This decorator wraps MCP tool functions to provide consistent logging of:
 292 |         - Tool name and parameters at invocation time
 293 |         - Execution time for performance tracking
 294 |         - Success or failure status
 295 |         - Summarized results or error information
 296 | 
 297 |         The decorator ensures that all tool calls are logged to a dedicated tools logger,
 298 |         which helps with diagnostics, debugging, and monitoring of tool usage patterns.
 299 |         Successful calls include timing information and a brief summary of the result,
 300 |         while failed calls include exception details.
 301 | 
 302 |         Args:
 303 |             func: The async function to wrap with logging. This should be a tool function
 304 |                  registered with the MCP server that will be called by models.
 305 | 
 306 |         Returns:
 307 |             A wrapped async function that performs the same operations as the original
 308 |             but with added logging before and after execution.
 309 | 
 310 |         Note:
 311 |             This decorator is automatically applied to all functions registered as tools
 312 |             via the @mcp.tool() decorator in the _register_tools method, so it doesn't
 313 |             need to be applied manually in most cases.
 314 |         """
 315 | 
 316 |         @wraps(func)
 317 |         async def wrapper(*args, **kwargs):
 318 |             start_time = time.time()
 319 |             tool_name = func.__name__
 320 | 
 321 |             # Format parameters for logging
 322 |             args_str = ", ".join([repr(arg) for arg in args[1:] if arg is not None])
 323 |             kwargs_str = ", ".join([f"{k}={repr(v)}" for k, v in kwargs.items() if k != "ctx"])
 324 |             params_str = ", ".join(filter(None, [args_str, kwargs_str]))
 325 | 
 326 |             # Log the request - only through tools_logger
 327 |             tools_logger.info(f"TOOL CALL: {tool_name}({params_str})")
 328 | 
 329 |             try:
 330 |                 result = await func(*args, **kwargs)
 331 |                 processing_time = time.time() - start_time
 332 | 
 333 |                 # Format result for logging
 334 |                 if isinstance(result, dict):
 335 |                     result_keys = list(result.keys())
 336 |                     result_summary = f"dict with keys: {result_keys}"
 337 |                 else:
 338 |                     result_str = str(result)
 339 |                     result_summary = (
 340 |                         (result_str[:100] + "...") if len(result_str) > 100 else result_str
 341 |                     )
 342 | 
 343 |                 # Log successful completion - only through tools_logger
 344 |                 tools_logger.info(
 345 |                     f"TOOL SUCCESS: {tool_name} completed in {processing_time:.2f}s - Result: {result_summary}"
 346 |                 )
 347 | 
 348 |                 return result
 349 |             except Exception as e:
 350 |                 processing_time = time.time() - start_time
 351 |                 tools_logger.error(
 352 |                     f"TOOL ERROR: {tool_name} failed after {processing_time:.2f}s: {str(e)}",
 353 |                     exc_info=True,
 354 |                 )
 355 |                 raise
 356 | 
 357 |         return wrapper
 358 | 
 359 |     @asynccontextmanager
 360 |     async def _server_lifespan(self, server: FastMCP):
 361 |         """
 362 |         Async context manager managing the server lifecycle during startup and shutdown.
 363 | 
 364 |         This method implements the lifespan protocol used by FastMCP (based on ASGI) to:
 365 |         1. Perform startup initialization before the server begins accepting requests
 366 |         2. Clean up resources when the server is shutting down
 367 |         3. Make shared context available to request handlers during the server's lifetime
 368 | 
 369 |         During startup, this method:
 370 |         - Initializes all configured LLM providers
 371 |         - Triggers dynamic docstring generation for tools that need it
 372 |         - Sets the global Gateway instance for access from other components
 373 |         - Prepares a shared context dictionary for use by request handlers
 374 | 
 375 |         During shutdown, it:
 376 |         - Clears the global Gateway instance reference
 377 |         - Handles any necessary cleanup of resources
 378 | 
 379 |         The lifespan context is active throughout the entire server runtime, from
 380 |         startup until shutdown is initiated.
 381 | 
 382 |         Args:
 383 |             server: The FastMCP server instance that's starting up, which provides
 384 |                    the framework context for the lifespan.
 385 | 
 386 |         Yields:
 387 |             Dict containing initialized resources that will be available to all
 388 |             request handlers during the server's lifetime.
 389 | 
 390 |         Note:
 391 |             This method is called automatically by the FastMCP framework during
 392 |             server startup and is not intended to be called directly.
 393 |         """
 394 |         self.logger.info(f"Starting Ultimate MCP Server '{self.name}'")
 395 | 
 396 |         # Add a flag to track if this is an SSE instance
 397 |         is_sse_mode = getattr(self, '_sse_mode', False)
 398 |         if is_sse_mode:
 399 |             self.logger.info("SSE mode detected - using persistent lifespan management")
 400 | 
 401 |         # Initialize providers
 402 |         await self._initialize_providers()
 403 | 
 404 |         try:
 405 |             await initialize_sql_tools()
 406 |             self.logger.info("SQL tools state initialized.")
 407 |         except Exception as e:
 408 |             self.logger.error(f"Failed to initialize SQL tools state: {e}", exc_info=True)
 409 | 
 410 |         # --- OPTIONAL: Pre-initialize SmartBrowser ---
 411 |         try:
 412 |             self.logger.info("Pre-initializing Smart Browser components...")
 413 |             # Call the imported initialization function
 414 |             await smart_browser_ensure_initialized()
 415 |             self.logger.info("Smart Browser successfully pre-initialized.")
 416 |         except Exception as e:
 417 |             # Log warning but don't stop server startup if pre-init fails
 418 |             self.logger.warning(f"Could not pre-initialize Smart Browser: {e}", exc_info=True)
 419 |         # ---------------------------------------------------------------------
 420 | 
 421 |         # --- Trigger Dynamic Docstring Generation ---
 422 |         # This should run after config is loaded but before the server is fully ready
 423 |         # It checks cache and potentially calls an LLM.
 424 |         self.logger.info("Initiating dynamic docstring generation for Marqo tool...")
 425 |         try:
 426 |             # Import the function here to avoid circular imports
 427 |             from ultimate_mcp_server.tools.marqo_fused_search import (
 428 |                 trigger_dynamic_docstring_generation,
 429 |             )
 430 | 
 431 |             await trigger_dynamic_docstring_generation()
 432 |             self.logger.info("Dynamic docstring generation/loading complete.")
 433 |         except Exception as e:
 434 |             self.logger.error(
 435 |                 f"Error during dynamic docstring generation startup task: {e}", exc_info=True
 436 |             )
 437 |         # ---------------------------------------------
 438 | 
 439 |         # --- Set the global instance variable ---
 440 |         # Make the fully initialized instance accessible globally AFTER init
 441 |         ultimate_mcp_server.core._gateway_instance = self
 442 |         self.logger.info("Global gateway instance set.")
 443 |         # ----------------------------------------
 444 | 
 445 |         # --- Attach StateStore to application state ---
 446 |         # This makes the StateStore available to all tools via ctx.fastmcp._state_store
 447 |         # Note: In FastMCP 2.0+, we store the state_store directly on the server instance
 448 |         # Tools can access it via the with_state_management decorator
 449 |         server._state_store = self.state_store
 450 |         self.logger.info("StateStore attached to server instance.")
 451 |         # -----------------------------------------------
 452 | 
 453 |         # Create lifespan context (still useful for framework calls)
 454 |         context = {
 455 |             "providers": self.providers,
 456 |             "provider_status": self.provider_status,
 457 |         }
 458 | 
 459 |         self.logger.info("Lifespan context initialized, MCP server ready to handle requests")
 460 | 
 461 |         try:
 462 |             # Import and call trigger_dynamic_docstring_generation again
 463 |             from ultimate_mcp_server.tools.marqo_fused_search import (
 464 |                 trigger_dynamic_docstring_generation,
 465 |             )
 466 | 
 467 |             await trigger_dynamic_docstring_generation()
 468 |             logger.info("Dynamic docstring generation/loading complete.")
 469 |             
 470 |             if is_sse_mode:
 471 |                 # For SSE mode, create a persistent context that doesn't shutdown easily
 472 |                 self.logger.info("Creating persistent SSE lifespan context")
 473 |                 
 474 |                 # Add a keepalive task for SSE mode
 475 |                 async def sse_lifespan_keepalive():
 476 |                     """Keepalive task to maintain SSE server lifespan."""
 477 |                     while True:
 478 |                         await asyncio.sleep(60)  # Keep alive every minute
 479 |                         # This task existing keeps the lifespan active
 480 |                 
 481 |                 # Start the keepalive task
 482 |                 keepalive_task = asyncio.create_task(sse_lifespan_keepalive())
 483 |                 
 484 |                 try:
 485 |                     yield context
 486 |                 finally:
 487 |                     # Cancel the keepalive task during shutdown
 488 |                     keepalive_task.cancel()
 489 |                     try:
 490 |                         await keepalive_task
 491 |                     except asyncio.CancelledError:
 492 |                         pass
 493 |             else:
 494 |                 yield context
 495 |                 
 496 |         finally:
 497 |             if is_sse_mode:
 498 |                 self.logger.info("SSE mode shutdown initiated")
 499 |                 
 500 |             try:
 501 |                 # --- Shutdown SQL Tools State ---
 502 |                 await shutdown_sql_tools()
 503 |                 self.logger.info("SQL tools state shut down.")
 504 |             except Exception as e:
 505 |                 self.logger.error(f"Failed to shut down SQL tools state: {e}", exc_info=True)
 506 | 
 507 |             # 2. Shutdown Smart Browser explicitly
 508 |             try:
 509 |                 self.logger.info("Initiating explicit Smart Browser shutdown...")
 510 |                 await smart_browser_shutdown()  # Call the imported function
 511 |                 self.logger.info("Smart Browser shutdown completed successfully.")
 512 |             except Exception as e:
 513 |                 logger.error(f"Error during explicit Smart Browser shutdown: {e}", exc_info=True)
 514 | 
 515 |             # --- Clear the global instance on shutdown ---
 516 |             ultimate_mcp_server.core._gateway_instance = None
 517 |             self.logger.info("Global gateway instance cleared.")
 518 |             # -------------------------------------------
 519 |             self.logger.info(f"Shutting down Ultimate MCP Server '{self.name}'")
 520 | 
 521 |     async def _initialize_providers(self):
 522 |         """
 523 |         Initialize all enabled LLM providers based on the loaded configuration.
 524 | 
 525 |         This asynchronous method performs the following steps:
 526 |         1. Identifies which providers are enabled and properly configured with API keys
 527 |         2. Skips providers that are in the exclusion list (specified at Gateway creation)
 528 |         3. Initializes each valid provider in parallel using asyncio tasks
 529 |         4. Updates the provider_status dictionary with the initialization results
 530 | 
 531 |         The method uses a defensive approach, handling cases where:
 532 |         - A provider is enabled but missing API keys
 533 |         - Configuration is incomplete or inconsistent
 534 |         - Initialization errors occur with specific providers
 535 | 
 536 |         After initialization, the Gateway will have a populated providers dictionary
 537 |         with available provider instances, and a comprehensive provider_status dictionary
 538 |         with status information for all providers (including those that failed to initialize).
 539 | 
 540 |         This method is automatically called during server startup and is not intended
 541 |         to be called directly by users of the Gateway class.
 542 | 
 543 |         Raises:
 544 |             No exceptions are propagated from this method. All provider initialization
 545 |             errors are caught, logged, and reflected in the provider_status dictionary.
 546 |         """
 547 |         self.logger.info("Initializing LLM providers")
 548 | 
 549 |         cfg = get_config()
 550 |         providers_to_init = []
 551 | 
 552 |         # Determine which providers to initialize based SOLELY on the loaded config
 553 |         for provider_name in [p.value for p in Provider]:
 554 |             # Skip providers that are in the exclusion list
 555 |             if provider_name in self.provider_exclusions:
 556 |                 self.logger.debug(f"Skipping provider {provider_name} (excluded)")
 557 |                 continue
 558 | 
 559 |             provider_config = getattr(cfg.providers, provider_name, None)
 560 |             # Special exception for Ollama: it doesn't require an API key since it runs locally
 561 |             if (
 562 |                 provider_name == Provider.OLLAMA.value
 563 |                 and provider_config
 564 |                 and provider_config.enabled
 565 |             ):
 566 |                 self.logger.debug(
 567 |                     f"Found configured and enabled provider: {provider_name} (API key not required)"
 568 |                 )
 569 |                 providers_to_init.append(provider_name)
 570 |             # Check if the provider is enabled AND has an API key configured in the loaded settings
 571 |             elif provider_config and provider_config.enabled and provider_config.api_key:
 572 |                 self.logger.debug(f"Found configured and enabled provider: {provider_name}")
 573 |                 providers_to_init.append(provider_name)
 574 |             elif provider_config and provider_config.enabled:
 575 |                 self.logger.warning(
 576 |                     f"Provider {provider_name} is enabled but missing API key in config. Skipping."
 577 |                 )
 578 |             # else: # Provider not found in config or not enabled
 579 |             #     self.logger.debug(f"Provider {provider_name} not configured or not enabled.")
 580 | 
 581 |         # Initialize providers in parallel
 582 |         init_tasks = [
 583 |             asyncio.create_task(
 584 |                 self._initialize_provider(provider_name), name=f"init-{provider_name}"
 585 |             )
 586 |             for provider_name in providers_to_init
 587 |         ]
 588 | 
 589 |         if init_tasks:
 590 |             await asyncio.gather(*init_tasks)
 591 | 
 592 |         # Log initialization summary
 593 |         available_providers = [
 594 |             name for name, status in self.provider_status.items() if status.available
 595 |         ]
 596 |         self.logger.info(
 597 |             f"Providers initialized: {len(available_providers)}/{len(providers_to_init)} available"
 598 |         )
 599 | 
 600 |     async def _initialize_provider(self, provider_name: str):
 601 |         """
 602 |         Initialize a single LLM provider with its API key and configuration.
 603 | 
 604 |         This method is responsible for initializing an individual provider by:
 605 |         1. Retrieving the provider's configuration and API key
 606 |         2. Importing the appropriate provider class
 607 |         3. Instantiating the provider with the configured API key
 608 |         4. Calling the provider's initialize method to establish connectivity
 609 |         5. Recording the provider's status (including available models)
 610 | 
 611 |         The method handles errors gracefully, ensuring that exceptions during any
 612 |         stage of initialization are caught, logged, and reflected in the provider's
 613 |         status rather than propagated up the call stack.
 614 | 
 615 |         Args:
 616 |             provider_name: Name of the provider to initialize, matching a value
 617 |                           in the Provider enum (e.g., "openai", "anthropic").
 618 | 
 619 |         Returns:
 620 |             None. Results are stored in the Gateway's providers and provider_status
 621 |             dictionaries rather than returned directly.
 622 | 
 623 |         Note:
 624 |             This method is called by _initialize_providers during server startup
 625 |             and is not intended to be called directly by users of the Gateway class.
 626 |         """
 627 |         api_key = None
 628 |         api_key_configured = False
 629 |         provider_config = None
 630 | 
 631 |         try:
 632 |             cfg = get_config()
 633 |             provider_config = getattr(cfg.providers, provider_name, None)
 634 | 
 635 |             # Get API key ONLY from the loaded config object
 636 |             if provider_config and provider_config.api_key:
 637 |                 api_key = provider_config.api_key
 638 |                 api_key_configured = True
 639 |             # Special case for Ollama: doesn't require an API key
 640 |             elif provider_name == Provider.OLLAMA.value and provider_config:
 641 |                 api_key = None
 642 |                 api_key_configured = True
 643 |                 self.logger.debug("Initializing Ollama provider without API key (not required)")
 644 |             else:
 645 |                 # This case should ideally not be reached if checks in _initialize_providers are correct,
 646 |                 # but handle defensively.
 647 |                 self.logger.warning(
 648 |                     f"Attempted to initialize {provider_name}, but API key not found in loaded config."
 649 |                 )
 650 |                 api_key_configured = False
 651 | 
 652 |             if not api_key_configured:
 653 |                 # Record status for providers found in config but without a key
 654 |                 if provider_config:
 655 |                     self.provider_status[provider_name] = ProviderStatus(
 656 |                         enabled=provider_config.enabled,  # Reflects config setting
 657 |                         available=False,
 658 |                         api_key_configured=False,
 659 |                         models=[],
 660 |                         error="API key not found in loaded configuration",
 661 |                     )
 662 |                 # Do not log the warning here again, just return
 663 |                 return
 664 | 
 665 |             # --- API Key is configured, proceed with initialization ---
 666 |             self.logger.debug(f"Initializing provider {provider_name} with key from config.")
 667 | 
 668 |             # Import PROVIDER_REGISTRY to use centralized provider registry
 669 |             from ultimate_mcp_server.core.providers import PROVIDER_REGISTRY
 670 | 
 671 |             # Use the registry instead of hardcoded providers dictionary
 672 |             provider_class = PROVIDER_REGISTRY.get(provider_name)
 673 |             if not provider_class:
 674 |                 raise ValueError(f"Invalid provider name mapping: {provider_name}")
 675 | 
 676 |             # Instantiate provider with the API key retrieved from the config (via decouple)
 677 |             # Ensure provider classes' __init__ expect 'api_key' as a keyword argument
 678 |             provider = provider_class(api_key=api_key)
 679 | 
 680 |             # Initialize provider (which should use the config passed)
 681 |             available = await provider.initialize()
 682 | 
 683 |             # Update status based on initialization result
 684 |             if available:
 685 |                 models = await provider.list_models()
 686 |                 self.providers[provider_name] = provider
 687 |                 self.provider_status[provider_name] = ProviderStatus(
 688 |                     enabled=provider_config.enabled,
 689 |                     available=True,
 690 |                     api_key_configured=True,
 691 |                     models=models,
 692 |                 )
 693 |                 self.logger.success(
 694 |                     f"Provider {provider_name} initialized successfully with {len(models)} models",
 695 |                     emoji_key="provider",
 696 |                 )
 697 |             else:
 698 |                 self.provider_status[provider_name] = ProviderStatus(
 699 |                     enabled=provider_config.enabled,
 700 |                     available=False,
 701 |                     api_key_configured=True,  # Key was found, but init failed
 702 |                     models=[],
 703 |                     error="Initialization failed (check provider API status or logs)",
 704 |                 )
 705 |                 self.logger.error(
 706 |                     f"Provider {provider_name} initialization failed", emoji_key="error"
 707 |                 )
 708 | 
 709 |         except Exception as e:
 710 |             # Handle unexpected errors during initialization
 711 |             error_msg = f"Error initializing provider {provider_name}: {str(e)}"
 712 |             self.logger.error(error_msg, exc_info=True)
 713 |             # Ensure status is updated even on exceptions
 714 |             enabled_status = provider_config.enabled if provider_config else False  # Best guess
 715 |             self.provider_status[provider_name] = ProviderStatus(
 716 |                 enabled=enabled_status,
 717 |                 available=False,
 718 |                 api_key_configured=api_key_configured,  # Reflects if key was found before error
 719 |                 models=[],
 720 |                 error=error_msg,
 721 |             )
 722 | 
 723 |     @property
 724 |     def system_instructions(self) -> str:
 725 |         """
 726 |         Return comprehensive system-level instructions for LLMs on how to use the gateway.
 727 | 
 728 |         This property generates detailed instructions that are injected into the system prompt
 729 |         for LLMs using the Gateway. These instructions serve as a guide for LLMs to effectively
 730 |         utilize the available tools and capabilities, helping them understand:
 731 | 
 732 |         - The categories of available tools and their purposes
 733 |         - Best practices for provider and model selection
 734 |         - Error handling strategies and patterns
 735 |         - Recommendations for efficient and appropriate tool usage
 736 |         - Guidelines for choosing the right tool for specific tasks
 737 | 
 738 |         The instructions are designed to be clear and actionable, helping LLMs make
 739 |         informed decisions about when and how to use different components of the
 740 |         Ultimate MCP Server. They're structured in a hierarchical format with sections
 741 |         covering core categories, best practices, and additional resources.
 742 | 
 743 |         Returns:
 744 |             A formatted string containing detailed instructions for LLMs on how to
 745 |             effectively use the Gateway's tools and capabilities. These instructions
 746 |             are automatically included in the system prompt for all LLM interactions.
 747 |         """
 748 |         # Tool loading message can be adjusted based on self.load_all_tools if needed
 749 |         tool_loading_info = "all available tools" if self.load_all_tools else "the Base Toolset"
 750 | 
 751 |         return f"""
 752 | # Ultimate MCP Server Tool Usage Instructions
 753 |         
 754 | You have access to the Ultimate MCP Server, which provides unified access to multiple language model
 755 | providers (OpenAI, Anthropic, etc.) through a standardized interface. This server instance has loaded {tool_loading_info}. 
 756 | Follow these instructions to effectively use the available tools.
 757 | 
 758 | ## Core Tool Categories
 759 | 
 760 | 1. **Provider Tools**: Use these to discover available providers and models
 761 |    - `get_provider_status`: Check which providers are available
 762 |    - `list_models`: List models available from a specific provider
 763 | 
 764 | 2. **Completion Tools**: Use these for text generation
 765 |    - `generate_completion`: Single-prompt text generation (non-streaming)
 766 |    - `chat_completion`: Multi-turn conversation with message history
 767 |    - `multi_completion`: Compare outputs from multiple providers/models
 768 | 
 769 | 3. **Tournament Tools**: Use these to run competitions between models
 770 |    - `create_tournament`: Create and start a new tournament
 771 |    - `get_tournament_status`: Check tournament progress
 772 |    - `get_tournament_results`: Get detailed tournament results
 773 |    - `list_tournaments`: List all tournaments
 774 |    - `cancel_tournament`: Cancel a running tournament
 775 | 
 776 | ## Best Practices
 777 | 
 778 | 1. **Provider Selection**:
 779 |    - Always check provider availability with `get_provider_status` before use
 780 |    - Verify model availability with `list_models` before using specific models
 781 | 
 782 | 2. **Error Handling**:
 783 |    - All tools include error handling in their responses
 784 |    - Check for the presence of an "error" field in responses
 785 |    - If an error occurs, adapt your approach based on the error message
 786 | 
 787 | 3. **Efficient Usage**:
 788 |    - Use cached tools when repeatedly calling the same function with identical parameters
 789 |    - For long-running operations like tournaments, poll status periodically
 790 | 
 791 | 4. **Tool Selection Guidelines**:
 792 |    - For single-turn text generation → `generate_completion`
 793 |    - For conversation-based interactions → `chat_completion`
 794 |    - For comparing outputs across models → `multi_completion`
 795 |    - For evaluating model performance → Tournament tools
 796 | 
 797 | ## Additional Resources
 798 | 
 799 | For more detailed information and examples, access these MCP resources:
 800 | - `info://server`: Basic server information
 801 | - `info://tools`: Overview of available tools
 802 | - `provider://{{provider_name}}`: Details about a specific provider
 803 | - `guide://llm`: Comprehensive usage guide for LLMs
 804 | - `guide://error-handling`: Detailed error handling guidance
 805 | - `examples://workflows`: Detailed examples of common workflows
 806 | - `examples://completions`: Examples of different completion types
 807 | - `examples://tournaments`: Guidance on tournament configuration and analysis
 808 | 
 809 | Remember to use appropriate error handling and follow the documented parameter formats
 810 | for each tool. All providers may not be available at all times, so always check status
 811 | first and be prepared to adapt to available providers.
 812 | """
 813 | 
 814 |     def _register_tools(self, load_all: bool = False):
 815 |         """
 816 |         Register all MCP tools with the server instance.
 817 | 
 818 |         This internal method sets up all available tools in the Ultimate MCP Server,
 819 |         making them accessible to LLMs through the MCP protocol. It handles:
 820 | 
 821 |         1. Setting up the basic echo tool for connectivity testing
 822 |         2. Conditionally calling the register_all_tools function to set up either
 823 |            the 'Base Toolset' or all specialized tools based on the `load_all` flag.
 824 | 
 825 |         The registration process wraps each tool function with logging functionality
 826 |         via the log_tool_calls decorator, ensuring consistent logging behavior across
 827 |         all tools. This provides valuable diagnostic information during tool execution.
 828 | 
 829 |         All registered tools become available through the MCP interface and can be
 830 |         discovered and used by LLMs interacting with the server.
 831 | 
 832 |         Args:
 833 |             load_all: If True, register all tools. If False, register only the base set.
 834 | 
 835 |         Note:
 836 |             This method is called automatically during Gateway initialization when
 837 |             register_tools=True (the default) and is not intended to be called directly.
 838 |         """
 839 |         # Import here to avoid circular dependency
 840 |         from ultimate_mcp_server.tools import register_all_tools
 841 | 
 842 |         self.logger.info("Registering core tools...")
 843 | 
 844 |         # Echo tool - define the function first, then register it
 845 |         @self.log_tool_calls
 846 |         async def echo(message: str, ctx: Context = None) -> Dict[str, Any]:
 847 |             """
 848 |             Echo back the message for testing MCP connectivity.
 849 | 
 850 |             Args:
 851 |                 message: The message to echo back
 852 | 
 853 |             Returns:
 854 |                 Dictionary containing the echoed message
 855 |             """
 856 |             self.logger.info(f"Echo tool called with message: {message}")
 857 |             return {"message": message}
 858 | 
 859 |         # Now register the decorated function with mcp.tool
 860 |         self.mcp.tool(echo)
 861 | 
 862 |         # Define our base toolset - use function names not module names
 863 |         base_toolset = [
 864 |             # Completion tools
 865 |             "generate_completion",
 866 |             "chat_completion",
 867 |             "multi_completion",
 868 |             # "stream_completion", # Not that useful for MCP
 869 |             # Provider tools
 870 |             "get_provider_status",
 871 |             "list_models",
 872 |             # Filesystem tools
 873 |             "read_file",
 874 |             "read_multiple_files",
 875 |             "write_file",
 876 |             "edit_file",
 877 |             "create_directory",
 878 |             "list_directory",
 879 |             "directory_tree",
 880 |             "move_file",
 881 |             "search_files",
 882 |             "get_file_info",
 883 |             "list_allowed_directories",
 884 |             "get_unique_filepath",
 885 |             # Optimization tools
 886 |             "estimate_cost",
 887 |             "compare_models",
 888 |             "recommend_model",
 889 |             # Local text tools
 890 |             "run_ripgrep",
 891 |             "run_awk",
 892 |             "run_sed",
 893 |             "run_jq",
 894 |             # Search tools
 895 |             "marqo_fused_search",
 896 |             # SmartBrowser class methods
 897 |             "search",
 898 |             "download",
 899 |             "download_site_pdfs",
 900 |             "collect_documentation",
 901 |             "run_macro",
 902 |             "autopilot",
 903 |             # SQL class methods
 904 |             "manage_database",
 905 |             "execute_sql",
 906 |             "explore_database",
 907 |             "access_audit_log",
 908 |             # Document processing class methods
 909 |             "convert_document",
 910 |             "chunk_document",
 911 |             "clean_and_format_text_as_markdown",
 912 |             "batch_format_texts",
 913 |             "optimize_markdown_formatting",
 914 |             "generate_qa_pairs",
 915 |             "summarize_document",
 916 |             "ocr_image",
 917 |             "enhance_ocr_text",
 918 |             "analyze_pdf_structure",
 919 |             "extract_tables",
 920 |             "process_document_batch",
 921 |             # Python sandbox class methods
 922 |             "execute_python",
 923 |             "repl_python",
 924 |         ]
 925 | 
 926 |         # Conditionally register tools based on load_all flag
 927 |         if load_all:
 928 |             self.logger.info("Calling register_all_tools to register ALL available tools...")
 929 |             register_all_tools(self.mcp)
 930 |         else:
 931 |             self.logger.info("Calling register_all_tools to register only the BASE toolset...")
 932 |             # Check if tool_registration filter is enabled in config
 933 |             cfg = get_config()
 934 |             if cfg.tool_registration.filter_enabled:
 935 |                 # If filtering is already enabled, respect that configuration
 936 |                 self.logger.info("Tool filtering is enabled - using config filter settings")
 937 |                 register_all_tools(self.mcp)
 938 |             else:
 939 |                 # Otherwise, set up filtering for base toolset
 940 |                 cfg.tool_registration.filter_enabled = True
 941 |                 cfg.tool_registration.included_tools = base_toolset
 942 |                 self.logger.info(f"Registering base toolset: {', '.join(base_toolset)}")
 943 |                 register_all_tools(self.mcp)
 944 | 
 945 |         # After tools are registered, save the tool names to a file for the tools estimator script
 946 |         try:
 947 |             import json
 948 | 
 949 |             from ultimate_mcp_server.tools import STANDALONE_TOOL_FUNCTIONS
 950 | 
 951 |             # Get tools from STANDALONE_TOOL_FUNCTIONS plus class-based tools
 952 |             all_tool_names = []
 953 | 
 954 |             # Add standalone tool function names
 955 |             for tool_func in STANDALONE_TOOL_FUNCTIONS:
 956 |                 if hasattr(tool_func, "__name__"):
 957 |                     all_tool_names.append(tool_func.__name__)
 958 | 
 959 |             # Add echo tool
 960 |             all_tool_names.append("echo")
 961 | 
 962 |             # Write to file
 963 |             with open("tools_list.json", "w") as f:
 964 |                 json.dump(all_tool_names, f, indent=2)
 965 | 
 966 |             self.logger.info(
 967 |                 f"Wrote {len(all_tool_names)} tool names to tools_list.json for context estimator"
 968 |             )
 969 |         except Exception as e:
 970 |             self.logger.warning(f"Failed to write tool names to file: {str(e)}")
 971 | 
 972 |     def _register_resources(self):
 973 |         """
 974 |         Register all MCP resources with the server instance.
 975 | 
 976 |         This internal method registers standard MCP resources that provide static
 977 |         information and guidance to LLMs using the Ultimate MCP Server. Resources differ
 978 |         from tools in that they:
 979 | 
 980 |         1. Provide static reference information rather than interactive functionality
 981 |         2. Are accessed via URI-like identifiers (e.g., "info://server", "guide://llm")
 982 |         3. Don't require API calls or external services to generate their responses
 983 | 
 984 |         Registered resources include:
 985 |         - Server and tool information (info:// resources)
 986 |         - Provider details (provider:// resources)
 987 |         - Usage guides and tutorials (guide:// resources)
 988 |         - Example workflows and usage patterns (examples:// resources)
 989 | 
 990 |         These resources serve as a knowledge base for LLMs to better understand how to
 991 |         effectively use the available tools and follow best practices. They help reduce
 992 |         the need for extensive contextual information in prompts by making reference
 993 |         material available on-demand through the MCP protocol.
 994 | 
 995 |         Note:
 996 |             This method is called automatically during Gateway initialization when
 997 |             register_tools=True (the default) and is not intended to be called directly.
 998 |         """
 999 | 
1000 |         @self.mcp.resource("info://server")
1001 |         def get_server_info() -> Dict[str, Any]:
1002 |             """
1003 |             Get information about the Ultimate MCP Server server.
1004 | 
1005 |             This resource provides basic metadata about the Ultimate MCP Server server instance,
1006 |             including its name, version, and supported providers. Use this resource to
1007 |             discover server capabilities and version information.
1008 | 
1009 |             Resource URI: info://server
1010 | 
1011 |             Returns:
1012 |                 Dictionary containing server information:
1013 |                 - name: Name of the Ultimate MCP Server server
1014 |                 - version: Version of the Ultimate MCP Server server
1015 |                 - description: Brief description of server functionality
1016 |                 - providers: List of supported LLM provider names
1017 | 
1018 |             Example:
1019 |                 {
1020 |                     "name": "Ultimate MCP Server",
1021 |                     "version": "0.1.0",
1022 |                     "description": "MCP server for accessing multiple LLM providers",
1023 |                     "providers": ["openai", "anthropic", "deepseek", "gemini"]
1024 |                 }
1025 | 
1026 |             Usage:
1027 |                 This resource is useful for clients to verify server identity, check compatibility,
1028 |                 and discover basic capabilities. For detailed provider status, use the
1029 |                 get_provider_status tool instead.
1030 |             """
1031 |             return {
1032 |                 "name": self.name,
1033 |                 "version": "0.1.0",
1034 |                 "description": "MCP server for accessing multiple LLM providers",
1035 |                 "providers": [p.value for p in Provider],
1036 |             }
1037 | 
1038 |         @self.mcp.resource("info://tools")
1039 |         def get_tools_info() -> Dict[str, Any]:
1040 |             """
1041 |             Get information about available Ultimate MCP Server tools.
1042 | 
1043 |             This resource provides a descriptive overview of the tools available in the
1044 |             Ultimate MCP Server, organized by category. Use this resource to understand which
1045 |             tools are available and how they're organized.
1046 | 
1047 |             Resource URI: info://tools
1048 | 
1049 |             Returns:
1050 |                 Dictionary containing tools information organized by category:
1051 |                 - provider_tools: Tools for interacting with LLM providers
1052 |                 - completion_tools: Tools for text generation and completion
1053 |                 - tournament_tools: Tools for running model tournaments
1054 |                 - document_tools: Tools for document processing
1055 | 
1056 |             Example:
1057 |                 {
1058 |                     "provider_tools": {
1059 |                         "description": "Tools for accessing and managing LLM providers",
1060 |                         "tools": ["get_provider_status", "list_models"]
1061 |                     },
1062 |                     "completion_tools": {
1063 |                         "description": "Tools for text generation and completion",
1064 |                         "tools": ["generate_completion", "chat_completion", "multi_completion"]
1065 |                     },
1066 |                     "tournament_tools": {
1067 |                         "description": "Tools for running and managing model tournaments",
1068 |                         "tools": ["create_tournament", "list_tournaments", "get_tournament_status",
1069 |                                  "get_tournament_results", "cancel_tournament"]
1070 |                     }
1071 |                 }
1072 | 
1073 |             Usage:
1074 |                 Use this resource to understand the capabilities of the Ultimate MCP Server and
1075 |                 discover available tools. For detailed information about specific tools,
1076 |                 use the MCP list_tools method.
1077 |             """
1078 |             return {
1079 |                 "provider_tools": {
1080 |                     "description": "Tools for accessing and managing LLM providers",
1081 |                     "tools": ["get_provider_status", "list_models"],
1082 |                 },
1083 |                 "completion_tools": {
1084 |                     "description": "Tools for text generation and completion",
1085 |                     "tools": ["generate_completion", "chat_completion", "multi_completion"],
1086 |                 },
1087 |                 "tournament_tools": {
1088 |                     "description": "Tools for running and managing model tournaments",
1089 |                     "tools": [
1090 |                         "create_tournament",
1091 |                         "list_tournaments",
1092 |                         "get_tournament_status",
1093 |                         "get_tournament_results",
1094 |                         "cancel_tournament",
1095 |                     ],
1096 |                 },
1097 |                 "document_tools": {
1098 |                     "description": "Tools for document processing (placeholder for future implementation)",
1099 |                     "tools": [],
1100 |                 },
1101 |             }
1102 | 
1103 |         @self.mcp.resource("guide://llm")
1104 |         def get_llm_guide() -> str:
1105 |             """
1106 |             Usage guide for LLMs using the Ultimate MCP Server.
1107 | 
1108 |             This resource provides structured guidance specifically designed for LLMs to
1109 |             effectively use the tools and resources provided by the Ultimate MCP Server. It includes
1110 |             recommended tool selection strategies, common usage patterns, and examples.
1111 | 
1112 |             Resource URI: guide://llm
1113 | 
1114 |             Returns:
1115 |                 A detailed text guide with sections on tool selection, usage patterns,
1116 |                 and example workflows.
1117 | 
1118 |             Usage:
1119 |                 This resource is primarily intended to be included in context for LLMs
1120 |                 that will be using the gateway tools, to help them understand how to
1121 |                 effectively use the available capabilities.
1122 |             """
1123 |             return """
1124 |                 # Ultimate MCP Server Usage Guide for Language Models
1125 |                 
1126 |                 ## Overview
1127 |                 
1128 |                 The Ultimate MCP Server provides a set of tools for accessing multiple language model providers
1129 |                 (OpenAI, Anthropic, etc.) through a unified interface. This guide will help you understand
1130 |                 how to effectively use these tools.
1131 |                 
1132 |                 ## Tool Selection Guidelines
1133 |                 
1134 |                 ### For Text Generation:
1135 |                 
1136 |                 1. For single-prompt text generation:
1137 |                    - Use `generate_completion` with a specific provider and model
1138 |                 
1139 |                 2. For multi-turn conversations:
1140 |                    - Use `chat_completion` with a list of message dictionaries
1141 |                 
1142 |                 3. For streaming responses (real-time text output):
1143 |                    - Use streaming tools in the CompletionTools class
1144 |                 
1145 |                 4. For comparing outputs across providers:
1146 |                    - Use `multi_completion` with a list of provider configurations
1147 |                 
1148 |                 ### For Provider Management:
1149 |                 
1150 |                 1. To check available providers:
1151 |                    - Use `get_provider_status` to see which providers are available
1152 |                 
1153 |                 2. To list available models:
1154 |                    - Use `list_models` to view models from all providers or a specific provider
1155 |                 
1156 |                 ### For Running Tournaments:
1157 |                 
1158 |                 1. To create a new tournament:
1159 |                    - Use `create_tournament` with a prompt and list of model IDs
1160 |                 
1161 |                 2. To check tournament status:
1162 |                    - Use `get_tournament_status` with a tournament ID
1163 |                 
1164 |                 3. To get detailed tournament results:
1165 |                    - Use `get_tournament_results` with a tournament ID
1166 |                 
1167 |                 ## Common Workflows
1168 |                 
1169 |                 ### Provider Selection Workflow:
1170 |                 ```
1171 |                 1. Call get_provider_status() to see available providers
1172 |                 2. Call list_models(provider="openai") to see available models
1173 |                 3. Call generate_completion(prompt="...", provider="openai", model="gpt-4o")
1174 |                 ```
1175 |                 
1176 |                 ### Multi-Provider Comparison Workflow:
1177 |                 ```
1178 |                 1. Call multi_completion(
1179 |                       prompt="...",
1180 |                       providers=[
1181 |                           {"provider": "openai", "model": "gpt-4o"},
1182 |                           {"provider": "anthropic", "model": "claude-3-opus-20240229"}
1183 |                       ]
1184 |                    )
1185 |                 2. Compare results from each provider
1186 |                 ```
1187 |                 
1188 |                 ### Tournament Workflow:
1189 |                 ```
1190 |                 1. Call create_tournament(name="...", prompt="...", model_ids=["openai/gpt-4o", "anthropic/claude-3-opus"])
1191 |                 2. Store the tournament_id from the response
1192 |                 3. Call get_tournament_status(tournament_id="...") to monitor progress
1193 |                 4. Once status is "COMPLETED", call get_tournament_results(tournament_id="...")
1194 |                 ```
1195 |                 
1196 |                 ## Error Handling Best Practices
1197 |                 
1198 |                 1. Always check for "error" fields in tool responses
1199 |                 2. Verify provider availability before attempting to use specific models
1200 |                 3. For tournament tools, handle potential 404 errors for invalid tournament IDs
1201 |                 
1202 |                 ## Performance Considerations
1203 |                 
1204 |                 1. Most completion tools include token usage and cost metrics in their responses
1205 |                 2. Use caching decorators for repetitive requests to save costs
1206 |                 3. Consider using stream=True for long completions to improve user experience
1207 |             """
1208 | 
1209 |         @self.mcp.resource("provider://{{provider_name}}")
1210 |         def get_provider_info(provider_name: str) -> Dict[str, Any]:
1211 |             """
1212 |             Get detailed information about a specific LLM provider.
1213 | 
1214 |             This resource provides comprehensive information about a specific provider,
1215 |             including its capabilities, available models, and configuration status.
1216 | 
1217 |             Resource URI template: provider://{provider_name}
1218 | 
1219 |             Args:
1220 |                 provider_name: Name of the provider to retrieve information for
1221 |                               (e.g., "openai", "anthropic", "gemini")
1222 | 
1223 |             Returns:
1224 |                 Dictionary containing detailed provider information:
1225 |                 - name: Provider name
1226 |                 - status: Current status (enabled, available, etc.)
1227 |                 - capabilities: List of supported capabilities
1228 |                 - models: List of available models and their details
1229 |                 - config: Current configuration settings (with sensitive info redacted)
1230 | 
1231 |             Example:
1232 |                 {
1233 |                     "name": "openai",
1234 |                     "status": {
1235 |                         "enabled": true,
1236 |                         "available": true,
1237 |                         "api_key_configured": true,
1238 |                         "error": null
1239 |                     },
1240 |                     "capabilities": ["chat", "completion", "embeddings", "vision"],
1241 |                     "models": [
1242 |                         {
1243 |                             "id": "gpt-4o",
1244 |                             "name": "GPT-4o",
1245 |                             "context_window": 128000,
1246 |                             "features": ["chat", "completion", "vision"]
1247 |                         },
1248 |                         # More models...
1249 |                     ],
1250 |                     "config": {
1251 |                         "base_url": "https://api.openai.com/v1",
1252 |                         "timeout_seconds": 30,
1253 |                         "default_model": "gpt-4.1-mini"
1254 |                     }
1255 |                 }
1256 | 
1257 |             Error Handling:
1258 |                 If the provider doesn't exist or isn't configured, returns an appropriate
1259 |                 error message in the response.
1260 | 
1261 |             Usage:
1262 |                 Use this resource to get detailed information about a specific provider
1263 |                 before using its models for completions or other operations.
1264 |             """
1265 |             # Check if provider exists in status dictionary
1266 |             provider_status = self.provider_status.get(provider_name)
1267 |             if not provider_status:
1268 |                 return {
1269 |                     "name": provider_name,
1270 |                     "error": f"Provider '{provider_name}' not found or not configured",
1271 |                     "status": {"enabled": False, "available": False, "api_key_configured": False},
1272 |                     "models": [],
1273 |                 }
1274 | 
1275 |             # Get provider instance if available
1276 |             provider_instance = self.providers.get(provider_name)
1277 | 
1278 |             # Build capability list based on provider name
1279 |             capabilities = []
1280 |             if provider_name in [
1281 |                 Provider.OPENAI.value,
1282 |                 Provider.ANTHROPIC.value,
1283 |                 Provider.GEMINI.value,
1284 |             ]:
1285 |                 capabilities = ["chat", "completion"]
1286 | 
1287 |             if provider_name == Provider.OPENAI.value:
1288 |                 capabilities.extend(["embeddings", "vision", "image_generation"])
1289 |             elif provider_name == Provider.ANTHROPIC.value:
1290 |                 capabilities.extend(["vision"])
1291 | 
1292 |             # Return provider details
1293 |             return {
1294 |                 "name": provider_name,
1295 |                 "status": {
1296 |                     "enabled": provider_status.enabled,
1297 |                     "available": provider_status.available,
1298 |                     "api_key_configured": provider_status.api_key_configured,
1299 |                     "error": provider_status.error,
1300 |                 },
1301 |                 "capabilities": capabilities,
1302 |                 "models": provider_status.models,
1303 |                 "config": {
1304 |                     # Include non-sensitive config info
1305 |                     "default_model": provider_instance.default_model if provider_instance else None,
1306 |                     "timeout_seconds": 30,  # Example default
1307 |                 },
1308 |             }
1309 | 
1310 |         @self.mcp.resource("guide://error-handling")
1311 |         def get_error_handling_guide() -> Dict[str, Any]:
1312 |             """
1313 |             Get comprehensive guidance on handling errors from Ultimate MCP Server tools.
1314 | 
1315 |             This resource provides detailed information about common error patterns,
1316 |             error handling strategies, and recovery approaches for each tool in the
1317 |             Ultimate MCP Server. It helps LLMs understand how to gracefully handle and recover
1318 |             from various error conditions.
1319 | 
1320 |             Resource URI: guide://error-handling
1321 | 
1322 |             Returns:
1323 |                 Dictionary containing error handling guidance organized by tool type:
1324 |                 - provider_tools: Error handling for provider-related tools
1325 |                 - completion_tools: Error handling for completion tools
1326 |                 - tournament_tools: Error handling for tournament tools
1327 | 
1328 |             Usage:
1329 |                 This resource helps LLMs implement robust error handling when using
1330 |                 the Ultimate MCP Server tools, improving the resilience of their interactions.
1331 |             """
1332 |             return {
1333 |                 "general_principles": {
1334 |                     "error_detection": {
1335 |                         "description": "How to detect errors in tool responses",
1336 |                         "patterns": [
1337 |                             "Check for an 'error' field in the response dictionary",
1338 |                             "Look for status codes in error messages (e.g., 404, 500)",
1339 |                             "Check for empty or null results where data is expected",
1340 |                             "Look for 'warning' fields that may indicate partial success",
1341 |                         ],
1342 |                     },
1343 |                     "error_recovery": {
1344 |                         "description": "General strategies for recovering from errors",
1345 |                         "strategies": [
1346 |                             "Retry with different parameters when appropriate",
1347 |                             "Fallback to alternative tools or providers",
1348 |                             "Gracefully degrade functionality when optimal path is unavailable",
1349 |                             "Clearly communicate errors to users with context and suggestions",
1350 |                         ],
1351 |                     },
1352 |                 },
1353 |                 "provider_tools": {
1354 |                     "get_provider_status": {
1355 |                         "common_errors": [
1356 |                             {
1357 |                                 "error": "Server context not available",
1358 |                                 "cause": "The server may not be fully initialized",
1359 |                                 "handling": "Wait and retry or report server initialization issue",
1360 |                             },
1361 |                             {
1362 |                                 "error": "No providers are currently configured",
1363 |                                 "cause": "No LLM providers are enabled or initialization is incomplete",
1364 |                                 "handling": "Proceed with caution and check if specific providers are required",
1365 |                             },
1366 |                         ],
1367 |                         "recovery_strategies": [
1368 |                             "If no providers are available, clearly inform the user of limited capabilities",
1369 |                             "If specific providers are unavailable, suggest alternatives based on task requirements",
1370 |                         ],
1371 |                     },
1372 |                     "list_models": {
1373 |                         "common_errors": [
1374 |                             {
1375 |                                 "error": "Invalid provider",
1376 |                                 "cause": "Specified provider name doesn't exist or isn't configured",
1377 |                                 "handling": "Use valid providers from the error message's 'valid_providers' field",
1378 |                             },
1379 |                             {
1380 |                                 "warning": "Provider is configured but not available",
1381 |                                 "cause": "Provider API key issues or service connectivity problems",
1382 |                                 "handling": "Use an alternative provider or inform user of limited options",
1383 |                             },
1384 |                         ],
1385 |                         "recovery_strategies": [
1386 |                             "When provider is invalid, fall back to listing all available providers",
1387 |                             "When models list is empty, suggest using the default model or another provider",
1388 |                         ],
1389 |                     },
1390 |                 },
1391 |                 "completion_tools": {
1392 |                     "generate_completion": {
1393 |                         "common_errors": [
1394 |                             {
1395 |                                 "error": "Provider not available",
1396 |                                 "cause": "Specified provider doesn't exist or isn't configured",
1397 |                                 "handling": "Switch to an available provider (check with get_provider_status)",
1398 |                             },
1399 |                             {
1400 |                                 "error": "Failed to initialize provider",
1401 |                                 "cause": "API key configuration or network issues",
1402 |                                 "handling": "Try another provider or check provider status",
1403 |                             },
1404 |                             {
1405 |                                 "error": "Completion generation failed",
1406 |                                 "cause": "Provider API errors, rate limits, or invalid parameters",
1407 |                                 "handling": "Retry with different parameters or use another provider",
1408 |                             },
1409 |                         ],
1410 |                         "recovery_strategies": [
1411 |                             "Use multi_completion to try multiple providers simultaneously",
1412 |                             "Progressively reduce complexity (max_tokens, simplify prompt) if facing limits",
1413 |                             "Fall back to more reliable models if specialized ones are unavailable",
1414 |                         ],
1415 |                     },
1416 |                     "multi_completion": {
1417 |                         "common_errors": [
1418 |                             {
1419 |                                 "error": "Invalid providers format",
1420 |                                 "cause": "Providers parameter is not a list of provider configurations",
1421 |                                 "handling": "Correct the format to a list of dictionaries with provider info",
1422 |                             },
1423 |                             {
1424 |                                 "partial_failure": "Some providers failed",
1425 |                                 "cause": "Indicated by successful_count < total_providers",
1426 |                                 "handling": "Use the successful results and analyze error fields for failed ones",
1427 |                             },
1428 |                         ],
1429 |                         "recovery_strategies": [
1430 |                             "Focus on successful completions even if some providers failed",
1431 |                             "Check each provider's 'success' field to identify which ones worked",
1432 |                             "If timeout occurs, consider increasing the timeout parameter or reducing providers",
1433 |                         ],
1434 |                     },
1435 |                 },
1436 |                 "tournament_tools": {
1437 |                     "create_tournament": {
1438 |                         "common_errors": [
1439 |                             {
1440 |                                 "error": "Invalid input",
1441 |                                 "cause": "Missing required fields or validation errors",
1442 |                                 "handling": "Check all required parameters are provided with valid values",
1443 |                             },
1444 |                             {
1445 |                                 "error": "Failed to start tournament execution",
1446 |                                 "cause": "Server resource constraints or initialization errors",
1447 |                                 "handling": "Retry with fewer rounds or models, or try again later",
1448 |                             },
1449 |                         ],
1450 |                         "recovery_strategies": [
1451 |                             "Verify model IDs are valid before creating tournament",
1452 |                             "Start with simple tournaments to validate functionality before complex ones",
1453 |                             "Use error message details to correct specific input problems",
1454 |                         ],
1455 |                     },
1456 |                     "get_tournament_status": {
1457 |                         "common_errors": [
1458 |                             {
1459 |                                 "error": "Tournament not found",
1460 |                                 "cause": "Invalid tournament ID or tournament was deleted",
1461 |                                 "handling": "Verify tournament ID or use list_tournaments to see available tournaments",
1462 |                             },
1463 |                             {
1464 |                                 "error": "Invalid tournament ID format",
1465 |                                 "cause": "Tournament ID is not a string or is empty",
1466 |                                 "handling": "Ensure tournament ID is a valid string matching the expected format",
1467 |                             },
1468 |                         ],
1469 |                         "recovery_strategies": [
1470 |                             "When tournament not found, list all tournaments to find valid ones",
1471 |                             "If tournament status is FAILED, check error_message for details",
1472 |                             "Implement polling with backoff for monitoring long-running tournaments",
1473 |                         ],
1474 |                     },
1475 |                 },
1476 |                 "error_pattern_examples": {
1477 |                     "retry_with_fallback": {
1478 |                         "description": "Retry with fallback to another provider",
1479 |                         "example": """
1480 |                             # Try primary provider
1481 |                             result = generate_completion(prompt="...", provider="openai", model="gpt-4o")
1482 |                             
1483 |                             # Check for errors and fall back if needed
1484 |                             if "error" in result:
1485 |                                 logger.warning(f"Primary provider failed: {result['error']}")
1486 |                                 # Fall back to alternative provider
1487 |                                 result = generate_completion(prompt="...", provider="anthropic", model="claude-3-opus-20240229")
1488 |                         """,
1489 |                     },
1490 |                     "validation_before_call": {
1491 |                         "description": "Validate parameters before making tool calls",
1492 |                         "example": """
1493 |                             # Get available providers first
1494 |                             provider_status = get_provider_status()
1495 |                             
1496 |                             # Check if requested provider is available
1497 |                             requested_provider = "openai"
1498 |                             if requested_provider not in provider_status["providers"] or not provider_status["providers"][requested_provider]["available"]:
1499 |                                 # Fall back to any available provider
1500 |                                 available_providers = [p for p, status in provider_status["providers"].items() if status["available"]]
1501 |                                 if available_providers:
1502 |                                     requested_provider = available_providers[0]
1503 |                                 else:
1504 |                                     return {"error": "No LLM providers are available"}
1505 |                         """,
1506 |                     },
1507 |                 },
1508 |             }
1509 | 
1510 |         @self.mcp.resource("examples://workflows")
1511 |         def get_workflow_examples() -> Dict[str, Any]:
1512 |             """
1513 |             Get comprehensive examples of multi-tool workflows.
1514 | 
1515 |             This resource provides detailed, executable examples showing how to combine
1516 |             multiple tools into common workflows. These examples demonstrate best practices
1517 |             for tool sequencing, error handling, and result processing.
1518 | 
1519 |             Resource URI: examples://workflows
1520 | 
1521 |             Returns:
1522 |                 Dictionary containing workflow examples organized by scenario:
1523 |                 - basic_provider_selection: Example of selecting a provider and model
1524 |                 - model_comparison: Example of comparing outputs across providers
1525 |                 - tournaments: Example of creating and monitoring a tournament
1526 |                 - advanced_chat: Example of a multi-turn conversation with system prompts
1527 | 
1528 |             Usage:
1529 |                 These examples are designed to be used as reference by LLMs to understand
1530 |                 how to combine multiple tools in the Ultimate MCP Server to accomplish common tasks.
1531 |                 Each example includes expected outputs to help understand the flow.
1532 |             """
1533 |             return {
1534 |                 "basic_provider_selection": {
1535 |                     "description": "Selecting a provider and model for text generation",
1536 |                     "steps": [
1537 |                         {
1538 |                             "step": 1,
1539 |                             "tool": "get_provider_status",
1540 |                             "parameters": {},
1541 |                             "purpose": "Check which providers are available",
1542 |                             "example_output": {
1543 |                                 "providers": {
1544 |                                     "openai": {"available": True, "models_count": 12},
1545 |                                     "anthropic": {"available": True, "models_count": 6},
1546 |                                 }
1547 |                             },
1548 |                         },
1549 |                         {
1550 |                             "step": 2,
1551 |                             "tool": "list_models",
1552 |                             "parameters": {"provider": "openai"},
1553 |                             "purpose": "Get available models for the selected provider",
1554 |                             "example_output": {
1555 |                                 "models": {
1556 |                                     "openai": [
1557 |                                         {
1558 |                                             "id": "gpt-4o",
1559 |                                             "name": "GPT-4o",
1560 |                                             "features": ["chat", "completion"],
1561 |                                         }
1562 |                                     ]
1563 |                                 }
1564 |                             },
1565 |                         },
1566 |                         {
1567 |                             "step": 3,
1568 |                             "tool": "generate_completion",
1569 |                             "parameters": {
1570 |                                 "prompt": "Explain quantum computing in simple terms",
1571 |                                 "provider": "openai",
1572 |                                 "model": "gpt-4o",
1573 |                                 "temperature": 0.7,
1574 |                             },
1575 |                             "purpose": "Generate text with the selected provider and model",
1576 |                             "example_output": {
1577 |                                 "text": "Quantum computing is like...",
1578 |                                 "model": "gpt-4o",
1579 |                                 "provider": "openai",
1580 |                                 "tokens": {"input": 8, "output": 150, "total": 158},
1581 |                                 "cost": 0.000123,
1582 |                             },
1583 |                         },
1584 |                     ],
1585 |                     "error_handling": [
1586 |                         "If get_provider_status shows provider unavailable, try a different provider",
1587 |                         "If list_models returns empty list, select a different provider",
1588 |                         "If generate_completion returns an error, check the error message for guidance",
1589 |                     ],
1590 |                 },
1591 |                 "model_comparison": {
1592 |                     "description": "Comparing multiple models on the same task",
1593 |                     "steps": [
1594 |                         {
1595 |                             "step": 1,
1596 |                             "tool": "multi_completion",
1597 |                             "parameters": {
1598 |                                 "prompt": "Write a haiku about programming",
1599 |                                 "providers": [
1600 |                                     {"provider": "openai", "model": "gpt-4o"},
1601 |                                     {"provider": "anthropic", "model": "claude-3-opus-20240229"},
1602 |                                 ],
1603 |                                 "temperature": 0.7,
1604 |                             },
1605 |                             "purpose": "Generate completions from multiple providers simultaneously",
1606 |                             "example_output": {
1607 |                                 "results": {
1608 |                                     "openai/gpt-4o": {
1609 |                                         "success": True,
1610 |                                         "text": "Code flows like water\nBugs emerge from the depths\nPatience brings order",
1611 |                                         "model": "gpt-4o",
1612 |                                     },
1613 |                                     "anthropic/claude-3-opus-20240229": {
1614 |                                         "success": True,
1615 |                                         "text": "Fingers dance on keys\nLogic blooms in silent thought\nPrograms come alive",
1616 |                                         "model": "claude-3-opus-20240229",
1617 |                                     },
1618 |                                 },
1619 |                                 "successful_count": 2,
1620 |                                 "total_providers": 2,
1621 |                             },
1622 |                         },
1623 |                         {
1624 |                             "step": 2,
1625 |                             "suggestion": "Compare the results for quality, style, and adherence to the haiku format",
1626 |                         },
1627 |                     ],
1628 |                     "error_handling": [
1629 |                         "Check successful_count vs total_providers to see if all providers succeeded",
1630 |                         "For each provider, check the success field to determine if it completed successfully",
1631 |                         "If a provider failed, look at its error field for details",
1632 |                     ],
1633 |                 },
1634 |                 "tournaments": {
1635 |                     "description": "Creating and monitoring a multi-model tournament",
1636 |                     "steps": [
1637 |                         {
1638 |                             "step": 1,
1639 |                             "tool": "create_tournament",
1640 |                             "parameters": {
1641 |                                 "name": "Sorting Algorithm Tournament",
1642 |                                 "prompt": "Implement a quicksort algorithm in Python that handles duplicates efficiently",
1643 |                                 "model_ids": ["openai/gpt-4o", "anthropic/claude-3-opus-20240229"],
1644 |                                 "rounds": 3,
1645 |                                 "tournament_type": "code",
1646 |                             },
1647 |                             "purpose": "Create a new tournament comparing multiple models",
1648 |                             "example_output": {
1649 |                                 "tournament_id": "tour_abc123xyz789",
1650 |                                 "status": "PENDING",
1651 |                             },
1652 |                         },
1653 |                         {
1654 |                             "step": 2,
1655 |                             "tool": "get_tournament_status",
1656 |                             "parameters": {"tournament_id": "tour_abc123xyz789"},
1657 |                             "purpose": "Check if the tournament has started running",
1658 |                             "example_output": {
1659 |                                 "tournament_id": "tour_abc123xyz789",
1660 |                                 "status": "RUNNING",
1661 |                                 "current_round": 1,
1662 |                                 "total_rounds": 3,
1663 |                             },
1664 |                         },
1665 |                         {
1666 |                             "step": 3,
1667 |                             "suggestion": "Wait for the tournament to complete",
1668 |                             "purpose": "Tournaments run asynchronously and may take time to complete",
1669 |                         },
1670 |                         {
1671 |                             "step": 4,
1672 |                             "tool": "get_tournament_results",
1673 |                             "parameters": {"tournament_id": "tour_abc123xyz789"},
1674 |                             "purpose": "Retrieve full results once the tournament is complete",
1675 |                             "example_output": {
1676 |                                 "tournament_id": "tour_abc123xyz789",
1677 |                                 "status": "COMPLETED",
1678 |                                 "rounds_data": [
1679 |                                     {
1680 |                                         "round_number": 1,
1681 |                                         "model_outputs": {
1682 |                                             "openai/gpt-4o": "def quicksort(arr): ...",
1683 |                                             "anthropic/claude-3-opus-20240229": "def quicksort(arr): ...",
1684 |                                         },
1685 |                                         "scores": {
1686 |                                             "openai/gpt-4o": 0.85,
1687 |                                             "anthropic/claude-3-opus-20240229": 0.92,
1688 |                                         },
1689 |                                     }
1690 |                                     # Additional rounds would be here in a real response
1691 |                                 ],
1692 |                             },
1693 |                         },
1694 |                     ],
1695 |                     "error_handling": [
1696 |                         "If create_tournament fails, check the error message for missing or invalid parameters",
1697 |                         "If get_tournament_status returns an error, verify the tournament_id is correct",
1698 |                         "If tournament status is FAILED, check the error_message field for details",
1699 |                     ],
1700 |                 },
1701 |                 "advanced_chat": {
1702 |                     "description": "Multi-turn conversation with system prompt and context",
1703 |                     "steps": [
1704 |                         {
1705 |                             "step": 1,
1706 |                             "tool": "chat_completion",
1707 |                             "parameters": {
1708 |                                 "messages": [
1709 |                                     {
1710 |                                         "role": "user",
1711 |                                         "content": "Hello, can you help me with Python?",
1712 |                                     }
1713 |                                 ],
1714 |                                 "provider": "anthropic",
1715 |                                 "model": "claude-3-opus-20240229",
1716 |                                 "system_prompt": "You are an expert Python tutor. Provide concise, helpful answers with code examples when appropriate.",
1717 |                                 "temperature": 0.5,
1718 |                             },
1719 |                             "purpose": "Start a conversation with a system prompt for context",
1720 |                             "example_output": {
1721 |                                 "text": "Hello! I'd be happy to help you with Python. What specific aspect are you interested in learning about?",
1722 |                                 "model": "claude-3-opus-20240229",
1723 |                                 "provider": "anthropic",
1724 |                             },
1725 |                         },
1726 |                         {
1727 |                             "step": 2,
1728 |                             "tool": "chat_completion",
1729 |                             "parameters": {
1730 |                                 "messages": [
1731 |                                     {
1732 |                                         "role": "user",
1733 |                                         "content": "Hello, can you help me with Python?",
1734 |                                     },
1735 |                                     {
1736 |                                         "role": "assistant",
1737 |                                         "content": "Hello! I'd be happy to help you with Python. What specific aspect are you interested in learning about?",
1738 |                                     },
1739 |                                     {
1740 |                                         "role": "user",
1741 |                                         "content": "How do I write a function that checks if a string is a palindrome?",
1742 |                                     },
1743 |                                 ],
1744 |                                 "provider": "anthropic",
1745 |                                 "model": "claude-3-opus-20240229",
1746 |                                 "system_prompt": "You are an expert Python tutor. Provide concise, helpful answers with code examples when appropriate.",
1747 |                                 "temperature": 0.5,
1748 |                             },
1749 |                             "purpose": "Continue the conversation by including the full message history",
1750 |                             "example_output": {
1751 |                                 "text": "Here's a simple function to check if a string is a palindrome in Python:\n\n```python\ndef is_palindrome(s):\n    # Remove spaces and convert to lowercase for more flexible matching\n    s = s.lower().replace(' ', '')\n    # Compare the string with its reverse\n    return s == s[::-1]\n\n# Examples\nprint(is_palindrome('racecar'))  # True\nprint(is_palindrome('hello'))    # False\nprint(is_palindrome('A man a plan a canal Panama'))  # True\n```\n\nThis function works by:\n1. Converting the string to lowercase and removing spaces\n2. Checking if the processed string equals its reverse (using slice notation `[::-1]`)\n\nIs there anything specific about this solution you'd like me to explain further?",
1752 |                                 "model": "claude-3-opus-20240229",
1753 |                                 "provider": "anthropic",
1754 |                             },
1755 |                         },
1756 |                     ],
1757 |                     "error_handling": [
1758 |                         "Always include the full conversation history in the messages array",
1759 |                         "Ensure each message has both 'role' and 'content' fields",
1760 |                         "If using system_prompt, ensure it's appropriate for the provider",
1761 |                     ],
1762 |                 },
1763 |             }
1764 | 
1765 |         @self.mcp.resource("examples://completions")
1766 |         def get_completion_examples() -> Dict[str, Any]:
1767 |             """
1768 |             Get examples of different completion types and when to use them.
1769 | 
1770 |             This resource provides detailed examples of different completion tools available
1771 |             in the Ultimate MCP Server, along with guidance on when to use each type. It helps with
1772 |             selecting the most appropriate completion tool for different scenarios.
1773 | 
1774 |             Resource URI: examples://completions
1775 | 
1776 |             Returns:
1777 |                 Dictionary containing completion examples organized by type:
1778 |                 - standard_completion: When to use generate_completion
1779 |                 - chat_completion: When to use chat_completion
1780 |                 - streaming_completion: When to use stream_completion
1781 |                 - multi_provider: When to use multi_completion
1782 | 
1783 |             Usage:
1784 |                 This resource helps LLMs understand the appropriate completion tool
1785 |                 to use for different scenarios, with concrete examples and use cases.
1786 |             """
1787 |             return {
1788 |                 "standard_completion": {
1789 |                     "tool": "generate_completion",
1790 |                     "description": "Single-turn text generation without streaming",
1791 |                     "best_for": [
1792 |                         "Simple, one-off text generation tasks",
1793 |                         "When you need a complete response at once",
1794 |                         "When you don't need conversation history",
1795 |                     ],
1796 |                     "example": {
1797 |                         "request": {
1798 |                             "prompt": "Explain the concept of quantum entanglement in simple terms",
1799 |                             "provider": "openai",
1800 |                             "model": "gpt-4o",
1801 |                             "temperature": 0.7,
1802 |                         },
1803 |                         "response": {
1804 |                             "text": "Quantum entanglement is like having two magic coins...",
1805 |                             "model": "gpt-4o",
1806 |                             "provider": "openai",
1807 |                             "tokens": {"input": 10, "output": 150, "total": 160},
1808 |                             "cost": 0.00032,
1809 |                             "processing_time": 2.1,
1810 |                         },
1811 |                     },
1812 |                 },
1813 |                 "chat_completion": {
1814 |                     "tool": "chat_completion",
1815 |                     "description": "Multi-turn conversation with message history",
1816 |                     "best_for": [
1817 |                         "Maintaining conversation context across multiple turns",
1818 |                         "When dialogue history matters for the response",
1819 |                         "When using system prompts to guide assistant behavior",
1820 |                     ],
1821 |                     "example": {
1822 |                         "request": {
1823 |                             "messages": [
1824 |                                 {"role": "user", "content": "What's the capital of France?"},
1825 |                                 {"role": "assistant", "content": "The capital of France is Paris."},
1826 |                                 {"role": "user", "content": "And what's its population?"},
1827 |                             ],
1828 |                             "provider": "anthropic",
1829 |                             "model": "claude-3-opus-20240229",
1830 |                             "system_prompt": "You are a helpful geography assistant.",
1831 |                         },
1832 |                         "response": {
1833 |                             "text": "The population of Paris is approximately 2.1 million people in the city proper...",
1834 |                             "model": "claude-3-opus-20240229",
1835 |                             "provider": "anthropic",
1836 |                             "tokens": {"input": 62, "output": 48, "total": 110},
1837 |                             "cost": 0.00055,
1838 |                             "processing_time": 1.8,
1839 |                         },
1840 |                     },
1841 |                 },
1842 |                 "streaming_completion": {
1843 |                     "tool": "stream_completion",
1844 |                     "description": "Generates text in smaller chunks as a stream",
1845 |                     "best_for": [
1846 |                         "When you need to show incremental progress to users",
1847 |                         "For real-time display of model outputs",
1848 |                         "Long-form content generation where waiting for the full response would be too long",
1849 |                     ],
1850 |                     "example": {
1851 |                         "request": {
1852 |                             "prompt": "Write a short story about a robot learning to paint",
1853 |                             "provider": "openai",
1854 |                             "model": "gpt-4o",
1855 |                         },
1856 |                         "response_chunks": [
1857 |                             {
1858 |                                 "text": "In the year 2150, ",
1859 |                                 "chunk_index": 1,
1860 |                                 "provider": "openai",
1861 |                                 "model": "gpt-4o",
1862 |                                 "finished": False,
1863 |                             },
1864 |                             {
1865 |                                 "text": "a maintenance robot named ARIA-7 was assigned to",
1866 |                                 "chunk_index": 2,
1867 |                                 "provider": "openai",
1868 |                                 "model": "gpt-4o",
1869 |                                 "finished": False,
1870 |                             },
1871 |                             {
1872 |                                 "text": "",
1873 |                                 "chunk_index": 25,
1874 |                                 "provider": "openai",
1875 |                                 "full_text": "In the year 2150, a maintenance robot named ARIA-7 was assigned to...",
1876 |                                 "processing_time": 8.2,
1877 |                                 "finished": True,
1878 |                             },
1879 |                         ],
1880 |                     },
1881 |                 },
1882 |                 "multi_provider": {
1883 |                     "tool": "multi_completion",
1884 |                     "description": "Get completions from multiple providers simultaneously",
1885 |                     "best_for": [
1886 |                         "Comparing outputs from different models",
1887 |                         "Finding consensus among multiple models",
1888 |                         "Fallback scenarios where one provider might fail",
1889 |                         "Benchmarking different providers on the same task",
1890 |                     ],
1891 |                     "example": {
1892 |                         "request": {
1893 |                             "prompt": "Provide three tips for sustainable gardening",
1894 |                             "providers": [
1895 |                                 {"provider": "openai", "model": "gpt-4o"},
1896 |                                 {"provider": "anthropic", "model": "claude-3-opus-20240229"},
1897 |                             ],
1898 |                         },
1899 |                         "response": {
1900 |                             "results": {
1901 |                                 "openai/gpt-4o": {
1902 |                                     "provider_key": "openai/gpt-4o",
1903 |                                     "success": True,
1904 |                                     "text": "1. Use compost instead of chemical fertilizers...",
1905 |                                     "model": "gpt-4o",
1906 |                                 },
1907 |                                 "anthropic/claude-3-opus-20240229": {
1908 |                                     "provider_key": "anthropic/claude-3-opus-20240229",
1909 |                                     "success": True,
1910 |                                     "text": "1. Implement water conservation techniques...",
1911 |                                     "model": "claude-3-opus-20240229",
1912 |                                 },
1913 |                             },
1914 |                             "successful_count": 2,
1915 |                             "total_providers": 2,
1916 |                             "processing_time": 3.5,
1917 |                         },
1918 |                     },
1919 |                 },
1920 |             }
1921 | 
1922 |         @self.mcp.resource("examples://tournaments")
1923 |         def get_tournament_examples() -> Dict[str, Any]:
1924 |             """
1925 |             Get detailed examples and guidance for running LLM tournaments.
1926 | 
1927 |             This resource provides comprehensive examples and guidance for creating,
1928 |             monitoring, and analyzing LLM tournaments. It includes detailed information
1929 |             about tournament configuration, interpreting results, and best practices.
1930 | 
1931 |             Resource URI: examples://tournaments
1932 | 
1933 |             Returns:
1934 |                 Dictionary containing tournament examples and guidance:
1935 |                 - tournament_types: Different types of tournaments and their uses
1936 |                 - configuration_guide: Guidance on how to configure tournaments
1937 |                 - analysis_guide: How to interpret tournament results
1938 |                 - example_tournaments: Complete examples of different tournament configurations
1939 | 
1940 |             Usage:
1941 |                 This resource helps LLMs understand how to effectively use the tournament
1942 |                 tools, with guidance on configuration, execution, and analysis.
1943 |             """
1944 |             return {
1945 |                 "tournament_types": {
1946 |                     "code": {
1947 |                         "description": "Tournaments where models compete on coding tasks",
1948 |                         "ideal_for": [
1949 |                             "Algorithm implementation challenges",
1950 |                             "Debugging exercises",
1951 |                             "Code optimization problems",
1952 |                             "Comparing models' coding abilities",
1953 |                         ],
1954 |                         "evaluation_criteria": [
1955 |                             "Code correctness",
1956 |                             "Efficiency",
1957 |                             "Readability",
1958 |                             "Error handling",
1959 |                         ],
1960 |                     },
1961 |                     # Other tournament types could be added in the future
1962 |                 },
1963 |                 "configuration_guide": {
1964 |                     "model_selection": {
1965 |                         "description": "Guidelines for selecting models to include in tournaments",
1966 |                         "recommendations": [
1967 |                             "Include models from different providers for diverse approaches",
1968 |                             "Compare models within the same family (e.g., different Claude versions)",
1969 |                             "Consider including both specialized and general models",
1970 |                             "Ensure all models can handle the task complexity",
1971 |                         ],
1972 |                     },
1973 |                     "rounds": {
1974 |                         "description": "How to determine the appropriate number of rounds",
1975 |                         "recommendations": [
1976 |                             "Start with 3 rounds for most tournaments",
1977 |                             "Use more rounds (5+) for more complex or nuanced tasks",
1978 |                             "Consider that each round increases total runtime and cost",
1979 |                             "Each round gives models a chance to refine their solutions",
1980 |                         ],
1981 |                     },
1982 |                     "prompt_design": {
1983 |                         "description": "Best practices for tournament prompt design",
1984 |                         "recommendations": [
1985 |                             "Be specific about the problem requirements",
1986 |                             "Clearly define evaluation criteria",
1987 |                             "Specify output format expectations",
1988 |                             "Consider including test cases",
1989 |                             "Avoid ambiguous or underspecified requirements",
1990 |                         ],
1991 |                     },
1992 |                 },
1993 |                 "analysis_guide": {
1994 |                     "score_interpretation": {
1995 |                         "description": "How to interpret model scores in tournament results",
1996 |                         "guidance": [
1997 |                             "Scores are normalized to a 0-1 scale (1 being perfect)",
1998 |                             "Consider relative scores between models rather than absolute values",
1999 |                             "Look for consistency across rounds",
2000 |                             "Consider output quality even when scores are similar",
2001 |                         ],
2002 |                     },
2003 |                     "output_analysis": {
2004 |                         "description": "How to analyze model outputs from tournaments",
2005 |                         "guidance": [
2006 |                             "Compare approaches used by different models",
2007 |                             "Look for patterns in errors or limitations",
2008 |                             "Identify unique strengths of different providers",
2009 |                             "Consider both the score and actual output quality",
2010 |                         ],
2011 |                     },
2012 |                 },
2013 |                 "example_tournaments": {
2014 |                     "algorithm_implementation": {
2015 |                         "name": "Binary Search Algorithm",
2016 |                         "prompt": "Implement a binary search algorithm in Python that can search for an element in a sorted array. Include proper error handling, documentation, and test cases.",
2017 |                         "model_ids": ["openai/gpt-4o", "anthropic/claude-3-opus-20240229"],
2018 |                         "rounds": 3,
2019 |                         "tournament_type": "code",
2020 |                         "explanation": "This tournament tests the models' ability to implement a standard algorithm with proper error handling and testing.",
2021 |                     },
2022 |                     "code_optimization": {
2023 |                         "name": "String Processing Optimization",
2024 |                         "prompt": "Optimize the following Python function to process large strings more efficiently: def find_substring_occurrences(text, pattern): return [i for i in range(len(text)) if text[i:i+len(pattern)] == pattern]",
2025 |                         "model_ids": [
2026 |                             "openai/gpt-4o",
2027 |                             "anthropic/claude-3-opus-20240229",
2028 |                             "anthropic/claude-3-sonnet-20240229",
2029 |                         ],
2030 |                         "rounds": 4,
2031 |                         "tournament_type": "code",
2032 |                         "explanation": "This tournament compares models' ability to recognize and implement optimization opportunities in existing code.",
2033 |                     },
2034 |                 },
2035 |                 "workflow_examples": {
2036 |                     "basic_tournament": {
2037 |                         "description": "A simple tournament workflow from creation to result analysis",
2038 |                         "steps": [
2039 |                             {
2040 |                                 "step": 1,
2041 |                                 "description": "Create the tournament",
2042 |                                 "code": "tournament_id = create_tournament(name='Sorting Algorithm Challenge', prompt='Implement an efficient sorting algorithm...', model_ids=['openai/gpt-4o', 'anthropic/claude-3-opus-20240229'], rounds=3, tournament_type='code')",
2043 |                             },
2044 |                             {
2045 |                                 "step": 2,
2046 |                                 "description": "Poll for tournament status",
2047 |                                 "code": "status = get_tournament_status(tournament_id)['status']\nwhile status in ['PENDING', 'RUNNING']:\n    time.sleep(30)  # Check every 30 seconds\n    status = get_tournament_status(tournament_id)['status']",
2048 |                             },
2049 |                             {
2050 |                                 "step": 3,
2051 |                                 "description": "Retrieve and analyze results",
2052 |                                 "code": "results = get_tournament_results(tournament_id)\nwinner = max(results['final_scores'].items(), key=lambda x: x[1])[0]\noutputs = {model_id: results['rounds_data'][-1]['model_outputs'][model_id] for model_id in results['config']['model_ids']}",
2053 |                             },
2054 |                         ],
2055 |                     }
2056 |                 },
2057 |             }
2058 | 
2059 | 
2060 | def start_server(
2061 |     host: Optional[str] = None,
2062 |     port: Optional[int] = None,
2063 |     workers: Optional[int] = None,
2064 |     log_level: Optional[str] = None,
2065 |     reload: bool = False,
2066 |     transport_mode: str = "streamable-http",
2067 |     include_tools: Optional[List[str]] = None,
2068 |     exclude_tools: Optional[List[str]] = None,
2069 |     load_all_tools: bool = False,  # Added: Flag to control tool loading
2070 | ) -> None:
2071 |     """
2072 |     Start the Ultimate MCP Server with configurable settings.
2073 | 
2074 |     This function serves as the main entry point for starting the Ultimate MCP Server
2075 |     in either SSE (HTTP server) or stdio (direct process communication) mode. It handles
2076 |     complete server initialization including:
2077 | 
2078 |     1. Configuration loading and parameter validation
2079 |     2. Logging setup with proper levels and formatting
2080 |     3. Gateway instantiation with tool registration
2081 |     4. Transport mode selection and server startup
2082 | 
2083 |     The function provides flexibility in server configuration through parameters that
2084 |     override settings from the configuration file, allowing for quick adjustments without
2085 |     modifying configuration files. It also supports tool filtering, enabling selective
2086 |     registration of specific tools.
2087 | 
2088 |     Args:
2089 |         host: Hostname or IP address to bind the server to (e.g., "localhost", "0.0.0.0").
2090 |              If None, uses the value from the configuration file.
2091 |         port: TCP port for the server to listen on when in SSE mode.
2092 |              If None, uses the value from the configuration file.
2093 |         workers: Number of worker processes to spawn for handling requests.
2094 |                 Higher values improve concurrency but increase resource usage.
2095 |                 If None, uses the value from the configuration file.
2096 |         log_level: Logging verbosity level. One of "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".
2097 |                   If None, uses the value from the configuration file.
2098 |         reload: Whether to automatically reload the server when code changes are detected.
2099 |                Useful during development but not recommended for production.
2100 |         transport_mode: Communication mode for the server. Options:
2101 |                       - "stdio": Run using standard input/output for direct process communication (default)
2102 |                       - "sse": Run as an HTTP server with Server-Sent Events for streaming
2103 |                       - "streamable-http": Run as an HTTP server with streaming request/response bodies (recommended for HTTP clients)
2104 |         include_tools: Optional list of specific tool names to include in registration.
2105 |                       If provided, only these tools will be registered unless they are
2106 |                       also in exclude_tools. If None, all tools are included by default.
2107 |         exclude_tools: Optional list of tool names to exclude from registration.
2108 |                       These tools will not be registered even if they are also in include_tools.
2109 |         load_all_tools: If True, load all available tools. If False (default), load only the base set.
2110 | 
2111 |     Raises:
2112 |         ValueError: If transport_mode is not one of the valid options.
2113 |         ConfigurationError: If there are critical errors in the server configuration.
2114 | 
2115 |     Note:
2116 |         This function does not return as it initiates the server event loop, which
2117 |         runs until interrupted (e.g., by a SIGINT signal). In SSE mode, it starts
2118 |         a Uvicorn server; in stdio mode, it runs the FastMCP stdio handler.
2119 |     """
2120 |     server_host = host or get_config().server.host
2121 |     server_port = port or get_config().server.port
2122 |     server_workers = workers or get_config().server.workers
2123 | 
2124 |     # Get the current config and update tool registration settings
2125 |     cfg = get_config()
2126 |     if include_tools or exclude_tools:
2127 |         cfg.tool_registration.filter_enabled = True
2128 | 
2129 |     if include_tools:
2130 |         cfg.tool_registration.included_tools = include_tools
2131 | 
2132 |     if exclude_tools:
2133 |         cfg.tool_registration.excluded_tools = exclude_tools
2134 | 
2135 |     # Validate transport_mode
2136 |     if transport_mode not in ["sse", "stdio", "streamable-http"]:
2137 |         raise ValueError(
2138 |             f"Invalid transport_mode: {transport_mode}. Must be 'sse', 'stdio', or 'streamable-http'"
2139 |         )
2140 | 
2141 |     # Determine final log level from the provided parameter or fallback to INFO
2142 |     final_log_level = (log_level or "INFO").upper()
2143 | 
2144 |     # Update LOGGING_CONFIG with the final level
2145 |     LOGGING_CONFIG["root"]["level"] = final_log_level
2146 |     LOGGING_CONFIG["loggers"]["ultimate_mcp_server"]["level"] = final_log_level
2147 |     LOGGING_CONFIG["loggers"]["ultimate_mcp_server.tools"]["level"] = final_log_level
2148 |     LOGGING_CONFIG["loggers"]["ultimate_mcp_server.completions"]["level"] = final_log_level
2149 | 
2150 |     # Set Uvicorn access level based on final level
2151 |     LOGGING_CONFIG["loggers"]["uvicorn.access"]["level"] = (
2152 |         final_log_level if final_log_level != "CRITICAL" else "CRITICAL"
2153 |     )
2154 | 
2155 |     # Ensure Uvicorn base/error logs are at least INFO unless final level is DEBUG
2156 |     uvicorn_base_level = "INFO" if final_log_level not in ["DEBUG"] else "DEBUG"
2157 |     LOGGING_CONFIG["loggers"]["uvicorn"]["level"] = uvicorn_base_level
2158 |     LOGGING_CONFIG["loggers"]["uvicorn.error"]["level"] = uvicorn_base_level
2159 | 
2160 |     # Configure logging
2161 |     logging.config.dictConfig(LOGGING_CONFIG)
2162 | 
2163 |     # Initialize the gateway if not already created
2164 |     global _gateway_instance
2165 |     if not _gateway_instance:
2166 |         # Create gateway with tool filtering based on config
2167 |         cfg = get_config()
2168 |         _gateway_instance = Gateway(
2169 |             name=cfg.server.name,
2170 |             register_tools=True,
2171 |             load_all_tools=load_all_tools,  # Pass the flag to Gateway
2172 |         )
2173 | 
2174 |     # Log startup info to stderr instead of using logging directly
2175 |     print("Starting Ultimate MCP Server server", file=sys.stderr)
2176 |     print(f"Host: {server_host}", file=sys.stderr)
2177 |     print(f"Port: {server_port}", file=sys.stderr)
2178 |     print(f"Workers: {server_workers}", file=sys.stderr)
2179 |     print(f"Log level: {final_log_level}", file=sys.stderr)
2180 |     print(f"Transport mode: {transport_mode}", file=sys.stderr)
2181 |     if transport_mode == "streamable-http":
2182 |         print(
2183 |             "Note: streamable-http is the recommended transport for HTTP-based MCP clients",
2184 |             file=sys.stderr,
2185 |         )
2186 | 
2187 |     # Log tool loading strategy
2188 |     if load_all_tools:
2189 |         print("Tool Loading: ALL available tools", file=sys.stderr)
2190 |     else:
2191 |         print("Tool Loading: Base Toolset Only", file=sys.stderr)
2192 |         base_toolset = [
2193 |             "completion",
2194 |             "filesystem",
2195 |             "optimization",
2196 |             "provider",
2197 |             "local_text",
2198 |             "search",
2199 |         ]
2200 |         print(f"  (Includes: {', '.join(base_toolset)})", file=sys.stderr)
2201 | 
2202 |     # Log tool filtering info if enabled
2203 |     if cfg.tool_registration.filter_enabled:
2204 |         if cfg.tool_registration.included_tools:
2205 |             print(
2206 |                 f"Including tools: {', '.join(cfg.tool_registration.included_tools)}",
2207 |                 file=sys.stderr,
2208 |             )
2209 |         if cfg.tool_registration.excluded_tools:
2210 |             print(
2211 |                 f"Excluding tools: {', '.join(cfg.tool_registration.excluded_tools)}",
2212 |                 file=sys.stderr,
2213 |             )
2214 | 
2215 |     if transport_mode in ["sse", "streamable-http"]:
2216 |         # Run in HTTP mode (unified handling for both SSE and streamable-http)
2217 |         import os
2218 |         import subprocess
2219 |         import threading
2220 |         import time
2221 | 
2222 |         import uvicorn
2223 | 
2224 |         print(f"Running in {transport_mode} mode...", file=sys.stderr)
2225 | 
2226 |         # Set up a function to run the tool context estimator after the server starts
2227 |         def run_tool_context_estimator():
2228 |             # Wait a bit for the server to start up
2229 |             time.sleep(5)
2230 |             try:
2231 |                 # Ensure tools_list.json exists
2232 |                 if not os.path.exists("tools_list.json"):
2233 |                     print("\n--- Tool Context Window Analysis ---", file=sys.stderr)
2234 |                     print(
2235 |                         "Error: tools_list.json not found. Tool registration may have failed.",
2236 |                         file=sys.stderr,
2237 |                     )
2238 |                     print(
2239 |                         "The tool context estimator will run with limited functionality.",
2240 |                         file=sys.stderr,
2241 |                     )
2242 |                     print("-" * 40, file=sys.stderr)
2243 | 
2244 |                 # Run the tool context estimator script with appropriate transport
2245 |                 cmd = ["python", "-m", "mcp_tool_context_estimator", "--quiet"]
2246 |                 # Pass transport mode for both HTTP transports (sse and streamable-http)
2247 |                 if transport_mode in ["sse", "streamable-http"]:
2248 |                     cmd.extend(["--transport", transport_mode])
2249 | 
2250 |                 result = subprocess.run(cmd, capture_output=True, text=True)
2251 | 
2252 |                 # Output the results to stderr
2253 |                 if result.stdout:
2254 |                     print("\n--- Tool Context Window Analysis ---", file=sys.stderr)
2255 |                     print(result.stdout, file=sys.stderr)
2256 |                     print("-" * 40, file=sys.stderr)
2257 |                 # Check if there was an error
2258 |                 if result.returncode != 0:
2259 |                     print("\n--- Tool Context Estimator Error ---", file=sys.stderr)
2260 |                     print(
2261 |                         "Failed to run mcp_tool_context_estimator.py - likely due to an error.",
2262 |                         file=sys.stderr,
2263 |                     )
2264 |                     print("Error output:", file=sys.stderr)
2265 |                     print(result.stderr, file=sys.stderr)
2266 |                     print("-" * 40, file=sys.stderr)
2267 |             except Exception as e:
2268 |                 print(f"\nError running tool context estimator: {str(e)}", file=sys.stderr)
2269 |                 print(
2270 |                     "Check if mcp_tool_context_estimator.py exists and is executable.",
2271 |                     file=sys.stderr,
2272 |                 )
2273 | 
2274 |         # Skip the tool-context estimator for SSE transport because it causes the server
2275 |         # to shut down when the estimator disconnects after completing its analysis.
2276 |         # SSE servers shut down when all clients disconnect, and the estimator is treated
2277 |         # as a client. Run it for streamable-http mode where this isn't an issue.
2278 |         if transport_mode == "streamable-http" and os.path.exists("mcp_tool_context_estimator.py"):
2279 |             threading.Thread(target=run_tool_context_estimator, daemon=True).start()
2280 | 
2281 |         # Setup graceful shutdown
2282 |         logger = logging.getLogger("ultimate_mcp_server.server")
2283 | 
2284 |         # Configure graceful shutdown with error suppression
2285 |         enable_quiet_shutdown()
2286 | 
2287 |         # Create a shutdown handler for gateway cleanup
2288 |         async def cleanup_resources():
2289 |             """Performs cleanup for various components during shutdown."""
2290 | 
2291 |             # First attempt quick tasks then long tasks with timeouts
2292 |             print("Cleaning up Gateway instance and associated resources...", file=sys.stderr)
2293 | 
2294 |             # Shutdown SQL Tools with timeout
2295 |             try:
2296 |                 await asyncio.wait_for(shutdown_sql_tools(), timeout=3.0)
2297 |             except (asyncio.TimeoutError, Exception):
2298 |                 pass  # Suppress errors during shutdown
2299 | 
2300 |             # Shutdown Connection Manager with timeout
2301 |             try:
2302 |                 from ultimate_mcp_server.tools.sql_databases import _connection_manager
2303 | 
2304 |                 await asyncio.wait_for(_connection_manager.shutdown(), timeout=2.0)
2305 |             except (asyncio.TimeoutError, Exception):
2306 |                 pass  # Suppress errors during shutdown
2307 | 
2308 |             # Shutdown Smart Browser with timeout
2309 |             try:
2310 |                 await asyncio.wait_for(smart_browser_shutdown(), timeout=5.0)
2311 |             except (asyncio.TimeoutError, Exception):
2312 |                 pass  # Suppress errors during shutdown
2313 | 
2314 |         # Register the cleanup function with the graceful shutdown system
2315 |         register_shutdown_handler(cleanup_resources)
2316 | 
2317 |         # Create FastMCP app with proper path configuration
2318 |         if transport_mode == "sse":
2319 |             # Mark the gateway instance as SSE mode for lifespan management
2320 |             _gateway_instance._sse_mode = True
2321 |             
2322 |             mcp_app = _gateway_instance.mcp.http_app(transport="sse", path="/sse")
2323 |             print("Note: Running in legacy SSE mode.", file=sys.stderr)
2324 |             
2325 |             # Add SSE keepalive mechanism to prevent automatic shutdown
2326 |             def sse_keepalive():
2327 |                 """Keepalive thread to prevent SSE server from shutting down when no clients are connected."""
2328 |                 while True:
2329 |                     time.sleep(30)  # Send keepalive every 30 seconds
2330 |                     try:
2331 |                         # This simple presence keeps the server alive
2332 |                         # The actual SSE connections will handle their own keepalive
2333 |                         pass
2334 |                     except Exception:
2335 |                         # If there's any error, just continue
2336 |                         pass
2337 |             
2338 |             # Start the keepalive thread as a daemon so it doesn't prevent shutdown
2339 |             keepalive_thread = threading.Thread(target=sse_keepalive, daemon=True, name="SSE-Keepalive")
2340 |             keepalive_thread.start()
2341 |             print("SSE keepalive thread started to prevent automatic shutdown.", file=sys.stderr)
2342 |             
2343 |         else:  # This path is for streamable-http
2344 |             mcp_app = _gateway_instance.mcp.http_app(path="/mcp")
2345 | 
2346 |         print(f"Running in {transport_mode} mode...", file=sys.stderr)
2347 |         print(f"[DEBUG] {transport_mode} app type: {type(mcp_app)}", file=sys.stderr)
2348 | 
2349 |         # === BEGIN NEW SPLIT-APP ARCHITECTURE ===
2350 |         from starlette.applications import Starlette
2351 |         from starlette.routing import Mount
2352 | 
2353 |         # 1) PRISTINE FastMCP wrapper – **NO** extra routes
2354 |         mcp_starlette = Starlette(
2355 |             routes=[Mount("/", mcp_app)],
2356 |             lifespan=mcp_app.lifespan,
2357 |         )
2358 | 
2359 |         # 2) FastAPI application for rich REST APIs & automatic docs
2360 |         api_app = FastAPI(
2361 |             title="Ultimate MCP Server API",
2362 |             description="REST API endpoints for the Ultimate MCP Server",
2363 |             version="1.0.0",
2364 |             docs_url="/docs",
2365 |             redoc_url="/redoc",
2366 |             openapi_url="/openapi.json",
2367 |         )
2368 | 
2369 |         # Add CORS middleware (FastAPI uses Starlette under the hood)
2370 |         api_app.add_middleware(
2371 |             CORSMiddleware,
2372 |             allow_origins=["*"],
2373 |             allow_methods=["*"],
2374 |             allow_headers=["*"],
2375 |             allow_credentials=True,
2376 |         )
2377 | 
2378 |         endpoint_path = "/sse" if transport_mode == "sse" else "/mcp"
2379 | 
2380 |         # Setup all UMS API endpoints
2381 |         setup_ums_api(api_app)
2382 | 
2383 |         # --- UMS Explorer Placeholder ---
2384 |         # 3) Combined application – avoid overlapping mounts
2385 |         final_app = Starlette(
2386 |             routes=[
2387 |                 Mount(endpoint_path, mcp_starlette),  # /mcp or /sse
2388 |                 Mount("/api", api_app),  # REST API under /api
2389 |             ],
2390 |             lifespan=mcp_app.lifespan,
2391 |         )
2392 | 
2393 |         # Logging of endpoints for clarity
2394 |         print(
2395 |             f"{transport_mode.upper()} endpoint available at: http://{server_host}:{server_port}{endpoint_path}",
2396 |             file=sys.stderr,
2397 |         )
2398 |         print(
2399 |             f"API endpoints available at: http://{server_host}:{server_port}/api/*",
2400 |             file=sys.stderr,
2401 |         )
2402 |         print(
2403 |             f"UMS Explorer available at: http://{server_host}:{server_port}/api/ums-explorer",
2404 |             file=sys.stderr,
2405 |         )
2406 |         print(
2407 |             f"Swagger UI available at: http://{server_host}:{server_port}/api/docs",
2408 |             file=sys.stderr,
2409 |         )
2410 |         print(
2411 |             f"ReDoc available at: http://{server_host}:{server_port}/api/redoc",
2412 |             file=sys.stderr,
2413 |         )
2414 |         print(
2415 |             f"OpenAPI spec available at: http://{server_host}:{server_port}/api/openapi.json",
2416 |             file=sys.stderr,
2417 |         )
2418 |         print(
2419 |             f"Discovery endpoint available at: http://{server_host}:{server_port}/",
2420 |             file=sys.stderr,
2421 |         )
2422 |         # === END NEW SPLIT-APP ARCHITECTURE ===
2423 | 
2424 |         # Use our custom quiet Uvicorn server for silent shutdown
2425 |         config = uvicorn.Config(
2426 |             final_app,
2427 |             host=server_host,
2428 |             port=server_port,
2429 |             log_config=LOGGING_CONFIG,
2430 |             log_level=final_log_level.lower(),
2431 |             lifespan="on",  # This tells uvicorn to look for and use the app's lifespan
2432 |         )
2433 |         server = create_quiet_server(config)
2434 |         server.run()
2435 |     else:  # stdio mode
2436 |         # --- Stdio Mode Execution ---
2437 |         logger.info("Running in stdio mode...")
2438 | 
2439 |         # Create a shutdown handler for stdio mode cleanup
2440 |         async def cleanup_resources():
2441 |             """Performs cleanup for various components during shutdown."""
2442 | 
2443 |             print("Cleaning up Gateway instance and associated resources...", file=sys.stderr)
2444 | 
2445 |             # Shutdown SQL Tools with timeout
2446 |             try:
2447 |                 await asyncio.wait_for(shutdown_sql_tools(), timeout=3.0)
2448 |             except (asyncio.TimeoutError, Exception):
2449 |                 pass  # Suppress errors during shutdown
2450 | 
2451 |             # Shutdown Connection Manager with timeout
2452 |             try:
2453 |                 from ultimate_mcp_server.tools.sql_databases import _connection_manager
2454 | 
2455 |                 await asyncio.wait_for(_connection_manager.shutdown(), timeout=2.0)
2456 |             except (asyncio.TimeoutError, Exception):
2457 |                 pass  # Suppress errors during shutdown
2458 | 
2459 |             # Shutdown Smart Browser with timeout
2460 |             try:
2461 |                 await asyncio.wait_for(smart_browser_shutdown(), timeout=5.0)
2462 |             except (asyncio.TimeoutError, Exception):
2463 |                 pass  # Suppress errors during shutdown
2464 | 
2465 |         # Configure graceful shutdown with error suppression
2466 |         enable_quiet_shutdown()
2467 | 
2468 |         # Register the same cleanup function for stdio mode
2469 |         register_shutdown_handler(cleanup_resources)
2470 | 
2471 |         try:
2472 |             # Run the FastMCP stdio loop - this will block until interrupted
2473 |             _gateway_instance.mcp.run()
2474 |         except (KeyboardInterrupt, SystemExit):
2475 |             # Normal shutdown - handled by graceful shutdown system
2476 |             pass
2477 |         except Exception:
2478 |             # Any other error - also handled by graceful shutdown
2479 |             pass
2480 |         # --- End Stdio Mode ---
2481 | 
2482 |     # --- Post-Server Exit ---
2483 |     logger.info("Server loop exited.")
2484 | 
```
Page 34/45FirstPrevNextLast