From 23bae2ab7d54606336a8183337d4b3374e99aa68 Mon Sep 17 00:00:00 2001 From: Joakim Persson Date: Wed, 29 Apr 2026 15:27:30 +0200 Subject: [PATCH] Use mempalace-mcp entry point directly, drop redundant wrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mempalace Python package ships a 'mempalace-mcp' console entry point; 'uv tool install' places it on PATH as a shim whose shebang points at the isolated venv's Python. Our hand-rolled wrapper at /usr/local/bin/mempalace-mcp-server was duplicating what uv installs for free — one less file to maintain. Fixes the MCP error users saw after the v1.14.28b → v1.14.29 upgrade path: custom opencode.json files typically had the pre-v1.14.29 command ['python3', '-m', 'mempalace.mcp_server'] which worked with the old pip install but fails silently after the uv-tool migration because system python3 cannot import from the venv. Opencode surfaced this as 'MCP error -32000: connection closed'. - generate-config.py now emits ['mempalace-mcp'] and keys its detect on shutil.which('mempalace-mcp'). - Dockerfile drops 'COPY rootfs/usr/local/bin/' and the chmod of the wrapper. Build shrinks from 30 to 29 stages. - rootfs/usr/local/bin/ removed entirely. - Smoke test asserts /usr/local/bin/mempalace-mcp is executable and prints its symlink target. - README's MemPalace section shows ['mempalace-mcp'] and explicitly warns against the old pattern with the observed failure mode. - CHANGELOG adds a v1.14.29c entry. --- AGENTS.md | 5 ++--- CHANGELOG.md | 9 +++++++++ DOCKER_HUB.md | 4 ++-- Dockerfile | 2 -- README.md | 4 ++-- rootfs/usr/local/bin/mempalace-mcp-server | 10 ---------- .../local/lib/opencode-devbox/generate-config.py | 13 +++++++------ scripts/smoke-test.sh | 2 +- 8 files changed, 23 insertions(+), 26 deletions(-) delete mode 100644 rootfs/usr/local/bin/mempalace-mcp-server diff --git a/AGENTS.md b/AGENTS.md index b281d3c..cd7ad55 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,8 +9,7 @@ Docker image packaging [opencode](https://opencode.ai) into a production-ready d - `Dockerfile` — single multi-stage build for both variants. OMOS variant is controlled by `INSTALL_OMOS=true` build arg; mempalace is controlled by `INSTALL_MEMPALACE` (default `true`). All GitHub-sourced binaries are pinned with version ARGs. - `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu. - `entrypoint-user.sh` — runs as developer: git config, opencode.json generation (delegated to `generate-config.py`), OMOS setup. -- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.json` from env vars. Never overwrites an existing config. Auto-registers MCP servers for detected tools (mempalace via the wrapper, gitea-mcp). -- `rootfs/usr/local/bin/mempalace-mcp-server` — wrapper that exec's the mempalace uv-tool venv's python with `-m mempalace.mcp_server`. Needed because system `python3` can't import from the isolated venv created by `uv tool install`. +- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.json` from env vars. Never overwrites an existing config. Auto-registers MCP servers for detected tools (mempalace via the `mempalace-mcp` entry point, gitea-mcp). - `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows. - `scripts/generate-dockerhub-md.py` — generates `DOCKER_HUB.md` from `README.md` using explicit section rules. `--check` fails if the committed file is out of sync (enforced by the `validate` workflow). - `DOCKER_HUB.md` — **auto-generated** from README. Do not edit directly. Pushed to Docker Hub description via CI API call. Must stay under 25 kB. Short description field must be ≤100 bytes. @@ -38,7 +37,7 @@ When bumping the opencode version, also bump `OPENCODE_VERSION` in `Dockerfile` - **GitHub/Gitea-sourced binaries float by default** — gosu, fzf, git-lfs, nvim, bat, eza, zoxide, uv, gitea-mcp, Go, oh-my-opencode-slim all default to `latest`. Each build-time install step reads the `/releases/latest` Location redirect (or the go.dev JSON feed for Go) and derives the concrete version. Use the same `ARCH` case-switch pattern for multi-arch support (amd64/arm64). Intentional pins: `OPENCODE_VERSION` (drives the image tag), `NODE_VERSION=22` (major pin), `DEBIAN_VERSION=trixie-slim` (OS base). Adding a new upstream tool: follow the existing floated-version pattern, don't hardcode a specific tag. - **Resolved versions are logged by the smoke test** — `scripts/smoke-test.sh` prints a "Resolved component versions" table as its first step. CI logs always capture what got baked into a given image even when ARGs default to `latest`. - **Shell scripts use `set -euo pipefail`** — both entrypoints are strict. Errors in volume chown or SSH permission operations are intentionally suppressed with `|| true`. -- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. The `mempalace` CLI is symlinked onto `PATH` by uv; the MCP server is reached via the `mempalace-mcp-server` wrapper. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. +- **MemPalace install path** — installed via `uv tool install` into `/opt/uv-tools/mempalace/`. Both the `mempalace` CLI and the `mempalace-mcp` MCP server binary are shipped as entry points by the mempalace package itself and placed on PATH by uv as shims whose shebangs point at the venv's Python. No hand-rolled wrapper is needed. Do not use `pip install --break-system-packages` — that was the previous approach and has been removed. Do not use `["python3", "-m", "mempalace.mcp_server"]` in `opencode.json` — system Python can't import from the uv venv. - **generate-config.py idempotency** — the script MUST never overwrite an existing `opencode.json`. Users bind-mount their config directory or persist it across container recreations; accidentally clobbering that file would destroy hand-edits. The smoke test asserts this. - **Docker Hub description update** — uses `/v2/auth/token` endpoint (not the deprecated `/v2/users/login`). Auth uses `identifier`/`secret` fields, returns `access_token`, sent as `Bearer`. Short description must be ≤100 bytes. diff --git a/CHANGELOG.md b/CHANGELOG.md index ef7d46f..cc0045a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a --- +## v1.14.29c — 2026-04-29 + +**Drop redundant mempalace-mcp-server wrapper, use the entry point mempalace ships.** + +- **Fix:** MCP integration with mempalace was still broken for users with custom `opencode.json` files because they typically had `["python3", "-m", "mempalace.mcp_server"]` from v1.14.28b and earlier. With the uv-tool install path, system python3 can't import mempalace and the MCP server subprocess exits immediately — opencode surfaced this as `MCP error -32000: connection closed`. Users should migrate to `["mempalace-mcp"]`. The auto-generated config in new containers already emits the new form. +- **Cleanup:** Remove the hand-rolled `/usr/local/bin/mempalace-mcp-server` wrapper. The mempalace Python package ships a `mempalace-mcp` console entry point; `uv tool install` places it on PATH as a shim whose shebang points at the isolated venv's Python. The wrapper was duplicating what uv installs for free. Removed `rootfs/usr/local/bin/` and its COPY + chmod lines from the Dockerfile. +- **Docs:** README's MemPalace section now shows `["mempalace-mcp"]` and explicitly warns against `["python3", "-m", "mempalace.mcp_server"]` with the observed failure mode. +- **Tests:** Smoke test asserts `/usr/local/bin/mempalace-mcp` is executable and prints its symlink target, replacing the previous wrapper-present check. + ## v1.14.29b — 2026-04-29 **Fix OMOS `bunx` detection + CI build reliability.** diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index 8281925..6101560 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -421,13 +421,13 @@ Add mempalace as an MCP server in your `opencode.json` (inside `~/.config/openco "mcp": { "mempalace": { "type": "local", - "command": ["mempalace-mcp-server"] + "command": ["mempalace-mcp"] } } } ``` -> The image installs mempalace into an isolated `uv tool` venv at `/opt/uv-tools/mempalace`. The `mempalace-mcp-server` wrapper on `PATH` exec's the venv's Python with the `mempalace.mcp_server` module — you don't need to know about the venv to use it. +> The image installs mempalace into an isolated `uv tool` venv at `/opt/uv-tools/mempalace/`. `uv tool install` places `mempalace-mcp` on `PATH` as a shim whose shebang points at the venv's Python, so MCP clients can invoke it as a normal binary without worrying about the venv. Do **not** use `["python3", "-m", "mempalace.mcp_server"]` — the system Python cannot import from the uv-managed venv and you'll get `ModuleNotFoundError` / `MCP error -32000: connection closed`. This gives opencode access to 29 MCP tools for searching memory, querying the knowledge graph, managing wings/rooms/drawers, and agent diaries. diff --git a/Dockerfile b/Dockerfile index 333e32c..d7c2d30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -340,11 +340,9 @@ COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc # ── Entrypoint ──────────────────────────────────────────────────────── COPY rootfs/usr/local/lib/opencode-devbox/ /usr/local/lib/opencode-devbox/ -COPY rootfs/usr/local/bin/ /usr/local/bin/ COPY entrypoint.sh /usr/local/bin/entrypoint.sh COPY entrypoint-user.sh /usr/local/bin/entrypoint-user.sh RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/entrypoint-user.sh \ - /usr/local/bin/mempalace-mcp-server \ /usr/local/lib/opencode-devbox/*.py # Start as root — entrypoint adjusts UID/GID then drops to developer diff --git a/README.md b/README.md index 0e7b107..ddd21ca 100644 --- a/README.md +++ b/README.md @@ -474,13 +474,13 @@ Add mempalace as an MCP server in your `opencode.json` (inside `~/.config/openco "mcp": { "mempalace": { "type": "local", - "command": ["mempalace-mcp-server"] + "command": ["mempalace-mcp"] } } } ``` -> The image installs mempalace into an isolated `uv tool` venv at `/opt/uv-tools/mempalace`. The `mempalace-mcp-server` wrapper on `PATH` exec's the venv's Python with the `mempalace.mcp_server` module — you don't need to know about the venv to use it. +> The image installs mempalace into an isolated `uv tool` venv at `/opt/uv-tools/mempalace/`. `uv tool install` places `mempalace-mcp` on `PATH` as a shim whose shebang points at the venv's Python, so MCP clients can invoke it as a normal binary without worrying about the venv. Do **not** use `["python3", "-m", "mempalace.mcp_server"]` — the system Python cannot import from the uv-managed venv and you'll get `ModuleNotFoundError` / `MCP error -32000: connection closed`. This gives opencode access to 29 MCP tools for searching memory, querying the knowledge graph, managing wings/rooms/drawers, and agent diaries. diff --git a/rootfs/usr/local/bin/mempalace-mcp-server b/rootfs/usr/local/bin/mempalace-mcp-server deleted file mode 100644 index 1619c1b..0000000 --- a/rootfs/usr/local/bin/mempalace-mcp-server +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh -# Launcher for the MemPalace MCP server. -# -# MemPalace is installed via `uv tool install` into an isolated venv -# under /opt/uv-tools/. System python3 cannot import mempalace directly, -# so this wrapper exec's the venv's python with the mcp_server module. -# -# Used by opencode.json: -# "command": ["mempalace-mcp-server"] -exec /opt/uv-tools/mempalace/bin/python -m mempalace.mcp_server "$@" diff --git a/rootfs/usr/local/lib/opencode-devbox/generate-config.py b/rootfs/usr/local/lib/opencode-devbox/generate-config.py index 5d25735..6f9de7c 100755 --- a/rootfs/usr/local/lib/opencode-devbox/generate-config.py +++ b/rootfs/usr/local/lib/opencode-devbox/generate-config.py @@ -75,14 +75,15 @@ def register_mcp_servers(config: dict) -> list[str]: servers: dict[str, dict] = {} # MemPalace — local-first AI memory (if installed). - # Uses the mempalace-mcp-server wrapper rather than invoking - # `python3 -m mempalace.mcp_server` directly, because mempalace - # lives in an isolated uv tool venv that system python3 cannot - # import from. The wrapper exec's the right interpreter. - if shutil.which("mempalace") and shutil.which("mempalace-mcp-server"): + # `mempalace-mcp` is the entry-point binary shipped by the mempalace + # Python package. `uv tool install mempalace` places it on PATH as a + # shim whose shebang points at the isolated venv's Python, so system + # `python3 -m mempalace.mcp_server` (which would fail — system + # python3 can't import from the uv venv) is unnecessary here. + if shutil.which("mempalace-mcp"): servers["mempalace"] = { "type": "local", - "command": ["mempalace-mcp-server"], + "command": ["mempalace-mcp"], } # Gitea — self-hosted Git forge API (if installed). diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 053d3f8..9df094f 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -99,7 +99,7 @@ echo "-- Optional / variant-gated --" # mempalace: present unless built with INSTALL_MEMPALACE=false if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v mempalace" >/dev/null 2>&1; then run "mempalace" "mempalace --help | head -1" - run "mempalace-mcp-server" "test -x /usr/local/bin/mempalace-mcp-server && echo wrapper-present" + run "mempalace-mcp" "test -x /usr/local/bin/mempalace-mcp && readlink /usr/local/bin/mempalace-mcp" else echo " - mempalace not installed (INSTALL_MEMPALACE=false)" fi