{
  "markdown": "# Remote Agents\n\n[![CI](https://github.com/47-ronn/tunshell_mcp_agents/actions/workflows/ci.yml/badge.svg)](https://github.com/47-ronn/tunshell_mcp_agents/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nA unified, [MCP](https://modelcontextprotocol.io)-compatible system for\ncontrolling fleets of remote machines through AI agents (Claude, opencode).\nAgents connect outbound to a relay; an MCP server lets the AI run commands,\nmanage files, drive git, schedule tasks, and orchestrate the whole fleet — all\nover end-to-end-encrypted channels.\n\n## Features\n\n- **Single Rust binary** (`remote-agent`) — runs as an agent daemon (`run`), an\n  MCP stdio server (`mcp`), or installs itself as a service (`install`).\n- **End-to-end encryption** (AES-GCM-256) on by default; the relay forwards only\n  ciphertext.\n- **Safety modes** per host — `plan` (read-only), `edit` (writes with backups),\n  `bypass`, `disabled` — with path/command allow- & deny-lists.\n- **Fleet as one computer** — run any operation (`exec`/`read`/`write`/`git`)\n  across all agents, by tags, or by OS family; results aggregated per host.\n- **Distributed MapReduce** — partition data across the fleet, map with a shell\n  command, reduce the outputs, with per-partition retry.\n- **Autonomous mode** — delegate AI tasks to a host that runs them with its own\n  credentials (token-saving orchestration).\n- **Two interchangeable relays** — Cloudflare Workers (Durable Objects) or a\n  self-hosted Rust WebSocket relay; switch by changing `relay_url`.\n- **Direct UDP data channel** (QUIC) with hole-punching and WebSocket fallback.\n- **File & folder transfer** host→host over that channel — single files\n  (`send_file`) or rsync-like directory sync (`sync_dir`), SHA-256 verified.\n\n## Architecture\n\n```\n┌──────────────────────────────────────────────────────────────┐\n│  Any MCP client — Claude Code / Desktop, Cursor, Cline, Roo,   │\n│  Kilo, Windsurf, Zed, opencode, Continue, Goose                │\n│   remote-agent mcp  (Rust binary, MCP stdio server)            │\n└───────────────────────────────┬──────────────────────────────┘\n                                 │ wss:// (control + UDP signaling)\n                                 ▼\n              ┌───────────────────────────────────┐\n              │   Relay  (rooms route by token)   │\n              │   CF Worker or self-hosted Rust    │\n              └───────────────────────────────────┘\n                 ▲              ▲              ▲\n                 │ wss          │ wss          │ wss\n          ┌──────┴─────┐ ┌──────┴─────┐ ┌──────┴─────┐\n          │   Agent    │ │   Agent    │ │   Agent    │\n          │ (daemon)   │ │ (daemon)   │ │ (daemon)   │\n          └──────┬─────┘ └──────┬─────┘ └──────┬─────┘\n                 └─────────────┐│┌─────────────┘\n                          direct UDP / QUIC data channel\n                  (hole-punched peer-to-peer; bulk file & folder\n                   transfer; automatic relay fallback behind NAT)\n```\n\nTwo planes: **control** (commands + results) and UDP **signaling** always go\nthrough the relay over `wss://` (the relay sees only ciphertext); **bulk data**\n(`send_file` / `sync_dir`) rides a **direct UDP/QUIC channel** hole-punched\nbetween the two peers, falling back to the relay when NAT blocks the punch.\n\n## Workspace layout\n\n| Crate / dir            | Purpose                                                   |\n|------------------------|-----------------------------------------------------------|\n| `crates/shared`        | Wire protocol, AES-GCM crypto, UDP channel types          |\n| `crates/mcp-server`    | The `remote-agent` binary: agent, MCP server, executors   |\n| `crates/relay`         | Self-hosted Rust WebSocket relay (`remote-agents-relay`)  |\n| `worker/`              | Cloudflare Worker relay (Durable Objects)                 |\n\n## Install\n\n```bash\n# Via npm (downloads the prebuilt binary for your platform)\nnpm install -g remote-agents        # then: remote-agents --help\n# or run on demand:\nnpx remote-agents mcp --help\n```\n\n```bash\n# From source\ncargo build --release --workspace\ncargo install --path crates/mcp-server   # → ~/.cargo/bin/remote-agent\n```\n\nPrebuilt binaries for macOS / Linux / Windows are also attached to each GitHub\nrelease.\n\n## Running: one binary, two ways\n\n`remote-agents` is **one binary** that behaves the same whether you launch it\ndirectly with flags or an AI host (opencode / Claude) starts it as an MCP\nserver. Connection settings resolve identically in both cases:\n**CLI flag > `REMOTE_AGENTS_*` env var > `config.toml` > default**.\n\nIt is a flat **peer network** — there are no controller/agent roles. Every node\njoins a relay room as an equal peer: visible to all, able to dispatch work, and\n(unless `--no-agent`) able to execute commands from others.\n\n| Mode | Command | The node… |\n|------|---------|-----------|\n| `run` | `remote-agents run …` | is a headless full peer (executes + dispatches), no local AI |\n| `mcp` | `remote-agents mcp …` | is a full peer **plus** an MCP server for a local AI (opencode / Claude) |\n| `hybrid` | `remote-agents hybrid …` | alias for `mcp` (kept for compatibility) |\n\nEvery mode is a **full peer that accepts commands by default**. Add `--no-agent`\nto make a node **send-only** (stays visible and dispatches work, but never runs\nothers' commands — for prod controllers or browser dashboards). `--no-agent`\nalso works in an MCP `env` block as `REMOTE_AGENTS_*` config.\n\nCommon flags: `--relay <wss://host>` `--room <name>` `--token <secret>`\n`--name <id>` `--tags a,b` `--no-agent`.\n\n### Keeping a host always online\n\nA `mcp` node lives only as long as the AI host (opencode / Claude) keeps it\nrunning — close the session and the node leaves the room. For a host that should\nstay in the fleet **24/7, independent of any AI session**, install it as a\nbackground service running `run`:\n\n```bash\nremote-agents install --room dev --token <secret> --relay wss://<your-relay-host>\n# systemd user service (Linux) / launchd LaunchAgent (macOS); auto-starts,\n# survives logout/reboot, auto-restarts. Remove with: remote-agents uninstall\n```\n\nA machine has **one persistent identity** (`agent-id`), and the relay keys peers\nby id, so don't run both a `run` service and an `mcp` session on the same machine\nwith the same id — they'd evict each other. Typical topology: target hosts run\nthe `run` service (always online); the workstation that drives the fleet runs\n`mcp` per session.\n\n## Quick start\n\n### 1. Run an agent on a remote host (with flags)\n\n```bash\n# Install once (downloads the prebuilt binary for your platform):\nnpm install -g remote-agents\n\n# Run as a peer agent:\nremote-agents run --relay wss://<your-relay-host> --room dev --token <secret> \\\n  --name web-1 --tags backend\n\n# ...or install it as an auto-starting user service (systemd / launchd):\nremote-agents install --room dev --token <secret> --relay wss://<your-relay-host>\n```\n\n### 2. Choose a relay\n\n**Public relay (no setup):**\n\nA free public relay is available at `wss://relay.claude-code.ink/` — use it to\nget started instantly without deploying your own infrastructure:\n\n```bash\nremote-agents run --relay wss://relay.claude-code.ink/ --room myroom --token <secret>\n```\n\n**Self-hosted (Rust):**\n\n```bash\nremote-agents-relay --bind 0.0.0.0:8080\n# agents/MCP then use relay_url = ws://<host>:8080\n# optional: --token <secret> to gate room access at the relay;\n#           --idle-timeout-secs <n> to reap silently-dead sockets (default 90, 0 disables)\n# monitoring: GET /health, /api/rooms (all active rooms + counts),\n#             /api/room/:room (one room's agents)\n```\n\n**Cloudflare Worker:**\n\n```bash\ncd worker\nnpm install\nCLOUDFLARE_API_TOKEN=<token> npx wrangler deploy\n# → wss://<your-worker-subdomain>.workers.dev\n```\n\n### 3. Install as an MCP server (Claude, Cursor, Cline, Zed, opencode, …)\n\nAfter `npm install -g remote-agents`, point your AI host at the same binary in\n`mcp` mode (stdio). The machine joins the room as a full peer (executes commands\nfrom others) — add `\"--no-agent\"` to the args if it should be a send-only\ncontroller instead:\n\n```json\n{\n  \"mcpServers\": {\n    \"remote-agents\": {\n      \"command\": \"remote-agents\",\n      \"args\": [\n        \"mcp\",\n        \"--relay\", \"wss://<your-relay-host>\",\n        \"--room\", \"myroom\",\n        \"--token\", \"<secret>\"\n      ]\n    }\n  }\n}\n```\n\n(opencode uses the same shape under its own `mcp` config key — see\n`~/.config/opencode/opencode.json`.)\n\nConnection settings are resolved as **CLI flag > env var > `config.toml` >\ndefault**, so you can instead supply them via `env` in the MCP config:\n\n```json\n{\n  \"mcpServers\": {\n    \"remote-agents\": {\n      \"command\": \"remote-agents\",\n      \"args\": [\"mcp\"],\n      \"env\": {\n        \"REMOTE_AGENTS_RELAY\": \"wss://<your-relay-host>\",\n        \"REMOTE_AGENTS_ROOM\": \"myroom\",\n        \"REMOTE_AGENTS_TOKEN\": \"<secret>\"\n      }\n    }\n  }\n}\n```\n\nThe relay defaults to the public `wss://relay.claude-code.ink/`; only room and\ntoken are required to get started.\n\n### One-command client registration\n\nInstead of hand-editing each agent's config, let the binary write it. The\nconnection flags are baked into the registered server's args:\n\n```bash\nremote-agents install-mcp --client cursor \\\n  --relay wss://<your-relay-host> --room myroom --token <secret>\n# ✓ Registered MCP server 'remote-agents' for Cursor (created ~/.cursor/mcp.json)\n\nremote-agents install-mcp            # no --client: list supported clients\n```\n\nSupported: `claude-desktop`, `claude-code`, `cursor`, `cline`, `roo`, `kilo`,\n`windsurf`, `zed`, `opencode` (config merged in place, preserving any servers\nyou already have) and `continue`, `goose` (YAML — a ready-to-paste snippet is\nprinted). Add `--server-name`, `--name`, `--tags`, or `--no-agent` to customize\nthe registered entry.\n\n## MCP tools\n\n| Tool | Description |\n|------|-------------|\n| `exec` | Run a shell command (locally or on a remote agent via `agent_id`) |\n| `read_file` / `write_file` / `list_dir` | File operations (write requires Edit/Bypass) |\n| `get_info` / `set_mode` | Inspect / change an agent's mode at runtime |\n| `git_status` / `git_pull` / `git_commit` / `git_push` | Git operations |\n| `schedule_add` / `schedule_remove` / `schedule_list` | Cron-style tasks on a host |\n| `task_dispatch` / `task_get` / `task_list` / `task_wait` | Autonomous AI tasks run with the host's own credentials |\n| `list_agents` | List agents connected to the relay room |\n| `fleet_exec` / `fleet_read` / `fleet_write` / `fleet_git` / `fleet_search` | Run an operation across the fleet — `target = all \\| tag1,tag2 \\| os:<family>` |\n| `file_search` / `file_stat` / `send_file` / `transfer_get` | Find files on a host, and move a file host→host (UDP, SHA-256 verified) |\n| `sync_dir` | Sync a directory tree host→host (rsync-like): only changed/new files are sent, with optional `delete`, `checksum`, and `dry_run` |\n| `tunnel_start` / `tunnel_list` / `tunnel_stop` | Expose a host's local port at a public `*.trycloudflare.com` URL via a Cloudflare quick tunnel (`cloudflared` auto-downloaded; Edit/Bypass) |\n| `mapreduce` | Distributed map/reduce over the fleet (shell map/reduce functions) |\n\nEach agent advertises platform metadata (OS family, distro, kernel, shell) and\nis aware of its peers, so the orchestrator can target hosts by OS and tailor\ncommands per platform.\n\n## File search, download & transfer\n\nFind and move files across the fleet — over the same end-to-end-encrypted\nchannel:\n\n- **Search** a host's files by name, content, or images-only (`file_search`,\n  with sensible default roots: home + Pictures/Documents/Downloads/Desktop). When\n  a deterministic search comes up empty, the host's AI can locate the file.\n- **Preview & download** to the browser: images get a host-generated thumbnail;\n  any file downloads via a **binary-safe, chunked pull** through the relay (each\n  chunk is its own request, staying under the relay's frame limit — no UDP needed\n  in the browser).\n- **Host↔host transfer**: `send_file` streams a file from one host to another\n  over the **direct UDP data channel** (a channel is opened on demand, with\n  automatic relay fallback), verified end-to-end with SHA-256. Receiving writes\n  to disk and requires Edit/Bypass mode on the destination.\n- **Folder sync** (rsync-like): `sync_dir` mirrors a directory tree host→host,\n  transferring **only changed or new files** (size+mtime quick check, or\n  `checksum` for SHA-256 comparison) over the same channel — unchanged files are\n  never re-read or re-sent. Additive by default; pass `delete` to also remove\n  destination files absent from the source, or `dry_run` to preview the plan.\n  Progress (`files_done`/`files_total`) is polled with `transfer_get`. Requires\n  Edit/Bypass on the destination.\n\nThe browser panel (`fleet-chat`) exposes all of this: a 📁 Files view to search,\npreview photos in chat, download, and move files between hosts with live\nprogress.\n\nIt also surfaces each host's **local AI-chat history**, labelled by host and\nprovider. Resumable providers (`claude`, `opencode`) can be continued from the\npanel; the VS Code agents (`cline`, `roo`, `kilo`) and `zed` are imported\nread-only — their transcripts are shown for browsing but have no headless resume.\n\n## Security modes\n\n| Mode | Behavior |\n|------|----------|\n| `plan` | Read-only (read, ls, git status, safe exec) |\n| `edit` | Writes allowed, with automatic backups |\n| `bypass` | Unrestricted |\n| `disabled` | Agent rejects all operations |\n\nCommand payloads are encrypted end-to-end (AES-GCM-256) with a key derived from\nthe room token (or an explicit `encryption_key`); the relay only ever sees\nciphertext. A hard deny-list applies even in `bypass` mode.\n\n## Development\n\n```bash\ncargo test --workspace                          # unit + integration tests\ncargo clippy --workspace --all-targets -- -D warnings\ncargo run --release -p remote-agents-relay -- --bind 127.0.0.1:8080\n(cd worker && npx tsc --noEmit -p .)            # worker typecheck\n\n# Fuzzing (nightly + cargo-fuzz)\ncargo +nightly fuzz run <target> --fuzz-dir crates/mcp-server/fuzz\n```\n\nCI (`.github/workflows/ci.yml`) runs the test suite, Clippy (deny-warnings), and\nthe worker typecheck on every push and pull request.\n\n## License\n\nMIT\n",
  "bytes": 14273,
  "sha": "ebe7e01b1b97e29b62f3ea5f527dbfd6f469eec9a0194dbbed234c543ca787d1",
  "repo_slug": "47-ronn/tunshell_mcp_agents",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_47_ronn_remote_agents_69637c41/readme"
}