Skip to main content
Glama
README.md
# AI Carousel Generator (MCP Server)

Generate premium, viral-ready 4K carousel slides from a single instruction -
an expert copywriter, an AI image engine, and a premium compositor, all
exposed as native **MCP (Model Context Protocol) tools** so any AI client can
call them.

- **4K output** - native 2160x2700 px (4:5 portrait) composites
- **High-EEAT copywriting** - data-backed insights, actionable frameworks,
  zero fluff, LinkedIn-optimized hooks (per-slide `visual_prompt`,
  `headline`, `body`, `animation_cue`)
- **Premium typography** - Notion-meets-Bloomberg layout: glassmorphic text
  cards, editorial type scale, slide indicators, animation-cue tags
- **Master tool** - one instruction in, a packaged set of slides + an
  animation script (timings) out

## Architecture

```
AI client (opencode, Claude Code, Claude Desktop, ...)
        |
        |  MCP stdio transport
        v
  mcp_server.py  (FastMCP "AI Carousel Generator")
        |
        +--> services/copywriter.py    expert content strategy (slide plan)
        +--> services/ai_generator.py  AI image engine (4K backgrounds)
        +--> services/compositing.py   premium typography compositor
```

The three MCP tools:

| Tool | Purpose |
| ---- | ------- |
| `create_single_slide` | Generate one AI background + compose one slide |
| `batch_generate_carousel` | Turn a high-level topic into a full carousel |
| `create_premium_viral_carousel` | Master tool: copywriting + 4K visuals + packaged animation metadata |

### Where does the "antigravity CLI" fit?

The **antigravity CLI is not an MCP client** - it is the *image-generation
backend* that `mcp_server.py` shells out to. The flow is:

```
AI client --MCP--> mcp_server.py --subprocess--> antigravity CLI -> 4K background PNG
                                                   (or a custom local HTTP endpoint)
```

So you do not "connect the MCP server to antigravity CLI". Instead you connect
your AI client to this MCP server, and the server renders images through
antigravity (or your own endpoint) for you. Configure the backend with
environment variables (see [Environment](#environment)).

## Requirements

- Python 3.10+
- [uv](https://docs.astral.sh/uv/) (recommended, portable) - **or** an
  existing virtual environment (`pip install -r requirements.txt`)
- The `antigravity` CLI on `PATH`, or a local image endpoint
  (`GENERATION_PROVIDER=endpoint`), or the optional placeholder fallback

## Quick start

```bash
pip install -r requirements.txt        # or: uv sync / uv venv
python mcp_server.py                    # runs over stdio - expected for MCP clients
```

Smoke-test the tools directly:

```bash
python _mcp_smoke.py
```

## Connecting an MCP client

The server speaks **stdio**, the transport used by Claude Code, Claude
Desktop, and opencode. No port, no web server.

### opencode

**Project-level** (works only in this folder) - commit this to the repo:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ai-carousel-generator": {
      "type": "local",
      "command": [".venv/Scripts/python.exe", "mcp_server.py"],
      "enabled": true
    }
  }
}
```

**Global** (available from any folder) - `~/.config/opencode/opencode.json`:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ai-carousel-generator": {
      "type": "local",
      "command": [
        "C:\\mcp-cursoal\\.venv\\Scripts\\python.exe",
        "C:\\mcp-cursoal\\mcp_server.py"
      ],
      "enabled": true,
      "environment": {
        "IMAGE_OUTPUT_DIR": "C:\\mcp-cursoal\\generated_images",
        "COMPOSED_SLIDES_DIR": "C:\\mcp-cursoal\\composed_slides"
      }
    }
  }
}
```

On machines with `uv` (instead of a local `.venv`), the portable variant is:

```json
{
  "mcp": {
    "ai-carousel-generator": {
      "type": "local",
      "command": [
        "uv", "run", "--directory", "C:\\mcp-cursoal",
        "--with-requirements", "requirements.txt",
        "python", "mcp_server.py"
      ],
      "enabled": true
    }
  }
}
```

After changing config, **quit and restart opencode** - config is loaded once
at startup. Then ask: *"Generate a 5-slide 4K carousel about B2B lead
generation"*.

### Claude Code

Register once (no file editing needed):

```bash
# uv-based (portable)
claude mcp add ai-carousel-generator --scope user -- \
    uv run --directory C:\mcp-cursoal --with-requirements requirements.txt \
    python mcp_server.py

# or with the local venv
claude mcp add ai-carousel-generator --scope user -- \
    C:\mcp-cursoal\.venv\Scripts\python.exe mcp_server.py
```

Verify with `claude mcp list`. Restart Claude Code, then prompt it to create a
carousel.

### Claude Desktop

Edit `claude_desktop_config.json` (Claude -> Settings -> Developer -> Edit
Config):

```json
{
  "mcpServers": {
    "ai-carousel-generator": {
      "command": "uv",
      "args": [
        "run", "--directory", "C:\\mcp-cursoal",
        "--with-requirements", "requirements.txt",
        "python", "mcp_server.py"
      ]
    }
  }
}
```

Restart Claude Desktop, then look for the hammer/tools icon next to the
carousel generator server.

## Tools

### `create_premium_viral_carousel(user_instruction, slide_count=3, render_resolution="4K") -> dict`

Master tool. Orchestration: copywriter -> 4K image generator -> compositor.

- `user_instruction` - high-level brief, e.g. `"B2B lead generation in 2026"`
- `slide_count` - number of slides (>= 1)
- `render_resolution` - `"4K"` (2160x2700) or `"HD"` (1080x1350)

Returns a dict with `topic`, `slide_count`, `render_resolution`,
`output_dir`, `slides` (per-slide `slide_number`, `headline`, `body`,
`animation_cue`, `image_path`) and `metadata.animation_script` (per-slide
`cue`, `duration_ms`, `start_ms` for Canva / After Effects).

Example instruction: *"Morning routines for high performers"* -> hook slide,
data slide, framework slide, CTA slide - each a 2160x2700 PNG in
`composed_slides/`.

### `batch_generate_carousel(topic, slide_count) -> list[str]`

Fast path: archetype-driven breakdown (Why It Matters, The Basics, Key
Principle, ...) returning a list of slide file paths.

### `create_single_slide(topic_prompt, headline, body, slide_number) -> str`

Fine-grained control: one background prompt + text + position, returns the
final slide path.

## Environment

All variables are optional (defaults shown):

| Variable | Default | Description |
| -------- | ------- | ----------- |
| `GENERATION_PROVIDER` | `antigravity` | `antigravity` or `endpoint` |
| `ANTIGRAVITY_CLI` | `antigravity` | Path to the antigravity CLI executable |
| `ANTIGRAVITY_MODEL` | `gemini-2.5-flash-image` | Image model for antigravity |
| `ANTIGRAVITY_ASPECT_RATIO` | `4:5` | Portrait 4K carousel aspect ratio |
| `TARGET_RESOLUTION_LABEL` | `4K` | Label for the target resolution |
| `GENERATION_API_URL` | `http://localhost:8001/generate` | Custom endpoint URL |
| `GENERATION_TIMEOUT_SECONDS` | `120` | Per-generation timeout |
| `DEFAULT_IMAGE_STYLE` | *(empty)* | Style hint for every generation |
| `AI_GENERATION_FALLBACK` | `false` | `true` = render placeholder PNGs if the engine is unavailable |
| `IMAGE_OUTPUT_DIR` | `generated_images` | Where raw AI backgrounds land |
| `COMPOSED_SLIDES_DIR` | `composed_slides` | Where final slides land |

## Deploying to Vercel

The FastAPI wrapper (`main.py`) deploys to Vercel with zero configuration:
Vercel auto-detects the FastAPI app (an `app` instance in `main.py` with
`fastapi` in `requirements.txt`) and serves it as a single Python 3.12
function. `vercel.json` only raises the function timeout to 60s.

```bash
vercel --prod
```

Notes for serverless:

- The working directory is **read-only** on Vercel. Output directories
  (`generated_images/`, `composed_slides/`) automatically fall back to the
  writable `/tmp` area, so the app imports and runs without crashing.
- The `antigravity` CLI is not available in the function. Set
  `GENERATION_PROVIDER=endpoint` (an external image API) or
  `AI_GENERATION_FALLBACK=true` (placeholder images) in the project's
  Environment Variables, otherwise image-generation endpoints return 502.
- Artifacts written to `/tmp` are ephemeral - download them before the
  invocation ends if you need to keep them.
- Long image generations may exceed the function timeout; `maxDuration` is
  set to 60s in `vercel.json`.

## Project structure

```
mcp_server.py            FastMCP server (stdio), 3 tools
main.py                  FastAPI wrapper (HTTP) - Vercel auto-detects this
services/
  copywriter.py          High-EEAT content strategy engine
  ai_generator.py        antigravity CLI / endpoint image engine (4K upscale)
  compositing.py         2160x2700 premium typography compositor
requirements.txt         Python dependencies
_mcp_smoke.py            In-process smoke test for all tools
```

## Troubleshooting

- **Server not listed / tools missing** - restart the client; MCP config is
  read at startup.
- **`antigravity CLI not found`** - install the CLI on `PATH`, or set
  `GENERATION_PROVIDER=endpoint`, or `AI_GENERATION_FALLBACK=true` for
  placeholder renders.
- **Slow first call** - 4K image generation + Gaussian-blur compositing takes
  several seconds per slide; this is expected.
- **Client on another machine** - replace `C:\mcp-cursoal` with the cloned
  repo path and ensure Python/uv exist there.