# Penpot Mcp Server

> MCP server for self-hosted Penpot — AI-powered UI/UX design and prototyping (66 tools)

- **Type:** MCP server
- **Install:** `agentstack add mcp-ancrz-penpot-mcp-server`
- **Verified:** Pending review
- **Seller:** [ancrz](https://agentstack.voostack.com/s/ancrz)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [ancrz](https://github.com/ancrz)
- **Source:** https://github.com/ancrz/penpot-mcp-server

## Install

```sh
agentstack add mcp-ancrz-penpot-mcp-server
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Penpot MCP Server

**AI-powered design tool access for self-hosted Penpot via Model Context Protocol.**

[](LICENSE)
[](https://python.org)
[](https://modelcontextprotocol.io)
[](TOOLS.md)

---

## What is this?

An [MCP server](https://modelcontextprotocol.io) that gives AI agents (like Claude Code, Cursor, or any MCP-compatible client) **full programmatic access** to your self-hosted [Penpot](https://penpot.app) instance. AI can read, create, modify, and export design elements — from rectangles and text to full UI components — all through natural language.

Think of it as the bridge between your AI assistant and your design tool.

### Problems it solves

| Problem | Solution |
|---|---|
| **Manual design work** | AI creates UI components, layouts, and prototypes directly in Penpot |
| **No programmatic API for Penpot** | 68 tools covering projects, shapes, text, exports, comments, and more |
| **Design-to-code gap** | Generate CSS from any shape, export to SVG/PNG, extract design tokens |
| **Repetitive tasks** | Batch operations — rename shapes, update colors, create variants |
| **Design system maintenance** | Read/write components, colors, typographies programmatically |

---

## Architecture

```mermaid
graph TB
    AI["AI Agent\n(Claude Code · Cursor · Gemini CLI)"]

    subgraph SERVERS["MCP Layer"]
        MCP["penpot-mcp — Python\n68 tools · :8787\nDB reads + API writes + Plugin"]
        OMCP["Penpot MCP — Official\n~20 tools · penpot/penpot monorepo\nPlugin API only · TypeScript"]
    end

    subgraph PENPOT["Penpot Stack (Docker)"]
        PG["PostgreSQL\n:5432"]
        BE["Backend\n:6060"]
        FE["Frontend\n:9001"]
        EX["Exporter\n:6061"]
    end

    subgraph BRIDGE["Browser Plugin Bridge"]
        WS["WebSocket Server\n:4402"]
        UI["ui.html\niframe · full browser API"]
        PJ["plugin.js\nworker sandbox · penpot.*"]
    end

    AI -->|"Streamable HTTP :8787"| MCP
    AI -->|"Streamable HTTP"| OMCP

    MCP -->|"asyncpg · direct SQL"| PG
    MCP -->|"httpx · RPC API"| BE
    MCP -->|"PNG / SVG export"| EX
    MCP |"WebSocket"| WS

    OMCP |"WebSocket :4402"| WS

    WS |"ws://localhost:4402"| UI
    UI |"postMessage"| PJ
    PJ -->|"penpot.* API"| FE
    FE -.->|"proxy"| BE
    BE --> EX

    style AI fill:#7c3aed,color:#fff
    style MCP fill:#2563eb,color:#fff
    style OMCP fill:#0f766e,color:#fff
    style PG fill:#16a34a,color:#fff
    style BE fill:#ea580c,color:#fff
    style FE fill:#ea580c,color:#fff
    style EX fill:#ea580c,color:#fff
    style WS fill:#0891b2,color:#fff
    style UI fill:#0891b2,color:#fff
    style PJ fill:#0891b2,color:#fff
```

**Tri-layer access strategy:**
- **Reads** go directly to PostgreSQL via `asyncpg` — fast and reliable, bypasses API overhead
- **Writes** go through Penpot's RPC API via `httpx` — ensures proper change tracking and undo history
- **Exports** use Penpot's built-in exporter (headless Chromium) for pixel-perfect SVG/PNG output
- **Live canvas** goes through the Browser Plugin bridge (port 4402) — shared architecture with the [official Penpot MCP](https://github.com/penpot/penpot/tree/develop/mcp), enabling both servers to coexist and complement each other in the same AI workflow

---

## Tech Stack

| Component | Technology | Purpose |
|---|---|---|
| Language | Python 3.13 | Runtime |
| MCP SDK | [FastMCP](https://github.com/modelcontextprotocol/python-sdk) | Protocol handling, tool registration |
| Database | [asyncpg](https://github.com/MagicStack/asyncpg) | Direct PostgreSQL access |
| HTTP Client | [httpx](https://www.python-httpx.org/) | Penpot RPC API calls |
| Validation | [Pydantic v2](https://docs.pydantic.dev/) | Automatic parameter validation |
| Package Manager | [uv](https://github.com/astral-sh/uv) | Fast Python dependency management |
| WebSocket | [websockets](https://websockets.readthedocs.io/) | Real-time browser plugin bridge |
| Container | Docker | Deployment alongside Penpot |

---

## Quick Start

### Prerequisites

1. **Self-hosted Penpot** running via Docker Compose ([official guide](https://help.penpot.app/technical-guide/getting-started/#install-with-docker))
2. **Docker** and **Docker Compose** v2 installed
3. **Access tokens enabled** in your Penpot instance (see [Enable Access Tokens](#enable-access-tokens))

### Option A: Automated Setup

```bash
git clone https://github.com/ancrz/penpot-mcp-server.git
cd penpot-mcp-server
chmod +x setup.sh
./setup.sh
```

The script will guide you through configuration, build the Docker image, and start the server.

### Option B: Manual Setup

#### 1. Clone the repository

```bash
git clone https://github.com/ancrz/penpot-mcp-server.git
cd penpot-mcp-server
```

#### 2. Create your configuration

```bash
cp .env.example .env
```

Edit `.env` with your Penpot details:

```env
# Your Penpot access token (see "Enable Access Tokens" below)
PENPOT_ACCESS_TOKEN=your-token-here

# Your Penpot database password (from your Penpot docker-compose.yml)
PENPOT_DB_PASS=your-db-password

# Public URL where you access Penpot in the browser
PENPOT_PUBLIC_URL=http://localhost:9001
```

#### 3. Add the MCP service to your Penpot Docker stack

Add the `penpot-mcp` service definition to your existing Penpot `docker-compose.yml`. See [`docker-compose.penpot.yml`](docker-compose.penpot.yml) for the complete service definition to copy.

#### 4. Build and start

```bash
docker compose up -d --build penpot-mcp
```

#### 5. Verify it's running

```bash
# Quick health check
curl -s http://localhost:8787/
# → {"service": "Penpot MCP", "status": "ok", "version": "0.1.0"}
```

```bash
# Full MCP protocol initialization
curl -s http://localhost:8787/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
```

You should see a JSON response with the server capabilities.

---

## Connect Your AI Agent

Penpot MCP uses **network transport** (streamable HTTP) — the server runs as a Docker container and clients connect via HTTP. This means:

- The server is **always running** independently (via Docker Compose)
- The client only needs the URL to connect — no process spawning
- **`env` in the client's JSON config is irrelevant** — credentials live in the server's own `.env` file (configured during [setup](#quick-start))
- Any client on the same machine (or network) can connect to `http://localhost:8787/mcp`

> **Key difference from stdio servers:** With stdio servers (like Skill Swarm), the client launches the process and injects env vars. With network servers like Penpot MCP, the server manages its own credentials. The `env` block in your client's MCP config has no effect.

---

### Claude Code

Claude Code uses `"type": "http"` for streamable HTTP connections.

**Global** (`~/.claude.json`):

```json
{
  "mcpServers": {
    "penpot": {
      "type": "http",
      "url": "http://localhost:8787/mcp"
    }
  }
}
```

**Project-level** (`.mcp.json` in your project root):

```json
{
  "mcpServers": {
    "penpot": {
      "type": "http",
      "url": "http://localhost:8787/mcp"
    }
  }
}
```

Restart Claude Code. You should see **68 tools** from the `penpot` server listed when you run `/mcp`.

> **Note:** Use `"type": "http"`, not `"streamable-http"`. Claude Code maps `http` to the streamable HTTP transport internally. Using `streamable-http` will cause a schema validation error.

---

### Gemini CLI

Gemini CLI uses `httpUrl` (not `url`) for streamable HTTP connections. Transport is inferred from the field name.

**Config file:** `~/.gemini/settings.json`

```json
{
  "mcpServers": {
    "penpot": {
      "httpUrl": "http://localhost:8787/mcp"
    }
  }
}
```

> **Note:** Gemini CLI distinguishes between `url` (SSE transport) and `httpUrl` (streamable HTTP transport). Penpot MCP uses streamable HTTP, so use `httpUrl`. No `type` field needed.

---

### Antigravity

Antigravity uses `serverUrl` for HTTP-based MCP servers.

**Config file:** `~/.gemini/antigravity/mcp_config.json`

```json
{
  "mcpServers": {
    "penpot": {
      "serverUrl": "http://localhost:8787/mcp"
    }
  }
}
```

> **Note:** Antigravity uses `serverUrl` (not `url` or `httpUrl`). If Antigravity runs inside Docker, make sure it can reach `localhost:8787` on the host — you may need `host.docker.internal:8787` instead of `localhost:8787` depending on your Docker network setup.

---

### Quick Comparison

| | Claude Code | Gemini CLI | Antigravity |
|---|---|---|---|
| **Config file** | `~/.claude.json` or `.mcp.json` | `~/.gemini/settings.json` | `~/.gemini/antigravity/mcp_config.json` |
| **URL field** | `"url"` | `"httpUrl"` | `"serverUrl"` |
| **Type field** | `"type": "http"` (required) | Not needed (inferred) | Not needed (inferred) |
| **`env` in JSON** | No effect (network server) | No effect (network server) | No effect (network server) |
| **Credentials** | Server's `.env` file | Server's `.env` file | Server's `.env` file |
| **Docker networking** | `localhost:8787` | `localhost:8787` | May need `host.docker.internal:8787` |

---

### Example prompts

Once connected, you can ask your AI agent things like:

- *"List my Penpot projects"*
- *"Create a login form with email/password fields and a submit button"*
- *"Export the Login Card frame as SVG"*
- *"What colors are defined in the design system?"*
- *"Add a comment at position (100, 200) saying 'Review this layout'"*

---

## Interactive Mode: Browser Plugin

The Penpot MCP Plugin bridges the AI agent with the **live Penpot canvas**, enabling real-time context awareness:

- **Live selection**: AI can query which shapes you currently have selected
- **Script execution**: AI can run JavaScript directly via the Penpot Plugin API

> These features require the browser plugin to be connected. The 66 headless tools work without it.

### Loading the Plugin

1. Make sure the MCP server is running: `docker compose up -d penpot-mcp`
2. Open Penpot in your browser
3. Press **Ctrl+Alt+P** (or Main Menu -> Plugin Manager)
4. Paste the URL in the input field: `http://localhost:8787/plugin/manifest.json`
5. Click **Install** → **Allow** on the permissions dialog
6. Click **Open** to launch the plugin panel

The plugin panel appears on the right. When the status indicator turns green, the AI agent has live access to the canvas.

### Penpot Flags Requirement

The Penpot backend must have `enable-plugins-runtime` in `PENPOT_FLAGS`:

```env
PENPOT_FLAGS=enable-login-with-password enable-registration enable-access-tokens enable-plugins-runtime
```

> **Restart required:** After adding `enable-plugins-runtime`, restart both `penpot-backend` and `penpot-frontend`:
> ```bash
> docker compose restart penpot-backend penpot-frontend
> ```

### Browser Compatibility

| Browser | Status | Notes |
|---------|--------|-------|
| **Firefox** | Works out of the box | No local network restrictions |
| **Chrome / Chromium** | Requires one-time approval | See below |
| **Brave** | Requires Shield disabled | See below |
| **Vivaldi** | Requires one-time approval | Same as Chrome |

#### Chrome / Vivaldi: Local Network Access

Chrome may show a permission popup: **"Allow [localhost:9001] to access your local network?"**

1. Click **Allow** when the popup appears
2. The plugin will connect automatically

If no popup appears and the plugin stays disconnected, check `chrome://flags/#private-network-access-respect-preflight-results` -- disable it for local development.

#### Brave: Shield

1. Click the **Shield icon** (lion) in the address bar
2. Disable the Shield for `localhost:9001` (or set to "No Blocking")
3. Reload the Penpot tab and reconnect the plugin

---

## Tools Overview

The server provides **68 tools** across 11 categories. See [**TOOLS.md**](TOOLS.md) for the complete reference with all parameters.

| Category | Count | Examples |
|---|---|---|
| Projects & Teams | 4 | `list_projects`, `list_teams`, `list_files`, `search_files` |
| File Operations | 9 | `create_file`, `get_file_pages`, `rename_file`, `duplicate_file` |
| Shape Reading | 6 | `get_shape_tree`, `get_shape_details`, `get_shape_css`, `search_shapes` |
| Components & Tokens | 4 | `get_design_tokens`, `get_colors_library`, `get_typography_library` |
| Comments | 6 | `create_comment`, `reply_to_comment`, `resolve_comment` |
| Media & Fonts | 3 | `upload_media`, `list_media_assets`, `list_fonts` |
| Database & Advanced | 3 | `query_database`, `get_webhooks`, `get_profile` |
| Snapshots | 2 | `create_snapshot`, `get_snapshots` |
| Export | 2 | `export_frame_png`, `export_frame_svg` |
| Shape Creation | 8 | `create_rectangle`, `create_frame`, `create_text`, `create_path` |
| Shape Modification | 12 | `set_fill`, `set_stroke`, `set_layout`, `move_shape`, `resize_shape` |
| Text Operations | 5 | `set_text_content`, `set_font`, `set_font_size`, `set_text_align` |
| Advanced Analysis | 2 | `get_file_raw_data`, `compare_revisions` |

---

## Configuration Reference

All settings are via environment variables. See [`.env.example`](.env.example) for a template.

| Variable | Default | Description |
|---|---|---|
| `PENPOT_BASE_URL` | `http://penpot-frontend:8080` | Internal Penpot URL (Docker network) |
| `PENPOT_PUBLIC_URL` | `http://localhost:9001` | Public URL where you access Penpot in browser |
| `PENPOT_ACCESS_TOKEN` | — | API access token (preferred auth method) |
| `PENPOT_EMAIL` | — | Penpot login email (fallback auth) |
| `PENPOT_PASSWORD` | — | Penpot login password (fallback auth) |
| `PENPOT_DB_HOST` | `penpot-postgres` | PostgreSQL host |
| `PENPOT_DB_PORT` | `5432` | PostgreSQL port |
| `PENPOT_DB_NAME` | `penpot` | Database name |
| `PENPOT_DB_USER` | `penpot` | Database user |
| `PENPOT_DB_PASS` | — | Database password |
| `MCP_HOST` | `0.0.0.0` | MCP server bind address |
| `MCP_PORT` | `8787` | MCP server port |
| `MCP_LOG_LEVEL` | `info` | Log level (debug/info/warning/error) |
| `WS_HOST` | `0.0.0.0` | WebSocket server bind address |
| `WS_PORT` | `4402` | WebSocket port for browser plugin |
| `PLUGIN_WS_URL` | `ws://localhost:4402` | WebSocket URL the browser plugin uses to connect |

---

## Enable Access Tokens

Penpot requires a feature flag to enable API access tokens.

### 1. Update your Penpot `.env` file

Add `enable-access-tokens` to your `PENPOT_FLAGS`:

```env
PENPOT_FLAGS=enable-login-with-password enable-registration enable-access-tokens
```

### 2. Restart Penpot

```bash
docker compose restart penpot-backend penpot-frontend
```

### 3. Create a token

1. Open Penpot in your browser
2. Click your avatar (bottom-left) → **Access Tokens**
3. Click **"Generate new token"**
4. Give it a name (e.g., "MCP Server")
5. Copy the token and paste it into your `.env` as `PENPOT_ACCESS_TOKEN`

---

## Penpot Docker Integration

The MCP server runs as a Docker container alongside your existing Penpot stack. You need to add it to your Penpot `docker-compose.yml`.

See [`docker-compose.penpot.yml`](docker-compose.penpot.yml) for the exact service definition to add. The key points:

- It connects to the `penpot` Docker network (same as other Penpot services)
- It depends on `penpot-postgres` (with health check) and `penpot-backend`
- It exposes port `8787` on localhost only (`127.0.0.1:8787:8787`)
- Environment variables reference Docker internal hostnames

---

## Development

### Running locally (outside Docker)

```bash
# Install uv if needed
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install dependencies
uv sync

# Run the server (needs .env configured for local access)
uv run penpot-mcp
```

For local development, point `PENPOT_DB_HOST` and `PENPOT_DB_PORT` to your host-mapped PostgreSQL port, and `PENPOT_BASE_URL` to `http://localhost:9001`.

### Running tests

```bash
uv sync --group dev
uv run pytest tests/ -v
```

### Project structure

```
penpot-mcp-server/
├── src/penpot_mcp/
│   ├── server.py            # FastMCP entry point, 68 tool re

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ancrz](https://github.com/ancrz)
- **Source:** [ancrz/penpot-mcp-server](https://github.com/ancrz/penpot-mcp-server)
- **License:** Apache-2.0

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-ancrz-penpot-mcp-server
- Seller: https://agentstack.voostack.com/s/ancrz
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
