← All posts
July 8, 2026

SyncNo Update: New Entities, Resilient Sync, PDF Generation & MCP — Past 2 Weeks

Over the past two weeks SyncNo jumped from v1.0.0 to v1.2.6 — more than 50 commits spanning new entities, a rewritten sync engine, PDF generation, and an MCP server that exposes your data to LLMs. This post walks through everything that…


Over the past two weeks SyncNo jumped from v1.0.0 to v1.2.6 — more than 50 commits spanning new entities, a rewritten sync engine, PDF generation, and an MCP server that exposes your data to LLMs. This post walks through everything that shipped, why it matters, and what changed under the hood.

At a glance

  • 10 new Syncro entities — appointments, contracts, leads, schedules, wiki, policy folders, portal users, syncro users, payments, and product serials.
  • Sync engine overhaul — exponential-backoff retries, per-item resume, parallel syncs, and proper cancel/abort handling.
  • PDF generation — branded PDFs for invoices, estimates, POs, and tickets, with editable HTML templates per entity.
  • MCP server — ~60 read-only tools so Claude Desktop (and any MCP client) can query your SyncNo data.
  • Self-hosted ops — in-app update detection, accurate update commands, a backup settings page, and a docker-compose override pattern.

New entities & pages

We expanded SyncNo from its original handful of entities to full coverage of almost every Syncro object — and wired up search, the sidebar, auth, and an appointments calendar to support them.

The new entities ship with backend routes, frontend list + detail pages, and search hooks, all built on the same patterns used for tickets, customers, and invoices:

  • Appointments — full sync plus a dedicated calendar component.
  • Contracts — synced, browsable, and searchable.
  • Leads — new syncable entity.
  • Schedules — schedule sync and detail pages.
  • Wiki — pages now sync, and the detail view renders bodies as sanitized HTML. Scripts, on* handlers, javascript: URLs, and iframes are stripped while formatting and safe links survive.
  • Policy folders — per-customer folder tree with linked assets.
  • Portal users and Syncro users — synced and browsable.
  • Payments and product serials — two more entities with their own routes, list pages, detail pages, and search hooks.

Sync engine overhaul

The sync engine got a major reliability pass. The headline changes:

  • 5xx pause + retry with exponential backoff (1, 2, 4, 8, 16 min, capped at 30). Each pause logs to the terminal, writes a SYNC_PAUSE row, emits an SSE pause event, and flips sync_state.phase to paused with a resume time.
  • Per-item resume, not per-page. A single absoluteIndex counter replaces the old page/item pair, so cancelling at item N resumes at N+1.
  • Parallel syncs, properly isolated. Per-entity AbortController keyed by entity / all. DELETE takes ?entity=X so you can cancel one sync without killing others.
  • Global request throttle (~143/min) shared across all syncs, so concurrent syncs no longer trip 429 storms.
  • Delta sync that actually inserts new records — checks DB existence first, so new rows always insert even when updated_at <= last_sync.
  • Live progress streaming — SSE events stream directly to the active sync client; per-entity rows now show stats inline; refresh restores the correct active sync key.
  • Polling instead of flaky SSE — replaced with GET /sync/events polling plus an AbortController wired through fetch + the detail loop. The cancel button now actually stops the running job, and the terminal shows a live poll count.
  • Fixed soft-delete bug — the old batched NOT IN clauses were corrupting Products data. Replaced with a temp table + NOT EXISTS pattern.
  • Tickets progress fixed — falls back to the detail_synced checkpoint so the progress bar actually moves; tickets now flip synced=1 when the detail phase completes, plus a one-shot startup backfill clears any stale rows.
  • Record count UI — sidebar brackets show [DB:N] [last-sync count] per entity, and the live total_to_sync is auto-persisted to sync_state.

PDF generation

You can now download a fully-branded PDF directly from any invoice, estimate, purchase order, or ticket detail page. The system is split into two settings pages:

  • Company — upload your logo, set your company name, contact block, ABN, ABN label, and tax label once. Those values flow into every PDF you generate.
  • PDF templates — four tabs (Invoice, Estimate, Purchase Order, Ticket) with editable HTML templates, each pre-filled with the default Syncro template. Add a custom message and disclaimer per template, and persist them safely in the database (multi-line strings no longer break the .env parser).

Templates use {{tag}} placeholders that mirror the Syncro tag names from samples/tags.txt. Smart table-generating tags — {{invoice_line_items_table_no_tax}}, {{purchase_order_line_items_table}}, {{ticket_public_comments_table}}, {{asset_table}}, and {{ticket_custom_fields_table}} — emit proper <tr> rows matching each template's CSS classes. Unrecognized tags are silently dropped so rendered PDFs never show literal {{...}} placeholders.

The renderer uses a fresh Puppeteer browser per render, closed in finally, so it stays stable on memory-constrained hosts. The backend Docker image now ships Chromium runtime libs and system fonts, with shm_size: 1gb and relaxed seccomp/apparmor so the Chromium sandbox starts cleanly.

MCP server

A dedicated Model Context Protocol (MCP) server now ships with SyncNo, exposing ~60 read-only tools over Streamable HTTP. Connect Claude Desktop (or any MCP client) and start querying your data conversationally.

  • Two-layer auth — clients authenticate to the MCP server with MCP_API_TOKEN; the MCP server authenticates to SyncNo with the existing SYNCNO_API_KEY (already role: service). No backend auth changes needed.
  • Single public endpoint — the MCP container runs internal-only; the frontend proxies /mcp to it, so LLM clients use the same URL as the rest of your app (https://<host>/mcp). The /mcp route is exempt from the NextAuth redirect; the bearer-token check is the real gate.
  • Smart defaultsraw_json stripped by default, pagination capped at 50, plus a 9-test unit/protocol suite shipping with the server.
  • Settings → MCP page — generate / rotate MCP_API_TOKEN, copy ready-made Claude Desktop JSON + curl snippets, and ship a built-in "If you are an AI…" self-configuration block for agent-driven setup.

Self-hosted ops improvements

  • In-app version + update detection — semver from the VERSION file replaces the old SHA-based version. Backend reads VERSION for current, hits the GitHub tags API for latest, and compares semver. The sidebar shows the current version for everyone; the Update button + SSH hint are admin-only and appear when updateAvailable is true.
  • Accurate update command per install dir/api/system/version now returns installDir and updateCommand from HOST_INSTALL_DIR (default /opt/syncno), so multi-instance installs (e.g. /opt/syncno-swc) get the correct copy-paste command.
  • docker-compose.override.yml pattern — documented for per-instance tweaks (custom port publishes, etc.) that conflict on git pull. Compose auto-merges the override file on top of the base.
  • Fixed silent update-check failures — bridge network MTU pinned to 1450 (was 1500; external ens3 MTU 1400 was blackholing the GitHub API ClientHello, so latest: null meant the update indicator never fired). Plus a named syncno network so service DNS aliases register when networks.default is overridden.
  • Server-side analytics install ping — moved from a daily browser ping to a backend startAnalytics() call at server boot, so install-id generation is now reliable.
  • Backup settings page — new admin page for configuring, testing, initializing, and scheduling database backups.
  • Env values editable from the UI — Config page now reads current env values (with secrets masked), writes them back on save, updates process.env at runtime, and persists to .env via a shared updateEnvFile helper. Compose mounts .env read-write so container writes propagate to the host.

Auth, demo mode & reliability

  • Login enforcement for managed tenants — demo builds still bypass auth, but managed builds redirect any unauthenticated request to /login?callbackUrl=<original> via a rewritten middleware (getToken from next-auth/jwt).
  • Hardened 401 handling — all ~19 list + detail pages now use a shared fetchJson helper that redirects to /login on 401 and throws a typed UnauthorizedError sentinel. The DataTable got an Array.isArray guard as defense-in-depth — list pages no longer crash with "TypeError: r is not iterable" when the session expires.
  • Demo mode, finally consistentIS_DEMO now checks DEMO_MODE/DEMO env vars (not just NEXT_PUBLIC_DEMO); the demo build no longer ships a DemoAutoLogin redirect-loop component; NEXTAUTH_SECRET is set so the SessionProvider doesn't break rendering; Settings/Logs/Users pages now bypass the useSession auth gate cleanly in demo mode.
  • No more stale API responses — the frontend proxy route is now force-dynamic and explicitly sets cache: no-store on upstream fetches, so Next.js can't serve stale JSON after a write.
  • Managed-tenant runtime hardening — backend reads .env tolerantly (no crash on malformed lines), honors PORT from env, reports APP_VERSION from /health and /api/system, and drops the full hop-by-hop header set on proxied requests.

UI polish

  • Row shading — unsynced rows now shade light red across all list pages.
  • Cleaner list pages — the redundant "Synced" checkbox column has been removed from all list pages.
  • useUrlState hook — extracted URL sync logic into a shared hook and adopted across list pages. DataTable no longer bails to "no rows" while loading — only after the load completes with zero rows.
  • Subphase in products sync — the progress message now shows the current subphase during a products sync.

Version timeline

  • v1.0.0 → v1.0.7 — version system, install dir, network MTU fix, override pattern
  • v1.1.x — sync engine overhaul (parallelism, retries, progress, resume, abort)
  • v1.2.0 — payments, serials, new entities (appointments, contracts, leads, schedules, wiki, policy folders, portal/syncro users), backup settings, analytics
  • v1.2.1 → v1.2.2 — demo mode, policy folder tree
  • v1.2.3 → v1.2.4 — auth enforcement, 401 hardening, demo-mode consistency
  • v1.2.5 — MCP server
  • v1.2.6 — PDF generation

Try it

If you only have time for one thing this week, try downloading a branded invoice PDF — it took longer to build than it looks. The MCP server is the more interesting one for AI-heavy workflows; spin it up and connect Claude Desktop.

As always, feedback welcome — open a ticket via the admin portal or reply on the community thread.

— The SyncNo team