Skip to content

Commit cf8aeb4

Browse files
r33drichardsclaude
andcommitted
Fix docker-compose integration test: volume permissions and async fetch
The mcp-js container runs as non-root mcpuser (UID 1000) but the /data directory never existed in the Docker image. When Docker mounted a named volume at /data, it was created as root-owned, so the server could not write heap snapshots — causing every /api/exec request to return HTTP 500 ("Error saving heap") and the health check to never pass. Fixes: - Dockerfile: create /data with mcpuser ownership before USER switch - docker-compose.yml: use tmpfs for /data (world-writable, works with the existing published image without waiting for a new Docker build) - Test script: print container logs and status on health check failure - Test script: wrap fetch() calls in async IIFE since fetch() is now async on main (returns a Promise) - CI workflow: trigger on Dockerfile changes too Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f2d5bbb commit cf8aeb4

4 files changed

Lines changed: 18 additions & 7 deletions

File tree

.github/workflows/docker-compose-integration.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ on:
44
push:
55
branches: [main]
66
paths:
7+
- Dockerfile
78
- docker-compose.yml
89
- policies/**
910
- tests/docker-compose-integration.sh
1011
pull_request:
1112
branches: [main]
1213
paths:
14+
- Dockerfile
1315
- docker-compose.yml
1416
- policies/**
1517
- tests/docker-compose-integration.sh

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ COPY --from=builder /app/server/target/release/server /usr/local/bin/mcp-v8
3838
# Set ownership
3939
RUN chown mcpuser:mcpuser /usr/local/bin/mcp-v8
4040

41+
# Create default data directory for stateful mode (heaps, sessions, etc.)
42+
RUN mkdir -p /data && chown mcpuser:mcpuser /data
43+
4144
# Switch to non-root user
4245
USER mcpuser
4346

docker-compose.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,9 @@ services:
1515
- --directory-path=/data/heaps
1616
- --session-db-path=/data/sessions
1717
- --opa-url=http://opa:8181
18-
volumes:
19-
- mcp-js-data:/data
18+
tmpfs:
19+
- /data
2020
ports:
2121
- "3000:3000"
2222
depends_on:
2323
- opa
24-
25-
volumes:
26-
mcp-js-data:

tests/docker-compose-integration.sh

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ wait_for_ready() {
5454
i=$((i + 1))
5555
done
5656
echo " ERROR: mcp-js did not become ready in time."
57+
echo ""
58+
echo "==> Container status:"
59+
docker compose -f "$COMPOSE_FILE" ps
60+
echo ""
61+
echo "==> mcp-js logs:"
62+
docker compose -f "$COMPOSE_FILE" logs mcp-js
63+
echo ""
64+
echo "==> opa logs:"
65+
docker compose -f "$COMPOSE_FILE" logs opa
5766
exit 1
5867
}
5968

@@ -87,7 +96,7 @@ fi
8796

8897
echo ""
8998
echo "==> Test 2: fetch() to allowed domain (registry.npmjs.org)"
90-
RESULT=$(exec_js '{"code":"const r = fetch(\"https://registry.npmjs.org/\"); r.ok;"}')
99+
RESULT=$(exec_js '{"code":"(async () => { const r = await fetch(\"https://registry.npmjs.org/\"); return r.ok; })()"}')
91100
OUTPUT=$(echo "$RESULT" | jq -r '.output')
92101
if [ "$OUTPUT" = "true" ]; then
93102
pass "fetch() to allowed domain returned ok=true"
@@ -101,7 +110,7 @@ echo ""
101110
echo "==> Test 3: fetch() to blocked domain (evil.example.com)"
102111
HTTP_CODE=$(curl -s -o /tmp/mcp-test-deny.json -w "%{http_code}" -X POST "$BASE_URL/api/exec" \
103112
-H "Content-Type: application/json" \
104-
-d '{"code":"fetch(\"https://evil.example.com/\");"}')
113+
-d '{"code":"(async () => { const r = await fetch(\"https://evil.example.com/\"); return r.ok; })()"}')
105114
BODY=$(cat /tmp/mcp-test-deny.json)
106115
if [ "$HTTP_CODE" = "500" ] && echo "$BODY" | grep -qi "denied\|policy"; then
107116
pass "fetch() to blocked domain was denied by policy (HTTP $HTTP_CODE)"

0 commit comments

Comments
 (0)