Spaces:
Build error
Build error
| """ | |
| MCP Proxy Manager for OpenHands. | |
| This module provides a manager class for handling FastMCP proxy instances, | |
| including initialization, configuration, and mounting to FastAPI applications. | |
| """ | |
| import logging | |
| from typing import Any, Optional | |
| from fastapi import FastAPI | |
| from fastmcp import FastMCP | |
| from fastmcp.utilities.logging import get_logger as fastmcp_get_logger | |
| from openhands.core.config.mcp_config import MCPStdioServerConfig | |
| logger = logging.getLogger(__name__) | |
| fastmcp_logger = fastmcp_get_logger('fastmcp') | |
| class MCPProxyManager: | |
| """ | |
| Manager for FastMCP proxy instances. | |
| This class encapsulates all the functionality related to creating, configuring, | |
| and managing FastMCP proxy instances, including mounting them to FastAPI applications. | |
| """ | |
| def __init__( | |
| self, | |
| auth_enabled: bool = False, | |
| api_key: Optional[str] = None, | |
| logger_level: Optional[int] = None, | |
| ): | |
| """ | |
| Initialize the MCP Proxy Manager. | |
| Args: | |
| name: Name of the proxy server | |
| auth_enabled: Whether authentication is enabled | |
| api_key: API key for authentication (required if auth_enabled is True) | |
| logger_level: Logging level for the FastMCP logger | |
| """ | |
| self.auth_enabled = auth_enabled | |
| self.api_key = api_key | |
| self.proxy: Optional[FastMCP] = None | |
| # Initialize with a valid configuration format for FastMCP | |
| self.config: dict[str, Any] = { | |
| 'mcpServers': {}, | |
| } | |
| # Configure FastMCP logger | |
| if logger_level is not None: | |
| fastmcp_logger.setLevel(logger_level) | |
| def initialize(self) -> None: | |
| """ | |
| Initialize the FastMCP proxy with the current configuration. | |
| """ | |
| if len(self.config['mcpServers']) == 0: | |
| logger.info( | |
| f"No MCP servers configured for FastMCP Proxy, skipping initialization." | |
| ) | |
| return None | |
| # Create a new proxy with the current configuration | |
| self.proxy = FastMCP.as_proxy( | |
| self.config, | |
| auth_enabled=self.auth_enabled, | |
| api_key=self.api_key, | |
| ) | |
| logger.info(f"FastMCP Proxy initialized successfully") | |
| async def mount_to_app( | |
| self, app: FastAPI, allow_origins: Optional[list[str]] = None | |
| ) -> None: | |
| """ | |
| Mount the SSE server app to a FastAPI application. | |
| Args: | |
| app: FastAPI application to mount to | |
| allow_origins: List of allowed origins for CORS | |
| """ | |
| if len(self.config['mcpServers']) == 0: | |
| logger.info( | |
| f"No MCP servers configured for FastMCP Proxy, skipping mount." | |
| ) | |
| return | |
| if not self.proxy: | |
| raise ValueError('FastMCP Proxy is not initialized') | |
| # Get the SSE app | |
| # mcp_app = self.proxy.http_app(path='/shttp') | |
| mcp_app = self.proxy.http_app(path='/sse', transport='sse') | |
| app.mount('/mcp', mcp_app) | |
| # Remove any existing mounts at root path | |
| if '/mcp' in app.routes: | |
| app.routes.remove('/mcp') | |
| app.mount('/', mcp_app) | |
| logger.info(f"Mounted FastMCP Proxy app at /mcp") | |
| async def update_and_remount( | |
| self, | |
| app: FastAPI, | |
| stdio_servers: list[MCPStdioServerConfig], | |
| allow_origins: Optional[list[str]] = None, | |
| ) -> None: | |
| """ | |
| Update the tools configuration and remount the proxy to the app. | |
| This is a convenience method that combines updating the tools, | |
| shutting down the existing proxy, initializing a new one, and | |
| mounting it to the app. | |
| Args: | |
| app: FastAPI application to mount to | |
| tools: List of tool configurations | |
| allow_origins: List of allowed origins for CORS | |
| """ | |
| tools = { | |
| t.name: t.model_dump() | |
| for t in stdio_servers | |
| } | |
| self.config['mcpServers'] = tools | |
| del self.proxy | |
| self.proxy = None | |
| # Initialize a new proxy | |
| self.initialize() | |
| # Mount the new proxy to the app | |
| await self.mount_to_app(app, allow_origins) | |