-
Notifications
You must be signed in to change notification settings - Fork 2
feat: Termux MCP integration (pull-to-local + Devin custom MCP configs) #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| ``` | ||
|
|
||
| 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. | ||
| 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": {} | ||
| } |
| 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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Was this helpful? React with π or π to provide feedback. |
||
| } | ||
| 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" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π streamable-http transport may not exist in the lowest pinned MCP version
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" | ||
| 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 | ||
|
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))) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π Info: Non-numeric port value produces an unhandled traceback
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() | ||
| 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}" | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Was this helpful? React with π or π to provide feedback.
Comment on lines
+11
to
+12
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Was this helpful? React with π or π to provide feedback. |
||
There was a problem hiding this comment.
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.shrunspip installon every invocation (requires network each start on the phone) and thenexecs the server in the foreground. The README's Option B showsTERMUX_MCP_TRANSPORT=sse ./run.shfollowed by./tunnel.shas if sequential, but the first command never returns; users need a second Termux session or backgrounding. Worth clarifying in the docs.Was this helpful? React with π or π to provide feedback.