Skip to main content
Glama
tomasmarekk

Figma Native MCP

by tomasmarekk
README.md
# Figma Native MCP

A local [Model Context Protocol](https://modelcontextprotocol.io/) server that lets MCP clients inspect and edit the Figma document currently open in the official Figma desktop app.

The primary path uses a development plugin and the Figma Plugin API. It does **not** call Figma's hosted MCP service and does not consume Figma REST requests for normal document control. Optional REST tools are available behind an explicit opt-in.

> [!IMPORTANT]
> The loopback bridge requires a high-entropy bearer token. Local image files and remote image URLs are denied unless you configure explicit allowlists. Arbitrary plugin JavaScript is disabled by default.

## Architecture

```text
MCP client
    │ stdio
    ▼
figma-native-mcp server
    │ token-authenticated loopback HTTP
    ▼
local bridge on 127.0.0.1:3845
    │ authenticated polling and results
    ▼
Figma development plugin
    │ Figma Plugin API
    ▼
currently open Figma document

Optional: figma-native-mcp server ── HTTPS ──► Figma REST API
```

A stdio server process can own the bridge itself or attach to a foreground/watcher-owned bridge after an authenticated identity check.

## Requirements

- Node.js 20 or newer
- Figma desktop on Windows or macOS
- An MCP client that supports stdio servers
- A Figma REST token only if you explicitly enable REST tools

The Node server is cross-platform. This project does not claim a supported desktop-plugin workflow for Linux because Figma does not provide an official Linux desktop app.

## Install

```bash
git clone https://github.com/tomasmarekk/native-figma-mcp.git
cd native-figma-mcp
npm ci
```

Generate a bridge token:

```bash
npm run --silent generate-token
```

PowerShell:

```powershell
$env:FIGMA_MCP_BRIDGE_TOKEN = (npm run --silent generate-token).Trim()
```

POSIX shells:

```bash
export FIGMA_MCP_BRIDGE_TOKEN="$(npm run --silent generate-token)"
```

Keep this token secret. It is separate from any Figma API token. The exact same value must be supplied to every MCP server process, the Figma plugin, and the optional Windows watcher.

## Figma development plugin

1. Open the Figma desktop app.
2. Choose **Plugins → Development → Import plugin from manifest**.
3. Select `<absolute-path-to-repo>/plugin/manifest.json`.
4. Run **Figma Native MCP Bridge** in the document you want to control.
5. Paste the generated bridge token into the plugin and click **Save & connect**.
6. Keep the plugin running while MCP tools need access to that document.

The plugin stores its URL and token using `figma.clientStorage`. The saved token is never displayed back in the UI. By default it connects only to `http://127.0.0.1:3845`; `localhost` is also accepted.

## Configure an MCP client

Generic stdio configuration:

```json
{
  "mcpServers": {
    "figma-native": {
      "command": "node",
      "args": ["/absolute/path/to/native-figma-mcp/server.mjs"],
      "env": {
        "FIGMA_MCP_BRIDGE_TOKEN": "replace-with-generated-token"
      }
    }
  }
}
```

Environment expansion is client-specific. Do not assume `${VAR}` is expanded in generic MCP JSON.

### Claude Code

POSIX:

```bash
claude mcp add \
  --scope user \
  --env "FIGMA_MCP_BRIDGE_TOKEN=$FIGMA_MCP_BRIDGE_TOKEN" \
  --transport stdio \
  figma-native \
  -- node "/absolute/path/to/native-figma-mcp/server.mjs"
```

PowerShell:

```powershell
claude mcp add `
  --scope user `
  --env "FIGMA_MCP_BRIDGE_TOKEN=$env:FIGMA_MCP_BRIDGE_TOKEN" `
  --transport stdio `
  figma-native `
  -- node "C:\path\to\native-figma-mcp\server.mjs"
```

A shared Claude Code `.mcp.json` can forward the environment variable without committing its value:

```json
{
  "mcpServers": {
    "figma-native": {
      "command": "node",
      "args": ["/absolute/path/to/native-figma-mcp/server.mjs"],
      "env": {
        "FIGMA_MCP_BRIDGE_TOKEN": "${FIGMA_MCP_BRIDGE_TOKEN}"
      }
    }
  }
}
```

### Codex

Prefer forwarding an already-set environment variable from `~/.codex/config.toml`:

```toml
[mcp_servers.figma-native]
command = "node"
args = ["/absolute/path/to/native-figma-mcp/server.mjs"]
env_vars = [
  "FIGMA_MCP_BRIDGE_TOKEN",
  "FIGMA_MCP_ASSET_ROOTS",
  "FIGMA_MCP_ASSET_HOSTS"
]
```

The CLI equivalent is:

```bash
codex mcp add figma-native \
  --env "FIGMA_MCP_BRIDGE_TOKEN=$FIGMA_MCP_BRIDGE_TOKEN" \
  -- node "/absolute/path/to/native-figma-mcp/server.mjs"
```

`codex mcp add --env KEY=value` persists the literal value; `env_vars` is preferable for forwarding an existing secret.

### Claude Desktop

Configuration locations:

- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`

Use absolute paths for both Node and `server.mjs`, then fully restart Claude Desktop. Claude Desktop stores the literal token in its configuration.

## Foreground bridge

The supported cross-platform lifecycle is a foreground process:

```bash
npm run bridge
```

Set `FIGMA_MCP_BRIDGE_TOKEN` first. The bridge binds to loopback only. `GET /health` is intentionally unauthenticated and reveals only service identity and version; authenticated `GET /status` proves the token matches and returns connection details.

## Optional Windows watcher

Windows users can install an optional current-user Scheduled Task that starts the bridge while `Figma.exe` is running:

```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-windows-watcher.ps1
```

Rotate the stored token:

```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-windows-watcher.ps1 -PromptForToken
```

Uninstall and remove encrypted watcher data/logs:

```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\scripts\uninstall-windows-watcher.ps1
```

Add `-KeepData` to retain them. The installer stores the token using current-user Windows DPAPI under `%LOCALAPPDATA%\figma-native-mcp`; it never places the token in Scheduled Task arguments or logs. The watcher does not enable plugin eval and stops only a bridge child it started.

On macOS and Linux, use `npm run bridge` or wrap it in your own user-level `launchd`/`systemd` configuration.

## Tool groups

| Area | Default | Authentication | Limits |
| --- | ---: | --- | --- |
| Plugin bridge tools | Enabled | Bridge bearer token | Local Figma Plugin API |
| URL parsing and status | Enabled | Detailed bridge status requires token | Local only |
| Arbitrary plugin eval | Disabled | Token plus explicit eval opt-in | Trusted code in plugin context |
| Figma REST tools | Disabled | Figma PAT/OAuth plus REST opt-in | Figma scopes, plans and rate limits |

Core plugin tools cover document/page metadata, selection, bounded trees/nodes, CSS inspection, renaming, frame/text creation, text updates, image placement, and live verification. REST tools cover account identity, files/nodes, image exports/fills, comments, versions, teams/projects, variables, and dev resources.

## Environment variables

| Variable | Default | Purpose |
| --- | --- | --- |
| `FIGMA_MCP_BRIDGE_TOKEN` | required | Shared bridge bearer token, minimum 32 characters |
| `FIGMA_MCP_BRIDGE_HOST` | `127.0.0.1` | Loopback host; only `127.0.0.1`, `localhost`, or `::1` |
| `FIGMA_MCP_BRIDGE_PORT` | `3845` | Bridge port; the development manifest permits 3845 |
| `FIGMA_MCP_BRIDGE_ONLY` | `0` | Run HTTP bridge without MCP stdio |
| `FIGMA_MCP_ENABLE_PLUGIN_EVAL` | `0` | Enable arbitrary plugin-context JavaScript |
| `FIGMA_MCP_ENABLE_REST` | `0` | Expose optional Figma REST tools |
| `FIGMA_MCP_ASSET_ROOTS` | empty | Allowed local asset roots, separated by the OS path delimiter |
| `FIGMA_MCP_ASSET_HOSTS` | empty | Comma-separated exact HTTPS hostnames for remote images |
| `FIGMA_MCP_MAX_ASSET_BYTES` | `10485760` | Maximum image size |
| `FIGMA_MCP_MAX_REST_RESPONSE_BYTES` | `26214400` | Maximum buffered Figma REST response |
| `FIGMA_MCP_TIMEOUT_MS` | `30000` | REST and asset timeout |
| `FIGMA_MCP_PLUGIN_TIMEOUT_MS` | `30000` | Plugin command timeout |
| `FIGMA_MCP_PLUGIN_CLIENT_STALE_MS` | `60000` | Plugin client staleness threshold |
| `FIGMA_ACCESS_TOKEN`, `FIGMA_PERSONAL_ACCESS_TOKEN`, `FIGMA_TOKEN` | empty | Figma personal-token aliases |
| `FIGMA_OAUTH_TOKEN` | empty | Figma OAuth bearer token |
| `FIGMA_API_BASE_URL` | `https://api.figma.com` | Advanced/testing REST base URL |
| `FIGMA_MCP_WATCH_PROCESS_NAMES` | `Figma.exe` | Windows watcher process list |
| `FIGMA_MCP_WATCH_POLL_MS` | `2000` | Watcher poll interval |
| `FIGMA_MCP_WATCH_INACTIVE_GRACE_MS` | `4000` | Delay before owned bridge shutdown |

## Asset access restrictions

Local paths are denied until `FIGMA_MCP_ASSET_ROOTS` is configured. Remote downloads are denied until `FIGMA_MCP_ASSET_HOSTS` is configured.

PowerShell:

```powershell
$env:FIGMA_MCP_ASSET_ROOTS = "C:\design-assets;D:\shared-assets"
$env:FIGMA_MCP_ASSET_HOSTS = "images.example.com,cdn.example.com"
```

POSIX:

```bash
export FIGMA_MCP_ASSET_ROOTS="/srv/design-assets:$HOME/Pictures"
export FIGMA_MCP_ASSET_HOSTS="images.example.com,cdn.example.com"
```

Local paths are canonicalized and must remain inside an allowed root after resolving symlinks. Remote URLs must use HTTPS, match an exact allowlisted hostname, resolve only to public addresses, and remain allowlisted after every redirect. Image signatures are validated and downloads are streamed with a hard byte cap.

## Plugin eval warning

`FIGMA_MCP_ENABLE_PLUGIN_EVAL=1` lets authenticated MCP callers execute arbitrary JavaScript with the permissions of the development plugin. That code can read or change the current Figma document. Keep eval off unless every connected MCP client and prompt is trusted. The Windows watcher never enables it automatically.

## Testing

```bash
npm run check
npm test
npm run smoke
```

With the updated development plugin open and configured in a Figma document:

```bash
npm run live:verify
```

REST verification is optional:

```bash
FIGMA_MCP_ENABLE_REST=1 \
FIGMA_VERIFY_REQUIRE_REST=1 \
FIGMA_TEST_FILE_KEY="SYNTHETIC_FILE_KEY" \
npm run live:verify
```

## Troubleshooting

- **401/403:** the MCP server, plugin, or watcher is using a different bridge token.
- **Port occupied:** another service or incompatible bridge owns port 3845. Stop it or use a matching custom port and development manifest.
- **Plugin disconnected:** keep the development plugin running in the target document; it reconnects after bridge restarts.
- **Image denied:** configure the correct local root or exact HTTPS hostname.
- **Claude Desktop cannot start Node:** use absolute executable and script paths and restart the app.
- **Watcher logs:** inspect `%LOCALAPPDATA%\figma-native-mcp\logs\figma-bridge-watch.log`; tokens and document metadata are not logged.

## Limitations

- The development plugin must remain running in the target document.
- Plugin operations depend on Figma Plugin API capabilities and permissions.
- The committed manifest permits port 3845; custom ports require updating the imported development manifest.
- Multiple open plugin clients may require an explicit `clientId` when target selection matters.
- REST tools remain subject to Figma token scopes, product plans, rate limits and API behavior.
- Loopback plus a token does not protect against a compromised local account or administrator.
- There is no claimed official Linux desktop Figma workflow.

## Contributing

Issues and pull requests are welcome. Keep security reports private as described in [SECURITY.md](SECURITY.md). Run `npm run check` and `npm test` before opening a pull request.

## License

[MIT](LICENSE) © 2026 Tomas Marek.

> Figma is a trademark of Figma, Inc. This project is independent and unofficial. It is not affiliated with, sponsored by, or endorsed by Figma, Inc. Other product and company names are trademarks of their respective owners.