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() | |