AgentStack
MCP unreviewed Apache-2.0 Self-run

Penpot Mcp Server

mcp-ancrz-penpot-mcp-server · by ancrz

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

No reviews yet
0 installs
12 views
0.0% view→install

Install

$ agentstack add mcp-ancrz-penpot-mcp-server

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Penpot Mcp Server? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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 that gives AI agents (like Claude Code, Cursor, or any MCP-compatible client) full programmatic access to your self-hosted Penpot 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

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, 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 | Protocol handling, tool registration | | Database | asyncpg | Direct PostgreSQL access | | HTTP Client | httpx | Penpot RPC API calls | | Validation | Pydantic v2 | Automatic parameter validation | | Package Manager | uv | Fast Python dependency management | | WebSocket | websockets | Real-time browser plugin bridge | | Container | Docker | Deployment alongside Penpot |


Quick Start

Prerequisites

  1. Self-hosted Penpot running via Docker Compose (official guide)
  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

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
git clone https://github.com/ancrz/penpot-mcp-server.git
cd penpot-mcp-server
2. Create your configuration
cp .env.example .env

Edit .env with your Penpot details:

# 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
docker compose up -d --build penpot-mcp
5. Verify it's running
# Quick health check
curl -s http://localhost:8787/
# → {"service": "Penpot MCP", "status": "ok", "version": "0.1.0"}
# 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):

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

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

{
  "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

{
  "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

{
  "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 InstallAllow 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:

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:

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

2. Restart Penpot

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)

# 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

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.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.