figmatk 0.1.0 → 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.
package/mcp-server.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { FigDeck } from './lib/fig-deck.mjs';
|
|
9
|
+
import { Deck } from './lib/api.mjs';
|
|
9
10
|
import { nid, ov, nestedOv, removeNode, parseId, positionChar } from './lib/node-helpers.mjs';
|
|
10
11
|
import { imageOv, hexToHash, hashToHex } from './lib/image-helpers.mjs';
|
|
11
12
|
import { deepClone } from './lib/deep-clone.mjs';
|
|
@@ -246,6 +247,95 @@ server.tool(
|
|
|
246
247
|
}
|
|
247
248
|
);
|
|
248
249
|
|
|
250
|
+
// ── create-deck ─────────────────────────────────────────────────────────
|
|
251
|
+
const THEMES = {
|
|
252
|
+
midnight: { dark: 'Black', light: 'White', accent: { r: 0.792, g: 0.863, b: 0.988 }, textDark: 'White', textLight: 'Black' },
|
|
253
|
+
ocean: { dark: 'Blue', light: 'Pale Blue', accent: { r: 0.129, g: 0.161, b: 0.361 }, textDark: 'White', textLight: 'Black' },
|
|
254
|
+
forest: { dark: 'Green', light: 'Pale Green', accent: { r: 0.592, g: 0.737, b: 0.384 }, textDark: 'White', textLight: 'Black' },
|
|
255
|
+
coral: { dark: 'Persimmon', light: 'Pale Persimmon', accent: { r: 0.184, g: 0.235, b: 0.494 }, textDark: 'White', textLight: 'Black' },
|
|
256
|
+
terracotta: { dark: 'Persimmon', light: 'Pale Persimmon', accent: { r: 0.906, g: 0.910, b: 0.820 }, textDark: 'White', textLight: 'Black' },
|
|
257
|
+
minimal: { dark: 'Black', light: 'White', accent: { r: 0.212, g: 0.271, b: 0.310 }, textDark: 'White', textLight: 'Black' },
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const SlideSchema = z.object({
|
|
261
|
+
type: z.enum(['title', 'bullets', 'two-column', 'stat', 'image-full', 'closing']),
|
|
262
|
+
title: z.string().optional(),
|
|
263
|
+
subtitle: z.string().optional(),
|
|
264
|
+
body: z.string().optional(),
|
|
265
|
+
bullets: z.array(z.string()).optional(),
|
|
266
|
+
stat: z.string().optional(),
|
|
267
|
+
caption: z.string().optional(),
|
|
268
|
+
image: z.string().optional().describe('Absolute path to image file'),
|
|
269
|
+
leftText: z.string().optional(),
|
|
270
|
+
rightText: z.string().optional(),
|
|
271
|
+
background: z.string().optional().describe('Override background (named color)'),
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
server.tool(
|
|
275
|
+
'figmatk_create_deck',
|
|
276
|
+
'Create a new Figma Slides .deck file from a structured description. No npm install needed — runs directly in the MCP server.',
|
|
277
|
+
{
|
|
278
|
+
output: z.string().describe('Output path for the .deck file, e.g. /tmp/my-deck.deck'),
|
|
279
|
+
title: z.string().describe('Deck title'),
|
|
280
|
+
theme: z.string().optional().describe('Theme: midnight | ocean | forest | coral | terracotta | minimal (default: midnight)'),
|
|
281
|
+
slides: z.array(SlideSchema).describe('Slides to create'),
|
|
282
|
+
},
|
|
283
|
+
async ({ output, title, theme, slides }) => {
|
|
284
|
+
const t = THEMES[theme ?? 'midnight'] ?? THEMES.midnight;
|
|
285
|
+
const deck = await Deck.create(title);
|
|
286
|
+
|
|
287
|
+
for (const s of slides) {
|
|
288
|
+
const slide = deck.addBlankSlide();
|
|
289
|
+
const isDark = ['title', 'closing', 'stat', 'image-full'].includes(s.type);
|
|
290
|
+
const bg = s.background ?? (isDark ? t.dark : t.light);
|
|
291
|
+
const fg = isDark ? t.textDark : t.textLight;
|
|
292
|
+
slide.setBackground(bg);
|
|
293
|
+
|
|
294
|
+
if (s.type === 'title' || s.type === 'closing') {
|
|
295
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
296
|
+
if (s.title) slide.addText(s.title, { style: 'Title', color: fg, x: 80, y: 360, width: 1760, align: 'CENTER' });
|
|
297
|
+
if (s.subtitle) slide.addText(s.subtitle, { style: 'Body 1', color: fg, x: 80, y: 540, width: 1760, align: 'CENTER' });
|
|
298
|
+
|
|
299
|
+
} else if (s.type === 'bullets') {
|
|
300
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
301
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
302
|
+
const items = s.bullets ?? (s.body ? s.body.split('\n') : []);
|
|
303
|
+
let y = 240;
|
|
304
|
+
for (const item of items) {
|
|
305
|
+
slide.addRectangle(80, y + 10, 12, 12, { fill: t.accent });
|
|
306
|
+
slide.addText(item, { style: 'Body 1', color: fg, x: 116, y, width: 1724, align: 'LEFT' });
|
|
307
|
+
y += 80;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
} else if (s.type === 'two-column') {
|
|
311
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
312
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
313
|
+
slide.addRectangle(960, 200, 4, 800, { fill: t.accent });
|
|
314
|
+
if (s.leftText) slide.addText(s.leftText, { style: 'Body 1', color: fg, x: 80, y: 240, width: 840, align: 'LEFT' });
|
|
315
|
+
if (s.rightText) slide.addText(s.rightText, { style: 'Body 1', color: fg, x: 1004, y: 240, width: 836, align: 'LEFT' });
|
|
316
|
+
if (s.image) await slide.addImage(s.image, { x: 1004, y: 200, width: 836, height: 800 });
|
|
317
|
+
|
|
318
|
+
} else if (s.type === 'stat') {
|
|
319
|
+
slide.addRectangle(0, 0, 1920, 8, { fill: t.accent });
|
|
320
|
+
if (s.title) slide.addText(s.title, { style: 'Header 2', color: fg, x: 80, y: 80, width: 1760, align: 'LEFT' });
|
|
321
|
+
if (s.stat) slide.addText(s.stat, { style: 'Title', color: fg, x: 80, y: 300, width: 1760, align: 'CENTER' });
|
|
322
|
+
if (s.caption) slide.addText(s.caption, { style: 'Body 1', color: fg, x: 80, y: 720, width: 1760, align: 'CENTER' });
|
|
323
|
+
|
|
324
|
+
} else if (s.type === 'image-full') {
|
|
325
|
+
if (s.image) await slide.addImage(s.image, { x: 0, y: 0, width: 1920, height: 1080 });
|
|
326
|
+
if (s.title || s.body) {
|
|
327
|
+
slide.addRectangle(0, 680, 1920, 400, { fill: { r: 0, g: 0, b: 0 }, opacity: 0.7 });
|
|
328
|
+
if (s.title) slide.addText(s.title, { style: 'Header 1', color: 'White', x: 80, y: 720, width: 1760, align: 'LEFT' });
|
|
329
|
+
if (s.body) slide.addText(s.body, { style: 'Body 1', color: 'White', x: 80, y: 880, width: 1760, align: 'LEFT' });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await deck.save(output);
|
|
335
|
+
return { content: [{ type: 'text', text: `Created ${output} — ${slides.length} slides. Open in Figma Desktop.` }] };
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
249
339
|
// ── Start server ────────────────────────────────────────────────────────
|
|
250
340
|
const transport = new StdioServerTransport();
|
|
251
341
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ description: >
|
|
|
6
6
|
clone or remove slides, or produce a .deck file for Figma Slides.
|
|
7
7
|
Powered by FigmaTK under the hood.
|
|
8
8
|
metadata:
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.2.0"
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
# Figma Slides Creator
|
|
@@ -23,10 +23,10 @@ To let the user view the result: tell them to **open the file in Figma Desktop**
|
|
|
23
23
|
|
|
24
24
|
| Task | Approach |
|
|
25
25
|
|------|----------|
|
|
26
|
-
| Create a new deck from scratch |
|
|
27
|
-
| Edit text or images in an existing deck |
|
|
28
|
-
| Clone, remove, or restructure slides |
|
|
29
|
-
| Inspect structure or read content |
|
|
26
|
+
| Create a new deck from scratch | **`figmatk_create_deck` MCP tool** — no npm install needed |
|
|
27
|
+
| Edit text or images in an existing deck | `figmatk_update_text`, `figmatk_insert_image` |
|
|
28
|
+
| Clone, remove, or restructure slides | `figmatk_clone_slide`, `figmatk_remove_slide` |
|
|
29
|
+
| Inspect structure or read content | `figmatk_inspect`, `figmatk_list_text` |
|
|
30
30
|
|
|
31
31
|
---
|
|
32
32
|
|
|
@@ -36,25 +36,60 @@ To let the user view the result: tell them to **open the file in Figma Desktop**
|
|
|
36
36
|
|
|
37
37
|
---
|
|
38
38
|
|
|
39
|
-
## Path A — Create from Scratch (
|
|
39
|
+
## Path A — Create from Scratch (MCP tool — preferred)
|
|
40
|
+
|
|
41
|
+
**Always use this path.** No npm install, no scripts, no workspace setup.
|
|
42
|
+
|
|
43
|
+
Call `figmatk_create_deck` with a structured slide description:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"output": "/tmp/my-deck.deck",
|
|
48
|
+
"title": "My Presentation",
|
|
49
|
+
"theme": "midnight",
|
|
50
|
+
"slides": [
|
|
51
|
+
{ "type": "title", "title": "My Presentation", "subtitle": "A subtitle" },
|
|
52
|
+
{ "type": "bullets", "title": "Key Points", "bullets": ["Point one", "Point two", "Point three"] },
|
|
53
|
+
{ "type": "two-column", "title": "Comparison", "leftText": "Left side content", "rightText": "Right side content" },
|
|
54
|
+
{ "type": "stat", "title": "By the numbers", "stat": "42%", "caption": "of users prefer this" },
|
|
55
|
+
{ "type": "image-full", "image": "/tmp/photo.jpg", "title": "Caption text" },
|
|
56
|
+
{ "type": "closing", "title": "Thank you", "subtitle": "Questions?" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
40
60
|
|
|
41
|
-
|
|
61
|
+
### Slide types
|
|
42
62
|
|
|
43
|
-
|
|
63
|
+
| Type | Fields |
|
|
64
|
+
|------|--------|
|
|
65
|
+
| `title` | `title`, `subtitle` |
|
|
66
|
+
| `bullets` | `title`, `bullets` (array) |
|
|
67
|
+
| `two-column` | `title`, `leftText`, `rightText`, `image` (right side) |
|
|
68
|
+
| `stat` | `title`, `stat` (big number), `caption` |
|
|
69
|
+
| `image-full` | `image` (path), `title`, `body` (overlay text) |
|
|
70
|
+
| `closing` | `title`, `subtitle` |
|
|
44
71
|
|
|
45
|
-
|
|
72
|
+
### Themes
|
|
46
73
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
`midnight` · `ocean` · `forest` · `coral` · `terracotta` · `minimal`
|
|
75
|
+
|
|
76
|
+
Each theme handles backgrounds, accent colors, and text colors automatically.
|
|
77
|
+
|
|
78
|
+
---
|
|
50
79
|
|
|
51
|
-
|
|
80
|
+
## Path A2 — Create from Scratch (Node.js script fallback)
|
|
52
81
|
|
|
53
|
-
|
|
82
|
+
Only use this if `figmatk_create_deck` is unavailable or you need layout control beyond what the MCP tool offers.
|
|
54
83
|
|
|
55
|
-
|
|
84
|
+
### Step 1 — Set up workspace (MANDATORY — never skip)
|
|
56
85
|
|
|
57
|
-
|
|
86
|
+
```bash
|
|
87
|
+
[ -d /tmp/figmatk-ws/node_modules ] || (mkdir -p /tmp/figmatk-ws && cd /tmp/figmatk-ws && npm init -y && npm install figmatk)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Step 2 — Write script to `/tmp/figmatk-ws/deck.mjs`
|
|
91
|
+
|
|
92
|
+
**Always use bare specifier** `import { Deck } from 'figmatk'` — never a file path.
|
|
58
93
|
|
|
59
94
|
```javascript
|
|
60
95
|
import { Deck } from 'figmatk';
|
|
@@ -64,32 +99,19 @@ function hex(h) {
|
|
|
64
99
|
}
|
|
65
100
|
|
|
66
101
|
const deck = await Deck.create('My Presentation');
|
|
67
|
-
|
|
68
|
-
// Each call to addBlankSlide() returns a new blank slide.
|
|
69
|
-
// The template blank slide is auto-removed on the first call.
|
|
70
102
|
const slide = deck.addBlankSlide();
|
|
71
|
-
slide.setBackground('Black');
|
|
72
|
-
slide.addText('Slide Title', {
|
|
73
|
-
style: 'Title', color: 'White',
|
|
74
|
-
x: 64, y: 80, width: 1792, align: 'LEFT'
|
|
75
|
-
});
|
|
76
|
-
slide.addText('Subtitle', {
|
|
77
|
-
style: 'Body 1', color: 'Grey',
|
|
78
|
-
x: 64, y: 240, width: 1200, align: 'LEFT'
|
|
79
|
-
});
|
|
80
|
-
|
|
103
|
+
slide.setBackground('Black');
|
|
104
|
+
slide.addText('Slide Title', { style: 'Title', color: 'White', x: 64, y: 80, width: 1792, align: 'LEFT' });
|
|
81
105
|
await deck.save('/tmp/my-presentation.deck');
|
|
82
|
-
console.log('Done
|
|
106
|
+
console.log('Done');
|
|
83
107
|
```
|
|
84
108
|
|
|
85
|
-
### Step 3 — Run
|
|
109
|
+
### Step 3 — Run
|
|
86
110
|
|
|
87
111
|
```bash
|
|
88
112
|
node /tmp/figmatk-ws/deck.mjs
|
|
89
113
|
```
|
|
90
114
|
|
|
91
|
-
If this fails, check the error and fix the script — **do not change the workspace setup or the import path**.
|
|
92
|
-
|
|
93
115
|
### ⚠️ Critical gotchas
|
|
94
116
|
|
|
95
117
|
| Issue | Wrong | Right |
|
|
@@ -179,6 +201,7 @@ Use this when the user provides a `.deck` file to modify.
|
|
|
179
201
|
|
|
180
202
|
| Tool | Purpose |
|
|
181
203
|
|------|---------|
|
|
204
|
+
| `figmatk_create_deck` | **Create a new deck from scratch** — no npm install needed |
|
|
182
205
|
| `figmatk_inspect` | Node hierarchy tree — structure, node IDs, slide count |
|
|
183
206
|
| `figmatk_list_text` | All text strings and image hashes per slide |
|
|
184
207
|
| `figmatk_list_overrides` | Editable override keys per symbol (component) |
|