Skip to main content
Glama
ASinanSaglam

cheminformatics_mcp_headless

by ASinanSaglam
README.md
# cheminformatics_mcp_headless

A cheminformatics MCP server whose tools are designed, implemented, tested,
and published entirely by an autonomous Claude Code loop — no human in the
loop once it's set running.

## What this is

Two ideas combined into one project:

1. **A headless, self-driving agent loop.** A single prompt
   ([`AGENT_LOOP.md`](AGENT_LOOP.md)) tells an agent, running non-interactively
   via `claude -p`, to do all of the following in one shot: pick a
   cheminformatics capability that's still missing, implement it, design a
   test for it, write the test, run it, iterate until it's green, and publish
   it — with no human approving any step. [`run_loop.sh`](run_loop.sh) drives
   this in a loop, one fresh process per skill, and survives Claude's usage
   limits by detecting the rate-limit message and backing off until it
   resets instead of dying.
2. **An MCP server for cheminformatics.** [`mcp_server.py`](mcp_server.py)
   dynamically discovers every skill under `skills/` and exposes it as an
   MCP tool — dropping a valid skill folder in is the only "publish" step;
   nothing needs editing to add a tool.

State lives entirely on disk ([`progress.md`](progress.md), the `skills/`
directory) because each loop iteration is a brand-new process with no memory
of the last one.

## Architecture

```
AGENT_LOOP.md  →  run_loop.sh  →  claude -p (one skill per run)
                                        │
                                        ▼
                              skills/<name>/{__init__.py, skill.py,
                                             test_skill.py, meta.json}
                                        │
                        pytest (skills/, tests/) validates it, including
                        against a shared standard-benchmark dataset
                        (tests/data/molecules.csv — see below)
                                        │
                                        ▼
                              mcp_server.py globs skills/*/meta.json
                              and registers each as an MCP tool
```

## The skill contract

Every skill is a folder `skills/<name>/` with exactly four files — see
[`skills/README.md`](skills/README.md) for the full contract:

- `__init__.py` — empty; makes the folder a real package.
- `skill.py` — a single, fully type-hinted, pure `run(...)` function. The
  MCP server introspects its signature to build the tool's JSON schema —
  there's no hand-written schema anywhere.
- `test_skill.py` — a plain pytest module (no custom test runner).
- `meta.json` — `{"name", "description"}` for the MCP tool listing.

## Testing against a real, standard dataset

Hand-picked test SMILES are easy to unconsciously cherry-pick into looking
right. Instead, every skill's test sweeps
[`tests/dataset.py`](tests/dataset.py): a small (195-row), vendored fixture
built from the ESOL/Delaney solubility set (MoleculeNet's standard small-
molecule benchmark) — 175 real compounds plus 20 deliberately broken rows
covering the three failure modes real-world SMILES data actually has:
unparsable SMILES, NaN/empty fields, and structurally incomplete rows. See
[`tests/data/build_dataset.py`](tests/data/build_dataset.py) for provenance;
the fixture is committed so tests never depend on network access.

This caught a real, systemic bug during development: `Chem.MolFromSmiles("")`
returns a valid *empty* molecule rather than `None`, and
`Chem.MolFromSmiles(None)` raises a raw `TypeError` — neither was caught by
the obvious `if mol is None: raise ValueError` guard every skill had. Every
skill now explicitly rejects blank/missing SMILES before it reaches RDKit.

## Setup

```bash
git clone git@github.com:ASinanSaglam/cheminformatics_mcp_headless.git
cd cheminformatics_mcp_headless
python -m venv .venv && source .venv/bin/activate   # or conda/micromamba
pip install -r requirements.txt
```

Run the tests:

```bash
python -m pytest
```

## Using the MCP server

The server speaks plain MCP-over-stdio, so any MCP-capable client can use
it — Claude Code, a local-model harness, a raw `mcp` Python client, etc.

**Claude Code:** this repo's [`.mcp.json`](.mcp.json) already has it configured:

```json
{
  "mcpServers": {
    "chem_skills": {
      "command": "python3",
      "args": ["${CLAUDE_PROJECT_DIR}/mcp_server.py"]
    }
  }
}
```

`${CLAUDE_PROJECT_DIR}` is expanded by Claude Code to this repo's root, so it
works regardless of where you cloned it. `python3` must resolve (via `PATH`)
to the environment you installed `requirements.txt` into — activate your
venv/conda/micromamba environment *before* starting `claude`. (A shell
*function* like some `conda`/`micromamba` activation wrappers won't work as
the `command` value itself — Claude Code execs it directly rather than
through your interactive shell — but an activated environment's `PATH`
works fine, since that's inherited normally.)

Start (or restart) a Claude Code session in this repo, approve the new MCP
server when prompted, then `/mcp` should show `chem_skills` connected.

**Any other MCP client:** point it at the same command/args as a stdio
server; see `mcp.client.stdio` in the `mcp` Python SDK for a minimal example.

## Running the loop yourself

```bash
./run_loop.sh 10   # builds up to 10 more skills, one claude -p process each
```

Progress and decisions are logged to `progress.md` and `loop.log`.

## Current skills

- **brics_fragmentation** — Break a molecule into fragments at BRICS retrosynthetic bonds (dummy-atom labeled cut points) for fragment-library/matched-pair generation.
- **canonical_tautomer** — Canonicalize a molecule's tautomer using RDKit's tautomer enumeration and scoring rules, given a SMILES string.
- **crippen_logp** — Calculate the Crippen-method octanol/water partition coefficient (LogP) of a molecule from its SMILES string.
- **double_bond_stereo** — Find stereogenic C=C double bonds in a SMILES molecule and report each as E, Z, or unspecified, with summary counts.
- **fraction_csp3** — Compute Fsp3, the fraction of sp3-hybridized carbons, of a molecule from its SMILES string.
- **functional_group_scan** — Detect common medchem functional groups (carboxylic acid, ester, amide, amines, alcohol, ether, aldehyde, ketone, nitrile, nitro, sulfonamide, halogen, aromatic ring) in a molecule via SMARTS, with match counts and atom indices.
- **lipinski_ro5** — Evaluate Lipinski's Rule of Five drug-likeness (MW, LogP, H-bond donors/acceptors, violation count) for a molecule from its SMILES string.
- **maximum_common_substructure** — Find the maximum common substructure (MCS) shared by two molecules given their SMILES strings, returning it as a SMARTS pattern with atom/bond counts.
- **molecular_formula** — Compute the Hill-order molecular formula (e.g. C9H8O4) of a molecule from its SMILES string.
- **molecular_weight** — Compute the molecular weight (g/mol) of a molecule from its SMILES string.
- **morgan_fingerprint** — Compute the Morgan (ECFP-like) circular fingerprint of a molecule as a sparse on-bit list, for similarity search, clustering, or ML feature vectors.
- **murcko_scaffold** — Extract the Murcko scaffold (ring systems and linkers, side chains stripped) from a molecule's SMILES, optionally as a generic topology-only scaffold.
- **pains_filter** — Screen a molecule (SMILES) against RDKit's built-in PAINS structural-alert catalog to flag known assay-interference substructures.
- **qed_score** — Compute the QED (Quantitative Estimate of Drug-likeness) score and its eight constituent properties for a molecule given as SMILES.
- **ring_system_analysis** — Analyze ring topology of a molecule from SMILES: ring count, sizes, aromaticity, fused ring systems, and macrocycle detection.
- **rotatable_bonds** — Count rotatable bonds in a molecule (conformational flexibility descriptor; half of the Veber oral-bioavailability rule alongside TPSA).
- **smiles_to_inchi** — Convert a SMILES string to InChI, InChIKey, and canonical SMILES.
- **smiles_to_molblock** — Convert a SMILES string to a 2D-coordinate MDL molblock (V2000 molfile).
- **standardize_molecule** — Standardize a molecule from SMILES by stripping counterions/salts (largest-fragment selection), neutralizing formal charges where possible, and returning the canonical SMILES.
- **stereocenter_analysis** — Find tetrahedral stereocenters in a SMILES molecule and report each as R, S, or unassigned (?), with summary counts.
- **substructure_match** — Match an arbitrary caller-supplied SMARTS substructure query against a SMILES molecule, returning every matching set of atom indices.
- **tanimoto_similarity** — Compute the Tanimoto similarity between two molecules' Morgan (ECFP-like) fingerprints, given their SMILES strings.
- **tpsa_descriptor** — Compute the topological polar surface area (TPSA, in Ų) of a molecule from its SMILES string.

This list grows as the loop runs; each skill's own `meta.json` is the source
of truth if this drifts out of date.