sap-mcp-bridge: new app — five SAP MCP servers behind one nginx-proxied port
This commit is contained in:
96
apps/sap-mcp-bridge/build/entrypoint.sh
Normal file
96
apps/sap-mcp-bridge/build/entrypoint.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env bash
|
||||
# sap-mcp-bridge entrypoint
|
||||
#
|
||||
# Lifecycle:
|
||||
# 1. (as root) Ensure /workspace exists and is owned by the mcp user,
|
||||
# then re-exec ourselves as mcp via gosu. This makes the mount
|
||||
# writable even when Docker auto-creates the host path as root.
|
||||
# 2. (as mcp) Install the signal trap BEFORE forking any children, so
|
||||
# a SIGTERM arriving during start-up still tears the whole group
|
||||
# down cleanly.
|
||||
# 3. (as mcp) Launch one supergateway per SAP MCP server on
|
||||
# 127.0.0.1:9001..9005. They speak Streamable HTTP at /mcp and
|
||||
# expose /healthz.
|
||||
# 4. (as mcp) Wait for every gateway's /healthz to answer (with a
|
||||
# cap), then launch nginx in the foreground. This avoids the
|
||||
# cold-start window where nginx 502s because upstreams haven't
|
||||
# bound yet.
|
||||
# 5. (as mcp) `wait -n` — exit as soon as ANY direct child (gateway
|
||||
# or nginx) dies. Docker restarts the container, so a crashed
|
||||
# gateway resets the whole group (acceptable for homelab use).
|
||||
set -euo pipefail
|
||||
|
||||
WORKDIR_HOST="${WORKDIR_HOST:-/workspace}"
|
||||
APP_UID=10001
|
||||
APP_GID=65534 # nogroup
|
||||
APP_USER="mcp"
|
||||
GATEWAY_PORTS=(9001 9002 9003 9004 9005)
|
||||
|
||||
# --- Stage 1: privilege handling -----------------------------------
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
mkdir -p "$WORKDIR_HOST"
|
||||
# Skip the chown if the dir is already owned correctly — recursive
|
||||
# chown on a large mounted workspace is slow and runs every restart.
|
||||
# The chown can fail on read-only / fuse / overlay mounts; we tolerate
|
||||
# failure rather than crash (the mcp user just won't be able to write).
|
||||
current_owner="$(stat -c '%u' "$WORKDIR_HOST" 2>/dev/null || echo "")"
|
||||
if [ "$current_owner" != "$APP_UID" ]; then
|
||||
chown -R "${APP_UID}:${APP_GID}" "$WORKDIR_HOST" 2>/dev/null || true
|
||||
fi
|
||||
exec gosu "${APP_USER}" "$0" "$@"
|
||||
fi
|
||||
|
||||
# --- Stage 2: install trap BEFORE children -------------------------
|
||||
# kill 0 sends SIGTERM to every process in our group; tini will reap
|
||||
# them. Done now so a SIGTERM that lands during start-up still tears
|
||||
# the whole group down cleanly.
|
||||
trap 'echo "[sap-mcp-bridge] received signal, shutting down"; kill 0' SIGTERM SIGINT
|
||||
|
||||
# --- Stage 3: launch gateways --------------------------------------
|
||||
cd "$WORKDIR_HOST" 2>/dev/null || cd /home/mcp
|
||||
|
||||
start() {
|
||||
local name="$1" port="$2" cmd="$3"
|
||||
echo "[sap-mcp-bridge] starting ${name} on 127.0.0.1:${port} -> ${cmd}"
|
||||
# --stateful gives each MCP client its own subprocess (safer for the
|
||||
# SAP servers, which keep auth state per-session).
|
||||
supergateway \
|
||||
--stdio "${cmd}" \
|
||||
--outputTransport streamableHttp \
|
||||
--port "${port}" \
|
||||
--streamableHttpPath /mcp \
|
||||
--healthEndpoint /healthz \
|
||||
--cors \
|
||||
--stateful \
|
||||
--logLevel info \
|
||||
&
|
||||
}
|
||||
|
||||
start "sap-cap" 9001 "iflow-mcp_cap-js-mcp-server"
|
||||
start "sap-abap-adt" 9002 "mcp-abap-adt"
|
||||
start "sap-odata" 9003 "btp-sap-odata-to-mcp-server"
|
||||
start "ui5-mcp-server" 9004 "ui5mcp"
|
||||
start "fiori-mcp-server" 9005 "fiori-mcp"
|
||||
|
||||
# --- Stage 4: wait for upstreams, then nginx -----------------------
|
||||
# Poll each gateway's /healthz for up to ~30s. Without this nginx can
|
||||
# come up before the Node processes finish booting and serve 502s on
|
||||
# the first requests (and to the Traefik healthcheck).
|
||||
for port in "${GATEWAY_PORTS[@]}"; do
|
||||
for _ in $(seq 30); do
|
||||
if curl -fsS -m 1 "http://127.0.0.1:${port}/healthz" >/dev/null 2>&1; then
|
||||
echo "[sap-mcp-bridge] upstream :${port} ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
echo "[sap-mcp-bridge] starting nginx reverse proxy on :8080"
|
||||
nginx -g 'daemon off;' &
|
||||
|
||||
# --- Stage 5: shut down on first child death -----------------------
|
||||
wait -n
|
||||
echo "[sap-mcp-bridge] a child exited, taking the container down"
|
||||
kill 0 2>/dev/null || true
|
||||
exit 1
|
||||
Reference in New Issue
Block a user