loading…
Search for a command to run...
loading…
Enables AI agents to programmatically access self-hosted Penpot instances to read, create, modify, and export design elements through natural language. It provi
Enables AI agents to programmatically access self-hosted Penpot instances to read, create, modify, and export design elements through natural language. It provides 66 tools for managing projects, shapes, design tokens, and comments using a dual-access strategy of direct database reads and RPC API writes.
AI-powered design tool access for self-hosted Penpot via Model Context Protocol.
License: Apache 2.0 Python 3.13+ MCP Protocol Tools: 68
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.
| 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 |
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:
asyncpg — fast and reliable, bypasses API overheadhttpx — ensures proper change tracking and undo history| 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 |
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.
git clone https://github.com/ancrz/penpot-mcp-server.git
cd penpot-mcp-server
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
Add the penpot-mcp service definition to your existing Penpot docker-compose.yml. See docker-compose.penpot.yml for the complete service definition to copy.
docker compose up -d --build penpot-mcp
# 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.
Penpot MCP uses network transport (streamable HTTP) — the server runs as a Docker container and clients connect via HTTP. This means:
env in the client's JSON config is irrelevant — credentials live in the server's own .env file (configured during setup)http://localhost:8787/mcpKey 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
envblock in your client's MCP config has no effect.
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 mapshttpto the streamable HTTP transport internally. Usingstreamable-httpwill cause a schema validation error.
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) andhttpUrl(streamable HTTP transport). Penpot MCP uses streamable HTTP, so usehttpUrl. Notypefield needed.
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(noturlorhttpUrl). If Antigravity runs inside Docker, make sure it can reachlocalhost:8787on the host — you may needhost.docker.internal:8787instead oflocalhost:8787depending on your Docker network setup.
| 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 |
Once connected, you can ask your AI agent things like:
The Penpot MCP Plugin bridges the AI agent with the live Penpot canvas, enabling real-time context awareness:
These features require the browser plugin to be connected. The 66 headless tools work without it.
docker compose up -d penpot-mcphttp://localhost:8787/plugin/manifest.jsonThe plugin panel appears on the right. When the status indicator turns green, the AI agent has live access to the canvas.
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 bothpenpot-backendandpenpot-frontend:docker compose restart penpot-backend penpot-frontend
| 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 may show a permission popup: "Allow [localhost:9001] to access your local network?"
If no popup appears and the plugin stays disconnected, check chrome://flags/#private-network-access-respect-preflight-results -- disable it for local development.
localhost:9001 (or set to "No Blocking")The server provides 68 tools across 11 categories. See 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 |
All settings are via environment variables. See .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 |
Penpot requires a feature flag to enable API access tokens.
.env fileAdd enable-access-tokens to your PENPOT_FLAGS:
PENPOT_FLAGS=enable-login-with-password enable-registration enable-access-tokens
docker compose restart penpot-backend penpot-frontend
.env as PENPOT_ACCESS_TOKENThe 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 for the exact service definition to add. The key points:
penpot Docker network (same as other Penpot services)penpot-postgres (with health check) and penpot-backend8787 on localhost only (127.0.0.1:8787:8787)# 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.
uv sync --group dev
uv run pytest tests/ -v
penpot-mcp-server/
├── src/penpot_mcp/
│ ├── server.py # FastMCP entry point, 68 tool registrations, plugin routes
│ ├── config.py # Pydantic Settings configuration
│ ├── gateway.py # Hybrid context gateway (DB + Plugin awareness)
│ ├── ws_controller.py # WebSocket server for browser plugin bridge (:4402)
│ ├── plugin/
│ │ ├── manifest.json # Penpot plugin manifest (served at /plugin/manifest.json)
│ │ ├── plugin.js # Plugin worker — penpot.* API only (no WebSocket in sandbox)
│ │ └── ui.html # Plugin iframe — WebSocket lives here, relays to plugin.js
│ ├── services/
│ │ ├── db.py # asyncpg connection pool
│ │ ├── api.py # httpx RPC API client
│ │ ├── changes.py # Penpot change operations builder
│ │ └── transit.py # Transit+JSON decoder
│ ├── tools/
│ │ ├── projects.py # Team & project queries
│ │ ├── files.py # File CRUD operations
│ │ ├── shapes.py # Shape reading & search
│ │ ├── create.py # Shape creation
│ │ ├── modify.py # Shape modification
│ │ ├── text.py # Text operations
│ │ ├── export.py # PNG/SVG export
│ │ ├── components.py # Components & design tokens
│ │ ├── comments.py # Comments & collaboration
│ │ ├── media.py # Media assets & fonts
│ │ ├── database.py # Raw SQL queries
│ │ └── advanced.py # File raw data & revision comparison
│ └── transformers/
│ ├── css.py # Shape → CSS conversion
│ ├── svg.py # Shape → SVG conversion
│ └── layout.py # Layout → CSS flexbox/grid
├── tests/
│ ├── conftest.py
│ ├── test_projects.py
│ ├── test_files.py
│ ├── test_shapes.py
│ └── test_e2e_login_form.py
├── pyproject.toml
├── Dockerfile
├── .env.example
├── setup.sh
├── docker-compose.penpot.yml
├── TOOLS.md
└── LICENSE
This project is licensed under the Apache License 2.0.
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"penpot-mcp-server": {
"command": "npx",
"args": []
}
}
}