The Android
client for the SealGate stdio tunnel — a device-side daemon that lets cloud
agents reach MCP (Model Context Protocol) servers running on your phone. It holds
a single outbound WebSocket to the SealGate backend, so there is no inbound
port to open, and your data and logins stay on the device.
Based on the design in Stdio Tunnels: Bridging Cloud Agents to Local MCPs.
The tunnel speaks the same wire protocol as the desktop sealgate-stdiod
daemon (protocol v2; see schema/tunnel-protocol.json). Where the desktop
daemon spawns npx/uvx subprocesses, this app answers MCP requests from
in-process Kotlin modules — a phone can't spawn stdio servers.
| Module | Tools | What it does |
|---|---|---|
deviceinfo |
1 | Device model, OS build, and identifiers. |
battery |
1 | Charge level and charging state. |
wifi |
1 | Wi-Fi connection status. |
bluetooth |
19 | BLE + classic Bluetooth: status, scan, GATT read/write, notify/indicate, RFCOMM/SPP, and pairing. |
usb |
6 | USB-OTG host access: enumerate devices, request permission, and raw bulk/control transfers. |
Each module's tool set is defined in its mcp/*Module.kt and surfaced to the
agent via tools/list.
- The daemon opens one outbound WebSocket — no incoming ports, works behind NAT and mobile carriers.
- After a
client_hello/server_hellohandshake it exchanges symmetricmcp_frames (opaque JSON-RPC bodies), routed to built-in modules by server name. - It runs as an Android foreground service so the OS keeps it alive, and reconnects forever with jittered exponential backoff.
- Android Studio (Ladybug / 2024.2+ recommended)
- JDK 17+
- Android SDK Platform 35, min SDK 26
- Open the project in Android Studio (File → Open, select this folder) and
let it sync, or build from the command line:
./gradlew assembleDebug # build the debug APK ./gradlew testDebugUnitTest # run JVM unit tests ./gradlew installDebug # install on a connected device/emulator
- Run the app, fill in the gateway WebSocket URL and your SealGate API key (from the dashboard), and tap Start tunnel. Settings persist across restarts; the ongoing notification shows the live connection state.
While the service runs, it posts an ongoing notification:
Each "stdio server" on mobile is an in-process Kotlin module. To add one:
- Extend
BaseMcpModule(seemcp/DeviceInfoModule.kt) — supply the tool descriptors and thetools/callhandler; the MCP lifecycle (initialize,ping,tools/list) is handled for you. - Register it in the
moduleslist inTunnelService.connect. - Enable a server with that module's name for the device in the SealGate dashboard; the tunnel binds it by name and acks the spawn.
See LICENSE.
