Multi-app MDX public hub with a Git-backed static CMS admin and an MCP docs endpoint.
Own your multi-product What’s New / features hub in Git — with MR editorial and agent MCP — not another hosted changelog widget.
Live demo: https://branchmark-demo.vercel.app · Repo: https://github.com/0xTHAC0/branchmark (public MIT template)
- Documentation / product teams that want blog, timeline, and pages from MDX in Git
- Orgs on GitLab that want PKCE editors → staging MRs without a hosted CMS database
- Multi-product teams registering several hubs in one repo (
content/public/_apps.yml) - AI agents reading hub content via
POST /mcp
- Teams that need email digests, unread segmentation, or feedback voting (Beamer / Canny territory)
- Teams that need a shipped GitHub OAuth CMS API today (typed / reserved; not implemented)
The sample apps are demo (default) and northstar (second product for AppSwitcher). Add more via _apps.yml.
| Capability | Status |
|---|---|
Marketing landing (/) |
Own |
| Public hub (blog / timeline / markdown pages) | Own |
| Multi-app registry | Own |
Static admin — GitLab PKCE (/admin) |
Own |
| What’s New JSON feed | Own |
| What’s New embed (inline script) | Own |
| AI draft What’s New from MRs | Own (needs AI_GATEWAY_API_KEY) |
| Hub analytics (view / Cmd+K) | Own (needs HUB_ANALYTICS_WEBHOOK_URL) |
| Admin Insights dashboard | Own (needs BLOB_READ_WRITE_TOKEN or HUB_ANALYTICS_JSONL_PATH) |
sitemap.xml / robots.txt |
Own |
MCP read tools (search_docs, get_page, …) |
Own |
llms.txt / llms-full.txt / /md/* agent mirrors |
Own |
| GitHub OAuth CMS route handlers | Not shipped |
| Hub Cmd+K search | Own |
| Admin history + releases | Own |
| Draft MR preview links (Admin discovery) | Own |
| Review App hosting / live preview deploy | Operator (Docker CI + preview host — docs/review-apps.md) |
| Shared published docs-kit MCP package | Own (@cmsmark/docs-kit-mcp) |
| Email digests / unread targeting | Not shipped |
- Marketing landing —
/brand-first page with CTAs to the demo hub and admin - Public hub — per-app routes for blog, timeline (features / release notes / changelog), and markdown pages
- Multi-app registry — register apps in
content/public/_apps.yml - Static admin —
/adminGitLab PKCE editor for content, workflow, media, history, and releases (draft → review → ready via MR labels; Preview when Review App env URL exists; live GFM body preview beside Source; Publish squash + deletecms/*when configured) - MCP —
POST /mcptools for listing and fetching hub pages (@cmsmark/docs-kit-mcp) - Write MCP (stdio) — privileged
npm run mcp:writetools that open/update draft MRs (no merge); token never on public/mcp - Agent index —
GET /llms.txt,/llms-full.txt,/md/...,/agent-readability.json - What’s New —
GET /[app]/whats-new.jsonbuilt from Release Notes timeline entries (CORS enabled for embeds) - What’s New RSS —
GET /[app]/whats-new.xml(RSS 2.0; classic readers, no CORS required) - What’s New embed —
/embed/branchmark-whats-new.js+ demo at/embed - AI draft from MRs — Workflow “Draft What’s New” (optional; requires
AI_GATEWAY_API_KEY; uses title, description, labels, commits, and a size-capped MR file diff) - Hub analytics — opt-in webhook for section views and Cmd+K open/select (
HUB_ANALYTICS_WEBHOOK_URL); no cookies, no product-analytics SaaS - Admin Insights — read-only
/admin/.../insightsdashboard whenBLOB_READ_WRITE_TOKEN(Vercel Blob) orHUB_ANALYTICS_JSONL_PATHis set; beacons persist alongside optional webhook forward - SEO —
GET /sitemap.xml(landing + public hubs/sections/posts) andGET /robots.txt(allows public; disallows/adminand/api); Open Graph / Twitter cards use/og.pngwith absolute URLs viaBRANCHMARK_PUBLIC_URL - Review Apps —
.gitlab-ci.ymlbuilds/pushes a Docker image and registersenvironment:urlfor Admin Preview; optional SSH deploy (docs/review-apps.md)
npm install
cp .env.example .env.local
# For /admin: set GITLAB_CLIENT_ID (GitLab OAuth Application ID, PKCE)
npm run devOpen http://localhost:3007 — Branchmark marketing landing (not an instant redirect).
- Landing: brand + Open demo hub → http://localhost:3007/demo
- Hub changelog: http://localhost:3007/demo/change-log
- What’s New feed: http://localhost:3007/demo/whats-new.json · RSS: http://localhost:3007/demo/whats-new.xml
- Second app (multi-app): http://localhost:3007/northstar — use the hub AppSwitcher between Demo and Northstar
- MCP:
POST http://localhost:3007/mcpwith tools such assearch_docs/list_pages - Agent index: http://localhost:3007/llms.txt and raw markdown http://localhost:3007/md/demo/pages/overview (also
/md/northstar/pages/overview) - What’s New embed demo: http://localhost:3007/embed
JSON feed
const res = await fetch("https://your-host/demo/whats-new.json");
const feed = await res.json();
// feed.items: { title, description, date, category, tags, imageUrl, ... }Inline embed
<div
data-branchmark-whats-new
data-hub="https://your-host"
data-app="demo"
data-limit="5"
data-theme="auto"
></div>
<script src="https://your-host/embed/branchmark-whats-new.js" defer></script>content/public/_apps.yml # app registry
content/public/<app>/sections/ # nav + layouts
content/public/<app>/posts/ # blog posts
content/public/<app>/timeline/ # features / release notes / changelog
content/public/<app>/pages/ # long-form pages
content/docs/ # CMS docs tree
Changelog = finer activity stream. Release Notes = curated highlights that feed What’s New. Tag timeline MDX with sections: [change-log] and/or sections: [release-notes].
Section gotchas:
hide: trueremoves the section from nav (route may still exist)- Section body matching
Coming Soon(case-insensitive) forces the Coming Soon UI for non-timeline layouts. For timeline layouts, tagged entries still render even if the body isComing Soon; empty timelines with that body show Coming Soon, otherwise an empty state.
Regenerate derived data after content changes:
npm run generate:agent
npm run generate:whats-newPrivileged local MCP for agents that performs the same branch → draft MR loop as /admin: create_page, save_draft, set_status, get_draft, list_drafts. No merge. Public POST /mcp stays read-only — never wire GITLAB_TOKEN into the HTTP MCP route.
# Set GITLAB_TOKEN and a real repo in public/admin/config.yml (or BRANCHMARK_REPO)
npm run mcp:writeCopy .cursor/mcp.json.example into your MCP config. Paths are under content/public/ (relative demo/pages/foo.mdx or full). Helpers live in scripts/lib/gitlab-writes.mjs.
| Script | Purpose |
|---|---|
npm run dev |
Next.js on port 3007 |
npm run build / npm start |
Production build and server |
npm test |
Vitest unit/component tests |
npm run test:e2e |
Playwright smoke tests |
npm run generate:mcp-data |
Build generated/mcp-data.json |
npm run generate:llms-txt |
Build generated/llms.txt + llms-full.txt |
npm run generate:agent |
MCP data + llms artifacts |
npm run generate:whats-new |
Build What's New feeds (static/Pages exports) |
npm run mcp:write |
Privileged stdio Write MCP (draft MRs; requires GITLAB_TOKEN) |
See PACKAGING.md for Docker, env truth table, and release checklist.
docker build -t branchmark .
docker run -p 3007:3007 branchmarkMIT — see LICENSE. Template, not a hosted SaaS. See CONTRIBUTING.md.