@formtrieb/cdf-mcp
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.
Status
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, and cdf_validate_profile
unless CDF_MCP_TOOLSET=full is 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.
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.
Install & build
Inside the monorepo:
pnpm install
pnpm --filter @formtrieb/cdf-mcp build
Produces a single bundled dist/index.js (~162 KB) with all workspace
dependencies inlined. Runnable directly:
node /abs/path/to/packages/cdf-mcp/dist/index.js
The server reads its config path from the CDF_CONFIG environment
variable; defaults to ./.cdf.config.yaml relative to the process
working directory.
Configure
A minimal .cdf.config.yaml:
spec_directories:
- ./specs/components
profile_path: ./ds.profile.yaml
token_sources:
- ./tokens
spec_directories — one or more roots containing *.spec.yaml /
*.component.yaml files. 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.
Tools
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.
Which mode do I want?
- 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. The
cdf Claude Code plugin
sets this for you.
Lean (default)
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. |
Full (CDF_MCP_TOOLSET=full)
Adds these 15 tools on top of the lean trio:
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_DIR env 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.md marker. Covers the common monorepo
layout where .cdf.config.yaml lives in a DS sub-directory.
- Package-bundled
dist/spec-fragments/profile/. The pnpm build
step copies cdf/specs/profile/*.md into 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.
Non-obvious tools
cdf_vocab_diverge
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 Profile description:
field.
apply — apply auto_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.
cdf_scaffold
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.
cdf_validate_profile
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 }.
Using with Claude Desktop
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, so node isn'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. Set CDF_CONFIG to an absolute path and paths
inside the config can stay relative.
Using with other MCP clients
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.)
MCP elicitation
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_scaffold requires the three identity fields as tool arguments.
cdf_vocab_diverge returns an error pointing to mode: "apply" +
auto_resolutions as 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.
Development
pnpm --filter @formtrieb/cdf-mcp build:typecheck
pnpm --filter @formtrieb/cdf-mcp build
pnpm --filter @formtrieb/cdf-mcp exec tsx tests/fixtures/vocab-diverge-smoke/run.ts
git restore packages/cdf-mcp/tests/fixtures/vocab-diverge-smoke/
Adapter-level tests live alongside the source:
pnpm --filter @formtrieb/cdf-mcp test
These 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 test
Versioning
Scoped 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.
License
Apache-2.0 — alignment with the CDF spec and reference implementation.