▮ ARI HARRISON
Portfolio · one engine, every surface

The open engine, and every
surface that reaches it.

Product portfolio for NovoMCP, the open computational chemistry engine, open-sourced July 2026 under Apache-2.0 and shipped through v1.4.0. Clone it and it boots with zero external services: no auth, no account, no API key. One engine (MCP + REST, 69 tools), one 11-stage discovery funnel, and the surfaces that reach it: chat, code, desktop, browser, editor, and the bundled dashboard. Each case below is a product decision: where it lives, who it's for, what trade-off it pays. The problem, the call, the build, the outcome.

01Tools in the engine69
02External services to boot0
03Autonomous funnel stages11
04Product surfaces shipped6
05Engines underneath1
Every surface is a door into the same engine, not a separate product. Start in Claude, continue in the browser, batch from the API, watch it from the bundled dashboard. One engine underneath, whichever door you use.
01 · MCP connector · AI assistants

Novo & Novo Compute. The chat surface.

Live

Add Novo to Claude, ChatGPT, Gemini, or Cursor and run the full discovery funnel in chat, with human-in-the-loop confirmation at each stage.

The Problem

Chemists already live in Claude and ChatGPT. Asking them to leave their assistant for a separate product is adoption death. But "an MCP server" is not a product either; it is a protocol. The product call is what you ship through it.

The Decision

Two MCP servers, not one. Novo exposes core tools on every tier including the free preview; Novo Compute exposes paid GPU/quantum tools. Different key prefixes (nmcp_ vs ncmcp_) so a leaked core key cannot burn GPU budget.

Same Docker image; one env flag (NOVO_SERVER_MODE) toggles the tool catalog. Zero duplicated code, a separate governance surface.

The Build

FastAPI MCP server, OAuth + RFC 8693 token exchange (a separate novomcp-auth service), Redis-cached key validation with a 5-minute TTL and an in-memory fallback so dashboard outages don't take MCP down.

Funnel stages use MCP elicitation, so the assistant prompts the user at each gate. Never silent autonomy on someone else's compounds.

The Outcome

Live on Claude AI, Claude Code, Cursor, ChatGPT (Plus / Pro / Business), and Gemini. 69 tools across the two-server split: ADMET, docking, MD, QM, structure prediction, materials, plus the hosted FAVES compliance API for regulated pipelines.

Self-hosted engines expose MCP at localhost:8018/mcp; cloud deployments run on a per-org subdomain.

Proof · Production MCP at scale
An MCP server is easy. Running one in production, across every major client, is the actual work.

A lot of teams now ship "an MCP server." What NovoMCP runs daily is harder: a two-server governance model on Anthropic's protocol with OAuth 2.1 + RFC 8693 token exchange for the paid tier, scoped Bearer keys with Redis-cached validation (5-minute TTL plus an in-memory fallback so the dashboard going down doesn't take MCP down with it), MCP elicitation driving an 11-stage human-in-the-loop funnel, and a separate identity service (novomcp-auth) that issues the exchange tokens.

Live across every major MCP client: Claude AI (web + desktop), Claude Code, Cursor, ChatGPT (Plus / Pro / Business), Gemini. One backend, one tool catalog, one billing path behind all of them.

2MCP servers, scoped
69tools served
5+clients in production
Apache-2.0license underneath
02 · REST API · your own code

api.novomcp.com. Bring your own model.

Live

Call any of the 69 tools from your own code, or wire them into your own agent. One REST surface, OpenAPI 3.1 as the source of truth.

The Problem

Some teams already have their own AI agent, their own automation, their own pipeline. They don't want OAuth flows, they don't want MCP. They want a Bearer key and a JSON body.

The Decision

One endpoint shape, not one route per tool. Every tool is POST /v1/tools/{name} with a Bearer key. No bespoke per-tool routes to design, document, or version. The tool catalog is the live OpenAPI 3.1 spec, so there are no separate docs to drift.

The same backend serves MCP and REST; zero code duplicated for the second surface.

The Build

FastAPI proxy at api.novomcp.com, OpenAPI 3.1 generated from the tool registry, Bearer-key auth shared with MCP (the same key validates either surface). Rate limits and credit accounting share the same Aurora-backed billing path.

The Outcome

Live. Engineers who don't want a chat assistant can curl their way to ADMET in one command. The OpenAPI spec also feeds custom GPTs, agent frameworks, and Postman collections: same engine, no extra maintenance.

03 · Workstation · NovoWorkbench

The workbench. On your machine.

Design-partner preview

The molecule canvas, 3D viewers, and discovery funnel as a native desktop for macOS and Windows. Local RDKit, offline alert screening, bring-your-own-LLM chat. Ships as OSS in v1.5.x.

The Problem

Plenty of researchers don't have API skills, don't use Claude, and don't want a chat to do their job. They want an app. Some live in regulated or offline environments where local-first is non-negotiable. The chat and REST surfaces don't reach them.

The Decision

Ship the engine as a native desktop workbench, not a browser SPA behind a dashboard. Local RDKit and alert screening (PAINS + Lipinski + controlled-substance flags) run without a network; bring-your-own-LLM chat routes to the provider you configure. The cloud runtime was retired with the OSS launch; the desktop rebuild ships as OSS in v1.5.x.

The Build

Tauri (Rust + system webview), Python sidecar bundled with the binary so the RDKit stack ships inside the app. Apple Developer ID notarized (team 8N9K9B7Y69), Gatekeeper-accepted. The v1.5.x rebuild routes engine calls to a configurable engine URL, self-hosted or cloud, with no hosted-dashboard dependency.

The Outcome

Design-partner preview live for macOS and Windows. Ships as OSS in v1.5.x per the product roadmap. Access is via /sales-inquiry until the OSS release.

04 · Browser · Chrome extension

Hover any SMILES. Get the profile.

Live

Profile cards on PubChem, ChEMBL, and preprints: ADMET, FAVES compliance, and similarity search across 122,454,458 compounds, on the page you're already on.

The Problem

Pharma and academic chemists live on PubChem / ChEMBL / DrugBank / preprint sites for half their day. Every "go open the workbench, paste this in" is friction. The value has to come to the page they are already on.

The Decision

Show value before asking for a login. Hover-card profiling runs against the free tier of the engine, with no account required for the basic ADMET and compliance read. The upgrade path is the side-panel advanced-compute tab and a "Continue in AI" deep-link.

Whitelisted hosts only, with no opportunistic SMILES detection across the open web. A privacy and safety call, not a technical one.

The Build

Manifest V3, content scripts on whitelisted molecular databases and preprint servers, side-panel UI, and a "Continue in AI" deep-link that hands the current SMILES into any MCP-connected assistant via a session handoff.

The Outcome

Published on the Chrome Web Store. Funnels to the dashboard for users who want an account, and to the MCP surface for users who want to keep working in their assistant.

05 · Editor · Word add-in

Profile SMILES where you write.

Private beta

Manuscripts, protocols, grants, regulatory writeups. SMILES detected anywhere in the document, profile cards in the task pane, ADMET + FAVES tables inserted as formatted Word tables, inside the editor.

The Problem

The pharma writeup happens in Word: grants, IND sections, protocols, manuscripts. Compounds get pasted in early and never profiled until much later, often after review. By then the wrong number is everywhere.

The Decision

Inline, but private-beta first. The hard part is not detecting SMILES in Word; it is the UX of an inline assistant that stays out of the way. AppSource-style review locks in a UX before validation; sideload during beta keeps the iteration loop fast.

Insert-as-table, not just preview, so the screening result becomes part of the document rather than a sidebar fact.

The Build

Office.js add-in. Manifest hosted statically at addin.novomcp.com (S3 + CloudFront + ACM, SSE-KMS-encrypted bucket with an explicit CloudFront kms:Decrypt grant on the CMK: a real gotcha that 403s the manifest silently if missed).

The Outcome

Private beta. Sideload the manifest at addin.novomcp.com/manifest.xml. AppSource submission gated on UX validation with the beta cohort.

06 · Control room · Bundled dashboard

The bundled dashboard. Ships with the engine.

Live

A minimal Next.js dashboard bundled with the open engine: auth, API keys, usage, and pipeline job monitoring. Runs on localhost when you self-host; on your dedicated subdomain in cloud deployments.

The Problem

Every surface needs the same answers: who is this key, what can it call, what has it called, what jobs are outstanding. Built per-surface, that is every surface reimplementing identity and observability. Built as a hosted-only product, it disappears when the hosted product does, as it did when the AWS shutdown consolidated the hosted stack down to the open engine.

The Decision

Bundle the dashboard with the engine itself. No separate hosted product. The dashboard ships inside the OSS repo (frontend-nextjs/), starts alongside the engine, points at localhost by default, and grows with the engine on the same release train. Self-hosted operators get identity, audit, and jobs UI out of the box.

The Build

Next.js 15 frontend shipped in the OSS repo at frontend-nextjs/. Reads from the engine's REST surface (same base URL as the tool catalog). Cloud deployments back identity / audit / async-jobs with Aurora PostgreSQL behind the same UI; self-hosted operators point it at SQLite / Postgres / whatever the auth service backs.

The Outcome

Minimal today, growing each release. A fresh git clone gives you the engine plus a UI to watch it, with no separate install. Cloud deployments get the same UI on their subdomain, no divergent build. New surfaces plug into the same identity and audit path the dashboard already speaks.

§ Also shipping // open source
Apache-2.0 · The engine's open core · Library + MCP

novomcp-lite

The lighter way to use NovoMCP's cheminformatics without standing up the whole engine. Nine zero-config tools: RDKit properties, profiling, synthetic-accessibility, PAINS/BRENK alerts, library screening, plus ChEMBL / ClinicalTrials.gov / bioRxiv / PubMed literature search, as a two-dependency package. No keys, no account, no services. Prompted by a researcher who wired NovoMCP up to his own agent and asked for a lighter, wrappers-only build.

Python librarypip install novomcp-liteCall the tools directly, two dependencies.
MCP serverpip install "novomcp-lite[mcp]"Point Claude, or any agent, at the same nine tools.

The design call: one implementation, no drift. novomcp-lite is not a fork of the engine; the full NovoMCP engine imports the exact same code. So the numbers from the two-dependency library are, to the decimal, the numbers from the full engine. The open core is not a marketing cut-down; it is what the product runs on.

Open source →GitHubPyPI
MIT · Local-first · MCP + CLI + Library + REST

NovoMD

Turns a SMILES string into 32+ molecular descriptors from a real 3D conformer: geometry, energy, electrostatics, surface, volume. Runs on your own machine, no account, no API key. The same one-engine-many-surfaces pattern as NovoMCP, applied at library scale, in the open.

Python librarypip install novomdcalculate_properties("CCO"). RDKit, NumPy, SciPy install automatically.
CLInovomd props "CCO"batch, explain, report. CSV / TSV / JSON / HTML out.
MCP (Hugging Face)quantnexusai-novomd.hf.spaceGradio MCP endpoint. Add to Claude as a custom connector.
REST (Docker)ghcr.io/ariharrisonlab/novomdFastAPI behind the same core. X-API-Key auth.

The design call: scope discipline. NovoMD computes descriptors and stops there: no ADMET, no pKa, no binding affinity, no solubility. That boundary is documented in the README and shipped as an agent skill so assistants are told explicitly where the tool's authority ends. Determinism on top: same SMILES, identical descriptors, any machine.

Open source →GitHubPyPIHugging Face
§ Cross-cutting decisions // the calls underneath every surface

Engine-first language

"Computational chemistry engine", not platform, OS, substrate, operating layer, or orchestration. Those nouns collide with every competitor in the category. The test on every public line: could a competitor put this exact sentence on their homepage unchanged? If yes, replace the noun.

Drives copy across nav, marketing, sales scripts, and the assistant's elicitation prompts. The same word in every surface.

Scale-to-zero by default

Every compute service ships with min_replicas=0 (Fargate min_capacity=0, EKS GPU nodegroup → 0 nodes). Only the public-path surfaces (the engine gateway, the bundled dashboard) keep min=1.

Carries the Azure cold-start posture forward to AWS, keeps unit economics honest on day one, and avoids a retrofit. Pre-warm is opt-in for cloud deployments, not a default cost.

Azure → AWS migration

Full inventory: Aurora (replacing Azure SQL), EKS (replacing Container Apps), S3 + CloudFront (replacing Blob), Secrets Manager, plus NVIDIA credits. ~30 services, ~12 weeks. A reconciliation layer (row parity, schema parity, NULL audit, value fidelity) baked into every cutover.

Customer surfaces, download links, the auto-update manifest, and MCP endpoints moved without version regression for existing users.

Private-endpoint-only infra

EKS API, Aurora, Redis, and internal ALBs are all private. GitHub-hosted runners can't reach them; every kubectl apply and psql times out from CI. The locked design: build on GH runners, deploy via GH OIDC → SSM → bastion.

Applied uniformly to every NovoServices repo so the pattern does not re-litigate per port. The bastion is pre-wired with kubectl, psql, and EKS + Aurora + Secrets perms.

Self-hosted or cloud. No middle.

The prior model had a Free Trial / Core / Scale / Enterprise credit ladder. Killed all of it at the OSS launch. Self-hosted, free forever (Apache-2.0, run it yourself) or cloud deployment, contact for pricing (per-org subdomain, SAML SSO, GPU pool, FAVES compliance). No self-serve middle tier.

Trade-off: no self-serve upgrade path from free to paid. In exchange: two real buyers, two paths, and no billing complexity in between.

Human-in-the-loop, not autonomous

The discovery funnel is 11 stages in the open engine (FEP is a paid Novo Compute service, not part of the OSS funnel). It runs as an MCP server inside the user's assistant; every stage prompts the user via MCP elicitation. Not a standalone agent running on someone else's compounds.

The audit trail records every gate, decision, and tool call. Pipeline audits are the quality gates; the funnel is what is audited.

The pluggable spine: local ↔ hosted, one codebase

Auth, metering, and audit are three swappable implementations behind stable protocols, chosen by env flag (NOVO_AUTH / NOVO_METER / NOVO_AUDIT = local · hosted · custom). A fresh clone runs the local spine: no auth, no credit accounting, audit to ~/.novo/audit.jsonl.

No OSS-vs-enterprise fork. The open engine is the product; auth / metering / audit is a runtime boundary, not a feature gate. Set the flags to custom and drop in your own implementations.

Wrap open compute, add an accelerated axis

The engine wraps established open tools (RDKit, GROMACS, AutoDock-GPU, OpenFold, Boltz, Gnina, xTB, ANI-2x, AIMNet2, MACE) rather than reimplementing them. The orchestration and governance are the work, not the physics. The GPU service wrappers ship as standalone open repos too: gromacs-md, novomcp-nnp, novomcp-qm.

As of v1.3.0, geometry, energy, and conformer tools take an engine axis (ase ↔ alchemi, crest ↔ alchemi): the same tool call runs the open reference path or an ALCHEMI GPU path, chosen per call. As of v1.4.0, the engine sources its cheminformatics from the open novomcp-lite package, so the engine and the library never diverge.

§ Contact

Open to product, AI, and 0-to-1 leadership conversations.