# Docs Pipeline — Architecture and Maintenance Map **Audience:** anyone (human or agent) about to change MCP-tool documentation, add a new MCP tool, or debug why `talonic.com/docs/mcp/*` is stale. **One-sentence summary:** this repo carries *two parallel docs surfaces* that feed *different parts* of `talonic.com/docs/*`; if you update the wrong one, your change will silently never appear on the user-visible page. --- ## The two surfaces | Surface in `talonic-mcp` | Built into | Consumed by | Feeds these pages | |---|---|---|---| | **`src/content/sections/*.ts`** (typed TS modules) | `dist/content.js` via `tsup`, exported as `@talonic/mcp/content` subpath | the **website** (`@/app/docs/mcp/McpContentPage` calls `getMcpSection(slug)`) | **`talonic.com/docs/mcp/*`** — all MCP docs pages | | **`docs/sections.json`** (hand-edited JSON) | shipped as-is; pulled at build time by the **platform** repo's `sync-external-docs.yml` workflow | the **`@talonic/docs`** npm package (published from the platform monorepo) | `talonic.com/docs/sdk/*`, `talonic.com/docs/api/*`, `talonic.com/docs/platform/*` — **NOT** `/docs/mcp/*` | **Critical implication:** if you edit `docs/sections.json` thinking you are updating the MCP docs page, **nothing on `talonic.com/docs/mcp` will change** — that file feeds the SDK / API / Platform doc pages on the website, not MCP. To update MCP docs you must edit `src/content/sections/*.ts`. (This split caused weeks of silent drift in May 2026; the fix that finally surfaced the gap is documented in this repo's git history under commit `eac4dd6 docs(content): close MCP-docs gap on talonic.com`.) ## End-to-end pipeline (MCP path) ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ talonic-mcp (this repo) │ │ │ │ src/content/sections/*.ts │ │ │ tsup build │ │ ▼ │ │ dist/content.js ← @talonic/mcp/content (npm subpath export) │ └─────────────────────────────────────────────────────────────────────────┘ │ │ git push to main triggers publish.yml ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ .github/workflows/publish.yml │ │ │ │ 1. Docs-drift guard (fails if src/tools changed without docs) │ │ 2. Auto-bump → npm publish → mcp-publisher publish → GitHub Release │ │ 3. Trigger website rebuild (repository_dispatch: mcp-updated) │ └─────────────────────────────────────────────────────────────────────────┘ │ │ repository_dispatch lands on website repo ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ talonicdev/website │ │ │ │ .github/workflows/update-docs.yml runs: │ │ npm update @talonic/mcp @talonic/node @talonic/docs │ │ git commit + push (chore: update @talonic/mcp to X.Y.Z) │ │ │ │ Vercel picks up the lockfile commit → rebuilds Next.js app │ │ │ │ At build time: src/app/docs/mcp/tools/*/page.tsx renders │ │ │ │ which calls getMcpSection() from @talonic/mcp/content │ │ (now at the just-published version) │ └─────────────────────────────────────────────────────────────────────────┘ │ ▼ talonic.com/docs/mcp/tools/extract (etc.) ``` The other surface (`docs/sections.json`) has its own pipeline through the **platform** repo's `sync-external-docs.yml` → `publish-docs.yml` → `@talonic/docs` → website's `update-docs.yml`. That flow updates SDK/API/Platform docs only. ## "Add a new MCP tool" — four-file checklist Adding a new tool surface (like `talonic_request_upload`, added in 0.1.46) requires changes in **both repos**. Missing any of the four files below produces a *specific, named* breakage — see the failure-mode table below. ### In `talonic-mcp` 1. **`src/content/sections/tools.ts`** — add the new section. Mirror the shape of an existing tool: `slug`, `parentSlug: "tools"`, `title`, `seoTitle`, `description`, `content[]`, `related[]`, `faq[]`, `mentions[]`. 2. **`src/content/seo.ts`** — add `{ id: "", label: "" }` to the `tools` entry of `MCP_NAV_SECTIONS`. Without this, breadcrumb + prev/next derivation in `helpers.ts` returns null/empty. ### In `talonicdev/website` 3. **`src/app/docs/mcp/tools//page.tsx`** — new 16-line file mirroring `filter/page.tsx`. The slug passed to `` must match the section slug in `talonic-mcp/src/content/sections/tools.ts`. 4. **Three sibling registries**, all of which must include the new tool: - `src/lib/docs-sync.ts` → add to `MCP_PAGE_MAP` (drives `mcpPageMeta()` title + breadcrumbs). - `src/app/docs/layout.tsx` → add to the slug → URL map (drives the sidebar's active-state highlighting). - `next.config.ts` → add `/docs/mcp/` → `/docs/mcp/tools/` redirect (because `McpContentPage` generates `related` URLs as `/docs/mcp/${slug}`, not `/docs/mcp/tools/`). That's the full list. After both pushes land, Vercel rebuilds and the new tool page is live. ## Failure-mode table | Symptom | Likely cause | File to fix | |---|---|---| | `/docs/mcp/tools/` returns 404 | Missing `page.tsx` route | Create `src/app/docs/mcp/tools//page.tsx` (website) | | Page renders but content is empty | Page exists, but `getMcpSection(slug)` returned null | Section missing from `src/content/sections/tools.ts` (mcp), OR slug typo | | `/docs/mcp/talonic-` 404s | Missing redirect | Add to `next.config.ts` (website) | | Sidebar doesn't highlight the active tool | Missing slug → URL map entry | Add to `src/app/docs/layout.tsx` (website) | | Wrong title / wrong breadcrumbs | Missing `MCP_PAGE_MAP` entry | Add to `src/lib/docs-sync.ts` (website) | | Sidebar doesn't list the tool at all | Missing `MCP_NAV_SECTIONS` entry | Add to `src/content/seo.ts` (mcp) and republish | | Edits to `docs/sections.json` don't appear on `/docs/mcp/*` | **You edited the wrong file.** | Edit `src/content/sections/*.ts` instead (mcp) | | `Docs-drift guard` fails on push | Tool source changed without docs/sections.json | Either update `docs/sections.json` too, or add `[skip docs]` to a commit message in the push | | Publish chain runs but `talonic.com/docs/mcp/*` still stale | Vercel cache OR website rebuild didn't fire | Check `gh run list --repo talonicdev/website --workflow=update-docs.yml`; manually fire `gh workflow run publish.yml -r main` from talonic-mcp | | `sync-external-docs.yml` 403 on checkout | `PLATFORM_SYNC_TOKEN` secret missing or under-scoped | Re-issue PAT with `Contents: read/write` on `talonicdev/platform`; update the secret | ## CI tokens (purposes, scopes, where they live) | Secret | Lives in | Used for | Required scope | |---|---|---|---| | `NPM_TOKEN` | talonic-mcp, talonic-node | `npm publish` | Granular npm automation token with **2FA bypass enabled**; `read/write` on `@talonic` org | | `WEBSITE_DISPATCH_TOKEN` | talonic-mcp, talonic-node | Fires `repository_dispatch` events to website + platform | PAT with `repo` (classic) or `metadata:write + contents:write` on website + platform (fine-grained) | | `PLATFORM_SYNC_TOKEN` | platform | `actions/checkout@v4` in `sync-external-docs.yml` so committed-by-PAT identity triggers downstream `publish-docs.yml` (GITHUB_TOKEN commits do NOT trigger downstream workflows by GitHub's anti-recursion policy) | Fine-grained PAT, **`Contents: read/write` on `talonicdev/platform` only** | | GitHub OIDC | talonic-mcp | `mcp-publisher login github-oidc` for the MCP Registry publish | Workflow declares `id-token: write` | | `GITHUB_TOKEN` (auto) | every workflow | Default per-run token | Permissions limited to what `permissions:` block in the workflow declares, **capped by org-level "Workflow permissions" setting** | When something authenticated breaks, check (in order): (1) the secret value (did it expire?), (2) its scope (does it cover the target repo + permission?), (3) the workflow's `permissions:` block, (4) the org-level workflow permissions ceiling. ## The docs-drift CI guard `publish.yml` has a `Docs-drift guard` step (in both `talonic-mcp` and `talonic-node`) that fails the publish run if any of: - `src/tools/**` (mcp) or `src/resources/**`, `src/client.ts`, `src/index.ts`, `src/types.ts`, `src/errors.ts` (node) - `src/http-server.ts` or `src/server-factory.ts` (mcp) …changed in the push, but `docs/sections.json` did not. **Opt out** with `[skip docs]` in any commit message in the push, used only for genuinely-non-doc-affecting changes (refactors, internal-only fixes, CI infrastructure, debug instrumentation). ⚠️ **The guard watches `docs/sections.json`, which feeds the SDK/API/Platform docs surface — *not* the MCP docs surface.** It is an existing safety net for that surface, retained because it has caught real drift there. It does **NOT** enforce that `src/content/sections/*.ts` was updated when MCP tools change. That second guard could be added in the future but does not exist today — for MCP tools, discipline + this doc are the only enforcement. ## File pointers (quick reference) **`talonic-mcp`:** - `src/content/sections/*.ts` — MCP doc content modules. **Edit these to change `talonic.com/docs/mcp/*`.** - `src/content/seo.ts` → `MCP_NAV_SECTIONS` — sidebar/nav structure. - `src/content/index.ts` — registry that combines the modules, plus `getMcpSection()`/`getAllMcpSections()` exports. - `src/content/helpers.ts` — breadcrumb + prev/next derivation. - `docs/sections.json` — feeds the *other* surface (SDK/API/Platform). - `.github/workflows/publish.yml` — auto-bump + publish chain; includes the docs-drift guard. **`talonicdev/website`:** - `src/app/docs/mcp/McpContentPage.tsx` — renders content from `@talonic/mcp/content` for any tool page. - `src/app/docs/mcp/tools//page.tsx` — per-tool page files (one per tool). - `src/app/docs/mcp/page.tsx` — MCP docs homepage with the tool grid. - `src/app/docs/layout.tsx` → `MCP_SLUG_TO_URL` (the slug → URL map at lines ~215-235). - `src/lib/docs-sync.ts` → `MCP_PAGE_MAP` (line ~410) and `MCP_SECTIONS` (line ~397). - `next.config.ts` → `redirects()` (lines ~46+) — the `/docs/mcp/` → `/docs/mcp/tools/` table. **`talonicdev/platform`:** - `packages/docs/src/content/{sdk,mcp}/sections.json` — synced from `talonic-mcp/docs/sections.json` (NOT from `src/content/sections/`). - `.github/workflows/sync-external-docs.yml` — the sync workflow, requires `PLATFORM_SYNC_TOKEN`. - `.github/workflows/publish-docs.yml` — publishes `@talonic/docs` to npm whenever `packages/docs/**` changes. ## Useful one-liners ```bash # Has the MCP docs surface drifted from the published npm package? diff <(node -e 'import("@talonic/mcp/content").then(m => console.log(JSON.stringify(m.MCP_NAV_SECTIONS, null, 2)))') \ <(node -e 'import("./src/content/seo.ts").then(m => console.log(JSON.stringify(m.MCP_NAV_SECTIONS, null, 2)))') # Quick probe: is a specific tool page live? curl -sSL -o /dev/null -w "%{http_code}\n" https://talonic.com/docs/mcp/tools/ # Did the latest publish chain reach the Registry? curl -fsS 'https://registry.modelcontextprotocol.io/v0/servers?search=io.github.talonicdev/talonic-mcp' \ | jq -r '.servers[] | select(._meta."io.modelcontextprotocol.registry/official".isLatest) | .server.version' # Did the platform sync actually find a diff (and therefore commit/trigger publish-docs)? gh run list --repo talonicdev/platform --workflow=sync-external-docs.yml --limit 5 ``` ## See also - `STATUS.md` — running surface tables, follow-ups, resolved items. - `CHANGELOG.md` — release-by-release log. - Individual workflow files for the auth/permission inline comments that explain why a specific token or `permissions:` block exists.