@seed-design/mcp 0.0.21 → 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 +86 -12
- package/package.json +2 -2
- package/src/bin/index.ts +24 -3
- package/src/config.ts +47 -0
- package/src/tools.ts +59 -12
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
@@ -636,7 +668,34 @@ function registerPrompts(server) {
|
|
|
636
668
|
});
|
|
637
669
|
}
|
|
638
670
|
|
|
639
|
-
var version = "0.0.
|
|
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.
|
|
3
|
+
"version": "0.0.22",
|
|
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.
|
|
26
|
+
"@seed-design/figma": "0.0.22",
|
|
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,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/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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|