with-figma 0.1.5 → 0.2.0

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.
@@ -13,9 +13,8 @@
13
13
  height: 100vh;
14
14
  }
15
15
 
16
- /* ─── Header ─── */
17
16
  .header {
18
- padding: 12px 16px;
17
+ padding: 10px 16px;
19
18
  border-bottom: 1px solid var(--figma-color-border, #e5e5e5);
20
19
  display: flex;
21
20
  align-items: center;
@@ -25,13 +24,14 @@
25
24
  width: 8px; height: 8px;
26
25
  border-radius: 50%;
27
26
  background: #ccc;
27
+ flex-shrink: 0;
28
28
  }
29
29
  .status-dot.connected { background: #18a957; }
30
30
  .status-dot.connecting { background: #f5a623; animation: pulse 1s infinite; }
31
31
  @keyframes pulse { 50% { opacity: 0.5; } }
32
- .header-title { font-weight: 600; flex: 1; }
32
+ .header-title { font-weight: 600; flex: 1; font-size: 13px; }
33
+ .header-status { font-size: 11px; color: var(--figma-color-text-secondary, #999); }
33
34
 
34
- /* ─── Selection Info ─── */
35
35
  .selection-bar {
36
36
  padding: 8px 16px;
37
37
  background: var(--figma-color-bg-secondary, #f5f5f5);
@@ -44,96 +44,69 @@
44
44
  color: var(--figma-color-text, #333);
45
45
  }
46
46
 
47
- /* ─── Chat Area ─── */
48
- .chat-area {
47
+ /* Activity Log */
48
+ .log-area {
49
49
  flex: 1;
50
50
  overflow-y: auto;
51
- padding: 16px;
51
+ padding: 12px 16px;
52
52
  display: flex;
53
53
  flex-direction: column;
54
- gap: 12px;
54
+ gap: 6px;
55
55
  }
56
- .message {
57
- max-width: 85%;
58
- padding: 10px 14px;
59
- border-radius: 12px;
56
+ .log-entry {
57
+ display: flex;
58
+ gap: 8px;
59
+ align-items: flex-start;
60
+ font-size: 12px;
60
61
  line-height: 1.5;
61
- word-break: break-word;
62
- }
63
- .message.user {
64
- align-self: flex-end;
65
- background: #0d99ff;
66
- color: #fff;
67
- border-bottom-right-radius: 4px;
68
- }
69
- .message.assistant {
70
- align-self: flex-start;
71
- background: var(--figma-color-bg-secondary, #f0f0f0);
72
- border-bottom-left-radius: 4px;
62
+ padding: 4px 0;
73
63
  }
74
- .message.system {
75
- align-self: center;
64
+ .log-entry .log-time {
65
+ color: var(--figma-color-text-tertiary, #aaa);
76
66
  font-size: 11px;
77
- color: var(--figma-color-text-tertiary, #999);
78
- background: none;
79
- padding: 4px;
67
+ flex-shrink: 0;
68
+ font-variant-numeric: tabular-nums;
80
69
  }
81
-
82
- /* ─── Input Area ─── */
83
- .input-area {
84
- padding: 12px 16px;
85
- border-top: 1px solid var(--figma-color-border, #e5e5e5);
86
- display: flex;
87
- gap: 8px;
70
+ .log-entry .log-icon {
71
+ flex-shrink: 0;
72
+ width: 16px;
73
+ text-align: center;
88
74
  }
89
- .input-area textarea {
90
- flex: 1;
91
- border: 1px solid var(--figma-color-border, #ddd);
92
- border-radius: 8px;
93
- padding: 8px 12px;
94
- font-size: 13px;
95
- font-family: inherit;
96
- resize: none;
97
- outline: none;
98
- background: var(--figma-color-bg, #fff);
75
+ .log-entry .log-text {
99
76
  color: var(--figma-color-text, #333);
100
- min-height: 40px;
101
- max-height: 120px;
77
+ word-break: break-word;
102
78
  }
103
- .input-area textarea:focus { border-color: #0d99ff; }
104
- .input-area button {
105
- background: #0d99ff;
106
- color: #fff;
107
- border: none;
108
- border-radius: 8px;
109
- padding: 8px 16px;
110
- font-size: 13px;
111
- font-weight: 600;
112
- cursor: pointer;
113
- align-self: flex-end;
79
+ .log-entry.status .log-text { color: var(--figma-color-text-secondary, #888); }
80
+ .log-entry.action .log-text { color: #0d99ff; }
81
+ .log-entry.success .log-text { color: #18a957; }
82
+ .log-entry.error .log-text { color: #f24822; }
83
+
84
+ .empty-state {
85
+ flex: 1;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ color: var(--figma-color-text-tertiary, #aaa);
90
+ font-size: 12px;
91
+ text-align: center;
92
+ padding: 40px;
93
+ line-height: 1.6;
114
94
  }
115
- .input-area button:hover { background: #0b85e0; }
116
- .input-area button:disabled { opacity: 0.5; cursor: not-allowed; }
117
95
  </style>
118
96
  </head>
119
97
  <body>
120
98
  <div class="header">
121
99
  <div class="status-dot" id="statusDot"></div>
122
- <span class="header-title">With Figma AI</span>
123
- <span id="statusText" style="font-size:11px; color:#999">Disconnected</span>
124
- </div>
125
-
126
- <div class="selection-bar" id="selectionBar">
127
- No selection
100
+ <span class="header-title">With Figma</span>
101
+ <span class="header-status" id="statusText">Disconnected</span>
128
102
  </div>
129
103
 
130
- <div class="chat-area" id="chatArea">
131
- <div class="message system">Select an element and describe what you want to do.</div>
132
- </div>
104
+ <div class="selection-bar" id="selectionBar">No selection</div>
133
105
 
134
- <div class="input-area">
135
- <textarea id="input" placeholder="이 요소를 어떻게 작업할까요?" rows="1"></textarea>
136
- <button id="sendBtn" disabled>Send</button>
106
+ <div class="log-area" id="logArea">
107
+ <div class="empty-state" id="emptyState">
108
+ Select elements in Figma,<br>then tell your AI agent in VS Code<br>what to do with them.
109
+ </div>
137
110
  </div>
138
111
 
139
112
  <script>
@@ -144,9 +117,8 @@
144
117
  let currentPage = null;
145
118
  let reconnectTimer = null;
146
119
 
147
- const chatArea = document.getElementById("chatArea");
148
- const input = document.getElementById("input");
149
- const sendBtn = document.getElementById("sendBtn");
120
+ const logArea = document.getElementById("logArea");
121
+ const emptyState = document.getElementById("emptyState");
150
122
  const statusDot = document.getElementById("statusDot");
151
123
  const statusText = document.getElementById("statusText");
152
124
  const selectionBar = document.getElementById("selectionBar");
@@ -156,19 +128,13 @@
156
128
  function connect() {
157
129
  if (ws && ws.readyState <= 1) return;
158
130
  setStatus("connecting");
159
-
160
131
  ws = new WebSocket(WS_URL);
161
132
 
162
133
  ws.onopen = () => {
163
134
  setStatus("connected");
164
- addMessage("system", "Connected to AI server.");
165
- // Send current selection state
135
+ addLog("status", "Connected to MCP server");
166
136
  if (currentSelection.length > 0) {
167
- ws.send(JSON.stringify({
168
- type: "selection-update",
169
- nodes: currentSelection,
170
- page: currentPage,
171
- }));
137
+ ws.send(JSON.stringify({ type: "selection-update", nodes: currentSelection, page: currentPage }));
172
138
  }
173
139
  };
174
140
 
@@ -176,9 +142,7 @@
176
142
  try {
177
143
  const msg = JSON.parse(event.data);
178
144
  handleServerMessage(msg);
179
- } catch (e) {
180
- console.error("Failed to parse message:", e);
181
- }
145
+ } catch (e) {}
182
146
  };
183
147
 
184
148
  ws.onclose = () => {
@@ -186,9 +150,7 @@
186
150
  scheduleReconnect();
187
151
  };
188
152
 
189
- ws.onerror = () => {
190
- ws.close();
191
- };
153
+ ws.onerror = () => ws.close();
192
154
  }
193
155
 
194
156
  function scheduleReconnect() {
@@ -199,28 +161,23 @@
199
161
  function setStatus(state) {
200
162
  statusDot.className = "status-dot " + (state === "connected" ? "connected" : state === "connecting" ? "connecting" : "");
201
163
  statusText.textContent = state === "connected" ? "Connected" : state === "connecting" ? "Connecting..." : "Disconnected";
202
- sendBtn.disabled = state !== "connected";
203
164
  }
204
165
 
205
- // ─── Server message handling ──────────────────────────────────
166
+ // ─── Server messages ──────────────────────────────────────────
206
167
 
207
168
  function handleServerMessage(msg) {
208
169
  switch (msg.type) {
209
- case "chat-response":
210
- addMessage("assistant", msg.text);
170
+ case "activity":
171
+ addLog(msg.level || "status", msg.text);
211
172
  break;
212
-
213
173
  case "figma-command":
214
- // Relay command to Figma plugin sandbox
215
174
  parent.postMessage({ pluginMessage: msg.command }, "*");
216
175
  break;
217
-
218
176
  case "request-selection":
219
177
  parent.postMessage({ pluginMessage: { type: "get-selection" } }, "*");
220
178
  break;
221
-
222
179
  case "status":
223
- addMessage("system", msg.text);
180
+ addLog("status", msg.text);
224
181
  break;
225
182
  }
226
183
  }
@@ -236,13 +193,8 @@
236
193
  currentSelection = msg.nodes;
237
194
  currentPage = msg.page;
238
195
  updateSelectionUI();
239
- // Forward to server
240
196
  if (ws && ws.readyState === WebSocket.OPEN) {
241
- ws.send(JSON.stringify({
242
- type: "selection-update",
243
- nodes: msg.nodes,
244
- page: msg.page,
245
- }));
197
+ ws.send(JSON.stringify({ type: "selection-update", nodes: msg.nodes, page: msg.page }));
246
198
  }
247
199
  break;
248
200
 
@@ -252,7 +204,6 @@
252
204
  }
253
205
  break;
254
206
 
255
- // Forward all result messages to server
256
207
  case "node-data":
257
208
  case "node-created":
258
209
  case "node-modified":
@@ -265,57 +216,46 @@
265
216
  }
266
217
  };
267
218
 
268
- // ─── UI Helpers ───────────────────────────────────────────────
219
+ // ─── UI ───────────────────────────────────────────────────────
269
220
 
270
221
  function updateSelectionUI() {
271
222
  if (currentSelection.length === 0) {
272
223
  selectionBar.innerHTML = "No selection";
273
224
  } else if (currentSelection.length === 1) {
274
225
  const n = currentSelection[0];
275
- selectionBar.innerHTML = `<span class="node-name">${n.name}</span> (${n.type}) — ${Math.round(n.width)}×${Math.round(n.height)}`;
226
+ selectionBar.innerHTML = '<span class="node-name">' + esc(n.name) + '</span> (' + n.type + ') — ' + Math.round(n.width) + '×' + Math.round(n.height);
276
227
  } else {
277
- selectionBar.innerHTML = `<span class="node-name">${currentSelection.length} elements</span> selected`;
228
+ selectionBar.innerHTML = '<span class="node-name">' + currentSelection.length + ' elements</span> selected';
278
229
  }
279
230
  }
280
231
 
281
- function addMessage(role, text) {
282
- const div = document.createElement("div");
283
- div.className = "message " + role;
284
- div.textContent = text;
285
- chatArea.appendChild(div);
286
- chatArea.scrollTop = chatArea.scrollHeight;
287
- }
232
+ const ICONS = { status: "○", action: "▶", success: "✓", error: "✗" };
233
+
234
+ function addLog(level, text) {
235
+ if (emptyState) emptyState.remove();
236
+ const entry = document.createElement("div");
237
+ entry.className = "log-entry " + level;
288
238
 
289
- // ─── User Input ───────────────────────────────────────────────
290
-
291
- function sendMessage() {
292
- const text = input.value.trim();
293
- if (!text || !ws || ws.readyState !== WebSocket.OPEN) return;
294
-
295
- addMessage("user", text);
296
- ws.send(JSON.stringify({
297
- type: "chat-message",
298
- text,
299
- selection: currentSelection,
300
- page: currentPage,
301
- }));
302
- input.value = "";
303
- input.style.height = "auto";
239
+ const now = new Date();
240
+ const time = now.getHours().toString().padStart(2, "0") + ":" +
241
+ now.getMinutes().toString().padStart(2, "0") + ":" +
242
+ now.getSeconds().toString().padStart(2, "0");
243
+
244
+ entry.innerHTML =
245
+ '<span class="log-time">' + time + '</span>' +
246
+ '<span class="log-icon">' + (ICONS[level] || "○") + '</span>' +
247
+ '<span class="log-text">' + esc(text) + '</span>';
248
+
249
+ logArea.appendChild(entry);
250
+ logArea.scrollTop = logArea.scrollHeight;
304
251
  }
305
252
 
306
- sendBtn.addEventListener("click", sendMessage);
307
- input.addEventListener("keydown", (e) => {
308
- if (e.key === "Enter" && !e.shiftKey) {
309
- e.preventDefault();
310
- sendMessage();
311
- }
312
- });
313
- input.addEventListener("input", () => {
314
- input.style.height = "auto";
315
- input.style.height = Math.min(input.scrollHeight, 120) + "px";
316
- });
253
+ function esc(s) {
254
+ const d = document.createElement("div");
255
+ d.textContent = s;
256
+ return d.innerHTML;
257
+ }
317
258
 
318
- // Start connection
319
259
  connect();
320
260
  </script>
321
261
  </body>
@@ -30120,16 +30120,21 @@ var currentPage = null;
30120
30120
  var pages = [];
30121
30121
  var pendingRequests = /* @__PURE__ */ new Map();
30122
30122
  var requestCounter = 0;
30123
- var chatQueue = [];
30124
30123
  function genRequestId() {
30125
30124
  return `req_${++requestCounter}_${Date.now()}`;
30126
30125
  }
30126
+ function sendActivity(text, level = "status") {
30127
+ if (figmaSocket && figmaSocket.readyState === import_ws.WebSocket.OPEN) {
30128
+ figmaSocket.send(JSON.stringify({ type: "activity", text, level }));
30129
+ }
30130
+ console.error(`[with-figma] [${level}] ${text}`);
30131
+ }
30127
30132
  var wss = new import_ws.WebSocketServer({ port: 3055 });
30128
30133
  console.error("[with-figma] WebSocket server listening on ws://127.0.0.1:3055");
30129
30134
  wss.on("connection", (socket) => {
30130
30135
  console.error("[with-figma] Figma plugin connected");
30131
30136
  figmaSocket = socket;
30132
- socket.send(JSON.stringify({ type: "status", text: "MCP server connected. Ready for commands." }));
30137
+ sendActivity("MCP server connected. Waiting for agent commands.");
30133
30138
  socket.on("message", (raw) => {
30134
30139
  try {
30135
30140
  const msg = JSON.parse(raw.toString());
@@ -30152,24 +30157,6 @@ function handleFigmaMessage(msg) {
30152
30157
  case "pages-list":
30153
30158
  pages = msg.pages || [];
30154
30159
  break;
30155
- case "chat-message":
30156
- currentSelection = msg.selection || currentSelection;
30157
- currentPage = msg.page || currentPage;
30158
- chatQueue.push({
30159
- text: msg.text,
30160
- selection: msg.selection || [],
30161
- page: msg.page || null,
30162
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
30163
- });
30164
- console.error(`[with-figma] Chat message queued: "${msg.text}"`);
30165
- if (figmaSocket) {
30166
- figmaSocket.send(JSON.stringify({
30167
- type: "chat-response",
30168
- text: `Queued for AI agent. Tell Codex in VS Code:
30169
- "Check Figma messages and process them"`
30170
- }));
30171
- }
30172
- break;
30173
30160
  // Results from Figma plugin operations
30174
30161
  case "node-data":
30175
30162
  case "node-created":
@@ -30206,7 +30193,7 @@ function sendToFigma(command, timeoutMs = 1e4) {
30206
30193
  }
30207
30194
  var server = new McpServer({
30208
30195
  name: "with-figma",
30209
- version: "0.1.0"
30196
+ version: "0.2.0"
30210
30197
  });
30211
30198
  server.resource("selection", "figma://selection", async (uri) => ({
30212
30199
  contents: [
@@ -30230,24 +30217,25 @@ server.tool(
30230
30217
  "get_selection",
30231
30218
  "Get the currently selected elements in Figma. Returns node details including type, size, position, and properties.",
30232
30219
  {},
30233
- async () => ({
30234
- content: [
30235
- {
30236
- type: "text",
30237
- text: currentSelection.length > 0 ? JSON.stringify({ page: currentPage, nodes: currentSelection }, null, 2) : "No elements are currently selected in Figma."
30238
- }
30239
- ]
30240
- })
30220
+ async () => {
30221
+ sendActivity("Inspecting current selection...", "action");
30222
+ const result = currentSelection.length > 0 ? JSON.stringify({ page: currentPage, nodes: currentSelection }, null, 2) : "No elements are currently selected in Figma.";
30223
+ sendActivity(
30224
+ currentSelection.length > 0 ? `Found ${currentSelection.length} selected element(s)` : "No elements selected",
30225
+ currentSelection.length > 0 ? "success" : "status"
30226
+ );
30227
+ return { content: [{ type: "text", text: result }] };
30228
+ }
30241
30229
  );
30242
30230
  server.tool(
30243
30231
  "get_node",
30244
30232
  "Get detailed information about a specific Figma node by ID.",
30245
30233
  { nodeId: external_exports3.string().describe("The Figma node ID") },
30246
30234
  async ({ nodeId }) => {
30235
+ sendActivity(`Inspecting node ${nodeId}...`, "action");
30247
30236
  const result = await sendToFigma({ type: "get-node", nodeId });
30248
- return {
30249
- content: [{ type: "text", text: JSON.stringify(result.node, null, 2) }]
30250
- };
30237
+ sendActivity(`Inspected: ${result.node?.name || nodeId}`, "success");
30238
+ return { content: [{ type: "text", text: JSON.stringify(result.node, null, 2) }] };
30251
30239
  }
30252
30240
  );
30253
30241
  server.tool(
@@ -30261,10 +30249,10 @@ server.tool(
30261
30249
  y: external_exports3.number().default(0).describe("Y position")
30262
30250
  },
30263
30251
  async (params) => {
30252
+ sendActivity(`Creating frame "${params.name}" (${params.width}\xD7${params.height})...`, "action");
30264
30253
  const result = await sendToFigma({ type: "create-frame", ...params });
30265
- return {
30266
- content: [{ type: "text", text: `Frame created: ${JSON.stringify(result.node, null, 2)}` }]
30267
- };
30254
+ sendActivity(`Created frame "${params.name}"`, "success");
30255
+ return { content: [{ type: "text", text: `Frame created: ${JSON.stringify(result.node, null, 2)}` }] };
30268
30256
  }
30269
30257
  );
30270
30258
  server.tool(
@@ -30281,15 +30269,15 @@ server.tool(
30281
30269
  fillColor: external_exports3.object({ r: external_exports3.number(), g: external_exports3.number(), b: external_exports3.number(), a: external_exports3.number().default(1) }).optional().describe("Fill color (RGBA 0-1)")
30282
30270
  },
30283
30271
  async (params) => {
30272
+ sendActivity(`Creating rectangle "${params.name}" (${params.width}\xD7${params.height})...`, "action");
30284
30273
  const command = { type: "create-rectangle", ...params };
30285
30274
  if (params.fillColor) {
30286
30275
  command.fills = [{ type: "SOLID", color: params.fillColor }];
30287
30276
  delete command.fillColor;
30288
30277
  }
30289
30278
  const result = await sendToFigma(command);
30290
- return {
30291
- content: [{ type: "text", text: `Rectangle created: ${JSON.stringify(result.node, null, 2)}` }]
30292
- };
30279
+ sendActivity(`Created rectangle "${params.name}"`, "success");
30280
+ return { content: [{ type: "text", text: `Rectangle created: ${JSON.stringify(result.node, null, 2)}` }] };
30293
30281
  }
30294
30282
  );
30295
30283
  server.tool(
@@ -30305,15 +30293,15 @@ server.tool(
30305
30293
  fillColor: external_exports3.object({ r: external_exports3.number(), g: external_exports3.number(), b: external_exports3.number(), a: external_exports3.number().default(1) }).optional().describe("Text color (RGBA 0-1)")
30306
30294
  },
30307
30295
  async (params) => {
30296
+ sendActivity(`Creating text "${params.characters.slice(0, 30)}${params.characters.length > 30 ? "..." : ""}"...`, "action");
30308
30297
  const command = { type: "create-text", ...params };
30309
30298
  if (params.fillColor) {
30310
30299
  command.fills = [{ type: "SOLID", color: params.fillColor }];
30311
30300
  delete command.fillColor;
30312
30301
  }
30313
30302
  const result = await sendToFigma(command);
30314
- return {
30315
- content: [{ type: "text", text: `Text created: ${JSON.stringify(result.node, null, 2)}` }]
30316
- };
30303
+ sendActivity(`Created text "${params.name}"`, "success");
30304
+ return { content: [{ type: "text", text: `Text created: ${JSON.stringify(result.node, null, 2)}` }] };
30317
30305
  }
30318
30306
  );
30319
30307
  server.tool(
@@ -30324,10 +30312,11 @@ server.tool(
30324
30312
  properties: external_exports3.record(external_exports3.unknown()).describe("Object of properties to set (e.g. { x: 10, name: 'New Name', visible: false })")
30325
30313
  },
30326
30314
  async ({ nodeId, properties }) => {
30315
+ const propKeys = Object.keys(properties).join(", ");
30316
+ sendActivity(`Modifying node ${nodeId} (${propKeys})...`, "action");
30327
30317
  const result = await sendToFigma({ type: "modify-node", nodeId, properties });
30328
- return {
30329
- content: [{ type: "text", text: `Node modified: ${JSON.stringify(result.node, null, 2)}` }]
30330
- };
30318
+ sendActivity(`Modified: ${result.node?.name || nodeId}`, "success");
30319
+ return { content: [{ type: "text", text: `Node modified: ${JSON.stringify(result.node, null, 2)}` }] };
30331
30320
  }
30332
30321
  );
30333
30322
  server.tool(
@@ -30335,10 +30324,10 @@ server.tool(
30335
30324
  "Delete a node from the Figma document.",
30336
30325
  { nodeId: external_exports3.string().describe("The node ID to delete") },
30337
30326
  async ({ nodeId }) => {
30327
+ sendActivity(`Deleting node ${nodeId}...`, "action");
30338
30328
  const result = await sendToFigma({ type: "delete-node", nodeId });
30339
- return {
30340
- content: [{ type: "text", text: `Deleted node: ${result.nodeId}` }]
30341
- };
30329
+ sendActivity(`Deleted node ${result.nodeId}`, "success");
30330
+ return { content: [{ type: "text", text: `Deleted node: ${result.nodeId}` }] };
30342
30331
  }
30343
30332
  );
30344
30333
  server.tool(
@@ -30350,7 +30339,9 @@ server.tool(
30350
30339
  scale: external_exports3.number().default(2).describe("Export scale factor")
30351
30340
  },
30352
30341
  async ({ nodeId, format, scale }) => {
30342
+ sendActivity(`Exporting node ${nodeId} as ${format}...`, "action");
30353
30343
  const result = await sendToFigma({ type: "export-node", nodeId, format, scale });
30344
+ sendActivity(`Exported as ${format}`, "success");
30354
30345
  if (format === "SVG") {
30355
30346
  const text = new TextDecoder().decode(new Uint8Array(result.data));
30356
30347
  return { content: [{ type: "text", text }] };
@@ -30368,37 +30359,15 @@ server.tool(
30368
30359
  }
30369
30360
  );
30370
30361
  server.tool(
30371
- "get_chat_messages",
30372
- "Get pending chat messages from the Figma plugin. Users send design requests via the Figma chat UI. Call this tool to check for new messages, then use other Figma tools to fulfill the requests. After processing, the queue is cleared.",
30373
- {},
30374
- async () => {
30375
- if (chatQueue.length === 0) {
30376
- return {
30377
- content: [{ type: "text", text: "No pending messages from Figma." }]
30378
- };
30379
- }
30380
- const messages = [...chatQueue];
30381
- chatQueue.length = 0;
30382
- return {
30383
- content: [
30384
- {
30385
- type: "text",
30386
- text: JSON.stringify(messages, null, 2)
30387
- }
30388
- ]
30389
- };
30390
- }
30391
- );
30392
- server.tool(
30393
- "send_chat_message",
30394
- "Send a message back to the Figma plugin chat UI to communicate with the user.",
30395
- { text: external_exports3.string().describe("Message text to display in chat") },
30396
- async ({ text }) => {
30397
- if (figmaSocket && figmaSocket.readyState === import_ws.WebSocket.OPEN) {
30398
- figmaSocket.send(JSON.stringify({ type: "chat-response", text }));
30399
- return { content: [{ type: "text", text: `Message sent to Figma chat: "${text}"` }] };
30400
- }
30401
- return { content: [{ type: "text", text: "Error: Figma plugin is not connected" }] };
30362
+ "send_activity",
30363
+ "Send an activity/status message to the Figma plugin log panel.",
30364
+ {
30365
+ text: external_exports3.string().describe("Activity message to display"),
30366
+ level: external_exports3.enum(["status", "action", "success", "error"]).default("status").describe("Log level")
30367
+ },
30368
+ async ({ text, level }) => {
30369
+ sendActivity(text, level);
30370
+ return { content: [{ type: "text", text: `Activity logged: "${text}"` }] };
30402
30371
  }
30403
30372
  );
30404
30373
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "with-figma",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server that connects AI agents to Figma via WebSocket",
5
5
  "license": "MIT",
6
6
  "bin": {