Metadata-Version: 2.4
Name: bio-agent-tools
Version: 0.1.0
Summary: MCP server exposing Crispex, Perturbio and Bindigo as agent tools
Author: Siavash Ghaffari
License-Expression: MIT
Project-URL: Homepage, https://github.com/Siavashghaffari/bio-agent-tools
Project-URL: Repository, https://github.com/Siavashghaffari/bio-agent-tools
Keywords: mcp,crispr,docking,single-cell,bioinformatics,agent-tools
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mcp>=2.1
Requires-Dist: crispex>=0.1.0
Requires-Dist: perturbio>=0.1.0
Requires-Dist: bindigo>=0.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# bio-agent-tools

An MCP server that exposes three CRISPR and drug-discovery packages as agent
tools, so guide design, screen analysis and docking happen in one conversation
instead of three repos and three CLIs.

| Wrapped package | Does |
|---|---|
| [Crispex](https://github.com/Siavashghaffari/Crispex) | SpCas9 sgRNA design: Ensembl lookup, PAM scanning, on-target scoring, ranked CSV |
| [Perturbio](https://github.com/Siavashghaffari/Perturbio) | Crop-Seq / CRISPR perturbation screen analysis at single-cell resolution |
| [Bindigo](https://github.com/Siavashghaffari/Bindigo) | Protein-ligand binding prediction, AutoDock Vina docking plus ML |

**[Status](#status)** · **[The five tools](#the-five-tools)** ·
**[A real session](#a-real-session)** · **[Install](#install)** ·
**[Configure](#configure)** · **[MCP client](#use-it-from-an-mcp-client)** ·
**[Docker](#docker)** · **[Troubleshooting](#troubleshooting)** ·
**[Development](#development)** · **[Design notes](#design-notes)**


## Status

Read this before installing.

| Tool | State |
|---|---|
| `design_guides` | **Working.** Verified against live Ensembl |
| `analyze_screen` | **Working.** Verified on a real 300-cell h5ad |
| `top_hits` | **Working** |
| `plot_volcano` | **Working.** Writes a real PNG |
| `predict_binding` | **Blocked upstream.** See below |

`predict_binding` cannot return a binding affinity yet, and the reason is not
this server. Bindigo 0.1.0 ships a skeleton `predict` command:
`bindigo/core/pipeline.py` validates inputs and checks for AutoDock Vina, then
returns a placeholder — docking, ML prediction and "Format and save results"
are all `TODO` upstream. The command **exits 0 and prints "Results saved to:
&lt;path&gt;" while writing no file at all**, so this server treats a missing
output CSV as failure rather than trusting the exit code, and says exactly why.

The wrapper around it is complete and tested, including the CSV parsing path.
When Bindigo implements its pipeline, `predict_binding` starts returning real
results with no change here.

## The five tools

| Tool | Arguments | Returns |
|---|---|---|
| `design_guides` | `gene` or `region`, `species`, `top_n` | Ranked guide table plus CSV path |
| `predict_binding` | `protein`, `ligand`, optional `center_x/y/z`, `size` | Kd, confidence, docking score, pose path |
| `analyze_screen` | `h5ad`, `guides`, `control_label`, `min_cells`, `fdr_threshold` | Results directory plus summary stats |
| `top_hits` | `results`, `perturbation`, `n` | Affected gene table |
| `plot_volcano` | `results`, `perturbation`, `fdr_threshold` | PNG path |

Two rules shape every response. **Tables plus a path, never bulk rows**: a
100-guide request returns the top handful as markdown and a path to the full
CSV. **Large objects move as paths**: h5ad files, results directories and PNGs
cross the boundary as file paths, never as matrices.

## A real session

Every block below is copied verbatim from an actual run on 2026-09-04. Nothing
here is illustrative, and nothing was written by hand.

### Designing guides

`design_guides(gene="TP53", top_n=5)`, against the live Ensembl REST API:

```
### sgRNA guides for TP53

- **Target**: TP53
- **Species**: human
- **Guides returned**: 5
- **Full CSV**: C:\Users\siava\.bio-agent-tools\work\guides_TP53_20260904-171851.csv

| rank | guide_sequence | pam_sequence | strand | efficiency_score | gc_content | offtarget_risk_estimate_1mm | offtarget_risk_estimate_2mm | offtarget_risk_estimate_3mm |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | TGTAGTTCCAGCTACTCTGG | AGG | - | 71.0 | 50.0 | 0 | 6 | 23 |
| 2 | ACACTCATTGCAGACTCAGG | TGG | + | 71.0 | 50.0 | 2 | 6 | 23 |
| 3 | ACTCGGATAAGATGCTGAGG | AGG | + | 71.0 | 50.0 | 3 | 6 | 24 |
| 4 | CTTCCAGTGTGATGATGGTG | AGG | + | 70.0 | 50.0 | 0 | 7 | 24 |
| 5 | CAATTGAAGGCTGTCAGTCG | TGG | - | 70.0 | 50.0 | 0 | 7 | 25 |

> **Off-target values are heuristic estimates, not measured off-target sites.** ...
```

Run it twice and the `offtarget_risk_estimate_*` columns will differ. That is
the point of the warning: they are randomised heuristics, not site counts.

### Analysing a screen

`analyze_screen` on the bundled 300-cell fixture:

```
### Screen analysed

- **Results directory**: C:\Users\siava\.bio-agent-tools\work\screen_tiny_20260904-171900
- **Cells**: 300
- **Genes**: 66
- **Perturbations tested**: 5
- **Perturbations**: BRCA1, EGFR, KRAS, MYC, TP53
- **Gene-level tests**: 330
- **Significant (FDR < 0.05)**: 41
```

Then `top_hits(results=..., perturbation="TP53", n=5)`. Note the label is the
target gene, `TP53`, not the guide `sgTP53_1`:

```
| gene | log_fc | pval_adj |
| --- | --- | --- |
| sgTP53_1 | 65.358795 | 1.6019990490646224e-16 |
| GENE_000 | -10.910223 | 1.6019990490646224e-16 |
| sgNTC_1 | -65.27223 | 1.6019990490646224e-16 |
| GENE_002 | -10.743561 | 1.952670077949663e-16 |
| GENE_001 | -10.119792 | 1.952670077949663e-16 |
```

And `plot_volcano`:

```
### Volcano plot

- **Perturbation**: TP53
- **PNG**: C:\Users\siava\.bio-agent-tools\work\volcano_TP53_20260904-171919.png
- **Genes plotted**: 66
- **Significant (FDR < 0.05)**: 8
```

### Docking, and why it stops

With no AutoDock Vina installed, `predict_binding` names the missing piece and
says the blast radius is one tool:

```
**predict_binding could not run.**

AutoDock Vina was not found. It is a native binary and cannot be installed with
pip: try `conda install -c conda-forge vina`, `brew install autodock-vina`, or a
release binary from https://github.com/ccsb-scripps/AutoDock-Vina/releases. If
it is installed elsewhere, set BIO_AGENT_VINA_PATH to its full path. This
disables predict_binding only; every other tool still works.
```

With Vina present, the call reaches Bindigo and stops there instead:

```
**predict_binding could not run.**

`bindigo predict` exited 0 but produced no results CSV. Bindigo 0.1.0's
prediction pipeline is a skeleton: it validates inputs and checks for AutoDock
Vina, then returns a placeholder without docking, scoring or writing output
(steps 3-9 are TODO upstream in bindigo/core/pipeline.py). No binding affinity
is available until that pipeline is implemented. Nothing is wrong with your
inputs or your Vina install.
```

This is the honest state of the chain today. `design_guides` runs, the handoff
works, and docking is blocked upstream rather than here.

### The cell-count guard

`analyze_screen` runs synchronously, so it refuses oversized inputs rather than
queueing them. With `BIO_AGENT_MAX_CELLS=100` against the 300-cell fixture:

```
**analyze_screen could not run.**

This dataset has 300 cells, above the limit of 100. analyze_screen runs
synchronously and a screen this size would block the session. Subset the data,
or raise the ceiling with the BIO_AGENT_MAX_CELLS environment variable if the
wait is acceptable.
```

The count is read from the h5ad in backed mode, so nothing large is loaded
before the refusal.

### Self-test

`python -m bio_agent_tools.selftest` on a machine with Crispex and Perturbio
installed but no Bindigo and no Vina:

```
Dependencies
------------------------------------------------------------------------
  [ok     ] Crispex (python package)
  [ok     ] Perturbio (python package)
  [MISSING] bindigo (console script)  -> disables predict_binding
  [MISSING] AutoDock Vina (binary)  -> disables predict_binding
  [absent ] Crispex human genome  -> not required by any tool

========================================================================
Summary
========================================================================
  design_guides      ok          300 tokens
  predict_binding    disabled     47 tokens
  analyze_screen     ok          173 tokens
  top_hits           ok           98 tokens
  plot_volcano       ok           46 tokens

All responses within the token budget.
```

Four tools working, one reporting exactly why it cannot, and every response
well inside the 1,000-token ceiling.


## Install

None of Crispex, Perturbio or Bindigo were published on PyPI as of
2026-09-04. `pyproject.toml` declares them by PyPI name, so once they are
published this is all you need:

```bash
pip install bio-agent-tools
```

**Until then**, install the three from git first, then this server without
dependency resolution:

```bash
pip install "crispex @ git+https://github.com/Siavashghaffari/Crispex@main" \
            "perturbio @ git+https://github.com/Siavashghaffari/Perturbio@main" \
            "bindigo @ git+https://github.com/Siavashghaffari/Bindigo@main"
pip install --no-deps "bio-agent-tools @ git+https://github.com/Siavashghaffari/bio-agent-tools@main"
pip install "mcp>=2.1"
```

### Prerequisites

| Requirement | Needed by | Notes |
|---|---|---|
| Network access to `rest.ensembl.org` | `design_guides` | Sequences come from the Ensembl REST API |
| AutoDock Vina binary | `predict_binding` | Not pip-installable. `conda install -c conda-forge vina`, `brew install autodock-vina`, or a [release binary](https://github.com/ccsb-scripps/AutoDock-Vina/releases) |
| An h5ad and a guide CSV | `analyze_screen` | Guide IDs must appear among the gene names |

**A reference genome is not required.** Crispex 0.1.0 never reads one:
`design_guides` fetches sequence from Ensembl, and the off-target columns are
sequence-composition heuristics. `crispex install-genome` is not a prerequisite
for any tool here.

A missing dependency disables only the tools that need it. The server always
starts, and each tool reports its own missing piece.

## Configure

| Variable | Default | Purpose |
|---|---|---|
| `BIO_AGENT_WORKSPACE` | `~/.bio-agent-tools/work` | Where outputs are written |
| `BIO_AGENT_INPUT_DIRS` | *(none)* | Extra directories tools may read, `os.pathsep`-separated |
| `BIO_AGENT_MAX_CELLS` | `50000` | `analyze_screen` refuses datasets above this |
| `BIO_AGENT_VINA_PATH` | *(none)* | Full path to the Vina binary if it is not on `PATH` |
| `BIO_AGENT_DOCKING_TIMEOUT` | `900` | Seconds before `bindigo predict` is killed |

Input paths are validated: a tool refuses any path outside the workspace and
the directories you list in `BIO_AGENT_INPUT_DIRS`.

## Use it from an MCP client

```json
{
  "mcpServers": {
    "bio-agent-tools": {
      "command": "python",
      "args": ["-m", "bio_agent_tools.server"],
      "env": {
        "BIO_AGENT_INPUT_DIRS": "/path/to/your/data"
      }
    }
  }
}
```

The server speaks MCP over stdio.

## Docker

The image bundles AutoDock Vina, which is the one prerequisite pip cannot
install.

```bash
docker build -t bio-agent-tools .
docker run --rm -i -v /my/data:/data -e BIO_AGENT_INPUT_DIRS=/data bio-agent-tools
```

`-i` is required: the server talks over stdin/stdout.

The image installs the three packages from git, because they are not on PyPI
yet. Once they are published, delete that block from the `Dockerfile` and the
`--no-deps` flag below it; `pip install .` will then resolve them from PyPI.

### The chained workflow, run in the image

Built image, verified 2026-09-04. Vina and the Bindigo console script are both
present:

```
$ docker run --rm --entrypoint vina bio-agent-tools:0.1.0 --version
AutoDock Vina v1.2.7

$ docker run --rm --entrypoint bindigo bio-agent-tools:0.1.0 --version
bindigo, version 0.1.0
```

Then guide design feeding into docking, in one container:

```
>>> Step 1: design guides for TP53
### sgRNA guides for TP53

- **Target**: TP53
- **Species**: human
- **Guides returned**: 5
- **Full CSV**: /work/guides_TP53_20260904-212404.csv

| rank | guide_sequence | pam_sequence | strand | efficiency_score | gc_content | offtarget_risk_estimate_1mm | offtarget_risk_estimate_2mm | offtarget_risk_estimate_3mm |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | TGTAGTTCCAGCTACTCTGG | AGG | - | 71.0 | 50.0 | 0 | 6 | 24 |
| 2 | ACTCGGATAAGATGCTGAGG | AGG | + | 71.0 | 50.0 | 1 | 5 | 25 |
| 3 | ACACTCATTGCAGACTCAGG | TGG | + | 71.0 | 50.0 | 3 | 4 | 24 |
| 4 | CTTCCAGTGTGATGATGGTG | AGG | + | 70.0 | 50.0 | 0 | 6 | 23 |
| 5 | CAATTGAAGGCTGTCAGTCG | TGG | - | 70.0 | 50.0 | 3 | 6 | 26 |

> **Off-target values are heuristic estimates, not measured off-target sites.** ...

[tokens: 292 ]

>>> Step 2: dock against 4HHB
**predict_binding could not run.**

`bindigo predict` exited 0 but produced no results CSV. Bindigo 0.1.0's
prediction pipeline is a skeleton: it validates inputs and checks for AutoDock
Vina, then returns a placeholder without docking, scoring or writing output
(steps 3-9 are TODO upstream in bindigo/core/pipeline.py). No binding affinity
is available until that pipeline is implemented. Nothing is wrong with your
inputs or your Vina install.

[tokens: 112 ]
```

That is the whole chain as it stands. Step 1 completes against live Ensembl
from inside the container. Step 2 reaches Bindigo with a working Vina v1.2.7
underneath it and stops at the upstream skeleton — which is the accurate
result, not a workaround. When Bindigo's pipeline lands, this same image
returns a Kd here.

The build also asserts that all five tools register before the image is
tagged:

```
#17 [13/13] RUN python -c "import asyncio; from bio_agent_tools import server; ...
#17 1.585 tools: ['design_guides', 'predict_binding', 'analyze_screen', 'top_hits', 'plot_volcano']
```

## Troubleshooting

**`pip install bio-agent-tools` cannot resolve crispex / perturbio / bindigo.**
They are not on PyPI yet. Use the git install shown under
[Install](#install).

**The Docker build fails at the Vina download with `SSL certificate problem:
unable to get local issuer certificate`.** Your network intercepts TLS and the
base image does not trust the interception CA. Export your organisation's root
certificate and add it before the download:

```dockerfile
COPY corp-ca.pem /usr/local/share/ca-certificates/corp-ca.crt
RUN update-ca-certificates
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt     SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt     GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt
```

The same condition breaks `git clone` and `pip` on the host. Fix those with
`git -c http.sslBackend=schannel` (Windows), `pip --use-feature=truststore`, or
`import truststore; truststore.inject_into_ssl()`. Point tools at your system
trust store rather than disabling verification.

**`UnicodeEncodeError: 'charmap' codec can't encode characters`.** A non-UTF-8
console. The wrapped packages print box-drawing characters. Set
`PYTHONIOENCODING=utf-8`, or use the Docker image, which sets it already.

**`analyze_screen` says a path is outside the allowed directories.** By design:
tools read only from the workspace and from `BIO_AGENT_INPUT_DIRS`. Add the
directory holding your data:

```bash
export BIO_AGENT_INPUT_DIRS=/path/to/data
```

**`analyze_screen` refuses a dataset as too large.** It runs synchronously and
has no job queue. Subset the data, or raise `BIO_AGENT_MAX_CELLS` if you are
willing to wait.

**`predict_binding` says Bindigo produced no results CSV.** Expected today —
see [Status](#status). Nothing is wrong with your inputs or your Vina install.

## Development

```bash
pip install --no-deps -e .
pip install "mcp>=2.1" pytest ruff
pytest tests/ -q
ruff check src tests
```

The offline suite mocks all three packages and passes with none of them
installed, no network and no Vina. One integration test runs Perturbio for real
on `tests/fixtures/tiny.h5ad` and skips when Perturbio is absent. Regenerate
the fixture with `python tests/fixtures/make_fixtures.py`.

A live smoke test that calls every tool once:

```bash
python -m bio_agent_tools.selftest
```

## Design notes

- `server.py` contains no package logic. It defines tools, calls a wrapper,
  renders through `trim`, and returns.
- Wrappers return plain dicts, never markdown, which is what lets them be
  tested with the underlying packages mocked out.
- The three packages are imported inside function bodies, so a missing package
  never stops the server from starting.
- stdout is the MCP transport, so everything the wrapped packages print is
  redirected to stderr. Perturbio prints progress unconditionally.
- Off-target estimates keep their long column names on purpose. See below.

## About the off-target numbers

Any response carrying `offtarget_risk_estimate_*` values also carries this
warning, because the numbers are easy to misread:

> Crispex 0.1.0 performs no genome-wide off-target search. It never reads the
> reference genome and never aligns anything. Those columns are a heuristic
> function of the guide's own sequence composition, randomised within a band,
> and they differ between runs. `perfect_match_assumed` is assumed, not
> verified.

Validate guides with Cas-OFFinder, CRISPOR or CHOPCHOP before ordering.

## Project

- [CHANGELOG.md](CHANGELOG.md) — release history and known limitations
- [CONTRIBUTING.md](CONTRIBUTING.md) — setup, conventions, scope boundaries
- [docs/](docs/) — the design documents this server was built from

## License

MIT. See [LICENSE](LICENSE).
