Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rotten-islands-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"figma-flutter-mcp": patch
---

Figma API key logic improved for API access
14 changes: 0 additions & 14 deletions .env.example

This file was deleted.

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<br>

<h1>Figma to Flutter MCP Server [Early]</h1>
<h1>Figma to Flutter MCP Server</h1>
<h3>Utilize Figma's rich data in your coding agent.<br/>Implement designs in Flutter way!</h3>
<a href="https://npmcharts.com/compare/figma-flutter-mcp?interval=30">
<img alt="weekly downloads" src="https://img.shields.io/npm/dm/figma-flutter-mcp.svg">
Expand All @@ -19,8 +19,8 @@

Use [Cursor](https://cursor.sh) or other AI-powered tools to access Figma's rich files, data, components and much more using [MCP server](https://modelcontextprotocol.io/).

## 📝 Early Release
Since this is my first time building MCP servers so marking this as ‘early,’ which means you might encounter bugs or unexpected behavior. As there's always a room for improvements so you can checkout the [issues](https://github.com/mhmzdev/figma-flutter-mcp/issues) to see what else there's to work or to improve.
## 📝 First Release
Since this is my first time building MCP servers which means you might encounter bugs or unexpected behavior. As there's always a room for improvements so you can checkout the [issues](https://github.com/mhmzdev/figma-flutter-mcp/issues) to see what else there's to work or to improve.

## 📚 How it works
1. [Components/Widgets](src/extractors/components/)
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.4",
"description": "MCP server for Figma to Flutter conversion",
"type": "module",
"main": "dist/server.mjs",
"main": "dist/cli.mjs",
"bin": {
"figma-flutter-mcp": "dist/cli.mjs"
},
Expand All @@ -14,8 +14,8 @@
],
"scripts": {
"build": "tsc",
"start": "node dist/server.mjs",
"dev": "tsx src/server.mts",
"start": "node dist/cli.mjs",
"dev": "tsx src/cli.mts",
"changeset": "changeset add",
"version": "changeset version && npm install --lockfile-only",
"release": "changeset publish"
Expand Down
44 changes: 10 additions & 34 deletions src/cli.mts
Original file line number Diff line number Diff line change
@@ -1,43 +1,19 @@
#!/usr/bin/env node

import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
import {resolve} from 'path';
import {program} from 'commander';

// Parse CLI arguments
program
.name('figma-flutter-mcp')
.description('Figma to Flutter MCP Server')
.option('--figma-api-key <key>', 'Figma API key')
.option('--stdio', 'Run in stdio mode for MCP client communication')
.parse();

const options = program.opts();

// Set environment variables from CLI args
if (options.figmaApiKey) {
process.env.FIGMA_API_KEY = options.figmaApiKey;
}

// Check if running in stdio mode (same pattern as working MCP)
const isStdioMode = process.env.NODE_ENV === "cli" || process.argv.includes("--stdio");
import {getServerConfig} from './config.mjs';
import {startMcpServer} from './server.mjs';

async function startServer(): Promise<void> {
if (isStdioMode) {
// Import and start MCP server in stdio mode
const {startMcpServer} = await import('./server.mjs');
await startMcpServer();
const config = getServerConfig();

if (config.isStdioMode) {
await startMcpServer(config.figmaApiKey);
} else {
console.log('Starting Figma Flutter MCP Server...');
console.log('Use --stdio flag for MCP client communication');
console.log('Example: figma-flutter-mcp --figma-api-key=YOUR_KEY --stdio');
}
}

// If we're being executed directly (not imported), start the server
if (process.argv[1]) {
startServer().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
}
startServer().catch((error) => {
console.error("Failed to start server:", error);
process.exit(1);
});
112 changes: 112 additions & 0 deletions src/config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {config as loadEnv} from "dotenv";
import yargs from "yargs";
import {hideBin} from "yargs/helpers";
import {resolve} from "path";

export interface ServerConfig {
figmaApiKey: string;
outputFormat: "yaml" | "json";
isStdioMode: boolean;
configSources: {
figmaApiKey: "cli" | "env";
envFile: "cli" | "default";
stdio: "cli" | "env" | "default";
};
}

function maskApiKey(key: string): string {
if (!key || key.length <= 4) return "****";
return `****${key.slice(-4)}`;
}

interface CliArgs {
"figma-api-key"?: string;
env?: string;
stdio?: boolean;
}

export function getServerConfig(): ServerConfig {
// Parse command line arguments
const argv = yargs(hideBin(process.argv))
.options({
"figma-api-key": {
type: "string",
description: "Figma API key",
},
env: {
type: "string",
description: "Path to custom .env file to load environment variables from",
},
stdio: {
type: "boolean",
description: "Run in stdio mode for MCP client communication",
default: false,
},
})
.help()
.version(process.env.npm_package_version || "0.0.1")
.parseSync() as CliArgs;

// Load environment variables from custom path or default
let envFilePath: string;
let envFileSource: "cli" | "default";

if (argv.env) {
envFilePath = resolve(argv.env);
envFileSource = "cli";
} else {
envFilePath = resolve(process.cwd(), ".env");
envFileSource = "default";
}

// Load .env file with override if custom path provided
loadEnv({path: envFilePath, override: !!argv.env});

const config: ServerConfig = {
figmaApiKey: "",
outputFormat: "json",
isStdioMode: false,
configSources: {
figmaApiKey: "env",
envFile: envFileSource,
stdio: "default",
},
};

// Handle FIGMA_API_KEY
if (argv["figma-api-key"]) {
config.figmaApiKey = argv["figma-api-key"];
config.configSources.figmaApiKey = "cli";
} else if (process.env.FIGMA_API_KEY) {
config.figmaApiKey = process.env.FIGMA_API_KEY;
config.configSources.figmaApiKey = "env";
}

// Handle stdio mode
if (argv.stdio) {
config.isStdioMode = true;
config.configSources.stdio = "cli";
} else if (process.env.NODE_ENV === "cli") {
config.isStdioMode = true;
config.configSources.stdio = "env";
}

// Validate configuration
if (!config.figmaApiKey) {
console.error("Error: FIGMA_API_KEY is required (via CLI argument or .env file)");
process.exit(1);
}

// Log configuration sources (only in non-stdio mode)
if (!config.isStdioMode) {
console.log("\nConfiguration:");
console.log(`- ENV_FILE: ${envFilePath} (source: ${config.configSources.envFile})`);
console.log(
`- FIGMA_API_KEY: ${maskApiKey(config.figmaApiKey)} (source: ${config.configSources.figmaApiKey})`
);
console.log(`- STDIO_MODE: ${config.isStdioMode} (source: ${config.configSources.stdio})`);
console.log(); // Empty line for better readability
}

return config;
}
35 changes: 9 additions & 26 deletions src/server.mts
Original file line number Diff line number Diff line change
@@ -1,42 +1,25 @@
import 'dotenv/config';
import {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js";
import {registerAllTools} from "./tools/index.mjs";

// Create MCP server
const server = new McpServer({
name: "figma-flutter-mcp",
version: "0.1.0"
});
export function createServer(figmaApiKey: string) {
const server = new McpServer({
name: "figma-flutter-mcp",
version: process.env.npm_package_version || "0.0.1"
});

// Parse CLI arguments for figma key
const args = process.argv.slice(2);
for (const arg of args) {
const lower = arg.toLowerCase();
const isKeyArg = lower.startsWith('--figma-api-key=');
if (isKeyArg) {
const key = arg.split('=')[1];
if (key && key.trim().length > 0) {
process.env.FIGMA_API_KEY = key;
}
}
registerAllTools(server, figmaApiKey);
return server;
}

// Register all tools
registerAllTools(server);

export async function startMcpServer(): Promise<void> {
export async function startMcpServer(figmaApiKey: string): Promise<void> {
try {
const server = createServer(figmaApiKey);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Figma-to-Flutter MCP Server connected via stdio");
} catch (error) {
console.error("Failed to start MCP server:", error);
process.exit(1);
}
}

// Auto-start if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
await startMcpServer();
}
4 changes: 0 additions & 4 deletions src/tools/config.mts

This file was deleted.

5 changes: 2 additions & 3 deletions src/tools/flutter/assets/assets.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import {z} from "zod";
import type {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {FigmaService} from "../../../services/figma.mjs";
import {getFigmaToken} from "../../config.mjs";
import {join} from 'path';
import {
createAssetsDirectory,
Expand All @@ -15,7 +14,7 @@ import {
type AssetInfo
} from "./asset-manager.mjs";

export function registerFlutterAssetTools(server: McpServer) {
export function registerFlutterAssetTools(server: McpServer, figmaApiKey: string) {
// Tool: Export Flutter Assets
server.registerTool(
"export_flutter_assets",
Expand All @@ -32,7 +31,7 @@ export function registerFlutterAssetTools(server: McpServer) {
}
},
async ({fileId, nodeIds, projectPath = process.cwd(), format = 'png', scale = 2, includeMultipleResolutions = false}) => {
const token = getFigmaToken();
const token = figmaApiKey;
if (!token) {
return {
content: [{
Expand Down
5 changes: 2 additions & 3 deletions src/tools/flutter/assets/svg-assets.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import {z} from "zod";
import type {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {FigmaService} from "../../../services/figma.mjs";
import {getFigmaToken} from "../../config.mjs";
import {join} from 'path';
import {
createSvgAssetsDirectory,
Expand All @@ -14,7 +13,7 @@ import {
type AssetInfo
} from "./asset-manager.mjs";

export function registerSvgAssetTools(server: McpServer) {
export function registerSvgAssetTools(server: McpServer, figmaApiKey: string) {
// Tool: Export SVG Flutter Assets
server.registerTool(
"export_svg_flutter_assets",
Expand All @@ -28,7 +27,7 @@ export function registerSvgAssetTools(server: McpServer) {
}
},
async ({fileId, nodeIds, projectPath = process.cwd()}) => {
const token = getFigmaToken();
const token = figmaApiKey;
if (!token) {
return {
content: [{
Expand Down
9 changes: 4 additions & 5 deletions src/tools/flutter/components/component-tool.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import {z} from "zod";
import type {McpServer} from "@modelcontextprotocol/sdk/server/mcp.js";
import {FigmaService} from "../../../services/figma.mjs";
import {getFigmaToken} from "../../config.mjs";
import {
ComponentExtractor,
VariantAnalyzer,
Expand All @@ -30,7 +29,7 @@ import {
} from "../assets/asset-manager.mjs";
import {join} from 'path';

export function registerComponentTools(server: McpServer) {
export function registerComponentTools(server: McpServer, figmaApiKey: string) {

// Main component analysis tool
server.registerTool(
Expand All @@ -50,7 +49,7 @@ export function registerComponentTools(server: McpServer) {
}
},
async ({input, nodeId, userDefinedComponent = false, maxChildNodes = 10, includeVariants = true, variantSelection, projectPath = process.cwd(), exportAssets = true}) => {
const token = getFigmaToken();
const token = figmaApiKey;
if (!token) {
return {
content: [{
Expand Down Expand Up @@ -231,7 +230,7 @@ export function registerComponentTools(server: McpServer) {
}
},
async ({input, nodeId}) => {
const token = getFigmaToken();
const token = figmaApiKey;
if (!token) {
return {
content: [{
Expand Down Expand Up @@ -318,7 +317,7 @@ export function registerComponentTools(server: McpServer) {
}
},
async ({input, nodeId, userDefinedComponent = false, showAllChildren = false}) => {
const token = getFigmaToken();
const token = figmaApiKey;
if (!token) {
return {
content: [{
Expand Down
Loading