* feat(lsposed): add debug agent bridge * feat(tools): add agent bridge MCP server * fix(lsposed): address agent bridge review feedback * fix(tools): format agent bridge MCP server * fix(tools): sort MCP server imports * fix(lsposed): centralize agent bridge lifecycle * fix(tools): detach adb stdin in agent bridge MCP server adb subprocesses inherited the server's stdin, which carries the MCP JSON-RPC frames, so the adb child consumed handshake bytes and wedged every MCP session. Run adb with stdin=DEVNULL. Switch the server shebang to the project's uv run --script convention and add smoke-test.py, an end-to-end MCP handshake check. * feat(lsposed): move agent control toggle into the Developer section The agent bridge is debug-only, so its toggle belongs with the other developer knobs rather than the Debugging tools section. Gate it behind BuildConfig.DEBUG so release builds (whose AgentControlBridge is a no-op stub) don't show a dead switch. * feat(lsposed): ship agent bridge in release, off by default The bridge was debug-only (real impl in src/debug, no-op stub in src/release, toggle gated by BuildConfig.DEBUG), which hid it from the release builds used for development. Move AgentControlBridge into the main source set so it builds everywhere, drop the BuildConfig.DEBUG guards, and keep the toggle off by default. Because an enabled bridge opens a loopback port other apps can use to detect VPN Hide, surface a dashboard info note while it is on. The host token path falls back to logcat on release builds, where run-as is unavailable. * style(lsposed): expression body for startServer ktlint's function-expression-body rule fires now that the early BuildConfig.DEBUG return is gone and startServer is a single expression. |
||
|---|---|---|
| .. | ||
| .gitignore | ||
| README.md | ||
| server.py | ||
| smoke-test.py | ||
VPN Hide Agent Bridge
Host-side MCP bridge for driving a VPN Hide build through adb.
The Android app starts a local HTTP bridge on 127.0.0.1:27193 when
Settings -> Developer -> Agent control is enabled. It is off by default and
ships in release builds too. The bridge is loopback-only and requires a bearer
token stored in the app-private file files/agent_bridge_token. While it is on,
the dashboard shows a note — leaving the port open is an on-device fingerprint,
so turn it off when you are done.
Prerequisites
- A VPN Hide APK installed on the target device (debug or release).
- VPN Hide opened at least once.
- Settings -> Developer -> Agent control enabled.
adbcan see the device.uvis installed on the host.
For multi-device setups, pass --serial <adb-serial>.
Raw HTTP
Forward the bridge:
adb forward tcp:27193 tcp:27193
Read the token. On a debug build, run-as reaches the app sandbox:
TOKEN="$(adb shell run-as dev.okhsunrog.vpnhide cat files/agent_bridge_token)"
On a release build run-as is unavailable, so read the token the bridge logs
once at startup:
TOKEN="$(adb logcat -d -s VpnHideAgentBridge | sed -n 's/.*token=\([A-Za-z0-9_-]*\).*/\1/p' | tail -1)"
List available functions:
curl -s \
-H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:27193/functions
Call a function:
curl -s \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"fn":"getStatisticsCaptureBaseline","args":{}}' \
http://127.0.0.1:27193/call
MCP server
Run directly:
uv run tools/agent-mcp/server.py --serial 3B241FDJG003LP
Example MCP client config:
{
"mcpServers": {
"vpnhide": {
"command": "uv",
"args": [
"run",
"/absolute/path/to/vpnhide/tools/agent-mcp/server.py",
"--serial",
"3B241FDJG003LP"
]
}
}
}
The MCP server fetches /functions from the app at startup and exposes one MCP
tool per bridge function. Tool calls are forwarded to POST /call with the
same bearer token.
Smoke test
smoke-test.py drives the full host -> MCP -> HTTP bridge -> app path: it
launches the server over stdio, performs the MCP handshake, lists tools, and
calls one read-only tool. Use it to confirm a debug build with Agent control
enabled is reachable:
./tools/agent-mcp/smoke-test.py --serial 3B241FDJG003LP
It exits non-zero on any failure.