figma-rust-mcp 2.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BuiHung1612
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # figma-mcp
2
+
3
+ High-performance, bidirectional Model Context Protocol (MCP) server written in **Rust** connecting AI coding assistants directly to **Figma Desktop**.
4
+
5
+ Enables AI agents (Claude Code, Cursor, Windsurf, Antigravity, VS Code, Zed) to **draw UI directly on the Figma canvas** and **read existing designs back** as structured JSON trees, design tokens, CSS, and screenshots β€” with zero external API keys needed.
6
+
7
+ ---
8
+
9
+ ## ⚑ Highlights
10
+
11
+ - **Pure Rust Native Performance**: Starts in `< 1ms`, uses `~3MB RAM`, zero GC pauses.
12
+ - **Bidirectional Bridge**: AI can both write UI (draw vectors, components, frames, autolayout) and read designs (extract hierarchy, compute styles, capture canvas).
13
+ - **Sandboxed JS Runtime**: Powered by **Boa** (pure Rust ECMAScript engine) for safe execution of design scripts.
14
+ - **Built-in Asset CDN**: Auto-fetches and normalizes SVG icons from 7 icon libraries (Ionicons, Lucide, Tabler, Bootstrap, Fluent, Phosphor).
15
+ - **Multi-Tab / Multi-Session Support**: Seamlessly connects to multiple open Figma files simultaneously.
16
+ - **100% Localhost Privacy**: All communication stays strictly on `127.0.0.1:38451` via HTTP long-polling.
17
+
18
+ ---
19
+
20
+ ## πŸ›οΈ Architecture
21
+
22
+ ```text
23
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
24
+ β”‚ Terminal (Standalone Server) β”‚
25
+ β”‚ figma-mcp β”‚
26
+ β”‚ β€’ MCP SSE Transport (/sse, /message) β”‚
27
+ β”‚ β€’ Sandboxed JS Runtime (Boa) β”‚
28
+ β”‚ β€’ Built-in Asset & Icon Resolver β”‚
29
+ β”‚ β€’ Async WebSocket & HTTP Long-Polling Bridge (Axum) β”‚
30
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–²β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
31
+ β”‚ (ws://127.0.0.1:38451) β”‚ (http://127.0.0.1:38451/sse)
32
+ β”‚ β”‚
33
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
34
+ β”‚ Figma Desktop β”‚ β”‚ AI Assistant(s) β”‚
35
+ β”‚ (Plugin Bridge) β”‚ β”‚ Antigravity/Cursorβ”‚
36
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ Claude Code / Zed β”‚
37
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
38
+ ```
39
+
40
+ ---
41
+
42
+ ## πŸš€ Quick Start
43
+
44
+ ### 1. One-Line Launch with NPX (No Rust toolchain needed)
45
+
46
+ You can launch `figma-rust-mcp` on any Mac, Linux, or Windows machine with zero setup:
47
+
48
+ ```bash
49
+ npx figma-rust-mcp
50
+ ```
51
+
52
+ This automatically downloads the prebuilt native binary for your architecture and starts the server.
53
+
54
+ Or if configuring in **Cursor / Claude Desktop / Windsurf / Antigravity MCP Settings**:
55
+
56
+ ```json
57
+ {
58
+ "mcpServers": {
59
+ "figma-rust-mcp": {
60
+ "command": "npx",
61
+ "args": ["-y", "figma-rust-mcp", "--stdio"]
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ ---
68
+
69
+ ### 2. Or Build from Source (Rust)
70
+
71
+ Prerequisites: [Rust toolchain](https://rustup.rs/) (`cargo >= 1.80`).
72
+
73
+ ```bash
74
+ git clone https://github.com/BuiHung1612/figma-mcp.git
75
+ cd figma-mcp
76
+ cargo build --release
77
+ ./target/release/figma-mcp
78
+ ```
79
+
80
+ ---
81
+
82
+ ### 3. Install the Figma Plugin
83
+
84
+ 1. Open **Figma Desktop**.
85
+ 2. Go to **Plugins** β†’ **Development** β†’ **Import plugin from manifest...**
86
+ 3. Select the file: `plugin/manifest.json` from this repository.
87
+ 4. Run the plugin (**Plugins** β†’ **Development** β†’ **Figma MCP Bridge**).
88
+ 5. The plugin UI will show a green dot (**Connected**).
89
+
90
+ ---
91
+
92
+ ### 4. Configure Your MCP Client
93
+
94
+ #### Google Antigravity (SSE Transport - Recommended)
95
+ Add to your `~/.gemini/config/mcp_config.json` or project `.agents/mcp_config.json`:
96
+ ```json
97
+ {
98
+ "mcpServers": {
99
+ "figma-mcp": {
100
+ "serverUrl": "http://127.0.0.1:38451/sse"
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ #### Claude Code / Cursor / Windsurf / VS Code
107
+ You can connect via SSE or stdio:
108
+
109
+ **Option A: SSE Transport (Recommended)**
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "figma-mcp": {
114
+ "url": "http://127.0.0.1:38451/sse"
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ **Option B: Stdio Subprocess (via NPX)**
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "figma-rust-mcp": {
125
+ "command": "npx",
126
+ "args": ["-y", "figma-rust-mcp", "--stdio"]
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ ---
133
+
134
+ ## πŸ› οΈ MCP Tools Reference
135
+
136
+ `figma-mcp` exposes 7 first-class MCP tools:
137
+
138
+ ### `figma_get_selection`
139
+ Ultra-fast, token-compressed inspection of currently selected layers/frames on the Figma canvas. Returns compacted layout and typography hierarchy with 60-80% fewer tokens.
140
+
141
+ ### `figma_inspect_node`
142
+ Inspects a specific node by ID or name, returning complete design context, CSS, auto-layout rules, and typography.
143
+
144
+ ### `figma_export_asset`
145
+ Exports any Figma node/layer directly into PNG, JPG, or SVG.
146
+ - `outputPath`: When specified (e.g. `src/assets/logo.png`), saves directly to disk and returns minimal JSON metadata instead of filling context window with large base64 strings!
147
+
148
+ ### `figma_status`
149
+ Checks live bridge connection status, connected Figma tabs, active file name, and latency statistics.
150
+
151
+ ### `figma_write`
152
+ Executes JavaScript draw commands inside the sandboxed VM to build or modify designs on the Figma canvas.
153
+ - Supports Auto-layout (`layoutMode`, `itemSpacing`, `padding`), typography, fills, strokes, corner radius, drop shadows, and component creation.
154
+ - Supports icon loading: `figma.loadIcon("ionicons", "heart", { size: 24, fill: "#ff4757" })`.
155
+ - Supports image insertion: `figma.loadImage(url, { width: 300, height: 200 })`.
156
+
157
+ ### `figma_read`
158
+ Universal reader for advanced queries: `get_design`, `scan_design`, `screenshot`, `export_svg`, `get_variables`, `get_styles`, `search_nodes`. Supports `outputPath` for file exports.
159
+
160
+ ### `figma_docs`
161
+ Fetches built-in documentation, design rules, layout guidelines, and code examples for `figma_write`.
162
+ - `section`: `"all"` | `"rules"` | `"layout"` | `"api"` | `"tokens"` | `"icons"`
163
+
164
+ ### `figma_rules`
165
+ Audits the current Figma document and generates consistency rules and token definitions to guide AI generation.
166
+
167
+ ---
168
+
169
+ ## πŸ’» Development & Testing
170
+
171
+ ```bash
172
+ # Run server directly in debug mode
173
+ cargo run
174
+
175
+ # Build release binary
176
+ cargo build --release
177
+
178
+ # Run automated MCP test suite
179
+ ./scripts/test-rust-mcp.sh
180
+
181
+ # Rebuild Figma plugin bundle (if plugin-src/ is modified)
182
+ node scripts/build-plugin.js
183
+ ```
184
+
185
+ ---
186
+
187
+ ## πŸ“„ License
188
+
189
+ MIT Β© [BuiHung1612](https://github.com/BuiHung1612) β€” Free to use, modify, and distribute. See [LICENSE](LICENSE) for details.
@@ -0,0 +1,445 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Figma MCP - NPX Binary Runner
5
+ * Automatically detects platform/arch, downloads prebuilt Rust binary from GitHub Releases,
6
+ * caches it locally, and executes it transparently.
7
+ */
8
+
9
+ import { spawn, execSync } from 'node:child_process';
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import os from 'node:os';
13
+ import https from 'node:https';
14
+ import http from 'node:http';
15
+ import { fileURLToPath } from 'node:url';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ // Read package.json version
21
+ const pkgPath = path.resolve(__dirname, '../package.json');
22
+ let pkgVersion = '2.8.2';
23
+ let repoOwner = 'BuiHung1612';
24
+ let repoName = 'figma-mcp';
25
+
26
+ try {
27
+ if (fs.existsSync(pkgPath)) {
28
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
29
+ if (pkg.version) pkgVersion = pkg.version;
30
+ if (pkg.repository && typeof pkg.repository.url === 'string') {
31
+ const m = pkg.repository.url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
32
+ if (m) {
33
+ repoOwner = m[1];
34
+ repoName = m[2];
35
+ }
36
+ }
37
+ }
38
+ } catch (_) {}
39
+
40
+ function getPlatformArch() {
41
+ const platform = process.platform;
42
+ const arch = process.arch;
43
+
44
+ let osName = '';
45
+ let archName = '';
46
+ let ext = 'tar.gz';
47
+ let binName = 'figma-mcp';
48
+
49
+ if (platform === 'darwin') {
50
+ osName = 'macos';
51
+ archName = arch === 'arm64' ? 'arm64' : 'x86_64';
52
+ } else if (platform === 'linux') {
53
+ osName = 'linux';
54
+ archName = arch === 'arm64' ? 'arm64' : 'x86_64';
55
+ } else if (platform === 'win32') {
56
+ osName = 'windows';
57
+ archName = 'x86_64';
58
+ ext = 'zip';
59
+ binName = 'figma-mcp.exe';
60
+ } else {
61
+ throw new Error(`Unsupported operating system: ${platform}`);
62
+ }
63
+
64
+ const assetName = `figma-mcp-${osName}-${archName}.${ext}`;
65
+ return { osName, archName, ext, binName, assetName };
66
+ }
67
+
68
+ function getCacheDir(version) {
69
+ const platform = process.platform;
70
+ let baseCache = '';
71
+
72
+ if (platform === 'win32') {
73
+ baseCache = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
74
+ } else if (platform === 'darwin') {
75
+ baseCache = path.join(os.homedir(), 'Library', 'Caches');
76
+ } else {
77
+ baseCache = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
78
+ }
79
+
80
+ return path.join(baseCache, 'figma-mcp', `v${version}`);
81
+ }
82
+
83
+ function downloadFile(url, destPath) {
84
+ return new Promise((resolve, reject) => {
85
+ const followRedirects = (currentUrl, redirectCount = 0) => {
86
+ if (redirectCount > 10) {
87
+ reject(new Error('Too many redirects while downloading binary'));
88
+ return;
89
+ }
90
+
91
+ const client = currentUrl.startsWith('https') ? https : http;
92
+ client.get(currentUrl, { headers: { 'User-Agent': 'figma-mcp-npx' } }, (res) => {
93
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
94
+ followRedirects(res.headers.location, redirectCount + 1);
95
+ return;
96
+ }
97
+
98
+ if (res.statusCode !== 200) {
99
+ reject(new Error(`Failed to download binary: HTTP ${res.statusCode} from ${currentUrl}`));
100
+ return;
101
+ }
102
+
103
+ const fileStream = fs.createWriteStream(destPath);
104
+ res.pipe(fileStream);
105
+
106
+ fileStream.on('finish', () => {
107
+ fileStream.close(() => resolve());
108
+ });
109
+
110
+ fileStream.on('error', (err) => {
111
+ fs.unlink(destPath, () => {});
112
+ reject(err);
113
+ });
114
+ }).on('error', (err) => {
115
+ reject(err);
116
+ });
117
+ };
118
+
119
+ followRedirects(url);
120
+ });
121
+ }
122
+
123
+ function extractArchive(archivePath, targetDir, ext) {
124
+ fs.mkdirSync(targetDir, { recursive: true });
125
+
126
+ if (ext === 'zip') {
127
+ try {
128
+ execSync(`tar -xf "${archivePath}" -C "${targetDir}"`, { stdio: 'pipe' });
129
+ return;
130
+ } catch (_) {
131
+ execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${targetDir}' -Force"`, { stdio: 'pipe' });
132
+ return;
133
+ }
134
+ }
135
+
136
+ execSync(`tar -xzf "${archivePath}" -C "${targetDir}"`, { stdio: 'pipe' });
137
+ }
138
+
139
+ async function findOrDownloadBinary() {
140
+ if (process.env.FIGMA_MCP_BIN && fs.existsSync(process.env.FIGMA_MCP_BIN)) {
141
+ return process.env.FIGMA_MCP_BIN;
142
+ }
143
+
144
+ const localRelease = path.resolve(__dirname, '../target/release/figma-mcp' + (process.platform === 'win32' ? '.exe' : ''));
145
+ const localDebug = path.resolve(__dirname, '../target/debug/figma-mcp' + (process.platform === 'win32' ? '.exe' : ''));
146
+
147
+ if (fs.existsSync(localRelease)) return localRelease;
148
+ if (fs.existsSync(localDebug)) return localDebug;
149
+
150
+ const info = getPlatformArch();
151
+ const cacheDir = getCacheDir(pkgVersion);
152
+ const cachedBin = path.join(cacheDir, info.binName);
153
+
154
+ if (fs.existsSync(cachedBin)) {
155
+ if (process.platform !== 'win32') {
156
+ try { fs.chmodSync(cachedBin, 0o755); } catch (_) {}
157
+ }
158
+ return cachedBin;
159
+ }
160
+
161
+ fs.mkdirSync(cacheDir, { recursive: true });
162
+ const archivePath = path.join(cacheDir, info.assetName);
163
+
164
+ const releaseUrl = `https://github.com/${repoOwner}/${repoName}/releases/download/v${pkgVersion}/${info.assetName}`;
165
+ const latestUrl = `https://github.com/${repoOwner}/${repoName}/releases/latest/download/${info.assetName}`;
166
+
167
+ process.stderr.write(`\x1b[36m[figma-mcp]\x1b[0m Downloading binary for ${info.osName}-${info.archName} (v${pkgVersion})...\n`);
168
+
169
+ try {
170
+ await downloadFile(releaseUrl, archivePath);
171
+ } catch (err) {
172
+ process.stderr.write(`\x1b[33m[figma-mcp]\x1b[0m Release v${pkgVersion} not found, trying latest release...\n`);
173
+ try {
174
+ await downloadFile(latestUrl, archivePath);
175
+ } catch (fallbackErr) {
176
+ throw new Error(`Failed to download figma-mcp prebuilt binary:\n - ${err.message}\n - ${fallbackErr.message}\n\nPlease verify your internet connection or build from source: cargo build --release`);
177
+ }
178
+ }
179
+
180
+ try {
181
+ extractArchive(archivePath, cacheDir, info.ext);
182
+ } finally {
183
+ try { fs.unlinkSync(archivePath); } catch (_) {}
184
+ }
185
+
186
+ if (!fs.existsSync(cachedBin)) {
187
+ const files = fs.readdirSync(cacheDir, { recursive: true });
188
+ const found = files.find(f => path.basename(f) === info.binName);
189
+ if (found) {
190
+ const foundPath = path.join(cacheDir, found);
191
+ fs.renameSync(foundPath, cachedBin);
192
+ } else {
193
+ throw new Error(`Binary ${info.binName} not found inside downloaded archive`);
194
+ }
195
+ }
196
+
197
+ if (process.platform !== 'win32') {
198
+ fs.chmodSync(cachedBin, 0o755);
199
+ }
200
+
201
+ process.stderr.write(`\x1b[32m[figma-mcp]\x1b[0m Ready! (cached in ${cacheDir})\n\n`);
202
+ return cachedBin;
203
+ }
204
+
205
+ // ─── SERVICE MANAGEMENT ───────────────────────────────────────────────────────
206
+
207
+ /**
208
+ * Install figma-mcp as a background service that auto-starts on login.
209
+ * - macOS: LaunchAgent plist (~/.config/figma-mcp/launchd.plist β†’ ~/Library/LaunchAgents/)
210
+ * - Linux: systemd user service (~/.config/systemd/user/figma-mcp.service)
211
+ * - Windows: Task Scheduler XML via schtasks
212
+ */
213
+ async function installService(binPath) {
214
+ const platform = process.platform;
215
+ const nodeExec = process.execPath;
216
+ const scriptPath = path.resolve(__filename);
217
+
218
+ if (platform === 'darwin') {
219
+ const plistLabel = 'io.github.figma-mcp.server';
220
+ const plistDir = path.join(os.homedir(), 'Library', 'LaunchAgents');
221
+ const plistPath = path.join(plistDir, `${plistLabel}.plist`);
222
+
223
+ fs.mkdirSync(plistDir, { recursive: true });
224
+
225
+ const logDir = path.join(os.homedir(), 'Library', 'Logs', 'figma-mcp');
226
+ fs.mkdirSync(logDir, { recursive: true });
227
+
228
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
229
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
230
+ <plist version="1.0">
231
+ <dict>
232
+ <key>Label</key>
233
+ <string>${plistLabel}</string>
234
+ <key>ProgramArguments</key>
235
+ <array>
236
+ <string>${binPath}</string>
237
+ </array>
238
+ <key>RunAtLoad</key>
239
+ <true/>
240
+ <key>KeepAlive</key>
241
+ <true/>
242
+ <key>StandardOutPath</key>
243
+ <string>${path.join(logDir, 'stdout.log')}</string>
244
+ <key>StandardErrorPath</key>
245
+ <string>${path.join(logDir, 'stderr.log')}</string>
246
+ <key>ThrottleInterval</key>
247
+ <integer>5</integer>
248
+ </dict>
249
+ </plist>`;
250
+
251
+ fs.writeFileSync(plistPath, plist, 'utf8');
252
+
253
+ // Unload first (ignore error if not loaded)
254
+ try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: 'pipe' }); } catch (_) {}
255
+ execSync(`launchctl load -w "${plistPath}"`, { stdio: 'inherit' });
256
+
257
+ console.log(`\x1b[32mβœ“ figma-mcp service installed!\x1b[0m`);
258
+ console.log(` Plist: ${plistPath}`);
259
+ console.log(` Logs: ${logDir}/stdout.log`);
260
+ console.log(`\n\x1b[36mThe server will now start automatically on every login.\x1b[0m`);
261
+ console.log(`\x1b[36mTo uninstall: npx figma-mcp --uninstall-service\x1b[0m`);
262
+
263
+ } else if (platform === 'linux') {
264
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
265
+ const servicePath = path.join(serviceDir, 'figma-mcp.service');
266
+
267
+ fs.mkdirSync(serviceDir, { recursive: true });
268
+
269
+ const unit = `[Unit]
270
+ Description=Figma MCP Bridge Server
271
+ After=network.target
272
+
273
+ [Service]
274
+ Type=simple
275
+ ExecStart=${binPath}
276
+ Restart=on-failure
277
+ RestartSec=5
278
+
279
+ [Install]
280
+ WantedBy=default.target
281
+ `;
282
+
283
+ fs.writeFileSync(servicePath, unit, 'utf8');
284
+ execSync(`systemctl --user daemon-reload`, { stdio: 'inherit' });
285
+ execSync(`systemctl --user enable --now figma-mcp`, { stdio: 'inherit' });
286
+
287
+ console.log(`\x1b[32mβœ“ figma-mcp systemd user service installed!\x1b[0m`);
288
+ console.log(` Service: ${servicePath}`);
289
+ console.log(`\n\x1b[36mThe server will now start automatically on every login.\x1b[0m`);
290
+ console.log(`\x1b[36mTo uninstall: npx figma-mcp --uninstall-service\x1b[0m`);
291
+
292
+ } else if (platform === 'win32') {
293
+ const taskName = 'FigmaMCPServer';
294
+ // Use schtasks to create a task that runs at login
295
+ const cmd = `schtasks /Create /F /TN "${taskName}" /TR "${binPath}" /SC ONLOGON /RL HIGHEST`;
296
+ execSync(cmd, { stdio: 'inherit' });
297
+
298
+ // Start it immediately too
299
+ try { execSync(`schtasks /Run /TN "${taskName}"`, { stdio: 'pipe' }); } catch (_) {}
300
+
301
+ console.log(`\x1b[32mβœ“ figma-mcp Windows Task installed!\x1b[0m`);
302
+ console.log(` Task: ${taskName}`);
303
+ console.log(`\n\x1b[36mThe server will now start automatically on every login.\x1b[0m`);
304
+ console.log(`\x1b[36mTo uninstall: npx figma-mcp --uninstall-service\x1b[0m`);
305
+
306
+ } else {
307
+ console.error(`\x1b[31mUnsupported platform: ${platform}\x1b[0m`);
308
+ process.exit(1);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Uninstall the background service.
314
+ */
315
+ async function uninstallService() {
316
+ const platform = process.platform;
317
+
318
+ if (platform === 'darwin') {
319
+ const plistLabel = 'io.github.figma-mcp.server';
320
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${plistLabel}.plist`);
321
+
322
+ if (!fs.existsSync(plistPath)) {
323
+ console.log(`\x1b[33mService not found at ${plistPath}\x1b[0m`);
324
+ process.exit(0);
325
+ }
326
+
327
+ try { execSync(`launchctl unload -w "${plistPath}"`, { stdio: 'inherit' }); } catch (_) {}
328
+ fs.unlinkSync(plistPath);
329
+
330
+ console.log(`\x1b[32mβœ“ figma-mcp service uninstalled.\x1b[0m`);
331
+
332
+ } else if (platform === 'linux') {
333
+ try { execSync(`systemctl --user disable --now figma-mcp`, { stdio: 'inherit' }); } catch (_) {}
334
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'figma-mcp.service');
335
+ if (fs.existsSync(servicePath)) fs.unlinkSync(servicePath);
336
+ try { execSync(`systemctl --user daemon-reload`, { stdio: 'inherit' }); } catch (_) {}
337
+
338
+ console.log(`\x1b[32mβœ“ figma-mcp service uninstalled.\x1b[0m`);
339
+
340
+ } else if (platform === 'win32') {
341
+ const taskName = 'FigmaMCPServer';
342
+ try { execSync(`schtasks /Delete /F /TN "${taskName}"`, { stdio: 'inherit' }); } catch (_) {}
343
+
344
+ console.log(`\x1b[32mβœ“ figma-mcp Windows Task removed.\x1b[0m`);
345
+
346
+ } else {
347
+ console.error(`\x1b[31mUnsupported platform: ${platform}\x1b[0m`);
348
+ process.exit(1);
349
+ }
350
+ }
351
+
352
+ /**
353
+ * Check if the service is currently installed.
354
+ */
355
+ function isServiceInstalled() {
356
+ const platform = process.platform;
357
+ if (platform === 'darwin') {
358
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'io.github.figma-mcp.server.plist');
359
+ return fs.existsSync(plistPath);
360
+ } else if (platform === 'linux') {
361
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'figma-mcp.service');
362
+ return fs.existsSync(servicePath);
363
+ } else if (platform === 'win32') {
364
+ try {
365
+ execSync('schtasks /Query /TN "FigmaMCPServer"', { stdio: 'pipe' });
366
+ return true;
367
+ } catch (_) { return false; }
368
+ }
369
+ return false;
370
+ }
371
+
372
+ // ─── MAIN ─────────────────────────────────────────────────────────────────────
373
+
374
+ async function main() {
375
+ const args = process.argv.slice(2);
376
+
377
+ if (args.includes('--install-service')) {
378
+ try {
379
+ const binPath = await findOrDownloadBinary();
380
+ await installService(binPath);
381
+ } catch (err) {
382
+ console.error(`\x1b[31m[figma-mcp error]\x1b[0m ${err.message}`);
383
+ process.exit(1);
384
+ }
385
+ return;
386
+ }
387
+
388
+ if (args.includes('--uninstall-service')) {
389
+ try {
390
+ await uninstallService();
391
+ } catch (err) {
392
+ console.error(`\x1b[31m[figma-mcp error]\x1b[0m ${err.message}`);
393
+ process.exit(1);
394
+ }
395
+ return;
396
+ }
397
+
398
+ if (args.includes('--service-status')) {
399
+ const installed = isServiceInstalled();
400
+ if (installed) {
401
+ console.log('\x1b[32mβœ“ figma-mcp service is installed.\x1b[0m');
402
+ console.log(' To uninstall: npx figma-mcp --uninstall-service');
403
+ } else {
404
+ console.log('\x1b[33mβœ— figma-mcp service is NOT installed.\x1b[0m');
405
+ console.log(' To install: npx figma-mcp --install-service');
406
+ }
407
+ return;
408
+ }
409
+
410
+ try {
411
+ const binPath = await findOrDownloadBinary();
412
+
413
+ const child = spawn(binPath, args, {
414
+ stdio: 'inherit',
415
+ env: process.env,
416
+ });
417
+
418
+ child.on('error', (err) => {
419
+ console.error(`[figma-mcp] Failed to start binary: ${err.message}`);
420
+ process.exit(1);
421
+ });
422
+
423
+ child.on('exit', (code, signal) => {
424
+ if (signal) {
425
+ process.kill(process.pid, signal);
426
+ } else {
427
+ process.exit(code ?? 0);
428
+ }
429
+ });
430
+
431
+ const forwardSignal = (sig) => {
432
+ if (child && !child.killed) {
433
+ child.kill(sig);
434
+ }
435
+ };
436
+
437
+ process.on('SIGINT', () => forwardSignal('SIGINT'));
438
+ process.on('SIGTERM', () => forwardSignal('SIGTERM'));
439
+ } catch (err) {
440
+ console.error(`\x1b[31m[figma-mcp error]\x1b[0m ${err.message}`);
441
+ process.exit(1);
442
+ }
443
+ }
444
+
445
+ main();