You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
/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.
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.
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.
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).
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]
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
Proposed approach: rewrite the Linux target in Rust
Rationale (Linux-first strategy):
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)
Target environment verified (rpi1, Raspberry Pi 5)
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)
initialize+ tool dispatch (axum + serde_json).sys_info(read /proc, /sys, wlr-randr) — no side effects.screenshotvia grim (Wayland) with scrot fallback.exec/ async exec / job registry parity.Effort estimate (rpi1-specific)
Open questions
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]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.
PlatformBackendtrait selected via#[cfg(target_os)], so the core never assumes an OS.PlatformBackendin the same Rust codebase usingobjc2/core-graphicsFFI (CGEvent for input, ScreenCaptureKit for capture, sysctl/TCC for sysinfo). This evolves naturally into a single unified Rust codebase (route 2).Why this path:
#[cfg].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]