OnCo

Build on OnCo

4,740 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.

Start here
  • OpenAPI 3.1 spec for /api/v1/, importable into Postman, Stoplight or an SDK generator.
  • 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 mcp exposes 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/onco.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://onco.cc/api/v1/entities/${id}.json`);
  if (!r.ok) throw new Error(`OnCo: ${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://onco.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://onco.cc";
const cancerId = "tnbc";
const biomarkers = ["trop2", "pdl1", "brca"]; // OnCo target ids; see /targets/

const { neighbours } = await fetch(`${SITE}/api/v1/entities/${cancerId}.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 tumour board and navigator 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://onco.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, cancers, 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://onco.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, pipeline 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/judegomila/OnCo.git && cd OnCo && npm ci

# Claude Desktop, Cursor, or any MCP client: add a stdio server
{
  "mcpServers": {
    "onco": { "command": "npm", "args": ["run", "mcp"], "cwd": "/path/to/OnCo" }
  }
}

# Tools exposed: search(query, limit), get_entity(id), list_kind(kind),
# for_cancer(cancerId), rank(...). Every answer carries the object url so the
# assistant can cite onco.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://onco.cc/embed/trop2/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>
<iframe src="https://onco.cc/embed/tnbc/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>
<iframe src="https://onco.cc/embed/trastuzumab-deruxtecan/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>
<iframe src="https://onco.cc/embed/merck/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>

Endpoints at a glance

PathContents
/api/v1/all.jsonAll 4,740 entities plus an incoming backlink map
/api/v1/search.jsonCompact search documents
/api/v1/<plural>.jsoncancers, fronts, technologies, targets, drugs, companies, institutions, pathways, terms, trials, pairings, roadmaps, ideas, collections, people, bottlenecks, key papers, journals
/api/v1/entities/<id>.jsonOne entity with route and neighbours by kind
/api/v1/ranking.jsonInstitution ranking with score components
/api/v1/benchmark.jsonThe open evaluation question set
/api/v1/meta.jsonBuild time, counts, licence, attribution text
/catalysts/feed.icsiCalendar feed of catalysts and readouts
/trials/index.json, /trials/<drugId>.jsonWeekly ClinicalTrials.gov phase 2/3 study counts and lists per product
/globocan/countries.jsonGLOBOCAN 2022 incidence and mortality by country and site

Licence and attribution

The data are CC BY 4.0 and the code is MIT. Name OnCo and link to onco.cc wherever the data or text derived from it appears, for example Data from OnCo (onco.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; GLOBOCAN data keep IARC’s terms; ClinicalTrials.gov data are public domain. Nothing here is medical advice, and every record carries an as of date: show it.

Found an error while building? Suggest an edit or open a pull request; corrections are logged at /corrections/.