@seed-design/mcp 0.0.20 → 0.0.22

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/bin/index.mjs CHANGED
@@ -4,8 +4,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
5
  import { v4 } from 'uuid';
6
6
  import WebSocket$1 from 'ws';
7
- import { createRestNormalizer, generateFigmaSummary, generateCode, getFigmaColorVariableNames } from '@seed-design/figma';
7
+ import { figma, react, createRestNormalizer, getFigmaColorVariableNames } from '@seed-design/figma';
8
8
  import { z } from 'zod';
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
9
11
 
10
12
  /**
11
13
  * Custom logging module that writes to stderr instead of stdout
@@ -261,8 +263,13 @@ function createFigmaWebSocketClient(serverUrl) {
261
263
  };
262
264
  }
263
265
 
264
- function registerTools(server, figmaClient) {
266
+ function registerTools(server, figmaClient, config = {}) {
265
267
  const { joinChannel, sendCommandToFigma } = figmaClient;
268
+ const { extend } = config ?? {};
269
+ const figmaPipeline = figma.createPipeline();
270
+ const reactPipeline = react.createPipeline({
271
+ extend
272
+ });
266
273
  // join_channel tool
267
274
  server.tool("join_channel", "Join a specific channel to communicate with Figma", {
268
275
  channel: z.string().describe("The name of the channel to join").default("")
@@ -331,6 +338,31 @@ function registerTools(server, figmaClient) {
331
338
  return formatErrorResponse("get_annotations", error);
332
339
  }
333
340
  });
341
+ // Component Info Tool
342
+ server.tool("get_component_info", "Get detailed information about a specific component node in Figma", {
343
+ nodeId: z.string().describe("The ID of the component node to get information about")
344
+ }, async ({ nodeId })=>{
345
+ try {
346
+ const result = await sendCommandToFigma("get_node_info", {
347
+ nodeId
348
+ });
349
+ const node = result.document;
350
+ if (node.type !== "COMPONENT" && node.type !== "COMPONENT_SET") {
351
+ return formatErrorResponse("get_component_info", new Error(`Node with ID ${nodeId} is not a component node`));
352
+ }
353
+ const key = result.componentSets[nodeId]?.key ?? result.components[nodeId]?.key;
354
+ if (!key) {
355
+ return formatErrorResponse("get_component_info", new Error(`${nodeId} is not present in exported component data`));
356
+ }
357
+ return formatObjectResponse({
358
+ name: node.name,
359
+ key,
360
+ componentPropertyDefinitions: node.componentPropertyDefinitions
361
+ });
362
+ } catch (error) {
363
+ return formatErrorResponse("get_node_info", error);
364
+ }
365
+ });
334
366
  // Node Info Tool
335
367
  server.tool("get_node_info", "Get detailed information about a specific node in Figma", {
336
368
  nodeId: z.string().describe("The ID of the node to get information about")
@@ -341,12 +373,12 @@ function registerTools(server, figmaClient) {
341
373
  });
342
374
  const normalizer = createRestNormalizer(result);
343
375
  const node = normalizer(result.document);
344
- const original = generateFigmaSummary(node, {
376
+ const original = figmaPipeline.generateCode(node, {
345
377
  shouldPrintSource: true,
346
378
  shouldInferVariableName: false,
347
379
  shouldInferAutoLayout: false
348
380
  }) ?? "Failed to generate summarized node info";
349
- const inferred = generateFigmaSummary(node, {
381
+ const inferred = figmaPipeline.generateCode(node, {
350
382
  shouldPrintSource: true,
351
383
  shouldInferVariableName: false,
352
384
  shouldInferAutoLayout: true
@@ -376,12 +408,12 @@ function registerTools(server, figmaClient) {
376
408
  });
377
409
  const normalizer = createRestNormalizer(result);
378
410
  const node = normalizer(result.document);
379
- const original = generateFigmaSummary(node, {
411
+ const original = figmaPipeline.generateCode(node, {
380
412
  shouldInferVariableName: false,
381
413
  shouldPrintSource: true,
382
414
  shouldInferAutoLayout: false
383
415
  }) ?? "Failed to generate summarized node info";
384
- const inferred = generateFigmaSummary(node, {
416
+ const inferred = figmaPipeline.generateCode(node, {
385
417
  shouldInferVariableName: true,
386
418
  shouldPrintSource: true,
387
419
  shouldInferAutoLayout: false
@@ -411,7 +443,7 @@ function registerTools(server, figmaClient) {
411
443
  nodeId
412
444
  });
413
445
  const normalizer = createRestNormalizer(result);
414
- const code = generateCode(normalizer(result.document), {
446
+ const code = reactPipeline.generateCode(normalizer(result.document), {
415
447
  shouldInferVariableName: true,
416
448
  shouldPrintSource: false,
417
449
  shouldInferAutoLayout: true
@@ -601,14 +633,14 @@ function registerPrompts(server) {
601
633
  - First use get_selection() to understand the current selection
602
634
  - If no selection ask user to select single node
603
635
 
604
- 2. Get React code of the selected nodes:
636
+ 2. Get React code of the selected node:
605
637
  - Use get_node_react_code() to get the React code of the selected node
606
638
  - If no selection ask user to select single node
607
639
  `
608
640
  }
609
641
  }
610
642
  ],
611
- description: "Best practices for reading Figma designs"
643
+ description: "Best practices for implementing React components"
612
644
  };
613
645
  });
614
646
  server.prompt("read_design_strategy", "Best practices for reading Figma designs", (_extra)=>{
@@ -636,7 +668,34 @@ function registerPrompts(server) {
636
668
  });
637
669
  }
638
670
 
639
- var version = "0.0.20";
671
+ var version = "0.0.22";
672
+
673
+ // Config loader
674
+ async function loadConfig(configPath) {
675
+ try {
676
+ const resolvedPath = path.resolve(process.cwd(), configPath);
677
+ if (!fs.existsSync(resolvedPath)) {
678
+ logger.error(`Config file not found: ${resolvedPath}`);
679
+ return null;
680
+ }
681
+ // Handle different file types
682
+ if (resolvedPath.endsWith(".json")) {
683
+ const content = fs.readFileSync(resolvedPath, "utf-8");
684
+ return JSON.parse(content);
685
+ }
686
+ if (resolvedPath.endsWith(".js") || resolvedPath.endsWith(".mjs") || resolvedPath.endsWith(".ts") || resolvedPath.endsWith(".mts")) {
687
+ // For JS/MJS/TS/MTS files, we can dynamically import with Bun
688
+ // Bun has built-in TypeScript support without requiring transpilation
689
+ const config = await import(resolvedPath);
690
+ return config.default || config;
691
+ }
692
+ logger.error(`Unsupported config file format: ${resolvedPath}`);
693
+ return null;
694
+ } catch (error) {
695
+ logger.error(`Failed to load config file: ${error instanceof Error ? error.message : String(error)}`);
696
+ return null;
697
+ }
698
+ }
640
699
 
641
700
  // Initialize CLI
642
701
  const cli = cac("@seed-design/mcp");
@@ -791,13 +850,28 @@ async function startWebSocketServer(port) {
791
850
  console.log(`WebSocket server running on port ${server.port}`);
792
851
  return server;
793
852
  }
794
- async function startMcpServer(serverUrl, experimental) {
853
+ async function startMcpServer(serverUrl, experimental, configPath) {
854
+ // Load config if provided
855
+ let configData = null;
856
+ if (configPath) {
857
+ configData = await loadConfig(configPath);
858
+ if (configData) {
859
+ logger.info(`Loaded configuration from: ${configPath}`);
860
+ // Log component transformers if present
861
+ if (configData.extend?.componentTransformers) {
862
+ const transformerKeys = configData.extend.componentTransformers.map((t)=>t.key);
863
+ if (transformerKeys.length > 0) {
864
+ logger.info(`Found ${transformerKeys.length} custom component transformers: ${transformerKeys.join(", ")}`);
865
+ }
866
+ }
867
+ }
868
+ }
795
869
  const figmaClient = createFigmaWebSocketClient(serverUrl);
796
870
  const server = new McpServer({
797
871
  name: "SEED Design MCP",
798
872
  version
799
873
  });
800
- registerTools(server, figmaClient);
874
+ registerTools(server, figmaClient, configData);
801
875
  if (experimental) {
802
876
  registerEditingTools(server, figmaClient);
803
877
  }
@@ -817,8 +891,8 @@ cli.command("", "Start the MCP server").option("--server <server>", "Server URL"
817
891
  default: "localhost"
818
892
  }).option("--experimental", "Enable experimental features", {
819
893
  default: false
820
- }).action(async (options)=>{
821
- await startMcpServer(options.server, options.experimental);
894
+ }).option("--config <config>", "Path to configuration file (.js, .mjs, .ts, .mts)").action(async (options)=>{
895
+ await startMcpServer(options.server, options.experimental, options.config);
822
896
  });
823
897
  cli.command("socket", "Start the WebSocket server").option("--port <port>", "Port number", {
824
898
  default: 3055
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/mcp",
3
- "version": "0.0.20",
3
+ "version": "0.0.22",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -22,20 +22,19 @@
22
22
  "lint:publish": "bun publint"
23
23
  },
24
24
  "dependencies": {
25
- "@modelcontextprotocol/sdk": "^1.7.0",
26
- "@seed-design/figma": "0.0.20",
25
+ "@modelcontextprotocol/sdk": "^1.10.2",
26
+ "@seed-design/figma": "0.0.22",
27
27
  "cac": "^6.7.14",
28
- "express": "^4.21.2",
29
28
  "uuid": "^11.1.0",
30
29
  "ws": "^8.18.1",
31
30
  "yargs": "^17.7.2",
32
31
  "zod": "^3.24.3"
33
32
  },
34
33
  "devDependencies": {
35
- "@types/bun": "^1.2.8",
34
+ "@types/bun": "^1.2.10",
36
35
  "@types/ws": "^8.18.1",
37
36
  "@types/yargs": "^17.0.33",
38
- "typescript": "^5.4.5"
37
+ "typescript": "^5.8.3"
39
38
  },
40
39
  "publishConfig": {
41
40
  "access": "public"
package/src/bin/index.ts CHANGED
@@ -9,6 +9,7 @@ import { registerEditingTools, registerTools } from "../tools";
9
9
  import { registerPrompts } from "../prompts";
10
10
  import { version } from "../../package.json" assert { type: "json" };
11
11
  import type { Server, ServerWebSocket } from "bun";
12
+ import { loadConfig, type McpConfig } from "../config";
12
13
 
13
14
  // Initialize CLI
14
15
  const cli = cac("@seed-design/mcp");
@@ -188,14 +189,33 @@ async function startWebSocketServer(port: number) {
188
189
  return server;
189
190
  }
190
191
 
191
- async function startMcpServer(serverUrl: string, experimental: boolean) {
192
+ async function startMcpServer(serverUrl: string, experimental: boolean, configPath?: string) {
193
+ // Load config if provided
194
+ let configData: McpConfig | null = null;
195
+ if (configPath) {
196
+ configData = await loadConfig(configPath);
197
+ if (configData) {
198
+ logger.info(`Loaded configuration from: ${configPath}`);
199
+
200
+ // Log component transformers if present
201
+ if (configData.extend?.componentTransformers) {
202
+ const transformerKeys = configData.extend.componentTransformers.map((t) => t.key);
203
+ if (transformerKeys.length > 0) {
204
+ logger.info(
205
+ `Found ${transformerKeys.length} custom component transformers: ${transformerKeys.join(", ")}`,
206
+ );
207
+ }
208
+ }
209
+ }
210
+ }
211
+
192
212
  const figmaClient = createFigmaWebSocketClient(serverUrl);
193
213
  const server = new McpServer({
194
214
  name: "SEED Design MCP",
195
215
  version,
196
216
  });
197
217
 
198
- registerTools(server, figmaClient);
218
+ registerTools(server, figmaClient, configData);
199
219
  if (experimental) {
200
220
  registerEditingTools(server, figmaClient);
201
221
  }
@@ -220,8 +240,9 @@ cli
220
240
  .command("", "Start the MCP server")
221
241
  .option("--server <server>", "Server URL", { default: "localhost" })
222
242
  .option("--experimental", "Enable experimental features", { default: false })
243
+ .option("--config <config>", "Path to configuration file (.js, .mjs, .ts, .mts)")
223
244
  .action(async (options) => {
224
- await startMcpServer(options.server, options.experimental);
245
+ await startMcpServer(options.server, options.experimental, options.config);
225
246
  });
226
247
 
227
248
  cli
package/src/config.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { CreatePipelineConfig } from "@seed-design/figma/codegen/targets/react";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { logger } from "./logger";
5
+
6
+ // Define config type
7
+ export interface McpConfig {
8
+ extend?: CreatePipelineConfig["extend"];
9
+ }
10
+
11
+ // Config loader
12
+ export async function loadConfig(configPath: string) {
13
+ try {
14
+ const resolvedPath = path.resolve(process.cwd(), configPath);
15
+
16
+ if (!fs.existsSync(resolvedPath)) {
17
+ logger.error(`Config file not found: ${resolvedPath}`);
18
+ return null;
19
+ }
20
+
21
+ // Handle different file types
22
+ if (resolvedPath.endsWith(".json")) {
23
+ const content = fs.readFileSync(resolvedPath, "utf-8");
24
+ return JSON.parse(content);
25
+ }
26
+
27
+ if (
28
+ resolvedPath.endsWith(".js") ||
29
+ resolvedPath.endsWith(".mjs") ||
30
+ resolvedPath.endsWith(".ts") ||
31
+ resolvedPath.endsWith(".mts")
32
+ ) {
33
+ // For JS/MJS/TS/MTS files, we can dynamically import with Bun
34
+ // Bun has built-in TypeScript support without requiring transpilation
35
+ const config = await import(resolvedPath);
36
+ return config.default || config;
37
+ }
38
+
39
+ logger.error(`Unsupported config file format: ${resolvedPath}`);
40
+ return null;
41
+ } catch (error) {
42
+ logger.error(
43
+ `Failed to load config file: ${error instanceof Error ? error.message : String(error)}`,
44
+ );
45
+ return null;
46
+ }
47
+ }
package/src/prompts.ts CHANGED
@@ -17,14 +17,14 @@ export function registerPrompts(server: McpServer): void {
17
17
  - First use get_selection() to understand the current selection
18
18
  - If no selection ask user to select single node
19
19
 
20
- 2. Get React code of the selected nodes:
20
+ 2. Get React code of the selected node:
21
21
  - Use get_node_react_code() to get the React code of the selected node
22
22
  - If no selection ask user to select single node
23
23
  `,
24
24
  },
25
25
  },
26
26
  ],
27
- description: "Best practices for reading Figma designs",
27
+ description: "Best practices for implementing React components",
28
28
  };
29
29
  },
30
30
  );
package/src/tools.ts CHANGED
@@ -1,11 +1,8 @@
1
+ import type { GetFileNodesResponse } from "@figma/rest-api-spec";
1
2
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import {
3
- createRestNormalizer,
4
- generateCode,
5
- generateFigmaSummary,
6
- getFigmaColorVariableNames,
7
- } from "@seed-design/figma";
3
+ import { createRestNormalizer, getFigmaColorVariableNames, react, figma } from "@seed-design/figma";
8
4
  import { z } from "zod";
5
+ import type { McpConfig } from "./config";
9
6
  import {
10
7
  formatErrorResponse,
11
8
  formatImageResponse,
@@ -14,8 +11,18 @@ import {
14
11
  } from "./responses";
15
12
  import type { FigmaWebSocketClient } from "./websocket";
16
13
 
17
- export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClient): void {
14
+ export function registerTools(
15
+ server: McpServer,
16
+ figmaClient: FigmaWebSocketClient,
17
+ config: McpConfig | null = {},
18
+ ): void {
18
19
  const { joinChannel, sendCommandToFigma } = figmaClient;
20
+ const { extend } = config ?? {};
21
+
22
+ const figmaPipeline = figma.createPipeline();
23
+ const reactPipeline = react.createPipeline({
24
+ extend,
25
+ });
19
26
 
20
27
  // join_channel tool
21
28
  server.tool(
@@ -117,6 +124,46 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
117
124
  },
118
125
  );
119
126
 
127
+ // Component Info Tool
128
+ server.tool(
129
+ "get_component_info",
130
+ "Get detailed information about a specific component node in Figma",
131
+ {
132
+ nodeId: z.string().describe("The ID of the component node to get information about"),
133
+ },
134
+ async ({ nodeId }) => {
135
+ try {
136
+ const result = (await sendCommandToFigma("get_node_info", {
137
+ nodeId,
138
+ })) as GetFileNodesResponse["nodes"][string];
139
+
140
+ const node = result.document;
141
+ if (node.type !== "COMPONENT" && node.type !== "COMPONENT_SET") {
142
+ return formatErrorResponse(
143
+ "get_component_info",
144
+ new Error(`Node with ID ${nodeId} is not a component node`),
145
+ );
146
+ }
147
+
148
+ const key = result.componentSets[nodeId]?.key ?? result.components[nodeId]?.key;
149
+ if (!key) {
150
+ return formatErrorResponse(
151
+ "get_component_info",
152
+ new Error(`${nodeId} is not present in exported component data`),
153
+ );
154
+ }
155
+
156
+ return formatObjectResponse({
157
+ name: node.name,
158
+ key,
159
+ componentPropertyDefinitions: node.componentPropertyDefinitions,
160
+ });
161
+ } catch (error) {
162
+ return formatErrorResponse("get_node_info", error);
163
+ }
164
+ },
165
+ );
166
+
120
167
  // Node Info Tool
121
168
  server.tool(
122
169
  "get_node_info",
@@ -131,13 +178,13 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
131
178
  const node = normalizer(result.document);
132
179
 
133
180
  const original =
134
- generateFigmaSummary(node, {
181
+ figmaPipeline.generateCode(node, {
135
182
  shouldPrintSource: true,
136
183
  shouldInferVariableName: false,
137
184
  shouldInferAutoLayout: false,
138
185
  }) ?? "Failed to generate summarized node info";
139
186
  const inferred =
140
- generateFigmaSummary(node, {
187
+ figmaPipeline.generateCode(node, {
141
188
  shouldPrintSource: true,
142
189
  shouldInferVariableName: false,
143
190
  shouldInferAutoLayout: true,
@@ -175,13 +222,13 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
175
222
  const node = normalizer(result.document);
176
223
 
177
224
  const original =
178
- generateFigmaSummary(node, {
225
+ figmaPipeline.generateCode(node, {
179
226
  shouldInferVariableName: false,
180
227
  shouldPrintSource: true,
181
228
  shouldInferAutoLayout: false,
182
229
  }) ?? "Failed to generate summarized node info";
183
230
  const inferred =
184
- generateFigmaSummary(node, {
231
+ figmaPipeline.generateCode(node, {
185
232
  shouldInferVariableName: true,
186
233
  shouldPrintSource: true,
187
234
  shouldInferAutoLayout: false,
@@ -217,7 +264,7 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
217
264
  const normalizer = createRestNormalizer(result);
218
265
 
219
266
  const code =
220
- generateCode(normalizer(result.document), {
267
+ reactPipeline.generateCode(normalizer(result.document), {
221
268
  shouldInferVariableName: true,
222
269
  shouldPrintSource: false,
223
270
  shouldInferAutoLayout: true,