UCR Research Computing ursa-bifrost
MCP server/CLI/One Go binary

ursa-bifrost

Let any AI assistant work a Slurm cluster, safely.

One Go binary that is both an MCP server and a CLI. It gives Claude, Codex, Gemini, Hermes, OpenCode and your own scripts typed, audited, read-mostly access to the Ursa Major HPC cluster at UC Riverside: job status, failure diagnosis, costs, software modules, batch-script checks, results, and (opt-in, two-step) submit and cancel.

  • Go 1.26
  • Streamable HTTP + stdio
  • MIT
You:      Why did job 236 fail?
Claude:   [job_explain 236]
          It failed after one minute on gpul4: the log says
          "ModuleNotFoundError: No module named 'pandas'". The script calls python
          without an environment. Load python-ml (PyTorch/GPU) or python-sci (CPU)
          before running it, or create a uv/Pixi environment inside the job.
That diagnosis came from the live cluster. The server holds no language model: it runs allow-listed scheduler queries and fixed diagnosis rules, returns JSON with the evidence and the exact commands it ran, and the assistant does the reasoning. Nothing a model sends is ever run as a shell command.
What it does

Typed answers about the cluster

Everything the MCP tools do is also a command, and every command takes --json. Every result is an envelope with the data, the evidence, and the scheduler commands that produced it, so an answer can be checked rather than trusted.

Explain failed jobs

job_explain runs fixed rules over the accounting record and the redacted log tail: out of memory, timeouts, missing modules, Python import errors, CUDA problems, disk full and more. Each finding carries its evidence and a fix.

Status and cost now

cluster_status and partitions: nodes powered up or down, queue depth, limits, GPUs, prices per node-hour, and the dollars per hour being spent right now.

Check a batch script

script_check statically checks a script against the real cluster: partition, limits, modules and their MPI prerequisite, GPU request, a core request on shared partitions, and worst-case cost. No submission.

Find software

modules_search, module_show and recipes: module versions, MPI prerequisites, GPU builds, prebuilt containers, known-good recipes and the shared reference databases.

Usage and waste

my_usage and waste_report: node-hours, core-hours, CPU efficiency and estimated cost, plus avoidable spend from idle cores, idle nodes and repeated failures.

Logs, files and results

Paged, redacted log windows; listings and chunked reads of your own home, scratch and job folders; signed download links for job outputs when a staging bucket is configured.

Interactive sessions

interactive_help returns the exact srun/salloc command, its hourly and worst-case cost, and how to reach the login node. It runs no job.

Submit and cancel

Opt-in tier A1, granted per person: plan, show worst-case cost, then confirm with a single-use token. Caps on nodes, hours and dollars are enforced before anything reaches the scheduler.

What you can ask

Plain questions, real cluster data

  • "What's running on the cluster and what is it costing per hour?"
  • "Why did my job 236 fail?"
  • "Which modules give me LAMMPS with GPU support?"
  • "Check this batch script before I submit it."
  • "How much did I spend last month, and how much was wasted on idle cores?"
  • "Write a script for PyTorch on one L4 GPU for 4 hours, show me the cost, then submit it."
  • "Show the last 200 lines of job 265's stderr, then give me download links for its results."
  • "Give me the exact command for an interactive session with 8 cores and a GPU."
  • Staff "Which nodes are down?" / "Draft a reply to this ticket about job 236."
The CLI

Same tools, in a terminal

Everything the MCP tools do is also a bifrost command. Two examples from the README:

$ bifrost check train.sbatch
PROBLEMS FOUND  partition standard  worst-case cost $0.13  (1 cores, 6% of a node)
  error   line 4: module "python3" not found in the cluster catalog; closest: pythia8, python-ml
  error   standard shares nodes between jobs: this script asks for no cores, so Slurm gives
          it 1 core and about 8 GB on standard. Ask for the cores it needs ...
  warning calls python without loading python-sci/python-ml or an environment
$ bifrost submit hello.sh
Plan (nothing submitted yet):
  partition  standard, 1 node(s), time limit 0 h 05 min
  scheduler  would start at ... on ... (standard)
  worst case $0.12  (today so far $0.00 of $50.00 day cap)
Submit this job? [y/N] y
Submitted job 265 ...  folder on the cluster: ~/bifrost-jobs/<stamp>-bifrost-hello

Submitting needs tier A1. Each submitted job runs in its own ~/bifrost-jobs/<stamp>-<name> folder, so its results are exactly that folder. Default caps: 4 nodes, 24 h, $25 per job, $50 per day; a plan over a cap is refused before anything reaches the scheduler.

How it works

A narrow, well-guarded crossing

The assistant sits on one side and the scheduler on the other. bifrost is the bridge: tier checks, rate limits, a short-TTL cache, redaction and audit, and a backend that can only build allow-listed commands.

Hosted server

Nothing to install. Recommended at UCR.

  • bifrost serve on Cloud Run, Streamable HTTP at /mcp
  • Google sign-in restricted to ucr.edu accounts listed in users.yaml (re-read on every request)
  • OAuth 2.1 with PKCE and dynamic client registration; rotating refresh tokens (30 days); sealed token storage
  • Per-user IAP tunnels and OS Login keys, so each person reaches the cluster as themselves
  • Works from any MCP client on any machine

Local binary

Your own gcloud login, no server.

  • bifrost mcp over stdio, plus the full CLI
  • Reaches the login node the same way you would by hand, through gcloud and IAP
  • One multiplexed SSH connection reused for every call: about 0.3 s per command once warm
  • Or backend: iap: bifrost opens the IAP tunnel itself with an OS Login key (8 h expiry) and pinned host keys
  • Works from your machine only
Tools

Three tiers, granted per caller

A caller only sees, and can only call, the tiers granted to them (tiers in the local config, users.yaml on the hosted server). Read tools carry readOnlyHint: true, act tools false.

R1

Read

Everyone

  • cluster_status
  • partitions
  • jobs_list
  • job_show
  • job_explain
  • job_log_tail
  • job_results
  • modules_search
  • module_show
  • recipes
  • script_check
  • my_usage
  • waste_report
  • storage_usage
  • files_list
  • files_read
  • env_check
  • interactive_help
  • results_link*
A1

Act

Opt-in per person

  • job_submit
  • job_submit_confirm
  • job_cancel
  • job_cancel_confirm
  • job_hold
  • job_hold_confirm
  • job_release
  • job_release_confirm
  • upload_prepare*
  • uploads_list*
R2

Staff

Research Computing

  • jobs_list_all
  • job_show_any
  • job_explain_any
  • usage_report
  • waste_report_all
  • health
  • ticket_draft

ticket_draft writes a reply draft for a "my job failed" ticket. It is never sent.

* when a staging bucket is configured. Also: resources hpc://catalog and hpc://policies; prompts diagnose_job, write_batch_script, monthly_usage_summary, triage_ticket. Full catalog: SPEC section 7.

Quick start

Hosted or local, pick one

The hosted server needs a UCR account that Research Computing has added. The local binary uses your own gcloud login and SSH.

A Use the hosted server

Point your client at this URL and sign in with Google when it asks:

https://bifrost-mcp-125853442225.us-central1.run.app/mcp

For example, in Claude Code:

claude mcp add --transport http ursa https://bifrost-mcp-125853442225.us-central1.run.app/mcp

Then run /mcp, pick ursa and choose Authenticate. Other clients are listed below.

B Run it locally

Linux/macOS release binary to ~/.local/bin, checksum-verified:

curl -fsSL https://raw.githubusercontent.com/UCR-Research-Computing/ursa-bifrost/main/scripts/get.sh | sh

Or with Go:

go install github.com/UCR-Research-Computing/ursa-bifrost/cmd/bifrost@latest

Then set up and check:

bifrost config init     # writes ~/.config/ursa-bifrost/config.yaml
bifrost doctor          # checks gcloud, SSH to the login node, the catalog
bifrost status

Add it to a client as a stdio server, in any JSON config:

{ "mcpServers": { "ursa": { "command": "bifrost", "args": ["mcp"] } } }
Clients

One line for each common client

The hosted server does OAuth 2.1 with dynamic client registration, PKCE and discovery, so any client that supports remote MCP servers signs you in by itself. Below, URL means the hosted /mcp URL above.

Hermes Agent

hermes mcp add ursa --url URL --auth oauth

Local: hermes mcp add ursa --command bifrost --args mcp

Claude Code

claude mcp add --transport http ursa URL

Then /mcp → Authenticate. Local: claude mcp add ursa -- bifrost mcp

claude.ai / Claude Desktop

Settings → Connectors → Add custom connector, paste the URL. On a Team or Enterprise plan an owner may need to allow custom connectors first.

Codex CLI

codex mcp add ursa --url URL

Signs in right away (Codex 0.160+). Local: codex mcp add ursa -- bifrost mcp

OpenCode

opencode mcp add ursa --url URL
opencode mcp auth ursa

Gemini CLI

gemini mcp add -s user -t http ursa URL

Then /mcp auth ursa. Local: gemini mcp add -s user ursa bifrost mcp

OpenAI API

A {"type": "mcp", "server_url": ...} tool in the Responses API. Keep allowed_tools to read tools when approval is off.

Python / curl

python3 examples/mcp_client.py login
python3 examples/mcp_client.py call job_explain '{"job_id": "236"}'

A complete client in one file, standard library only.

Config-file versions, local (stdio) variants and troubleshooting for every client: docs/CLIENTS.md.

Self-hosting

Bring it to your own cluster

Nothing in bifrost is specific to UCR except configuration: the login node, the catalog (partitions, prices, modules, recipes), log roots and caps. Start with bifrost config init, point ssh or iap at your login node, and write a catalog.

The hosted Cloud Run deployment (Secret Manager, sealed token store, users file) is documented step by step in docs/DEPLOY.md; deploy/deploy.sh does it idempotently.

deploy/deploy.sh plan      # read-only: shows what is missing
deploy/deploy.sh apply     # asks before each billable or IAM change
Tested against real output

Development

make check                  # gofmt, go vet, go test -race, build   (before every commit)
scripts/mutation_check.sh   # every safety guard must be caught by a test
scripts/mcp_smoke.py        # drives `bifrost mcp` over stdio like a real client

Tests run against anonymized recordings of real Ursa Major output in testdata/ (Slurm 25.11.4). scripts/make_fixtures.py regenerates them from a read-only probe and refuses to write if a real username survives; CI checks again.

Also in the repo: agent/, a separate read-only web dashboard (ursa-agent) whose panels call bifrost tools directly as the signed-in person, with an assistant drawer that plans jobs that only run after you press Approve.