Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ templates/
params/
repo/
examples/
.omc/
58 changes: 54 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,53 @@
# 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
#
# Run (CPU-only):
# 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

Expand All @@ -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

Expand All @@ -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/*
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
31 changes: 21 additions & 10 deletions clean_scripts/use_case_1_quick_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -165,14 +173,17 @@ 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"]

cmd = [
sys.executable,
str(bindcraft_path / "run_bindcraft.py"),
f"--settings={settings_file}",
f"--settings={settings_file_abs}",
]

if filters_json:
Expand Down
31 changes: 21 additions & 10 deletions clean_scripts/use_case_2_async_submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -137,14 +145,17 @@ 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"]

cmd = [
sys.executable,
str(bindcraft_path / "run_bindcraft.py"),
f"--settings={settings_file}",
f"--settings={settings_file_abs}",
]

if filters_json:
Expand Down
31 changes: 21 additions & 10 deletions clean_scripts/use_case_4_batch_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -127,14 +135,17 @@ 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"]

cmd = [
sys.executable,
str(bindcraft_path / "run_bindcraft.py"),
f"--settings={settings_file}",
f"--settings={settings_file_abs}",
]

if filters_json:
Expand Down