diff --git a/.changeset/rotten-islands-kneel.md b/.changeset/rotten-islands-kneel.md new file mode 100644 index 0000000..10c503a --- /dev/null +++ b/.changeset/rotten-islands-kneel.md @@ -0,0 +1,5 @@ +--- +"figma-flutter-mcp": patch +--- + +Figma API key logic improved for API access diff --git a/.env.example b/.env.example deleted file mode 100644 index b8ff06b..0000000 --- a/.env.example +++ /dev/null @@ -1,14 +0,0 @@ -# Your Figma API access token -# Get it from your Figma account settings: https://www.figma.com/developers/api#access-tokens -FIGMA_API_KEY=your_figma_api_key_here - -# Figma file configuration -# This is the ID in your Figma URL: https://www.figma.com/file/{FILE_KEY}/filename -FIGMA_FILE_ID=your_figma_file_key_here - -# Optional: Configuration overrides -FIGMA_MAX_DEPTH=3 -FIGMA_MAX_COMPONENTS=10 - -# Server configuration -PORT=3333 \ No newline at end of file diff --git a/README.md b/README.md index ad013f2..f47de77 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
-

Figma to Flutter MCP Server [Early]

+

Figma to Flutter MCP Server

Utilize Figma's rich data in your coding agent.
Implement designs in Flutter way!

weekly downloads @@ -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/) diff --git a/package.json b/package.json index 2aab292..41babc7 100644 --- a/package.json +++ b/package.json @@ -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" }, @@ -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" diff --git a/src/cli.mts b/src/cli.mts index 1c917e8..4a19012 100644 --- a/src/cli.mts +++ b/src/cli.mts @@ -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 ', '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 { - 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); - }); -} \ No newline at end of file +startServer().catch((error) => { + console.error("Failed to start server:", error); + process.exit(1); +}); \ No newline at end of file diff --git a/src/config.mts b/src/config.mts new file mode 100644 index 0000000..11edd5e --- /dev/null +++ b/src/config.mts @@ -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; +} \ No newline at end of file diff --git a/src/server.mts b/src/server.mts index 08da23e..ee40b56 100644 --- a/src/server.mts +++ b/src/server.mts @@ -1,32 +1,20 @@ -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 { +export async function startMcpServer(figmaApiKey: string): Promise { try { + const server = createServer(figmaApiKey); const transport = new StdioServerTransport(); await server.connect(transport); console.error("Figma-to-Flutter MCP Server connected via stdio"); @@ -34,9 +22,4 @@ export async function startMcpServer(): Promise { 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(); } \ No newline at end of file diff --git a/src/tools/config.mts b/src/tools/config.mts deleted file mode 100644 index 4d63d36..0000000 --- a/src/tools/config.mts +++ /dev/null @@ -1,4 +0,0 @@ -// src/tools/config.mts -export function getFigmaToken(): string | null { - return process.env.FIGMA_API_KEY || null; -} \ No newline at end of file diff --git a/src/tools/flutter/assets/assets.mts b/src/tools/flutter/assets/assets.mts index 9a0c97b..b6a7f0b 100644 --- a/src/tools/flutter/assets/assets.mts +++ b/src/tools/flutter/assets/assets.mts @@ -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, @@ -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", @@ -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: [{ diff --git a/src/tools/flutter/assets/svg-assets.mts b/src/tools/flutter/assets/svg-assets.mts index b40307b..39f2d86 100644 --- a/src/tools/flutter/assets/svg-assets.mts +++ b/src/tools/flutter/assets/svg-assets.mts @@ -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, @@ -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", @@ -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: [{ diff --git a/src/tools/flutter/components/component-tool.mts b/src/tools/flutter/components/component-tool.mts index 67c5711..3dbcbec 100644 --- a/src/tools/flutter/components/component-tool.mts +++ b/src/tools/flutter/components/component-tool.mts @@ -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, @@ -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( @@ -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: [{ @@ -231,7 +230,7 @@ export function registerComponentTools(server: McpServer) { } }, async ({input, nodeId}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ @@ -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: [{ diff --git a/src/tools/flutter/index.mts b/src/tools/flutter/index.mts index 82629fd..2e21fad 100644 --- a/src/tools/flutter/index.mts +++ b/src/tools/flutter/index.mts @@ -5,17 +5,17 @@ import {registerSvgAssetTools} from "./assets/svg-assets.mjs"; import {registerComponentTools} from "./components/component-tool.mjs"; import {registerScreenTools} from "./screens/screen-tool.mjs"; -export function registerFlutterTools(server: McpServer) { +export function registerFlutterTools(server: McpServer, figmaApiKey: string) { // Register Flutter asset management tools - registerFlutterAssetTools(server); + registerFlutterAssetTools(server, figmaApiKey); // Register SVG asset management tools - registerSvgAssetTools(server); + registerSvgAssetTools(server, figmaApiKey); // Register component analysis tools - registerComponentTools(server); + registerComponentTools(server, figmaApiKey); // Register screen analysis tools - registerScreenTools(server); + registerScreenTools(server, figmaApiKey); } diff --git a/src/tools/flutter/screens/screen-tool.mts b/src/tools/flutter/screens/screen-tool.mts index 354e09c..38a9639 100644 --- a/src/tools/flutter/screens/screen-tool.mts +++ b/src/tools/flutter/screens/screen-tool.mts @@ -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 { ScreenExtractor, parseComponentInput, @@ -27,7 +26,7 @@ import { } from "../assets/asset-manager.mjs"; import {join} from 'path'; -export function registerScreenTools(server: McpServer) { +export function registerScreenTools(server: McpServer, figmaApiKey: string) { // Main screen analysis tool server.registerTool( @@ -46,7 +45,7 @@ export function registerScreenTools(server: McpServer) { } }, async ({input, nodeId, maxSections = 15, extractNavigation = true, extractAssets = true, projectPath = process.cwd(), deviceTypeDetection = true}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ @@ -145,7 +144,7 @@ export function registerScreenTools(server: McpServer) { } }, async ({input, nodeId, showAllSections = false}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ diff --git a/src/tools/flutter/theme/colors/theme-tool.mts b/src/tools/flutter/theme/colors/theme-tool.mts index 2eb26df..7c0927c 100644 --- a/src/tools/flutter/theme/colors/theme-tool.mts +++ b/src/tools/flutter/theme/colors/theme-tool.mts @@ -2,12 +2,11 @@ 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 {extractThemeColors} from "../../../../extractors/colors/index.mjs"; import {SimpleThemeGenerator} from "./theme-generator.mjs"; import {join} from 'path'; -export function registerThemeTools(server: McpServer) { +export function registerThemeTools(server: McpServer, figmaApiKey: string) { server.registerTool( "extract_theme_colors", { @@ -21,7 +20,7 @@ export function registerThemeTools(server: McpServer) { } }, async ({fileId, nodeId, projectPath = process.cwd(), generateThemeData = false}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ @@ -130,7 +129,7 @@ export function registerThemeTools(server: McpServer) { } }, async ({fileId, nodeId}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ diff --git a/src/tools/flutter/theme/typography/typography-tool.mts b/src/tools/flutter/theme/typography/typography-tool.mts index ba18e6c..9935654 100644 --- a/src/tools/flutter/theme/typography/typography-tool.mts +++ b/src/tools/flutter/theme/typography/typography-tool.mts @@ -3,12 +3,11 @@ 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 {extractThemeTypography} from "../../../../extractors/typography/index.mjs"; import {TypographyGenerator} from "./typography-generator.mjs"; import {join} from 'path'; -export function registerTypographyTools(server: McpServer) { +export function registerTypographyTools(server: McpServer, figmaApiKey: string) { server.registerTool( "extract_theme_typography", { @@ -23,7 +22,7 @@ export function registerTypographyTools(server: McpServer) { } }, async ({fileId, nodeId, projectPath = process.cwd(), generateTextTheme = false, familyVariableName = 'fontFamily'}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ @@ -164,7 +163,7 @@ export function registerTypographyTools(server: McpServer) { } }, async ({fileId, nodeId}) => { - const token = getFigmaToken(); + const token = figmaApiKey; if (!token) { return { content: [{ diff --git a/src/tools/index.mts b/src/tools/index.mts index abeb7fd..b927be2 100644 --- a/src/tools/index.mts +++ b/src/tools/index.mts @@ -4,17 +4,24 @@ import {registerFlutterTools} from "./flutter/index.mjs"; import {registerThemeTools} from "./flutter/theme/colors/theme-tool.mjs"; import {registerTypographyTools} from "./flutter/theme/typography/typography-tool.mjs"; -export function registerAllTools(server: McpServer) { +export function registerAllTools(server: McpServer, figmaApiKey: string) { + console.error('πŸ› οΈ Tools Debug - Starting tool registration...'); + // Register all tool categories - registerFlutterTools(server); - registerThemeTools(server); - registerTypographyTools(server); - + registerFlutterTools(server, figmaApiKey); + console.error('πŸ› οΈ Tools Debug - Flutter tools registered'); + + registerThemeTools(server, figmaApiKey); + console.error('πŸ› οΈ Tools Debug - Theme tools registered'); + + registerTypographyTools(server, figmaApiKey); + console.error('πŸ› οΈ Tools Debug - Typography tools registered'); + console.log("πŸ“‹ Registered tool categories:"); console.log(" πŸš€ Flutter tools - Widgets, Screens"); console.log(" 🏞️ Export assets - Images, SVGs"); console.log(" 🎨 Theme tools - Colors, Typography"); console.log(" πŸ“ Typography tools - Fonts, Sizes"); -} -export {getFigmaToken} from "./config.mjs"; \ No newline at end of file + console.error('πŸ› οΈ Tools Debug - All tools registration complete'); +} \ No newline at end of file