Skip to main content
Glama
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.