rl-mcp
Watch, diagnose and steer a live RL training run — from an AI agent, or from your shell.
Training a policy is a slow feedback loop. Launch, wait an hour, squint at TensorBoard, guess which reward weight was wrong, relaunch. rlmcp shortens it.
Attach it to a running job and you can ask the live process what the robot is doing, look at it, measure how jerky it is, change a reward weight, unlock harder terrain, and roll back if that made things worse. No restart.
Two backends ship: mjlab, which is what this is developed against, and IsaacLab. The simulator sits behind a small adapter, so a third is a couple of hundred lines rather than a fork.
⚠️ Experimental. The surface is real and tested, but it is still moving.
🚀 Start here
If you use a coding agent, give it one line:
Wrap my mjlab env with rlmcp (github.com/mktk1117/rl-mcp) and show me how to use it.
(Say IsaacLab instead if that is what you train on — the docs cover both.)
It reads the docs here, adds the wrapper to your training script, and drives the run from there.
If you are driving yourself, jump to Install and Try it.
What it looks like
rlmcp status # iteration, stage, headline metrics
rlmcp diagnose --seconds 4 # is the gait smooth? is it tracking?
rlmcp shot --where terrain=pyramid_stairs # look at a robot on the stairs
rlmcp video --seconds 5 # and one at 0, 50, 100, 200 … unasked
rlmcp view # where the browser view is (a run has one)
rlmcp set reward.action_rate_l2.weight -0.25 --why "ankles chattering at 15 Hz"
rlmcp run set_terrain terrains='["flat","random_rough"]' max_level=4
rlmcp curriculum advance --why "flat is solved"
rlmcp checkpoint before-experiment # and `rlmcp load` to undo
rlmcp events --interventions # everything anyone did, and why
rlmcp play # watch a finished run's policy
Almost all of that is an MCP tool too, so an agent can drive the whole loop,
and it sees the screenshots and plots, not just their file paths. play is
the exception, and stays CLI-only because it opens a window.
The CLI knows who is reading. A terminal gets aligned tables, and pictures open by themselves. A pipe gets JSON, and each command's JSON is a parsing contract that does not move — the shapes are written down and pinned by tests, in docs/tools.md.
The whole integration
One call, between building the environment and building the runner.
from mjlab.envs import ManagerBasedRlEnv
import rlmcp
env = ManagerBasedRlEnv(cfg=env_cfg, device="cuda:0", render_mode="rgb_array")
env = rlmcp.wrap(env, session_dir=log_dir / "rlmcp", curriculum="terrain")
vec_env = RslRlVecEnvWrapper(env)
runner = VelocityOnPolicyRunner(vec_env, agent_cfg, str(log_dir), device)
env.attach_runner(runner) # PPO knobs, checkpoints, iteration boundaries
runner.learn(num_learning_iterations=agent_cfg.max_iterations)
That is it. Details and traps: docs/your-task.md.
The same one call on IsaacLab
IsaacLab launches its own app before anything else can be imported, so the wrap
goes after gym.make. Downstream is the same CLI, the same MCP tools and the
same JSON, minus what only mjlab can answer — the terrain commands, which an
IsaacLab run reports as absent rather than broken.
env = gym.make(task, cfg=env_cfg, render_mode="rgb_array")
env = rlmcp_isaaclab.wrap(env, session_dir=log_dir / "rlmcp", task_id=task,
service_every_steps=agent_cfg.num_steps_per_env)
rlmcp shot --env-id 300, taken mid-training from a stock
Isaac-Velocity-Rough-Anymal-D-v0 run: one robot on the stairs, with the other
2047 environments carrying on behind it. Nothing about the task was described to
rlmcp — 73 tunable knobs came out of walking the environment's own manager
configs, the same walk it uses on mjlab. The example script is
examples/train_isaaclab.py, and the things that
differ — cameras, the robot's name, which Isaac Sim goes with which driver — are
in docs/isaaclab.md.
What you get
| Look at the robot | Screenshots and clips of real training steps, not a separate eval. Pick which robot with --where terrain=stairs. |
| Watch it live | Every run serves a browser view; rlmcp view says where. It needs no renderer, so it works on a headless box, and it costs nothing while nobody has the tab open — which is why it is on rather than a flag. The tab has mjlab's own panels: reward bars, term plots, contact and force overlays. Pause view (or rlmcp view --pause) freezes the tab and hands the run back its full speed, without giving the port back. --realtime buffers a window and plays it at the speed the robot actually moves, with mjlab's player in the tab. |
| Know why it moves badly | diagnose measures jerk, chatter, effort, posture and gait, then says which lever to pull. When it cannot measure properly, it says so instead of guessing. |
| Tune anything, live | 97 knobs on the G1 rough task, discovered from the environment. Reward weights, randomization ranges, PPO hyperparameters. Applied between rollout batches, never mid-step. |
| A ladder that drives itself | Curriculum stages promote on earned conditions, not a schedule. Override any of it from a shell. |
| Undo | Checkpoints save weights and parameters, stage and extension state. |
| Your task's own words | Extensions add verbs, metrics and env selectors. They reach the CLI, MCP and curriculum stages at once, without editing rlmcp. |
| A record that survives | Every change is logged with its reason, and every run stamps the code it launched with. Runs are kept as a graph with hypotheses and verdicts, so three failures in a row become one conclusion. |
| Look at a finished run | rlmcp play restores the conditions a checkpoint trained under before rendering it. Otherwise a good policy looks broken. |
A worked example: in-hand cube reorientation
A SharpaWave hand turning a cube to a commanded orientation anywhere in SO(3),
then holding it there for a full second. This is the final policy, replayed from
its checkpoint with rlmcp play:
Ten runs to get there. Three worked, four were falsified, three were stopped
early. That is the normal shape of RL work, which is why rlmcp keeps runs as a
record instead of a folder of logs. rlmcp record graph writes one
self-contained HTML page — no server, no build step — and it looks like this:
Each card is one run: the clip it produced, and the one line it concluded. Click any of them and the panel on the right shows what that run predicted, what would have proved it wrong, what changed since its parent, and what it measured.
Runs 005 to 007 are the interesting part. The policy froze, because standing still paid about 2600x more than making progress. Cheap drops broke the freeze and produced the mirror failure: the cube thrown off the palm within half a second. Re-pricing the trade in between failed too. Three failures, one conclusion: the reward was never the binding constraint. 008 changed the action interface instead — an EMA filter — and went from 1.1 to 16.8 goals per minute with the cube held 99.4% of the time.
That conclusion is not in any single run. It is in the shape of three siblings, which is the argument for keeping runs as a graph.
The same page has a tree view for the ancestry itself and a parameters view for what changed across runs. More in docs/records.md.
Install
pip install rl-mcp # training side: numpy, matplotlib, pillow, imageio
pip install 'rl-mcp[server]' # adds the MCP SDK for the agent side
uv pip install takes the same lines. For what is on main right now, or to
skip installing altogether and run the CLI straight from GitHub:
pip install 'rl-mcp[server] @ git+https://github.com/mktk1117/rl-mcp'
uvx --from 'rl-mcp[server] @ git+https://github.com/mktk1117/rl-mcp' rlmcp sessions
A checkout installs as pip install -e '.[server]'.
The MCP SDK is optional on purpose. The training process does not need it, and
the server does not need a simulator. Both mcp>=2 and mcp 1.x work.
The simulator is yours to install. rlmcp asks only that it be importable from the training process — for IsaacLab that means installing into the interpreter Isaac Sim runs under.
Register the server with Claude Code:
claude mcp add rlmcp -- rlmcp-server --root /path/to/logs
Try it
rlmcp tasks # which ids exist here, before anything has run
# train, with the terrain ladder driving itself
rlmcp-train Mjlab-Velocity-Rough-Unitree-G1 --num-envs 4096
Then, from another shell:
rlmcp sessions # what is running
rlmcp status
rlmcp curriculum
rlmcp diagnose --seconds 4
The explicit-curriculum version of the same run is in examples/train_g1_rough_curriculum.py. On IsaacLab, the equivalent first run is examples/train_isaaclab.py — same four commands afterwards.
Documentation
| page | for |
|---|---|
| docs/tools.md | Every tool, one entry each. Shell command, MCP call, what comes back, and the traps. Start here for either audience. |
| docs/mcp-server.md | Agents: setup, session pinning, liveness, deferred jobs, a worked steering session. |
| docs/tuning.md | The tuning loop: verify the task first, watch the right numbers, read symptoms into levers. The distilled findings of two campaigns. |
| docs/your-task.md | Putting rlmcp on your own task, in five steps. |
| docs/curriculum.md | Writing the stage ladder. |
| docs/extensions.md | Teaching rlmcp your task's vocabulary. |
| docs/isaaclab.md | Driving an IsaacLab run: the one line, cameras, what differs from mjlab. |
| docs/records.md | Hypotheses, verdicts, feedback, code snapshots, the record graph. |
| docs/design.md | How it fits together, how parameters are found, other simulators. |
| docs/style.md | The style guide: two spaces, what ruff checks, and why each rule is on or off. |
| AGENTS.md | Contributing to this repository. |
How it works, briefly
Two processes that never touch each other. They talk through a plain directory of JSON files.
training process session directory agent process
torch + a simulator plain JSON files no simulator
┌───────────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ rlmcp-wrapped env │ │ status.json │ │ MCP server │
│ publishes metrics │ ──writes──► │ metrics.jsonl │ ───reads──► │ (38 tools) │
│ records frames │ │ events.jsonl │ │ rlmcp CLI │
│ runs commands │ ◄──reads─── │ artifacts/ │ ◄──writes── │ your own scripts │
│ between batches │ │ inbox/ outbox/ │ │ │
└───────────────────┘ └─────────────────┘ └──────────────────┘
Telemetry flows left to right. Commands flow right to left. Nobody shares memory, and nothing blocks the training loop.
That buys four things:
- The agent side never imports torch or a simulator, so starting or killing it cannot disturb training.
- Commands run between rollout batches, so an edit can never race the simulator.
- Everything is inspectable with
catwhen something goes wrong. - A run leaves a complete, replayable record behind after it exits.
The same split is why a second backend was cheap. Parameter discovery, trace sampling, metrics and the wrapper are written against the shape mjlab and IsaacLab share; each adapter is only what is genuinely its own — how a robot is found in the scene and how a frame is rendered. More in docs/design.md.
Tests
pytest tests -q # 855 tests, ~10s, no GPU and no simulator required
License
Apache-2.0 — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rl_mcp-0.1.1.tar.gz.
File metadata
- Download URL: rl_mcp-0.1.1.tar.gz
- Upload date:
- Size: 750.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3be25172e2c17f8418448d5d3713c46bb53050dcfc8a68268fcbbd6f2b21ac3c
|
|
| MD5 |
3f86c19a8d9129a185b91e7a5a4e17fe
|
|
| BLAKE2b-256 |
897532cf0433d2a4a1c8bdfb0890fa7da912501c2a1e5fd78d4d4afff58c00cc
|
Provenance
The following attestation bundles were made for rl_mcp-0.1.1.tar.gz:
Publisher:
publish.yml on mktk1117/rl-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rl_mcp-0.1.1.tar.gz -
Subject digest:
3be25172e2c17f8418448d5d3713c46bb53050dcfc8a68268fcbbd6f2b21ac3c - Sigstore transparency entry: 2709184821
- Sigstore integration time:
-
Permalink:
mktk1117/rl-mcp@ef53ed48b3c570b8d3d8d7c23a79803034f7bef0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mktk1117
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ef53ed48b3c570b8d3d8d7c23a79803034f7bef0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file rl_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: rl_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 622.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f3e5aeaab6af2cb6b7957b0942925ca78e8788aa811989c35d48571e8ee4e27
|
|
| MD5 |
c610e06993587eab68a260619b6441f5
|
|
| BLAKE2b-256 |
68524aa97a54296ec8799d7b1974a5d30eb6de45d267209d928c486c14790a63
|
Provenance
The following attestation bundles were made for rl_mcp-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on mktk1117/rl-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rl_mcp-0.1.1-py3-none-any.whl -
Subject digest:
3f3e5aeaab6af2cb6b7957b0942925ca78e8788aa811989c35d48571e8ee4e27 - Sigstore transparency entry: 2709184870
- Sigstore integration time:
-
Permalink:
mktk1117/rl-mcp@ef53ed48b3c570b8d3d8d7c23a79803034f7bef0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mktk1117
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ef53ed48b3c570b8d3d8d7c23a79803034f7bef0 -
Trigger Event:
release
-
Statement type: