Skip to content

Commit 49a2284

Browse files
authored
Merge pull request #61 from r33drichards/claude/add-mcp-js-docker-6PwWp
Claude/add mcp js docker 6 pw wp
2 parents 832b44b + cf8aeb4 commit 49a2284

4 files changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Docker Compose Integration Test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- Dockerfile
8+
- docker-compose.yml
9+
- policies/**
10+
- tests/docker-compose-integration.sh
11+
pull_request:
12+
branches: [main]
13+
paths:
14+
- Dockerfile
15+
- docker-compose.yml
16+
- policies/**
17+
- tests/docker-compose-integration.sh
18+
schedule:
19+
- cron: "0 6 * * *" # daily at 06:00 UTC
20+
workflow_dispatch:
21+
22+
jobs:
23+
integration-test:
24+
name: Docker Compose Integration
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 10
27+
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v4
31+
32+
- name: Run integration tests
33+
run: ./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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,18 @@ services:
66
- "8181:8181"
77
volumes:
88
- ./policies:/policies:ro
9+
10+
mcp-js:
11+
image: docker.io/wholelottahoopla/mcp-js:latest
12+
command:
13+
- mcp-v8
14+
- --http-port=3000
15+
- --directory-path=/data/heaps
16+
- --session-db-path=/data/sessions
17+
- --opa-url=http://opa:8181
18+
tmpfs:
19+
- /data
20+
ports:
21+
- "3000:3000"
22+
depends_on:
23+
- opa
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env bash
2+
# Integration test for docker-compose.yml (mcp-js + OPA)
3+
#
4+
# Asserts:
5+
# 1. JavaScript execution works
6+
# 2. fetch() to an allowed domain succeeds
7+
# 3. fetch() to a blocked domain is denied by OPA policy
8+
# 4. Heap snapshots are created and can be restored
9+
#
10+
# Usage:
11+
# ./tests/docker-compose-integration.sh
12+
#
13+
# Prerequisites: docker compose, curl, jq
14+
15+
set -euo pipefail
16+
17+
COMPOSE_FILE="docker-compose.yml"
18+
BASE_URL="http://localhost:3000"
19+
PASSED=0
20+
FAILED=0
21+
22+
# ── Helpers ──────────────────────────────────────────────────────────────────
23+
24+
cleanup() {
25+
echo ""
26+
echo "==> Tearing down containers..."
27+
docker compose -f "$COMPOSE_FILE" down -v --remove-orphans 2>/dev/null || true
28+
}
29+
trap cleanup EXIT
30+
31+
fail() {
32+
echo " FAIL: $1"
33+
FAILED=$((FAILED + 1))
34+
}
35+
36+
pass() {
37+
echo " PASS: $1"
38+
PASSED=$((PASSED + 1))
39+
}
40+
41+
wait_for_ready() {
42+
local url="$1"
43+
local retries=30
44+
local i=0
45+
echo "==> Waiting for mcp-js to be ready..."
46+
while [ $i -lt $retries ]; do
47+
if curl -sf -o /dev/null -X POST "$url/api/exec" \
48+
-H "Content-Type: application/json" \
49+
-d '{"code":"1"}' 2>/dev/null; then
50+
echo " mcp-js is ready."
51+
return 0
52+
fi
53+
sleep 2
54+
i=$((i + 1))
55+
done
56+
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
66+
exit 1
67+
}
68+
69+
exec_js() {
70+
local payload="$1"
71+
curl -sf -X POST "$BASE_URL/api/exec" \
72+
-H "Content-Type: application/json" \
73+
-d "$payload"
74+
}
75+
76+
# ── Start services ───────────────────────────────────────────────────────────
77+
78+
echo "==> Starting docker-compose services..."
79+
docker compose -f "$COMPOSE_FILE" up -d
80+
81+
wait_for_ready "$BASE_URL"
82+
83+
# ── Test 1: Basic JavaScript execution ───────────────────────────────────────
84+
85+
echo ""
86+
echo "==> Test 1: Basic JavaScript execution"
87+
RESULT=$(exec_js '{"code":"const x = 2 + 3; x;"}')
88+
OUTPUT=$(echo "$RESULT" | jq -r '.output')
89+
if [ "$OUTPUT" = "5" ]; then
90+
pass "JS execution returned 5"
91+
else
92+
fail "Expected output '5', got '$OUTPUT'"
93+
fi
94+
95+
# ── Test 2: fetch() to allowed domain succeeds ──────────────────────────────
96+
97+
echo ""
98+
echo "==> Test 2: fetch() to allowed domain (registry.npmjs.org)"
99+
RESULT=$(exec_js '{"code":"(async () => { const r = await fetch(\"https://registry.npmjs.org/\"); return r.ok; })()"}')
100+
OUTPUT=$(echo "$RESULT" | jq -r '.output')
101+
if [ "$OUTPUT" = "true" ]; then
102+
pass "fetch() to allowed domain returned ok=true"
103+
else
104+
fail "Expected output 'true', got '$OUTPUT' (full response: $RESULT)"
105+
fi
106+
107+
# ── Test 3: fetch() to blocked domain is denied by OPA ──────────────────────
108+
109+
echo ""
110+
echo "==> Test 3: fetch() to blocked domain (evil.example.com)"
111+
HTTP_CODE=$(curl -s -o /tmp/mcp-test-deny.json -w "%{http_code}" -X POST "$BASE_URL/api/exec" \
112+
-H "Content-Type: application/json" \
113+
-d '{"code":"(async () => { const r = await fetch(\"https://evil.example.com/\"); return r.ok; })()"}')
114+
BODY=$(cat /tmp/mcp-test-deny.json)
115+
if [ "$HTTP_CODE" = "500" ] && echo "$BODY" | grep -qi "denied\|policy"; then
116+
pass "fetch() to blocked domain was denied by policy (HTTP $HTTP_CODE)"
117+
else
118+
fail "Expected HTTP 500 with policy denial, got HTTP $HTTP_CODE: $BODY"
119+
fi
120+
121+
# ── Test 4: Heap snapshot created and restorable ─────────────────────────────
122+
123+
echo ""
124+
echo "==> Test 4: Heap snapshot persistence"
125+
126+
# 4a: Execute code that sets state, capture the heap hash
127+
RESULT=$(exec_js '{"code":"var counter = 42;"}')
128+
HEAP=$(echo "$RESULT" | jq -r '.heap')
129+
if [ -z "$HEAP" ] || [ "$HEAP" = "null" ]; then
130+
fail "No heap hash returned from initial execution"
131+
else
132+
pass "Heap hash returned: ${HEAP:0:16}..."
133+
134+
# 4b: Restore the heap and read the persisted state
135+
RESULT2=$(exec_js "{\"code\":\"counter;\",\"heap\":\"$HEAP\"}")
136+
OUTPUT2=$(echo "$RESULT2" | jq -r '.output')
137+
if [ "$OUTPUT2" = "42" ]; then
138+
pass "Heap restored successfully, counter = 42"
139+
else
140+
fail "Expected counter=42 after heap restore, got '$OUTPUT2'"
141+
fi
142+
fi
143+
144+
# ── Summary ──────────────────────────────────────────────────────────────────
145+
146+
echo ""
147+
echo "=============================="
148+
echo "Results: $PASSED passed, $FAILED failed"
149+
echo "=============================="
150+
151+
if [ "$FAILED" -gt 0 ]; then
152+
exit 1
153+
fi

0 commit comments

Comments
 (0)