release: remote HTTP MCP — bearer + OAuth, Docker image, CI publish, per-client docs #65
@@ -52,6 +52,9 @@ jobs:
|
||||
IMAGE="git.hiddenden.cafe/${GITHUB_REPOSITORY,,}"
|
||||
echo "image=${IMAGE}" >> "$GITHUB_OUTPUT"
|
||||
echo "sha_tag=${IMAGE}:sha-${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
|
||||
# Per-commit container name: the self-hosted runner shares the host
|
||||
# Docker daemon, so a fixed name collides across concurrent/re-run jobs.
|
||||
echo "container=familiar-smoke-${GITHUB_SHA::12}" >> "$GITHUB_OUTPUT"
|
||||
if [ "${GITHUB_REF_NAME}" = "main" ]; then
|
||||
echo "branch_tags=${IMAGE}:latest ${IMAGE}:main" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
@@ -67,8 +70,13 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IMG="${{ steps.meta.outputs.sha_tag }}"
|
||||
CONTAINER="${{ steps.meta.outputs.container }}"
|
||||
TOKEN="smoke-$(date +%s)"
|
||||
|
||||
# Remove any container left behind by a killed/re-run job before we
|
||||
# reuse the name (shared host daemon → stale containers can survive).
|
||||
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
|
||||
|
||||
# 1. Runs as a non-root user (uid 1000).
|
||||
uid=$(docker run --rm --entrypoint id "$IMG" -u)
|
||||
echo "container uid: $uid"
|
||||
@@ -83,13 +91,13 @@ jobs:
|
||||
# socket and runs the probe in the server's own netns, where
|
||||
# 127.0.0.1:8080 always works regardless of runner topology. The image
|
||||
# ships python + httpx, so no curl or extra pull is needed.
|
||||
docker run -d --name familiar-smoke -e FAMILIAR_HTTP_TOKEN="$TOKEN" "$IMG"
|
||||
docker run -d --name "$CONTAINER" -e FAMILIAR_HTTP_TOKEN="$TOKEN" "$IMG"
|
||||
|
||||
# probe "<httpx call returning a Response>" -> prints the status code
|
||||
probe() { docker exec familiar-smoke python -c "import httpx; print(($1).status_code)"; }
|
||||
probe() { docker exec "$CONTAINER" python -c "import httpx; print(($1).status_code)"; }
|
||||
|
||||
for i in $(seq 1 30); do
|
||||
if docker exec familiar-smoke python -c \
|
||||
if docker exec "$CONTAINER" python -c \
|
||||
"import httpx; httpx.get('http://127.0.0.1:8080/healthz', timeout=5)" >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
@@ -98,7 +106,7 @@ jobs:
|
||||
|
||||
echo "== /healthz (expect 200) =="
|
||||
code=$(probe "httpx.get('http://127.0.0.1:8080/healthz', timeout=10)")
|
||||
[ "$code" = "200" ] || { echo "::error::/healthz returned $code"; docker logs familiar-smoke; exit 1; }
|
||||
[ "$code" = "200" ] || { echo "::error::/healthz returned $code"; docker logs "$CONTAINER"; exit 1; }
|
||||
|
||||
echo "== MCP without token (expect 401) =="
|
||||
code=$(probe "httpx.post('http://127.0.0.1:8080/mcp', timeout=10, headers={'Accept':'application/json, text/event-stream','Content-Type':'application/json'}, json={'jsonrpc':'2.0','id':1,'method':'initialize','params':{}})")
|
||||
@@ -106,7 +114,7 @@ jobs:
|
||||
|
||||
echo "== MCP with valid token (expect 200) =="
|
||||
code=$(probe "httpx.post('http://127.0.0.1:8080/mcp', timeout=10, headers={'Authorization':'Bearer $TOKEN','Accept':'application/json, text/event-stream','Content-Type':'application/json'}, json={'jsonrpc':'2.0','id':1,'method':'initialize','params':{'protocolVersion':'2025-06-18','capabilities':{},'clientInfo':{'name':'ci','version':'1'}}})")
|
||||
[ "$code" = "200" ] || { echo "::error::authenticated MCP returned $code"; docker logs familiar-smoke; exit 1; }
|
||||
[ "$code" = "200" ] || { echo "::error::authenticated MCP returned $code"; docker logs "$CONTAINER"; exit 1; }
|
||||
|
||||
echo "Image smoke-test passed."
|
||||
|
||||
@@ -135,7 +143,7 @@ jobs:
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
docker rm -f familiar-smoke || true
|
||||
docker rm -f ${{ steps.meta.outputs.container }} || true
|
||||
docker rmi -f ${{ steps.meta.outputs.sha_tag }} ${{ steps.meta.outputs.branch_tags }} || true
|
||||
docker image prune -f || true
|
||||
docker builder prune -f || true
|
||||
Reference in new issue
Block a user