Content
# ui-mockup-mcp
A cross-agent MCP server that generates layered UI mockups via `gpt-image-2`. Usable from both Claude Code and Codex CLI through one local install.
The premise is the workflow from [r/codex](https://www.reddit.com/r/codex/comments/1t1klni/): rather than "prompt to one mockup", decompose the design into a system (colors, typography, layout, controls, components) and have the coding agent implement each layer sequentially. Coherent UIs out, fine-grained editing handles in ("shift colors to teal/orange" instead of "make it more pink").
## Status
v0. Four tools implemented end to end against a self-hosted [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) instance. All four have been smoked against the real `gpt-image-2`. Out of scope for v0: reference images, web UI, caching, Figma export.
## Prereqs
- Node 22+ (24 LTS recommended)
- pnpm 10 (`corepack enable && corepack prepare pnpm@latest`)
- An OpenAI-compatible image endpoint that exposes `gpt-image-2`. Either:
- OpenAI's public API directly (`https://api.openai.com/v1`), or
- A self-hosted [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) instance, or any other OpenAI-compatible relay.
- The bearer token for that endpoint, exposed as the `CLIPROXY_API_KEY` env var.
## Install
```sh
git clone <repo-url> ui-mockup-mcp
cd ui-mockup-mcp
pnpm install
pnpm build
```
Configure env. The server requires two variables: the bearer token and the endpoint URL.
```sh
cp .env.example .env.local
# edit .env.local to set:
# CLIPROXY_API_KEY=...
# CLIPROXY_BASE_URL=https://api.openai.com/v1 (or your CLIProxyAPI instance)
```
Verify the server boots:
```sh
echo '{}' | node dist/server.js
# expect: stderr "ui-mockup MCP server connected via stdio", exit 0
```
## Configure your agent
Both clients run the server as a stdio subprocess.
### Claude Code
Copy the template and edit the absolute path:
```sh
cp .mcp.json.example .mcp.json
# replace /absolute/path/to/ui-mockup-mcp with your clone's absolute path
```
The template uses `${CLIPROXY_API_KEY}` and `${CLIPROXY_BASE_URL}` substitution, so Claude Code reads them from your shell env at server-spawn time. Export both before launching `claude`. `.mcp.json` itself is gitignored to avoid leaking your absolute path.
Verify with `claude mcp list`. The server should show `ui-mockup: ... ✓ Connected`.
### Codex CLI
In `~/.codex/config.toml`:
```toml
[mcp_servers.ui_mockup]
command = "node"
args = ["/absolute/path/to/ui-mockup-mcp/dist/server.js"]
env = { CLIPROXY_API_KEY = "your-key-here", CLIPROXY_BASE_URL = "https://api.openai.com/v1" }
```
(TOML keys can't contain hyphens, so the table key uses underscores. Tool names are still `generate_design_system` etc., unchanged.)
Verify with `codex mcp list`. The key is shown masked as `*****`.
## 30 second walkthrough
In either client, ask the agent to use the server. Example session:
> "Use ui-mockup to generate a design system for a small SaaS analytics dashboard, linear-dense style. Then make an overview screen and a dark-mode variant."
The agent will:
1. Call `generate_design_system({brief, style_anchor: "linear-dense"})`. ~60-90s, returns 5 PNG content blocks (colors, typography, layout, controls, components) plus a `design.md` stub with `FILL IN` markers and the prompts used.
2. Look at the returned image blocks visually and complete `design.md`: fill in `colors_hex`, `primary_font`, `type_scale_base_px`, `density` in YAML frontmatter, and replace each `<!-- FILL IN: ... -->` block in the markdown sections.
3. Call `generate_screen_mockup({design_system_dir, screen_brief: "..."})`. The tool reads `design.md` (hard-fails if any `FILL IN` remains) and inlines the parsed palette + type + style anchor into the screen prompt. ~60-90s, returns one screen PNG.
4. Call `render_screen_variant({screen_mockup_path, instruction: "dark mode"})`. Edits API preserves the original composition. ~60-90s.
Mid-flow, the agent can also call `edit_design_token({design_system_dir, layer: "colors", instruction: "shift to teal greens with a warm orange accent"})` to rework a single layer. Colors and typography use the edits API to preserve the specimen grid; layout, controls, and components regenerate from the stored prompt plus your instruction. The previous version of the layer is preserved as `<layer>.prev-N.png`.
Output of an end to end run lands at `output/<iso-timestamp>-<slug>-<nanoid>/`:
```
colors.png
typography.png
layout.png
controls.png
components.png
design.md # stub written by the server, filled in by the agent
manifest.json # full record of inputs, prompts, model snapshot, edits, screens, variants
screens/<slug>.png
screens/<slug>.variants/<instruction-slug>.png
```
## Tool reference
| Tool | When |
|---|---|
| `generate_design_system(brief, style_anchor, style_notes?)` | Start of a new project. Produces the 5 PNG layered specimen + design.md stub. |
| `generate_screen_mockup(design_system_dir, screen_brief)` | After `design.md` is completed, produce one screen that conforms to the system. |
| `edit_design_token(design_system_dir, layer, instruction)` | Targeted rework of a single layer. Colors and typography preserve layout; others regenerate. |
| `render_screen_variant(screen_mockup_path, instruction)` | Variants of an existing screen: responsive sizes, dark/light mode, empty or error states. |
`style_anchor` is an enum: `linear-dense`, `stripe-minimal`, `notion-editorial`, `vercel-mono`, `apple-glass`, `framer-vibrant`, `custom`. With `custom`, pass `style_notes`. See `src/lib/style-anchors.ts` for the prose descriptions used in prompts.
## design.md contract
The server writes a stub with YAML frontmatter and per-section `FILL IN` markers. The calling agent reads the returned image blocks and completes the file. `generate_screen_mockup` and `edit_design_token` validate this on every call:
```yaml
---
run_id: ...
brief: ...
style_anchor: ...
colors_hex: ["#0F172A", "#0EA5A5", ...] # non-empty array of hex codes
primary_font: "Inter" # non-empty
secondary_font: "" # optional
type_scale_base_px: 16 # > 0
density: "compact" # compact | comfortable | spacious
---
```
If any `FILL IN` marker remains in body or frontmatter, the next tool call errors with the section name and the marker text, telling the agent exactly what to complete.
## Smoke test
End to end against the real proxy. Costs real money. Run manually after touching `cliproxy.ts` or any handler:
```sh
export CLIPROXY_API_KEY=your-key
pnpm smoke
```
The script runs all four tools sequentially and prints elapsed time per step plus the final run directory.
## Two proxy quirks worth knowing
These bit me during implementation and the fixes live in `src/lib/cliproxy.ts`. Documenting here so you don't lose 20 minutes to them later:
1. **CLIProxyAPI blocks the OpenAI JS SDK's `User-Agent`.** A request bearing `OpenAI/JS x.y.z` returns HTTP 403 "Your request was blocked". Direct curl with the same body returns 200. Fix: pass `defaultHeaders: { "User-Agent": "curl/8.0.0", "X-Stainless-Lang": null, ... }` to the SDK constructor.
2. **The proxy's `/v1/images/edits` route requires the multipart PNG part to carry `Content-Type: image/png`.** The SDK's default `fs.createReadStream(path)` uploads as `application/octet-stream`, which the proxy rejects with HTTP 400 "Expected a base64-encoded data URL with an image MIME type". Fix: `toFile(buf, "name.png", { type: "image/png" })`.
If you point this server at the real OpenAI API instead of CLIProxyAPI, both workarounds are still harmless. They only matter when the proxy is in the path.
## Security note
The Claude Code project-scoped `.mcp.json` is normally checked into git. If yours contains the API key inline, treat the repo as private or move the key to user-scoped config:
```sh
claude mcp remove ui-mockup -s project
claude mcp add -s user ui-mockup --env CLIPROXY_API_KEY=your-key -- node /absolute/path/to/dist/server.js
```
The Codex `~/.codex/config.toml` is already user-scoped and is not in any project repo.
## Architecture cheat sheet
```
src/
├── server.ts MCP stdio server, registers 4 tools
├── types.ts StyleAnchor + Layer Zod enums
├── tools/
│ ├── generate-design-system.ts parallel 5-layer generation, stub design.md
│ ├── generate-screen-mockup.ts reads completed design.md, inlines into screen prompt
│ ├── edit-design-token.ts per-layer: edits API or regenerate
│ └── render-screen-variant.ts edits API on an existing screen
├── prompts/ .md templates with {{var}} substitution
│ ├── system/ one per layer
│ ├── screen.md
│ ├── edit-token-edit.md
│ ├── edit-token-regenerate.md
│ └── screen-variant.md
└── lib/
├── cliproxy.ts OpenAI SDK wrapper, backoff, the two header fixes
├── template.ts {{var}} substitution
├── paths.ts slugify, run id, findRunDir, suffixing helpers
├── manifest.ts manifest.json schema + read/write
├── design-md.ts stub writer + Zod-validated reader
├── image.ts Sharp scale-down for MCP image blocks
├── style-anchors.ts anchor name to prose description
└── log.ts stderr JSON logger
```
## Out of scope (v0)
- Reference images as input.
- Web UI for browsing past runs.
- Prompt-result caching.
- Multi-tenant API keys.
- Figma export.
- Automatic output cleanup or pruning.
- Cost ledger or enforcement.
- npm publish or CI.
## License
Personal use.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.