@seed-design/mcp 0.0.21 → 0.0.23

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 { createRestNormalizer, figma, react, 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
@@ -56,7 +58,7 @@ function createFigmaWebSocketClient(serverUrl) {
56
58
  ws.on("open", ()=>{
57
59
  logger.info("Connected to Figma socket server");
58
60
  // Reset channel on new connection
59
- currentChannel = null;
61
+ joinChannel("local-default");
60
62
  });
61
63
  ws.on("message", (data)=>{
62
64
  try {
@@ -261,8 +263,9 @@ 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 ?? {};
266
269
  // join_channel tool
267
270
  server.tool("join_channel", "Join a specific channel to communicate with Figma", {
268
271
  channel: z.string().describe("The name of the channel to join").default("")
@@ -331,6 +334,31 @@ function registerTools(server, figmaClient) {
331
334
  return formatErrorResponse("get_annotations", error);
332
335
  }
333
336
  });
337
+ // Component Info Tool
338
+ server.tool("get_component_info", "Get detailed information about a specific component node in Figma", {
339
+ nodeId: z.string().describe("The ID of the component node to get information about")
340
+ }, async ({ nodeId })=>{
341
+ try {
342
+ const result = await sendCommandToFigma("get_node_info", {
343
+ nodeId
344
+ });
345
+ const node = result.document;
346
+ if (node.type !== "COMPONENT" && node.type !== "COMPONENT_SET") {
347
+ return formatErrorResponse("get_component_info", new Error(`Node with ID ${nodeId} is not a component node`));
348
+ }
349
+ const key = result.componentSets[nodeId]?.key ?? result.components[nodeId]?.key;
350
+ if (!key) {
351
+ return formatErrorResponse("get_component_info", new Error(`${nodeId} is not present in exported component data`));
352
+ }
353
+ return formatObjectResponse({
354
+ name: node.name,
355
+ key,
356
+ componentPropertyDefinitions: node.componentPropertyDefinitions
357
+ });
358
+ } catch (error) {
359
+ return formatErrorResponse("get_node_info", error);
360
+ }
361
+ });
334
362
  // Node Info Tool
335
363
  server.tool("get_node_info", "Get detailed information about a specific node in Figma", {
336
364
  nodeId: z.string().describe("The ID of the node to get information about")
@@ -341,15 +369,19 @@ function registerTools(server, figmaClient) {
341
369
  });
342
370
  const normalizer = createRestNormalizer(result);
343
371
  const node = normalizer(result.document);
344
- const original = generateFigmaSummary(node, {
345
- shouldPrintSource: true,
346
- shouldInferVariableName: false,
347
- shouldInferAutoLayout: false
372
+ const noInferPipeline = figma.createPipeline({
373
+ shouldInferAutoLayout: false,
374
+ shouldInferVariableName: false
375
+ });
376
+ const inferPipeline = figma.createPipeline({
377
+ shouldInferAutoLayout: true,
378
+ shouldInferVariableName: true
379
+ });
380
+ const original = noInferPipeline.generateCode(node, {
381
+ shouldPrintSource: true
348
382
  }) ?? "Failed to generate summarized node info";
349
- const inferred = generateFigmaSummary(node, {
350
- shouldPrintSource: true,
351
- shouldInferVariableName: false,
352
- shouldInferAutoLayout: true
383
+ const inferred = inferPipeline.generateCode(node, {
384
+ shouldPrintSource: true
353
385
  }) ?? "Failed to generate summarized node info";
354
386
  return formatObjectResponse({
355
387
  original: {
@@ -376,15 +408,19 @@ function registerTools(server, figmaClient) {
376
408
  });
377
409
  const normalizer = createRestNormalizer(result);
378
410
  const node = normalizer(result.document);
379
- const original = generateFigmaSummary(node, {
380
- shouldInferVariableName: false,
381
- shouldPrintSource: true,
382
- shouldInferAutoLayout: false
411
+ const noInferPipeline = figma.createPipeline({
412
+ shouldInferAutoLayout: false,
413
+ shouldInferVariableName: false
414
+ });
415
+ const inferPipeline = figma.createPipeline({
416
+ shouldInferAutoLayout: true,
417
+ shouldInferVariableName: true
418
+ });
419
+ const original = noInferPipeline.generateCode(node, {
420
+ shouldPrintSource: true
383
421
  }) ?? "Failed to generate summarized node info";
384
- const inferred = generateFigmaSummary(node, {
385
- shouldInferVariableName: true,
386
- shouldPrintSource: true,
387
- shouldInferAutoLayout: false
422
+ const inferred = inferPipeline.generateCode(node, {
423
+ shouldPrintSource: true
388
424
  }) ?? "Failed to generate summarized node info";
389
425
  return {
390
426
  nodeId,
@@ -411,10 +447,13 @@ function registerTools(server, figmaClient) {
411
447
  nodeId
412
448
  });
413
449
  const normalizer = createRestNormalizer(result);
414
- const code = generateCode(normalizer(result.document), {
450
+ const pipeline = react.createPipeline({
451
+ shouldInferAutoLayout: true,
415
452
  shouldInferVariableName: true,
416
- shouldPrintSource: false,
417
- shouldInferAutoLayout: true
453
+ extend
454
+ });
455
+ const code = pipeline.generateCode(normalizer(result.document), {
456
+ shouldPrintSource: false
418
457
  }) ?? "Failed to generate code";
419
458
  return formatTextResponse(code);
420
459
  } catch (error) {
@@ -636,7 +675,34 @@ function registerPrompts(server) {
636
675
  });
637
676
  }
638
677
 
639
- var version = "0.0.21";
678
+ var version = "0.0.23";
679
+
680
+ // Config loader
681
+ async function loadConfig(configPath) {
682
+ try {
683
+ const resolvedPath = path.resolve(process.cwd(), configPath);
684
+ if (!fs.existsSync(resolvedPath)) {
685
+ logger.error(`Config file not found: ${resolvedPath}`);
686
+ return null;
687
+ }
688
+ // Handle different file types
689
+ if (resolvedPath.endsWith(".json")) {
690
+ const content = fs.readFileSync(resolvedPath, "utf-8");
691
+ return JSON.parse(content);
692
+ }
693
+ if (resolvedPath.endsWith(".js") || resolvedPath.endsWith(".mjs") || resolvedPath.endsWith(".ts") || resolvedPath.endsWith(".mts")) {
694
+ // For JS/MJS/TS/MTS files, we can dynamically import with Bun
695
+ // Bun has built-in TypeScript support without requiring transpilation
696
+ const config = await import(resolvedPath);
697
+ return config.default || config;
698
+ }
699
+ logger.error(`Unsupported config file format: ${resolvedPath}`);
700
+ return null;
701
+ } catch (error) {
702
+ logger.error(`Failed to load config file: ${error instanceof Error ? error.message : String(error)}`);
703
+ return null;
704
+ }
705
+ }
640
706
 
641
707
  // Initialize CLI
642
708
  const cli = cac("@seed-design/mcp");
@@ -791,13 +857,28 @@ async function startWebSocketServer(port) {
791
857
  console.log(`WebSocket server running on port ${server.port}`);
792
858
  return server;
793
859
  }
794
- async function startMcpServer(serverUrl, experimental) {
860
+ async function startMcpServer(serverUrl, experimental, configPath) {
861
+ // Load config if provided
862
+ let configData = null;
863
+ if (configPath) {
864
+ configData = await loadConfig(configPath);
865
+ if (configData) {
866
+ logger.info(`Loaded configuration from: ${configPath}`);
867
+ // Log component transformers if present
868
+ if (configData.extend?.componentHandlers) {
869
+ const handlers = configData.extend.componentHandlers;
870
+ if (handlers.length > 0) {
871
+ logger.info(`Found ${handlers.length} custom component handlers`);
872
+ }
873
+ }
874
+ }
875
+ }
795
876
  const figmaClient = createFigmaWebSocketClient(serverUrl);
796
877
  const server = new McpServer({
797
878
  name: "SEED Design MCP",
798
879
  version
799
880
  });
800
- registerTools(server, figmaClient);
881
+ registerTools(server, figmaClient, configData);
801
882
  if (experimental) {
802
883
  registerEditingTools(server, figmaClient);
803
884
  }
@@ -817,8 +898,8 @@ cli.command("", "Start the MCP server").option("--server <server>", "Server URL"
817
898
  default: "localhost"
818
899
  }).option("--experimental", "Enable experimental features", {
819
900
  default: false
820
- }).action(async (options)=>{
821
- await startMcpServer(options.server, options.experimental);
901
+ }).option("--config <config>", "Path to configuration file (.js, .mjs, .ts, .mts)").action(async (options)=>{
902
+ await startMcpServer(options.server, options.experimental, options.config);
822
903
  });
823
904
  cli.command("socket", "Start the WebSocket server").option("--port <port>", "Port number", {
824
905
  default: 3055
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/mcp",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/sdk": "^1.10.2",
26
- "@seed-design/figma": "0.0.21",
26
+ "@seed-design/figma": "0.0.23",
27
27
  "cac": "^6.7.14",
28
28
  "uuid": "^11.1.0",
29
29
  "ws": "^8.18.1",
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,31 @@ 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?.componentHandlers) {
202
+ const handlers = configData.extend.componentHandlers;
203
+ if (handlers.length > 0) {
204
+ logger.info(`Found ${handlers.length} custom component handlers`);
205
+ }
206
+ }
207
+ }
208
+ }
209
+
192
210
  const figmaClient = createFigmaWebSocketClient(serverUrl);
193
211
  const server = new McpServer({
194
212
  name: "SEED Design MCP",
195
213
  version,
196
214
  });
197
215
 
198
- registerTools(server, figmaClient);
216
+ registerTools(server, figmaClient, configData);
199
217
  if (experimental) {
200
218
  registerEditingTools(server, figmaClient);
201
219
  }
@@ -220,8 +238,9 @@ cli
220
238
  .command("", "Start the MCP server")
221
239
  .option("--server <server>", "Server URL", { default: "localhost" })
222
240
  .option("--experimental", "Enable experimental features", { default: false })
241
+ .option("--config <config>", "Path to configuration file (.js, .mjs, .ts, .mts)")
223
242
  .action(async (options) => {
224
- await startMcpServer(options.server, options.experimental);
243
+ await startMcpServer(options.server, options.experimental, options.config);
225
244
  });
226
245
 
227
246
  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/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, figma, getFigmaColorVariableNames, react } 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,13 @@ 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 ?? {};
19
21
 
20
22
  // join_channel tool
21
23
  server.tool(
@@ -117,6 +119,46 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
117
119
  },
118
120
  );
119
121
 
122
+ // Component Info Tool
123
+ server.tool(
124
+ "get_component_info",
125
+ "Get detailed information about a specific component node in Figma",
126
+ {
127
+ nodeId: z.string().describe("The ID of the component node to get information about"),
128
+ },
129
+ async ({ nodeId }) => {
130
+ try {
131
+ const result = (await sendCommandToFigma("get_node_info", {
132
+ nodeId,
133
+ })) as GetFileNodesResponse["nodes"][string];
134
+
135
+ const node = result.document;
136
+ if (node.type !== "COMPONENT" && node.type !== "COMPONENT_SET") {
137
+ return formatErrorResponse(
138
+ "get_component_info",
139
+ new Error(`Node with ID ${nodeId} is not a component node`),
140
+ );
141
+ }
142
+
143
+ const key = result.componentSets[nodeId]?.key ?? result.components[nodeId]?.key;
144
+ if (!key) {
145
+ return formatErrorResponse(
146
+ "get_component_info",
147
+ new Error(`${nodeId} is not present in exported component data`),
148
+ );
149
+ }
150
+
151
+ return formatObjectResponse({
152
+ name: node.name,
153
+ key,
154
+ componentPropertyDefinitions: node.componentPropertyDefinitions,
155
+ });
156
+ } catch (error) {
157
+ return formatErrorResponse("get_node_info", error);
158
+ }
159
+ },
160
+ );
161
+
120
162
  // Node Info Tool
121
163
  server.tool(
122
164
  "get_node_info",
@@ -130,17 +172,21 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
130
172
  const normalizer = createRestNormalizer(result);
131
173
  const node = normalizer(result.document);
132
174
 
175
+ const noInferPipeline = figma.createPipeline({
176
+ shouldInferAutoLayout: false,
177
+ shouldInferVariableName: false,
178
+ });
179
+ const inferPipeline = figma.createPipeline({
180
+ shouldInferAutoLayout: true,
181
+ shouldInferVariableName: true,
182
+ });
133
183
  const original =
134
- generateFigmaSummary(node, {
184
+ noInferPipeline.generateCode(node, {
135
185
  shouldPrintSource: true,
136
- shouldInferVariableName: false,
137
- shouldInferAutoLayout: false,
138
186
  }) ?? "Failed to generate summarized node info";
139
187
  const inferred =
140
- generateFigmaSummary(node, {
188
+ inferPipeline.generateCode(node, {
141
189
  shouldPrintSource: true,
142
- shouldInferVariableName: false,
143
- shouldInferAutoLayout: true,
144
190
  }) ?? "Failed to generate summarized node info";
145
191
 
146
192
  return formatObjectResponse({
@@ -174,17 +220,21 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
174
220
  const normalizer = createRestNormalizer(result);
175
221
  const node = normalizer(result.document);
176
222
 
223
+ const noInferPipeline = figma.createPipeline({
224
+ shouldInferAutoLayout: false,
225
+ shouldInferVariableName: false,
226
+ });
227
+ const inferPipeline = figma.createPipeline({
228
+ shouldInferAutoLayout: true,
229
+ shouldInferVariableName: true,
230
+ });
177
231
  const original =
178
- generateFigmaSummary(node, {
179
- shouldInferVariableName: false,
232
+ noInferPipeline.generateCode(node, {
180
233
  shouldPrintSource: true,
181
- shouldInferAutoLayout: false,
182
234
  }) ?? "Failed to generate summarized node info";
183
235
  const inferred =
184
- generateFigmaSummary(node, {
185
- shouldInferVariableName: true,
236
+ inferPipeline.generateCode(node, {
186
237
  shouldPrintSource: true,
187
- shouldInferAutoLayout: false,
188
238
  }) ?? "Failed to generate summarized node info";
189
239
 
190
240
  return {
@@ -216,11 +266,14 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
216
266
  const result: any = await sendCommandToFigma("get_node_info", { nodeId });
217
267
  const normalizer = createRestNormalizer(result);
218
268
 
269
+ const pipeline = react.createPipeline({
270
+ shouldInferAutoLayout: true,
271
+ shouldInferVariableName: true,
272
+ extend,
273
+ });
219
274
  const code =
220
- generateCode(normalizer(result.document), {
221
- shouldInferVariableName: true,
275
+ pipeline.generateCode(normalizer(result.document), {
222
276
  shouldPrintSource: false,
223
- shouldInferAutoLayout: true,
224
277
  }) ?? "Failed to generate code";
225
278
 
226
279
  return formatTextResponse(code);
package/src/websocket.ts CHANGED
@@ -54,7 +54,7 @@ export function createFigmaWebSocketClient(serverUrl: string) {
54
54
  ws.on("open", () => {
55
55
  logger.info("Connected to Figma socket server");
56
56
  // Reset channel on new connection
57
- currentChannel = null;
57
+ joinChannel("local-default");
58
58
  });
59
59
 
60
60
  ws.on("message", (data: any) => {