Troubleshooting¶
Start with csharp_diagnose: it reports the active configuration, whether the bridge is listening, which
files have a live plugin session, the state of the snapshot cache and any closed quota buckets.
"The plugin is not connected"¶
Every write tool and every plugin-first read needs a running plugin in that file. Open the file in Figma
Desktop and start Plugins → Development → Snail Bridge, then check figma_get_status. The plugin attaches to
every server it finds on the port range, so another instance cannot steal the connection; if the server
still does not see it, open the panel — the circle on the right shows the log — see
Plugin setup.
The panel shows the update banner and never connects¶
The server refused the panel because it did not send this machine's bridge token: it runs from a bundle installed
before the token existed, or it was imported from somewhere other than ~/.s3l-mcp-figma/plugin. Close the plugin and
start it again from Plugins → Development → Snail Bridge; if the banner stays, import manifest.json from the data
folder again. The server log says did not send this machine's bridge token for every refusal. Deleting
bridge.token makes a new one at the next start, and the installer writes it into the panel again.
ERR_CONNECTION_REFUSED lines in the DevTools console¶
Chrome prints its own net::ERR_CONNECTION_REFUSED line for every probe of a closed port — no JavaScript can
suppress it. With at least one live connection the plugin does not probe at all, so a quiet console is the
normal connected state. The lines appear only while no server is running: every 3 seconds for the first
half minute after the plugin loads (or after the last server goes away), then once per 30 seconds. That is the
discovery loop waiting for a server to start, not a failure — the Pause button silences it completely.
A remark was sent and nothing happened¶
Nothing in MCP lets a server wake an agent, so a remark left while nothing is running waits until the agent
asks for it with figma_get_remarks. The field keeps saying queued until it is read. If the agent never
asks, the words simply sit in the queue — that is the protocol, not a fault. Two ways out: the agent can end
its turn with figma_get_remarks(waitMs: 30000) and hear you within a second, or you can press cancel and
take the queue back.
A remark typed while figma_execute_steps is running is different: it stops the build after the current
step and comes back inside that call's answer, so it needs no separate read.
Expected node, got null, or the script ran in another file¶
The target of the commands does not follow the human's clicks, so this no longer happens by itself. It means
the agent works in the wrong file: figma_get_status shows the target, and figma_navigate with the file's
link sets it. With several files connected and no target chosen, the write tools refuse and list the files
instead of guessing.
The panel says "no agent is working in this file"¶
Remarks go only to the servers that claimed this file. Either the agent has not named the file yet, so ask it
to figma_navigate there, or it works in another one. The words stay in the field; press Enter again once the
agent is here.
"The file this agent works in, … is not connected"¶
The agent chose that file with figma_navigate, and its plugin is no longer running there: the window was closed or
the plugin stopped. The target is kept on purpose, so a write never lands in another agent's file. Run Snail Bridge in
that file again, or let the agent call figma_navigate with the file it should work in now.
The panel calls an agent claude-code or agent 2¶
A shared server names an agent after the first folder its client lists as an MCP root. A client that lists none keeps
the name it gave in initialize, and one without that is numbered. Clients that open with the 2026-07-28 protocol
revision no longer send roots at all.
The shared server answers 403¶
The request carried an Origin header. Every browser sends one and an agent's client never does, so the server
refuses it: no web page may drive Figma through it. A tool that sends Origin on purpose cannot use the shared
server; give it a stdio server instead.
The shared server does not start¶
Its log is ~/Library/Logs/Snail.MCP.Figma.log. An address that is not loopback is refused at start; a busy port
9233 needs another one in SNAIL_MCP_FIGMA_URL, in the job and in every client registration.
Reads return source: "cache-stale"¶
Figma was unreachable — usually the quota — so the last known snapshot was served instead of failing. The
content may be older than the file. Force a fresh read with source: "plugin" when the plugin is running.
429 and a Retry-After of many hours¶
A daily plan quota, not a burst limit. The endpoint bucket is closed locally until it expires and further
requests are refused without touching the network; csharp_diagnose lists the closed buckets under
rateLimit. Renders live in the same expensive bucket, so during a block take pictures through the plugin —
figma_take_screenshot or figma_get_component_image with a live session.
Variables come back as 403¶
The Figma Variables REST API is Enterprise-only. Read variables through the plugin instead
(figma_get_token_values, or figma_get_variables with a live session) — that works on any plan.
"Not connected" in a few milliseconds, right after a call that worked¶
Look at how long the refusal took. A call refused within milliseconds never reached the plugin: the socket
had already dropped, and the script in that call is not what dropped it. A call that hangs for tens of seconds
and then times out did reach the plugin, and the plugin may still finish it — read the state again before
repeating anything. figma_get_status shows a new connectionId and connectedAt after a reconnect, and
globals kept between figma_execute calls are gone with the old connection.
The server reports an old state after a rebuild¶
A running process keeps the assembly it started with. Restart the MCP server in your client; figma_reconnect
and figma_reload_plugin only restart the plugin inside Figma, not the server process.
Nothing works and stdout looks wrong¶
The server speaks JSON-RPC over stdio, so nothing but protocol messages may reach stdout; logs go to stderr.
If a client reports a parse error, capture stderr and check for a stray write. The repository guards this with
StdioContractTests, which runs the built server as a real process and fails on the first non-JSON line.