Skip to content

FR: First-class Linux support (Rust rewrite) — optimize for cheap scalable Linux nodes #15

Description

@chaodu-agent

Summary

Add first-class Linux support for instance-mcp. Linux should be treated as a first-class citizen (arguably the primary target): cheap Linux mini PCs / VMs are easy to acquire and scale horizontally, whereas Mac minis are expensive and fewer. We should optimize for Linux deployment.

Motivation

  • Scale-out: spin up many inexpensive Linux nodes (mini PCs, VMs, Raspberry Pi) as instance-mcp endpoints.
  • Cost: Mac mini is expensive; Linux boxes are cheap and disposable.
  • Deployment: prefer a single static binary, trivial cross-compile (x86_64 + arm64), minimal/no runtime deps, container/VM friendly.

Proposed approach: rewrite the Linux target in Rust

Rationale (Linux-first strategy):

  • Deployment: single static binary, scp-and-run, zero runtime deps; small container images (distroless/scratch).
  • Cross-compile: cargo + cross gives clean x86_64 and arm64 builds.
  • Linux systems ecosystem: mature crates for the platform-specific bits — uinput (evdev/input-linux), Wayland (wayland-rs/smithay), /proc (sysinfo), HTTP (axum/hyper), JSON-RPC (serde_json). MCP has Rust references.
  • Long term, Rust can also target macOS (objc2/FFI for CGEvent), giving a path to unify both platforms on one codebase.

Tradeoff acknowledged: this means rewriting ~3,073 lines and, in the interim, maintaining two codebases (macOS Swift + Linux Rust) with behavior kept in sync. Justified because Linux is the strategic primary target.

Current macOS implementation analysis (Swift)

  • ~3,073 lines, Swift package, platforms: .macOS(.v14). Linked frameworks: ScreenCaptureKit, CoreGraphics, Network.
  • Platform-agnostic (~62%) — pure Foundation, portable in spirit: MCPServer, MCPHTTPEndpoint, JSONRPC, HTTP, AuthPolicy, ToolProfile, UpstreamMCP, ReverseAttachClient, AttachManager, ExecTool, AsyncExecTools, ExecSupport, JobRegistry.
  • Platform-specific (~640 lines, 5 files) needing Linux backends:
    • MouseTool / KeyTool / Input — CGEvent x27 -> Linux: uinput (ydotool or /dev/uinput)
    • ScreenshotTool — ScreenCaptureKit/CGImage -> Linux Wayland: grim
    • SysInfoTool — CGDisplay/sysctl/TCC -> /proc + wlr-randr; permission model differs
    • OsascriptTool — AppleScript -> drop on Linux (no equivalent)
    • Network.framework transport -> SwiftNIO/axum/hyper

Target environment verified (rpi1, Raspberry Pi 5)

  • arch: aarch64; OS: Debian 13 (trixie); 4 cores, 8 GB.
  • Display: labwc (Wayland) + lightdm. (SSH session is tty.)
  • Screenshot tooling already present: grim, wlr-randr, scrot.
  • /dev/uinput exists (root-only; needs a udev rule for non-root access).
  • ydotool/xdotool: not installed. Swift: not installed. gcc/make present; docker/podman: no.
    Implication: most platform-specific features can shell out to existing CLIs (grim for screenshots, ydotool for input), which reduces the need for deep language bindings and de-risks the rewrite.

Scope / phased plan (proposal)

  1. Rust MCP skeleton: HTTP transport + JSON-RPC + initialize + tool dispatch (axum + serde_json).
  2. sys_info (read /proc, /sys, wlr-randr) — no side effects.
  3. screenshot via grim (Wayland) with scrot fallback.
  4. exec / async exec / job registry parity.
  5. Input synthesis (mouse/key) via ydotool + /dev/uinput (udev permission rule).
  6. Auth policy + reverse-attach parity.
  7. arm64 + x86_64 CI, static binary packaging.

Effort estimate (rpi1-specific)

  • Rust MCP PoC (handshake + sys_info + screenshot via grim): ~0.5–1 day.
  • Minimal usable (headless: exec + sys_info + screenshot + MCP proxy): ~3–5 days.
    • input synthesis (ydotool/uinput + udev): +2–3 days.
  • Full parity + tests + multi-arch CI: ~2 weeks.

Open questions

  • Confirm Rust (this proposal) vs Swift-for-Linux (reuse ~62% but weaker Linux ecosystem, heavier deploy).
  • Headless vs GUI scope per node type.
  • Whether to eventually unify macOS onto the Rust codebase.

Architecture

Layered design: a platform-agnostic MCP core (transport, JSON-RPC, dispatch, auth, exec, jobs) with a thin platform backend behind a trait/protocol. macOS uses CoreGraphics/ScreenCaptureKit; Linux shells out to grim/ydotool and reads /proc.

flowchart TB
  subgraph client["openab-pty agent (MCP client)"]
    A[MCP over HTTP + JSON-RPC]
  end

  subgraph core["Platform-agnostic core (~62% reusable logic)"]
    T[HTTP/1.1 transport]
    J[JSON-RPC / MCP dispatch]
    AU[Auth policy + reverse-attach]
    EX[exec / async-exec / job registry]
    UP[upstream MCP proxy]
    PB{{PlatformBackend abstraction}}
  end

  subgraph macos["macOS backend (Swift, existing)"]
    M1[Input: CGEvent]
    M2[Screenshot: ScreenCaptureKit]
    M3[SysInfo: CGDisplay / sysctl / TCC]
    M4[osascript: AppleScript]
  end

  subgraph linux["Linux backend (Rust, target)"]
    L1[Input: ydotool / dev-uinput]
    L2[Screenshot: grim on Wayland]
    L3[SysInfo: /proc, /sys, wlr-randr]
    L4[osascript dropped]
  end

  A -->|HTTP| T --> J --> PB
  J --- AU
  J --- EX
  J --- UP
  PB -.selected at build time.-> macos
  PB -.selected at build time.-> linux

  linux --> N1[cheap x86_64 mini PC / VM]
  linux --> N2[arm64 Raspberry Pi 5]
Loading

Deployment target: single static binary per arch (x86_64 + arm64), scp-and-run, minimal deps.

Strategy / Roadmap (decided)

Pragmatic middle path — build Rust Linux first, designed cross-platform from day one, then grow into a single unified Rust codebase; keep Swift alive only as a transitional macOS backend until the Rust macOS backend is mature.

  1. Phase 1 — Rust Linux, cross-platform by design. Write the Rust MCP core (transport, JSON-RPC, dispatch, auth, exec, jobs) plus a solid Linux backend. From the start, put all platform-specific behavior behind a PlatformBackend trait selected via #[cfg(target_os)], so the core never assumes an OS.
  2. Phase 2 — add the macOS backend (FFI). Implement the macOS PlatformBackend in the same Rust codebase using objc2 / core-graphics FFI (CGEvent for input, ScreenCaptureKit for capture, sysctl/TCC for sysinfo). This evolves naturally into a single unified Rust codebase (route 2).
  3. Phase 3 — retire Swift. The existing Swift macOS implementation keeps serving in production during Phase 1–2. Once the Rust macOS backend reaches parity and is validated, retire the Swift version.

Why this path:

  • Avoids throwing away the working Swift build up front.
  • Avoids the permanent two-codebase sync trap — there is one Rust core; only the thin platform backend forks by #[cfg].
  • Linux-first (the strategic primary target) ships soonest; macOS converges later without a rewrite of the core.
flowchart TB
  subgraph client["openab-pty agent (MCP client)"]
    A[MCP over HTTP + JSON-RPC]
  end

  subgraph rustcore["Single Rust codebase"]
    T[HTTP transport]
    J[JSON-RPC / MCP dispatch]
    AU[Auth policy + reverse-attach]
    EX[exec / async-exec / job registry]
    UP[upstream MCP proxy]
    PB{{PlatformBackend trait -cfg target_os-}}
  end

  subgraph linux["Linux backend -Phase 1, Rust-"]
    L1[Input: ydotool / dev-uinput]
    L2[Screenshot: grim on Wayland]
    L3[SysInfo: /proc, /sys, wlr-randr]
  end

  subgraph macrust["macOS backend -Phase 2, Rust FFI-"]
    R1[Input: core-graphics CGEvent]
    R2[Screenshot: objc2 ScreenCaptureKit]
    R3[SysInfo: sysctl / TCC]
  end

  subgraph swift["Swift macOS -existing, transitional-"]
    S1[serves until Rust macOS parity, then retired]
  end

  A -->|HTTP| T --> J --> PB
  J --- AU
  J --- EX
  J --- UP
  PB -.cfg linux.-> linux
  PB -.cfg macos, Phase 2.-> macrust
  swift -.replaced by.-> macrust

  linux --> N1[cheap x86_64 mini PC / VM]
  linux --> N2[arm64 Raspberry Pi 5]
Loading

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions