Skip to main content
Glama
nelvinzfx

termux-mcp-shell

by nelvinzfx

termux-mcp-shell

Streamable HTTP MCP server that gives an agent shell and file access inside Termux.

Install

One-liner

curl -fsSL https://raw.githubusercontent.com/nelvinzfx/termux-mcp-shell/master/install.sh | sh

The installer installs Python, Git, and Termux's native Rust build toolchain, clones or updates the repository at ~/termux-mcp-shell, installs Python dependencies, creates mcpsh and mcpsh-stop, and adds the repository's bin directory to detected Bash, Zsh, or Fish configuration. Rust is required because PyPI does not provide Android wheels for pydantic-core; the installer prepares maturin and disables build isolation so pip uses Termux's Rust instead of the unsupported rustup Android target. It is safe to rerun.

Use another destination or repository with:

MCP_DEST=$HOME/mcp MCP_REPO_URL=https://github.com/example/fork \
  sh -c 'curl -fsSL https://raw.githubusercontent.com/nelvinzfx/termux-mcp-shell/master/install.sh | sh'

Manual

pkg install python python-pip git rust make pkg-config patchelf
python -m pip install --upgrade "setuptools>=70.1" wheel "maturin>=1.10,<2"
python -m pip install --no-build-isolation -r requirements.txt
python server.py

Only the mcp SDK is a direct Python dependency. The server otherwise uses the Python standard library.

Related MCP server: Android MCP Server

Run and stop

Foreground:

python server.py

Background, surviving terminal-tab closure:

mcpsh
mcpsh-stop

mcpsh writes the PID to ~/.mcpsh.pid, logs to ~/.mcpsh.log, and prints the active endpoint and exposure status. The default MCP endpoint is:

http://127.0.0.1:8088/mcp

The server binds loopback-only 127.0.0.1:8088 by default. LAN access requires explicit MCP_HOST=0.0.0.0; set MCP_AUTH_TOKEN whenever using a non-loopback bind address.

Configuration

Environment variable

Default

Purpose

MCP_HOST

127.0.0.1

Bind address; set 0.0.0.0 explicitly for LAN

MCP_PORT

8088

HTTP port

MCP_TRUNC_LIMIT

8192

Initial command-output bytes returned

MCP_MAX_SESSIONS

50

In-memory command-output buffers

MCP_READ_MAX_LINES

2000

Maximum lines per text read

MCP_READ_MAX_BYTES

51200

Approximate byte cap per text read

MCP_AUTH_TOKEN

unset

Optional shared Bearer/X-API-Key token

Tools

Shell and output

run_command(command, timeout?, cwd?) runs /bin/sh -c asynchronously. Timeout or cancellation kills the command's complete process group. Omitted cwd defaults to $HOME; relative cwd values resolve from $HOME. Large stdout/stderr responses include a session_id and continuation offsets for read_output(session_id, stream, offset, length).

Reading files

read_file(path, offset=1, limit=null, line_numbers=true) returns paginated UTF-8 text plus the exact file SHA-256. Line-number prefixes are display-only and must not be copied into match_text; set line_numbers=false when copying exact source. read_files(reads) batches up to 20 objects shaped as {path, offset, limit, line_numbers} with the same semantics. read_file_bytes(path, offset=0, length=4096) returns Base64 for binary or minified data.

Filesystem work runs in worker threads, so slow storage does not block unrelated MCP requests.

Relative file-tool paths resolve from $HOME, independent of server launch cwd, and ~ and absolute paths remain supported. Android does not provide /tmp. File-tool paths under /tmp and a run_command cwd under /tmp are mapped to Termux's writable $TMPDIR. Responses return the actual mapped path so later shell commands can reuse it. Literal /tmp/... text inside run_command.command is deliberately not rewritten; use the returned path or $TMPDIR/... there.

Writing files

write_file(path, content, expected_sha256=null, create_only=false) atomically creates or replaces one UTF-8 file and its parent directories. expected_sha256 requires an existing file with that exact current hash; create_only=true requires a missing target. The two guards cannot be combined. append_file(path, content, expected_sha256=null) atomically appends and can reject a stale current file. All writes return the resulting SHA-256.

Editing files

edit_file(path, edits, dry_run=false, expected_sha256=null) edits one existing UTF-8 file. edit_files(files, dry_run=false) applies the same operation atomically across multiple existing UTF-8 files; each file item is {path, edits, expected_sha256?}. These tools never create files; use write_file to create or replace one. Inputs are native arrays. Each edit has one canonical shape:

{
  "mode": "replace_match | insert_before | insert_after",
  "match_text": "unique text or anchor",
  "write_text": "literal replacement or insertion"
}

Example transaction:

{
  "files": [
    {
      "path": "src/A.kt",
      "expected_sha256": "hash-from-read_file",
      "edits": [
        {
          "mode": "replace_match",
          "match_text": "val enabled = false",
          "write_text": "val enabled = true"
        }
      ]
    },
    {
      "path": "src/B.kt",
      "edits": [
        {
          "mode": "insert_after",
          "match_text": "fun stop() {}",
          "write_text": "\nfun reset() {}"
        }
      ]
    }
  ],
  "dry_run": true
}

Compatibility input also accepts matchText/writeText and mode aliases insert_before_match/insert_after_match. Schemas, documentation examples, and results remain canonical snake_case. old_text/new_text and camel-case variants remain unsupported. Insertions are literal and never add a newline automatically.

Every match must resolve uniquely. Matching supports normalized Unicode, trailing-whitespace tolerance, and indentation-insensitive blocks. Fuzzy matching only locates the original source span; unmatched text is never normalized or rewritten. Overlapping edits and multiple operations at the same source position are rejected before writing. The server validates every file before writing anything, preserves UTF-8 BOM, line endings, and permission modes, always returns diffs, and attempts rollback if publishing one file fails. dry_run previews without writes. The recommended guarded flow is read_file(line_numbers=false) then use its SHA-256 for dry_run, then apply the same payload and hash. Re-read and rebuild the payload after any stale-source error.

Authentication

The unauthenticated default is reachable only over loopback. Anyone who can reach a non-loopback bind can execute commands and read or modify files. LAN exposure is explicit and should always use a strong token:

MCP_HOST=0.0.0.0 MCP_AUTH_TOKEN="<strong-random-token>" mcpsh

The server does not generate or persist secrets.

Clients may send either:

Authorization: Bearer <token>

or:

X-API-Key: <token>

Authentication uses one shared token. There is no TLS, per-client identity, or rate limiting. For exposure outside loopback or a trusted private network, place the server behind TLS and stronger access controls.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nelvinzfx/termux-mcp-shell'

If you have feedback or need assistance with the MCP directory API, please join our Discord server