Add run-devenv-agentic command, starting the Serena MCP server in the container

Serena provides useful tools for the agentic workflow for penpot.
The following additional extensions are added:

1. uv and Serena installation, including a suitable serena_config.yml, are added to the devenv docker image
2. Serena configuration options are set via env vars and flags in manage.sh
3. run-devenv can now take -e flags which it forwards to docker exec

GitHub #9315
This commit is contained in:
Michael Panchenko 2026-05-05 14:24:02 +02:00 committed by Dominik Jain
parent 85cf3fcc3c
commit c2a1d5c6f7
7 changed files with 275 additions and 3 deletions

View File

@ -31,6 +31,7 @@ project_name: "penpot"
languages:
- clojure
- typescript
- rust
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
@ -127,3 +128,14 @@ ignored_memory_patterns: []
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
added_modes:
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
# Paths can be absolute or relative to the project root.
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
# symbols and references across package boundaries.
# Currently supported for: TypeScript.
# Example:
# additional_workspace_folders:
# - ../sibling-package
# - ../shared-lib
additional_workspace_folders: []

View File

@ -185,7 +185,12 @@ ENV CLJKONDO_VERSION=2026.04.15 \
BABASHKA_VERSION=1.12.208 \
CLJFMT_VERSION=0.16.4 \
PIXI_VERSION=0.67.2 \
GITHUB_CLI_VERSION=2.91.0
GITHUB_CLI_VERSION=2.91.0 \
UV_VERSION=0.11.9 \
UV_TOOL_DIR=/opt/uv/tools \
UV_TOOL_BIN_DIR=/opt/utils/bin \
UV_PYTHON_INSTALL_DIR=/opt/uv/python \
SERENA_VERSION=v1.3.0
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
@ -309,6 +314,31 @@ RUN set -ex; \
mv /tmp/mc /opt/utils/bin/; \
chmod +x /opt/utils/bin/mc;
# Install uv
RUN set -ex; \
ARCH="$(dpkg --print-architecture)"; \
case "${ARCH}" in \
aarch64|arm64) \
BINARY_URL="https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-aarch64-unknown-linux-musl.tar.gz"; \
;; \
amd64|x86_64) \
BINARY_URL="https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-musl.tar.gz"; \
;; \
*) \
echo "Unsupported arch: ${ARCH}"; \
exit 1; \
;; \
esac; \
curl -LfsSo /tmp/uv.tar.gz ${BINARY_URL}; \
cd /opt/utils/bin; \
tar -xf /tmp/uv.tar.gz --strip-components=1; \
rm -rf /tmp/uv.tar.gz;
# Install uv-managed tools
RUN set -ex; \
/opt/utils/bin/uv tool install -p 3.13 \
"serena-agent@${SERENA_VERSION}" \
--prerelease=allow;
################################################################################
## DEVENV BASE
@ -421,6 +451,11 @@ ENV LANG='C.UTF-8' \
JAVA_HOME="/opt/jdk" \
CARGO_HOME="/opt/cargo" \
RUSTUP_HOME="/opt/rustup" \
UV_TOOL_DIR="/opt/uv/tools" \
UV_TOOL_BIN_DIR="/opt/utils/bin" \
UV_PYTHON_INSTALL_DIR="/opt/uv/python" \
SERENA_HOME="/home/penpot/.serena" \
SERENA_CONTEXT="claude-code" \
PATH="/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH"
COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick
@ -429,6 +464,7 @@ COPY --from=setup-jvm /opt/clojure /opt/clojure
COPY --from=setup-node /opt/node /opt/node
COPY --from=setup-utils /opt/utils /opt/utils
COPY --from=setup-utils /opt/gh /opt/gh
COPY --from=setup-utils /opt/uv /opt/uv
COPY --from=setup-rust /opt/cargo /opt/cargo
COPY --from=setup-rust /opt/rustup /opt/rustup
COPY --from=setup-rust /opt/emsdk /opt/emsdk
@ -444,6 +480,7 @@ COPY files/tmux.conf /root/.tmux.conf
COPY files/sudoers /etc/sudoers
COPY files/Caddyfile /home/
COPY files/serena_config.yml /home/serena_config.yml
COPY files/selfsigned.crt /home/
COPY files/selfsigned.key /home/
COPY files/start-tmux.sh /home/start-tmux.sh

View File

@ -57,6 +57,10 @@ services:
- 4201:4201
- 4202:4202
# Serena MCP server (agentic mode only)
- ${SERENA_EXTERNAL_PORT:-14281}:14281
- ${SERENA_DASHBOARD_EXTERNAL_PORT:-14282}:24282
environment:
- EXTERNAL_UID=${CURRENT_USER_ID}
# SMTP setup

View File

@ -10,7 +10,17 @@ cp /root/.bashrc /home/penpot/.bashrc
cp /root/.vimrc /home/penpot/.vimrc
cp /root/.tmux.conf /home/penpot/.tmux.conf
# Seed SERENA_HOME with default config on first run
mkdir -p ${SERENA_HOME}
if [ ! -f "${SERENA_HOME}/serena_config.yml" ]; then
cp /home/serena_config.yml "${SERENA_HOME}/serena_config.yml"
fi
chown -R penpot:users ${SERENA_HOME}
chown penpot:users /home/penpot
# we need to be able to install rust-analyzer and possibly other dependencies with rustup
chown -R penpot:ubuntu /opt/rustup
rsync -ar --chown=penpot:users /opt/cargo/ /home/penpot/.cargo/
export JAVA_OPTS="-Djava.net.preferIPv4Stack=true"

View File

@ -0,0 +1,153 @@
language_backend: LSP
# line ending convention to use when writing source files.
# Possible values: "lf" (Unix), "crlf" (Windows), "native" (platform default).
# Note that Serena's own files (e.g. memories and configuration files) always use native line endings.
# This setting can be overridden on a per-project basis in project.yml files.
line_ending: native
# whether to open a graphical window with Serena's logs.
# This is mainly supported on Windows and (partly) on Linux; not available on macOS.
# If you prefer a browser-based tool, use the `web_dashboard` option instead.
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
#
# Being able to inspect logs is useful both for troubleshooting and for monitoring the tool calls,
# especially when using the agno playground, since the tool calls are not always shown,
# and the input params are never shown in the agno UI.
# When used as MCP server for Claude Desktop, the logs are primarily for troubleshooting.
# Note: unfortunately, the various entities starting the Serena server or agent do so in
# mysterious ways, often starting multiple instances of the process without shutting down
# previous instances. This can lead to multiple log windows being opened, and only the last
# window being updated. Since we can't control how agno or Claude Desktop start Serena,
# we have to live with this limitation for now.
gui_log_window: false
# whether to start the Serena Dashboard, which provides detailed information on your Serena session,
# the current configuration and furthermore allows some settings to be conveniently modified on the fly.
# We strongly recommend to always enable this option!
# If you want to prevent the Dashboard window from being opened on launch,
# set `web_dashboard_open_on_launch` to false (see below).
# Further information: https://oraios.github.io/serena/02-usage/060_dashboard.html
web_dashboard: true
# whether to open the Dashboard window/browser tab when Serena starts (provided that web_dashboard is enabled).
# If set to false, you can still open the dashboard manually by clicking on the Serena icon in your system
# tray on Windows and macOS. On Linux, there is no system tray support, so you can only open the dashboard by
# a) telling the LLM to "open the dashboard" (provided that the open_dashboard tool is enabled) or by
# b) manually navigating to http://localhost:24282/dashboard/ in your web browser (actual port
# may be higher if you have multiple instances running; try ports 24283, 24284, etc.)
# See also: https://oraios.github.io/serena/02-usage/060_dashboard.html
web_dashboard_open_on_launch: false
# defines the interface (application mode) used for the web dashboard (if enabled).
# If empty/null, use platform-dependent default. Otherwise, possible values:
# * browser: the dashboard is opened in the default browser (if `web_dashboard_open_on_launch` is true)
# This is supported on all platforms.
# * app: the dashboard is opened in a separate native-like app window with accompanying tray icon, whose
# lifecycle is tied to the Serena process.
# If `web_dashboard_open_on_launch` is false, the dashboard can be conveniently accessed via the tray icon.
# This is supported on Windows and macOS, but note that on macOS, where tray icons are very visible,
# this may result in too many icons being displayed when using multi-agent setups.
# * tray_manager: use a global tray icon to provide access to the dashboards of all running Serena instances,
# opening the dashboard in browser tabs when selected from the tray menu.
# This is EXPERIMENTAL. It is tested on Windows only. We will establish macOS support, but it is yet untested.
# On Linux, this cannot be universally supported, but it may work in some desktop environments.
web_dashboard_interface:
# the address the web dashboard will listen on (bind address).
web_dashboard_listen_address: 0.0.0.0
# address where JetBrains plugin servers are running (only relevant when using the JetBrains language backend)
jetbrains_plugin_server_address: 127.0.0.1
# the minimum log level for the GUI log window and the dashboard (10 = debug, 20 = info, 30 = warning, 40 = error)
log_level: 20
# whether to trace the communication between Serena and the language servers.
# This is useful for debugging language server issues.
trace_lsp_communication: false
# advanced configuration option allowing to configure language server-specific options.
# Maps the language key to the options.
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
# No documentation on options means no options are available.
ls_specific_settings: {}
# list of paths to ignore across all projects.
# Same syntax as gitignore, so you can use * and **.
# These patterns are merged additively with each project's own ignored_paths.
ignored_paths: []
# list of regex patterns which, when matched, mark a memory entry as readonly.
# For example, "global/.*" will mark all global memories as read-only.
# You can extend the list on a per-project basis in the project.yml configuration file.
read_only_memory_patterns: []
# list of regex patterns for memories to completely ignore.
# Matching memories will not appear in list_memories or activate_project output
# and cannot be accessed via read_memory or write_memory.
# To access ignored memory files, use the read_file tool on the raw file path.
# This is useful for projects with large numbers of archived memory files.
# You can extend the list on a per-project basis in the project.yml configuration file.
# Example: ["_archive/.*", "_episodes/.*"]
ignored_memory_patterns: []
# timeout, in seconds, after which tool executions are terminated
tool_timeout: 240
# list of tools to be globally excluded
excluded_tools: []
# list of optional tools (which are disabled by default) to be included
included_optional_tools: []
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
fixed_tools: []
# list of mode names to that are always to be included in the set of active modes
# The full set of modes to be activated is base_modes + default_modes.
# If this is undefined, no base modes are included.
# The project configuration (project.yml) may override this setting.
base_modes: [no-onboarding]
# list of mode names that are to be activated by default.
# The full set of modes to be activated is base_modes + default_modes.
# These modes can be overridden by the project configuration (project.yml) or through the CLI (--mode).
default_modes:
- interactive
- editing
default_max_tool_answer_chars: 150000
# the name of the token count estimator to use for tool usage statistics.
# See the `RegisteredTokenCountEstimator` enum for available options.
#
# By default, a very naive character count estimator is used, which simply counts the number of characters.
# You can configure this to TIKTOKEN_GPT4 to use a local tiktoken-based estimator for GPT-4 (will download tiktoken
# data files on first run), or ANTHROPIC_CLAUDE_SONNET_4 which will use the (free of cost) Anthropic API to
# estimate the token count using the Claude Sonnet 4 tokenizer.
token_count_estimator: CHAR_COUNT
# time budget (seconds) per tool call for the retrieval of additional symbol information
# such as docstrings or parameter information.
# (currently only used by LSP-based tools).
# If the budget is exceeded, Serena stops issuing further retrieval requests
# and returns partial info results.
# 0 disables the budget (no early stopping). Negative values are invalid.
# This is an advanced setting that can help alleviate problems with LSP servers
# that have a slow implementation of request_hover (clangd is one of those)
# or with tool calls that find very many symbols.
# Can be overridden in project.yml.
symbol_info_budget: 10
# template for the location of the per-project .serena data folder (memories, caches, etc.).
# Supports the following placeholders:
# $projectDir - the absolute path to the project root directory
# $projectFolderName - the name of the project directory
# Default: "$projectDir/.serena" (data stored inside the project directory)
# Example for a central location: "/projects-metadata/$projectFolderName/.serena"
project_serena_folder_location: "$projectDir/.serena"
# the list of registered project paths (updated automatically).
projects:
- /home/penpot/penpot

View File

@ -47,10 +47,16 @@ if echo "$PENPOT_FLAGS" | grep -q "enable-mcp"; then
pnpm run build;
popd
tmux new-window -t penpot:4 -n 'mcp server'
tmux new-window -t penpot:4 -n 'mcp'
tmux select-window -t penpot:4
tmux send-keys -t penpot 'cd penpot/mcp' enter C-l
tmux send-keys -t penpot './scripts/start-mcp-devenv' enter
fi
if [ "${SERENA_ENABLED:-false}" = "true" ]; then
tmux new-window -t penpot:5 -n 'serena'
tmux select-window -t penpot:5
tmux send-keys -t penpot "serena start-mcp-server --transport streamable-http --port 14281 --project penpot --context ${SERENA_CONTEXT} --host 0.0.0.0" enter
fi
tmux -2 attach-session -t penpot

View File

@ -108,13 +108,57 @@ function log-devenv {
}
function run-devenv-tmux {
local extra_env_args=()
while [[ $# -gt 0 ]]; do
case "$1" in
-e)
extra_env_args+=(-e "$2"); shift 2;;
-e*)
extra_env_args+=(-e "${1#-e}"); shift;;
*)
shift;;
esac
done
if [[ ! $(docker ps -f "name=penpot-devenv-main" -q) ]]; then
start-devenv
echo "Waiting for containers fully start (5s)..."
sleep 5;
fi
docker exec -ti penpot-devenv-main sudo -EH -u penpot PENPOT_PLUGIN_DEV=$PENPOT_PLUGIN_DEV /home/start-tmux.sh
docker exec -ti \
"${extra_env_args[@]}" \
penpot-devenv-main sudo -EH -u penpot PENPOT_PLUGIN_DEV=$PENPOT_PLUGIN_DEV /home/start-tmux.sh
}
function run-devenv-agentic {
local serena_context="desktop-app"
local serena_external_port="14281"
local serena_dashboard_external_port="14282"
while [[ $# -gt 0 ]]; do
case "$1" in
--serena-context)
serena_context="$2"; shift 2;;
*)
shift;;
esac
done
if [[ ! $(docker ps -f "name=penpot-devenv-main" -q) ]]; then
SERENA_EXTERNAL_PORT="$serena_external_port" \
SERENA_DASHBOARD_EXTERNAL_PORT="$serena_dashboard_external_port" \
start-devenv
echo "Waiting for containers fully start (5s)..."
sleep 5;
fi
run-devenv-tmux \
-e SERENA_ENABLED=true \
-e SERENA_CONTEXT="$serena_context" \
-e PENPOT_FLAGS="${PENPOT_FLAGS} enable-mcp"
}
function run-devenv-shell {
@ -358,6 +402,9 @@ function usage {
echo "- stop-devenv Stops the development oriented docker compose service."
echo "- drop-devenv Remove the development oriented docker compose containers, volumes and clean images."
echo "- run-devenv Attaches to the running devenv container and starts development environment"
echo " Optional -e flags are forwarded to 'docker exec' (e.g. -e MY_VAR=value)."
echo "- run-devenv-agentic Like run-devenv but with additional processes for agentic development enabled."
echo " Options: --serena-context CONTEXT (default: desktop-app)"
echo "- run-devenv-shell Attaches to the running devenv container and starts a bash shell."
echo "- isolated-shell Starts a bash shell in a new devenv container."
echo "- log-devenv Show logs of the running devenv docker compose service."
@ -405,6 +452,9 @@ case $1 in
run-devenv)
run-devenv-tmux ${@:2}
;;
run-devenv-agentic)
run-devenv-agentic ${@:2}
;;
run-devenv-shell)
run-devenv-shell ${@:2}
;;