Build on Nuclide
862 linked objects as static JSON, with no key and permissive CORS, plus an MCP server for assistants. Below: the spec, the types, three recipes you can paste, embeddable cards, and what attribution the licence asks of you.
- OpenAPI 3.1 description for
/api/v1/, generated at build time from the file layout, importable into Postman, Stoplight or an SDK generator (an older hand-written copy is at /openapi.yaml). - Endpoint list with live links and counts.
- src/lib/schema.ts: the Zod schema that every object is validated against at build time. It is the source of truth for field names and enums.
- MCP server:
npm run mcpexposes search, get_entity, list_kind, for_cancer and rank tools over stdio.
Types in your project
The schema file has no dependencies beyond Zod. Copy it (or add the repo as a git submodule) and infer the types; the JSON under /api/v1/ parses with it unchanged.
// types/nuclide.ts: copy src/lib/schema.ts from the repo next to this file, then
import { EntitySchema, type Entity, type Drug, type Trial, KIND_META } from "./schema";
export type Neighbours = Record<string, Array<{ id: string; kind: string; name: string; route: string }>>;
export type EntityResponse = { entity: Entity; route: string; neighbours: Neighbours };
export async function getEntity(id: string): Promise<EntityResponse> {
const r = await fetch(`https://nuclide.cc/api/v1/entities/${id}.json`);
if (!r.ok) throw new Error(`Nuclide: ${id} not found`);
const data = (await r.json()) as EntityResponse;
data.entity = EntitySchema.parse(data.entity); // validates and applies defaults
return data;
}
export async function listKind<K extends Entity["kind"]>(kind: K): Promise<Extract<Entity, { kind: K }>[]> {
const r = await fetch(`https://nuclide.cc/api/v1/${KIND_META[kind].plural}.json`);
return (await r.json()) as Extract<Entity, { kind: K }>[];
}Recipe 1: a trial matcher
Take a cancer and a set of biomarkers, find the products aimed at those targets in that cancer, then pull live recruiting studies from ClinicalTrials.gov for each product. Two static fetches plus the public registry API; no server needed.
const SITE = "https://nuclide.cc";
const indicationId = "tnbc";
const biomarkers = ["trop2", "pdl1", "brca"]; // Nuclide target ids; see /targets/
const { neighbours } = await fetch(`${SITE}/api/v1/entities/${indicationId}.json`).then((r) => r.json());
const products = (neighbours.drug ?? []) as Array<{ id: string; name: string }>;
const drugs = await fetch(`${SITE}/api/v1/drugs.json`).then((r) => r.json());
const byId = new Map(drugs.map((d: { id: string }) => [d.id, d]));
const matches = products
.map((p) => byId.get(p.id))
.filter((d) => d && d.targets.some((t: string) => biomarkers.includes(t)) && d.status !== "withdrawn");
for (const d of matches) {
const q = new URLSearchParams({ "query.intr": d.name.replace(/\s*\(.*?\)\s*/g, " ").trim(), "query.cond": "triple negative breast cancer", "filter.overallStatus": "RECRUITING", pageSize: "5", format: "json" });
const studies = await fetch(`https://clinicaltrials.gov/api/v2/studies?${q}`).then((r) => r.json());
console.log(d.name, d.status, studies.studies?.map((s: { protocolSection: { identificationModule: { nctId: string } } }) => s.protocolSection.identificationModule.nctId));
}The site’s own ask and query surfaces use the same approach; see src/lib/biomarker-match.ts and src/lib/ctgov.ts in the repo for the edge cases (aliases, combination names, condition wording).
Recipe 2: a dashboard
One fetch of all.json gives every object plus a backlink map, enough to compute counts, funnels and rankings client-side. This counts products by modality and status for a target.
const { entities, incoming } = await fetch("https://nuclide.cc/api/v1/all.json").then((r) => r.json());
const drugs = entities.filter((e) => e.kind === "drug" && e.targets.includes("her2"));
const funnel = {};
for (const d of drugs) funnel[d.status ?? "unknown"] = (funnel[d.status ?? "unknown"] ?? 0) + 1;
console.table(funnel);
// Everything that links to HER2, grouped by kind (trials, indications, pathways, papers...)
const linkers = incoming["her2"].reduce((m, x) => ({ ...m, [x.kind]: (m[x.kind] ?? 0) + 1 }), {});
console.table(linkers);
// Refresh: meta.json carries the build time; re-fetch when it changes
const { built, counts } = await fetch("https://nuclide.cc/api/v1/meta.json").then((r) => r.json());all.json is a few megabytes; for a production dashboard fetch the per-kind files you need and cache on meta.json’s built timestamp. The pages under landscape grid and scorecards are built this way at build time.
Recipe 3: a chatbot over MCP
The MCP server gives an assistant grounded tools instead of a scraped context window. Clone the repo, install, and point your MCP-capable client at it.
git clone https://github.com/casadesante/Nuclide.git && cd Nuclide && npm ci
# Claude Desktop, Cursor, or any MCP client: add a stdio server
{
"mcpServers": {
"nuclide": { "command": "npm", "args": ["run", "mcp"], "cwd": "/path/to/Nuclide" }
}
}
# Tools exposed: search(query, limit), get_entity(id), list_kind(kind),
# for_cancer(indicationId), rank(...). Every answer carries the object url so the
# assistant can cite nuclide.cc and the reader can check the source.Without MCP, the same grounding works with search.json (compact id, kind, name, tldr, route documents) as a retrieval index and entities/<id>.json as the fetch step.
Embeddable cards
Every object has an iframe card at /embed/<id>/: kind, status, name and plain-English summary, linking back to the page. Cards are noindex and inherit nothing from your page.
<iframe src="https://nuclide.cc/embed/trop2/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe><iframe src="https://nuclide.cc/embed/merck/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>Endpoints at a glance
| Path | Contents |
|---|---|
| /api/v1/all.json | All 862 entities plus an incoming backlink map |
| /api/v1/search.json | Compact search documents |
| /api/v1/<plural>.json | indications, isotopes, fronts, technologies, targets, drugs, companies, institutions, pathways, terms, trials, pairings, roadmaps, ideas, collections, people, bottlenecks, opportunities, key papers, journals |
| /api/v1/entities/<id>.json | One entity with route and neighbours by kind |
| /api/v1/my-indications.json | Every cancer as id, name, route and hub group (the chooser list) |
| /api/v1/ranking.json | Institution ranking with score components |
| /api/v1/benchmark.json | The open evaluation question set |
| /api/v1/meta.json | Build time, counts, licence, attribution text |
| /catalysts/feed.ics | iCalendar feed of catalysts and readouts |
| /trials/index.json, /trials/<drugId>.json | Weekly ClinicalTrials.gov phase 2/3 study counts and lists per product |
Licence and attribution
The data are CC BY-NC 4.0 (free for non-commercial use; commercial use needs a licence from Nuclide) and the code is MIT. Name Nuclide and link to nuclide.cc wherever the data or text derived from it appears, for example Data from Nuclide (nuclide.cc), CC BY 4.0. The same notice is in /api/v1/meta.json so it can travel with the data.
Logos remain their owners’ trademarks; molecule structures keep their PubChem and RCSB terms; ClinicalTrials.gov data are public domain. Nothing here is medical advice, and every record carries the date it was last checked: show it.
Found an error while building? Suggest an edit or open a pull request; corrections are logged at /corrections/.