rc-mcp

module
v0.0.0-...-b8fbad1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 31, 2026 License: MIT

README

rc-mcp

A personal fleet remote-control system built on the Model Context Protocol (MCP). It lets you inspect and control your own Linux desktop machines from any MCP-capable LLM client — Claude Desktop, Claude Code, or any other spec-compliant MCP host.

You are not a multi-tenant service here: one operator, their own machines. There's no user management or RBAC — the security model is about keeping your LLM client from doing anything you didn't approve, not about isolating users from each other.

How it fits together

 MCP client (Claude Desktop, Claude Code, ...)
          │  MCP over Streamable HTTP (bearer token)
          ▼
   rc-mcp-server  ──────────────  admin API + web UI (loopback only)
          │  WebSocket (device token, outbound from the agent)
          ▼
   rc-mcp-agent  (runs on each controlled machine)
          │
          ▼
   shell, filesystem, processes, screenshots, sysinfo, input
  • rc-mcp-server is a relay hub. It speaks MCP to LLM clients and a WebSocket wire protocol to agents, and routes tool calls between them. It never executes any tool logic itself.
  • rc-mcp-agent runs on each machine you want to control. It dials out to the server (no inbound port needed on the desktop — NAT/firewall friendly) and does the actual work: running commands, reading files, taking screenshots, and so on.
  • Every capability an agent exposes is opt-in and configured per-agent, so a laptop you rarely touch can run with just sysinfo, while a workstation can run with everything enabled.

The full protocol and architecture design lives in docs/specs/backend.md; this README is the practical "how do I run this" guide.

What an LLM client can do through it

Capability Tools
Shell shell_exec (one-shot), shell_session_start/write/close (interactive PTY sessions)
Filesystem fs_read, fs_write, fs_list, fs_delete, fs_stat
Processes process_list, process_info, process_signal
Screenshots screenshot_capture, screenshot_watch (periodic, streamed) — X11 and Wayland
System info sysinfo_get
Input injection input_key, input_mouse_click, input_mouse_move, input_type — off by default, every call requires confirmation with no bypass

Plus MCP resources (clients://list, job://{id}, sysinfo://.../overview, audit://log, shell://sessions), three guided prompts (diagnose_system, safe_cleanup, shell_workflow), and argument completion for device IDs and file paths.

Anything that mutates state (running a command, writing a file, killing a process) requires the client to confirm via MCP elicitation before it's dispatched — the agent never receives a destructive call the operator hasn't explicitly approved in that moment. Every call is recorded in an append-only audit log.

Quick start

You'll need Go 1.25+ and, for the container path, Docker.

1. Run the server
export AUTH_TOKEN=$(openssl rand -hex 64)          # required — the server won't start without it
export RC_AUDIT_LOG_PATH=./rc-mcp-audit.log         # production default (/var/log/rc-mcp/...) needs root
export DEVICE_REGISTRY_PATH=./rc-mcp-devices.json   # production default (/var/lib/rc-mcp/...) needs root
go run ./cmd/server

This serves plain ws:// (no TLS) on 0.0.0.0:8080 — fine for same-host or same-LAN testing, but see Docker Compose below for a real deployment.

Or via Docker Compose, which additionally fronts the server with nginx over TLS (see docker-compose.yml and .env.example for the full settings list):

cp .env.example .env   # fill in AUTH_TOKEN at minimum
# nginx needs a TLS cert; for local dev, a self-signed one is enough
# (see docker/nginx/certs/README.md for the one-liner and the real-CA note)
docker compose up

MCP clients and agents connect through nginx on :443; the admin API stays on 127.0.0.1:9090, loopback only by design (see Security) and never proxied.

2. Pair a machine

On the machine you want to control (use wss://your-server-host/agent/ws instead if you're going through the Docker Compose/nginx TLS path above):

export AGENT_SERVER_URL=ws://127.0.0.1:8080/agent/ws
go run ./cmd/agent

First run has no device token yet, so the agent prints a pairing code (a fresh one each run — don't reuse an old one) and waits:

Pairing code: ABCD-1234
Expires at:   2026-01-01T00:05:00Z
Approve on the server with: curl -X POST http://127.0.0.1:9090/admin/approve -d '{"code":"ABCD-1234"}'

Run that curl command (with your printed code, not the example above) from the server host:

curl -X POST http://127.0.0.1:9090/admin/approve -d '{"code":"ABCD-1234"}'

or open http://127.0.0.1:9090/ in a browser for the same thing with a UI — pending codes, paired devices with revoke, and the audit log. Once approved, the agent saves its device token locally and reconnects on its own from then on, including after the server or machine restarts.

3. Point an MCP client at it

Configure your MCP client (Claude Desktop, Claude Code, etc.) with the server's /mcp endpoint and the AUTH_TOKEN bearer token, e.g. in claude_desktop_config.json or a project's .mcp.json (see docs/examples/mcp-config.json):

{
  "mcpServers": {
    "rc-mcp": {
      "type": "http",
      "url": "https://your-server-host/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_AUTH_TOKEN"
      }
    }
  }
}

or with the Claude Code CLI:

claude mcp add --transport http rc-mcp https://your-server-host/mcp \
  --header "Authorization: Bearer YOUR_AUTH_TOKEN"

The client will see whichever tools your paired agents have capabilities enabled for.

Configuration

Every setting is an environment variable — see .env.example for the full list with defaults, and docs/specs/backend.md Section 15 for the authoritative descriptions. A few worth knowing up front:

  • AGENT_CAPABILITIES (agent side) — comma-separated list of what an agent exposes: shell,screenshot,filesystem,process,sysinfo by default; input is available but never on by default.
  • RC_SHELL_SKIP_CONFIRM, RC_FS_SKIP_CONFIRM, RC_PROCESS_SKIP_CONFIRM (server side) — skip the confirmation prompt for that tool group. Input injection has no such flag; it always confirms.
  • RC_SHELL_ALLOWLIST / RC_SHELL_DENYLIST — regex patterns (one per line) a shell command must pass before it's ever dispatched.
  • RC_GLOBAL_FS_ALLOWED_ROOTS / AGENT_FS_ALLOWED_ROOTS — restrict which absolute paths filesystem tools can touch, enforced server-side and agent-side respectively.
  • MCP_SESSION_STORE=redis + REDIS_ADDR — switch from the single-instance in-memory/file-backed defaults to a Redis-backed session store, device registry, and cross-replica dispatch routing, for running more than one server replica. See docs/operations/scaling.md.
  • AGENT_AUTO_UPDATE=true — let an agent download, checksum-verify, and install a newer build the server advertises. Off by default. See docs/operations/agent-releases.md.

Security

A few things are load-bearing, not incidental:

  • The LLM can never pair, approve, or revoke a device. Pairing approval only exists on the admin API/web UI, which only listens on loopback — there is no code path from the MCP surface to device management.
  • Destructive actions require live confirmation via MCP elicitation, shown to the human at the client, not assumed from a prior approval.
  • Every tool call is audited, append-only, with the session, target device, tool, and outcome — SHA-256 digested by default, or in full under RC_AUDIT_FULL_ARGS=true for forensic use.
  • Agents connect outbound only. No inbound port on the controlled machine, and each connection re-authenticates with a per-device token the server can revoke at any time.

Found a vulnerability? See SECURITY.md for how to report it privately.

Development

go build ./...
go test ./...

This is a plain Go module — no Nx, no JS/TS runtime code. CI runs go build, go vet/golangci-lint, and go test directly. See docs/adr/0001-nx-go-integration.md for the history of why Nx was tried and later dropped.

Docs

License

MIT

Directories

Path Synopsis
agent
client
Package client implements the desktop agent's WebSocket connection to rc-mcp-server: dialing with exponential backoff, the hello/hello_ack re-authentication handshake on reconnect, and a keepalive heartbeat.
Package client implements the desktop agent's WebSocket connection to rc-mcp-server: dialing with exponential backoff, the hello/hello_ack re-authentication handshake on reconnect, and a keepalive heartbeat.
executor
This file implements the fs_read/fs_write/fs_list/fs_delete/fs_stat executors.
This file implements the fs_read/fs_write/fs_list/fs_delete/fs_stat executors.
updater
Package updater implements the agent's opt-in auto-update mechanism (docs/specs/backend.md Section 19, phase-3-post-mvp Risks "unsigned binary replacement"): on connect, if the server advertises a different version than the running binary and AGENT_AUTO_UPDATE=true, download the new binary from the release pipeline's endpoint (docs/operations/ agent-releases.md), verify it against its published checksum, and only on success replace the running binary and restart.
Package updater implements the agent's opt-in auto-update mechanism (docs/specs/backend.md Section 19, phase-3-post-mvp Risks "unsigned binary replacement"): on connect, if the server advertises a different version than the running binary and AGENT_AUTO_UPDATE=true, download the new binary from the release pipeline's endpoint (docs/operations/ agent-releases.md), verify it against its published checksum, and only on success replace the running binary and restart.
cmd
agent command
Command rc-mcp-agent is the desktop agent entry point: it loads (or obtains via first-run pairing) a persistent device token, then runs the dial/connect/pair/reconnect lifecycle against rc-mcp-server.
Command rc-mcp-agent is the desktop agent entry point: it loads (or obtains via first-run pairing) a persistent device token, then runs the dial/connect/pair/reconnect lifecycle against rc-mcp-server.
server command
Command rc-mcp-server is the relay-hub server entry point: it starts the agent WebSocket / health HTTP listener, the localhost-only admin API, wires the device registry through both, and handles graceful shutdown.
Command rc-mcp-server is the relay-hub server entry point: it starts the agent WebSocket / health HTTP listener, the localhost-only admin API, wires the device registry through both, and handles graceful shutdown.
internal
admin
Package admin implements the localhost-only admin API used to approve or reject agent pairing codes.
Package admin implements the localhost-only admin API used to approve or reject agent pairing codes.
agent
Package agent implements the server side of the desktop-agent WebSocket connection lifecycle: accepting upgrades on GET /agent/ws, running the hello/pairing handshake, and tracking online/offline devices.
Package agent implements the server side of the desktop-agent WebSocket connection lifecycle: accepting upgrades on GET /agent/ws, running the hello/pairing handshake, and tracking online/offline devices.
audit
Package audit implements the server-side, append-only audit log: the authoritative, tamper-resistant record of every tool invocation.
Package audit implements the server-side, append-only audit log: the authoritative, tamper-resistant record of every tool invocation.
auth
Package auth provides constant-time bearer token comparison primitives shared by every auth boundary in rc-mcp-server.
Package auth provides constant-time bearer token comparison primitives shared by every auth boundary in rc-mcp-server.
devices
Package devices implements the device registry: the durable record of every paired desktop agent, plus the pairing-code lifecycle used to enroll new devices.
Package devices implements the device registry: the durable record of every paired desktop agent, plus the pairing-code lifecycle used to enroll new devices.
fsroot
Package fsroot implements the server-side global filesystem root policy (RC_GLOBAL_FS_ALLOWED_ROOTS, docs/specs/backend.md Section 12.6): a coarse, server-side check applied before dispatch, in addition to (not instead of) each agent's own AGENT_FS_ALLOWED_ROOTS.
Package fsroot implements the server-side global filesystem root policy (RC_GLOBAL_FS_ALLOWED_ROOTS, docs/specs/backend.md Section 12.6): a coarse, server-side check applied before dispatch, in addition to (not instead of) each agent's own AGENT_FS_ALLOWED_ROOTS.
jobs
Package jobs implements the server-side job store for long-running dispatch pattern (a) operations (e.g.
Package jobs implements the server-side job store for long-running dispatch pattern (a) operations (e.g.
mcp/completions
Package completions implements the "completions" MCP capability from docs/specs/backend.md Section 2: argument auto-completion for tool inputs, most usefully clientId (sourced from the device registry) and filesystem paths (dispatched to the target agent's fs_list).
Package completions implements the "completions" MCP capability from docs/specs/backend.md Section 2: argument auto-completion for tool inputs, most usefully clientId (sourced from the device registry) and filesystem paths (dispatched to the target agent's fs_list).
mcp/prompts
Package prompts implements the three operator-defined prompt templates from docs/specs/backend.md Section 5: diagnose_system, safe_cleanup, and shell_workflow.
Package prompts implements the three operator-defined prompt templates from docs/specs/backend.md Section 5: diagnose_system, safe_cleanup, and shell_workflow.
mcp/resources
Package resources implements the five MCP resources from docs/specs/backend.md Section 4: clients://list, job://{id}, sysinfo://{clientId}/overview, audit://log, and shell://sessions — read-only status surfaces with per-session subscriptions and pushed notifications/resources/updated events.
Package resources implements the five MCP resources from docs/specs/backend.md Section 4: clients://list, job://{id}, sysinfo://{clientId}/overview, audit://log, and shell://sessions — read-only status surfaces with per-session subscriptions and pushed notifications/resources/updated events.
mcp/schema
Package schema implements the subset of JSON Schema the tool input schemas in internal/mcp/tools use, so tools/call arguments can be validated in one shared path before any handler runs (Section 12.6, Section 13 "Invalid params").
Package schema implements the subset of JSON Schema the tool input schemas in internal/mcp/tools use, so tools/call arguments can be validated in one shared path before any handler runs (Section 12.6, Section 13 "Invalid params").
mcp/tools
This file implements the input_key, input_mouse_click, input_mouse_move, and input_type tools: the `input` capability area (docs/specs/backend.md Section 19).
This file implements the input_key, input_mouse_click, input_mouse_move, and input_type tools: the `input` capability area (docs/specs/backend.md Section 19).
mcp/types
Package types holds the MCP tool input/output types shared by the server's tool handlers and (for the fields the agent-side executors also need) the desktop agent.
Package types holds the MCP tool input/output types shared by the server's tool handlers and (for the fields the agent-side executors also need) the desktop agent.
protocol
Package protocol defines the wire protocol shared between rc-mcp-server and rc-mcp-agent: the JSON envelope, binary frame header, and protocol version negotiation.
Package protocol defines the wire protocol shared between rc-mcp-server and rc-mcp-agent: the JSON envelope, binary frame header, and protocol version negotiation.
redisclient
Package redisclient provides the minimal key/value operation set the Redis-backed SessionStore (internal/session) and DeviceRegistry (internal/devices) implementations need, isolating the go-redis dependency to this one package.
Package redisclient provides the minimal key/value operation set the Redis-backed SessionStore (internal/session) and DeviceRegistry (internal/devices) implementations need, isolating the go-redis dependency to this one package.
session
Package session implements MCP session state: the per-Mcp-Session-Id record of negotiated capabilities, the SSE fan-in event channel and replay buffer, active shell session mappings, and pending server-initiated request/response correlation (elicitation).
Package session implements MCP session state: the per-Mcp-Session-Id record of negotiated capabilities, the SSE fan-in event channel and replay buffer, active shell session mappings, and pending server-initiated request/response correlation (elicitation).
shellpolicy
Package shellpolicy implements operator-configurable shell command allowlist/denylist enforcement, evaluated server-side before any dispatch to an agent (docs/specs/backend.md Section 19, phase-3-post-mvp Risks "ReDoS").
Package shellpolicy implements operator-configurable shell command allowlist/denylist enforcement, evaluated server-side before any dispatch to an agent (docs/specs/backend.md Section 19, phase-3-post-mvp Risks "ReDoS").
transport
Package transport implements the MCP-facing Streamable HTTP transport: the single /mcp endpoint (POST/GET/DELETE), its SSE stream, and the bearer-auth / origin-allowlist middleware that gate every request.
Package transport implements the MCP-facing Streamable HTTP transport: the single /mcp endpoint (POST/GET/DELETE), its SSE stream, and the bearer-auth / origin-allowlist middleware that gate every request.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL