Skip to main content
Glama
yaswanth169

Cryosparc_mcp_Server

by yaswanth169
README.md
# Cryosparc_mcp_Server

Mode A MCP server for running the standard CryoSPARC W1->W2 pipeline from natural-language clients.

## What it does

- Exposes MCP tools to:
  - start standard pipeline
  - pause at interactive checkpoints
  - resume after UI interaction
  - check status
  - generate report
- Uses a state file per run so disconnects/restarts can resume.

## Quick start

```bash
cd Cryosparc_mcp_Server
python3 -m pip install --user -r requirements.txt
cp config/profile.example.yaml config/profile.yaml
```

Update `config/profile.yaml` with your CryoSPARC details and dataset path.

## Run MCP server (stdio)

```bash
python3 -m src.cryosparc_mcp_server.server --config config/profile.yaml
```

Use this command as the MCP server command in Claude/Cursor MCP settings.

## Why the MCP “goes down” when Cursor or SSH drops

**The MCP process is tied to Cursor’s stdio pipe.** It is **not** a background daemon. If Cursor times out, closes the SSH session, or the jump host resets the connection, that Python process **exits**. That is normal.

**CryoSPARC jobs keep running** on the cluster/master — only the **bridge** (MCP + `cryosparc-tools`) stops. Your pipeline state is still in `runs/<run_id>.json` on the machine where the MCP ran. After reconnecting MCP, use **`cs_pipeline_status`** / **`cs_resume_pipeline`** / **`cs_continue_pipeline`** with the same `run_id`.

### Long workflows: “MCP should wait” — who actually times out?

**Your CryoSPARC MCP server *does* wait** for each tool until that tool’s Python code finishes (or errors). Nothing in this repo artificially cuts off a long `subprocess` or CryoSPARC call.

What stops “waiting” is almost always the **MCP client in Cursor** (and sometimes **SSH**), not the server:

- Cursor tracks each **tool call** and, after an internal limit, sends **`Request timed out`** (`-32001`) and tears down that stdio session.
- That closes stdin → the remote `run_server.py` hits **`BrokenResourceError`** even though CryoSPARC may still be busy on the cluster.

So “MCP should wait” = **you need the client (Cursor) to allow a longer tool call**, or **avoid one giant tool call** until Cursor supports it reliably.

#### Try a longer timeout in Cursor (version-dependent)

Open **Command Palette → “Preferences: Open User Settings (JSON)”** and try adding (values in **milliseconds**):

```json
"mcp.server.timeout": 600000,
"mcp.elicitation.timeout": 600000
```

Restart Cursor. **Names and support change by Cursor version**; if those keys are ignored, check [Cursor forum / release notes](https://forum.cursor.com/) for “MCP timeout”. There is no knob inside `Cryosparc_mcp_Server` that replaces this.

#### Reliable pattern when timeouts stay short

Until the client waits as long as your jobs:

1. **`cs_start_pipeline`** then repeated **`cs_continue_pipeline`** with **`steps=1`** — each HTTP-ish “step” returns sooner so a single MCP call is less likely to exceed Cursor’s limit.
2. Or run the heavy **`Tools_Cryosparc_Workflow`** stages in **`tmux`** on the server and use MCP only for **`cs_pipeline_status`** / **`cs_list_runs`** (short calls).

#### Future server-side option (design)

A **“start job, return immediately, poll status”** tool shape (async + `run_id`) would match how long HPC workflows work under strict client timeouts. Today, **`cs_start_pipeline` / `cs_continue_pipeline`** are the closest match to that pattern.

---

### Cursor “Request timed out” on long tools (summary)

Calls like **`cs_plan_and_run_standard_pipeline`** or **`cs_resume_pipeline`** can run **many minutes** (import, motion, CTF, etc.). Cursor’s MCP client often **times out around ~2–3 minutes** (varies by version) while the server is still working. After timeout you may see `BrokenResourceError` / `Connection closed` in logs — the remote Python then exits when stdin closes.

**Mitigation:** Increase Cursor timeouts if your build supports it (above), and/or prefer **`cs_start_pipeline`** + **`cs_continue_pipeline`** with `steps=1`. Or run heavy steps from **`tmux`** on the server with the CLI workflow, and use MCP only for short checks.

### SSH + `"declare -x" is not valid JSON` (red “Error” in Cursor)

MCP **must** get **only JSON-RPC on stdout**. Anything else breaks parsing → Cursor shows **Error** / **Show Output**.

**Causes:**

1. **`bash -lc` (login shell)** — can run `~/.bash_profile` and dump `declare -x` to stdout. **Do not use `-l`.**
2. **Even `bash -c`** — if `~/.bashrc` / system bashrc runs `export -p`, `set -x`, or similar, you can **still** get `declare -x` on stdout.

**Fix (strong):** avoid **bash** for the remote wrapper — use **`/bin/sh -c`** (usually **dash** on Ubuntu/Debian) so you do not hit bash’s `BASH_ENV`, login hooks, or `export -p` spam. Also add **`-T`** so SSH does not allocate a TTY (fewer surprises from interactive startup).

```json
"args": [
  "-o", "ServerAliveInterval=60",
  "-T",
  "-J", "USER@jump:20022",
  "-p", "20022",
  "USER@10.0.1.2",
  "/bin/sh", "-c",
  "export CRYOSPARC_EMAIL='you@example.com' && export CRYOSPARC_PASSWORD='...' && cd /mnt/.../Cryosparc_mcp_Server && exec python3 -u run_server.py --config config/profile.yaml"
]
```

If you still see `declare -x` after this, your site may run **`bash -l`** (or similar) **before** your command. Then nothing on the client can fully fix stdout pollution — you need **`~/.bash_profile` / `~/.profile`** to **not** print or `export -p` to **stdout** (only stderr), or a dedicated account without noisy profile scripts.

**Fallback:** `bash --noprofile --norc -c '...'` is still worth trying if `/bin/sh` is linked to bash and behaves oddly.

Or the helper script (after `chmod +x` on the server):

```bash
chmod +x scripts/run_mcp_stdio.sh
```

```json
"bash", "--noprofile", "--norc", "-c",
"export CRYOSPARC_EMAIL='...' && export CRYOSPARC_PASSWORD='...' && exec /mnt/.../Cryosparc_mcp_Server/scripts/run_mcp_stdio.sh"
```

### Why it breaks **in the middle** of a run

Typical sequence in your logs:

1. **`cs_plan_and_run_standard_pipeline`** or **`cs_resume_pipeline`** runs **longer than Cursor’s MCP timeout** (~3 minutes) → `Request timed out`.
2. Cursor closes the request; the **SSH stdio pipe** dies → remote Python hits **`BrokenResourceError`** / **`Connection closed`**.
3. Cursor Settings shows **cryosparc-mode-a** as **Error** until you **toggle the server off and on** (starts a new `ssh`).

**CryoSPARC jobs often keep running**; your `runs/<run_id>.json` on the server still has the last state. After reconnecting MCP, use **`cs_pipeline_status`** with that `run_id`, then **`cs_continue_pipeline`** / **`cs_resume_pipeline`** — but use **short steps** (`steps=1`) so each tool call finishes **under ~3 minutes** when possible.

**Note:** Log lines like `INFO Processing request` may appear under **`[error]`** in Cursor’s MCP output; that is **Cursor’s log level**, not necessarily a Python exception.

### Keep SSH from idle-dropping

In `~/.ssh/config` on Windows:

```
Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3
```

## Tool flow (Mode A)

1. `cs_start_pipeline` -> starts run and executes one step immediately.
2. `cs_continue_pipeline` -> runs N additional steps (default 1).
3. `cs_resume_pipeline` -> continues after interactive checkpoint.
4. `cs_plan_and_run_standard_pipeline` -> convenience "run until next checkpoint/finish".
5. If checkpoint is returned, complete that interactive job in CryoSPARC UI.
6. `cs_pipeline_status` -> status anytime.
7. `cs_pipeline_report` -> final run report.

## Checkpoints vs the old CLI workflow

The `Tools_Cryosparc_Workflow` scripts use `input()` so the **terminal** waits until you press Enter after the CryoSPARC UI step.

The **MCP server cannot block on stdin** (stdio is used for the MCP protocol). So when `checkpoint_required` is true, the tool **returns immediately** with a message. **Cursor / the assistant** should:

1. Read `operator_instruction` and `checkpoint_message`.
2. **Stop** and tell you what to do in the CryoSPARC UI.
3. Wait until you say you are done (e.g. “continue”).
4. Call **`cs_resume_pipeline`** with the same `run_id` (not `cs_continue_pipeline` at interactive jobs).

Every summary now includes **`operator_instruction`**, **`next_suggested_tool`**, and **`next_suggested_args`** so the client knows exactly what to do next.

## Notes

- Interactive checkpoints: Curate Exposures, Inspect Picks (W1/W2), Select 2D (W1/W2).
- Pipeline definition is in `workflow_definitions/w1_w2_standard.yaml`.
- Run state is stored in `runs/`.