closed betaAccess comes in small rounds.

Hand your robot to your customer's people. And to their AI.

One small bridge process puts a ROS 2 robot on the internet. You pick, in a console, which topics, services, actions and cameras become API. Your app, your login page, your name on it — and an MCP server for every assistant that asks.

mcp on both sides

Configure it from your assistant. Then let theirs ask.

Fleetless is not a dashboard an agent has to click through. Everything the console does is a tool; everything the robot exposes is a tool. One host for you, one endpoint per app for the people your robot works for.

mcp — warehouse_dash — livemcp.fleetless.dev
you› expose /battery/state.percentage on amr-04 as battery_percentage, 1 Hz
tool→ console_create_datapoint { robot: "amr-04", slug: "battery_percentage", max_hz: 1 }
result← ok · draft v4
you› publish it
tool→ console_publish_configuration { robot: "amr-04" }
result← published · v4
ops lead› is the robot charging?
tool→ get_datapoint { robot: "amr-04", slug: "battery_percentage" }
result← { value: 87.5, unit: "%", timestamp_ms: 1788514742497 }
ops lead› send it to the dock
tool→ start_action { robot: "amr-04", slug: "dock" }
result← refused · role observe may not start dock
for you
https://mcp.fleetless.dev

Paste it into Claude Code, Codex, Claude Desktop or anything else that speaks the Model Context Protocol. The client discovers everything from the bare host, registers itself and asks for no client id.

robot tools
14
console_* tools
59

Read datapoints, call services and actions, watch jobs, take a camera snapshot — and every console operation: robots, exposure, apps, roles, users. Build the robot and the app from the same chat.

for your users
https://mcp.fleetless.dev/<app_identifier>

Switch on your app's own endpoint and hand it to your users. They sign in through your login page — Fleetless never shows them one — and get the same 14 robot tools, filtered by their role.

the point

Your customer's operations lead asks their assistant whether the robot is charging, instead of writing to your support address.

Their identity stays in your app. Their role decides what the assistant may do; the tool list stays the same for everyone, and a refusal says why.

limit 1A camera is a take-a-snapshot-now tool. Never a live video session.
limit 2Tools only. No MCP resources, no prompts.
limit 3The tool set never varies by role. Refusals do the talking.
camera

Video to the browser. The robot never finds out how popular it is.

The bridge binds four kinds of source: ROS 2 image topics, RTSP, MJPEG and V4L2. Per camera you set resolution, frame rate and bitrate — which is also how you cap the robot's own uplink.

One uplink per camera, regardless of audience. The cloud fans out. Live over WebRTC: the first viewer starts the stream, the last one leaving stops it. Snapshots are always on, at your cadence, served from a cloud cache.

No recording. Fleetless hands you the pixels, not the archive.

camera_front1 uplink · 3 viewers
live · receiving 1280×720 · 12 fps WebRTC snapshot cached · 1 Hz
snapshotAlways on. Your cadence. One capture serves everyone who asks.
liveOn demand. Starts with the first viewer, stops after the last.
exposure

The ROS graph is private. You decide what becomes API.

Five kinds of thing can be exposed. Each gets a slug you choose, decoupled from the ROS name. Rename the topic; nobody's app breaks.

kinddirectionwhat it does when things go wrong
Datapointrobot → clientsOne field or a whole topic; unit, range and scaling per field; max N Hz or on change; optional retention. Every value carries timestamp_ms: when the bridge captured it, never when your app received it.
Publisherclients → robotA timeout and a failsafe message the bridge publishes itself when clients go quiet — dropped link included. cmd_vel, 300 ms, zero-twist failsafe: a lost connection stops the robot instead of leaving it committed to its last command.
Servicerequest → responseParameters validated in the cloud against rules you wrote: min/max, enum, pattern, required. An invalid value never reaches the bridge.
Actiona job with a lifecycleFeedback, progress, result, cancel. A disconnect does not abort a running job; the result arrives on reconnect. A job lost to a bridge restart is reported as lost, not as still running.
Camerarobot → clientsSnapshot and live, as above. Bandwidth is set per camera, on the robot's side.
slugs, not ROS names

Every exposed thing has the name you gave it. The robot can call it whatever it likes.

/battery/state.percentage → battery_percentage
three datapoints nobody configures

Every robot ships them. Build your offline state on the first one instead of inferring it from timeouts.

bridge_state · robot_details · bridge_pressure
offline robot

A command to a robot that is not there is rejected immediately. There is no queue that fires an hour later.

bridge_state → offline · rejected
users and roles

Two identity spaces. They never meet.

Your team lives in the console. The users of one app live in that app: each belongs to exactly one app, has exactly one role, and signs in through your UI — password or any number of OpenID Connect providers you configure — over a JSON auth API.

Permissions are a matrix of role × service, per robot, per slug. Every app starts with observe and operate and can define its own. Roles are the only filter. A per-app server key exists for your own backend.

amr-04 · slugobserveoperateshift_lead
battery_percentagereadreadread
camera_frontsnapshotsnapshot · livesnapshot · live
drivepublish
dockstart · cancelstart
set_modecall
Two roles come with every app. shift_lead is one a developer added. The MCP tools, the SDK and the REST API all read this same table.
the sdk

A dozen lines. Browser or Node, unchanged.

@fleetless/sdk on npm. TypeScript, framework-agnostic, ESM and CJS. Same slugs, same roles, same validation as everything else on this page.

live 3D

A recipe with a working demo: three.js + urdf-loader, loading your URDF and meshes from the authenticated asset store. Joint states are an ordinary datapoint.

your login UI

A recipe on the client auth API. The page is yours; the tokens are our problem.

mcp server

A recipe for switching on your app's endpoint and handing it out.

scene templates

Ready-made three.js scenes for a robot app.

typescript@fleetless/sdk
import { createClient } from "@fleetless/sdk";

const client = createClient({
  apiUrl: "https://api.fleetless.dev",
  appIdentifier: "warehouse_dash",
});
await client.auth.login("user@example.com", "correct-horse-battery");

const battery = await client.datapoints.get("robot-id", "battery_percentage");
console.log(battery.value, battery.timestamp_ms);

client.datapoints.subscribe("robot-id", "battery_percentage", {
  onEvent(event) {
    console.log(event.value, event.timestamp_ms);
  },
});

One afternoon. Bridge, robot, datapoint, a number on a page.

Fleetless is in closed beta and access comes in small rounds. The list is the way in.

POST api.fleetless.dev/api/waitlist · one field · no newsletter