figma-mcp-setup 1.0.0

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/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # Figma MCP Setup
2
+
3
+ <div align="center">
4
+ <img src="https://img.shields.io/npm/v/figma-mcp-setup" alt="NPM Version" />
5
+ <img src="https://img.shields.io/badge/UI-Clack-blue?color=0023d7" alt="Clack UI" />
6
+ <img src="https://img.shields.io/badge/Security-Strict-success" alt="Security Validated" />
7
+ </div>
8
+
9
+ <br />
10
+
11
+ **If you use `figma-mcp-setup`, you will instantly secure and scaffold your AI agent's connection to Figma's Dev Mode without manual configuration or risking credential theft.**
12
+
13
+ > **Created by Muhaideen Nausar**
14
+
15
+ ---
16
+
17
+ ## 🚀 The Quick Win: Getting Started
18
+
19
+ ### The Pitch
20
+ `figma-mcp-setup` is a secure, interactive CLI tool that bootstraps a Figma Model Context Protocol (MCP) server for your AI agents. It handles the secure storage of your Personal Access Token (PAT) and configuration mapping in seconds, so you can focus on building agents, not configuring them.
21
+
22
+ ### Installation
23
+ You need **Node.js v18+** installed. You can run the CLI directly without installing it globally:
24
+
25
+ ```bash
26
+ npx figma-mcp-setup
27
+ ```
28
+
29
+ ### The "Hello World" Example
30
+ To get your AI agent ready to talk to Figma:
31
+ 1. Open your terminal in your project's root folder.
32
+ 2. Run `npx figma-mcp-setup`.
33
+ 3. Paste your Figma Personal Access Token when prompted.
34
+ 4. The CLI will automatically create a secure `.agents/mcp_config.json` and update your `.gitignore`.
35
+
36
+ ### Key Limitations & Prerequisites
37
+ - **API Key Required**: You must have a Figma Personal Access Token (PAT).
38
+
39
+ **How to get your Figma PAT:**
40
+ 1. Log in to your Figma account on the web or desktop app.
41
+ 2. Click your profile picture in the top-left corner and select **Settings**.
42
+ 3. Navigate to the **Personal access tokens** section.
43
+ 4. Click **Generate new token**.
44
+ 5. Give it a name (e.g., "AI Agent MCP") and ensure it has the appropriate read permissions for your files.
45
+ 6. Copy the generated token immediately. It will start with `figd_`.
46
+
47
+ ---
48
+
49
+ ## 📚 The Mechanics: Reference Manual
50
+
51
+ While primarily a CLI tool, `figma-mcp-setup` is built with modularity in mind. Below is the API reference for the core internal mechanics, useful for developers wishing to contribute or extend the package programmatically.
52
+
53
+ ### `initCommand()`
54
+ - **Description**: The primary entry point for the CLI tool. Orchestrates the prompts, file system hardening, and config generation.
55
+ - **Parameters**: None.
56
+ - **Return Value**: `Promise<void>`.
57
+ - **Error Handling**: Catches internal errors, stops the UI spinner, and gracefully exits the process with a clean `cancel()` message to the terminal.
58
+
59
+ ### `promptForToken()`
60
+ - **Description**: Securely prompts the user for their Figma PAT using masked terminal inputs.
61
+ - **Parameters**: None.
62
+ - **Return Value**: `Promise<string>` - Resolves to the validated token string.
63
+ - **Error Handling**: Returns validation errors in the UI if the token is empty or does not strictly match the `^figd_[a-zA-Z0-9\-_]+$` regex.
64
+
65
+ ### `mergeFigmaConfig(configPath: string, token: string)`
66
+ - **Description**: Merges the Figma MCP configuration into the target JSON file without overwriting existing unrelated server configurations.
67
+ - **Parameters**:
68
+ - `configPath` (type: `string`, required): Absolute path to the `mcp_config.json` file.
69
+ - `token` (type: `string`, required): The validated Figma PAT.
70
+ - **Return Value**: `void`.
71
+ - **Error Handling**: Throws a security error if the target path is a symlink (preventing arbitrary file overwrite). Throws a `SyntaxError` if the existing JSON is corrupted.
72
+
73
+ ### `ensureAgentsFolder(cwd: string)`
74
+ - **Description**: Scaffolds the `.agents` folder and enforces strict POSIX security.
75
+ - **Parameters**:
76
+ - `cwd` (type: `string`, required): The current working directory.
77
+ - **Return Value**: `void`.
78
+ - **Error Handling**: Throws if `.agents` is a symlink or if it exists but is not a directory. Safely catches `ENOENT` to create the directory with `0o700` permissions.
79
+
80
+ ---
81
+
82
+ ## ⚙️ How the MCP Server JSON Works (Under the Hood)
83
+
84
+ You might be wondering: *"How does generating a JSON file actually run a server?"*
85
+
86
+ When `figma-mcp-setup` completes, it generates a `.agents/mcp_config.json` file that looks something like this:
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "figma-dev-mode-mcp-server": {
92
+ "command": "npx",
93
+ "args": ["-y", "figma-developer-mcp", "--stdio"],
94
+ "env": {
95
+ "FIGMA_API_KEY": "figd_YourSecureTokenHere"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ Here is exactly what happens when your AI Agent (the **MCP Client**) reads this file:
103
+
104
+ 1. **The Execution**: The AI agent sees the `"command": "npx"` and uses Node.js to dynamically download and execute the `figma-developer-mcp` package. *(Note: This is an external third-party package maintained by the community, not part of this CLI tool.)*
105
+ 2. **The Connection (`--stdio`)**: It launches this package as a background child process and communicates with it via standard input and output streams (`stdio`).
106
+ 3. **The Authentication**: It securely passes your `FIGMA_API_KEY` into the environment variables of that child process.
107
+ 4. **The Interaction**: When the AI agent wants to read a Figma file, it sends a JSON-RPC message over `stdio`. The `figma-developer-mcp` server receives the message, uses your PAT to make a REST API call to Figma's servers, and returns the real-time Dev Mode data back to the AI.
108
+
109
+ ---
110
+
111
+ ## 💡 Advanced Use Cases & Tutorials
112
+
113
+ ### How to Automate Figma AI Agent Setup
114
+ If you are deploying a template repository for AI agents, you can instruct your users to run `npx figma-mcp-setup` as part of your repository setup scripts.
115
+ 1. Add `"setup": "npx figma-mcp-setup"` to your `package.json` scripts.
116
+ 2. When developers clone your repository and run `npm run setup`, the CLI will automatically trigger, prompting them for their Figma PAT.
117
+ 3. The secure `.agents/mcp_config.json` is instantly generated and ready for your local LLM runner (like Claude Desktop) to consume.
118
+
119
+ ### Best Practice: Integrating the MCP Config into Your AI Agents
120
+ Once `figma-mcp-setup` completes, it generates an `mcp_config.json` file.
121
+ **Best Practice:** Do not read this file directly into your application bundle or version control. Instead, configure your MCP client (like Claude Desktop or a custom LangChain agent) to point directly to the `.agents/mcp_config.json` file path. The CLI has already secured this file with strict `0o600` permissions, ensuring only the active system user can read it.
122
+
123
+ ---
124
+
125
+ ## 🩹 Maintenance & Ecosystem
126
+
127
+ ### FAQ & Troubleshooting
128
+
129
+ **Q: I'm getting an "Invalid token format" error.**
130
+ - **Cause**: You pasted an OAuth token or an invalid string.
131
+ - **Fix**: Ensure your token was generated directly from Figma's "Personal access tokens" settings. It must begin with exactly `figd_`.
132
+
133
+ **Q: I see a "Security Alert: .agents is a symlink" error.**
134
+ - **Cause**: The CLI detected that `.agents` is a symbolic link. This is a severe security risk.
135
+ - **Fix**: Delete the existing `.agents` symlink (`rm -rf .agents`) and run the setup again to let the CLI create a secure, genuine directory.
136
+
137
+ ### Contributing
138
+ We welcome contributions! To get started:
139
+ 1. Clone the repository and run `npm install`.
140
+ 2. The codebase is written in TypeScript. Core logic is split between `src/commands` and `src/services`.
141
+ 3. To test your changes locally, run `npm run build` followed by `node dist/index.js`.
142
+ 4. **Security Policy**: Please ensure all new file system operations include strict symlink checks and permission constraints (e.g., `0o600` for files).
143
+
144
+ ### Versioning
145
+ We follow Semantic Versioning (SemVer). Breaking changes will be clearly documented in the GitHub Releases page. Every major version (e.g., v2.0) will include a "Breaking Changes" guide so users are never surprised.
package/bin/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,38 @@
1
+ import { appendToGitignore, ensureAgentsFolder, getConfigPath, configExists, mergeFigmaConfig } from '../services/fileSystem.js';
2
+ import { promptForToken, promptForMerge } from '../services/prompts.js';
3
+ import { intro, outro, spinner, cancel, note } from '@clack/prompts';
4
+ import chalk from 'chalk';
5
+ import figlet from 'figlet';
6
+ export async function initCommand() {
7
+ const brandColor = chalk.hex('#0023d7');
8
+ console.clear();
9
+ console.log('\n' + brandColor(figlet.textSync('Figma MCP')));
10
+ console.log(brandColor(' by Muhaideen Nausar\n'));
11
+ intro(chalk.inverse(' Setup Figma MCP Server '));
12
+ const cwd = process.cwd();
13
+ const token = await promptForToken();
14
+ const configPath = getConfigPath(cwd);
15
+ if (configExists(configPath)) {
16
+ const shouldMerge = await promptForMerge();
17
+ if (!shouldMerge) {
18
+ cancel('Setup aborted by user.');
19
+ process.exit(0);
20
+ }
21
+ }
22
+ const s = spinner();
23
+ s.start('Configuring secure environment');
24
+ try {
25
+ ensureAgentsFolder(cwd);
26
+ mergeFigmaConfig(configPath, token);
27
+ appendToGitignore(cwd);
28
+ s.stop('Environment configured successfully');
29
+ }
30
+ catch (error) {
31
+ s.stop('Failed to configure environment');
32
+ cancel(error.message);
33
+ process.exit(1);
34
+ }
35
+ note('Figma MCP Server configuration added securely!\n' +
36
+ 'The .agents directory is isolated and ignored by git.', 'Success');
37
+ outro(brandColor('You are all set!'));
38
+ }
@@ -0,0 +1,11 @@
1
+ export const FIGMA_MCP_CONFIG = {
2
+ "command": "npx",
3
+ "args": [
4
+ "-y",
5
+ "figma-developer-mcp",
6
+ "--stdio"
7
+ ],
8
+ "env": {
9
+ "FIGMA_API_KEY": "YOUR_TOKEN_HERE"
10
+ }
11
+ };
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { initCommand } from './commands/init.js';
2
+ import { cancel } from '@clack/prompts';
3
+ async function main() {
4
+ try {
5
+ await initCommand();
6
+ }
7
+ catch (error) {
8
+ cancel(error instanceof Error ? error.message : String(error));
9
+ process.exit(1);
10
+ }
11
+ }
12
+ main();
@@ -0,0 +1,100 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { FIGMA_MCP_CONFIG } from '../constants/configTemplate.js';
4
+ export function appendToGitignore(cwd) {
5
+ const gitignorePath = path.join(cwd, '.gitignore');
6
+ try {
7
+ const stats = fs.lstatSync(gitignorePath);
8
+ if (stats.isSymbolicLink()) {
9
+ console.warn('Security Warning: .gitignore is a symlink. Skipping modification to prevent arbitrary file overwrite.');
10
+ return;
11
+ }
12
+ const content = fs.readFileSync(gitignorePath, 'utf-8');
13
+ const lines = content.split(/\r?\n/).map(l => l.trim());
14
+ const isIgnored = lines.some(l => l === '.agents' || l === '.agents/' || l === '/.agents' || l === '/.agents/');
15
+ if (!isIgnored) {
16
+ const trailingNewline = content.length === 0 || content.endsWith('\n') || content.endsWith('\r');
17
+ const toAppend = trailingNewline ? '.agents\n' : '\n.agents\n';
18
+ fs.appendFileSync(gitignorePath, toAppend);
19
+ }
20
+ }
21
+ catch (e) {
22
+ if (e.code === 'ENOENT') {
23
+ fs.writeFileSync(gitignorePath, '.agents\n');
24
+ }
25
+ else {
26
+ throw e;
27
+ }
28
+ }
29
+ }
30
+ export function ensureAgentsFolder(cwd) {
31
+ const agentsPath = path.join(cwd, '.agents');
32
+ try {
33
+ const stats = fs.lstatSync(agentsPath);
34
+ if (stats.isSymbolicLink()) {
35
+ throw new Error('Security Alert: .agents is a symlink. Path traversal attack prevented.');
36
+ }
37
+ if (!stats.isDirectory()) {
38
+ throw new Error('Security Alert: .agents exists but is not a directory.');
39
+ }
40
+ // Restrict permissions even if folder already existed
41
+ fs.chmodSync(agentsPath, 0o700);
42
+ }
43
+ catch (e) {
44
+ if (e.code === 'ENOENT') {
45
+ fs.mkdirSync(agentsPath, { recursive: true, mode: 0o700 });
46
+ // chmodSync ensures the 0o700 mode is properly applied regardless of umask
47
+ fs.chmodSync(agentsPath, 0o700);
48
+ }
49
+ else {
50
+ throw e;
51
+ }
52
+ }
53
+ }
54
+ export function getConfigPath(cwd) {
55
+ return path.join(cwd, '.agents', 'mcp_config.json');
56
+ }
57
+ export function configExists(configPath) {
58
+ try {
59
+ const stats = fs.lstatSync(configPath);
60
+ return stats.isFile(); // Ignore symlinks or directories
61
+ }
62
+ catch (e) {
63
+ return false;
64
+ }
65
+ }
66
+ export function mergeFigmaConfig(configPath, token) {
67
+ let config = { mcpServers: {} };
68
+ try {
69
+ const stats = fs.lstatSync(configPath);
70
+ if (stats.isSymbolicLink()) {
71
+ throw new Error('Security Alert: mcp_config.json is a symlink. Path traversal attack prevented.');
72
+ }
73
+ const content = fs.readFileSync(configPath, 'utf-8');
74
+ config = JSON.parse(content);
75
+ if (!config.mcpServers) {
76
+ config.mcpServers = {};
77
+ }
78
+ }
79
+ catch (e) {
80
+ if (e.code === 'ENOENT') {
81
+ // File doesn't exist, proceed with empty config
82
+ }
83
+ else if (e.name === 'SyntaxError') {
84
+ console.error('Error parsing existing mcp_config.json. Overwriting might corrupt data.');
85
+ throw new Error('Failed to parse existing mcp_config.json');
86
+ }
87
+ else {
88
+ throw e; // Bubble up unexpected errors
89
+ }
90
+ }
91
+ // Inject token into template
92
+ const figmaConfig = JSON.parse(JSON.stringify(FIGMA_MCP_CONFIG));
93
+ figmaConfig.env.FIGMA_API_KEY = token;
94
+ // Merge
95
+ config.mcpServers['figma-dev-mode-mcp-server'] = figmaConfig;
96
+ // Write back securely
97
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
98
+ // chmodSync ensures correct permissions if file already existed with wider permissions
99
+ fs.chmodSync(configPath, 0o600);
100
+ }
@@ -0,0 +1,30 @@
1
+ import { password, confirm, isCancel, cancel } from '@clack/prompts';
2
+ export async function promptForToken() {
3
+ const token = await password({
4
+ message: 'Enter your Figma Personal Access Token (PAT):',
5
+ validate: (input) => {
6
+ if (!input || input.trim() === '') {
7
+ return 'Token cannot be empty.';
8
+ }
9
+ if (!/^figd_[a-zA-Z0-9\-_]+$/.test(input.trim())) {
10
+ return 'Invalid token format. Figma PATs must start with "figd_" followed by alphanumeric characters.';
11
+ }
12
+ }
13
+ });
14
+ if (isCancel(token)) {
15
+ cancel('Operation cancelled.');
16
+ process.exit(0);
17
+ }
18
+ return token.trim();
19
+ }
20
+ export async function promptForMerge() {
21
+ const shouldMerge = await confirm({
22
+ message: 'Found existing mcp_config.json. Merge Figma configuration into it?',
23
+ initialValue: true
24
+ });
25
+ if (isCancel(shouldMerge)) {
26
+ cancel('Operation cancelled.');
27
+ process.exit(0);
28
+ }
29
+ return shouldMerge;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "figma-mcp-setup",
3
+ "version": "1.0.0",
4
+ "description": "Interactive CLI tool to instantly scaffold a secure Figma MCP server configuration for your AI agents.",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "figma-mcp-setup": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build",
18
+ "test": "vitest run"
19
+ },
20
+ "keywords": [
21
+ "figma",
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "cli",
25
+ "ai",
26
+ "agent"
27
+ ],
28
+ "author": "Muhaideen Nausar",
29
+ "license": "ISC",
30
+ "dependencies": {
31
+ "@clack/prompts": "^1.6.0",
32
+ "chalk": "^5.6.2",
33
+ "figlet": "^1.11.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/figlet": "^1.7.0",
37
+ "@types/node": "^26.1.0",
38
+ "typescript": "^6.0.3",
39
+ "vitest": "^4.1.9"
40
+ }
41
+ }