@seed-design/mcp 0.0.15 → 0.0.17

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.
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { cac } from 'cac';
2
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
4
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
5
  import require$$0 from 'stream';
@@ -11363,7 +11364,7 @@ function requireWebsocket () {
11363
11364
  }
11364
11365
 
11365
11366
  var websocketExports = requireWebsocket();
11366
- var WebSocket = /*@__PURE__*/getDefaultExportFromCjs(websocketExports);
11367
+ var WebSocket$1 = /*@__PURE__*/getDefaultExportFromCjs(websocketExports);
11367
11368
 
11368
11369
  var subprotocol;
11369
11370
  var hasRequiredSubprotocol;
@@ -11858,13 +11859,13 @@ function createFigmaWebSocketClient(serverUrl) {
11858
11859
  // Update the connectToFigma function
11859
11860
  function connectToFigma(port = 3055) {
11860
11861
  // If already connected, do nothing
11861
- if (ws && ws.readyState === WebSocket.OPEN) {
11862
+ if (ws && ws.readyState === WebSocket$1.OPEN) {
11862
11863
  logger.info("Already connected to Figma");
11863
11864
  return;
11864
11865
  }
11865
11866
  const wsUrl = serverUrl === "localhost" ? `${WS_URL}:${port}` : WS_URL;
11866
11867
  logger.info(`Connecting to Figma socket server at ${wsUrl}...`);
11867
- ws = new WebSocket(wsUrl);
11868
+ ws = new WebSocket$1(wsUrl);
11868
11869
  ws.on("open", ()=>{
11869
11870
  logger.info("Connected to Figma socket server");
11870
11871
  // Reset channel on new connection
@@ -11948,7 +11949,7 @@ function createFigmaWebSocketClient(serverUrl) {
11948
11949
  }
11949
11950
  // Function to join a channel
11950
11951
  async function joinChannel(channelName) {
11951
- if (!ws || ws.readyState !== WebSocket.OPEN) {
11952
+ if (!ws || ws.readyState !== WebSocket$1.OPEN) {
11952
11953
  throw new Error("Not connected to Figma");
11953
11954
  }
11954
11955
  try {
@@ -11966,7 +11967,7 @@ function createFigmaWebSocketClient(serverUrl) {
11966
11967
  function sendCommandToFigma(command, params = {}, timeoutMs = 30000) {
11967
11968
  return new Promise((resolve, reject)=>{
11968
11969
  // If not connected, try to connect first
11969
- if (!ws || ws.readyState !== WebSocket.OPEN) {
11970
+ if (!ws || ws.readyState !== WebSocket$1.OPEN) {
11970
11971
  connectToFigma();
11971
11972
  reject(new Error("Not connected to Figma. Attempting to connect..."));
11972
11973
  return;
@@ -16680,24 +16681,6 @@ function registerTools(server, figmaClient) {
16680
16681
  return formatErrorResponse("get_node_react_code", error);
16681
16682
  }
16682
16683
  });
16683
- // Clone Node Tool
16684
- server.tool("clone_node", "Clone an existing node in Figma", {
16685
- nodeId: z.string().describe("The ID of the node to clone"),
16686
- x: z.number().optional().describe("New X position for the clone"),
16687
- y: z.number().optional().describe("New Y position for the clone")
16688
- }, async ({ nodeId, x, y })=>{
16689
- try {
16690
- const result = await sendCommandToFigma("clone_node", {
16691
- nodeId,
16692
- x,
16693
- y
16694
- });
16695
- const typedResult = result;
16696
- return formatTextResponse(`Cloned node "${typedResult.name}" with new ID: ${typedResult.id}${x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : ""}`);
16697
- } catch (error) {
16698
- return formatErrorResponse("clone_node", error);
16699
- }
16700
- });
16701
16684
  // Export Node as Image Tool
16702
16685
  server.tool("export_node_as_image", "Export a node as an image from Figma", {
16703
16686
  nodeId: z.string().describe("The ID of the node to export"),
@@ -16737,6 +16720,27 @@ function registerTools(server, figmaClient) {
16737
16720
  return formatErrorResponse("retrieve_color_variable_names", error);
16738
16721
  }
16739
16722
  });
16723
+ }
16724
+ function registerEditingTools(server, figmaClient) {
16725
+ const { sendCommandToFigma } = figmaClient;
16726
+ // Clone Node Tool
16727
+ server.tool("clone_node", "Clone an existing node in Figma", {
16728
+ nodeId: z.string().describe("The ID of the node to clone"),
16729
+ x: z.number().optional().describe("New X position for the clone"),
16730
+ y: z.number().optional().describe("New Y position for the clone")
16731
+ }, async ({ nodeId, x, y })=>{
16732
+ try {
16733
+ const result = await sendCommandToFigma("clone_node", {
16734
+ nodeId,
16735
+ x,
16736
+ y
16737
+ });
16738
+ const typedResult = result;
16739
+ return formatTextResponse(`Cloned node "${typedResult.name}" with new ID: ${typedResult.id}${x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : ""}`);
16740
+ } catch (error) {
16741
+ return formatErrorResponse("clone_node", error);
16742
+ }
16743
+ });
16740
16744
  server.tool("set_fill_color", "Set the fill color of a node", {
16741
16745
  nodeId: z.string().describe("The ID of the node to set the fill color of"),
16742
16746
  colorToken: z.string().describe("The color token to set the fill color to. Format: `{category}/{name}`. Example: `bg/brand`")
@@ -16844,6 +16848,29 @@ function registerTools(server, figmaClient) {
16844
16848
  }
16845
16849
 
16846
16850
  function registerPrompts(server) {
16851
+ server.prompt("react_implementation_strategy", "Best practices for implementing React components", (_extra)=>{
16852
+ return {
16853
+ messages: [
16854
+ {
16855
+ role: "assistant",
16856
+ content: {
16857
+ type: "text",
16858
+ text: `When implementing React components, follow these best practices:
16859
+
16860
+ 1. Start with selection:
16861
+ - First use get_selection() to understand the current selection
16862
+ - If no selection ask user to select single node
16863
+
16864
+ 2. Get React code of the selected nodes:
16865
+ - Use get_node_react_code() to get the React code of the selected node
16866
+ - If no selection ask user to select single node
16867
+ `
16868
+ }
16869
+ }
16870
+ ],
16871
+ description: "Best practices for reading Figma designs"
16872
+ };
16873
+ });
16847
16874
  server.prompt("read_design_strategy", "Best practices for reading Figma designs", (_extra)=>{
16848
16875
  return {
16849
16876
  messages: [
@@ -16869,36 +16896,196 @@ function registerPrompts(server) {
16869
16896
  });
16870
16897
  }
16871
16898
 
16872
- // Add command line argument parsing
16873
- const args = process.argv.slice(2);
16874
- const serverArg = args.find((arg)=>arg.startsWith("--server="));
16875
- const serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
16876
- // Create Figma WebSocket client
16877
- const figmaClient = createFigmaWebSocketClient(serverUrl);
16878
- // Create MCP server
16879
- const server = new McpServer({
16880
- name: "SEED Design MCP",
16881
- version: "1.0.0"
16882
- });
16883
- // Register tools and prompts
16884
- registerTools(server, figmaClient);
16885
- registerPrompts(server);
16886
- // Start the server
16887
- async function main() {
16899
+ var version = "0.0.17";
16900
+
16901
+ // Initialize CLI
16902
+ const cli = cac("@seed-design/mcp");
16903
+ // Store WebSocket clients by channel
16904
+ const channels = new Map();
16905
+ function handleWebSocketConnection(ws) {
16906
+ console.log("New client connected");
16907
+ ws.send(JSON.stringify({
16908
+ type: "system",
16909
+ message: "Please join a channel to start chatting"
16910
+ }));
16911
+ ws.close = ()=>{
16912
+ console.log("Client disconnected");
16913
+ channels.forEach((clients, channelName)=>{
16914
+ if (clients.has(ws)) {
16915
+ clients.delete(ws);
16916
+ clients.forEach((client)=>{
16917
+ if (client.readyState === WebSocket.OPEN) {
16918
+ client.send(JSON.stringify({
16919
+ type: "system",
16920
+ message: "A user has left the channel",
16921
+ channel: channelName
16922
+ }));
16923
+ }
16924
+ });
16925
+ }
16926
+ });
16927
+ };
16928
+ }
16929
+ async function startWebSocketServer(port) {
16930
+ const server = Bun.serve({
16931
+ port,
16932
+ // uncomment this to allow connections in windows wsl
16933
+ // hostname: "0.0.0.0",
16934
+ fetch (req, server) {
16935
+ // Handle CORS preflight
16936
+ if (req.method === "OPTIONS") {
16937
+ return new Response(null, {
16938
+ headers: {
16939
+ "Access-Control-Allow-Origin": "*",
16940
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
16941
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
16942
+ }
16943
+ });
16944
+ }
16945
+ // Handle WebSocket upgrade
16946
+ const success = server.upgrade(req, {
16947
+ headers: {
16948
+ "Access-Control-Allow-Origin": "*"
16949
+ }
16950
+ });
16951
+ if (success) return;
16952
+ // Return response for non-WebSocket requests
16953
+ return new Response("WebSocket server running", {
16954
+ headers: {
16955
+ "Access-Control-Allow-Origin": "*"
16956
+ }
16957
+ });
16958
+ },
16959
+ websocket: {
16960
+ open: handleWebSocketConnection,
16961
+ message (ws, message) {
16962
+ try {
16963
+ console.log("Received message from client:", message);
16964
+ const data = JSON.parse(message);
16965
+ if (data.type === "join") {
16966
+ const channelName = data.channel;
16967
+ if (!channelName || typeof channelName !== "string") {
16968
+ ws.send(JSON.stringify({
16969
+ type: "error",
16970
+ message: "Channel name is required"
16971
+ }));
16972
+ return;
16973
+ }
16974
+ // Create channel if it doesn't exist
16975
+ if (!channels.has(channelName)) {
16976
+ channels.set(channelName, new Set());
16977
+ }
16978
+ // Add client to channel
16979
+ const channelClients = channels.get(channelName);
16980
+ channelClients.add(ws);
16981
+ // Notify client they joined successfully
16982
+ ws.send(JSON.stringify({
16983
+ type: "system",
16984
+ message: `Joined channel: ${channelName}`,
16985
+ channel: channelName
16986
+ }));
16987
+ console.log("Sending message to client:", data.id);
16988
+ ws.send(JSON.stringify({
16989
+ type: "system",
16990
+ message: {
16991
+ id: data.id,
16992
+ result: "Connected to channel: " + channelName
16993
+ },
16994
+ channel: channelName
16995
+ }));
16996
+ // Notify other clients in channel
16997
+ channelClients.forEach((client)=>{
16998
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
16999
+ client.send(JSON.stringify({
17000
+ type: "system",
17001
+ message: "A new user has joined the channel",
17002
+ channel: channelName
17003
+ }));
17004
+ }
17005
+ });
17006
+ return;
17007
+ }
17008
+ // Handle regular messages
17009
+ if (data.type === "message") {
17010
+ const channelName = data.channel;
17011
+ if (!channelName || typeof channelName !== "string") {
17012
+ ws.send(JSON.stringify({
17013
+ type: "error",
17014
+ message: "Channel name is required"
17015
+ }));
17016
+ return;
17017
+ }
17018
+ const channelClients = channels.get(channelName);
17019
+ if (!channelClients || !channelClients.has(ws)) {
17020
+ ws.send(JSON.stringify({
17021
+ type: "error",
17022
+ message: "You must join the channel first"
17023
+ }));
17024
+ return;
17025
+ }
17026
+ // Broadcast to all clients in the channel
17027
+ channelClients.forEach((client)=>{
17028
+ if (client.readyState === WebSocket.OPEN) {
17029
+ console.log("Broadcasting message to client:", data.message);
17030
+ client.send(JSON.stringify({
17031
+ type: "broadcast",
17032
+ message: data.message,
17033
+ sender: client === ws ? "You" : "User",
17034
+ channel: channelName
17035
+ }));
17036
+ }
17037
+ });
17038
+ }
17039
+ } catch (err) {
17040
+ console.error("Error handling message:", err);
17041
+ }
17042
+ },
17043
+ close (ws) {
17044
+ // Remove client from their channel
17045
+ channels.forEach((clients)=>{
17046
+ clients.delete(ws);
17047
+ });
17048
+ }
17049
+ }
17050
+ });
17051
+ console.log(`WebSocket server running on port ${server.port}`);
17052
+ return server;
17053
+ }
17054
+ async function startMcpServer(serverUrl, experimental) {
17055
+ const figmaClient = createFigmaWebSocketClient(serverUrl);
17056
+ const server = new McpServer({
17057
+ name: "SEED Design MCP",
17058
+ version
17059
+ });
17060
+ registerTools(server, figmaClient);
17061
+ if (experimental) {
17062
+ registerEditingTools(server, figmaClient);
17063
+ }
17064
+ registerPrompts(server);
16888
17065
  try {
16889
- // Try to connect to Figma socket server
16890
17066
  figmaClient.connectToFigma();
16891
17067
  } catch (error) {
16892
17068
  logger.warn(`Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`);
16893
17069
  logger.warn("Will try to connect when the first command is sent");
16894
17070
  }
16895
- // Start the MCP server with stdio transport
16896
17071
  const transport = new StdioServerTransport();
16897
17072
  await server.connect(transport);
16898
17073
  logger.info("FigmaMCP server running on stdio");
16899
17074
  }
16900
- // Run the server
16901
- main().catch((error)=>{
16902
- logger.error(`Error starting FigmaMCP server: ${error instanceof Error ? error.message : String(error)}`);
16903
- process.exit(1);
17075
+ // Define CLI commands
17076
+ cli.command("", "Start the MCP server").option("--server <server>", "Server URL", {
17077
+ default: "localhost"
17078
+ }).option("--experimental", "Enable experimental features", {
17079
+ default: false
17080
+ }).action(async (options)=>{
17081
+ await startMcpServer(options.server, options.experimental);
17082
+ });
17083
+ cli.command("socket", "Start the WebSocket server").option("--port <port>", "Port number", {
17084
+ default: 3055
17085
+ }).action(async (options)=>{
17086
+ await startWebSocketServer(options.port);
16904
17087
  });
17088
+ cli.help();
17089
+ cli.version(version);
17090
+ // Parse CLI args
17091
+ cli.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seed-design/mcp",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/daangn/seed-design.git",
@@ -12,21 +12,19 @@
12
12
  "sideEffects": false,
13
13
  "files": [
14
14
  "bin",
15
- "src"
15
+ "src",
16
+ "package.json"
16
17
  ],
17
- "bin": {
18
- "main": "./bin/main.mjs",
19
- "socket": "./bin/socket.mjs"
20
- },
18
+ "bin": "./bin/index.mjs",
21
19
  "scripts": {
22
20
  "clean": "rm -rf lib",
23
- "dev": "bun run --watch ./src/bin/socket.ts",
24
21
  "build": "bunchee",
25
22
  "lint:publish": "bun publint"
26
23
  },
27
24
  "dependencies": {
28
25
  "@modelcontextprotocol/sdk": "^1.7.0",
29
- "@seed-design/figma": "0.0.15",
26
+ "@seed-design/figma": "0.0.17",
27
+ "cac": "^6.7.14",
30
28
  "express": "^4.21.2",
31
29
  "yargs": "^17.7.2"
32
30
  },
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cac } from "cac";
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { logger } from "../logger";
7
+ import { createFigmaWebSocketClient } from "../websocket";
8
+ import { registerEditingTools, registerTools } from "../tools";
9
+ import { registerPrompts } from "../prompts";
10
+ import { version } from "../../package.json" assert { type: "json" };
11
+ import type { Server, ServerWebSocket } from "bun";
12
+
13
+ // Initialize CLI
14
+ const cli = cac("@seed-design/mcp");
15
+
16
+ // Store WebSocket clients by channel
17
+ const channels = new Map<string, Set<ServerWebSocket<any>>>();
18
+
19
+ function handleWebSocketConnection(ws: ServerWebSocket<any>) {
20
+ console.log("New client connected");
21
+
22
+ ws.send(
23
+ JSON.stringify({
24
+ type: "system",
25
+ message: "Please join a channel to start chatting",
26
+ }),
27
+ );
28
+
29
+ ws.close = () => {
30
+ console.log("Client disconnected");
31
+ channels.forEach((clients, channelName) => {
32
+ if (clients.has(ws)) {
33
+ clients.delete(ws);
34
+ clients.forEach((client) => {
35
+ if (client.readyState === WebSocket.OPEN) {
36
+ client.send(
37
+ JSON.stringify({
38
+ type: "system",
39
+ message: "A user has left the channel",
40
+ channel: channelName,
41
+ }),
42
+ );
43
+ }
44
+ });
45
+ }
46
+ });
47
+ };
48
+ }
49
+
50
+ async function startWebSocketServer(port: number) {
51
+ const server = Bun.serve({
52
+ port,
53
+ // uncomment this to allow connections in windows wsl
54
+ // hostname: "0.0.0.0",
55
+ fetch(req: Request, server: Server) {
56
+ // Handle CORS preflight
57
+ if (req.method === "OPTIONS") {
58
+ return new Response(null, {
59
+ headers: {
60
+ "Access-Control-Allow-Origin": "*",
61
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
62
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
63
+ },
64
+ });
65
+ }
66
+
67
+ // Handle WebSocket upgrade
68
+ const success = server.upgrade(req, {
69
+ headers: {
70
+ "Access-Control-Allow-Origin": "*",
71
+ },
72
+ });
73
+
74
+ if (success) return;
75
+
76
+ // Return response for non-WebSocket requests
77
+ return new Response("WebSocket server running", {
78
+ headers: {
79
+ "Access-Control-Allow-Origin": "*",
80
+ },
81
+ });
82
+ },
83
+ websocket: {
84
+ open: handleWebSocketConnection,
85
+ message(ws: ServerWebSocket<any>, message: string | Buffer) {
86
+ try {
87
+ console.log("Received message from client:", message);
88
+ const data = JSON.parse(message as string);
89
+
90
+ if (data.type === "join") {
91
+ const channelName = data.channel;
92
+ if (!channelName || typeof channelName !== "string") {
93
+ ws.send(JSON.stringify({ type: "error", message: "Channel name is required" }));
94
+ return;
95
+ }
96
+
97
+ // Create channel if it doesn't exist
98
+ if (!channels.has(channelName)) {
99
+ channels.set(channelName, new Set());
100
+ }
101
+
102
+ // Add client to channel
103
+ const channelClients = channels.get(channelName)!;
104
+ channelClients.add(ws);
105
+
106
+ // Notify client they joined successfully
107
+ ws.send(
108
+ JSON.stringify({
109
+ type: "system",
110
+ message: `Joined channel: ${channelName}`,
111
+ channel: channelName,
112
+ }),
113
+ );
114
+
115
+ console.log("Sending message to client:", data.id);
116
+
117
+ ws.send(
118
+ JSON.stringify({
119
+ type: "system",
120
+ message: {
121
+ id: data.id,
122
+ result: "Connected to channel: " + channelName,
123
+ },
124
+ channel: channelName,
125
+ }),
126
+ );
127
+
128
+ // Notify other clients in channel
129
+ channelClients.forEach((client) => {
130
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
131
+ client.send(
132
+ JSON.stringify({
133
+ type: "system",
134
+ message: "A new user has joined the channel",
135
+ channel: channelName,
136
+ }),
137
+ );
138
+ }
139
+ });
140
+ return;
141
+ }
142
+
143
+ // Handle regular messages
144
+ if (data.type === "message") {
145
+ const channelName = data.channel;
146
+ if (!channelName || typeof channelName !== "string") {
147
+ ws.send(JSON.stringify({ type: "error", message: "Channel name is required" }));
148
+ return;
149
+ }
150
+
151
+ const channelClients = channels.get(channelName);
152
+ if (!channelClients || !channelClients.has(ws)) {
153
+ ws.send(
154
+ JSON.stringify({ type: "error", message: "You must join the channel first" }),
155
+ );
156
+ return;
157
+ }
158
+
159
+ // Broadcast to all clients in the channel
160
+ channelClients.forEach((client) => {
161
+ if (client.readyState === WebSocket.OPEN) {
162
+ console.log("Broadcasting message to client:", data.message);
163
+ client.send(
164
+ JSON.stringify({
165
+ type: "broadcast",
166
+ message: data.message,
167
+ sender: client === ws ? "You" : "User",
168
+ channel: channelName,
169
+ }),
170
+ );
171
+ }
172
+ });
173
+ }
174
+ } catch (err) {
175
+ console.error("Error handling message:", err);
176
+ }
177
+ },
178
+ close(ws: ServerWebSocket<any>) {
179
+ // Remove client from their channel
180
+ channels.forEach((clients) => {
181
+ clients.delete(ws);
182
+ });
183
+ },
184
+ },
185
+ });
186
+
187
+ console.log(`WebSocket server running on port ${server.port}`);
188
+ return server;
189
+ }
190
+
191
+ async function startMcpServer(serverUrl: string, experimental: boolean) {
192
+ const figmaClient = createFigmaWebSocketClient(serverUrl);
193
+ const server = new McpServer({
194
+ name: "SEED Design MCP",
195
+ version,
196
+ });
197
+
198
+ registerTools(server, figmaClient);
199
+ if (experimental) {
200
+ registerEditingTools(server, figmaClient);
201
+ }
202
+ registerPrompts(server);
203
+
204
+ try {
205
+ figmaClient.connectToFigma();
206
+ } catch (error) {
207
+ logger.warn(
208
+ `Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`,
209
+ );
210
+ logger.warn("Will try to connect when the first command is sent");
211
+ }
212
+
213
+ const transport = new StdioServerTransport();
214
+ await server.connect(transport);
215
+ logger.info("FigmaMCP server running on stdio");
216
+ }
217
+
218
+ // Define CLI commands
219
+ cli
220
+ .command("", "Start the MCP server")
221
+ .option("--server <server>", "Server URL", { default: "localhost" })
222
+ .option("--experimental", "Enable experimental features", { default: false })
223
+ .action(async (options) => {
224
+ await startMcpServer(options.server, options.experimental);
225
+ });
226
+
227
+ cli
228
+ .command("socket", "Start the WebSocket server")
229
+ .option("--port <port>", "Port number", { default: 3055 })
230
+ .action(async (options) => {
231
+ await startWebSocketServer(options.port);
232
+ });
233
+
234
+ cli.help();
235
+ cli.version(version);
236
+
237
+ // Parse CLI args
238
+ cli.parse();
package/src/prompts.ts CHANGED
@@ -1,6 +1,34 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
 
3
3
  export function registerPrompts(server: McpServer): void {
4
+ server.prompt(
5
+ "react_implementation_strategy",
6
+ "Best practices for implementing React components",
7
+ (_extra) => {
8
+ return {
9
+ messages: [
10
+ {
11
+ role: "assistant",
12
+ content: {
13
+ type: "text",
14
+ text: `When implementing React components, follow these best practices:
15
+
16
+ 1. Start with selection:
17
+ - First use get_selection() to understand the current selection
18
+ - If no selection ask user to select single node
19
+
20
+ 2. Get React code of the selected nodes:
21
+ - Use get_node_react_code() to get the React code of the selected node
22
+ - If no selection ask user to select single node
23
+ `,
24
+ },
25
+ },
26
+ ],
27
+ description: "Best practices for reading Figma designs",
28
+ };
29
+ },
30
+ );
31
+
4
32
  server.prompt("read_design_strategy", "Best practices for reading Figma designs", (_extra) => {
5
33
  return {
6
34
  messages: [
package/src/tools.ts CHANGED
@@ -230,28 +230,6 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
230
230
  },
231
231
  );
232
232
 
233
- // Clone Node Tool
234
- server.tool(
235
- "clone_node",
236
- "Clone an existing node in Figma",
237
- {
238
- nodeId: z.string().describe("The ID of the node to clone"),
239
- x: z.number().optional().describe("New X position for the clone"),
240
- y: z.number().optional().describe("New Y position for the clone"),
241
- },
242
- async ({ nodeId, x, y }) => {
243
- try {
244
- const result = await sendCommandToFigma("clone_node", { nodeId, x, y });
245
- const typedResult = result as { name: string; id: string };
246
- return formatTextResponse(
247
- `Cloned node "${typedResult.name}" with new ID: ${typedResult.id}${x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : ""}`,
248
- );
249
- } catch (error) {
250
- return formatErrorResponse("clone_node", error);
251
- }
252
- },
253
- );
254
-
255
233
  // Export Node as Image Tool
256
234
  server.tool(
257
235
  "export_node_as_image",
@@ -295,6 +273,32 @@ export function registerTools(server: McpServer, figmaClient: FigmaWebSocketClie
295
273
  }
296
274
  },
297
275
  );
276
+ }
277
+
278
+ export function registerEditingTools(server: McpServer, figmaClient: FigmaWebSocketClient): void {
279
+ const { sendCommandToFigma } = figmaClient;
280
+
281
+ // Clone Node Tool
282
+ server.tool(
283
+ "clone_node",
284
+ "Clone an existing node in Figma",
285
+ {
286
+ nodeId: z.string().describe("The ID of the node to clone"),
287
+ x: z.number().optional().describe("New X position for the clone"),
288
+ y: z.number().optional().describe("New Y position for the clone"),
289
+ },
290
+ async ({ nodeId, x, y }) => {
291
+ try {
292
+ const result = await sendCommandToFigma("clone_node", { nodeId, x, y });
293
+ const typedResult = result as { name: string; id: string };
294
+ return formatTextResponse(
295
+ `Cloned node "${typedResult.name}" with new ID: ${typedResult.id}${x !== undefined && y !== undefined ? ` at position (${x}, ${y})` : ""}`,
296
+ );
297
+ } catch (error) {
298
+ return formatErrorResponse("clone_node", error);
299
+ }
300
+ },
301
+ );
298
302
 
299
303
  server.tool(
300
304
  "set_fill_color",
package/bin/socket.mjs DELETED
@@ -1,155 +0,0 @@
1
- #!/usr/bin/env node
2
- // Store clients by channel
3
- const channels = new Map();
4
- function handleConnection(ws) {
5
- // Don't add to clients immediately - wait for channel join
6
- console.log("New client connected");
7
- // Send welcome message to the new client
8
- ws.send(JSON.stringify({
9
- type: "system",
10
- message: "Please join a channel to start chatting"
11
- }));
12
- ws.close = ()=>{
13
- console.log("Client disconnected");
14
- // Remove client from their channel
15
- channels.forEach((clients, channelName)=>{
16
- if (clients.has(ws)) {
17
- clients.delete(ws);
18
- // Notify other clients in same channel
19
- clients.forEach((client)=>{
20
- if (client.readyState === WebSocket.OPEN) {
21
- client.send(JSON.stringify({
22
- type: "system",
23
- message: "A user has left the channel",
24
- channel: channelName
25
- }));
26
- }
27
- });
28
- }
29
- });
30
- };
31
- }
32
- const server = Bun.serve({
33
- port: 3055,
34
- // uncomment this to allow connections in windows wsl
35
- // hostname: "0.0.0.0",
36
- fetch (req, server) {
37
- // Handle CORS preflight
38
- if (req.method === "OPTIONS") {
39
- return new Response(null, {
40
- headers: {
41
- "Access-Control-Allow-Origin": "*",
42
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
43
- "Access-Control-Allow-Headers": "Content-Type, Authorization"
44
- }
45
- });
46
- }
47
- // Handle WebSocket upgrade
48
- const success = server.upgrade(req, {
49
- headers: {
50
- "Access-Control-Allow-Origin": "*"
51
- }
52
- });
53
- if (success) {
54
- return; // Upgraded to WebSocket
55
- }
56
- // Return response for non-WebSocket requests
57
- return new Response("WebSocket server running", {
58
- headers: {
59
- "Access-Control-Allow-Origin": "*"
60
- }
61
- });
62
- },
63
- websocket: {
64
- open: handleConnection,
65
- message (ws, message) {
66
- try {
67
- console.log("Received message from client:", message);
68
- const data = JSON.parse(message);
69
- if (data.type === "join") {
70
- const channelName = data.channel;
71
- if (!channelName || typeof channelName !== "string") {
72
- ws.send(JSON.stringify({
73
- type: "error",
74
- message: "Channel name is required"
75
- }));
76
- return;
77
- }
78
- // Create channel if it doesn't exist
79
- if (!channels.has(channelName)) {
80
- channels.set(channelName, new Set());
81
- }
82
- // Add client to channel
83
- const channelClients = channels.get(channelName);
84
- channelClients.add(ws);
85
- // Notify client they joined successfully
86
- ws.send(JSON.stringify({
87
- type: "system",
88
- message: `Joined channel: ${channelName}`,
89
- channel: channelName
90
- }));
91
- console.log("Sending message to client:", data.id);
92
- ws.send(JSON.stringify({
93
- type: "system",
94
- message: {
95
- id: data.id,
96
- result: "Connected to channel: " + channelName
97
- },
98
- channel: channelName
99
- }));
100
- // Notify other clients in channel
101
- channelClients.forEach((client)=>{
102
- if (client !== ws && client.readyState === WebSocket.OPEN) {
103
- client.send(JSON.stringify({
104
- type: "system",
105
- message: "A new user has joined the channel",
106
- channel: channelName
107
- }));
108
- }
109
- });
110
- return;
111
- }
112
- // Handle regular messages
113
- if (data.type === "message") {
114
- const channelName = data.channel;
115
- if (!channelName || typeof channelName !== "string") {
116
- ws.send(JSON.stringify({
117
- type: "error",
118
- message: "Channel name is required"
119
- }));
120
- return;
121
- }
122
- const channelClients = channels.get(channelName);
123
- if (!channelClients || !channelClients.has(ws)) {
124
- ws.send(JSON.stringify({
125
- type: "error",
126
- message: "You must join the channel first"
127
- }));
128
- return;
129
- }
130
- // Broadcast to all clients in the channel
131
- channelClients.forEach((client)=>{
132
- if (client.readyState === WebSocket.OPEN) {
133
- console.log("Broadcasting message to client:", data.message);
134
- client.send(JSON.stringify({
135
- type: "broadcast",
136
- message: data.message,
137
- sender: client === ws ? "You" : "User",
138
- channel: channelName
139
- }));
140
- }
141
- });
142
- }
143
- } catch (err) {
144
- console.error("Error handling message:", err);
145
- }
146
- },
147
- close (ws) {
148
- // Remove client from their channel
149
- channels.forEach((clients)=>{
150
- clients.delete(ws);
151
- });
152
- }
153
- }
154
- });
155
- console.log(`WebSocket server running on port ${server.port}`);
package/src/bin/main.ts DELETED
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { logger } from "../logger";
6
- import { createFigmaWebSocketClient } from "../websocket";
7
- import { registerTools } from "../tools";
8
- import { registerPrompts } from "../prompts";
9
-
10
- // Add command line argument parsing
11
- const args = process.argv.slice(2);
12
- const serverArg = args.find((arg) => arg.startsWith("--server="));
13
- const serverUrl = serverArg ? serverArg.split("=")[1] : "localhost";
14
-
15
- // Create Figma WebSocket client
16
- const figmaClient = createFigmaWebSocketClient(serverUrl);
17
-
18
- // Create MCP server
19
- const server = new McpServer({
20
- name: "SEED Design MCP",
21
- version: "1.0.0",
22
- });
23
-
24
- // Register tools and prompts
25
- registerTools(server, figmaClient);
26
- registerPrompts(server);
27
-
28
- // Start the server
29
- async function main() {
30
- try {
31
- // Try to connect to Figma socket server
32
- figmaClient.connectToFigma();
33
- } catch (error) {
34
- logger.warn(
35
- `Could not connect to Figma initially: ${error instanceof Error ? error.message : String(error)}`,
36
- );
37
- logger.warn("Will try to connect when the first command is sent");
38
- }
39
-
40
- // Start the MCP server with stdio transport
41
- const transport = new StdioServerTransport();
42
- await server.connect(transport);
43
- logger.info("FigmaMCP server running on stdio");
44
- }
45
-
46
- // Run the server
47
- main().catch((error) => {
48
- logger.error(
49
- `Error starting FigmaMCP server: ${error instanceof Error ? error.message : String(error)}`,
50
- );
51
- process.exit(1);
52
- });
package/src/bin/socket.ts DELETED
@@ -1,194 +0,0 @@
1
- import type { Server, ServerWebSocket } from "bun";
2
-
3
- // Store clients by channel
4
- const channels = new Map<string, Set<ServerWebSocket<any>>>();
5
-
6
- function handleConnection(ws: ServerWebSocket<any>) {
7
- // Don't add to clients immediately - wait for channel join
8
- console.log("New client connected");
9
-
10
- // Send welcome message to the new client
11
- ws.send(
12
- JSON.stringify({
13
- type: "system",
14
- message: "Please join a channel to start chatting",
15
- }),
16
- );
17
-
18
- ws.close = () => {
19
- console.log("Client disconnected");
20
-
21
- // Remove client from their channel
22
- channels.forEach((clients, channelName) => {
23
- if (clients.has(ws)) {
24
- clients.delete(ws);
25
-
26
- // Notify other clients in same channel
27
- clients.forEach((client) => {
28
- if (client.readyState === WebSocket.OPEN) {
29
- client.send(
30
- JSON.stringify({
31
- type: "system",
32
- message: "A user has left the channel",
33
- channel: channelName,
34
- }),
35
- );
36
- }
37
- });
38
- }
39
- });
40
- };
41
- }
42
-
43
- const server = Bun.serve({
44
- port: 3055,
45
- // uncomment this to allow connections in windows wsl
46
- // hostname: "0.0.0.0",
47
- fetch(req: Request, server: Server) {
48
- // Handle CORS preflight
49
- if (req.method === "OPTIONS") {
50
- return new Response(null, {
51
- headers: {
52
- "Access-Control-Allow-Origin": "*",
53
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
54
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
55
- },
56
- });
57
- }
58
-
59
- // Handle WebSocket upgrade
60
- const success = server.upgrade(req, {
61
- headers: {
62
- "Access-Control-Allow-Origin": "*",
63
- },
64
- });
65
-
66
- if (success) {
67
- return; // Upgraded to WebSocket
68
- }
69
-
70
- // Return response for non-WebSocket requests
71
- return new Response("WebSocket server running", {
72
- headers: {
73
- "Access-Control-Allow-Origin": "*",
74
- },
75
- });
76
- },
77
- websocket: {
78
- open: handleConnection,
79
- message(ws: ServerWebSocket<any>, message: string | Buffer) {
80
- try {
81
- console.log("Received message from client:", message);
82
- const data = JSON.parse(message as string);
83
-
84
- if (data.type === "join") {
85
- const channelName = data.channel;
86
- if (!channelName || typeof channelName !== "string") {
87
- ws.send(
88
- JSON.stringify({
89
- type: "error",
90
- message: "Channel name is required",
91
- }),
92
- );
93
- return;
94
- }
95
-
96
- // Create channel if it doesn't exist
97
- if (!channels.has(channelName)) {
98
- channels.set(channelName, new Set());
99
- }
100
-
101
- // Add client to channel
102
- const channelClients = channels.get(channelName)!;
103
- channelClients.add(ws);
104
-
105
- // Notify client they joined successfully
106
- ws.send(
107
- JSON.stringify({
108
- type: "system",
109
- message: `Joined channel: ${channelName}`,
110
- channel: channelName,
111
- }),
112
- );
113
-
114
- console.log("Sending message to client:", data.id);
115
-
116
- ws.send(
117
- JSON.stringify({
118
- type: "system",
119
- message: {
120
- id: data.id,
121
- result: "Connected to channel: " + channelName,
122
- },
123
- channel: channelName,
124
- }),
125
- );
126
-
127
- // Notify other clients in channel
128
- channelClients.forEach((client) => {
129
- if (client !== ws && client.readyState === WebSocket.OPEN) {
130
- client.send(
131
- JSON.stringify({
132
- type: "system",
133
- message: "A new user has joined the channel",
134
- channel: channelName,
135
- }),
136
- );
137
- }
138
- });
139
- return;
140
- }
141
-
142
- // Handle regular messages
143
- if (data.type === "message") {
144
- const channelName = data.channel;
145
- if (!channelName || typeof channelName !== "string") {
146
- ws.send(
147
- JSON.stringify({
148
- type: "error",
149
- message: "Channel name is required",
150
- }),
151
- );
152
- return;
153
- }
154
-
155
- const channelClients = channels.get(channelName);
156
- if (!channelClients || !channelClients.has(ws)) {
157
- ws.send(
158
- JSON.stringify({
159
- type: "error",
160
- message: "You must join the channel first",
161
- }),
162
- );
163
- return;
164
- }
165
-
166
- // Broadcast to all clients in the channel
167
- channelClients.forEach((client) => {
168
- if (client.readyState === WebSocket.OPEN) {
169
- console.log("Broadcasting message to client:", data.message);
170
- client.send(
171
- JSON.stringify({
172
- type: "broadcast",
173
- message: data.message,
174
- sender: client === ws ? "You" : "User",
175
- channel: channelName,
176
- }),
177
- );
178
- }
179
- });
180
- }
181
- } catch (err) {
182
- console.error("Error handling message:", err);
183
- }
184
- },
185
- close(ws: ServerWebSocket<any>) {
186
- // Remove client from their channel
187
- channels.forEach((clients) => {
188
- clients.delete(ws);
189
- });
190
- },
191
- },
192
- });
193
-
194
- console.log(`WebSocket server running on port ${server.port}`);