mco-protocol / app.py
paradiseDev's picture
Upload 7 files
ec0371a verified
#!/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()