Plain-English analysis of robotics, drone, and IoT data, with intelligent edge data reduction.
Bagel by Extelligence lets you ask questions about robotics, drone, and IoT data in plain English. Every calculation over your message data is DuckDB SQL, not model guesswork, and Bagel shows you the query so you can audit it.
Is my IMU sensor overheating?
Bagel also has an intelligent edge data reduction pipeline: describe an event and Bagel runs the detection on the robot, keeping the windows that matter and dropping the rest. An MCP server puts all of it in your LLM's hands: Claude Code, Gemini, Cursor, or a fully local model.
Bagel was the first MCP server to ship a real analysis toolkit for robotics data, and it keeps the LLM where it belongs: in front of your logs, never in your robot's control loop.
No MCP client, no LLM, no config: run the same deterministic checks against a bundled sample log and get a robot-health report card straight to your terminal.
docker run -it --rm ghcr.io/extelligence-ai/bagel/px4:latest demo
sample.ulg - 41.5s, 2018 messages, 77 topics
Power ⚠️ min 21.07V, largest drop 2.37V at ~t=+4.8s, end 23.45V (battery_status_0)
IMU ✅ accel_z stddev 1.6x the log baseline at ~t=+36.8s (sensor_combined_0)
GPS — skipped: no GPS topic
Data gaps ✅ no gap > 1.05x median interval (checked battery_status_0, sensor_combined_0)
...
The ROS2 images (ros2-kilted, ros2-jazzy, ros2-iron, ros2-humble) run
demo the same way, against a lighter bundled sample (px4 is the one that
ships with a flight log rich enough to show every check). Point it at your
own log with demo /path/to/log (mount it with -v first), or keep reading
for the full MCP setup below.
[!TIP] Already have Claude Code? Just paste the link to this repo and tell Claude what environment you want:
Set up https://github.com/Extelligence-ai/bagel for ROS2 Kilted.
Claude will clone the repo, start Docker, and wire up the MCP connection for you.
Install Docker Desktop and Claude Code (or another MCP-enabled LLM).
[!NOTE] arm64 hosts (Apple Silicon, Raspberry Pi, Jetson, Graviton):
ros2-kilted— the default service, and the oneserver.jsonpins — ships as a multi-arch image, so Docker pulls a native arm64 build. No extra setup.The other services are published for amd64 only. Docker Desktop emulates them automatically, so they run on Apple Silicon (slower, but they work). On arm64 Linux with plain Docker Engine there is no emulation by default and they fail immediately with
exec format error— install QEMU/binfmt first:docker run --privileged --rm tonistiigi/binfmt --install amd64
git clone https://github.com/Extelligence-ai/bagel.git && cd bagel
docker compose run --service-ports ros2-kilted
[!TIP] Port 8000 already in use? Set
MCP_SERVER_PORTto something else, for exampleMCP_SERVER_PORT=8100 docker compose run --service-ports ros2-kilted, and use that port in step 2.
Pick the service that matches your environment:
| Service | Use case |
|---|---|
ros2-kilted | ROS2 Kilted (latest) |
ros2-jazzy | ROS2 Jazzy |
ros2-jazzy-jev | ROS2 Jazzy + on-robot decision model (GPU, beta) |
ros2-iron | ROS2 Iron |
ros2-humble | ROS2 Humble |
ros1-noetic | ROS1 Noetic |
ros1-noetic-cv | ROS1 Noetic + CV |
px4 | PX4 flight logs |
ardupilot | ArduPilot flight logs |
betaflight | Betaflight flight logs |
iot | IoT / MQTT (live) |
The -jev image (beta) adds PyTorch for running a decision model on the robot
(backend: local in the anomaly gate). Build any
other service the same way with --build-arg JEV_MODE=true. CPU-only robots don't need
it: the hosted Jev backend works in every image.
[!TIP] To give Bagel access to your local files, edit
compose.yamlbefore starting Docker: uncomment and update thevolumessection under your chosen service.
Wait for this output:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
In a new terminal:
claude mcp add --transport sse bagel http://localhost:8000/sse
[!NOTE] The MCP endpoint is bound to
localhostonly (not exposed to the LAN) for security. To share it with other machines, drop the127.0.0.1prefix incompose.yamland put an authenticated proxy in front: see SECURITY.md.
claude
Summarize the metadata of the ROS2 bag "./data/sample/ros2/mcap".
That’s it: you’re chatting with your data.
Swap step 2 for a local model: your data and your LLM stay on the machine:
brew install ollama && ollama serve & # or ollama.com
ollama pull qwen3:8b
uvx ollmcp --mcp-server-url http://localhost:8000/sse --model qwen3:8b
Model picks, expectations, and troubleshooting: Local LLMs guide.
Bagel works with any MCP-enabled LLM. Setup runbooks for tested alternatives:
Can’t find your LLM? Open a ticket.
Bagel ships an agent plugin: four skills that teach the agent when and how
to drive the server (log triage, pipeline authoring, live sinks, visualization
export) plus the MCP connection, wired automatically. The same plugin/
directory serves both Claude Code and OpenAI Codex.
/plugin marketplace add Extelligence-ai/bagel
/plugin install bagel@bagel
Codex and ChatGPT users: install bagel from the
OpenAI Plugins Directory
(one click), or clone the repo and add it as a plugin marketplace (the repo
carries .agents/plugins/marketplace.json). Directory installs bundle the
skills only, so also connect the server once in ~/.codex/config.toml:
[mcp_servers.bagel]
url = "http://localhost:8000/mcp"
Repo-marketplace and Claude Code installs wire this connection automatically.
Then start the container for your data format (see Quickstart): the plugin
connects to http://localhost:8000/mcp by default. Any other MCP client can
discover the same workflows server-side via the list_agent_capabilities tool.
A robot records more data than you can afford to move. Bagel turns a question into a detector, runs it where the data is recorded, and ships only the windows around real events.
Here it is in one conversation:
Don't know the event in advance? The anomaly gate (beta) learns what normal looks like on the robot, asks Jev to name whatever isn't, and keeps only those slices, each with a JSON label, for any bucket: S3, GCS, Azure, MinIO or R2.
The session above: a 20-minute (1,200 s) recording and the prompt "keep 10 seconds before and after every deceleration harder than −10 m/s²". The preview detects 7 events, merges them into 4 windows, and keeps 92 s of the 1,200 (7.6%); the run writes a 2.1 GB bag down to 161 MB. These figures are illustrative demo output, not a measured benchmark: the ratio is event-window duration over total duration, so it depends entirely on your workload.
| Industry | Formats |
|---|---|
| Robotics | ROS1, ROS2, MCAP (any profile), Copper (via MCAP export), ROS text logs (~/.ros/log) |
| Robot learning | Gantry Bench evidence bundles — a dataset verdict's working (per-clip signal checks, robot-test ladder, findings) as queryable tables |
| Drones | PX4, ArduPilot, Betaflight |
| Automotive | ASAM MDF4 (.mf4), CAN captures (.blf/.asc + DBC) · beta |
| IoT | MQTT (live, Sparkplug B), PostgreSQL / TimescaleDB, InfluxDB 3 |
| Hardware state | WaffleForm snapshots (.waffleform.yaml), auto-detected via waffle-iron · beta |
You already have ros2 *, PlotJuggler, and grep. Bagel doesn't replace them: it
answers the questions they make you work for, then hands off to them:
| You do this today | Ask Bagel instead |
|---|---|
ros2 bag info for metadata | "Summarize this bag": same prompt works on PX4, ArduPilot, MCAP, MQTT, Postgres |
ros2 topic echo /imu and eyeball raw values | "What's the peak z-deceleration in /imu? Running average over 5 s?" · real SQL underneath: peaks, running averages, percentiles, cross-topic correlations |
| Scrub PlotJuggler timelines hunting for the event | "Find every deceleration under −10 m/s² and cut ±30 s snippets": then open the result in PlotJuggler with a pre-framed layout |
rqt_console, or grep ~/.ros/log | "Read the ERRORs from ~/.ros/log and tell me what went wrong": tracebacks included, no bag needed |
| Echo two topics in two terminals, correlate in a spreadsheet | "What's the correlation between current and voltage?": topics live in one SQL relation, so joins and corr() are one question |
ros2 bag record -a and babysit the disk | A standing edge pipeline: record continuously, keep only event windows, drop the rest |
| A bash loop over 200 bags | "Run this pipeline on every bag in the folder": one pipeline, whole fleet, with a combined report |
scp/aws s3 sync scripts to ship data off the robot | Upload to S3, GCS, or Azure as a pipeline step, checksum-skipping files already there |
| A different viewer per format: FlightPlot for PX4, MAVExplorer for ArduPilot, Blackbox Explorer for Betaflight | The same conversation for all of them, and ROS, MCAP, MQTT, Postgres, InfluxDB |
| Write a one-off pandas script per question | Ask the question; Bagel writes and runs the query |
One sentence of plain language, one answer, instead of a pipeline of commands and a script you'll delete tomorrow.
You can ask Bagel almost anything. For example:
What’s the correlation between current and voltage in the
/spot/status/battery_statestopic?
I think the robot hit a pothole. Can you check for sudden deceleration on the z-axis to confirm?
Every time the drone decelerates harder than -10 m/s², keep 10 seconds before and after. Drop everything else.
Did anything change on this robot since last week?
Time to put Bagel to the test: can it catch a drone doing barrel rolls? Spoiler: 🎉 It totally can.
When you ask a question, Bagel analyzes your data source’s metadata and topics to build a high-level understanding.
Based on your prompt, if further inspection is needed, Bagel identifies the most relevant topics and interprets their meaning and structure. Bagel then writes the relevant topic messages to an Apache Arrow file and uses DuckDB to generate and execute queries against it.
This process is repeated as needed, running new queries until Bagel finds the best answer to your question.
LLMs excel at language but struggle with math. Bagel overcomes this by generating deterministic DuckDB SQL queries. These queries are displayed for you to audit, and you can guide Bagel to correct any errors.
Bagel learns new capabilities through POML files: a structured set of instructions that describe a “trick,” such as computing latency statistics.
For example, let’s define ./src/agent/examples/woof.poml.
<poml>
<task>
Count the topics in the data source.
If the count is odd, say "woof", else say "meow".
</task>
<output-format>
Return the sound, the topic count, and a few cute emojis. Nothing else.
</output-format>
</poml>
Prompt Bagel:
Run the POML capability "./src/agent/examples/woof.poml" on the ROS2 bag "./data/sample/ros2/mcap".
Result:
meow 🐱 4 topics 🐱💤🎯
Bagel discovers your own capabilities from ~/.bagel/capabilities/:
save_agent_capability
and it's reusable in any future session.src/agent/compose/pipeline.poml for the house style)
into ~/.bagel/capabilities/.Either way it shows up in list_agent_capabilities as user/<name> and runs
with run_poml_capability — from Claude Code, Claude Desktop, or any MCP
client. Teams: keep the directory in your own git repo and sync it to every
robot; it's just files. On Linux, run mkdir -p ~/.bagel/capabilities once
before starting the container so the mount is owned by you, not root.
preview_anomalies dry-runs the screen first~/.ros/log errors and warnings without opening a bagRough edges we know about, so you don't find them the hard way:
asammdf, python-can);
real CANape/INCA/Vector-produced captures haven't crossed our test bench yet.
LeRobot exports load-test clean with the real lerobot package, but no policy
has been trained from a Bagel export yet.anomaly and decide gates (recorded logs and live
subscriptions), preview_anomalies, the on-robot local backend and the
ros2-jazzy-jev image. Its Jev backend has been run against live Jev through Vercel
AI Gateway on a real drive, a synthetic fault log and a live MQTT stream; a direct
TypeSafe key is not yet exercised, and detection quality has not been measured on
logs with known incidents. On a live subscription the flagged window is kept as
Parquet (write_topics_to_file); MCAP/rosbag snippets need a recorded log and are
refused there. The baseline is learned per run and restarts with the
process, so in screen mode the first minutes of each run are never flagged. Labels,
settings and defaults may change between releases.We’d love your help! The easiest way to support the project is by giving it a ⭐ on GitHub.
Other great ways to contribute:
Before contributing, please review the guidelines.
Join the conversation in our Discord server. We hang out there regularly.
Bagel is open source under the Apache License 2.0.
For maintainers: discovery audits and evaluation, listing maintenance, and reproducible user reports.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
docker run -i --rm ghcr.io/extelligence-ai/bagel/ros2-kilted:2.4.0Merge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-extelligence-ai-bagel": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/extelligence-ai/bagel/ros2-kilted:2.4.0"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceghcr.io/extelligence-ai/bagel/ros2-kilted:2.4.0dockerBagel works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.