Skip to content

Ship eic-mcp: launcher for the EIC MCP servers#328

Closed
aprozo wants to merge 1 commit into
eic:masterfrom
aprozo:feat/eic-mcp
Closed

Ship eic-mcp: launcher for the EIC MCP servers#328
aprozo wants to merge 1 commit into
eic:masterfrom
aprozo:feat/eic-mcp

Conversation

@aprozo

@aprozo aprozo commented Jul 1, 2026

Copy link
Copy Markdown

What

Adds /opt/local/bin/eic-mcp, a small registry-driven launcher that sets up and runs the EIC MCP
servers as local SSE services for an LLM client (opencode, Claude Code, Copilot, …) inside eic-shell:

eic-mcp setup     # one-time: clone + build the enabled servers into $HOME
eic-mcp up        # start them (SSE on 127.0.0.1:9101-9103)
eic-mcp list | status | logs <name> | down

Why

The MCP servers need a little orchestration — build, run over SSE, per-server auth/config — that
isn't shipped today. eic-mcp provides it as one command, so a user only needs eic-shell. It is
registry-driven: new EIC servers are added with a drop-in ~/.config/eic-mcp/servers.d/*.conf (no
edit to the script), and stdio-only servers are exposed over SSE via a supergateway bridge.

Details

  • New file containers/eic/eic-mcp (bash), COPY'd to /opt/local/bin/eic-mcp — same pattern as
    eic-shell / eic-info / eic-news.
  • rucio authenticates automatically with the shared read-only eicread account from
    /opt/rucio/etc/rucio.cfg — no secrets in the image.
  • Servers build into $HOME/eic-mcp/servers (not the image), so the image change is just one
    script + one COPY line.

Dependency

Full SSE functionality depends on SSE-transport support in the servers (PRs to
eic/uproot-mcp-server, eic/xrootd-mcp-server, eic/rucio-eic-mcp-server). Until those merge,
eic-mcp setup clones their feat/sse-transport branches and falls back to the default branch
otherwise. (Once xrootd-mcp-server / zenodo-mcp-server are packaged and built in the image,
eic-mcp can also run those directly instead of cloning.)

Test

Verified end-to-end inside eic_xl:nightly: eic-mcp setup (fresh venv/npm build) → up
(uproot/xrootd/rucio all ready) → live MCP tool calls (rucio list_scopes, xrootd list_directory
against JLab, uproot get_file_structure) → down. Lifecycle edge cases (double-up idempotency,
port-in-use, unknown server) covered by a test harness.

Copilot AI review requested due to automatic review settings July 1, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new eic-mcp launcher to the EIC container image, intended to orchestrate running multiple EIC MCP servers locally over SSE for LLM clients (including cloning/building servers into $HOME, starting/stopping them, and exposing /sse endpoints).

Changes:

  • Introduces containers/eic/eic-mcp, a registry-driven Bash launcher with setup/up/down/status/logs subcommands for MCP servers.
  • Adds the launcher to the image as /opt/local/bin/eic-mcp via a new COPY --chmod=0755 line in the EIC Dockerfile.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
containers/eic/eic-mcp New Bash launcher that manages cloning/building and running MCP servers as local SSE services.
containers/eic/Dockerfile Installs eic-mcp into the final image under /opt/local/bin.

Comment thread containers/eic/eic-mcp
Comment on lines +94 to +100
run_in_env() {
if in_container; then bash -lc "$1"; else
local sif; sif="$(find_sif)"
[ -z "$sif" ] && { echo "ERROR: not inside eic-shell and no eic_xl .sif found (set EIC_MCP_SIF)." >&2; exit 1; }
apptainer exec "$sif" bash -lc "$1"
fi
}
Comment thread containers/eic/eic-mcp
Comment on lines +101 to +109
run_in_env_bg() { # cmd pidfile logfile — setsid → own process group for reliable stop
local cmd="$1" pidfile="$2" logfile="$3" sif
if in_container; then
setsid bash -lc "$cmd" >"$logfile" 2>&1 </dev/null & echo $! >"$pidfile"
else
sif="$(find_sif)"; [ -z "$sif" ] && { echo "ERROR: no eic_xl .sif found (set EIC_MCP_SIF)." >&2; return 1; }
setsid apptainer exec "$sif" bash -lc "$cmd" >"$logfile" 2>&1 </dev/null & echo $! >"$pidfile"
fi
}
Comment thread containers/eic/eic-mcp Outdated
|| echo "Some servers failed — check 'eic-mcp logs <name>'." >&2
return $rc
}
pid_on_port() { command -v ss >/dev/null 2>&1 || return 0; ss -ltnpH "sport = :$1" 2>/dev/null | grep -oP 'pid=\K[0-9]+' | head -1; }
Comment thread containers/eic/eic-mcp Outdated
printf "%-8s %-30s %s\n" "$n" "http://$BIND_HOST:${SRV_PORT[$n]}/sse" "$st"
done
}
cmd_logs() { local n="${1:-}"; [ -z "$n" ] && { echo "usage: eic-mcp logs <name>" >&2; exit 2; }; exec tail -n 100 -f "$LOG_DIR/$n.log"; }
Comment thread containers/eic/eic-mcp
Comment on lines +212 to +230
stop_one() {
local name="$1" pidfile="$RUN_DIR/$1.pid" port="${SRV_PORT[$1]:-}" pid i
[ -z "$port" ] && { echo " (unknown server $name)"; return; }
if [ -f "$pidfile" ]; then
pid="$(cat "$pidfile" 2>/dev/null)"
if [[ "$pid" =~ ^[0-9]+$ ]]; then
kill -TERM -"$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
for ((i = 0; i < 6; i++)); do port_open "$port" || break; sleep 1; done
if port_open "$port"; then kill -KILL -"$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true; fi
fi
rm -f "$pidfile"
fi
if port_open "$port"; then pid="$(pid_on_port "$port")"; [[ "$pid" =~ ^[0-9]+$ ]] && kill -9 "$pid" 2>/dev/null || true; fi
if port_open "$port"; then
echo " ✗ $name: still listening on $port — could not stop (check 'eic-mcp logs $name' or kill it manually)" >&2
else
echo " stopped $name"
fi
}
Add /opt/local/bin/eic-mcp, a small registry-driven launcher that sets up
(clone + build into $HOME) and runs the EIC MCP servers (uproot, xrootd, rucio;
extensible) as local SSE services for an LLM client such as opencode:

  eic-mcp setup     # one-time: clone + build the enabled servers
  eic-mcp up        # start them (SSE on 127.0.0.1:9101-9103)
  eic-mcp list | status | logs <name> | down

New servers are added with a drop-in ~/.config/eic-mcp/servers.d/*.conf (no edit
to the script). stdio-only servers are exposed over SSE via a supergateway bridge.

Depends on the servers' SSE-transport support (PRs to eic/{uproot,xrootd,rucio}
-mcp-server); until those land, 'eic-mcp setup' clones their feat/sse-transport
branches and falls back to the default branch otherwise.
@aprozo

aprozo commented Jul 1, 2026

Copy link
Copy Markdown
Author

Thanks for the review — addressed all five in the latest push (force-updated `feat/eic-mcp`):

  • #1 & irt: rollout v1.0.5 #2 (`run_in_env`/`run_in_env_bg` using `bash -lc`): now `bash --norc --noprofile -c '. /etc/eic-env.sh 2>/dev/null; …'`, matching `entrypoint.sh`/`eic-shell` — explicit env, no user/system dotfile side effects.
  • Request package py-wurlitzer #3 (`pid_on_port` aborting `down` under `set -euo pipefail`): removed — `stop_one` no longer calls `pid_on_port`.
  • GitHub workflow #5 (killing an unrelated PID bound to the port): removed the port-owner fallback kill; `stop_one` now only kills the process group it started (the `setsid` leader) and, if the port still cannot be freed, reports "still listening" instead of force-killing whatever is there.
  • #4 (`cmd_logs` path traversal): the name is validated against the registry before building the path (`eic-mcp logs ../../etc/passwd` → rejected).

Verified in `eic_xl:nightly`: `up` → live MCP calls (xrootd `list_directory`, rucio `list_scopes`) → `down`, plus the traversal guard.

Comment thread containers/eic/eic-mcp

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tool should be managed and CI-tested outside of the container repository.

@aprozo aprozo closed this Jul 7, 2026
aprozo added a commit to aprozo/containers that referenced this pull request Jul 17, 2026
Supersedes the bundled eic-mcp script (and eic#328): the launcher and every MCP
server now come from eic-spack recipes and land in /opt/local/bin via the
environment view.

- xl/spack.yaml: add eic-mcp, opencode, rucio-eic-mcp-server, supergateway,
  uproot-mcp-server (xrootd-/zenodo-mcp-server were already in)
- packages.yaml: version/variant requires for the new packages; py-mcp +cli
- drop containers/eic/eic-mcp and its Dockerfile COPY
- eic-spack.sh: TESTING pin to aprozo/eic-spack feat/mcp-servers (contains
  the recipes); to be reverted to eic/eic-spack at the merge SHA
aprozo added a commit to aprozo/containers that referenced this pull request Jul 17, 2026
The launcher and every MCP server now come from eic-spack recipes and land
in /opt/local/bin via the environment view (supersedes eic#328: no bundled
script, no runtime clone/build).

- xl/spack.yaml: add eic-mcp, opencode, rucio-eic-mcp-server, supergateway,
  uproot-mcp-server (xrootd-/zenodo-mcp-server were already in)
- packages.yaml: version/variant requires for the new packages; py-mcp +cli
- eic-spack.sh: TESTING pin to aprozo/eic-spack (develop@dd8a73e + the MCP
  recipes); to be reverted to eic/eic-spack at the merge SHA
aprozo added a commit to aprozo/containers that referenced this pull request Jul 17, 2026
The eic-mcp launcher and every MCP server now come from eic-spack recipes
and land in /opt/local/bin via the environment view (supersedes eic#328: no
bundled script, no runtime clone/build).

- xl/spack.yaml: add eic-mcp, rucio-eic-mcp-server, supergateway,
  uproot-mcp-server (xrootd-/zenodo-mcp-server were already in)
- packages.yaml: version requires for the servers and bridge; py-mcp +cli
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants