Skip to main content
Glama
bazucas

webots-mcp

by bazucas

webots-mcp

Give AI agents full control and observability of the Webots robot simulator.

An MCP (Model Context Protocol) server that lets any MCP client — Claude Code, Codex, opencode, or your own agent — launch simulations, train reinforcement-learning models, watch what the robot is doing while it trains (without slowing training down), evaluate trained models, and manipulate the scene interactively.

Ask your agent things like:

"Start a 200k-step training run and tell me how it's going every few minutes." "Why did the reward drop? Show me a screenshot of what the robot is doing right now." "Evaluate the latest model for 10 episodes and compare it with yesterday's run." "Freeze the sim, put a box 25 cm in front of the robot, step 100 timesteps, and show me the sensor readings."

How it works

The Webots simulation state is only reachable from a controller inside the simulation loop — and during training, the robot's controller is the RL trainer. This server solves that with two independent channels that never touch the training hot path:

┌────────────── AI agent (any MCP client) ──────────────┐
│                 webots-mcp  (server.py)                │
└──────┬──────────────────┬──────────────────┬──────────┘
       │ JSON-TCP         │ files            │ subprocess
       ▼                  ▼                  ▼
  OBSERVER robot     runs/*/metrics.jsonl   webots / training
  (webots_bridge.py, (written by the SB3    (start, stop,
   2nd supervisor,    callback in           monitor)
   <extern>)          rl_metrics.py)
       │                  ▲
       ▼                  │
    Webots ◄────── training controller (untouched)
  1. Observer robot — an invisible second supervisor robot (synchronization FALSE) runs webots_bridge.py as an extern controller. It reads pose, velocity, scene objects and takes screenshots in parallel with training, costing ~3% fps.

  2. Metrics files — a Stable-Baselines3 callback (rl_metrics.py) appends one JSON line per episode (with per-component reward breakdown and termination reason) and per policy update (fps, losses). The server only reads files: training never waits for the agent.

  3. Process management — the server launches and stops Webots instances as subprocesses, each on its own port.

All state lives in files — killing the server loses nothing.

Quick start

Requirements: Python ≥ 3.10 and Webots (tested with R2025a on macOS; Linux/Windows install layouts are auto-detected via WEBOTS_HOME or PATH).

pip install git+https://github.com/bazucas/Webots_MCP        # installs the `webots-mcp` command
# with the RL extras (stable-baselines3 + gymnasium, for training/eval):
pip install "webots-mcp[rl] @ git+https://github.com/bazucas/Webots_MCP"

Or without installing anything, via uv:

uvx --from git+https://github.com/bazucas/Webots_MCP webots-mcp

Or clone and run from source: git clone ... && pip install -e ".[dev]".

Then register the server in your MCP client — the working directory must be your Webots project root (that's how the server finds worlds and runs/):

Claude Code

.mcp.json in the project root (cwd is the project automatically):

{
  "mcpServers": {
    "webots": { "command": "webots-mcp" }
  }
}

(If you didn't pip install, use "command": "python3", "args": ["/path/to/webots_mcp/webots_mcp/server.py"] — same for the clients below.)

opencode

opencode.json in the project root:

{
  "mcp": {
    "webots": { "type": "local", "command": ["webots-mcp"] }
  }
}

OpenAI Codex CLI (GPT)

~/.codex/config.toml (set WEBOTS_MCP_PROJECT since cwd may vary):

[mcp_servers.webots]
command = "webots-mcp"
env = { WEBOTS_MCP_PROJECT = "/home/you/my-webots-project" }

Cursor

.cursor/mcp.json in the project root — same shape as Claude Code's .mcp.json.

Claude Desktop / other MCP clients

Any client that speaks MCP over stdio works: command webots-mcp (or python3 /path/to/webots_mcp/webots_mcp/server.py). If the client doesn't launch it from your project directory (Claude Desktop doesn't), set WEBOTS_MCP_PROJECT in the server's env.

Restart/reconnect the client and approve the server — 23 tools appear.

Configuration

Everything is overridable with environment variables:

Variable

Meaning

Default

WEBOTS_MCP_PROJECT

project root

current working dir

WEBOTS_HOME

Webots installation

/Applications/Webots.app

WEBOTS_MCP_RL_WORLD

training world (.wbt)

<project>/thymio_rl_template/worlds/thymio_rl_env.wbt

WEBOTS_MCP_EVAL_WORLD

evaluation world (.wbt)

.../thymio_eval.wbt

WEBOTS_MCP_BRIDGE_WORLD

interactive world (.wbt)

.../thymio_bridge.wbt

Tools

Process management

Tool

What it does

webots_start(world?, mode?)

Launch a headless Webots instance; returns port, pid, log path

webots_stop(port)

Graceful SIGTERM (trainers save models in their finally)

webots_list()

Managed instances and liveness

Training

Tool

What it does

train_start(total_timesteps?, mode?)

Start a training run; returns run_id

train_status(run_id?)

timesteps done/target, fps, mean reward, termination breakdown, ETA, alive/finished

metrics_summary(run_id?, last_n?)

Aggregates of the last N episodes + per-component reward breakdown + first-vs-last trend

metrics_tail(run_id?, n?)

Raw last lines of metrics.jsonl

runs_list() / runs_compare(run_ids?)

All runs; side-by-side comparison

Live observation (during training)

Tool

What it does

observer_attach(port, robot_name?)

Connect the bridge — "observer" watches a training run; "" connects to the robot of an interactive world

sim_status()

Sim time, robot pose and velocity, obstacle inventory (including ones that fell off the arena)

sim_screenshot()

JPEG of the 3D view, returned inline to the agent

scene_query(def_name)

Fields of any scene node by DEF

observer_detach()

Disconnect

Evaluation

Tool

What it does

eval_run(algo?, episodes?, deterministic?)

Run a trained model N episodes; returns mean/min/max reward, episode lengths, terminations

Interactive control (world with the robot's controller set to <extern>)

Tool

What it does

sim_get_obs() / sim_set_motors(l, r)

Read sensors / drive motors

sim_pause(bool) / sim_step(n)

Deterministic stepping: freeze → act → step N → read

sim_set_pose() / sim_reset_robot()

Teleport / reset to initial pose

sim_spawn_box(x, y, z?, size?) / sim_clear_boxes()

Ad-hoc obstacles with physics

Plus the MCP prompt diagnose_training(run_id?) — a guided training-health analysis (trend, terminations, reward hacking signals, concrete shaping suggestions) — and resources webots://runs and webots://runs/{run_id}/metrics.

Adapting it to your project

The defaults reflect the project this was born in (Thymio II, ISCTE-IUL robotics course), but the pattern is generic. Your project needs:

  1. An observer robot in the training world (this is the whole trick — copy-paste):

Robot {
  name "observer"
  supervisor TRUE
  synchronization FALSE
  controller "<extern>"
}
  1. A DEF ROBOT on your main robot node, so the bridge can find it from outside.

  2. The metrics callback in your SB3 training script:

from rl_metrics import MetricsCallback
model.learn(total_timesteps=n, callback=MetricsCallback(run_name="ppo", runs_root="/path/to/project/runs"))

For per-component reward breakdowns, have your env put them in the info dict at episode end:

info["episode_metrics"] = {
    "termination_reason": "fall",           # your labels
    "steps": steps, "distance": d, "cells_visited": c,
    "reward_components": {"forward": 12.3, "cliff": -5.0},
}
  1. For train_start, your training controller should read TRAIN_TIMESTEPS from the environment; for eval_run, your evaluation controller should read EVAL_ALGO, EVAL_EPISODES, EVAL_DETERMINISTIC and write results to the JSON path in EVAL_OUT.

webots_bridge.py also works standalone without MCP — one JSON command per TCP connection on 127.0.0.1:10101:

printf '{"cmd":"get_obs"}\n' | nc -w 5 127.0.0.1 10101

Tests

pip install -e ".[dev]"
pytest tests -m "not webots"   # unit + MCP stdio protocol (~1 s, no Webots needed)
pytest tests -m webots         # integration against a real Webots (~20 s)
pytest tests                   # everything (37 tests)

CI (GitHub Actions) runs the fast suite on Linux, macOS and Windows for every push.

Integration tests need a real Webots project (WEBOTS_MCP_PROJECT); they skip cleanly if it's missing. Bridge unit tests inject a fake Webots API via sys.modules["controller"], so most of the suite runs anywhere.

Limitations & notes

  • Webots itself was only exercised on macOS (R2025a). The server code runs on Linux/Windows in CI, and install layouts for all three OSes are covered by unit tests, but end-to-end Webots runs on Linux/Windows are untested — PRs welcome.

  • The bridge listens on localhost only, with no authentication — don't expose the port.

  • One bridge connection at a time (BRIDGE_PORT, default 10101).

  • Avoid simulationReset in training code (it kills the observer's connection); reset by teleporting the robot instead.

License

MIT — do whatever you want with it.

Related MCP Connectors