figctx-mcp
figma-free-mcp
fig-local-context turns a locally exported Figma .fig file into a stable,
agent-ready context bundle. It runs entirely on the machine that owns the
export: there are no Figma API calls, MCP calls, browser automation, or remote
services during extraction.
The project is designed for implementation agents that need trustworthy design facts such as layer hierarchy, text, dimensions, auto-layout, color, typography, effects, z-order, and embedded asset paths. It prioritizes useful context over pixel-perfect rendering.
Status
The CLI and local MCP server are implemented. The current compatibility target
is normal Figma Save local copy exports: ZIP archives containing
meta.json, canvas.fig, a thumbnail, and optional images/ entries. The
canvas.fig payload is decoded locally from Figma's Kiwi binary format.
The initial compatibility target is the fig-kiwi canvas signature. The format
is an undocumented Figma implementation detail, so compatibility is deliberately
versioned and unsupported variants fail explicitly instead of being guessed.
Maintained fork changes and Figma Inspector
This maintained fork adds the local component-instance resolution used by the
Figma Inspector workflow. The
Inspector repository contains the inspection decisions, AST pruning, cache and
rate-limit handling, while this repository provides the local .fig extraction
and MCP context consumed by that workflow.
The changes in this fork include:
Preserve an instance's original
node_idandmain_component_idfromsymbolData, while keeping the rawchildIdsunchanged for diagnostics.Resolve local
SYMBOLdefinitions into per-instanceresolvedChildIds, including nested components with cycle protection and isolated IDs when the same component is used more than once.Apply text overrides by their
guidPathtarget instead of relying on the order in which Figma stores overrides. BothtextData.charactersand the compact override form are supported.Make node context traversal and bounded inspection use the resolved children, and report the component identity and whether the instance was expanded.
Add normalization tests covering local expansion, nested cycles, repeated component instances, and target-specific text overrides.
These changes keep offline inspection local-first; the Inspector workflow only uses a cloud fallback when the local bundle cannot resolve the component.
Commands
figctx extract design.fig --out .figctx/design
figctx inspect .figctx/design --node <node-id>
figctx search .figctx/design "checkout" --type TEXT --limit 10
figctx pack .figctx/design --node <node-id> --format codex
figctx render .figctx/design --node <node-id> > artwork.svgInstall and use
Requires Node.js 20 or newer. No checkout or build is needed for normal use.
Run the CLI without installing it globally:
npx -y figctx@0.1.0 extract design.fig --out .figctx/design
npx -y figctx@0.1.0 inspect .figctx/design --node '1-2'
npx -y figctx@0.1.0 pack .figctx/design --node 'https://www.figma.com/design/file/name?node-id=1-2' --format codex
npx -y figctx@0.1.0 render .figctx/design --node '1-2' > artwork.svgOr install both local tools once:
npm install --global figctx
figctx extract design.fig --out .figctx/design
figctx-mcp --root "$PWD/.figctx/design"The npm distribution contains Node.js executables, not native platform binaries. Use an exact package version in MCP configuration so a design agent has a repeatable tool contract.
Contributor setup
Contributors need pnpm as well as Node.js 20+:
pnpm install
pnpm build
node packages/cli/dist/main.js extract design.fig --out .figctx/designReleasing
Public versions follow Semantic Versioning. Update packages/cli/package.json
and CHANGELOG.md in the release pull request, merge it, then create the
matching protected tag (vX.Y.Z). The publish workflow runs the complete
check, creates the npm tarball, publishes it, and attaches the tarball plus
SHA-256 to the GitHub Release.
For the first 0.1.0 publication, an npm owner must publish the tarball
manually with 2FA. Afterwards configure npm Trusted Publishing for
symonbaikov/figma-free-mcp, workflow file publish.yml, and the
npm-publish GitHub environment; future tags publish with GitHub OIDC and no
long-lived npm token.
Prompt examples
Implement a Figma page
The following prompt can be given to an implementation agent:
On the `page-stress` branch, implement the entire page from the Figma design.
Use fig-context-extracter to inspect the design. It will help you extract all
styles, images, and text from the local Figma file:
https://github.com/symonbaikov/fig-local-context
The local `.fig` file is located at:
Downloads/Страница гайда (Copy).fig
Implement both the desktop and mobile versions from the start.
Desktop design:
https://www.figma.com/design/EXfHitAQKwAIBHdY5fqa9A/%D0%A1%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0-%D0%B3%D0%B0%D0%B9%D0%B4%D0%B0--Copy-?node-id=54-1224&t=6N2Rf8lhG1IMJCRY-4
Mobile design:
https://www.figma.com/design/EXfHitAQKwAIBHdY5fqa9A/%D0%A1%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0-%D0%B3%D0%B0%D0%B9%D0%B4%D0%B0--Copy-?node-id=35-438&t=6N2Rf8lhG1IMJCRY-4Pixel-validation workflow
Save an exported PNG for each frame that needs visual parity, then attach it to its local canonical node ID. All steps are local and make no Figma request:
# Refuse pixel-perfect work early if a required local font is missing.
figctx font-check .figctx/design --font-dir ./fonts
# Attach the PNG exported from that frame in Figma.
figctx reference .figctx/design --node '320-182023' --image ./references/mobile-page.png
# After implementation, compare a local browser screenshot to the reference.
figctx compare .figctx/design --node '320-182023' --candidate ./screenshots/mobile-page.png
# Make visual drift fail CI only when it exceeds the chosen allowance.
figctx compare .figctx/design --node '320-182023' --candidate ./screenshots/mobile-page.png --max-mismatch-ratio 0.02
# Validate a bundle without reopening its source .fig.
figctx doctor .figctx/designreference stores a PNG in references/ with its dimensions and SHA-256.
compare writes comparisons/<node-id>/diff.png and report.json, including
the mismatch pixel count and ratio. pack and MCP get_frame_bundle return
all references attached within the requested subtree.
--node accepts canonical bundle IDs (1:2), Figma URL IDs (1-2), and a
Figma URL containing node-id. Resolution is local to the extracted bundle;
it never requests the linked Figma file. When the local export carries an
originFileKey, a URL with /design/<file-key>/ or /file/<file-key>/ must
match it; a URL for another Figma file fails with
NODE_REFERENCE_FILE_MISMATCH before a node is returned.
pack --format codex returns the selected node's complete depth-first subtree,
descendant text, deduplicated image/vector references, available maximal
vector-only groups, and every style token used by that subtree. Use render
with one listed group ID to receive a single self-contained SVG; it does not
write to the bundle. This is the intended command for an agent implementing a
whole section or page, while inspect remains a concise single-node lookup.
Start the MCP server after extraction:
npx -y --package figctx@0.1.0 figctx-mcp --root "$PWD/.figctx/design"It exposes list_frames, list_frame_summaries, search_nodes, get_node_context, get_frame_bundle,
review_visual_match, get_vector_svg, get_style_tokens, get_asset, and inspect_node via stdio. Node and frame responses include
attached reference metadata when present. The server reads only bundle files
plus the candidate PNG supplied to review_visual_match; it never opens the source .fig, writes to the bundle, or uses the network.
Use list_frame_summaries or search_nodes to discover node IDs without loading full node records; list_frame_summaries returns 100 entries by default and includes nextCursor for the next batch (up to 200 with limit). list_frames remains available with its existing detailed response.
Use inspect_node for a compact, bounded preview before implementation. It accepts a node reference plus optional depth (default 2, maximum 5) and maxChildren (default 20, maximum 100); its response reports omitted descendants and summarizes images/vectors without exposing asset paths or hashes. Use get_frame_bundle only when implementing a section or page: it uses the same complete-subtree contract as figctx pack.
For Codex, configure the npm package with an exact version and absolute bundle path:
{
"mcpServers": {
"figctx": {
"command": "npx",
"args": [
"-y",
"--package",
"figctx@0.1.0",
"figctx-mcp",
"--root",
"/absolute/path/to/design.figctx"
]
}
}
}For Claude Desktop, put the same server command in its configuration file:
{
"mcpServers": {
"figctx": {
"command": "npx",
"args": [
"-y",
"--package",
"figctx@0.1.0",
"figctx-mcp",
"--root",
"/absolute/path/to/design.figctx"
]
}
}
}Agent visual self-review
An implementation agent should capture a same-viewport PNG after its first
working version and again before it finishes. It calls
review_visual_match with the target Figma node, the local screenshot path,
and phase: "midpoint" or phase: "final".
The tool returns the attached Figma reference, the candidate, and a pixel diff
as MCP image blocks, followed by a corrective prompt. Its fixed acceptance
gate is mismatchRatio <= 0.005 (0.5%). When passed is false, the agent must
make the smallest corrective changes, take a fresh screenshot, and call the
tool again; it must not declare the implementation complete first. Reference
and candidate dimensions must match.
extract creates a self-contained bundle. inspect and pack read that bundle
only; they do not need to reopen the original .fig file.
.figctx/design/
├── manifest.json
├── document.raw.json
├── document.agent.json
├── tokens/
│ ├── colors.json
│ ├── typography.json
│ ├── effects.json
│ ├── fonts.json
│ └── variables.json
├── assets/images/
├── assets/images.json
├── assets/thumbnail.png
├── assets/vectors.json
├── assets/vectors/
│ ├── vector-network-<id>.svg
│ └── vector-network-<id>.bin.gz
├── references/index.json
├── references/<node-id>.png
├── comparisons/<node-id>/report.json
├── comparisons/<node-id>/diff.png
└── frames/<node-id>/context.mdBundle files
manifest.jsonrecords the source file name and SHA-256, parser and contract versions, detected variants, extraction status, and warnings.document.raw.jsonis the decoded Kiwi document for diagnostics. Large binary blobs remain files or references, not base64 JSON payloads.document.agent.jsonis the stable normalized layer tree. Nodes retain IDs, names, type, parent/child ordering, absolute bounds, transforms, visibility, opacity, masks and frame clipping flags, constraints, layout details, paints, effects, text, Figma-computed text layout metrics (baselines and font metadata), and asset/vector references.tokens/contains deduplicated colors, typography, and effects, each with the source node IDs that produced it.tokens/fonts.jsonrecords required font family, style, PostScript name, observed weight, and source node IDs. It never copies licensed system fonts.tokens/variables.jsonrecords local Figma variable collections, modes, and values when the.figexport contains them. Existing bundles may not have this optional file; consumers return empty variables in that case.Text nodes with mixed local styles expose compact
textSegmentsruns indocument.agent.json; each run contains the character range, resolved typography overrides, and fill override when present. Nodes also retain local style references and variable bindings when the export provides them.assets/images/contains extracted raster assets with extensions inferred from their real byte signatures, not from Figma's extensionless filenames.assets/images.jsonmaps every original image hash to its local, inferred path. When present in the export,assets/thumbnail.pngis retained as the unmodified document-level visual baseline for implementation review.assets/vectors/retains the original Kiwi vector-network blobs and, when their geometry is valid, materializes a portable SVG beside each blob.assets/vectors.jsonmaps a blob ID to its lossless gzip-compressed path and optionalsvgPath;document.agent.jsoncarries the same fields invectorRef. SVG paths usecurrentColor, so consumers can style inline SVG consistently; malformed vector blobs remain available only as.bin.gz.Core consumers can use
composeVectorGroupSvg()to compose a maximal vector-only subtree into one SVG. It appliesframeMaskDisabled: falseas SVG clip paths and Figmamask: truelayers as masks for their following siblings. Raster fills, strokes, blend modes, effects, gradients, and text make a group unavailable rather than producing a partial SVG.frames/*/context.mdis a deterministic, compact summary for every canvas and top-level frame. Nested frames remain fully addressable withpackand are intentionally not duplicated as thousands of tiny files.
Supported input and compatibility policy
An accepted archive must be a valid ZIP containing canvas.fig. The first
decoder supports a canvas.fig beginning with fig-kiwi; it reads the embedded
Kiwi schema, decompresses the document chunks, and normalizes the decoded tree.
Other canvas payloads are not treated as corrupt by default. They receive the
structured UNSUPPORTED_FIG_VARIANT result so a new adapter can be added with
real evidence. The output contract has its own version independent of the
decoder version, allowing parser internals to change without silently breaking
agent integrations.
Safety and privacy
Extraction is local-only. The tool does not authenticate with, contact, or upload anything to Figma or another service.
ZIP paths are treated as untrusted. Traversal entries, duplicate critical entries, malformed metadata, and configurable resource-limit violations fail safely.
Output is written into a temporary sibling directory and renamed only after a complete successful extraction, preventing half-written agent bundles.
The original
.figis read-only. Existing output directories are refused unless the caller explicitly requests replacement.Real customer or proprietary
.figfiles and generated bundles are never committed as fixtures.
Architecture
The repository is a TypeScript/Node.js workspace with these boundaries:
packages/coreowns archive reading, Kiwi decoding, normalizing, token extraction, and atomic bundle writing.packages/cliowns command parsing, human-readable diagnostics, and exit codes.packages/mcp-serverserves existing bundles over stdio and never reparses Figma files or contacts Figma.examples/contains schemas and synthetic examples only.
The Kiwi dependency is isolated behind a KiwiDecoder adapter. This keeps the
undocumented binary format separate from the long-lived public JSON contract.
Errors
Failures are structured in manifest.json, printed concisely by the CLI, and
use nonzero exit codes. Initial error codes are:
INVALID_FIG_ARCHIVEMISSING_CANVASUNSUPPORTED_FIG_VARIANTCORRUPT_KIWI_CHUNKRESOURCE_LIMIT_EXCEEDEDOUTPUT_EXISTSNODE_NOT_FOUNDNODE_REFERENCE_FILE_MISMATCHINVALID_REFERENCE_IMAGEREFERENCE_IMAGE_DIMENSION_MISMATCHREFERENCE_NOT_FOUND
Tests and fixtures
The test suite has three layers:
Unit tests for archive/variant detection, safe paths, image signatures, and token normalization.
Committed synthetic fixture archives for successful, malformed, and unsupported cases, including snapshot tests for the stable agent document.
A local-only acceptance test enabled with
FIGCTX_ACCEPTANCE_FIG. It checks successful decode of a real export without copying, snapshotting, printing, or committing the design or its output.
Private real-export CI
Real Figma acceptance runs on same-repository pull requests to main and on
manual dispatch. Fork pull requests run the normal CI only, so they never
receive the private fixture credential. The acceptance job downloads an
immutable release asset from a separate private repository, verifies its
SHA-256, extracts it, compares hashes of safe bundle outputs, and exercises all
CLI and MCP tools without printing or uploading design data.
One-time GitHub setup:
Create a private test-data repository and publish the export as the release asset named by
tests/acceptance/fixture-contract.json.Set
FIGCTX_TEST_DATA_REPOSITORYas a repository variable andFIGCTX_TEST_DATA_TOKENas a fine-grained, read-only token with access only to that private repository.Protect
mainand require both the existing CI check andReal Figma acceptance / real-figfor internal pull requests.
To rotate the fixture, publish a new immutable release asset, run
FIGCTX_ACCEPTANCE_FIG=/path/to/figctx-acceptance.fig pnpm test:real-fig
locally after updating the contract hashes, and review only the resulting
checksum/count diff. Never commit the .fig, generated bundle, logs, or
workflow artifacts.
Non-goals for the first release
Screenshot-perfect rendering
A Figma API client, Figma MCP client, or browser automation
Editing or writing
.figfilesA hosted service
References and attribution
The implementation follows the local-export flow described by
Figma Help.
It treats the format as unstable, consistent with
Evan Wallace's parser note.
The Kiwi binary runtime is MIT-licensed and provides the embedded-schema
decoding model used by this project; see evanw/kiwi.
The project also acknowledges kreako/fig2json
as an open-source precedent for local, LLM-oriented .fig conversion.
License
MIT. See LICENSE.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/POLAX7/figma-free-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server