This is a submission for the Notion MCP Challenge
What I Built
A designer opens a Notion table. Changes a color reference. An AI agent reads the change, validates it against every other token in the system, generates new JSON files, runs the build pipeline, and writes "synced" or "error" back to the same Notion row. The CSS output updates. The designer never leaves Notion.
This is flintwork-token-sync — an MCP server that turns Notion into a design token editor for flintwork, a design system I built from scratch with a three-tier token architecture (global → semantic → component), headless React components, and WAI-ARIA compliance.
The problem it solves
Design token handoff is broken. A designer changes a color in Figma. Someone copies the hex value into a JSON file. They typo it. Or they reference a token that was renamed last sprint. Or they change the light theme but forget the dark theme. The build succeeds because JSON doesn't validate intent — and the wrong color ships to production.
The fix is not more process. It's making the source of truth editable by designers with validation that catches errors before they reach code.
How it works
Four Notion databases mirror the three tiers of the token system, plus an audit log:
Global Tokens — 93 raw palette values. Every color, spacing value, font size, and shadow in the system. Each row is a token with its name, value, type, and sync status.
Semantic Tokens — 96 intent mappings across light theme, dark theme, and typography. color.text.primary references {color.gray.900} in light mode and {color.gray.50} in dark mode. A designer changing the primary text color edits one cell. Both themes stay consistent because the architecture enforces it.
Component Tokens — 64 component-specific bindings. button.primary.bg references {color.interactive.default}. The designer doesn't need to know the hex value — they work at the intent level.
Build Log — Every sync recorded. Timestamp, result, token counts, errors. The complete audit trail.
When a designer edits a token value in Notion, they tell Claude: "Sync my design tokens." Claude orchestrates two MCP servers:
- Notion MCP (remote) — reads token data from the four databases, writes sync status and build log entries back
- flintwork-token-sync MCP (local) — validates all 253 tokens, generates JSON files, runs the build pipeline that produces CSS custom properties
The sync validates everything before writing anything. If a designer references {color.purple.999} and that token doesn't exist, the sync stops. No JSON generated. No CSS changed. The error appears in the Notion row's Error column: "Unresolved reference: {color.purple.999} does not match any known token." The designer sees it immediately, fixes it, syncs again.
Five MCP tools
| Tool | What it does |
|---|---|
sync_tokens |
Full pipeline: read → validate → generate JSON → build CSS → write status back |
validate_tokens |
Read and validate only — preview errors before committing |
diff_tokens |
Compare Notion state against files on disk — see what would change |
build_tokens |
Run the CSS build on existing JSON files |
get_token_summary |
Current state: counts, status breakdown, errors |
The diff tool is what makes this practical for a real workflow. Before syncing, the designer asks "What changed?" and sees exactly which tokens were modified and what the old and new values are. No surprises.
What makes this different from a Notion API integration
This is not a script that calls the Notion API. It's a custom MCP server that Claude Desktop connects to alongside Notion MCP. The AI agent orchestrates both servers — reading from one, processing through the other, writing results back. The MCP protocol is the connective tissue, not a wrapper.
The validation is real. It checks:
- Token name format (dot-separated alphanumeric)
- Color values (hex format, transparent, none)
- Dimension values (px, rem, unitless for line-height)
- Font weights (numeric only)
- Reference resolution (does the referenced token exist?)
- Circular references (A references B references A)
The build is real. The generated JSON feeds into flintwork's token build pipeline, which resolves cross-tier references and outputs CSS custom properties with full theme support. A single [data-theme="dark"] attribute swap on the root element switches every color in the system.
Video Demo
Show us the code
vmvenkatesh78
/
flintwork-token-sync
MCP server that syncs design tokens between Notion and flintwork's token pipeline.
flintwork-token-sync
MCP server that syncs design tokens between Notion databases and flintwork's token pipeline. Designers edit tokens in Notion, an AI agent validates and builds them into CSS.
Architecture
Claude Desktop
├── Notion MCP (remote) — reads/writes Notion workspace
└── flintwork-token-sync MCP (local) — validates, generates, builds
Claude orchestrates both MCP servers. It reads token state from Notion via Notion MCP, calls this server's tools to validate and build, and writes results back to Notion via Notion MCP.
MCP Tools
| Tool | What it does |
|---|---|
sync_tokens |
Full pipeline: read → validate → generate JSON → build CSS → write status + build log |
validate_tokens |
Read and validate only — check for errors before committing |
diff_tokens |
Compare Notion state against JSON files on disk — preview what would change |
build_tokens |
Run the build pipeline on existing JSON files |
get_token_summary |
Read current state from Notion — counts, status breakdown, errors |
Notion Databases
…Key files:
-
src/core/sync.ts— The orchestrator. Single source of truth for the read → validate → generate → build → write-status pipeline. Both the MCP server and CLI call this function. -
src/core/validate.ts— Token validation. Hex format, reference resolution, circular reference detection, token name format. Pure functions, no Notion dependency. -
src/core/diff.ts— Compares Notion state against JSON files on disk. Normalizes values (arrays to comma-separated, numbers to strings) for accurate comparison. -
src/core/generate-json.ts— Transforms Notion rows into flintwork-compatible JSON. Groups by category, theme, and component. Handles font weights as numbers, font families as arrays. -
src/mcp-server/server.ts— Five MCP tools registered with the MCP SDK. Each tool is a thin wrapper around core functions. -
src/core/build-log.ts— Writes audit trail entries to the Build Log database after every sync.
The design system it syncs to: flintwork — headless React components (Button, Dialog, Tabs) with compound component API, full WAI-ARIA compliance, and 190 tests.
Architecture:
Claude Desktop
├── Notion MCP (remote)
│ └── reads/writes 4 Notion databases
└── flintwork-token-sync MCP (local)
├── validate_tokens → hex, refs, cycles, names
├── diff_tokens → compare Notion vs disk
├── sync_tokens → full pipeline
├── build_tokens → run CSS build
└── get_token_summary → current state
↓
flintwork/src/tokens/ (JSON files)
↓
flintwork/dist/tokens/tokens.css (CSS output)
Tests: 59 tests covering validation (token names, all value types, reference resolution, circular detection), JSON generation (file grouping, value formatting, all three tiers), and diff (added, removed, modified, unchanged, normalization).
How I Used Notion MCP
Notion MCP is not a backend for this project. It's the entire interface. There is no dashboard, no separate UI, no frontend. The designer works in Notion. The AI agent works through Notion MCP.
Reading tokens: Claude reads all four databases through Notion MCP. Each token is a row with properties — Name (title), Value/Reference (text), Type/Theme/Component (select), Status (select), Last Synced (date), Error (text). The MCP server normalizes these into typed objects for validation.
Writing status: After every sync, the tool writes back to each changed row. Successful sync → Status: "synced", Last Synced: timestamp, Error: cleared. Validation failure → Status: "error", Error: specific message. The designer sees the result in the same table they edited, without switching tools.
Build log: Every sync writes a new row to the Build Log database — timestamp, result, token counts per tier, errors. The designer can open the Build Log and see the complete history of every sync.
Diff preview: Before syncing, the designer can ask Claude "What changed?" Claude reads the current Notion state via MCP and compares it against the JSON files on disk. The diff shows exactly which tokens were added, removed, or modified with before/after values.
Why MCP, not just the Notion API: The same validation and build logic is available as a CLI (npm run cli sync) for CI pipelines and scripts. But the MCP interface is what makes it accessible to designers. They don't run terminal commands. They talk to Claude in a chat window. Claude handles the orchestration — reading from Notion MCP, calling the custom MCP server for validation and build, writing results back through Notion MCP. Two MCP servers, one AI agent, bidirectional flow.
Optimized write-back: The initial implementation wrote status to all 253 rows on every sync (~88 seconds at Notion's rate limit). This exceeded Claude Desktop's MCP tool call timeout. The fix: only write back to rows whose status actually changed. A typical sync after one designer edit writes 1 row instead of 253.
Top comments (0)