diff --git a/.gitignore b/.gitignore index fd8dee4..248acec 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ templates/ params/ repo/ examples/ +.omc/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index cac2b3b..b923370 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,9 +5,14 @@ # 1. builder – installs all conda/pip dependencies # 2. runtime – minimal image with only what's needed to run # -# Build: +# Build (without proxy): # docker build -t bindcraft-mcp . # +# Build (with proxy - recommended for China): +# docker build --build-arg HTTP_PROXY=http://host.docker.internal:7890 \ +# --build-arg HTTPS_PROXY=http://host.docker.internal:7890 \ +# -t bindcraft-mcp . +# # Run (GPU): # docker run --gpus all -it bindcraft-mcp # @@ -15,15 +20,38 @@ # docker run -it bindcraft-mcp ############################################################################### +# Proxy configuration (pass via --build-arg) +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + # ---------- Stage 1: builder ---------- FROM continuumio/miniconda3:24.7.1-0 AS builder +# Re-declare ARGs for this stage +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + +# Set environment variables for proxy in builder stage +ENV HTTP_PROXY=${HTTP_PROXY} +ENV HTTPS_PROXY=${HTTPS_PROXY} +ENV NO_PROXY=${NO_PROXY} +ENV http_proxy=${HTTP_PROXY} +ENV https_proxy=${HTTPS_PROXY} +ENV no_proxy=${NO_PROXY} + RUN apt-get update && apt-get install -y \ git gcc g++ wget \ && rm -rf /var/lib/apt/lists/* WORKDIR /app +# Configure conda to use USTC mirror (China) +RUN mkdir -p /root/.conda && \ + printf 'channels:\n - https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge/\nshow_channel_urls: true\n' > /root/.condarc && \ + conda clean -i + # Create conda environment with all dependencies RUN conda create -p /env python=3.10 -y @@ -38,14 +66,15 @@ RUN conda install -p /env \ chex dm-haiku 'flax<0.10.0' dm-tree joblib ml-collections immutabledict optax \ -c conda-forge -y -# Install JAX with CUDA 12 support for GPU acceleration -RUN /env/bin/pip install --no-cache-dir 'jax[cuda12]>=0.4,<=0.6.0' +# Configure pip to use USTC mirror and install JAX with CUDA 12 support +RUN /env/bin/pip config set global.index-url https://mirrors.ustc.edu.cn/pypi/simple && \ + /env/bin/pip install --no-cache-dir 'jax[cuda12]>=0.4,<=0.6.0' # Install PyRosetta (may fail without license – non-fatal) RUN conda install -p /env pyrosetta pdbfixer \ --channel https://conda.graylab.jhu.edu -c conda-forge -y 2>/dev/null || true -# Install pip packages +# Install pip packages (using USTC mirror configured above) RUN /env/bin/pip install --no-cache-dir fastmcp==2.13.1 loguru click RUN /env/bin/pip install --no-cache-dir git+https://github.com/sokrypton/ColabDesign.git --no-deps || true @@ -55,6 +84,19 @@ RUN conda clean -a -y # ---------- Stage 2: runtime ---------- FROM continuumio/miniconda3:24.7.1-0 AS runtime +# Re-declare ARGs for this stage +ARG HTTP_PROXY +ARG HTTPS_PROXY +ARG NO_PROXY + +# Set environment variables for proxy in runtime stage +ENV HTTP_PROXY=${HTTP_PROXY} +ENV HTTPS_PROXY=${HTTPS_PROXY} +ENV NO_PROXY=${NO_PROXY} +ENV http_proxy=${HTTP_PROXY} +ENV https_proxy=${HTTPS_PROXY} +ENV no_proxy=${NO_PROXY} + RUN apt-get update && apt-get install -y \ libgomp1 libgfortran5 git wget \ && rm -rf /var/lib/apt/lists/* @@ -118,6 +160,14 @@ ENV FONTCONFIG_PATH=/app/.fontconfig ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=compute,utility +# Reset proxy environment variables (don't carry proxy into runtime) +ENV HTTP_PROXY= +ENV HTTPS_PROXY= +ENV NO_PROXY= +ENV http_proxy= +ENV https_proxy= +ENV no_proxy= + # Allow any UID to resolve via NSS (fixes getpwuid KeyError for --user flag) RUN chmod 666 /etc/passwd # Create entrypoint that adds the runtime UID to /etc/passwd if missing diff --git a/README.md b/README.md index 7b501e6..25be613 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,20 @@ cd bindcraft_mcp # Build the Docker image docker build -t bindcraft_mcp:latest . +# Build with proxy (recommended for users in China) +docker build --build-arg HTTP_PROXY=http://host.docker.internal:7890 \ + --build-arg HTTPS_PROXY=http://host.docker.internal:7890 \ + -t bindcraft_mcp:latest . +``` + +**Note for users in China:** The Dockerfile is pre-configured to use USTC mirrors for both conda and pip, which significantly speeds up package downloads without requiring a proxy: +- **Conda**: `https://mirrors.ustc.edu.cn/anaconda/cloud/conda-forge` +- **PyPI**: `https://mirrors.ustc.edu.cn/pypi/simple` + +```bash +# Build without proxy (uses USTC mirrors by default) +docker build -t bindcraft_mcp:latest . + # Register with Claude Code (runs as current user to avoid permission issues) claude mcp add bindcraft -- docker run -i --rm --user `id -u`:`id -g` --gpus all --ipc=host -v `pwd`:`pwd` bindcraft_mcp:latest ``` diff --git a/clean_scripts/use_case_1_quick_design.py b/clean_scripts/use_case_1_quick_design.py index 367fe9c..a04bbb4 100644 --- a/clean_scripts/use_case_1_quick_design.py +++ b/clean_scripts/use_case_1_quick_design.py @@ -57,22 +57,30 @@ def generate_target_settings( binder_length: int = 130, num_designs: int = 1 ) -> Dict[str, Any]: - """Generate target settings JSON for BindCraft.""" + """Generate target settings JSON for BindCraft in correct format. + + Returns settings in the format expected by run_bindcraft.py: + - design_path: Output directory for designed binders + - binder_name: Prefix name for designed binders + - starting_pdb: Target protein PDB structure + - chains: Target chain(s) to design binder against + - target_hotspot_residues: Residue numbers to target (comma-separated) + - lengths: [min_length, max_length] range for binder length + - number_of_final_designs: Target number of accepted designs + """ target_pdb_abs = resolve_path(target_pdb) output_dir_abs = resolve_path(output_dir) settings = { - "target_pdb": target_pdb_abs, - "target_chains": chains, + "design_path": output_dir_abs, # NOT "output_dir" "binder_name": name, - "binder_length": binder_length, - "num_designs": num_designs, - "output_dir": output_dir_abs, + "starting_pdb": target_pdb_abs, # NOT "target_pdb" + "chains": chains, # NOT "target_chains" + "target_hotspot_residues": hotspot if hotspot else "", # NOT "hotspot" + "lengths": [binder_length, binder_length], # Array format, NOT single int + "number_of_final_designs": num_designs # NOT "num_designs" } - if hotspot: - settings["hotspot"] = hotspot - return settings @@ -165,6 +173,9 @@ def run_quick_design( settings_file = output_dir / "target_settings.json" save_json(target_settings, settings_file) + # Get absolute path to settings file + settings_file_abs = str(settings_file.resolve()) + # Prepare command filters_json = defaults["filters"] if final_config["filters_enabled"] else None advanced_json = defaults["advanced"] @@ -172,7 +183,7 @@ def run_quick_design( cmd = [ sys.executable, str(bindcraft_path / "run_bindcraft.py"), - f"--settings={settings_file}", + f"--settings={settings_file_abs}", ] if filters_json: diff --git a/clean_scripts/use_case_2_async_submission.py b/clean_scripts/use_case_2_async_submission.py index bfd155f..3ecba3d 100644 --- a/clean_scripts/use_case_2_async_submission.py +++ b/clean_scripts/use_case_2_async_submission.py @@ -58,22 +58,30 @@ def generate_target_settings( binder_length: int = 130, num_designs: int = 3 ) -> Dict[str, Any]: - """Generate target settings JSON for BindCraft.""" + """Generate target settings JSON for BindCraft in correct format. + + Returns settings in the format expected by run_bindcraft.py: + - design_path: Output directory for designed binders + - binder_name: Prefix name for designed binders + - starting_pdb: Target protein PDB structure + - chains: Target chain(s) to design binder against + - target_hotspot_residues: Residue numbers to target (comma-separated) + - lengths: [min_length, max_length] range for binder length + - number_of_final_designs: Target number of accepted designs + """ target_pdb_abs = resolve_path(target_pdb) output_dir_abs = resolve_path(output_dir) settings = { - "target_pdb": target_pdb_abs, - "target_chains": chains, + "design_path": output_dir_abs, # NOT "output_dir" "binder_name": name, - "binder_length": binder_length, - "num_designs": num_designs, - "output_dir": output_dir_abs, + "starting_pdb": target_pdb_abs, # NOT "target_pdb" + "chains": chains, # NOT "target_chains" + "target_hotspot_residues": hotspot if hotspot else "", # NOT "hotspot" + "lengths": [binder_length, binder_length], # Array format, NOT single int + "number_of_final_designs": num_designs # NOT "num_designs" } - if hotspot: - settings["hotspot"] = hotspot - return settings @@ -137,6 +145,9 @@ def run_async_submission( settings_file = output_dir / "target_settings.json" save_json(target_settings, settings_file) + # Get absolute path to settings file + settings_file_abs = str(settings_file.resolve()) + # Prepare command filters_json = defaults["filters"] if final_config["filters_enabled"] else None advanced_json = defaults["advanced"] @@ -144,7 +155,7 @@ def run_async_submission( cmd = [ sys.executable, str(bindcraft_path / "run_bindcraft.py"), - f"--settings={settings_file}", + f"--settings={settings_file_abs}", ] if filters_json: diff --git a/clean_scripts/use_case_4_batch_design.py b/clean_scripts/use_case_4_batch_design.py index 280ea36..bd6c5d6 100644 --- a/clean_scripts/use_case_4_batch_design.py +++ b/clean_scripts/use_case_4_batch_design.py @@ -77,22 +77,30 @@ def generate_target_settings( binder_length: int = 130, num_designs: int = 2 ) -> Dict[str, Any]: - """Generate target settings JSON for BindCraft.""" + """Generate target settings JSON for BindCraft in correct format. + + Returns settings in the format expected by run_bindcraft.py: + - design_path: Output directory for designed binders + - binder_name: Prefix name for designed binders + - starting_pdb: Target protein PDB structure + - chains: Target chain(s) to design binder against + - target_hotspot_residues: Residue numbers to target (comma-separated) + - lengths: [min_length, max_length] range for binder length + - number_of_final_designs: Target number of accepted designs + """ target_pdb_abs = resolve_path(target_pdb) output_dir_abs = resolve_path(output_dir) settings = { - "target_pdb": target_pdb_abs, - "target_chains": chains, + "design_path": output_dir_abs, # NOT "output_dir" "binder_name": name, - "binder_length": binder_length, - "num_designs": num_designs, - "output_dir": output_dir_abs, + "starting_pdb": target_pdb_abs, # NOT "target_pdb" + "chains": chains, # NOT "target_chains" + "target_hotspot_residues": hotspot if hotspot else "", # NOT "hotspot" + "lengths": [binder_length, binder_length], # Array format, NOT single int + "number_of_final_designs": num_designs # NOT "num_designs" } - if hotspot: - settings["hotspot"] = hotspot - return settings @@ -127,6 +135,9 @@ def submit_single_job( settings_file = job_output_dir / "target_settings.json" save_json(target_settings, settings_file) + # Get absolute path to settings file + settings_file_abs = str(settings_file.resolve()) + # Prepare command filters_json = defaults["filters"] if config["filters_enabled"] else None advanced_json = defaults["advanced"] @@ -134,7 +145,7 @@ def submit_single_job( cmd = [ sys.executable, str(bindcraft_path / "run_bindcraft.py"), - f"--settings={settings_file}", + f"--settings={settings_file_abs}", ] if filters_json: