Skip to main content
Glama
aihustlerahul-ui

Figma MCP local

README.md
# Design Intelligence Platform (Local MVP)

A local, high-performance replacement for the hosted Figma MCP server. It downloads Figma files
once into a SQLite cache, builds a full-text search index over them, and exposes a local MCP
endpoint that Cursor (or any MCP client) queries — without touching the Figma API.

```
Cursor ──► http://localhost:8787/mcp ──► SQLite cache ──► FTS5 search index
                                              ▲
                                              │ only during explicit sync
                                          Figma API
```

The cache is the source of truth. The Figma API is called in exactly three situations: validating
your token on connect, a cheap version check at the start of a sync, and the full file download
when the version changed (or `--force`).

## Architecture

Clean architecture with constructor-based dependency injection; [src/app.ts](src/app.ts) is the
composition root that wires everything together.

```
src/
  api/            REST routes (connect, sync, projects, status, logs, health)
  mcp/            MCP server: 8 tools over Streamable HTTP (stateless)
  figma/          Figma REST API client (axios, typed errors, request logging)
  sync/           Parser: Figma document tree -> flattened node rows + search docs
  database/       Drizzle schema, better-sqlite3 client (WAL), idempotent migrations
  search/         FTS5 search repository (safe query building, bm25 ranking, snippets)
  cache/          Cache metadata key/value store (file versions, bookkeeping)
  config/         Zod-validated .env configuration
  services/       AccountService, SyncService (job engine), QueryService, simplifier
  repositories/   Row-level access: accounts, files/pages, nodes, components, styles, variables
  types/          Figma API types + simplified domain types
  utils/          Structured logger (JSON lines + ring buffer for the dashboard)
  shared/         Typed application errors
frontend/         React + Vite + Tailwind dashboard (Connect / Projects / MCP Status)
tests/            Vitest suites incl. end-to-end MCP-over-HTTP tests with a mocked Figma API
scripts/          CLI sync
```

### How data is stored

A Figma file is a single huge JSON tree. Instead of storing one blob (slow to query) or fully
normalizing every property (schema churn), each node becomes one row: queryable columns
(`node_id`, `parent_id`, `page_id`, `type`, `name`, `depth`, `child_index`) plus the node's own
JSON with children stripped. Subtrees are rebuilt breadth-first at query time down to a depth
limit. Search runs over an FTS5 virtual table indexing names and text content.

### Simplified node format

`get_node` defaults to a compact design representation built for code generation: hex colors,
auto-layout expressed as flex terms (`mode/gap/padding/justify/align`), resolved typography, and
component instance names. Raw Figma JSON is available with `format: "raw"`.

## Installation

Requires Node.js 20+.

```bash
npm install
cp .env.example .env   # optional; defaults work out of the box
npm run dev
```

- Dashboard (dev): http://localhost:5173 (proxies to the API)
- API + MCP server: http://localhost:8787

For a production-style run: `npm run build && npm start` — the built dashboard is then served
directly at http://localhost:8787.

## Environment variables

| Variable                       | Default     | Description                                      |
| ------------------------------ | ----------- | ------------------------------------------------ |
| `PORT`                         | `8787`      | REST + MCP server port                           |
| `DATABASE_URL`                 | `sqlite.db` | SQLite database file path                        |
| `LOG_LEVEL`                    | `info`      | `debug` \| `info` \| `warn` \| `error`           |
| `FIGMA_MAX_RATE_LIMIT_RETRIES` | `3`         | Automatic retries on a Figma 429 (0 disables)    |

## Using it

1. **Connect** — open the dashboard, paste a Figma personal access token
   (Figma → Settings → Security → Personal access tokens). When creating the token, grant these
   scopes:
   - `current_user:read` — used once to validate the token
   - `file_content:read` — required to download files
   - `file_metadata:read` — recommended; lets re-syncs use the cheap Tier 3 metadata endpoint
     (the app falls back to a Tier 1 shallow fetch without it)
   - `file_variables:read` — optional, Enterprise plans only

   The token is validated against `/v1/me` and stored in the local SQLite database. *It is
   stored in plaintext — this is a single-user local dev tool; treat `sqlite.db` accordingly.*
2. **Sync** — paste a file key (the segment after `figma.com/design/` in a file URL) and click
   Sync. The full file is downloaded, flattened into SQLite, and indexed. Re-syncs first do a
   cheap version check and no-op when nothing changed.
3. **Connect Cursor** — add to your Cursor MCP settings:

```json
{
  "mcpServers": {
    "design-intelligence": {
      "type": "http",
      "url": "http://localhost:8787/mcp"
    }
  }
}
```

Then ask Cursor things like *"Generate the Login page"*, *"Find all buttons"*, *"List every
page"*, *"Find typography styles"* — all answered from the local cache.

### CLI sync

```bash
npm run sync -- <fileKey>            # sync (version-checked)
npm run sync -- <fileKey> --force    # re-download unconditionally
```

## MCP tools

| Tool               | Purpose                                                                  |
| ------------------ | ------------------------------------------------------------------------ |
| `sync_file`        | Explicitly (re)download a file into the cache; `chunked` for huge files  |
| `sync_node`        | Incrementally re-sync one node/frame/page subtree (must already be cached) |
| `get_file_summary` | One-call overview: counts, page list, top components — orient before drilling in |
| `list_pages`       | Pages of a cached file with their top-level frames                       |
| `get_node`         | Node subtree — simplified (default) or raw; `depth` bounds the size      |
| `search`           | FTS over pages, frames, components, text and styles; `limit`/`offset`     |
| `list_components`  | Components / component sets with descriptions; `limit`/`offset` + total   |
| `get_styles`       | Shared styles and design variables; `limit`/`offset` + totals            |

`fileKey` is optional on read tools when exactly one file is cached. Read tools are marked
`readOnlyHint` so clients like Cursor can auto-run them safely.

## REST API

`POST /api/connect` · `POST /api/sync` · `POST /api/sync-node` · `GET /api/projects` ·
`GET /api/status` · `GET /api/logs` · `GET /api/health` · `GET /api/connection`

## How sync works

1. `GET /v1/files/:key/meta` — compare `version` against the cached one; stop if unchanged.
   This is a Tier 3 endpoint (large rate budget). Without the `file_metadata:read` scope it
   falls back to `GET /v1/files/:key?depth=1` (Tier 1).
2. `GET /v1/files/:key` — full download (no vector geometry). Tier 1 — the scarcest rate-limit
   class (per Figma's [rate limits](https://developers.figma.com/docs/rest-api/rate-limits/),
   roughly 10–20 requests/min on Dev/Full seats since Nov 2025), which is exactly why this
   platform caches instead of proxying.
3. Parse: flatten the document tree into node rows and search documents.
4. Single SQLite transaction: replace file, pages, nodes, components, styles.
5. Variables via `GET /v1/files/:key/variables/local` — **Enterprise-plan only**; a 403 is
   expected on personal/pro plans and logged as a warning, not an error.
6. Rebuild the FTS5 index for the file.

Sync runs as a background job; poll `GET /api/status` or watch the dashboard. Nothing else ever
calls the Figma API.

For very large files, pass `chunked: true` (the `sync_file` tool / `POST /api/sync`) to download
page-by-page — a shallow `?depth=1` fetch for the page list, then one `?ids=<pageId>` request per
page — instead of one giant whole-file response. It bounds peak payload/memory at the cost of more
Tier-1 requests (the 429 retry above absorbs transient limits).

### Incremental sync

Iterating on one screen shouldn't re-download the whole file. `sync_node` (MCP) /
`POST /api/sync-node` fetches a single node via `GET /v1/files/:key/nodes?ids=`, re-roots the
returned subtree from the cached node's position, and atomically replaces just that branch — node
rows and their search index — while upserting any component/style metadata the response carries.
The node must already be cached from a prior full sync; a full `sync_file` remains the way to
reconcile structural changes such as added or deleted pages.

## Scripts

`npm run dev` · `npm run build` · `npm start` · `npm test` · `npm run lint` · `npm run format` ·
`npm run sync -- <fileKey>`

## Error handling

Typed errors with stable codes surface everywhere: `INVALID_TOKEN`, `NOT_CONNECTED`,
`FILE_NOT_FOUND`, `CACHE_MISS`, `CACHE_EMPTY`, `RATE_LIMITED`, `SYNC_IN_PROGRESS`,
`VALIDATION_ERROR`, `FIGMA_API_ERROR`. MCP tools return them as tool errors instead of crashing
the transport.

## Future roadmap (designed for, not implemented)

Multiple accounts (the API client is already token-stateless), semantic/vector search (sits next
to the FTS table), AI code generation (consumes the simplified node format), background sync,
component dependency graphs, and Electron packaging.