Spaces:
Runtime error
Runtime error
| #!/usr/bin/env python3 | |
| """ | |
| MCO Hackathon Demo - Main Entry Point | |
| This script sets up and runs the MCO Hackathon project: | |
| 1. Starts the MCO MCP server for orchestration. | |
| 2. Launches the polished Gradio UI for the demonstration. | |
| """ | |
| import os | |
| import sys | |
| import subprocess | |
| import time | |
| import shutil | |
| # Set up environment variables before other imports | |
| APP_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| os.environ["MCO_CONFIG_DIR"] = os.path.join(APP_DIR, "mco-config") | |
| os.environ["MCO_SERVER_URL"] = "http://localhost:3000" | |
| # Import the Gradio UI creation function | |
| from gradio_ui import create_gradio_ui | |
| print("--- app.py: Script execution started (top level) ---") | |
| # Global variable to hold the MCO server process | |
| mco_server_process = None | |
| def setup_mco_server(): | |
| print("--- app.py: setup_mco_server() function started ---") | |
| """Installs MCO dependencies via npm and starts the server.""" | |
| global mco_server_process | |
| print("--- Setting up and starting MCO MCP server ---") | |
| # Step 1: Ensure npm dependencies are installed (cleanly). | |
| print("--- Performing a clean install of MCO server dependencies via npm... ---") | |
| try: | |
| # Clean up old artifacts | |
| node_modules_path = os.path.join(APP_DIR, "node_modules") | |
| package_lock_path = os.path.join(APP_DIR, "package-lock.json") | |
| if os.path.exists(node_modules_path): | |
| print(f"Attempting to remove existing node_modules directory: {node_modules_path}") | |
| try: | |
| shutil.rmtree(node_modules_path) | |
| print(f"Successfully removed {node_modules_path}") | |
| except PermissionError: | |
| print(f"Warning: Permission denied when trying to remove {node_modules_path}. Skipping removal.") | |
| except Exception as e: | |
| print(f"Warning: Could not remove {node_modules_path}: {e}. Skipping removal.") | |
| if os.path.exists(package_lock_path): | |
| print(f"Attempting to remove existing package-lock.json: {package_lock_path}") | |
| try: | |
| os.remove(package_lock_path) | |
| print(f"Successfully removed {package_lock_path}") | |
| except PermissionError: | |
| print(f"Warning: Permission denied when trying to remove {package_lock_path}. Skipping removal.") | |
| except Exception as e: | |
| print(f"Warning: Could not remove {package_lock_path}: {e}. Skipping removal.") | |
| # General npm install for all dependencies | |
| print("Running general 'npm install'...") | |
| npm_install_command = ["npm", "install"] | |
| print(f"Executing: {' '.join(npm_install_command)}") | |
| npm_install_process = subprocess.run(npm_install_command, cwd=APP_DIR, check=True, capture_output=True, text=True) | |
| print("--- npm install stdout ---") | |
| print(npm_install_process.stdout) | |
| print("--- npm install stderr ---") | |
| print(npm_install_process.stderr) | |
| print("'npm install' completed.") | |
| # Specifically install/update @paradiselabs/mco-protocol to the latest version | |
| print("Ensuring latest '@paradiselabs/mco-protocol' is installed...") | |
| npm_update_specific_pkg_command = ["npm", "install", "@paradiselabs/mco-protocol@latest"] | |
| print(f"Executing: {' '.join(npm_update_specific_pkg_command)}") | |
| npm_update_specific_pkg_process = subprocess.run(npm_update_specific_pkg_command, cwd=APP_DIR, check=True, capture_output=True, text=True) | |
| print("--- npm install @paradiselabs/mco-protocol@latest stdout ---") | |
| print(npm_update_specific_pkg_process.stdout) | |
| print("--- npm install @paradiselabs/mco-protocol@latest stderr ---") | |
| print(npm_update_specific_pkg_process.stderr) | |
| print("'@paradiselabs/mco-protocol@latest' installed/updated.") | |
| # Hot-patch mco-server.js to fix internal pathing issue | |
| mco_server_script_to_patch_path = os.path.join(APP_DIR, "node_modules", "@paradiselabs", "mco-protocol", "bin", "mco-server.js") | |
| if os.path.exists(mco_server_script_to_patch_path): | |
| print(f"Attempting to hot-patch {mco_server_script_to_patch_path}...") | |
| try: | |
| with open(mco_server_script_to_patch_path, 'r') as f: | |
| content = f.read() | |
| original_substring = "require('./lib/" | |
| corrected_substring = "require('../lib/" | |
| if original_substring in content: | |
| content = content.replace(original_substring, corrected_substring) | |
| with open(mco_server_script_to_patch_path, 'w') as f: | |
| f.write(content) | |
| print(f"Successfully patched {mco_server_script_to_patch_path}: replaced all instances of '{original_substring}' with '{corrected_substring}'.") | |
| else: | |
| print(f"Hot-patch warning: Original substring '{original_substring}' not found in {mco_server_script_to_patch_path}. The script might already be patched or has changed.") | |
| except Exception as patch_err: | |
| print(f"Error during hot-patching {mco_server_script_to_patch_path}: {patch_err}") | |
| else: | |
| print(f"Hot-patch error: {mco_server_script_to_patch_path} not found. Cannot apply patch.") | |
| except subprocess.CalledProcessError as e: | |
| print(f"ERROR: npm command failed with exit code {e.returncode}.") | |
| print(f"Command: {' '.join(e.cmd)}") | |
| print(f"stdout:\n{e.stdout}") | |
| print(f"stderr:\n{e.stderr}") | |
| sys.exit(1) | |
| except FileNotFoundError: | |
| print("ERROR: `npm` command not found. Please ensure Node.js and npm are installed.") | |
| sys.exit(1) | |
| except Exception as e: | |
| print(f"An unexpected error occurred during npm setup: {e}") | |
| sys.exit(1) | |
| # Step 2: Attempt to run the server using mco-cli.js, with CWD at package root. | |
| server_script_name = "mco-cli.js" # Using the CLI script as an alternative entry point | |
| server_script_path = os.path.join(APP_DIR, "node_modules", "@paradiselabs", "mco-protocol", "bin", server_script_name) | |
| mco_config_dir = os.environ["MCO_CONFIG_DIR"] | |
| if not os.path.exists(server_script_path): | |
| print(f"ERROR: MCO CLI script not found at '{server_script_path}' after npm install.") | |
| sys.exit(1) | |
| # Construct the command to run the CLI script directly with node, using the 'serve' subcommand. | |
| server_command = [ | |
| "node", | |
| server_script_path, | |
| "serve", # The 'serve' subcommand for mco-cli.js | |
| mco_config_dir, | |
| "--host", "0.0.0.0", | |
| "--port", os.environ.get("MCO_PORT", "3001") | |
| ] | |
| try: | |
| print(f"Starting MCO server: {' '.join(server_command)}") | |
| package_root_dir = os.path.join(APP_DIR, "node_modules", "@paradiselabs", "mco-protocol") | |
| if not os.path.isdir(package_root_dir): | |
| print(f"ERROR: MCO package directory not found at '{package_root_dir}'. This is unexpected after npm install.") | |
| sys.exit(1) | |
| # The server's stdout and stderr will now go to the main console. | |
| mco_server_process = subprocess.Popen(server_command, cwd=package_root_dir) | |
| print(f"MCO server process started with PID: {mco_server_process.pid} (CWD: {package_root_dir})") | |
| time.sleep(5) # Allow server more time to initialize and write to logs | |
| except Exception as e: | |
| print(f"Error starting MCO server: {e}") | |
| sys.exit(1) | |
| def main(): | |
| print("--- app.py: main() function started ---") | |
| """Main function to run the server and UI.""" | |
| global mco_server_process | |
| try: | |
| setup_mco_server() | |
| if mco_server_process and mco_server_process.poll() is None: | |
| print("--- MCO server is running. Launching Gradio UI... ---") | |
| gradio_app = create_gradio_ui() | |
| print("--- Calling gradio_app.launch() locally... ---") | |
| gradio_app.launch() | |
| print("--- Gradio UI has been shut down. --- ") | |
| # If we reach here, it means Gradio's launch() returned. | |
| # We should still try to shut down the server cleanly. | |
| print("--- Shutting down MCO server... ---") | |
| if mco_server_process: | |
| print("--- Terminating MCO server process... ---") | |
| mco_server_process.terminate() | |
| mco_server_process.wait() | |
| print("--- MCO server stopped. ---") | |
| except Exception as e: | |
| print(f"An unexpected error occurred: {e}") | |
| # print("Creating sample SNLP files...") | |
| # generator.generate_sample_files("General", "Python") | |
| # print("Sample SNLP files created") | |
| def setup_modal(): | |
| """Set up Modal for deployment or local use.""" | |
| if IS_HF_SPACE: | |
| print("Running in Hugging Face Space. Checking for Modal secrets...") | |
| modal_token_id = os.environ.get("MODAL_TOKEN_ID") | |
| modal_token_secret = os.environ.get("MODAL_TOKEN_SECRET") | |
| if not modal_token_id: | |
| print("Warning: MODAL_TOKEN_ID secret not found in Hugging Face Space. Ensure it's set in Space secrets.") | |
| if not modal_token_secret: | |
| print("Warning: MODAL_TOKEN_SECRET secret not found in Hugging Face Space. Ensure it's set in Space secrets.") | |
| if modal_token_id and modal_token_secret: | |
| print("β Modal secrets (MODAL_TOKEN_ID, MODAL_TOKEN_SECRET) appear to be set for Hugging Face Space.") | |
| return # No further local setup needed for HF Spaces | |
| # Local Modal setup | |
| try: | |
| import modal | |
| # Check if Modal is set up (e.g., token configured) | |
| try: | |
| # A light check, actual token validity is checked by Modal CLI/client calls | |
| # This just ensures the library can be initialized to some extent. | |
| modal.Image.debian_slim() # Example of a basic Modal object usage | |
| print("Modal appears to be set up locally.") | |
| except Exception as e: | |
| # This exception might be broad, could be config error or other Modal issue | |
| print(f"Modal local setup might be incomplete or there's an issue: {str(e)}") | |
| print("If you encounter Modal errors, ensure you have run 'modal token new' and have an active Modal environment.") | |
| except ImportError: | |
| print("Modal not installed locally, installing...") | |
| try: | |
| subprocess.run( | |
| [sys.executable, "-m", "pip", "install", "modal"], | |
| check=True, | |
| capture_output=True, text=True | |
| ) | |
| print("Modal installed successfully. Please re-run the application.") | |
| print("You may also need to run 'modal token new' to configure your Modal token.") | |
| sys.exit(1) # Exit so user can re-run with modal installed | |
| except subprocess.CalledProcessError as e: | |
| print(f"Failed to install Modal: {e.stderr}") | |
| print("Please install Modal manually: pip install modal") | |
| sys.exit(1) | |
| def validate_environment(): | |
| """Validate the environment for running the application""" | |
| print("Validating environment...") | |
| # Check Python version | |
| python_version = sys.version_info | |
| print(f"Python version: {python_version.major}.{python_version.minor}.{python_version.micro}") | |
| if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 8): | |
| print("Warning: Python 3.8+ recommended") | |
| # Check required packages | |
| # Package name -> import name mapping | |
| required_packages = { | |
| "gradio": "gradio", | |
| "modal": "modal", | |
| "anthropic": "anthropic", # Still check for library, key is handled by Modal/HF secrets | |
| "requests": "requests", | |
| "beautifulsoup4": "bs4" | |
| } | |
| missing_packages = [] | |
| for package_name, import_name in required_packages.items(): | |
| try: | |
| __import__(import_name) | |
| print(f"β {package_name} installed") | |
| except ImportError: | |
| missing_packages.append(package_name) | |
| print(f"β {package_name} missing") | |
| if missing_packages: | |
| print("\nInstalling missing packages...") | |
| try: | |
| subprocess.run( | |
| [sys.executable, "-m", "pip", "install"] + missing_packages, | |
| check=True, capture_output=True, text=True | |
| ) | |
| print("Missing packages installed. Please re-run the application.") | |
| sys.exit(1) # Exit so user can re-run with packages installed | |
| except subprocess.CalledProcessError as e: | |
| print(f"Failed to install missing packages: {e.stderr}") | |
| print(f"Please install them manually: pip install {' '.join(missing_packages)}") | |
| sys.exit(1) | |
| # Check Node.js | |
| try: | |
| node_version = subprocess.run( | |
| ["node", "--version"], | |
| capture_output=True, | |
| text=True, | |
| check=True | |
| ).stdout.strip() | |
| print(f"Node.js version: {node_version}") | |
| except (subprocess.CalledProcessError, FileNotFoundError): | |
| print("Warning: Node.js not found, required for MCO MCP server. Please install Node.js.") | |
| # Check npm | |
| try: | |
| npm_version = subprocess.run( | |
| ["npm", "--version"], | |
| capture_output=True, | |
| text=True, | |
| check=True | |
| ).stdout.strip() | |
| print(f"npm version: {npm_version}") | |
| except (subprocess.CalledProcessError, FileNotFoundError): | |
| print("Warning: npm not found, required for MCO MCP server. Please install npm.") | |
| # ANTHROPIC_API_KEY check removed as it's handled by Modal secrets or HF Space secrets | |
| # if "ANTHROPIC_API_KEY" not in os.environ: | |
| # print("Warning: ANTHROPIC_API_KEY environment variable not set") | |
| print("Environment validation complete") | |
| def main(): | |
| """Main entry point""" | |
| print("Starting MCO Hackathon project...") | |
| # Validate environment | |
| validate_environment() | |
| # Set up MCO server | |
| setup_mco_server() | |
| # Set up Modal | |
| setup_modal() | |
| # Create and launch Gradio UI | |
| print("Launching Gradio UI...") | |
| app = create_ui() | |
| app.launch(share=True, server_name="0.0.0.0", server_port=7860) | |
| if __name__ == "__main__": | |
| main() | |