Skip to content

Commit 37a3680

Browse files
Rynaroclaude
andauthored
fix(mcp): drop static docker --name so MCP servers survive host reconnect (#452)
All four Docker-based MCP templates hardcoded `docker run --name <mcp>-<slug>`. On a dropped stdio pipe the host (e.g. Claude Code) re-spawns the server, but the original `--rm` container is still Up, so the reconnect's `docker run --name …` collides with "container name already in use" and the host surfaces an opaque MCP -32000 (JSON-RPC server error). Remove the static --name from crystalium/tonberry/atomos/atlas-aci templates. Per-project identity/filtering already rides `--label eidolons.project=<slug>`, and parallel-project isolation is preserved by the distinct per-project bind mounts — every `docker run` is its own container regardless of name; the name only added a singleton guard that broke reconnect. `cli/src/memory.sh` already strips --name from its out-of-band recall path for this exact reason. atlas-aci's summary log line + render tests now assert the --label slug in place of the retired --name. Consumers parse .mcp.json args by value, so existing generated files with --name stay readable; regenerate via `eidolons mcp sync`. mcp_atlas_aci.bats 17/17. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2fb1938 commit 37a3680

7 files changed

Lines changed: 39 additions & 36 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version
1212

1313
- **README revamped for the v2 line — research-grounded recomposition, not a polish pass.** Grounded in a 16-repo study of celebrated OSS READMEs plus peer-reviewed attention/reading research (F-pattern scanning, curiosity-gap theory, serial-position effects, processing fluency, the blemishing effect), produced with deliberate model tiering (haiku fact audit / sonnet research / opus cold-read checker / Fable composition). New: a **"See it route"** demo showing real, reproducible `eidolons run --json` kernel output; a **"When Eidolons is the wrong tool"** honest-scoping section; the ESL + **ECM** "lifecycle and the context economy" section (four sibling-contract table, 5-MCP catalogue incl. atomos); a linked MANIFESTO (previously orphaned) closing in the recency slot; and one deadpan acknowledgment of the Final Fantasy naming. Restructured: benefit-first intro (the ~90-word 11-concept sentence is gone), team-before-proof ordering, the free zero-token routing benchmark now *leads* "Does it actually work?" with the four billed measurements compressed to one summary table + `<details>`. Verified maker≠checker under ESL change `.spectra/changes/readme-v2-revamp/` (30/30 links, every number traced to committed scorecards, dynamic badges intact, dual-audience cold-read PASS).
1414

15+
### Fixed
16+
17+
- **Docker MCP servers no longer fail to reconnect after a dropped stdio pipe** (`cli/templates/mcp/{crystalium,tonberry,atomos,atlas-aci}.mcp.json.tmpl`). All four Docker-based MCP templates hardcoded a static `docker run --name <mcp>-<slug>`. When a host (e.g. Claude Code) lost the stdio connection and re-spawned the server, the original `--rm` container was still `Up`, so the reconnect's `docker run --name …` collided — `docker: Error response from daemon: Conflict. The container name "…" is already in use` — the subprocess exited immediately and the host surfaced it as an opaque MCP `-32000` (JSON-RPC server error). The static `--name` is removed from every template; per-project identity/filtering now rides the already-present `--label eidolons.project=<slug>`, and parallel-project isolation is preserved by the distinct per-project bind mounts (every `docker run` is its own container regardless of name — the name only added a singleton guard that broke reconnect). `cli/src/memory.sh` already stripped `--name` from its out-of-band recall path for this exact reason (*"avoid colliding with running serve container"*). atlas-aci's summary log line (`cli/src/mcp_atlas_aci.sh`) and its render tests (`cli/tests/mcp_atlas_aci.bats`) now assert the `--label` slug in place of the retired `--name`. Consumers already parse `.mcp.json` args by value, so existing generated files with `--name` remain readable; regenerate cleanly via `eidolons mcp sync`.
18+
1519
## [2.3.0] — 2026-07-07 — ECM P2/P3: host adapters + the atomos MCP
1620

1721
### Added

cli/src/mcp_atlas_aci.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ What it does:
8080
4. Renders the Atlas-ACI MCP template into <project-root>/.mcp.json.
8181
8282
The generated .mcp.json wires Atlas-ACI as a Docker-based MCP server with:
83-
- A per-project container name (atlas-aci-<slug>) for parallel-project isolation.
83+
- A per-project label (eidolons.project=<slug>) for identification/filtering.
84+
No static --name — it would collide with the running serve container when the
85+
host re-spawns on reconnect (docker: "container name already in use").
8486
- Distinct bind mounts so each project's codegraph.db is independent.
8587
- The pinned image digest (no tag drift).
8688
@@ -296,7 +298,7 @@ fi
296298

297299
# ─── Summary ──────────────────────────────────────────────────────────────
298300
info "Atlas-ACI MCP scaffold complete."
299-
info " Container name : atlas-aci-${PROJECT_SLUG}"
301+
info " Project label : eidolons.project=${PROJECT_SLUG}"
300302
info " Bind mount : ${MEMEX_DIR} → /memex (inside container)"
301303
info " Image digest : ${IMAGE_DIGEST}"
302304
info ""

cli/templates/mcp/atlas-aci.mcp.json.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
"--rm",
88
"-i",
99
"--read-only",
10-
"--name",
11-
"atlas-aci-__PROJECT_SLUG__",
1210
"--label",
1311
"eidolons.project=__PROJECT_SLUG__",
1412
"-v",

cli/templates/mcp/atomos.mcp.json.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"run",
77
"--rm",
88
"-i",
9-
"--name",
10-
"atomos-__PROJECT_SLUG__",
119
"--label",
1210
"eidolons.project=__PROJECT_SLUG__",
1311
"-v",

cli/templates/mcp/crystalium.mcp.json.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"run",
77
"--rm",
88
"-i",
9-
"--name",
10-
"crystalium-__PROJECT_SLUG__",
119
"--label",
1210
"eidolons.project=__PROJECT_SLUG__",
1311
"-v",

cli/templates/mcp/tonberry.mcp.json.tmpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
"run",
77
"--rm",
88
"-i",
9-
"--name",
10-
"tonberry-__PROJECT_SLUG__",
119
"--label",
1210
"eidolons.project=__PROJECT_SLUG__",
1311
"-v",

cli/tests/mcp_atlas_aci.bats

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,21 @@ file_md5() {
214214
# .atlas/memex/.gitkeep must exist.
215215
[ -f "$project/.atlas/memex/.gitkeep" ]
216216

217-
# The --name entry in the args array must equal "atlas-aci-<slug>".
217+
# No static --name (it would collide with the running serve container on host
218+
# reconnect). Per-project identity lives in the --label instead.
219+
run bash -c "jq -e 'any(.mcpServers.\"atlas-aci\".args[]; . == \"--name\") | not' '$project/.mcp.json'"
220+
[ "$status" -eq 0 ]
221+
222+
# The --label entry must equal "eidolons.project=<slug>".
218223
# Slug of "fresh-project" is "fresh-project".
219-
local name_val
220-
name_val="$(jq -r '
224+
local label_val
225+
label_val="$(jq -r '
221226
.mcpServers."atlas-aci".args as $a |
222-
($a | indices("--name"))[0] as $i |
227+
($a | indices("--label"))[0] as $i |
223228
$a[$i + 1]
224229
' "$project/.mcp.json")"
225230

226-
[ "$name_val" = "atlas-aci-fresh-project" ]
231+
[ "$label_val" = "eidolons.project=fresh-project" ]
227232

228233
# Security hardening flags (H1): --cap-drop ALL and --security-opt no-new-privileges
229234
# must be present in the args array (defense-in-depth on top of the UID 10001 Dockerfile).
@@ -324,8 +329,8 @@ file_md5() {
324329
[ "$db_hash_before" = "$db_hash_after" ]
325330
}
326331

327-
# ─── Test 4: two distinct project roots → distinct --name and bind paths ───
328-
@test "mcp atlas-aci: two distinct roots produce distinct --name values and bind-mount paths" {
332+
# ─── Test 4: two distinct project roots → distinct --label and bind paths ───
333+
@test "mcp atlas-aci: two distinct roots produce distinct --label values and bind-mount paths" {
329334
local project_a="$BATS_TEST_TMPDIR/project-alpha"
330335
local project_b="$BATS_TEST_TMPDIR/project-beta"
331336
mkdir -p "$project_a" "$project_b"
@@ -335,25 +340,25 @@ file_md5() {
335340
run_generator --project-root "$project_b"
336341
[ "$status" -eq 0 ]
337342

338-
# Extract --name values from each generated .mcp.json.
339-
local name_a name_b
340-
name_a="$(jq -r '
343+
# Extract --label values from each generated .mcp.json.
344+
local label_a label_b
345+
label_a="$(jq -r '
341346
.mcpServers."atlas-aci".args as $a |
342-
($a | indices("--name"))[0] as $i |
347+
($a | indices("--label"))[0] as $i |
343348
$a[$i + 1]
344349
' "$project_a/.mcp.json")"
345-
name_b="$(jq -r '
350+
label_b="$(jq -r '
346351
.mcpServers."atlas-aci".args as $a |
347-
($a | indices("--name"))[0] as $i |
352+
($a | indices("--label"))[0] as $i |
348353
$a[$i + 1]
349354
' "$project_b/.mcp.json")"
350355

351-
# Names must differ.
352-
[ "$name_a" != "$name_b" ]
356+
# Labels must differ.
357+
[ "$label_a" != "$label_b" ]
353358

354-
# Each --name must reference its own slug.
355-
[ "$name_a" = "atlas-aci-project-alpha" ]
356-
[ "$name_b" = "atlas-aci-project-beta" ]
359+
# Each --label must reference its own slug.
360+
[ "$label_a" = "eidolons.project=project-alpha" ]
361+
[ "$label_b" = "eidolons.project=project-beta" ]
357362

358363
# The bind-mount source for /memex must be distinct in each file.
359364
# Extract the string that precedes ":/memex" from the args array.
@@ -389,20 +394,20 @@ file_md5() {
389394
run bash -c "jq empty '$project/.mcp.json'"
390395
[ "$status" -eq 0 ]
391396

392-
# Extract the slug from the --name arg.
393-
local name_val
394-
name_val="$(jq -r '
397+
# Extract the slug from the --label arg.
398+
local label_val
399+
label_val="$(jq -r '
395400
.mcpServers."atlas-aci".args as $a |
396-
($a | indices("--name"))[0] as $i |
401+
($a | indices("--label"))[0] as $i |
397402
$a[$i + 1]
398403
' "$project/.mcp.json")"
399404

400-
# Expected: "atlas-aci-my-project-dir"
401-
[ "$name_val" = "atlas-aci-my-project-dir" ]
405+
# Expected: "eidolons.project=my-project-dir"
406+
[ "$label_val" = "eidolons.project=my-project-dir" ]
402407

403-
# The slug portion (strip leading "atlas-aci-") must match
408+
# The slug portion (strip leading "eidolons.project=") must match
404409
# the pattern: only lowercase letters, digits, and dashes.
405-
local slug="${name_val#atlas-aci-}"
410+
local slug="${label_val#eidolons.project=}"
406411
run bash -c "printf '%s' '$slug' | grep -Eq '^[a-z0-9-]+$'"
407412
[ "$status" -eq 0 ]
408413

0 commit comments

Comments
 (0)