[中文](./README.md) | [English](./README_EN.md)

# figma-mcp-dsh

`figma-mcp-dsh` is a read-only Figma plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness). It lets models inspect file structure, colors, typography, components, and styles, then render selected frames as images visible in the current conversation.

## Features

The plugin helps models understand designs without changing cloud files:

- Read Figma files, pages, and node trees
- Extract colors (including the stops of a gradient), type scales, layout properties, and dimensions
- Report auto-layout alignment and wrapping, masks, and rotation — so a tilted or clipped layer cannot be described as a straight, complete one
- Inspect local components, component sets, variants, and styles
- Export a selected frame and return it to the model as an image
- Limit traversal depth so large files do not consume the model context
- Return a summary and save the full result locally when output is too large
- Handle rate limits, expired tokens, and missing permissions

## Requirements

- **DeepSeek Harness ≥ 0.2.0-rc.2**. Verified: desktop app `0.2.1-rc.3`, web edition `0.2.1-rc.2` and `0.2.0-rc.2`. **On DSH 0.1.x, stay on `figma-mcp-dsh@0.1.3`** — do not upgrade to 0.2.0.
- Node.js 20 or later
- `pnpm` (needed for CLI installs; the desktop app's plugin page uses its bundled pnpm)
- A Figma Personal Access Token (PAT) with access to the target file

The Figma token requires these read-only scopes:

```text
file_content:read
file_metadata:read
file_comments:read
file_dev_resources:read
```

## Installation and configuration

> **The plugin ships as a bundle**: installing it mounts it automatically, so **a fresh install no longer needs a hand-written `cordis.patch.yml` entry**.
>
> **The two editions have separate profiles**: the desktop app always uses `desktop`, `dsh web` defaults to `web`. A plugin installed into one profile only affects that edition. **Using both? Install it twice.**

### 1. Install into the desktop app

Desktop app → **Settings → Plugins → Add plugin** → enter `figma-mcp-dsh` → **Install / Enable**; restart when prompted.

That is all: the plugin manager writes `figma-mcp-dsh` into `dsh.profile.bundles` in `~/.dsh/profiles/desktop/package.json`, and the patch file shipped inside the package inserts the `figma` row.

### 2. Install into the web edition

The **Plugins** page in `dsh web` goes through the same entry point, or use the CLI:

```bash
dsh plugin --profile web add figma-mcp-dsh
```

Same result: the package is selected into `dsh.profile.bundles`. Restart, or wait for the hot reload.

### 3. Other installation methods

#### From GitHub

```bash
dsh plugin --profile web add github:N107meow/figma-mcp-dsh
```

The GitHub path goes through the plugin manager too, so the bundle is added automatically.

#### Dependency only (not recommended)

```bash
cd ~/.dsh/profiles/web
pnpm add figma-mcp-dsh          # or pnpm add github:N107meow/figma-mcp-dsh
```

`pnpm add` **installs the dependency but does not select the package into `dsh.profile.bundles`**, so nothing is mounted. If you insist on this route, add `"figma-mcp-dsh"` to the profile's `dsh.profile.bundles` array yourself.

#### Local development (`link:`)

```bash
cd ~/.dsh/profiles/web
pnpm add link:/absolute/path/to/figma-mcp-dsh
```

`link:` is a development path and also does not add the bundle: add the row from §4 by hand (the pre-0.2.0 behaviour still works). Local development also requires host packages that match your DSH version; `npm run check:deps` verifies them.

### 4. Confirm it is mounted (and the manual fallback)

```bash
dsh --profile web --dump-config | grep -A2 -- "- id: figma"
```

If you see `- id: figma` / `name: figma-mcp-dsh`, you are done.

**Manual fallback** (old installs, `link:` development, or an auto-mount that failed): append this to `~/.dsh/profiles/<name>/cordis.patch.yml`

```yaml
- insert:
    - id: figma
      name: figma-mcp-dsh
```

> If the bundle is already in `dsh.profile.bundles`, this row duplicates the same id: measured behaviour is no error (the later row wins), but the duplicate should be removed.

### 5. Configure the Figma token

Store the token in `~/.dsh/.credentials.yaml`:

```yaml
refs:
  FIGMA_TOKEN: your_figma_token_here
```

You can also set the `FIGMA_TOKEN` environment variable. The plugin resolves credentials for every operation, so replacing a token does not require a DSH restart.

#### Getting a token in three steps

**1. Open Settings** — click **your avatar** in Figma's top-left corner and choose **Settings**.

![Avatar → Settings](https://cdn.jsdelivr.net/gh/N107meow/figma-mcp-dsh@8ca5f60632bb3e2cd9f664809023ec81041f5dc7/docs/images/token-1-open-settings.png)

**2. Switch to the Security tab** — in the settings dialog, open **Security**, find **Personal access tokens**, and click **Generate new token**.

![Settings → Security → Personal access tokens → Generate new token](https://cdn.jsdelivr.net/gh/N107meow/figma-mcp-dsh@8ca5f60632bb3e2cd9f664809023ec81041f5dc7/docs/images/token-2-security-tab.png)

**3. Name it, pick an expiry, tick read-only scopes** — use a name you will recognise later (for example `dsh-figma-readonly`); set **Expiration** to `90 days` (90 days is the maximum for a personal access token); tick **only the four read-only scopes below** and no write scope, then click **Generate token**. **Copy it immediately** — the value is shown once.

![Name, expiration, and read-only scopes](https://cdn.jsdelivr.net/gh/N107meow/figma-mcp-dsh@8ca5f60632bb3e2cd9f664809023ec81041f5dc7/docs/images/token-3-token-form.png)

> The screenshots show the Chinese interface; the English labels are given in bold. The [Figma token setup guide](./docs/TOKEN_SETUP.md) covers each scope, rotation, and what to do when a token stops working.

### 6. Upgrading, and migrating from 0.1.x

**Upgrade**:

```bash
dsh plugin --profile <name> update figma-mcp-dsh
```

> ⚠️ **An upgrade does not necessarily add the bundle**: the plugin manager only selects the package into `dsh.profile.bundles` on a *fresh* install. When the dependency already exists, `reconcile` does not treat it as a new bundle. Confirm with §4 after upgrading.

**Migrating from 0.1.x (the order matters)**:

1. Make sure the new version is installed and **confirm** `figma-mcp-dsh` is in `dsh.profile.bundles` (if it is not, reinstall once, or append `"figma-mcp-dsh"` to the end of `dsh.profile.bundles` in the profile's `package.json`, keeping the existing entries);
2. **Only after that**, delete the `figma` row that 0.1.x required you to write by hand in `cordis.patch.yml`.

Doing it the other way round leaves a short window where the plugin is installed but not mounted.

**Version gate**: `0.2.0` requires DSH `>= 0.2.0-rc.2`; on DSH 0.1.x stay on `figma-mcp-dsh@0.1.3`.

**Both versions can be installed from GitHub** (each has its own tag):

```bash
# Current version (bundle, mounts itself)
dsh plugin --profile web add github:N107meow/figma-mcp-dsh#v0.2.0

# Legacy version (DSH 0.1.x; plain plugin, add the §4 row by hand)
dsh plugin --profile web add github:N107meow/figma-mcp-dsh#v0.1.3
```

> **The plugin and DSH versions must match.** DSH validates the plugin's `peerDependencies`, and a mismatch makes the **installation fail outright** (`installation rejected: … is incompatible with dsh …`). That message means the wrong version was chosen — choose the right one. Do not work around it with `dsh plugin allow-version`: that exemption exists for "known incompatible, risk accepted".

**The token is global**: `~/.dsh/.credentials.yaml` is shared by both editions, so it only needs configuring once (a custom `DSH_HOME` needs its own copy).

## Usage examples

After installation, paste a Figma file or node URL into a DSH conversation and describe what you need. For example:

```text
Analyze the layout, primary colors, and type scale in this Figma frame:
https://www.figma.com/design/your_file_key/project?node-id=12-345
```

The model selects the appropriate read operation for the task. You can also request a specific result:

```text
List the components and variants defined in this file.
Render node 101:202 and analyze its visual hierarchy.
List the color and text styles in this design file.
```

The plugin registers two model-facing tools:

| Tool | Purpose |
| --- | --- |
| `figma_capabilities` | Lists available operations; use `detail="full"` for complete parameter schemas |
| `figma_call` | Runs a read operation; select it with `op` and pass a Figma URL through `target` |

Example tool call:

```javascript
figma_call({
  op: "file_nodes",
  target: "https://www.figma.com/design/your_file_key/project?node-id=12-345",
})
```

## Seven supported operations

Every operation uses a read-only Figma endpoint and cannot change design files.

| `op` | Purpose | Rate-limit tier |
| --- | --- | --- |
| `file_meta` | Read the file name, version, modification time, and current role | Tier 3 |
| `file` | Read pages and top-level frame structure | Tier 1 |
| `file_nodes` | Read one or more node subtrees by node ID | Tier 1 |
| `image_render` | Export selected nodes and return images to the model | Tier 1 |
| `components` | List components and variants defined in the current file | Tier 1 |
| `component_sets` | List component sets defined in the current file | Tier 1 |
| `styles` | List fill, text, effect, and grid styles | Tier 1 |

`file_nodes` is the recommended starting point for a single page or frame. When a URL includes `node-id`, the plugin resolves that node and applies `depth=2` unless you provide a depth.

## Security and privacy

Read-only access and credential protection are architectural constraints:

- The capability registry only declares `GET` requests, and dispatch checks each method again
- The plugin rejects automatic redirects so the token cannot reach a non-Figma domain
- Image downloads from signed Figma URLs do not include the Figma token
- Logs and error results redact known credentials
- Large results and rendered images stay in the local `.figma/` directory, which Git ignores
- Git ignores `.env.*`, `.credentials.yaml`, and recorded-data directories
- `npm run check:secrets` scans pending files and Git history for tokens and private identifiers

Do not commit real Figma tokens, private file URLs, or exported design content.

## Frequently asked questions

These answers cover the most common installation errors and runtime behavior.

### Why does the plugin return `unconfigured`?

The plugin cannot resolve `FIGMA_TOKEN`. Check `~/.dsh/.credentials.yaml` or the environment variables available to the current process.

### Why does the plugin return `token_invalid`?

The token has expired or was revoked. Create a token and replace the old value. The next operation will use the new credential.

### Why does the plugin return `forbidden_scope`?

The token lacks a required permission. Create a token with the four read-only scopes listed in the requirements.

### Why does the plugin return `credential_error`?

DSH's own credential service failed while resolving the token — a credentials file that is unreadable or no longer valid YAML, or a service that is not mounted — so no request was sent to Figma. This differs from `unconfigured`: that means the service answered "no token", while this means the service did not answer at all. Check that `~/.dsh/.credentials.yaml` is readable and is valid YAML, confirm it holds the entry you expect, and restart DSH if the file looks fine.

### Why does a request wait before running?

The plugin uses conservative defaults to protect your Figma API quota. Requests wait instead of being dropped when the quota is empty. After a `429` response, the plugin adjusts its limits from the response headers.

### Why is there no Variables operation?

The Figma Variables API is limited to Enterprise organizations and requires an additional scope. The plugin does not expose this operation.

### What happens when a result is too large?

The plugin reduces the traversal depth and retries once. If the result remains too large, it returns a structural summary and writes the full result to the local `.figma/` directory.

### Where are rendered images stored?

Rendered images are saved under `.figma/images/`, copied to DSH attachment storage, and returned to the current conversation as image content.

### How do I verify a local development build?

Run this command in the repository root:

```bash
npm run verify
```

The command checks host dependencies, read-only boundaries, sensitive data, and the test suite.

This project is available under the [MIT License](./LICENSE). Report problems through [GitHub Issues](https://github.com/N107meow/figma-mcp-dsh/issues). This project is not affiliated with or endorsed by Figma.
