Local, open-source MCP server, CLI, and agent skill for local, validated access to the ETH Zurich Euler HPC cluster (Slurm) from a laptop — designed for the free public share (standard student plan).
The MCP server gives AI agents (Claude, Cursor, VS Code Copilot, Prime Agent, Codex-style tools) a local interface to submit, monitor, cancel, and retrieve Slurm jobs. Resource limits and execution controls are implemented in the server.
eth-euler-hpc-mcp
├── eth-euler-hpc-mcp MCP server (stdio, local, open source)
├── eth-hpc human CLI (bash, macOS/Linux)
└── skills/eth-euler-hpc agent skill (operating rules + workflow)
Euler is a shared supercomputer with defined resource, storage, and access constraints. This project encodes the relevant public-share limits in the validation and execution workflow: non-compliant job requests are rejected before submission.
- Why
- Contents
- Requirements
- Install the MCP server
- Connect an MCP client
- Prime Agent integration
- MCP tool reference
- CLI reference
- Public-share limits (encoded in the validator)
- Validation and security model
- Storage and data workflow
- Operational workflow
- Development
- Troubleshooting
- Observed behavior
- Disclaimer & license
- Managing Slurm manually is tedious and error-prone. The MCP server provides a structured interface for common job, queue, cancellation, and data-transfer operations.
- The public share has fixed limits (48 CPU cores, 128 GiB of memory, no GPUs) and a priority below shareholder groups.
- The MCP server validates job requests against the public-share limits, requires an explicit time limit, rejects GPU requests, and reports scratch-data retention requirements before submission.
- The server runs locally over stdio, uses the user's SSH configuration and (off campus) the ETH VPN, and does not transmit keys or passwords.
eth-euler-hpc-mcp/
├── pyproject.toml ← MCP server package (hatchling)
├── src/eth_euler_hpc_mcp/
│ ├── server.py ← MCP tools (FastMCP, stdio)
│ ├── validate.py ← public-share validator (pure, tested)
│ ├── runner.py ← SSH/rsync subprocess helpers
│ ├── config.py ← ~/.config/eth-hpc/config loader
│ └── templates/job.sbatch ← public-share job-script template
├── skills/
│ ├── eth-euler-hpc/ ← agent skill (symlink into ~/.agents/skills)
│ │ ├── SKILL.md ← operating rules + workflow (agent-facing)
│ │ ├── scripts/eth-hpc ← the CLI (bash 3.2 compatible — macOS default)
│ │ ├── templates/job.sbatch ← CLI's public-share job-script template
│ │ └── references/limits.md ← researched limits + sources + machine state
│ └── eth-euler-hpc-mcp/ ← Prime Agent Python skill (stdio client)
│ ├── SKILL.md
│ └── src/eth_euler_hpc_mcp/ ← McpIntegration client (euler_hpc singleton)
├── tests/
│ ├── test-eth-hpc.sh ← 24 CLI unit tests (no cluster needed)
│ ├── test_validate.py ← validator unit tests
│ ├── test_config.py ← config loader tests
│ └── test_server_e2e.py ← boots the MCP server over stdio (no cluster)
├── install.sh ← symlink skills + CLI into place
├── Makefile ← install / test / lint
└── LICENSE ← MIT
- macOS or Linux with
ssh,rsync(the CLI needs only what macOS ships). - Python ≥ 3.10 and uv (or pipx) for the MCP server.
- An ETH account with Euler access (auto-created on first SSH login).
- Off-campus: the ETH VPN (https://www.vpn.ethz.ch/).
# from this repo (recommended: get the CLI + skills too):
git clone https://github.com/gbrlpzz/eth-euler-hpc-mcp
cd eth-euler-hpc-mcp
./install.sh # symlinks skills + CLI
uv tool install . # installs the 'eth-euler-hpc-mcp' command
# or from PyPI (server only):
uv tool install eth-euler-hpc-mcpConfigure your account (~/.config/eth-hpc/config):
USERNAME="your-eth-username"
SSH_KEY="~/.ssh/euler" # any filename works — never commit real keysSmoke-test:
eth-euler-hpc-mcp --help # server binary present
eth-hpc doctor # CLI preflight (config, VPN, SSH auth)The server speaks the standard MCP protocol over stdio — the same
transport Claude Desktop, Cursor, VS Code, and others use for local servers.
Add it to your client's mcpServers config:
If you cloned the repo instead of installing the command:
{
"mcpServers": {
"eth-euler-hpc-mcp": {
"command": "uv",
"args": ["run", "--project", "/absolute/path/to/eth-euler-hpc-mcp", "eth-euler-hpc-mcp"]
}
}
}Environment overrides (set in the client or your shell): ETH_HPC_CONFIG
(alternate config file), ETH_HPC_USER (username fallback).
The repo ships a Prime Agent Python skill at skills/eth-euler-hpc-mcp
(installed by ./install.sh into ~/.agents/skills/eth-euler-hpc-mcp). It
spawns the local server over stdio via the McpIntegration base class and
exposes the tools as async methods on an euler_hpc singleton:
from eth_euler_hpc_mcp import euler_hpc
await euler_hpc.euler_doctor() # preflight
await euler_hpc.euler_limits() # limits + storage rules
await euler_hpc.euler_validate_submit(script_path="job.sbatch")
await euler_hpc.euler_submit(script_path="job.sbatch") # dry-run preview
await euler_hpc.euler_submit(script_path="job.sbatch", dry_run=False, confirm=True)
await euler_hpc.euler_queue(job_id="12345")
await euler_hpc.euler_cancel(job_id="12345", confirm=True)
await euler_hpc.euler_push(local_path="data/", scratch=True, dry_run=False, confirm=True)
await euler_hpc.euler_pull(remote_path="~/eth-hpc-jobs/slurm-*.out", local_dir="results/", confirm=True)Restart the agent session (or /reload) after ./install.sh so the skill is
picked up. Overrides: ETH_EULER_HPC_MCP_CMD and ETH_EULER_HPC_MCP_REPO.
| Tool | Purpose | State changes |
|---|---|---|
euler_limits |
Print researched public-share limits + storage rules | none |
euler_gen |
Return a public-share #SBATCH template |
none |
euler_doctor |
Preflight: config, VPN/network reachability, SSH auth | none |
euler_quota |
Storage quotas (lquota) |
none |
euler_queue [job_id] |
Your jobs (myjobs, squeue fallback) |
none |
euler_validate_submit(script_path) |
Validate a job script against limits | none |
euler_submit(script_path, dry_run=true, confirm=false) |
Validate, then submit | dry-run default; confirm required |
euler_cancel(job_id, confirm=false) |
Cancel a job | confirm required |
euler_push(local_path, remote_dir?, scratch?, dry_run=true, confirm=false) |
Upload via rsync | dry-run default; confirm required |
euler_pull(remote_path, local_dir?, dry_run=true, confirm=false) |
Download via rsync | dry-run default; confirm required |
Every state-changing tool defaults to dry-run and requires confirm=true
before execution. This makes the generated commands reviewable before they are
sent to Euler.
eth-hpc doctor | ssh [--first-login] | quota | queue [jobid] |
submit <file> | gen [file] | interactive [--cpus N --time H:MM:SS --mem-per-cpu G] |
cancel <jobid> | push <local> [dir|--scratch] | pull <remote> [dir] |
tunnel start|status|stop | limits | version | help
Global flags: --dry-run (print, don't execute), -y/--yes, --config FILE.
| Command | Purpose |
|---|---|
doctor |
Preflight: config, VPN/network reachability, SSH auth |
ssh [--first-login] |
Interactive shell on a login node |
quota |
Storage quotas (lquota) |
queue [jobid] |
Jobs (myjobs, squeue fallback) |
gen [file] |
Generate a public-share job template |
submit <file> |
Validate against public-share limits, then submit (script is copied to ~/eth-hpc-jobs/ on Euler) |
interactive |
Small srun --pty compute session for debugging |
cancel <jobid> |
Cancel (confirmation required unless -y) |
push <local> [dir|--scratch] |
Upload via rsync (uses configured SSH key) |
pull <remote> [dir] |
Download via rsync |
tunnel start|status|stop |
Manage an euler-tunnel batch job |
limits |
Print researched public-share limits |
version / help |
Version / usage |
| Resource | Limit | Source |
|---|---|---|
| CPU cores per job | 48 max | docs.hpc.ethz.ch/users/shareholders |
| Memory per job | 128 GiB max | docs.hpc.ethz.ch/users/shareholders |
| GPUs | none | shareholders-only hardware |
| Default time limit | 1 h (Slurm) — tool requires --time, maximum 24 h |
docs.hpc.ethz.ch |
$HOME |
50 GB, 500k files, backed up | docs.hpc.ethz.ch/hardware/storage |
$SCRATCH |
2.5 TB, 1M files, purged after 15 days, no backup | docs.hpc.ethz.ch/hardware/storage |
All limits are configurable in ~/.config/eth-hpc/config (MAX_TIME,
MAX_CPUS, MAX_MEM_GB) and documented with sources in
skills/eth-euler-hpc/references/limits.md.
| Area | Implementation |
|---|---|
| CPU and memory limits | validate_sbatch in the MCP server and CLI (rejects non-compliant requests) |
| GPU requests | validator (the public share has no GPUs) |
| Time limit | validator requires --time and applies the configured maximum (24 h by default) |
| Compute placement | workflow guidance; interactive uses srun --pty rather than running compute on login nodes |
| State-changing operations | MCP tools default to dry-run and require confirm=true; the CLI provides --dry-run and cancellation confirmation |
| Credential handling | server runs locally over stdio and uses the user's ~/.ssh/ configuration; no hosted credential service |
| Input handling | job IDs are numeric; remote paths use a restricted character set; subprocesses use argument lists (no shell=True) |
| Scratch retention | validator reports the 15-day purge and lack of backup; results should be copied back |
The server runs locally over stdio, does not open a network listener, and does not persist credentials. SSH authentication uses the user's local configuration.
| Path | Capacity | Backup | Purge | Use for |
|---|---|---|---|---|
$HOME (/cluster/home/<user>) |
50 GB, 500k files | nightly + snapshots | on account deletion | code, results, small important files |
$SCRATCH (/cluster/scratch/<user>) |
2.5 TB, 1M files | none | after 15 days | large datasets, intermediate data |
$TMPDIR (node-local) |
node-dependent | none | at job end | fast I/O during a job (request with --tmp) |
| Project/Work | shareholder-only | — | — | not available on the public share |
- Large datasets →
$SCRATCH(euler_push(local_path=..., scratch=True)). - Always rsync final results back to
$HOME/$SLURM_SUBMIT_DIRbefore the job ends (euler_pull(remote_path="~/eth-hpc-jobs/...", local_dir="results/", confirm=True)). - Many small files →
tarthem first. -
TB transfers → contact cluster support.
- Run
euler_doctorbefore remote operations. If network reachability fails, connect to the ETH VPN and retry. - Preview every submission:
euler_submit(script_path=...)prints the exact commands; then call again withdry_run=false, confirm=true. - Use the validator for every submission. GPU requests, missing
--time, and requests above the configured CPU or memory limits are rejected. - Submit compute through Slurm. Use login nodes for editing, compiling, and
submitting; use batch jobs or the CLI's
interactivecommand for computation. - Large-data workflow:
euler_push(..., scratch=True)→ the job reads$SCRATCH(or$TMPDIRfor I/O-heavy steps) →euler_pullcopies results back. Scratch is purged after 15 days and is not backed up. - Treat
PENDINGas a queue state. Public-share jobs can wait behind shareholder jobs. Polleuler_queue(job_id=...); do not resubmit solely because - Keep credentials local. The config is stored at
~/.config/eth-hpc/configand keys at~/.ssh/; do not echo or commit them. - Clean up completed runs. Remove temporary remote job files
(
~/eth-hpc-jobs/) when a session is complete.
make test # 24 bash unit tests + Python validator/MCP e2e tests (no cluster needed)
make lint # shellcheck, if installed (brew install shellcheck)
make install # same as ./install.shContributor notes:
- The CLI targets bash 3.2 (macOS default) — no bash 4+ features, no external dependencies beyond what macOS ships.
- The MCP server is a thin Python package over the same validator; the
validator logic is intentionally duplicated in Bash and Python and kept in
sync by tests.
test_validate.pyandtest_server_e2e.pycover it. - Remote commands use SSH. State-changing operations require an explicit command or confirmation, depending on the operation.
- Limits are researched from the official docs (2026-08) and re-verified live
via
lquota,myjobs,sacctmgr show qos— policy can change.
| Symptom | Cause / fix |
|---|---|
Connection timed out on ssh |
Not on ETH network/VPN → connect VPN first |
Too many authentication failures |
Many local keys → the CLI pins IdentitiesOnly=yes when SSH_KEY is set |
| No verification-code prompt on first login | Use eth-hpc ssh --first-login (-o PreferredAuthentications=keyboard-interactive -o PubkeyAuthentication=no) |
Job stuck PENDING (Priority / ReqNodeNotAvail) |
Normal for public share; wait, poll euler_queue(job_id=...) |
slurmstepd: error: poll(): Bad address |
Memory/network issue; check job memory usage |
sudo needed on Euler |
No sudo on Euler — install in $HOME or use modules |
module load fails |
Use module avail / module spider to find the right module |
| Compute node can't reach the internet | module load eth_proxy (use sparingly — shared limited proxy) |
LC_CTYPE locale warnings (macOS) |
Comment out SendEnv LANG LC_* in /etc/ssh/ssh_config |
| MCP client says "server not found" | Install the server (uv tool install eth-euler-hpc-mcp) or use the uv run --project <repo> command form |
Verified live on 2026-08-07 (public share):
- A small batch job completed successfully (exit 0) on a compute node and its
output was pulled back; interactive
srunsessions work too. - Public-share jobs queue behind shareholders: expect ~5–10 minutes of wait even on an idle cluster. Plan for it.
- Cancellation, push/pull round trips, validation rejections, and euler-tunnel were verified.
Community-maintained, not affiliated with ETH Zurich. Verify limits against the official documentation and your account before relying on them. Follow the ETH Acceptable Use Policy and current cluster documentation.
MIT — see LICENSE.