figma-mcp-downloader 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yuichiroh Arai
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,117 @@
1
+ # figma-mcp-downloader
2
+
3
+ CLI for saving Figma MCP tool results to local files.
4
+
5
+ > [!IMPORTANT]
6
+ > This documentation is written exclusively for AI agents with access to the Figma MCP tool schema.
7
+
8
+ ## Prerequisites
9
+
10
+ - Figma **Desktop** app must be running (Local MCP server starts automatically)
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install -g figma-mcp-downloader
16
+ ```
17
+
18
+ Or run directly with `npx`:
19
+
20
+ ```bash
21
+ npx figma-mcp-downloader <subcommand> [options]
22
+ ```
23
+
24
+ ## Subcommands and MCP Tool Mapping
25
+
26
+ Subcommands share the same names as MCP tools.
27
+
28
+ | Subcommand | MCP Tool |
29
+ | -------------------- | -------------------- |
30
+ | `get_design_context` | `get_design_context` |
31
+ | `get_metadata` | `get_metadata` |
32
+
33
+ > [!NOTE]
34
+ > Other Figma MCP tools (e.g., `get_screenshot`, `get_variable_defs`) are not currently supported by this CLI.
35
+
36
+ ```bash
37
+ npx figma-mcp-downloader [subcommand] <output-file> [options]
38
+ ```
39
+
40
+ ## Common Arguments/Options
41
+
42
+ > [!IMPORTANT]
43
+ > Options without descriptions are identical to the corresponding Figma MCP tool parameters. Refer to the Figma MCP tool schema for details.
44
+ > The MCP response contains multiple content items: the first is the generated JSX code or XML, and the rest are supplementary AI guidance. Use `-c` to extract only the JSX code or XML.
45
+
46
+ | Argument/Option | Figma MCP Parameter | Description |
47
+ | -------------------------------------- | ------------------- | ------------------------------------------------------------------- |
48
+ | `<output-file>` | - | Output file path |
49
+ | `-c, --content-only` | - | Save only the first text content (JSX code or XML) |
50
+ | `-i, --node-id <id>` | `nodeId` | The ID of the node in the Figma document. |
51
+ | `-l, --client-languages <languages>` | `clientLanguages` | A comma separated list of programming languages used by the client. |
52
+ | `-f, --client-frameworks <frameworks>` | `clientFrameworks` | A comma separated list of frameworks used by the client |
53
+
54
+ > [!NOTE]
55
+ > Both `-l` and `-f` options are used for logging purposes to understand which languages and frameworks are being used. Specify them based on available context, but omit them if unsure.
56
+
57
+ ## get_design_context
58
+
59
+ Get the design context for a layer or selection and save the full JSON response from the MCP tool to a file.
60
+
61
+ ```bash
62
+ npx figma-mcp-downloader get_design_context <output-file> [options]
63
+ ```
64
+
65
+ > [!IMPORTANT]
66
+ > This CLI always sets `forceCode` to `true` when calling the MCP tool.
67
+
68
+ | Argument/Option | Figma MCP Parameter | Description |
69
+ | ---------------------------- | ------------------- | ------------------------------------------------------- |
70
+ | `-a, --artifact-type <type>` | `artifactType` | The type of artifact the user is creating or modifying. |
71
+ | `-t, --task-type <type>` | `taskType` | The type of task being performed. |
72
+
73
+ > [!NOTE]
74
+ > Valid values for `-a`: `WEB_PAGE_OR_APP_SCREEN`, `COMPONENT_WITHIN_A_WEB_PAGE_OR_APP_SCREEN`, `REUSABLE_COMPONENT`, `DESIGN_SYSTEM`
75
+ > Valid values for `-t`: `CREATE_ARTIFACT`, `CHANGE_ARTIFACT`, `DELETE_ARTIFACT`
76
+
77
+ ### Examples
78
+
79
+ ```bash
80
+ # With -c: saves JSX only
81
+ npx figma-mcp-downloader get_design_context design_context.jsx -i "123:456" -c -l typescript -f react,tailwindcss
82
+ # Without -c: saves full JSON response
83
+ npx figma-mcp-downloader get_design_context design_context.json -i "123:456" -l html,css,javascript -f vue
84
+ ```
85
+
86
+ ## get_metadata
87
+
88
+ Get the sparse XML representation for a layer or selection and save the full JSON response from the MCP tool to a file.
89
+
90
+ ```bash
91
+ npx figma-mcp-downloader get_metadata <output-file> [options]
92
+ ```
93
+
94
+ ### Examples
95
+
96
+ ```bash
97
+ # With -c: saves XML only
98
+ npx figma-mcp-downloader get_metadata metadata.xml -i "123:456" -c -l typescript -f react,tailwindcss
99
+ # Without -c: saves full JSON response
100
+ npx figma-mcp-downloader get_metadata metadata.json -i "123:456" -l html,css,javascript -f vue
101
+ ```
102
+
103
+ ## Environment Variables
104
+
105
+ The MCP server URL can be configured via environment variables.
106
+
107
+ | Variable | Description | Default |
108
+ | --------------- | -------------- | --------------------------- |
109
+ | `FIGMA_MCP_URL` | MCP server URL | `http://127.0.0.1:3845/mcp` |
110
+
111
+ ```bash
112
+ # PowerShell
113
+ $env:FIGMA_MCP_URL="http://localhost:4000/mcp"; npx figma-mcp-downloader get_design_context design_context.jsx -c
114
+
115
+ # Linux/Mac
116
+ FIGMA_MCP_URL="http://localhost:4000/mcp" npx figma-mcp-downloader get_metadata metadata.xml -c
117
+ ```
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { registerGetDesignContextCommand } from "./get_design_context.js";
4
+ import { registerGetMetadataCommand } from "./get_metadata.js";
5
+ const program = new Command();
6
+ program
7
+ .name("figma-mcp-downloader")
8
+ .description("CLI tool to download design data from Figma MCP Server")
9
+ .version("0.1.0");
10
+ registerGetDesignContextCommand(program);
11
+ registerGetMetadataCommand(program);
12
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerGetDesignContextCommand(program: Command): void;
@@ -0,0 +1,66 @@
1
+ import { addCommonOptions, closeMcpTransport, createMcpClient, handleError, saveResult, } from "./utils.js";
2
+ const VALID_ARTIFACT_TYPES = [
3
+ "WEB_PAGE_OR_APP_SCREEN",
4
+ "COMPONENT_WITHIN_A_WEB_PAGE_OR_APP_SCREEN",
5
+ "REUSABLE_COMPONENT",
6
+ "DESIGN_SYSTEM",
7
+ ];
8
+ const VALID_TASK_TYPES = [
9
+ "CREATE_ARTIFACT",
10
+ "CHANGE_ARTIFACT",
11
+ "DELETE_ARTIFACT",
12
+ ];
13
+ export function registerGetDesignContextCommand(program) {
14
+ addCommonOptions(program
15
+ .command("get_design_context")
16
+ .description("Get the design context for a layer or selection and save the full JSON response from the MCP tool to a file.")
17
+ .argument("<output-file>", "Output file path, e.g., design_context.jsx, design_context.json")
18
+ .option("-a, --artifact-type <type>", `The type of artifact the user is creating or modifying. Valid values: ${VALID_ARTIFACT_TYPES.join(", ")}`)
19
+ .option("-t, --task-type <type>", `The type of task being performed. Valid values: ${VALID_TASK_TYPES.join(", ")}`)).action(async (outputFile, options) => {
20
+ const nodeId = options.nodeId;
21
+ // Validate artifactType
22
+ if (options.artifactType &&
23
+ !VALID_ARTIFACT_TYPES.includes(options.artifactType)) {
24
+ console.error(`❌ Error: Invalid artifact type: ${options.artifactType}`);
25
+ console.log("\nValid artifact types:");
26
+ VALID_ARTIFACT_TYPES.forEach((type) => console.log(` - ${type}`));
27
+ process.exit(1);
28
+ }
29
+ // Validate taskType
30
+ if (options.taskType &&
31
+ !VALID_TASK_TYPES.includes(options.taskType)) {
32
+ console.error(`❌ Error: Invalid task type: ${options.taskType}`);
33
+ console.log("\nValid task types:");
34
+ VALID_TASK_TYPES.forEach((type) => console.log(` - ${type}`));
35
+ process.exit(1);
36
+ }
37
+ const { client, transport } = await createMcpClient("design-context");
38
+ try {
39
+ console.log(`\n🎨 Getting design context for Node ID: ${nodeId || "(selected)"}...`);
40
+ const result = await client.callTool({
41
+ name: "get_design_context",
42
+ arguments: {
43
+ nodeId: nodeId,
44
+ forceCode: true,
45
+ ...(options.artifactType && {
46
+ artifactType: options.artifactType,
47
+ }),
48
+ ...(options.taskType && { taskType: options.taskType }),
49
+ ...(options.clientLanguages && {
50
+ clientLanguages: options.clientLanguages,
51
+ }),
52
+ ...(options.clientFrameworks && {
53
+ clientFrameworks: options.clientFrameworks,
54
+ }),
55
+ },
56
+ });
57
+ saveResult(result, outputFile, options.contentOnly ?? false, nodeId || "(selected)", options.force ?? false);
58
+ }
59
+ catch (error) {
60
+ handleError(error);
61
+ }
62
+ finally {
63
+ await closeMcpTransport(transport);
64
+ }
65
+ });
66
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerGetMetadataCommand(program: Command): void;
@@ -0,0 +1,32 @@
1
+ import { addCommonOptions, closeMcpTransport, createMcpClient, handleError, saveResult, } from "./utils.js";
2
+ export function registerGetMetadataCommand(program) {
3
+ addCommonOptions(program
4
+ .command("get_metadata")
5
+ .description("Get the sparse XML representation for a layer or selection and save the full JSON response from the MCP tool to a file.")
6
+ .argument("<output-file>", "Output file path, e.g., metadata.xml, metadata.json")).action(async (outputFile, options) => {
7
+ const nodeId = options.nodeId;
8
+ const { client, transport } = await createMcpClient("metadata");
9
+ try {
10
+ console.log(`\n📋 Getting metadata for Node ID: ${nodeId || "(selected)"}...`);
11
+ const result = await client.callTool({
12
+ name: "get_metadata",
13
+ arguments: {
14
+ ...(nodeId && { nodeId: nodeId }),
15
+ ...(options.clientLanguages && {
16
+ clientLanguages: options.clientLanguages,
17
+ }),
18
+ ...(options.clientFrameworks && {
19
+ clientFrameworks: options.clientFrameworks,
20
+ }),
21
+ },
22
+ });
23
+ saveResult(result, outputFile, options.contentOnly ?? false, nodeId || "(selected)", options.force ?? false);
24
+ }
25
+ catch (error) {
26
+ handleError(error);
27
+ }
28
+ finally {
29
+ await closeMcpTransport(transport);
30
+ }
31
+ });
32
+ }
@@ -0,0 +1,7 @@
1
+ export interface ContentItem {
2
+ type: string;
3
+ text?: string;
4
+ }
5
+ export interface ToolResult {
6
+ content: ContentItem[];
7
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import { Command } from "commander";
4
+ import { ToolResult } from "./types.js";
5
+ export declare const FIGMA_MCP_URL: string;
6
+ export declare function createMcpClient(scriptName: string): Promise<{
7
+ client: Client;
8
+ transport: StreamableHTTPClientTransport;
9
+ }>;
10
+ export declare function closeMcpTransport(transport: StreamableHTTPClientTransport): Promise<void>;
11
+ export declare function saveResult(result: ToolResult, outputFile: string, contentOnly: boolean, nodeIdLabel: string, force?: boolean): boolean;
12
+ export declare function handleError(error: unknown): void;
13
+ export interface CommonOptions {
14
+ nodeId?: string;
15
+ clientLanguages: string;
16
+ clientFrameworks: string;
17
+ contentOnly?: boolean;
18
+ force?: boolean;
19
+ }
20
+ /**
21
+ * 共通オプションをコマンドに追加する
22
+ */
23
+ export declare function addCommonOptions(command: Command): Command;
package/dist/utils.js ADDED
@@ -0,0 +1,84 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ // Requires Figma desktop app to be running
6
+ // Can be overridden with FIGMA_MCP_URL environment variable
7
+ export const FIGMA_MCP_URL = process.env.FIGMA_MCP_URL || "http://127.0.0.1:3845/mcp";
8
+ export async function createMcpClient(scriptName) {
9
+ const transport = new StreamableHTTPClientTransport(new URL(FIGMA_MCP_URL));
10
+ const client = new Client({ name: `${scriptName}-script`, version: "1.0.0" }, { capabilities: {} });
11
+ console.log("🔌 Connecting to Figma MCP Server...");
12
+ await client.connect(transport);
13
+ console.log("✅ Connected!");
14
+ return { client, transport };
15
+ }
16
+ export async function closeMcpTransport(transport) {
17
+ try {
18
+ await transport.close();
19
+ console.log("\n🔌 Disconnected from server");
20
+ }
21
+ catch {
22
+ // Ignore close errors
23
+ }
24
+ }
25
+ export function saveResult(result, outputFile, contentOnly, nodeIdLabel, force = false) {
26
+ const savePath = path.resolve(process.cwd(), outputFile);
27
+ const cwd = process.cwd();
28
+ // Prevent path traversal attacks: ensure savePath is within cwd
29
+ // Skip check if --force is used
30
+ if (!force) {
31
+ const relativePath = path.relative(cwd, savePath);
32
+ if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
33
+ console.error(`❌ Error: Output path must be within the current working directory.`);
34
+ console.error(` Attempted path: ${savePath}`);
35
+ console.error(` Current directory: ${cwd}`);
36
+ console.error(` Use --force to bypass this check only if explicitly permitted by the user.`);
37
+ process.exitCode = 1;
38
+ return false;
39
+ }
40
+ }
41
+ const dir = path.dirname(savePath);
42
+ // Create directory if it doesn't exist
43
+ if (!fs.existsSync(dir)) {
44
+ fs.mkdirSync(dir, { recursive: true });
45
+ }
46
+ let saved = false;
47
+ if (contentOnly) {
48
+ // --content-only: Find the first type="text" item in the array and save it
49
+ for (const item of result.content) {
50
+ if (item.type === "text" && item.text) {
51
+ fs.writeFileSync(savePath, item.text, "utf-8");
52
+ console.log(`💾 Saved: ${savePath}`);
53
+ saved = true;
54
+ break;
55
+ }
56
+ }
57
+ if (!saved) {
58
+ console.warn(`⚠️ No text data returned for ${nodeIdLabel}`);
59
+ process.exitCode = 1;
60
+ }
61
+ }
62
+ else {
63
+ // Default: Save the full JSON response
64
+ fs.writeFileSync(savePath, JSON.stringify(result, null, 2), "utf-8");
65
+ console.log(`💾 Saved: ${savePath}`);
66
+ saved = true;
67
+ }
68
+ return saved;
69
+ }
70
+ export function handleError(error) {
71
+ console.error("❌ Error:", error instanceof Error ? error.message : error);
72
+ process.exitCode = 1;
73
+ }
74
+ /**
75
+ * 共通オプションをコマンドに追加する
76
+ */
77
+ export function addCommonOptions(command) {
78
+ return command
79
+ .option("-i, --node-id <id>", 'The ID of the node in the Figma document, eg. "123:456" or "123-456"')
80
+ .option("-c, --content-only", "Save only the first text content, not the full JSON response")
81
+ .option("-l, --client-languages <languages>", "A comma separated list of programming languages used by the client in the current context", "unknown")
82
+ .option("-f, --client-frameworks <frameworks>", "A comma separated list of frameworks used by the client in the current context", "unknown")
83
+ .option("--force", "Allow writing outside the current working directory. Do not use unless explicitly permitted by the user.");
84
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "figma-mcp-downloader",
3
+ "version": "1.0.0",
4
+ "description": "CLI for saving Figma MCP tool results to local files",
5
+ "author": "Yuichiroh Arai",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE"
12
+ ],
13
+ "bin": {
14
+ "figma-mcp-downloader": "dist/cli.js"
15
+ },
16
+ "scripts": {
17
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
18
+ "dev": "bun --watch src/cli.ts"
19
+ },
20
+ "keywords": [
21
+ "figma",
22
+ "mcp",
23
+ "cli",
24
+ "design",
25
+ "download",
26
+ "model-context-protocol"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20.0.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/yuichiroharai/figma-mcp-downloader.git"
34
+ },
35
+ "homepage": "https://github.com/yuichiroharai/figma-mcp-downloader#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/yuichiroharai/figma-mcp-downloader/issues"
38
+ },
39
+ "dependencies": {
40
+ "@modelcontextprotocol/sdk": "^1.24.1",
41
+ "commander": "^14.0.2"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^9.39.2",
45
+ "@types/node": "^24.10.1",
46
+ "eslint": "^9.39.2",
47
+ "globals": "^16.5.0",
48
+ "prettier": "^3.7.4",
49
+ "prettier-plugin-organize-imports": "^4.3.0",
50
+ "typescript": "^5.9.3",
51
+ "typescript-eslint": "^8.49.0"
52
+ },
53
+ "volta": {
54
+ "node": "24.12.0"
55
+ }
56
+ }