🤖 MCP
An AI tool speaking the Model Context Protocol drives a robot on a signed-in person’s behalf: datapoints, services, actions, jobs, snapshots. Two endpoints serve the two populations Fleetless holds.
Two endpoints
| Endpoint | Who signs in | Sign-in | Tools |
|---|---|---|---|
https://mcp.fleetless.dev |
your team, Fleetless users | the Fleetless portal — emailed code or passkey | 14 robot tools and 70 console_* tools |
https://mcp.fleetless.dev/<app_identifier> |
the users of that one app | your app’s own login page | the same 14 robot tools |
Both hosts are friendly aliases for the canonical resource identifiers,
https://api.fleetless.dev/mcp and
https://api.fleetless.dev/mcp/<app_identifier>, and every OAuth issuer,
resource and audience is minted from that base URL and checked against it, never
against the arriving host. Use them where a tool insists on a full path.
An app user cannot use the central endpoint, and a Fleetless user cannot use an app’s. Neither endpoint does three things:
- No live camera. A camera is one snapshot tool, never a video session.
- No MCP resources or prompts. Tools only.
- No role-shaped catalog. Every session sees the same list, and the refusals do the talking.
Your team: the central endpoint
Paste https://mcp.fleetless.dev. Every Fleetless user has access,
unconditionally, and the login is always by emailed code or passkey, never a
password, with no federation branch.
There is no client id to configure. The authorization server advertises an
RFC 7591 registration_endpoint, so a tool registers itself on first
connection; Fleetless registers public clients only. A tool that cannot
register itself uses the well-known id fleetless-central-mcp, the one client
that skips consent.
Every self-registered client passes a consent screen naming it before a code is minted. Central consent is asked on each connection and nothing is stored — a remembered grant with no withdrawal door would be the worse answer.
Your users: an app’s endpoint
Switch it on per app with mcp_enabled, then hand your users
https://mcp.fleetless.dev/<app_identifier>. The authorize step renders
nothing: it writes an interaction and redirects the browser to the app’s
mcp_login_url, with the interaction id substituted for {interaction}.
Switching mcp_enabled on needs the Plus plan or above; see
Plans and Limits.
- Read the pending request (
GET /api/client/mcp/interactions/:id) with the user signed in — the bearer moves one field,already_granted, and an anonymous read always answersfalse. - Sign the person in with the login form your app already has.
- Draw a consent screen and post
POST …/:id/approveorPOST …/:id/denywith the user’s own access token. The reply carries the URL to send the browser to; a denial redirects too, witherror=access_denied.
| Field | What it is |
|---|---|
client_name |
The client’s own claim, from an unauthenticated registration |
client_name_verified |
The literal false; there is no verified case |
scopes |
Always empty. The user’s role decides, re-read per call |
already_granted |
A record, not a check. Approve succeeds without one |
An interaction lives ten minutes, and the approving token’s app_id must
match it; anything else answers one 410. A standing consent lets a later
authorization skip your consent screen, and two doors withdraw it: the app
user’s own (DELETE /api/client/mcp/grants/:clientId) and the developer’s
(DELETE /api/apps/:id/users/:userId/mcp-grants/:clientId).
Withdrawal cuts the live session, at that client's very next call. The
endpoint reads the standing consent on every request, so a withdrawn client
is refused with a 401 and a challenge back to your consent screen,
unexpired token and all. It ends one client, not the person.
Both endpoints issue refresh tokens: each refresh consumes the presented token, answers a new pair, and lives ninety days from its last use. A blocked account, a withdrawn consent or a removed team member is re-read at every refresh, and a refused refresh revokes the session. An app user’s password change, or a two-factor reset — an owner’s on Settings › Team for a Fleetless user, an app’s admin for one of its own users — revokes every session the account holds, without waiting to be asked.
The catalogs
Every session is offered the same tools; what differs is what a call answers. These fourteen serve both endpoints.
| Tool | Input | What it does |
|---|---|---|
robots_list |
— | The robots you reach, with bridge state and configuration version |
robot_describe |
robot_id |
The datasheet for one robot |
datapoint_read |
robot_id, slug |
The latest value, at the bridge’s capture time |
datapoint_history |
robot_id, slug, from |
The recorded series, optionally bucketed |
service_call |
robot_id, slug |
Calls a service, waits for its result |
action_start |
robot_id, slug |
Starts an action; the result, or the running job |
jobs_list |
robot_id |
What runs now; with history: true, what has run. That half needs action_history |
job_get |
robot_id, slug |
The most recent job on one slug |
job_cancel |
robot_id, slug |
Cancels what runs on a slug, or only job_id; answers with the action server’s verdict |
publish |
robot_id, slug, message |
One message to a publisher, never a stream |
camera_snapshot |
robot_id, slug |
The most recent snapshot, with its age |
assets_list |
robot_id |
Meshes and textures. Needs assets, as do the two below |
asset_get |
robot_id, asset_id |
One asset’s metadata and a signed link |
urdf_get |
robot_id |
A signed link to the URDF, mesh references rewritten |
robot_describe is the datasheet: every exposure the role grants, with slug,
kind and description, a datapoint’s unit and decimals, and the parameter
JSON Schema built from the configuration’s own rules —
min_value/max_value/enum/regex, as
minimum/maximum/enum/pattern. An exposure with no description is
listed with description: null rather than hidden.
A Fleetless user is additionally offered one tool per console operation, each calling the service the console’s own route calls, so it audits as that person. An app user is offered none.
| Family | Tools | What for |
|---|---|---|
| Robots | 13 |
Create, rename, details, delete with a preview, exposures, asset sync, token rotate, joint-state mapping, clearing the store |
| Configuration | 8 |
The draft, publish, versions and restore, slug usage and renames |
| Introspection | 4 |
The cached ROS graph, message types, and refreshing both |
| Apps | 6 |
List, read, create, update; the auth configuration |
| Roles | 5 |
Roles, their permissions, the per-role MCP preview |
| Server keys | 4 |
List, create, rotate, revoke |
| Team | 4 |
The organisation’s Fleetless users |
| Team invitations | 4 |
List, invite, re-issue, revoke |
| App users | 8 |
List, invite, invitations, block, unblock, reset mail, two-factor reset |
| Org | 4 |
The organisation, its quotas, its metered usage |
| Audit | 1 |
The organisation’s audit events |
| Fleet health | 6 |
Alerts, job runs, bridge latency, resource health |
console_config_draft_put replaces the whole fleetless.yaml; there are no
targeted edits, on purpose.
Secrets come back once. console_robot_create and
console_robot_token_rotate return the bridge token,
console_server_key_create and console_server_key_rotate the key. No
tool reads one back, so a lost secret is replaced by rotating.
Writing a description a model can use
A description lives in the robot’s own fleetless.yaml under description: —
one per datapoint, service, action, publisher and camera, plus one per
parameter. It is the same text for every app that reaches that robot, so write
for the least specialized audience.
The model has never seen your robot. Say what the value or action physically
means, what it costs in side effects and irreversibility, and the preconditions
an operator knows and a model does not. A datapoint’s unit, a parameter’s
min_value/max_value/enum/regex and the kind travel as fields of their
own.
The concrete sentence is the perishable one. "Drives to the dock at 0.4
m/s" is actionable today and a lie the moment someone retunes the docking
speed, with nothing to notice. Say what a call does and what it costs, and
let the datasheet carry the numbers: they are read from the configuration and
cannot drift from it.
For the person pasting the URL
Add the endpoint as a remote MCP server. Most tools ask for a URL and nothing else; discovery, registration and the token exchange follow on their own. Sign in, and the tool list appears.
The URL is not a secret, and a tool being in the list is not a promise that it works: the refusals are where a role makes itself felt.
Standing approvals at an app’s endpoint are yours to take back from inside the app that issued them, and the developer can take them back too — see MCP consent.