Figma design context for the agent: reads a frame's node tree, auto-layout, paints and typography, resolves the variables it is bound to, and renders a screenshot to compare code against — plus four bundled design-to-code skills. Connect with one button against Figma's own consent page; the plugin ships its own OAuth app, refreshes tokens automatically, and never asks for or displays a token.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:KitanWang/dsh-figma-plugin
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Figma design context for DeepSeek Harness. English | 中文
Hand DSH a Figma link and it can read the design: the node tree with its auto-layout, sizing, paints and typography, the design tokens the nodes are bound to, and a rendered screenshot it can actually look at — then write UI code that matches. Plus four bundled skills that carry the workflow.
Connect a Figma account with one button — no token to create, copy, or paste — and the agent can read the design.
This is the DSH counterpart to the Figma plugin in Codex. See How this compares for what is the same and what is different.
Install
Installs straight from GitHub — no registry publish needed:
dsh plugin --profile web add github:KitanWang/dsh-figma-plugin
Then restart dsh web (a newly added bundle is composed at boot).
Requires Node.js 20+ and DeepSeek Harness. If the plugin is published to npm
later, dsh plugin --profile web add dsh-figma-plugin will work too.
Connect Figma
Open Settings → Figma and press Connect Figma. Your browser opens Figma's own sign-in and consent page; approve it and the page flips to Connected, showing which Figma account you are using.
That is the whole flow. The plugin carries its own Figma OAuth application, so you never register one, and nothing about credentials is ever shown in the browser.
Already connected and want a different account? Press Reconnect — it starts a fresh sign-in and replaces the stored grant.
You can also ask the agent:
Connect to Figma.
The agent calls figma_login, which returns the authorization URL for you to
open. The agent never asks you to paste a token into chat.
How the connection is stored
The granted access and refresh tokens are stored as one credential record
(figma/oauth) in the harness credential store
($DSH_HOME/.credentials.yaml), and the access token is refreshed
automatically before it expires. The browser only ever learns whether the
connection is live and which account it belongs to — never a token, an expiry,
or any part of the OAuth client.
No personal access token path. A PAT would mean asking you to create a token in Figma's settings and paste it in, which is exactly the friction this plugin exists to remove. If you need token-based authentication for CI, use a separate integration.
export FIGMA_ACCESS_TOKEN=figd_xxx
dsh web
To set config explicitly, target the row by id in the profile's
cordis.patch.yml (~/.dsh/profiles/web/cordis.patch.yml):
- id: figma
config:
outputDir: .figma
Tools
| Tool | What it does |
|---|---|
figma_get_design_context |
Start here. One node as a compact structured tree + indented outline + rendered screenshot, with the Figma variables the node binds resolved to names and per-mode values. |
figma_get_screenshot |
Render one or more nodes to PNG/JPG/SVG/PDF, save them, and attach the image for viewing. |
figma_get_file |
List a file's pages and top-level frames — use it to find the node id when you only have a file link. |
figma_get_variables |
The file's local variables as design tokens: per-mode values with aliases resolved. Optionally write CSS and/or JSON. |
figma_get_styles |
Published paint, text, effect, and grid styles with their values. |
figma_get_components |
Components and component sets with keys, node ids, and variant properties. |
figma_get_dev_resources |
Dev resources (linked docs, tickets, code) attached to nodes. |
figma_get_comments |
Comment threads with their node anchors. |
figma_post_comment |
Post a comment anchored to a node or canvas position. Writes to Figma — confirm first. |
figma_whoami |
Verify the connection and report the authenticated account. |
figma_login |
Report the connection, or start sign-in and return the URL for the human to open. |
Every tool accepts either a full Figma URL (url) or a bare file key
(fileKey); node ids may be written 1-2 (URL form) or 1:2 (API form).
URLs for design, legacy file, proto, FigJam board, and Slides all parse.
Example
Implement this frame:
https://www.figma.com/design/AbC123/Home?node-id=12-345
The agent calls figma_get_design_context, reads the outline and the
screenshot, checks the repo's existing components and tokens, and writes the
component. The figma-design-to-code skill drives that sequence.
Skills
Four skills are registered into the harness-global skill layer, so every agent and preset sees them:
| Skill | For |
|---|---|
figma-design-to-code |
Implement a frame with fidelity; map it onto the repo's components and tokens; verify against the screenshot. |
figma-design-system |
Inventory variables, styles, and components; emit tokens and a durable rules document. |
figma-code-connect |
Generate Code Connect templates binding Figma components to code components. |
figma-design-review |
Compare an implementation against its design and report measured deltas, optionally as Figma comments. |
Configuration
All keys are optional.
| Key | Default | Meaning |
|---|---|---|
apiBaseUrl |
https://api.figma.com |
Override for a proxy. |
requestTimeoutMs |
30000 |
Per-request timeout. |
maxRetries |
2 |
Retries for 429/5xx, honouring Retry-After. |
outputDir |
.dsh-figma |
Where exports are written; relative paths resolve against the session workspace. |
maxNodes |
400 |
Default node budget for a design-context projection. |
maxDepth |
8 |
Default depth budget. |
skills |
true |
Register the bundled skills. |
scopes |
see below | Space-separated OAuth scopes requested at authorization. |
redirectUri |
'' |
Absolute redirect URI override; must match the Figma app exactly. Only loopback URLs are accepted. |
callbackPath |
/figma/oauth/callback |
Callback path appended to the redirect URI. |
connectionRoutes |
true |
Serve the OAuth callback and connection page. Off means tools-only, with no HTTP surface. |
authorizationUrl / tokenUrl / refreshUrl |
Figma's endpoints | Overridable for a proxy, a test, or Figma for Government. |
clientId / clientSecret |
shipped values | The plugin's own OAuth app. Only a fork or a deployment that wants a different app sets these; they are never read from or written by the browser. |
tools |
all on | Per-tool switches: whoami, file, designContext, screenshot, variables, styles, components, devResources, comments, postComment, login. |
The default scopes are current_user:read, file_content:read,
file_metadata:read, file_comments:read, file_comments:write,
file_dev_resources:read, library_content:read, and library_assets:read.
Figma fails the whole authorization when it is asked for a scope the app does
not have enabled, so these must all be selected on the app's OAuth scopes
page. file_variables:read is intentionally excluded because Figma marks it
Enterprise-only: on any other plan it cannot be enabled at all, and requesting
it would break sign-in entirely. On Enterprise, enable it on the app and add it
through the scopes config to make figma_get_variables work.
The plugin's OAuth app
Figma's token endpoint authenticates the client with HTTP Basic
(client_id:client_secret) and supports no secret-less public-client mode, so a
plugin that must not ask the user for credentials has to ship one client of its
own. It lives in one place — lib/oauth-app.js — and a fork
or deployment can override it with config or with FIGMA_CLIENT_ID /
FIGMA_CLIENT_SECRET.
Because Figma matches redirect URLs exactly, the app must list every redirect a deployment can use. The default GUI port is 3080:
http://127.0.0.1:3080/figma/oauth/callback
http://localhost:3080/figma/oauth/callback
If the GUI runs on another port, that port's callback URL must be registered on the OAuth app too — Figma matches redirect URLs exactly, and the connection page shows the exact URL to register while an authorization is pending.
Tools-only deployments
A deployment with no web server (or connectionRoutes: false) registers the
tools but no HTTP route. figma_login then reports that sign-in is unavailable,
and the tools explain that Figma is not connected. There is no token fallback:
this plugin authenticates only through its own OAuth grant.
Security notes
- The OAuth callback is an ordinary HTTP route, deliberately not behind the
harness's cross-origin API fence: Figma returns the browser with a top-level
cross-site navigation, which that fence rejects.
stateis therefore the authentication — 32 random bytes generated in-process, compared in constant time, and required to match a pending attempt. - Every state-changing route is same-origin POST only. A request with no
Originis refused rather than trusted, and a posted body cannot substitute client credentials. - The browser never receives the access token, the refresh token, or any part of
the OAuth client. The status payload is exactly
connected,available, and the pending attempt's state — asserted against leakage in the test suite. - The redirect URI must be a loopback http(s) URL, so a one-time code cannot be sent to a host this process does not own.
- Figma expires authorization codes after 30 seconds, so the exchange happens inside the callback request itself, before anything else is awaited.
- The shipped client secret is readable by anyone who installs the package. That is inherent to Figma requiring a secret; the app therefore requests only the scopes it needs, and a deployment can rotate it by setting its own values.
How this compares to the Codex Figma plugin
The Codex plugin is three things bolted together: a .codex-plugin/plugin.json
manifest, an app connector (.app.json → Figma's hosted MCP server), and a
bundle of skills, agents, commands, and a post-write hook. The design
intelligence lives in Figma's MCP server; the plugin is mostly wiring plus
prompt material.
DSH has the same primitives — a skill registry (ctx.skills), a tool
registry (ctx.tools), subagents, and an MCP bridge
(@deepseek-ai/dsh-mcp-client) — but nothing packaged for Figma. This plugin
fills that gap natively rather than by proxying Figma's MCP server, so it needs
no running Figma desktop:
| Codex + Figma plugin | dsh-figma | |
|---|---|---|
| Design reads | Figma MCP server (OAuth) | Figma REST API (OAuth) |
| Sign-in | browser authorization, hosted by Figma | browser authorization, hosted by Figma |
| OAuth client | Figma's own, shipped in the connector | the plugin's own, shipped in lib/oauth-app.js |
| Credential storage | connector-managed | harness credential store (records), auto-refreshed |
| Credentials the user handles | none | none |
| Requires Figma desktop running | No (hosted MCP) | No |
| Skills | 7 bundled, Figma-authored | 4 bundled, written for these tools |
| Design tokens | via MCP get_variable_defs |
figma_get_variables (modes + alias resolution + CSS/JSON export) |
| Code Connect | MCP + Figma CLI | skill guides template generation; publish with the Figma CLI |
| Write back to canvas | Yes (MCP + Plugin API) | No — see below |
| Setup | install plugin, authorize Figma | install plugin, click Connect Figma |
Why the plugin ships its own OAuth app
Figma's token endpoint authenticates the client with HTTP Basic
(client_id:client_secret) and supports no secret-less public-client mode, so
there is no way to sign a user in without some client secret. Shipping one in
the package keeps the user's side to a single button; the tradeoff is that the
secret is readable by anyone who installs the plugin. Figma also restricts
hosted-MCP dynamic client registration to clients in its MCP Catalog, so a
third-party plugin cannot mint a shared client either.
Want Figma's own MCP tools too?
They coexist. Point the harness MCP bridge at Figma's local Dev Mode server
(Figma desktop → Preferences → Enable Dev Mode MCP Server) and you get
mcp__figma__* tools alongside the figma_* ones:
# ~/.dsh/profiles/web/cordis.patch.yml
- insert:
- id: figma-devmode-mcp
name: '@deepseek-ai/dsh-mcp-client'
config:
transport: streamable-http
serverName: figma
url: http://127.0.0.1:3845/mcp
headers: {}
toolCallTimeoutMs: 60000
failOnStartupError: false
That is the closest thing to a drop-in replacement for Codex's app connector,
and it is what you want if you need canvas write-back. The bridge's HTTP
transport takes headers, not an OAuth flow, so Figma's hosted MCP endpoint
(https://mcp.figma.com/mcp) is not usable through it without a bearer token
of your own.
Limitations
- No canvas write-back. Creating or editing nodes in Figma is only possible through the Plugin API (which runs inside Figma) or Figma's MCP server. Use the MCP bridge above if you need it. This plugin reads.
- Variables need Figma Enterprise.
figma_get_variablescalls an endpoint restricted to full members of Enterprise orgs. On a lower plan it fails with a clear message;figma_get_stylesstill works. - Code Connect is guided, not automated. There is no Code Connect endpoint in the Figma REST API. The skill reads the component inventory and writes template files; publishing them is the Figma CLI's job.
- Exports land on disk. Screenshots are written under
outputDir(default<workspace>/.dsh-figma/). Add that to your.gitignore. An image is also attached inline whenever the current model accepts image input. - Rate limits are Figma's. The client retries 429/5xx with backoff, but a large file walked node by node can still hit the limit.
- Sign-in needs a credential store and a web server. Both are in the default web profile. A tools-only composition registers the tools but cannot sign in, and says so.
- The shipped OAuth secret is public. Anyone who installs the package can read it. It grants only the scopes listed above, and a deployment can rotate it by supplying its own client in config or the environment.
- The redirect port must be registered. Figma matches redirect URLs exactly
and the callback route lives on the GUI's own server, so a GUI launched with
--portcannot complete sign-in until that port's callback URL is added to the OAuth app. The connection page prints the exact URL to add while an authorization is pending.
Development
npm test # 116 unit + integration tests, no network
node scripts/smoke.mjs # mount in a real Cordis context; assert registration
node scripts/routes-smoke.mjs # drive the OAuth routes against a real WebServer
npm test runs a stub Figma API over a local socket, so the whole tool
surface — auth header, query building, rendering, file writes, token export —
is exercised without a Figma account. The OAuth half is covered by pure-function
tests (including the RFC 7636 PKCE vector), a state-machine suite over an
in-memory credential store, and HTTP tests for the callback and panel routes.
scripts/smoke.mjs mounts the plugin next to the harness's real ToolRuntime,
SkillRegistry, and SystemPrompt services and asserts the tools, skills, and
prompt section land. scripts/routes-smoke.mjs goes further: it mounts a real
WebServer plus a credential provider and then drives the connection API over
HTTP, asserting that the redirect URI uses the live port, that a cross-origin
connect is refused, that a forged callback state fails, and that no secret
crosses the wire.
The plugin has no build step and no runtime dependencies beyond the harness's
own packages. lib/client.js is hand-written plain browser JavaScript in the
module-loader's factory form, so no bundler is needed.
License
MIT. See LICENSE.
Links
More in this category
Tencent/WeKnora#dsh-weknora★ 27530
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 1711
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1115
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
gitroomhq/postiz-agent#dsh-postiz★ 475
Connects DeepSeek Harness to Postiz over MCP: list connected social media channels, fetch per-platform posting rules, and schedule, draft, or publish posts to X, LinkedIn, Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Bluesky, Mastodon, Discord, Slack, Telegram and more; adds a postiz workflow skill.
EthanYoQ/Invoice-Downloader#dsh-invoice-downloader★ 428
Local IMAP invoice download, OCR, archive, and Excel reimbursement summaries for DeepSeek Harness.
anysearch-team/anysearch-dsh★ 420
AnySearch-powered real-time web and vertical search provider for DeepSeek Harness.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.