NovoMD turns a SMILES string into a set of molecular descriptors. It runs on your own machine, with no account and no API key. Install it as a Python library, call it from the command line, or run it as a REST service.
What it is, and what it is not
NovoMD computes 32+ outcome-level descriptors from a 3D conformer: geometry, an energy estimate, electrostatics, surface and volume, atom counts, and the coordinates for visualization. The calculation is local and deterministic.
It does not run full molecular dynamics trajectories, docking, binding affinity, or ADMET. The scope is deliberate. For that work, see Beyond property calculation below.
Quick start
Python library
The shortest path. No server, no key.
pip install novomd
from novomd import calculate_properties
props = calculate_properties("CCO")
print(props["molecular_weight"]) # 46.07
print(props["radius_of_gyration"])
Process a list in one call. A bad SMILES does not stop the batch; each item carries its own status.
from novomd import calculate_properties_batch
results = calculate_properties_batch(["CCO", "CC(=O)O", "NOT_VALID"])
for item in results:
if item["status"] == "ok":
print(item["smiles"], item["properties"]["molecular_weight"])
else:
print(item["smiles"], "->", item["error"])
RDKit, NumPy, and SciPy install automatically. Everything runs on your hardware.
Command line
novomd props "CCO"
novomd props "CC(=O)OC1=CC=CC=C1C(=O)O" --compact
novomd batch molecules.smi --out results.csv
batch reads a .smi file (one SMILES per line) and writes a CSV, TSV, or JSON table.
From an AI assistant (MCP)
NovoMD exposes a Model Context Protocol endpoint, so assistants like Claude can query molecular properties directly.
Endpoint: https://quantnexusai-novomd.hf.space/gradio_api/mcp/sse
Add it as a custom connector in Claude (Settings, then Integrations), or point any MCP-compatible client at the same URL. Then ask:
- "Calculate the molecular properties of aspirin (CC(=O)OC1=CC=CC=C1C(=O)O)."
- "What is the dipole moment of caffeine?"
The endpoint works with Claude (web and desktop), Cursor, Continue.dev, and any client that speaks the MCP specification.
For agents that run the package locally, a drop-in agent skill teaches the assistant when to use NovoMD, how to read its output, and the boundary it must not cross (no ADMET, pKa, or binding claims).
REST service (Docker)
For networked or containerized use, run the same core behind FastAPI.
# pre-built image
docker run -d -p 8010:8010 \
-e NOVOMD_API_KEY="your-secure-api-key" \
--name novomd \
ghcr.io/ariharrisonlab/novomd:latest
curl http://localhost:8010/health
Or from source:
pip install "novomd[server]"
uvicorn main:app --host 0.0.0.0 --port 8010
What you get
32+ descriptors, calculated from an embedded 3D structure:
- Geometry (7): radius of gyration, asphericity, eccentricity, inertia shape factor, span, principal moments of inertia
- Energy (6): conformer energy, van der Waals, electrostatic, torsion strain, angle strain, optimization delta
- Electrostatics (6): dipole moment, total charge, max and min partial charge, charge span, electrostatic potential
- Surface and volume (4): SASA, molecular volume, globularity, surface-to-volume ratio
- Atom counts (2): total atoms, heavy atoms
- Visualization (5+): full atomic coordinates, atom types, bond connectivity
Energy values are estimates from the conformer, not from a force-field simulation. The descriptors are derived from real 3D coordinates, not mocked. SCIENCE.md documents the method and is honest about which fields are real geometry and which are deterministic estimates; docs/benchmark_report.md is a reproducible, offline, single-core benchmark, and the determinism guarantee (same SMILES, identical descriptors, any machine) holds regardless of hardware.
Library reference
from novomd import calculate_properties, calculate_properties_batch
# one molecule -> descriptor dict
calculate_properties("CCO", add_hydrogens=True, optimize_3d=True)
# many molecules -> list of {smiles, status, properties | error}
calculate_properties_batch(["CCO", "C"], max_batch_size=1000)
Both raise InvalidSMILESError for unparseable input and RDKitNotAvailableError if RDKit is missing. The batch function isolates per-item failures instead of raising.
Conformer ensembles (optional)
By default NovoMD embeds a single conformer. For ensemble-averaged descriptors, install the optional extra:
pip install 'novomd[ensemble]' # requires Python 3.12+
from novomd import calculate_properties
props = calculate_properties("CCCCc1ccccc1OCCO", conformers=50)
print(props["method"]) # "openconf_ensemble"
print(props["n_conformers"])
print(props["conformational_flexibility_rgyr"]) # ensemble spread of R_gyr
Geometries come from openconf (MIT, by the Rowan team). NovoMD uses openconf's conformers and Boltzmann weights, then computes its descriptors as a population average. Conformer-dependent values (shape, span, dipole) are Boltzmann-weighted; structural fields come from the lowest-energy conformer. If the extra is not installed, NovoMD falls back to the single-conformer calculation: no error, still local, still no account.
Interpretation
Beyond raw numbers, NovoMD reads a molecule's profile: the standard medicinal-chemistry descriptors and the textbook rule-of-thumb checks, with a plain-language summary.
from novomd import calculate_druglikeness, summarize, interpret
d = calculate_druglikeness("CC(=O)OC1=CC=CC=C1C(=O)O") # aspirin
d["logp"], d["tpsa"], d["qed"] # 1.31, 63.6, 0.55
d["lipinski"]["violations"] # []
d["veber"]["passes"] # True
summarize(d)
# "A small, moderately lipophilic molecule (MW 180.16, logP 1.31).
# Satisfies Lipinski's rule of five with no violations. Meets the Veber
# criteria (TPSA 63.6, 2 rotatable bonds). QED 0.55 (moderate drug-likeness)."
interpret("CCO") # the descriptors plus a "summary" key, in one call
From the command line:
novomd explain "CC(=O)OC1=CC=CC=C1C(=O)O"
novomd explain "CCO" --json
This describes a molecule using public cheminformatics (logP, TPSA, QED, Lipinski, Veber). It does not predict ADMET, pKa, solubility, or binding. That boundary is deliberate.
Reports
Roll identity, drug-likeness, and the summary into a one-page report. Markdown for a PR or a notebook, HTML (with a 2D structure depiction) to share, or JSON for a pipeline.
from novomd import generate_report
generate_report("CCO") # markdown (default)
generate_report("CCO", fmt="html") # styled HTML with a 2D depiction
generate_report("CCO", fmt="json") # machine-readable
novomd report "CC(=O)OC1=CC=CC=C1C(=O)O" --out aspirin.html
novomd report "CCO" --format json
The format is inferred from the --out extension (.md / .html / .json), or set it with --format.
REST API
All endpoints except /health require an API key in the X-API-Key header.
| Endpoint | Method | Description |
|---|---|---|
/health |
GET | Health check (no auth) |
/status |
GET | Service status and capabilities |
/smiles-to-omd |
POST | Convert SMILES to OpenMD with 32+ properties |
/batch |
POST | Calculate properties for many SMILES in one call |
/atom2md |
POST | Convert PDB to OpenMD format |
/force-fields |
GET | List available force fields |
/force-field-types/{ff} |
GET | Atom types for a force field |
curl -X POST http://localhost:8010/batch \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"molecules": ["CCO", "CC(=O)O", "NOT_VALID"]}'
{
"count": 3,
"succeeded": 2,
"failed": 1,
"results": [
{"smiles": "CCO", "status": "ok", "properties": {"molecular_weight": 46.07, "...": "..."}},
{"smiles": "CC(=O)O", "status": "ok", "properties": {"...": "..."}},
{"smiles": "NOT_VALID", "status": "error", "error": "Invalid SMILES string: 'NOT_VALID'"}
]
}
Batches are capped at 1,000 molecules per request and share the service rate limit.
Notebooks
| Notebook | Topic |
|---|---|
| 01_getting_started.ipynb | Basic usage and conversion |
| 02_molecular_properties.ipynb | Property analysis with pandas and matplotlib |
| 03_visualization.ipynb | 3D visualization with plotly and py3Dmol |
| 04_batch_processing.ipynb | One-call batch, library and endpoint |
Beyond property calculation
NovoMD computes molecular descriptors locally. It does not run full MD trajectories, docking, ADMET, or compliance.
For those, the same team builds NovoMCP, a computational engine for AI-native discovery: 122M enriched compounds, docking and FEP pipelines, ADMET and compliance scoring, and an immutable audit trail on every step. NovoMD is open and always will be. NovoMCP is the production layer for work that outgrows it.
Learn more: novomcp.com
Force fields
AMBER14, AMBER99SB, CHARMM36, OPLS-AA/M, GROMOS 54A7. Property values are conformer-derived and force-field-independent; the force field affects only the OpenMD output.
Configuration
Set these in a .env file or as environment variables (REST service only).
| Variable | Description | Default |
|---|---|---|
NOVOMD_API_KEY |
API authentication key (required) | - |
PORT |
Server port | 8010 |
HOST |
Server host | 0.0.0.0 |
LOG_LEVEL |
DEBUG, INFO, WARNING, ERROR | INFO |
CORS_ORIGINS |
Comma-separated origins, or "*" for all | localhost:3000,localhost:8080 |
RATE_LIMIT |
e.g. "100/minute", "1000/hour" | 100/minute |
Development
pip install -e ".[dev,server]" # core + server + dev tools
pre-commit install
pytest tests/ -v
pytest tests/ --cov=novomd --cov=main --cov-report=term-missing
black . && isort . && flake8 .
mypy novomd main.py auth.py config.py
bandit -r . -x ./tests
NovoMD/
├── novomd/ # importable library (framework-free core)
│ ├── core.py # property calculation
│ ├── batch.py # batch with per-item error isolation
│ ├── conversion.py # PDB to OpenMD
│ ├── cli.py # `novomd` command
│ └── exceptions.py
├── main.py # FastAPI service (imports the core)
├── config.py # configuration
├── auth.py # API-key authentication
├── tests/ # unit + integration tests
├── examples/ # Jupyter notebooks
└── .github/workflows/ # CI and PyPI publish
Security
NovoMD runs locally by default; no molecular data leaves your machine. For the REST service, use a strong NOVOMD_API_KEY, deploy behind TLS, and restrict CORS_ORIGINS. To report a vulnerability, see SECURITY.md.
Contributing
Contributions are welcome. See CONTRIBUTING.md.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
License
MIT. See LICENSE.
Citation
@software{novomd2025,
title = {NovoMD: Local-First Molecular Property Calculation},
author = {NovoMCP},
year = {2025},
url = {https://github.com/ariharrisonlab/NovoMD}
}
Built by the NovoMCP team
Release files for novomd 1.5.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| novomd-1.5.2.tar.gz | 36.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| novomd-1.5.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.5 kB
Release files / novomd-1.5.2.tar.gz
| Download URL | novomd-1.5.2.tar.gz |
|---|---|
| Size | 36.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b7cace249630cb33db69319865ce15c182fa72bce37b91766374c1915de5ca66
|
|
BLAKE2b-256 checksum How to use checksums |
3310929da2f5119a2d84654045003a37ff957900cce9fcf707b1e8da30c38f62
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 6, 2026.
Transparency logRelease files / novomd-1.5.2-py3-none-any.whl
| Download URL | novomd-1.5.2-py3-none-any.whl |
|---|---|
| Size | 27.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
be4834da51cb2c62c937bc06535ef99deefa33b3f7160e08fc96eb98d30d0527
|
|
BLAKE2b-256 checksum How to use checksums |
420cd16d4c180214de52d3e33019a0a3b194839eb3e166828ce9c1704f0a5d47
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 6, 2026.
Transparency log