slide-mcp
by Rikuto-des
README.md
# slide-mcp
Create **designed Figma Slides** from a prompt — using **your own LLM API key** (BYOK).
Type *"Make an 8-slide product intro"* in the plugin, and a polished, multi-layout
deck appears in your open Figma Slides file — **39 layouts, 17 design systems**,
charts, images, tables, and more. Everything runs inside the plugin: **no server,
no account, and your API key never leaves your machine.**
## Contents
- [How it works](#how-it-works)
- [Install & build](#install--build)
- [Load the plugin in Figma](#load-the-plugin-in-figma)
- [Use it (BYOK)](#use-it-byok)
- [Direct JSON (no LLM)](#direct-json-no-llm)
- [IDE / MCP mode (optional)](#ide--mcp-mode-optional--copilot--use-a-subscription)
- [Choosing a design system](#choosing-a-design-system)
- [`create_deck` data format](#create_deck-data-format)
- [Layout reference](#layout-reference)
- [Design systems](#design-systems-themepreset)
- [Troubleshooting](#troubleshooting)
- [Updating the plugin](#updating-the-plugin)
- [Project structure](#project-structure)
- [Notes & limitations](#notes--limitations)
## How it works
The default mode is **BYOK** — a single Figma Slides plugin, no backend:
1. You enter your **API key** and a **topic** in the plugin panel.
2. The plugin calls your selected LLM (**Anthropic**, **Google Gemini**, or
**OpenAI**) with your key, asking it to design the deck and return it as
`create_deck` JSON (structure, layouts, colors).
3. The plugin's layout engine builds the slides in Figma.
You can also **paste a `create_deck` JSON directly** to build an exact deck
without calling any LLM (see [Direct JSON](#direct-json-no-llm)), or drive it
from an **IDE assistant** via the bundled MCP server (see
[IDE / MCP mode](#ide--mcp-mode-optional--copilot--use-a-subscription)) — useful
when you'd rather use a Copilot/Claude subscription than per-token API billing.
> The API key is stored locally via Figma's `clientStorage` (this machine only)
> and is sent only to the provider you choose.
## Install & build
The plugin loads from local files (`manifest.json` → `code.js` + `ui.html`), so
build it once after cloning:
```bash
git clone https://github.com/Rikuto-des/slide-mcp.git
cd slide-mcp
npm install && npm run build # builds the plugin → code.js
```
> `code.js` and `node_modules/` are git-ignored, so you build after cloning.
## Load the plugin in Figma
1. Open the **Figma desktop app** and open (or create) a **Figma Slides** file.
2. Menu → **Plugins → Development → Import plugin from manifest…**
3. Select **`manifest.json`** from this repo.
4. Run it: **Plugins → Development → slide-mcp**.
You only **import** once. After code changes you just **re-run** it (a
`manifest.json` change needs a re-import — see [Updating](#updating-the-plugin)).
## Use it (BYOK)
In the plugin panel:
1. **Provider** — choose Anthropic (Claude), Google Gemini, or OpenAI (GPT).
2. **API key** — paste your own key (stored on this machine only; remembered per
provider). Get one:
- Anthropic → [console.anthropic.com](https://console.anthropic.com/settings/keys)
- Gemini → [aistudio.google.com](https://aistudio.google.com/app/apikey)
- OpenAI → [platform.openai.com](https://platform.openai.com/api-keys)
3. **Model** — pick or type a model id (suggestions per provider).
4. *(optional)* **Design system** — pick one, or let the AI choose.
5. **Topic** — describe the deck, then press **生成 / Generate**.
Example topic:
> 自社プロダクトの紹介を8枚で。表紙、課題、解決策、主要機能3つ、実績の数値、料金、まとめ。
For very large decks (e.g. all 39 layouts) prefer a strong model — output can be
long. See [examples/](examples) for ready-made prompts.
## Direct JSON (no LLM)
Open **"JSON直貼り"** in the panel and paste a `create_deck` JSON
(`{ "slides": [ ... ] }`) to build an exact deck with no API call — handy for
reproducible decks or testing every layout. Example:
[examples/all-layouts.json](examples/all-layouts.json) renders all 39 layouts,
one per slide.
## IDE / MCP mode (optional — Copilot / use a subscription)
BYOK bills per token against your own API key. If you'd rather drive the plugin
from an **IDE assistant you already pay for** (GitHub Copilot, or Claude
Code/Desktop), use the bundled MCP server instead — the assistant calls the
`create_deck` tool and the plugin builds the slides. The plugin itself never
touches your subscription, so this stays within each tool's own terms.
```
Copilot / Claude ──stdio──▶ slide-mcp-server (MCP + WS bridge) ──WS──▶ this plugin
```
1. Start the MCP server. The repo ships `.vscode/mcp.json`, which runs the
published server via `npx` (`npx -y slide-mcp-server@latest`). In VS Code,
start the **`slide-mcp`** server from the MCP list (Copilot Agent mode).
2. In the plugin panel, open **"IDE / MCP 連携"** and press **接続**. It connects
to `ws://localhost:3055` and flips to **接続済み**.
3. Ask the assistant: *"slide-mcp で〜のデッキを作って"*.
### Running in GitHub Codespaces (private tunnel, browser-only OK)
Figma runs on your local machine, so the plugin must reach the bridge inside the
Codespace. Use GitHub's **authenticated, private** port forward — no public
exposure:
1. In the Codespace, the server binds `0.0.0.0` (set in `.vscode/mcp.json` via
`SLIDE_MCP_HOST=0.0.0.0`). Start it from the Copilot MCP list.
2. On your **local machine**, forward the port (works even if the Codespace is
browser-only):
```bash
gh codespace ports forward 3055:3055 -c <codespace-name>
# optional one-word alias in ~/.zshrc:
# alias slidetunnel='gh codespace ports forward 3055:3055 -c <codespace-name>'
```
The port stays **Private** (GitHub-authenticated) — it is not made public.
3. The plugin's `ws://localhost:3055` now tunnels to the Codespace. Press **接続**.
> Run the server in **one** place only. Don't also start a local
> `slide-mcp-server` while the tunnel is up — two servers fighting for port 3055
> is the usual cause of "connected but nothing happens".
## Choosing a design system
You can set the look in **three** places (most specific wins):
1. **In the plugin panel** — the panel lists all 17 systems with color swatches.
Click one; it becomes the default whenever a deck doesn't specify a theme.
Your choice is remembered across sessions.
2. **Per deck** — set `theme.preset` (e.g. `"theme": { "preset": "ocean" }`).
3. **Per slide** — set `slide.theme.preset` on a single slide (e.g. one dark
section inside a light deck).
## `create_deck` data format
This is the JSON the LLM produces — and what you can paste into **JSON直貼り**:
```jsonc
{
"replace": true, // optional: delete existing slides first
"theme": { // optional; omit to use the panel default
"preset": "light", // one of the 17 systems (see below)
"accent": "#2563eb", // optional per-field color overrides:
"background": "#ffffff",
"titleColor": "#0f172a",
"bodyColor": "#334155",
"secondaryColor": "#64748b",
"surfaceColor": "#f1f5f9",
"font": "Inter", // preferred font family (best-effort)
"pageNumbers": true // footer page numbers (default true)
},
"slides": [
{
"layout": "content", // optional; auto-detected if omitted
"kicker": "string", // small eyebrow label above the title
"title": "string",
"subtitle": "string",
"body": "string", // paragraph; for statement/quote/code it's the message/code
"bullets": ["string", "..."],
"items": [ // structured rows (charts, cards, steps, …)
{ "title": "", "value": "", "label": "", "desc": "", "bullets": ["..."], "image": "https://…" }
],
"image": "https://…", // for image-right/left, hero, gallery, team
"table": { "headers": ["A","B"], "rows": [["1","2"],["3","4"]] },
"notes": "string", // speaker notes (stored as plugin data)
"background": "#0B1021", // per-slide background (hex or a name)
"theme": { "preset": "dark" } // per-slide design-system override
}
]
}
```
## Layout reference
Omit `layout` to auto-detect: a title-only slide becomes `cover` (first slide) or
`section`; anything with body/bullets/items becomes `content`.
| layout | shows | main fields |
| --- | --- | --- |
| `cover` | title slide with a side color panel | kicker, title, subtitle |
| `cover-bold` | full-bleed accent bg, centered title | kicker, title, subtitle |
| `cover-minimal` | editorial whitespace, left-aligned + rule | kicker, title, subtitle |
| `cover-split` | 50/50 accent panel + title | kicker, title, subtitle |
| `section` | divider with a big ghost number | kicker, title, subtitle |
| `closing` | closing / thank-you slide | kicker, title, subtitle |
| `content` | title + numbered bullets | kicker, title, subtitle, body, bullets[] |
| `two-col` | bullets in two columns | kicker, title, bullets[] |
| `checklist` | ✓ checklist | kicker, title, bullets[] |
| `agenda` | numbered table of contents | title, bullets[] |
| `split` | term / definition rows | title, items[].title + items[].desc |
| `quadrant` | 2×2 matrix of four cards | title, items[].title/desc (4) |
| `statement` | one big quote / takeaway | body, subtitle |
| `quote` | designed pull-quote + author | body, subtitle |
| `callout` | highlighted message box | body, subtitle |
| `stats` | 2–4 big numbers | title, items[].value/label/desc |
| `big-number` | one huge KPI | kicker, items[0].value/label/desc |
| `gauge` | single circular % gauge + label | title, items[0].value/label/desc |
| `bars` | horizontal bar chart | title, items[].label/value |
| `columns` | vertical column chart | title, items[].label/value |
| `progress` | percent progress bars | title, items[].label/value (0–100) |
| `donut` | donut / pie chart + legend | title, items[].label/value |
| `line` | line chart | title, items[].label/value |
| `funnel` | funnel | title, items[].label/value |
| `pyramid` | hierarchy pyramid | title, items[].label (+value) |
| `steps` | numbered process row | title, items[].title/desc |
| `roadmap` | horizontal phased roadmap | title, items[].title/desc (3–5) |
| `timeline` | vertical timeline | title, items[].title/desc |
| `compare` | two cards side by side | title, items[0..1].title + bullets[] |
| `cards` | 2–4 feature cards | title, items[].title/desc |
| `table` | data table (zebra rows) | title, table.headers[], table.rows[][] |
| `split-mix` | text left + image/chart right | title, bullets/body + image OR items[] |
| `team` | profile cards with avatars | title, items[].image/title/desc |
| `testimonial` | quote + avatar + name/role | body, image, subtitle, items[0].desc |
| `image-right` | text left, image right | kicker, title, bullets[], image |
| `image-left` | image left, text right | kicker, title, body, image |
| `hero` | full-bleed image + overlaid title | kicker, title, subtitle, image |
| `gallery` | grid of images with captions | title, items[].image/title |
| `code` | code on a dark panel (monospace) | title, body (the code; newlines kept) |
Notes:
- **Charts** (`bars`, `columns`, `progress`, `donut`, `line`, `funnel`, `pyramid`,
`gauge`) read a number out of `items[].value` (e.g. `"68%"`, `"1,200"`).
- **Covers:** `cover` (side panel), `cover-bold` (full accent, centered),
`cover-minimal` (editorial), `cover-split` (50/50).
- **Images** accept any `https` PNG / JPEG / GIF URL (max 4096 px). A bad/slow URL
falls back to a plain placeholder after ~7 s.
## Design systems (`theme.preset`)
17 systems, each a 6-color palette (some with a preferred font):
- **Light:** `light` (default), `sky`, `ocean`, `emerald`, `forest`, `warm`,
`sunset`, `rose`, `plum`, `indigo`, `mono`, `paper` (serif)
- **Dark:** `dark`, `midnight`, `graphite`, `carbon`, `royal`
See [Choosing a design system](#choosing-a-design-system) for how to select one.
## Troubleshooting
- **"APIエラー" / API error.** Check the API key, the model id, and that the key
has credit. The exact provider message is shown in the panel.
- **"呼び出しに失敗 (CORS / Failed to fetch)".** A provider's API blocked the
direct browser request. Anthropic supports direct calls; if Gemini or OpenAI is
blocked in your environment, switch providers or open an issue.
- **"生成結果の解析に失敗".** The model's output wasn't valid JSON — usually it was
cut off because the deck was too long. Ask for fewer slides or use a stronger
model (Opus / Sonnet / GPT‑4o / Gemini Pro).
- **Images show as gray boxes.** The image URL was unreachable/blocked or timed
out (~7 s).
- **Japanese text looks wrong / boxes.** Install/keep **Noto Sans JP** in Figma
(the plugin auto-prefers it for CJK and falls back to Inter). Override with
`theme.font`.
## Updating the plugin
- **Changed `code.ts` or `ui.html`?** `npm run build`, then **re-run** the plugin
in Figma (Plugins → Development → slide-mcp). Re-running reloads `code.js` +
`ui.html` from disk.
- **Changed `manifest.json`?** **Re-import** the plugin (Plugins → Development →
Import plugin from manifest…). A re-run is not enough for manifest changes.
## Project structure
```
manifest.json Figma plugin manifest (entry: code.js, ui: ui.html)
code.ts Plugin main thread — layout engine (build → code.js)
ui.html Plugin UI — BYOK panel + JSON-paste + IDE/MCP connect
tsconfig.json Plugin TS config (target es2017 — required by Figma)
.vscode/mcp.json VS Code MCP server config (for the IDE/MCP path)
server/ MCP server + WebSocket bridge (optional — IDE/MCP mode)
src/index.ts MCP server (stdio) + WS bridge; generate.mjs CLI
examples/ Ready-made prompts + create_deck JSON (incl. all-layouts.json)
assets/community/ Figma Community publishing assets
```
## Notes & limitations
- **BYOK / privacy:** your API key is stored locally (`clientStorage`) and sent
only to the provider you pick. The plugin has no backend and collects nothing.
- **Network access:** `manifest.json` allows `*` so the plugin can reach the three
provider APIs and fetch user-supplied image URLs (any host).
- **Speaker notes:** Figma's plugin API has no native speaker-notes field, so
`notes` are stored on the slide via `setSharedPluginData("slide_mcp", "notes")`.
- **TypeScript target:** the plugin builds to **es2017** — Figma's plugin runtime
rejects newer syntax. Don't raise the target.
- **Disclaimer:** an unofficial, community project — not affiliated with, sponsored
by, or endorsed by Figma or Anthropic. "Figma" is a trademark of Figma, Inc., used
here only descriptively to say what this tool works with.
## Develop
```bash
npm run watch # rebuild code.ts → code.js on save
```
After changing the plugin, re-run it from Figma's Plugins → Development menu.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues