MCP adapter for cdf-core. Exposes eighteen tools that let an LLM client read, validate, author, and refactor CDF component specs and Profiles, plus fetch + extract Figma files for Profile scaffolding. Three lean tools (enum de-collapse, vocabulary-divergence, Profile validation) are on by default; the other fifteen unlock behind an opt-in env var.
This package is a thin wrapper — all parsing, validation, and analysis
logic lives in cdf-core. The adapter's job is to turn those functions
into MCP tools an agent or human can call through a client like Claude
Desktop, the MCP Inspector, or a custom runtime.
v2.0.0 — 2026-07-12. Eighteen tools (three in the default lean mode;
CDF_MCP_TOOLSET=full restores the rest). Two breaking changes:
- Lean-by-default tool surface. New installs register only
cdf_decollapse,cdf_vocab_diverge, andcdf_validate_profileunlessCDF_MCP_TOOLSET=fullis set in the server's environment. See the Tools section below for the full lean/full split. - Five zero-consumer profile-read tools removed —
cdf_get_profile_section,cdf_list_profiles,cdf_coverage_profile,cdf_diff_profile,cdf_resolve_extends(all added in v1.6.0) are gone. Migration notes in CHANGELOG.
cdf_decollapse (drafted against an unpublished v1.8.0 line) ships
for the first time in this release — it de-collapses a crammed enum
into its independent axes (e.g. a State property mixing interaction,
content, and validation) and lists the combinations those axes imply
but weren't declared, each as a question for the author to confirm or
reject.
No project setup needed. Add the server to your MCP client config:
{
"mcpServers": {
"cdf-mcp": {
"command": "npx",
"args": ["-y", "@formtrieb/cdf-mcp@^2.0.0"]
}
}
}Then paste your component's declared variant values into the chat — straight from a Figma variant property, a props enum, or token-name suffixes:
My text field has one
Stateproperty:Enabled, Hovered, Focused, Error, Disabled. Anything hiding in there?
The client calls cdf_decollapse and you get back something like:
Your 'State' property mixes 2 independent concerns (interaction, validation). You're missing a few combinations — 1) hovered × validation: intentional or forgotten? 2) focused × validation: intentional or forgotten? 3) disabled × validation: intentional or forgotten?
That's the point of the tool: a flattened enum can't express
Error × Hovered, so nobody ever decided whether it should exist.
The tool asks — it never judges. Supply token_names (your token
paths or CSS custom-property names) and the answer additionally
cross-checks which enum values actually have token backing, and
suppresses questions with no token evidence at all.
If you use the cdf Claude Code plugin,
the /decollapse command paves this flow end-to-end — including
pulling the declared values straight out of a Figma component set.
Earlier milestones in this line: v1.7.0's N1+N5 Figma Access
Modernization added five tools that port the bash walker + renderers
to TypeScript and add a figma_execute runtime adapter, unblocking the
Snapshot Skill's T0-path (no PAT needed) for evaluators; v1.6.1 added
cdf_get_spec_fragment; v1.6.0 added Profile read/diff/coverage (5
tools, since removed — see above); v1.5.0 was the breaking BBQ — drop
cdf_emit_profile, rename cdf_validate → cdf_validate_component,
add cdf_validate_profile (L0–L8). See CHANGELOG for migration notes.
License: Apache-2.0. Published on npm as @formtrieb/cdf-mcp,
installed automatically by the cdf Claude Code plugin or manually
via npx @formtrieb/cdf-mcp.
Inside the monorepo:
pnpm install
pnpm --filter @formtrieb/cdf-mcp buildProduces a single bundled dist/index.js (~162 KB) with all workspace
dependencies inlined. Runnable directly:
node /abs/path/to/packages/cdf-mcp/dist/index.jsThe server reads its config path from the CDF_CONFIG environment
variable; defaults to ./.cdf.config.yaml relative to the process
working directory.
A minimal .cdf.config.yaml:
spec_directories:
- ./specs/components
profile_path: ./ds.profile.yaml
token_sources:
- ./tokensspec_directories— one or more roots containing*.spec.yaml/*.component.yamlfiles. Walked recursively.profile_path— the single Profile YAML that declares vocabularies, token grammar, interaction patterns, etc.token_sources— DTCG token tree root(s). Optional. Set to[]or omit if no tokens are needed (headless setups).
All paths may be absolute or relative to the config file's directory.
CDF-mcp registers a lean 3-tool surface by default. Set
CDF_MCP_TOOLSET=full in the server's environment to register the
full 18-tool surface — lean is always a strict subset of full, so
nothing in the default 3 tools behaves differently once you opt into
full.
- Lean (default) — you're evaluating CDF against an unfamiliar design system, or your day-to-day loop is enum de-collapse, vocabulary-divergence checking, or Profile validation. Zero configuration; the smallest tool-choice surface for a client to reason about.
- Full (
CDF_MCP_TOOLSET=full) — you're doing CDF-authoring work end to end: listing/reading/resolving/diffing component specs, scaffolding from Figma analysis data, or fetching + extracting Figma files for Profile scaffolding. ThecdfClaude Code plugin sets this for you.
| Tool | Purpose |
|---|---|
cdf_decollapse |
De-collapse a crammed enum into its independent axes. Takes the declared variant values verbatim — straight from a Figma component set's variant list or a token family's suffixes, no re-interpretation — and infers which of those values are actually bundling several independent concerns into one property or token suffix (e.g. a State enum that mixes interaction, content, and validation). Lists the combinations that are implied by those axes but weren't declared, each as a question for the author to confirm or reject. A suggestion, not a judgment — an omission may well be intentional. |
cdf_vocab_diverge |
Detect near-miss typos (Levenshtein ≤2) in property values + interaction-pattern states against the Profile; elicit resolutions; apply renames + persist rationale into the Profile. |
cdf_validate_profile |
Validate a CDF Profile YAML. Runs L0–L7 by default (parse, required fields, types, schema-baking, cross-field, vocabulary isolation, extends-resolution, set_mapping globs); L8 (token-reference resolution against token_sources) is opt-in via resolve_tokens: true. Accepts either profile_path or inline profile_yaml. |
Adds these 15 tools on top of the lean trio:
| Tool | Purpose |
|---|---|
cdf_validate_component |
Run the validator against one or all component specs. Returns structured issues by severity. (Renamed from cdf_validate in v1.5.0.) |
cdf_list |
List all component specs with optional property/state/anatomy counts, filterable by category. |
cdf_get |
Return a single section (e.g. properties, states, tokens) of a component spec, to avoid dumping the whole file. |
cdf_resolve |
Resolve a token path via the Profile's grammar — useful for verifying what a binding would look up at build time. |
cdf_check_tokens |
Report unresolved / placeholder tokens per component (magenta #f305b7, unresolved grammar paths). |
cdf_diff |
Compare two components (or two versions of one) — surfaces properties/states/tokens that differ. |
cdf_coverage |
Token-coverage report (component-spec scope): how many component-declared bindings are placeholders, gaps, etc. |
cdf_suggest |
"Code-review" suggestions for a component spec — completeness, accessibility, tokens, Figma, consistency. Less strict than cdf_validate_component. |
cdf_scaffold |
Generate a CDF YAML skeleton from Figma analysis data (uses MCP elicitation to confirm component name, category, description). |
cdf_get_spec_fragment |
Read one fragment of CDF-PROFILE-SPEC (e.g. Vocabularies, TokenGrammar, InteractionPatterns). Fragments live under cdf/specs/profile/; the monolith cdf/specs/CDF-PROFILE-SPEC.md is a generated publication artefact. Use format: "sections" for a parsed heading map. |
cdf_fetch_figma_file |
REST-fetch a Figma file by file_key; PAT via pat: arg or FIGMA_PAT env (arg overrides). Caches to .cdf-cache/figma/{file_key}.json. Use force_refresh: true to bypass cache. |
cdf_resolve_figma_variables |
Wrap the figma_execute Variable-Oracle pattern. Caches resolved Variables to .cdf-cache/figma/{file_key}.variables.json. Returns counts + cached path. |
cdf_extract_figma_file |
TypeScript port of the bash walker. Two modes: source: "rest" (consumes cdf_fetch_figma_file cache) and source: "runtime" (consumes figma_execute raw-tree output, T0-path). Emits phase-1-output.yaml consumable by cdf-profile-scaffold Skill. |
cdf_render_findings |
TypeScript port of render-findings.sh. Reads findings.yaml → emits markdown (8-section, decision-vocab-rendered, ship-blocker-grouped). Schema-version hard-fail. |
cdf_render_snapshot |
TypeScript port of render-snapshot.sh. Reads <prefix>.snapshot.{profile,findings}.yaml → emits 4-section snapshot markdown (BANNER → FINDINGS → BLIND_SPOTS → UPGRADE). 15-finding cap, schema-version hard-fail. |
The cdf_fetch_figma_file / cdf_resolve_figma_variables /
cdf_extract_figma_file / cdf_render_findings / cdf_render_snapshot
tools subsume the previous bash pipeline (scripts/figma-phase1-extract.sh,
scripts/extract-to-yaml.sh, scripts/render-findings.sh,
scripts/render-snapshot.sh). Bash scripts remain in the Formtrieb
monorepo as a regression bridge until a future cleanup release; external
adopters install the cdf-mcp tool surface and skip bash entirely.
cdf_get_spec_fragment fragment directory resolution (in order — first
match wins):
CDF_SPECS_DIRenv var — explicit override. Use when the fragments live somewhere unusual or when scripting against a pinned spec revision.- Walk up from the config's directory looking for a sibling/ancestor
cdf/specs/profile/index.mdmarker. Covers the common monorepo layout where.cdf.config.yamllives in a DS sub-directory. - Package-bundled
dist/spec-fragments/profile/. Thepnpm buildstep copiescdf/specs/profile/*.mdinto the package output, so an npm-installed user without the monorepo layout still gets working fragments out of the box. - Last-resort fallback:
${configDir}/cdf/specs— the tool returns a clear "fragment not found" error when invoked, with the path it actually checked for diagnosis.
All tools return either { content: [{ type: "text", text: "<JSON>" }] }
on success or { isError: true, content: [...] } on failure. Writes are
limited to cdf_scaffold (new Component files) and cdf_vocab_diverge
(surgical YAML edits on existing files). Profile YAML is written by
the host's Write tool (Skill-orchestrated), not by an MCP tool.
Three modes:
dry-run(default) — detect + report, no mutation.interactive— elicit a resolution per divergence (capped at 3 per invocation, per design Principle 1); applies chosen renames; appends a dated decision line to the relevant Profiledescription:field.apply— applyauto_resolutions(JSON map keyed by divergence id) or the Recommendation defaults; no prompts.
Agent-mode bypass:
{
"mode": "dry-run",
"skip_elicitation": true
}Returns findings without mutating anything — useful for CI checks or read-only agents. See the smoke-test fixture for a worked end-to-end example and a Claude-Desktop walkthrough.
Consumes JSON outputs from Figma-side tooling (figma_analyze_component_set,
extract-token-map.js, audit-sub-interactions.js) and emits a draft
.spec.yaml. Always elicits the user to confirm component_name,
category, and description — tool arguments are treated as
suggestions, not final values.
Profile-level validator. Runs L0–L7 by default; L8 opt-in:
cdf_validate_profile({
profile_path: "./acme.profile.yaml", // file on disk
resolve_tokens: true, // optional; enables L8
severity: "warning", // optional; default "warning"
})
Or with an inline YAML string (the cdf-profile-scaffold Skill uses
this during iterative emit/validate loops before the YAML lands on disk):
cdf_validate_profile({
profile_yaml: "<full Profile YAML>",
base_dir: "/abs/path/to/profile/dir", // for extends + token_sources resolution
resolve_tokens: false,
})
L8 (resolve_tokens: true) walks
interaction_patterns.<p>.token_mapping paths and resolves them
against the DTCG files declared in token_sources. When sources
aren't reachable, a warning is emitted and L8 is skipped — the rest
of the report is unaffected.
Returns the same ValidationReport shape as cdf_validate_component:
{ file, valid, errors, warnings, info, summary }.
Add to ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS) or the platform equivalent:
{
"mcpServers": {
"formtrieb-cdf": {
"command": "/absolute/path/to/node",
"args": ["/abs/path/to/packages/cdf-mcp/dist/index.js"],
"env": {
"CDF_CONFIG": "/abs/path/to/your/project/.cdf.config.yaml"
}
}
}
}Two gotchas worth flagging:
- Use an absolute path to
node. MCP clients launch servers without your shell environment, sonodeisn't on PATH. If you use nvm, the path includes the version (~/.nvm/versions/node/vX.Y.Z/bin/node) and will break when you switch versions. - MCP servers launch with
cwd=/(or similar foreign root). All relative paths in.cdf.config.yaml(./specs,./tokens) are resolved relative to the config file's directory — not to the server's cwd. SetCDF_CONFIGto an absolute path and paths inside the config can stay relative.
Any MCP stdio client works. The MCP Inspector is handy for interactive exploration:
npx @modelcontextprotocol/inspector node /abs/path/to/packages/cdf-mcp/dist/index.js(Set CDF_CONFIG in the environment before the command.)
Several tools use MCP elicitation (cdf_scaffold, cdf_vocab_diverge
in interactive mode) to ask the user to confirm or override recommended
values. If the client doesn't advertise the elicitation capability
in its initialize handshake, these tools fall back gracefully:
cdf_scaffoldrequires the three identity fields as tool arguments.cdf_vocab_divergereturns an error pointing tomode: "apply"+auto_resolutionsas the non-elicited path.
In practice, a capable agent (like Claude Desktop, even when the protocol-level cap is absent) wraps the elicitation conversationally — asking the user directly in chat and then calling the tool with the answered-for arguments. Same UX shape, different transport.
# Fast type-check (no emit, large heap for the MCP SDK's deep types)
pnpm --filter @formtrieb/cdf-mcp build:typecheck
# Bundle to dist/
pnpm --filter @formtrieb/cdf-mcp build
# Run the smoke-test fixture end-to-end (no MCP transport)
pnpm --filter @formtrieb/cdf-mcp exec tsx tests/fixtures/vocab-diverge-smoke/run.ts
# Reset the smoke fixture after a mutation run
git restore packages/cdf-mcp/tests/fixtures/vocab-diverge-smoke/Adapter-level tests live alongside the source:
pnpm --filter @formtrieb/cdf-mcp testThese cover tool-handler behaviour against tempdir fixtures and the
spec-fragment resolution paths. Pure-function tests for parsers,
validators, and analyzers live in cdf-core:
pnpm --filter @formtrieb/cdf-core testScoped tags in the form cdf-mcp-vX.Y.Z mark releases. Tagged
versions align with the tool set — a minor bump adds tools or
backward-compatible argument fields; a major bump changes tool
signatures or semantics.
Related packages have their own tag namespaces (cdf-vX.Y.Z for the
format spec, cdf-core-vX.Y.Z when cdf-core starts tagging). There
is no lockstep; cdf-mcp can ship v1.5 while cdf-core is at v1.0.
Apache-2.0 — alignment with the CDF spec and reference implementation.