Skip to main content
ADOS ships a Model Context Protocol connector so an MCP-capable client (Claude Code, Claude Desktop, Cursor, VS Code, or a plain HTTP client) can read a node, administer it, and, when you explicitly grant it, fly it. Two halves, and they are separate decisions:
  1. The connector is a Node server you run on your own machine, from the ADOS-MCP repo. It is the MCP server your AI client talks to, and a client of the agent’s HTTP API.
  2. The agent-side token edge is the scoped credential an agent will accept in place of the full pairing key. It is off by default and stays inert until you set mcp.token_accept_enabled.
No model runs on the drone. The connector holds no telemetry buffers and runs no inference; it calls interfaces that already exist and presents them through MCP.
Token acceptance ships off. mcp.token_accept_enabled defaults to false, and ados mcp status reports the posture. A token you mint before flipping the flag is valid, but the auth edge refuses it until the flag is on.

The trust boundary

The agent’s auth edge accepts three credentials on the LAN: the pairing key (X-ADOS-Key), a dashboard-PIN session, and, behind the accept flag, a scoped MCP token on the x-ados-mcp-token header. A new agent route is unreachable by token until it is classified, which is the intended direction: an unclassified route is denied rather than silently over-granted.

Scopes

Six scope groups. A token carries one or more; a route carries exactly one required class. flight and destructive are never granted implicitly. On the connector side, a tool whose class is flight (or that otherwise affects flight) is refused outright while the MAVLink proxy enforce flag is off.

Enable the token edge on a node

ados mcp enable writes the config key and restarts the control front so the new posture takes effect. It is a local root operation, not a REST call, so it needs sudo. Never hand-edit /etc/ados/config.yaml for this; the command is the sanctioned path.
Check the posture and the minted tokens at any time:
ados mcp status --json prints the raw /api/mcp/status body, which carries accept_enabled and a tokens array. Each token entry carries token_id, label, scopes, and the revoked / expired flags.

Mint a scoped token

The command prints a “shown once” banner followed by the token on its own line. The secret is never written to the store, so if you lose it you mint a new one. Store it in your MCP client’s environment, not in your shell history. On-box, the mint / status / revoke routes are admitted without a key because loopback is inside the trust boundary; off-box they take the front’s normal auth lane.

Revoke

A single revoke adds the id to the denylist. --all rotates the 128-bit salt folded into every derived token key, so every previously-minted token fails verification at once. That is the incident response: rotate, then re-mint the tokens you still want. See Revocation and incidents for the wider playbook.

Agent routes

Run the connector

The connector ships as a container image, or you can clone and build it. To build from source:
Prerequisites: Node 20 or newer, git, and pnpm. pnpm install && pnpm build is the manual equivalent. Three deployment modes, selected with --target. All three expose the identical tool set; only the reach differs. Add one LAN node to Claude Code, with the key in the client environment rather than your shell:
Check reach without starting a server. --verify connects, checks auth and reachability, prints the result, and exits 0 or 1:

Run with Docker

The connector also ships as a multi-arch image, ghcr.io/altnautica/ados-mcp (linux/amd64, linux/arm64). Tags are X.Y.Z, X.Y and latest for releases, and edge for the main branch. Pin X.Y.Z for a reproducible setup. Create the data folder and pull the image:
Add it to Claude Code. The key stays in the client’s environment; -e NAME forwards it into the container, so it never appears in the docker arguments:
Check reach with a one-shot run:
For a client that reads .mcp.json (the key is read from your environment, so the file holds no secret):
To run it as a long-lived Streamable HTTP service, start the mcp service from a checkout. It listens on 127.0.0.1:8091, mounts ~/.ados/mcp and passes --transport http, which a service needs because auto picks stdio whenever stdin is not a terminal:
Behind your own reverse proxy, run with --trust-proxy --http-host 0.0.0.0 and leave the port unpublished.
The image runs as user id 1000. On Linux, if your user id differs, add --user "$(id -u):$(id -g)" after docker run. Create ~/.ados/mcp before the first run: otherwise Docker creates it owned by root, the audit sink cannot write, and write tools refuse.
Docker limits:
  • LAN auto-discovery and mDNS advertising need --network host, on Linux.
  • .local names may not resolve inside a container. Use the drone’s IP address.
  • --gcs local inside a container points at the container itself. Use --convex-url http://host.docker.internal:3210, and on Linux also add --add-host host.docker.internal:host-gateway.
  • The on-box Unix socket does not apply, so a warning about it is expected.
  • Through Docker Desktop’s port publishing, tokens pinned to a source IP see the Docker gateway address.
  • Never pass -t to a stdio run.
  • cmd.exe does not expand $HOME. Use PowerShell or a POSIX shell. In Git Bash, put MSYS_NO_PATHCONV=1 in front of the command: without it Git Bash rewrites the container side of the -v mount, the server still connects, and the audit trail silently stays inside the container.

Connector flags

Environment equivalents include ADOS_MCP_AGENT_KEY, ADOS_AGENT_HOST, ADOS_NODE_ID, ADOS_MCP_HTTP_PORT, ADOS_MCP_HTTP_HOST, ADOS_MCP_FLEET, ADOS_MCP_FLEET_PATH, ADOS_MCP_DISCOVER, ADOS_MCP_OWNER, ADOS_MCP_TOKEN, ADOS_CONVEX_URL, ADOS_MQTT_URL, ADOS_MCP_AUDIT_PATH and ADOS_MCP_LOG_LEVEL.

Transports

  • Streamable HTTP: one endpoint, POST /mcp, with an SSE upgrade for streams. The primary networked transport.
  • stdio: the local one-liner a client launches. Its principal holds every scope except flight and destructive, passes the scope check like any other token, and declares that a human must be present. Presence on your laptop is not presence on the aircraft, so reaching the flight tools needs a flight-scoped --token.
  • Unix socket: /run/ados/mcp.sock, on-box in agent and local-fleet modes. Presence on that socket is the credential, so this principal skips the scope check. It does not skip the safety gate.

The safety gate

Every principal passes the class-specific safety gate, including the on-box socket principal. Presence on a socket is not a human confirming an action, so an admin call still needs its confirmation and a flight call still needs a fresh operator-present signal or a signed confirm bound to that call. Every call, allowed or denied, produces one redacted audit event. --sim only opts in to the SITL waiver. The server asks the target whether its flight controller is a simulator and refuses to start if it is not, so the waiver cannot be asserted onto real hardware. readOnlyHint and destructiveHint annotations are advertised honestly, but they are hints. The server is the enforcement point.

Tools

The flight.* tools arm, take off, change mode, land and return a real aircraft. Read Safety before you grant a flight scope.
The connector exposes 48 tools at MCP protocol revision 2025-06-18. Every deployment mode exposes the same set. The tools are grouped by prefix:

Vision and navigation tools

All three talk to the drone directly; they are not relayed through a ground station. vision.designate is a flight-class tool: an active follow retargets to the designated box, so it needs the flight scope and passes the safety gate (a confirm_id or simulation). No tool starts a follow; a follow begins only when an operator designates a subject and turns the Follow-Me Skill on. The full list, with each tool’s scope class and annotations, is in catalog/tools-catalog.json. A tool contributed by a plugin is floored at the scope its granted capabilities imply. Every non-read flight-control capability (for example mavlink.write, msp.write, mission.write, estimator.pose.inject and vision.track.designate) floors the tool at flight, and every hardware.* capability floors it at admin. A capability that is neither in the generated floor nor on the read and safe-write allowlist is treated as admin. See Permissions for the capability catalog. The Mission Control MCP tab has a guided wizard with a Docker / From source toggle. It fills in a node’s host and pairing key, generates the claude mcp add command for the runtime you pick, builds the inline local-fleet blob for every node you own, and mints the machine credential for fleet mode.

Extensions that contribute MCP tools

An extension can contribute MCP tools, resources and prompts on both its agent and GCS halves. That contribution class needs schema_version: 3 in manifest.yaml; 1 is the baseline and 2 unlocks the additional agent fields. See the manifest reference.

Next steps

MCP in Mission Control

Use the setup wizard to generate the add command and the fleet blob.

Security model

How credentials, scopes and capabilities fit together.

Revocation and incidents

Rotate and revoke credentials after an incident.

Hardening

Lock a node down before it leaves the bench.
Last modified on November 3, 2026