Skip to main content
Glama
README.md
# cesm-runner-mcp

MCP server for driving CESM case lifecycle: setup → build → submit → monitor.

Complements the [CrocoDash MCP](https://github.com/CROCODILE-CESM/MCP_CrocoDash), which handles grid creation and forcing. This server picks up where that one leaves off — once a case directory exists, this server drives the CIME build/run pipeline.

## Tools

### Case inspection
| Tool | Purpose |
|------|---------|
| `list_cases(search_root)` | Find all CESM cases under a directory |
| `get_case_status(case_dir)` | Read CaseStatus log + key XML config |
| `check_input_data(case_dir)` | Verify all required input files are staged |

### Case lifecycle
All three accept an optional `container=<session_name>` arg to run inside a crocontainer session instead of on the host,
but only for cases made for the container's CIME machine, `ubuntu-latest` (e.g. `/workspace/case` after `run_case_in_container`).
A host case (`machine=derecho`) is refused: bundle it with `bundle_case` (CrocoDash MCP) and use `run_case_in_container`.

| Tool | Purpose |
|------|---------|
| `case_setup(case_dir, [container])` | Run `./case.setup` |
| `case_build(case_dir, [container])` | Compile the model (`./case.build`) |
| `case_submit(case_dir, no_batch, [container])` | Submit to PBS/slurm or run interactively |

### XML config
| Tool | Purpose |
|------|---------|
| `xmlquery(case_dir, variable)` | Query any CESM XML variable |
| `xmlchange(case_dir, variable, value)` | Set a CESM XML variable |
| `preview_run(case_dir)` | Show PE layout and batch script without submitting |

### Monitoring
| Tool | Purpose |
|------|---------|
| `tail_log(case_dir, component, lines)` | Read recent run log output |
| `get_job_status(case_dir)` | Check PBS/slurm queue for this case's job |
| `list_compsets(cesmroot, filter)` | List available compsets in a CESM install |

### Container management (crocontainer fast-iteration path)
| Tool | Purpose |
|------|---------|
| `build_sandbox([sandbox_dir, rebuild, queue, account, dry_run])` | One-time: qsub a PBS job (~1hr) that builds the Apptainer sandbox; returns the job id and log path. `rebuild=True` moves an existing sandbox to `<path>.old-<timestamp>` first; `dry_run=True` shows the script and command only |
| `start_container(scratch_dir, [sandbox_or_image, inputdata_dir, name, runtime])` | Start a persistent container session |
| `run_case_in_container(container, bundle_dir)` | Copy a `crocodash bundle` into the session and run `run_case.sh` (fork → build → run) |
| `container_exec(name, case_dir, command)` | Run any command inside a running session |
| `stop_container(name)` | Stop and clean up the session |
| `list_containers()` | Show running sessions |

## Resources

| URI | Content |
|-----|---------|
| `cesm://case/{case_dir*}/env` | All XML config variables (same as `xmlquery(case_dir, "--listall")`) |
| `cesm://case/{case_dir*}/status` | CaseStatus log + key config (same as `get_case_status`) |
| `cesm://case/{case_dir*}/logs` | Log files in the case dir, its RUNDIR and `DOUT_S_ROOT/logs` |

`{case_dir*}` takes the case path as-is, slashes included, e.g.
`cesm://case//glade/u/home/<user>/croc_cases/my_case/status`.

## Setup

```bash
pip install -e .
# or in the CrocoDash conda env:
conda run -n CrocoDash pip install -e .
```

## Running

```bash
cesm-runner-mcp
# or
python server.py
```

Wire up to Claude Code by adding to `.mcp.json`:

```json
{
  "mcpServers": {
    "cesm-runner": {
      "command": "python",
      "args": ["/path/to/MCP_cesm_runner/server.py"]
    }
  }
}
```

## Fast iteration with crocontainer

Instead of waiting in the PBS queue, run CESM interactively inside a container.
On Derecho, the full GLADE filesystem is mounted transparently so all host paths work inside.

### One-time setup (Derecho)
Call `build_sandbox()` via the MCP: it qsubs the build (Derecho `develop` queue, 3 h) and
returns the job id and log path. `build_sandbox(rebuild=True)` replaces a stale sandbox,
keeping the old one as `<path>.old-<timestamp>`. Or build it by hand:
```bash
# Build the writable sandbox (~1 hr on a compute node)
qcmd -l walltime=03:00:00 -- apptainer build --sandbox \
  /glade/derecho/scratch/$USER/crocontainer_sandbox \
  docker://ghcr.io/crocodile-cesm/crocontainer:latest-amd64
```

### Typical MCP session
The container only builds and runs cases for its own CIME machine (`ubuntu-latest`),
so a case made on Derecho goes in as a bundle, which `run_case.sh` forks for `ubuntu-latest`:
```
bundle_case("~/croc_cases/mycase", "~/scratch/croc_bundles")      # CrocoDash MCP

start_container(scratch_dir="~/scratch/croc_scratch")
  → Apptainer instance starts; /glade mounted; inputdata at /glade/campaign/cesm/cesmdata/inputdata

run_case_in_container("croc_session", "~/scratch/croc_bundles/mycase_case_bundle")
  → forks the case to /workspace/case, builds and runs it; no queue

# Debugger MCP finds an error → fix → resubmit inside the container:
container_exec("croc_session", "/workspace/case", "./xmlchange STOP_N=3")
case_submit("/workspace/case", no_batch=True, container="croc_session")

stop_container("croc_session")
```

### Inputdata on Derecho
CESM inputdata is mounted directly from GLADE — no downloading required:
`/glade/campaign/cesm/cesmdata/inputdata` → `/root/cesm/inputdata` inside container

### On a laptop (Podman)
```
start_container(
  scratch_dir="./scratch",
  sandbox_or_image="ghcr.io/crocodile-cesm/crocontainer:latest",
  inputdata_dir="./cesm_nyf_inputdata",   # pre-downloaded NYF data
  runtime="podman"
)
```

## Design

- **Stateless**: each tool reads from the filesystem; no in-memory state
- **Shells out to CIME scripts**: uses `./xmlquery`, `./case.setup`, etc. directly — version-safe and matches what users run manually
- **Works with any CESM install**: not specific to CrocoDash or MOM6
- **Container-aware**: lifecycle tools accept `container=` to route via `apptainer exec instance://` or `podman exec` instead of running on the host

## CESM install on Derecho

Default reference install: `~/work/installs/CESM_crocodash` (CESM branch `crocodash`)

Cases live in: `~/croc_cases/`

CESM inputdata: `/glade/campaign/cesm/cesmdata/inputdata`

TDQS

A3.8/5.0

Scored across 17 tools

Disambiguation5/5

All 17 tools have clearly distinct purposes, covering different stages of CESM case execution, container management, and configuration queries. No two tools appear to overlap in functionality, reducing the chance of misselection.

Naming Consistency4/5

Tool names consistently use lowercase with underscores and combine verbs and nouns. However, there is a minor inconsistency in order: some are verb_noun (e.g., build_sandbox) while others are noun_verb (e.g., case_build). Still, the pattern is predictable and readable.

Tool Count5/5

With 17 tools, the server is well-scoped for its purpose of running CESM cases. Each tool serves a distinct function without being excessive, covering setup, build, submit, monitoring, container management, and configuration.

Completeness4/5

The tool set covers the primary lifecycle of CESM case execution, including build, setup, submit, status checks, log tails, and XML modifications. Minor gaps like case creation or input data download are absent, but these are outside the stated 'runner' scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues