penpot/docker/images/docker-compose.yaml
Dr. Dominik Jain c0f0b14540
🎉 Add direct MCP feedback channel via dedicated tool (#12551)
* ♻️ Move devenv-only tools to a separate folder

* 🎉 Add an MCP feedback channel

Add a user-approved feedback tool and an MCP-token-authenticated backend
endpoint that queues plain-text email. Enable the tool through the
configured backend URI in both local and multi-user mode.

Validate subject and feedback limits, enforce per-profile rate limits,
and document configuration. Cover authentication, queueing, tool
availability, and failure handling with backend and MCP tests.

Closes #12490

AI-assisted-by: gpt-6

* ♻️ Move MCP feedback submission into RPC

Use the standard RPC wrappers for feedback and remove the dedicated
HTTP endpoint, route wiring, and custom rate limiter.

Restrict MCP tokens to opted-in routes and commands. Add a shared RPC
call helper to the MCP backend client and adapt the existing tests.

AI-assisted-by: gpt-6

* 🐛 Harden MCP feedback auth, validation and messages

Accept MCP tokens when either the mcp or the access-tokens flag is on, so
instances that enable MCP without personal access tokens can send
feedback. Personal tokens still need access-tokens. Keep the issuer in one
constant, decode tokens without raising to reject unknown issuers, and
name the issuer and type checks.

Reject control characters in the subject, and in the feedback all but
tabs and line breaks, in both the command and the tool, so a NUL answers
400 instead of failing when the mail job is stored. Answer 403 with
profile-blocked or profile-not-active for unusable profiles, record only
who sent feedback in the audit event, and add doc metadata.

Replace on OAuth approval an MCP token issued before MCP tokens had their
own issuer, or one this instance cannot decrypt, since the backend rejects
both.

In the MCP server, mention disabled access tokens on 401, drop the unused
503 case, and say that delivery happens in the background. Document the
flags in the README.

AI-assisted-by: claude-opus-5-5

* 📚 Document the MCP feedback tool and MCP token rules

List submit_feedback among the MCP tools and add an MCP feedback section to
the configuration guide covering PENPOT_MCP_BACKEND_URI,
PENPOT_MCP_FEEDBACK_EMAIL, the flags and the email settings. Add commented
examples of both variables to the compose file.

In the agent memories, record when OAuth approval replaces an MCP token,
how the MCP server reaches the backend, and which flags, routes and
commands accept MCP tokens.

AI-assisted-by: claude-opus-5-5
2026-10-29 10:35:46 +01:00

320 lines
10 KiB
YAML

## Common flags:
# demo-users
# email-verification
# log-emails
# log-invitation-tokens
# login-with-github
# login-with-gitlab
# login-with-google
# login-with-ldap
# login-with-oidc
# login-with-password
# prepl-server
# registration
# secure-session-cookies
# smtp
# smtp-debug
# telemetry
# webhooks
##
## You can read more about all available flags and other
## environment variables here:
## https://help.penpot.app/technical-guide/configuration/#penpot-configuration
#
# WARNING: if you're exposing Penpot to the internet, you should remove the flags
# 'disable-secure-session-cookies' and 'disable-email-verification'
x-flags: &penpot-flags
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies enable-mcp enable-admin-console
## Public URI where Penpot is reached. To serve Penpot from a subpath, include
## it with a trailing slash, e.g. https://example.com/penpot/. A reverse proxy in
## front must forward the subpath unchanged, without rewriting it.
x-uri: &penpot-public-uri
PENPOT_PUBLIC_URI: http://localhost:9001
x-body-size: &penpot-http-body-size
# Max body size
PENPOT_HTTP_SERVER_MAX_BODY_SIZE: 367001600
# Deprecation warning: this variable is deprecated. Use PENPOT_HTTP_SERVER_MAX_BODY (defaults to 367001600)
PENPOT_HTTP_SERVER_MAX_MULTIPART_BODY_SIZE: 367001600
## Penpot SECRET KEY. It serves as a master key from which other keys for subsystems
## (eg http sessions, or invitations) are derived.
##
## We recommend to use a truly randomly generated
## 512 bits base64 encoded string here. You can generate one with:
##
## python3 -c "import secrets; print(secrets.token_urlsafe(64))"
x-secret-key: &penpot-secret-key
PENPOT_SECRET_KEY: change-this-insecure-key
## Single database configuration block, shared by backend and admin-console.
## Don't touch them unless you are using custom postgresql connection parameters.
x-database: &penpot-database
PENPOT_DATABASE_URI: postgresql://penpot-postgres/penpot
PENPOT_DATABASE_USERNAME: penpot
PENPOT_DATABASE_PASSWORD: penpot
networks:
penpot:
volumes:
penpot_postgres_v15:
penpot_assets:
# penpot_mailpit_data:
# penpot_traefik:
services:
## Traefik service declaration example. Consider using it if you are going to expose
## penpot to the internet, or a different host than `localhost`.
# traefik:
# image: traefik:v3.3
# networks:
# - penpot
# command:
# - "--api.insecure=true"
# - "--entryPoints.web.address=:80"
# - "--providers.docker=true"
# - "--providers.docker.exposedbydefault=false"
# - "--entryPoints.websecure.address=:443"
# - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
# - "--certificatesresolvers.letsencrypt.acme.email=<EMAIL_ADDRESS>"
# - "--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json"
# volumes:
# - "penpot_traefik:/traefik"
# - "/var/run/docker.sock:/var/run/docker.sock"
# ports:
# - "80:80"
# - "443:443"
penpot-frontend:
image: "penpotapp/frontend:${PENPOT_VERSION:-2.18}"
restart: always
ports:
- 9001:8080
volumes:
- penpot_assets:/opt/data/assets
depends_on:
- penpot-backend
- penpot-admin-console
- penpot-mcp
- penpot-exporter
networks:
- penpot
# labels:
# - "traefik.enable=true"
# ## HTTPS: example of labels for the case where penpot will be exposed to the
# ## internet with HTTPS using traefik.
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`)"
# ## When Penpot is served from a subpath, match it too:
# - "traefik.http.routers.penpot-https.rule=Host(`<DOMAIN_NAME>`) && PathPrefix(`/<SUBPATH>`)"
# - "traefik.http.routers.penpot-https.entrypoints=websecure"
# - "traefik.http.routers.penpot-https.tls.certresolver=letsencrypt"
# - "traefik.http.routers.penpot-https.tls=true"
environment:
<< : [*penpot-flags, *penpot-http-body-size, *penpot-public-uri]
# Don't touch it; this uses an internal docker network to
# communicate with the admin-console.
PENPOT_ADMIN_CONSOLE_URI: http://penpot-admin-console:3000
# Set to "true" on hosts where IPv6 is disabled at kernel boot level.
# PENPOT_DISABLE_IPV6_LISTEN: "true"
penpot-backend:
image: "penpotapp/backend:${PENPOT_VERSION:-2.18}"
restart: always
volumes:
- penpot_assets:/opt/data/assets
depends_on:
penpot-postgres:
condition: service_healthy
penpot-valkey:
condition: service_healthy
networks:
- penpot
## Configuration environment variables for the backend container.
environment:
<< : [*penpot-flags, *penpot-public-uri, *penpot-http-body-size, *penpot-secret-key, *penpot-database]
# Don't touch it; this uses an internal docker network to
# communicate with the admin-console.
PENPOT_ADMIN_CONSOLE_URI: http://penpot-admin-console:3000
## Valkey (or previously redis) is used for the websockets notifications. Don't touch
## unless the valkey container has different parameters or different name.
PENPOT_REDIS_URI: redis://penpot-valkey/0
## Default configuration for assets storage: using filesystem based with all files
## stored in a docker volume.
PENPOT_OBJECTS_STORAGE_BACKEND: fs
PENPOT_OBJECTS_STORAGE_FS_DIRECTORY: /opt/data/assets
## Also can be configured to to use a S3 compatible storage.
# AWS_ACCESS_KEY_ID: <KEY_ID>
# AWS_SECRET_ACCESS_KEY: <ACCESS_KEY>
# PENPOT_OBJECTS_STORAGE_BACKEND: s3
# PENPOT_OBJECTS_STORAGE_S3_ENDPOINT: <ENDPOINT>
# PENPOT_OBJECTS_STORAGE_S3_BUCKET: <BUKET_NAME>
## Telemetry. When enabled, a periodical process will send anonymous data about this
## instance. Telemetry data will enable us to learn how the application is used,
## based on real scenarios. If you want to help us, please leave it enabled. You can
## audit what data we send with the code available on github.
PENPOT_TELEMETRY_ENABLED: "true"
PENPOT_TELEMETRY_REFERER: compose
## Example SMTP/Email configuration. By default, emails are sent to the Mailpit
## service, but for production usage it is recommended to setup a real SMTP
## provider. Emails are used to confirm user registrations & invitations. Look below
## how the Mailpit service is configured.
PENPOT_SMTP_DEFAULT_FROM: no-reply@example.com
PENPOT_SMTP_DEFAULT_REPLY_TO: no-reply@example.com
PENPOT_SMTP_HOST: penpot-mailpit
PENPOT_SMTP_PORT: 1025
PENPOT_SMTP_USERNAME:
PENPOT_SMTP_PASSWORD:
PENPOT_SMTP_TLS: "false"
PENPOT_SMTP_SSL: "false"
## Inbox for the feedback that MCP agents send. Set it together with
## PENPOT_MCP_BACKEND_URI on the penpot-mcp service.
# PENPOT_MCP_FEEDBACK_EMAIL: mcp-feedback@example.com
penpot-admin-console:
image: "penpotapp/admin-console:${PENPOT_VERSION:-2.18}"
restart: always
depends_on:
penpot-postgres:
condition: service_healthy
networks:
- penpot
environment:
<< : [*penpot-public-uri, *penpot-secret-key, *penpot-database]
# Don't touch it; this uses an internal docker network to
# communicate with the frontend.
PENPOT_INTERNAL_URI: http://penpot-frontend:8080
penpot-mcp:
image: "penpotapp/mcp:${PENPOT_VERSION:-2.18}"
restart: always
networks:
- penpot
# environment:
## Offers the submit_feedback tool, which emails agent feedback through the
## backend. Set it together with PENPOT_MCP_FEEDBACK_EMAIL on the backend.
# PENPOT_MCP_BACKEND_URI: http://penpot-backend:6060/
penpot-exporter:
image: "penpotapp/exporter:${PENPOT_VERSION:-2.18}"
restart: always
depends_on:
penpot-valkey:
condition: service_healthy
networks:
- penpot
environment:
<< : [*penpot-flags, *penpot-secret-key, *penpot-public-uri]
# Don't touch it; this uses an internal docker network to
# communicate with the frontend.
PENPOT_INTERNAL_URI: http://penpot-frontend:8080
## Valkey (or previously Redis) is used for the websockets notifications
## and for storing the state export jobs
PENPOT_REDIS_URI: redis://penpot-valkey/0
# PENPOT_EXPORTER_MAX_CONCURRENT_JOBS: 4
# PENPOT_EXPORTER_MAX_JOBS_PER_PROFILE: 2
# PENPOT_EXPORTER_QUEUE_MAX: 64
# PENPOT_EXPORTER_JOB_TTL: 3600
# PENPOT_WASM_WORKER_POOL_MAX: 2
# PENPOT_WASM_WORKER_POOL_MIN: 1
# PENPOT_WASM_WORKER_IDLE_TIMEOUT: 300
# PENPOT_WASM_WORKER_IMAGE_CACHE_SIZE: 134217728
penpot-postgres:
image: "postgres:15"
restart: always
stop_signal: SIGINT
healthcheck:
test: ["CMD-SHELL", "pg_isready -U penpot"]
interval: 2s
timeout: 10s
retries: 5
start_period: 2s
volumes:
- penpot_postgres_v15:/var/lib/postgresql/data
networks:
- penpot
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
- POSTGRES_DB=penpot
- POSTGRES_USER=penpot
- POSTGRES_PASSWORD=penpot
penpot-valkey:
image: valkey/valkey:8.1
restart: always
healthcheck:
test: ["CMD-SHELL", "valkey-cli ping | grep PONG"]
interval: 1s
timeout: 3s
retries: 5
start_period: 3s
networks:
- penpot
environment:
# You can increase the max memory size if you have sufficient resources,
# although this should not be necessary.
- VALKEY_EXTRA_FLAGS=--maxmemory 128mb --maxmemory-policy volatile-lfu
## A Mailpit service, used as temporal SMTP server. You can access via HTTP to the
## port 8025 to read all emails the penpot platform has sent. Should be only used as a
## temporal solution while no real SMTP provider is configured. Messages are kept in
## memory only and are lost when the container is recreated; uncomment the volume
## below (and the one in the top-level volumes: section) to persist them instead.
penpot-mailpit:
image: axllent/mailpit:v1
restart: always
# volumes:
# - penpot_mailpit_data:/data
# environment:
# - MP_DATABASE=/data/mailpit.db
expose:
- '1025'
ports:
- "8025:8025"
networks:
- penpot