Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions integrations/termux-mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Termux MCP integration

This integration exposes the fork's 45+ Termux/Android control tools through
MCP. The server must run on the phone in Termux because its tools shell out to
Android, `adb`, and `termux-api`; it cannot run in Devin's cloud VM. Run it
locally on the phone, then connect Devin to it.

## Prerequisites on the phone

Install the required Termux packages:

```bash
pkg install python termux-api android-tools cloudflared openssh
```

Also install the Termux:API app from F-Droid, grant storage access, and
configure device connectivity:

```bash
termux-setup-storage
```

For Android 12+, enable wireless debugging and pair `adb` with the phone as
described by the fork's setup instructions.

## Option A: STDIO over SSH

Start the SSH server in Termux (the default port is 8022):

```bash
sshd
```

In Devin, add a custom MCP using
`devin-custom-mcp.stdio-ssh.json`. Fill in `<PHONE_SSH_USER>`,
`<PHONE_HOST>`, and `<PHONE_TERMUX_DIR>` first. Devin's runtime must be able
to reach the phone over SSH, such as through a publicly reachable host or
tunnel, and SSH key authentication should be configured.

## Option B: HTTP via cloudflared tunnel (recommended)

On the phone, start the SSE server and then the quick tunnel:

```bash
TERMUX_MCP_TRANSPORT=sse ./run.sh
./tunnel.sh
```
Comment on lines +44 to +47

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ“ Info: run.sh reinstalls dependencies and blocks on every start; README implies two sequential commands

run.sh runs pip install on every invocation (requires network each start on the phone) and then execs the server in the foreground. The README's Option B shows TERMUX_MCP_TRANSPORT=sse ./run.sh followed by ./tunnel.sh as if sequential, but the first command never returns; users need a second Termux session or backgrounding. Worth clarifying in the docs.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.


Take the `https://*.trycloudflare.com` URL printed by cloudflared and add a
custom MCP in Devin using `devin-custom-mcp.http.json`, replacing
`<TUNNEL_URL>` so the URL is `<tunnel>/sse`.

## Pull-to-local command execution

Pull this monorepo onto the phone, then run the integration locally:

```bash
cd integrations/termux-mcp
export TERMUX_MCP_DIR=/path/to/termux-mcp-server-fork
./run.sh
```

`run.sh` creates `.venv`, installs the MCP dependency, and installs the fork
in editable mode when `TERMUX_MCP_DIR` is set. Override
`TERMUX_MCP_TRANSPORT`, `TERMUX_MCP_HOST`, and `TERMUX_MCP_PORT` as needed.

To add the connection, open Devin β†’ Settings β†’ Connections β†’ Add a custom MCP
(`/settings/connections/custom-mcp`), or use one of the prefilled JSON files
in this directory.
8 changes: 8 additions & 0 deletions integrations/termux-mcp/devin-custom-mcp.http.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "termux",
"description": "Control an Android phone via Termux MCP over HTTP (cloudflared tunnel)",
"transport": "SSE",
"url": "<TUNNEL_URL>/sse",
"auth_method": "none",
"headers": {}
}
14 changes: 14 additions & 0 deletions integrations/termux-mcp/devin-custom-mcp.stdio-ssh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "termux",
"description": "Control an Android phone via Termux MCP over SSH",
"transport": "STDIO",
"command": "ssh",
"args": [
"-p",
"8022",
"<PHONE_SSH_USER>@<PHONE_HOST>",
"<PHONE_TERMUX_DIR>/integrations/termux-mcp/.venv/bin/python",
"<PHONE_TERMUX_DIR>/integrations/termux-mcp/serve.py"
],
"env": {}
Comment on lines +10 to +13

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ“ Info: SSH stdio config depends on the venv having the fork installed

The stdio-ssh template invokes .venv/bin/python serve.py with an empty env, so it only works if run.sh was previously executed with TERMUX_MCP_DIR set (editable install into the venv). If the operator only ever set TERMUX_MCP_DIR at runtime without the editable install, this path fails with the import error. Adding TERMUX_MCP_DIR to the template's env block would make it self-contained.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

}
19 changes: 19 additions & 0 deletions integrations/termux-mcp/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="$SCRIPT_DIR/.venv"

if [[ ! -d "$VENV_DIR" ]]; then
python -m venv "$VENV_DIR"
fi

# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
pip install "mcp[cli]>=1.2.0,<2"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ” streamable-http transport may not exist in the lowest pinned MCP version

serve.py accepts streamable-http (integrations/termux-mcp/serve.py:8), but the dependency pin here allows very old 1.x releases where FastMCP.run() only supports stdio/sse. If pip resolves an older 1.x on the phone, selecting streamable-http fails at runtime after passing the local validation check. Consider raising the floor of the pin to the first release that shipped streamable HTTP.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.


if [[ -n "${TERMUX_MCP_DIR:-}" ]]; then
pip install -e "$TERMUX_MCP_DIR"
fi

exec python "$SCRIPT_DIR/serve.py"
55 changes: 55 additions & 0 deletions integrations/termux-mcp/serve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import os
import sys


DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8765
DEFAULT_TRANSPORT = "stdio"
ALLOWED_TRANSPORTS = {"stdio", "sse", "streamable-http"}
NETWORK_TRANSPORTS = {"sse", "streamable-http"}

try:
from termux_mcp_server import mcp
except ImportError:
termux_mcp_dir = os.environ.get("TERMUX_MCP_DIR")
if termux_mcp_dir:
sys.path.insert(0, termux_mcp_dir)
try:
from termux_mcp_server import mcp
except ImportError as exc:
print(f"Import of termux_mcp_server failed: {exc}", file=sys.stderr)
mcp = None
else:
mcp = None
Comment thread
timerloggedout-spec marked this conversation as resolved.

if mcp is None:
print(
"Could not import termux_mcp_server. Install the fork with "
"'pip install -e /path/to/termux-mcp-server-fork' or set "
"TERMUX_MCP_DIR to its checkout.",
file=sys.stderr,
)
raise SystemExit(1)


def main():
transport = os.environ.get("TERMUX_MCP_TRANSPORT", DEFAULT_TRANSPORT)
host = os.environ.get("TERMUX_MCP_HOST", DEFAULT_HOST)
port = int(os.environ.get("TERMUX_MCP_PORT", str(DEFAULT_PORT)))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ“ Info: Non-numeric port value produces an unhandled traceback

int(os.environ.get("TERMUX_MCP_PORT", ...)) at integrations/termux-mcp/serve.py:37 runs before the transport validation and raises a raw ValueError traceback for a malformed value, unlike the friendly message given for a bad transport. Wrapping it in the same style of validation would keep the operator experience consistent.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

if transport not in ALLOWED_TRANSPORTS:
allowed = ", ".join(sorted(ALLOWED_TRANSPORTS))
print(
f"Invalid TERMUX_MCP_TRANSPORT {transport!r}; choose one of: {allowed}.",
file=sys.stderr,
)
raise SystemExit(2)

if transport in NETWORK_TRANSPORTS:
mcp.settings.host = host
mcp.settings.port = port

mcp.run(transport=transport)


if __name__ == "__main__":
main()
12 changes: 12 additions & 0 deletions integrations/termux-mcp/tunnel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

if ! command -v cloudflared >/dev/null 2>&1; then
echo "cloudflared is required but was not found." >&2
echo "On Termux, install it with: pkg install cloudflared" >&2
echo "On other platforms, install cloudflared from https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/" >&2
exit 1
fi

# Paste the printed https://*.trycloudflare.com URL into Devin's custom-MCP form (append the server path, see below).
exec cloudflared tunnel --url "http://${TERMUX_MCP_HOST:-127.0.0.1}:${TERMUX_MCP_PORT:-8765}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ“ Info: tunnel.sh target host follows TERMUX_MCP_HOST, which can be a bind-only address

If the operator sets TERMUX_MCP_HOST=0.0.0.0 for the server to listen broadly, tunnel.sh will also dial http://0.0.0.0:PORT (integrations/termux-mcp/tunnel.sh:12). That happens to work on Linux but is not a valid destination in general; using a dedicated TERMUX_MCP_TUNNEL_TARGET or defaulting the dial host to 127.0.0.1 regardless of the bind address would be more robust.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Cloudflare quick tunnel exposes the MCP server publicly with no authentication

The recommended HTTP path publishes the local MCP SSE endpoint through a public trycloudflare.com URL (integrations/termux-mcp/tunnel.sh:12) while the accompanying Devin config sets "auth_method": "none" and empty headers (integrations/termux-mcp/devin-custom-mcp.http.json:6-7). Anyone who learns or guesses the tunnel URL can invoke the 45+ Termux/Android control tools (shell execution, adb, SMS/camera via termux-api) on the phone without any credential.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

Loading