tmux-mcp-rs

Crates.io Version CI Crates.io Downloads License Discord Buymecoffee

tmux-mcp-rs is a Model Context Protocol (MCP) server for tmux. It lets MCP clients create sessions, shape windows and panes, run tracked commands, inspect output, manage tmux buffers, and drive interactive terminal programs through structured tools instead of brittle screen scraping.

Use it when an agent needs a real TTY, long-running commands, parallel panes, resumable terminal state, or a tmux session that a human can attach to during the same task.

[!WARNING] This server can let an MCP client run shell commands, type into panes, read terminal output, modify tmux sessions, and read or write tmux buffers. The default runtime policy is permissive. Use isolated tmux sockets and a config.toml policy before exposing it to a client you do not fully trust.

Contents

What the server provides

  • Session, window, pane, client, and buffer tools with structured inputs and outputs.
  • execute-command and get-command-result for shell commands with command IDs, resource URIs, status, output, and side-channel exit-code tracking (optional waitMs; prefer resource subscribe for completion).
  • Pane, window, session, client, server, and tracked-command resources for lightweight state checks without re-running tools.
  • Optional raw input tools for interactive programs, prompts, REPLs, and TUIs.
  • Runtime policy controls for tool filtering, command filtering, socket/session/pane scoping, and coarse operation groups.
  • Compile-time feature flags that remove raw input tools from the binary for hardened builds.
  • Optional SSH routing so the local MCP server can control a remote tmux server.

The MCP path is usually more reliable than a plain tmux skill because every operation has a named tool, typed parameters, stable IDs, and structured responses. Clients do not need to infer pane IDs from captured text or parse command output to determine whether a command has finished.

Requirements

  • tmux 3.0 or newer on PATH.
  • Rust 1.70 or newer when building from source.
  • A shell supported by the command tracker: bash, zsh, or fish.

On startup the server runs tmux -V. It exits when it detects tmux 2.x because tmux 2.x uses different output formats and split flags. If the version cannot be detected, startup continues and tmux errors are reported by the affected tool calls.

Installation

Cargo

cargo install tmux-mcp-rs

Homebrew

brew install bnomei/tmux-mcp/tmux-mcp-rs

npm

npx @bnomei/tmux-mcp-rs --version
# or: npm install -g @bnomei/tmux-mcp-rs

The npm package is a thin wrapper: on first run it downloads the matching GitHub Release binary, verifies the .sha256, caches it, and forwards argv.

Docker

docker run --rm ghcr.io/bnomei/tmux-mcp:0.6.0 --version

The image is self-contained: it includes Alpine tmux 3.x and the prebuilt musl Linux binary. Sessions run inside the container, not on your desktop tmux server.

  • Default (recommended for Docker): isolated agent sandbox. Human attach uses docker exec (see below).
  • Optional (Linux only): mount a host tmux socket so the container drives a host-side server you can tmux attach to natively. Docker Desktop on macOS/Windows generally cannot reach host Unix sockets this way.
  • For easy human/agent co-attach on a laptop, prefer Homebrew, cargo, or npm on the host instead of Docker.

stdio MCP client (keep stdin attached):

docker run --rm -i \
  --name tmux-mcp \
  -v "$PWD:/workspace" \
  ghcr.io/bnomei/tmux-mcp:0.6.0

Watch a session created inside the container:

docker exec -it tmux-mcp tmux attach -t workspace

Linux host-socket wiring (advanced):

# host: start an isolated tmux server
tmux -S /tmp/tmux-mcp-agent.sock -f /dev/null new-session -d -s workspace

# container: talk to that socket (image still needs its own tmux client)
docker run --rm -i \
  -v /tmp/tmux-mcp-agent.sock:/tmp/tmux-mcp-agent.sock \
  --user "$(id -u):$(id -g)" \
  ghcr.io/bnomei/tmux-mcp:0.6.0 \
  --socket /tmp/tmux-mcp-agent.sock

# host: attach as usual
tmux -S /tmp/tmux-mcp-agent.sock attach -t workspace

Match container UID/GID to the socket owner when permissions fail. More detail: packaging/README.md.

GitHub Releases

Download a prebuilt archive from GitHub Releases, extract it, and place tmux-mcp-rs on your PATH.

From source

git clone https://github.com/bnomei/tmux-mcp.git
cd tmux-mcp
cargo build --release

The binary is written to target/release/tmux-mcp-rs.

Verify the install

tmux -V
tmux-mcp-rs --version

Expected output:

tmux 3.x
tmux-mcp-rs <version>

Quick start

  1. Add the server to your MCP client.

    Codex CLI:

    codex mcp add tmux -- tmux-mcp-rs
    

    Claude Code:

    claude mcp add --transport stdio tmux -- tmux-mcp-rs
    

    Generic MCP client configuration:

    {
      "mcpServers": {
        "tmux": {
          "command": "tmux-mcp-rs"
        }
      }
    }
    
  2. In your MCP client, ask the agent to create or list tmux sessions.

    A typical first task is:

    Create a tmux session named workspace, list its windows and panes, then run pwd in the first pane.
    

    The tool responses should include a session ID, window ID, pane ID, and a commandId for the tracked command.

  3. Attach to the same tmux session if you want to watch or participate.

    tmux attach -t workspace
    

For safer multi-agent work, start with an isolated socket instead of the default tmux server:

tmux -S /tmp/tmux-mcp-agent.sock -f /dev/null new-session -d -s workspace
tmux -S /tmp/tmux-mcp-agent.sock attach -t workspace

Then configure the MCP server with --socket /tmp/tmux-mcp-agent.sock, TMUX_MCP_SOCKET=/tmp/tmux-mcp-agent.sock, or a per-tool socket override.

Client configuration

Use args when your MCP client accepts JSON configuration:

{
  "mcpServers": {
    "tmux": {
      "command": "tmux-mcp-rs",
      "args": [
        "--shell-type",
        "zsh",
        "--socket",
        "/tmp/tmux-mcp-agent.sock",
        "--config",
        "/path/to/config.toml"
      ]
    }
  }
}

For a remote tmux server, add --ssh:

{
  "mcpServers": {
    "tmux": {
      "command": "tmux-mcp-rs",
      "args": [
        "--ssh",
        "user@host",
        "--socket",
        "/tmp/tmux-mcp-agent.sock"
      ]
    }
  }
}

Every MCP tool except socket-for-path accepts an optional socket parameter. If a tool omits it, the server uses the process default socket.

Configuration reference

tmux-mcp-rs does not auto-load a config file. Pass one with --config /path/to/config.toml.

CLI options

Option Default Description
--shell-type <SHELL> bash Shell used for shell-aware command tracking. Supported values: bash, zsh, fish. If [shell].type exists in the config file, the config value wins.
--config <PATH> unset TOML configuration file to read. Invalid TOML, invalid regexes, and unknown tools or groups fail startup.
--socket <PATH> tmux default socket Sets TMUX_MCP_SOCKET for this server process. A per-tool socket parameter still wins for that call.
--ssh <CONNECTION> unset Routes tmux commands through SSH. This value wins over [ssh].remote and TMUX_MCP_SSH.
--help n/a Prints CLI help.
--version n/a Prints the binary version.

Environment variables

Variable Description
TMUX_MCP_SOCKET Default tmux socket path when no per-tool socket override is provided. If unset, the server uses tmux's default socket path: $TMUX_TMPDIR/tmux-$UID/default, or /tmp/tmux-$UID/default when TMUX_TMPDIR is unset.
TMUX_MCP_SSH SSH connection string used when --ssh and [ssh].remote are unset. The string is parsed with shell-word rules. Unbalanced quotes fail startup.
TMUX_MCP_TOOLS Per-process override for [security.tools]. Use allow:<items> or deny:<items>. Without a prefix, the value is treated as a denylist.
RUST_LOG Enables tracing output through tracing-subscriber when set. Logs are written to stderr.

Precedence rules:

Setting Precedence
Socket Per-tool socket parameter, then --socket or TMUX_MCP_SOCKET, then tmux default socket path.
SSH --ssh, then [ssh].remote, then TMUX_MCP_SSH, then no SSH.
Shell type [shell].type, then --shell-type, then bash.
Tool filter TMUX_MCP_TOOLS, then [security.tools], then deny mode with no denied items.

Example config

[shell]
type = "zsh"

[ssh]
remote = "user@host"

[security]
enabled = true
allow_execute_command = true
allow_raw_mode = true
allow_send_keys = false
allow_kill = true
allow_create = true
allow_split = true
allow_rename = true
allow_move = true
allow_capture = true
allow_list = true
allowed_sockets = ["/tmp/tmux-mcp-agent.sock"]
allowed_sessions = ["workspace"]
allowed_panes = ["%1"]
allowed_buffer_paths = ["/srv/tmux-mcp-buffers"]

[security.command_filter]
mode = "allowlist"
patterns = ["^cargo ", "^git ", "^rg ", "^sed "]

[security.tools]
mode = "deny"
items = ["@raw-input"]

[tracking]
capture_initial_lines = 1000
capture_max_lines = 16000
completed_retention_minutes = 240
completed_max_entries = 1000
tracking_deadline_seconds = 600

[search]
streaming_threshold_bytes = 262144

Security options

Key Type Default Description
security.enabled boolean true Enables policy checks. Set to false only when you want to bypass every policy check.
security.allow_execute_command boolean true Allows execute-command and get-command-result.
security.allow_raw_mode boolean true Allows execute-command with rawMode=true, which sends the command without tracking markers.
security.allow_send_keys boolean true Allows raw input tools: send-keys, send-hex, paste-text, and special-key helpers.
security.allow_kill boolean true Allows kill-session, kill-window, kill-pane, and detach-client.
security.allow_create boolean true Allows create-session and create-window.
security.allow_split boolean true Allows split-pane.
security.allow_rename boolean true Allows session, window, and pane rename tools.
security.allow_move boolean true Allows focus, resize, zoom, layout, join, break, swap, move, and synchronize-panes tools.
security.allow_capture boolean true Allows capture-pane and tmux buffer read, write, and search tools. Buffer file operations are part of this capture surface.
security.allow_list boolean true Allows session, window, pane, client, buffer, and current-session listing tools.
security.allowed_sockets string array or unset unset When set, the effective socket for every tool and resource request must exactly match one of these paths.
security.allowed_sessions string array or unset unset When set, session-scoped operations are limited to exact tmux session IDs or names in the list.
security.allowed_panes string array or unset unset When set, direct pane operations are limited to the listed pane IDs.
security.allowed_buffer_paths string array or unset unset Canonical directories allowed for save-buffer and load-buffer. When unset, paths must be relative and stay under the local temp directory's tmux-mcp-buffers/ directory, or /tmp/tmux-mcp-buffers/ over SSH. An empty list denies all buffer file paths.
security.command_filter.mode off, allowlist, denylist off Regex command filter mode.
security.command_filter.patterns string array [] Regex patterns used by the command filter.
security.tools.mode deny, allow deny Runtime tool-surface filter mode.
security.tools.items string array [] Exact tool names or groups such as @raw-input.

security.command_filter checks each non-empty shell statement for execute-command, send-keys (literal and non-literal), paste-text, set-buffer, append-buffer, and the decoded bytes passed to send-hex. It splits unquoted ;, |, &, and newlines, then recursively checks command substitutions, process substitutions, subshells, and brace groups. It rejects shell forms it cannot safely analyze, including ANSI-C $'...' quoting and shell -c wrappers. It does not screen special-key helpers. For a hard boundary, disable raw input tools at runtime or compile them out.

Tracking options

Key Default Description
tracking.capture_initial_lines 1000 Pane lines captured when refreshing partial output for a running command.
tracking.capture_max_lines 16000 Maximum pane lines captured when extracting final output between the human-visible START/DONE markers.
tracking.capture_backoff_factor 2 Accepted for configuration compatibility. Side-channel completion no longer uses capture backoff.
tracking.completed_retention_minutes 240 Age limit for completed command history retained in memory.
tracking.completed_max_entries 1000 Maximum completed command entries retained in memory. Set to 0 to disable entry-count pruning and rely on retention time only.
tracking.tracking_deadline_seconds 600 How long the side-channel watcher waits for tmux wait-for before marking tracking_error.

Search options

Key Default Description
search.streaming_threshold_bytes 262144 Buffer size threshold where search streams through a temp file instead of loading the full buffer text into one string for the search pass.

Security hardening

By default, policy enforcement is enabled but permissive:

  • All coarse allow_* gates are true.
  • command_filter.mode is off.
  • allowed_sockets, allowed_sessions, allowed_panes, and allowed_buffer_paths are unset. Buffer file operations remain restricted to the default temp-directory sandbox.
  • [security.tools] is deny mode with no denied items.

That default is convenient for local experimentation, but it is not a sandbox.

Use an isolated tmux socket

tmux -S /tmp/tmux-mcp-agent.sock -f /dev/null new-session -d -s workspace
tmux-mcp-rs --socket /tmp/tmux-mcp-agent.sock --config config.toml
[security]
allowed_sockets = ["/tmp/tmux-mcp-agent.sock"]

When allowed_sockets is set, calls without an explicit socket still resolve to the process default socket and must match the allowlist.

Remove raw input at runtime

[security]
allow_send_keys = false

[security.tools]
mode = "deny"
items = ["@raw-input"]

Use this when shell input should go through execute-command so command filtering and command-result tracking remain central.

Expose only read tools and tracked command execution

[security.tools]
mode = "allow"
items = ["@read", "execute-command"]

@read includes get-command-result, listing tools, capture tools, buffer read/search tools, and socket-for-path.

Override the tool surface for one process

TMUX_MCP_TOOLS=send-keys,paste-text tmux-mcp-rs
TMUX_MCP_TOOLS=deny:@raw-input tmux-mcp-rs
TMUX_MCP_TOOLS=allow:@read,execute-command tmux-mcp-rs

Compile out raw input tools

The default Cargo feature set includes interactive and special-keys.

  • interactive registers send-keys, send-hex, and paste-text.
  • special-keys registers send-cancel, send-eof, send-escape, send-enter, send-tab, send-backspace, send-up, send-down, send-left, send-right, send-page-up, send-page-down, send-home, and send-end.

Disable those features to remove the tools from the binary:

# Filtered-only build: execute-command is the only shell-input path.
cargo build --release --no-default-features --features rayon,rapidfuzz

# Keep raw keystrokes but remove special-key helpers.
cargo build --release --no-default-features --features rayon,rapidfuzz,interactive

execute-command is always registered and remains subject to security.command_filter.

MCP tool reference

Tool availability depends on Cargo features and runtime policy. Runtime-denied tools are removed from the advertised MCP tool list and denied when called.

Tool groups

Group Tools
@all Every known tool compiled into the binary.
@read socket-for-path, list/find tools, capture-pane, buffer read/search tools, and get-command-result.
@socket socket-for-path.
@list list-sessions, find-session, list-windows, list-panes, list-clients, list-buffers, get-current-session.
@execute execute-command, get-command-result.
@capture capture-pane.
@buffer-read list-buffers, show-buffer, search-buffer, subsearch-buffer.
@buffer-write save-buffer, load-buffer, delete-buffer, set-buffer, append-buffer, rename-buffer.
@create create-session, create-window.
@split split-pane.
@rename rename-session, rename-window, rename-pane.
@move move-window, select-window, select-pane, resize-pane, zoom-pane, select-layout, join-pane, break-pane, swap-pane, set-synchronize-panes.
@kill kill-session, kill-window, kill-pane, detach-client.
@interactive send-keys, send-hex, paste-text.
@special-keys send-cancel, send-eof, send-escape, send-enter, send-tab, send-backspace, send-up, send-down, send-left, send-right, send-page-up, send-page-down, send-home, send-end.
@raw-input All @interactive and @special-keys tools.

Tools by task

Task Tools
Socket utility socket-for-path derives a deterministic /tmp/<hash>.sock path from a project path.
Session management list-sessions, find-session, create-session, kill-session, get-current-session, rename-session.
Window management list-windows, create-window, kill-window, rename-window, move-window, select-window, select-layout, set-synchronize-panes.
Pane management list-panes, split-pane, kill-pane, rename-pane, capture-pane, select-pane, resize-pane, zoom-pane, join-pane, break-pane, swap-pane.
Command execution execute-command returns commandId + resourceUri; prefer resources/subscribe then read on resources/updated, or get-command-result with waitMs.
Client management list-clients, detach-client.
Buffer inspection list-buffers, show-buffer, search-buffer, subsearch-buffer.
Buffer mutation save-buffer, load-buffer, delete-buffer, set-buffer, append-buffer, rename-buffer.
Raw input send-keys, paste-text, send-hex, send-cancel, send-eof, send-escape, send-enter, send-tab, send-backspace, send-up, send-down, send-left, send-right, send-page-up, send-page-down, send-home, send-end.

Prefer execute-command with resource subscribe/read (or get-command-result + waitMs) for non-interactive commands. Tracked commands queue per pane. Use capture-pane for live progress only; do not treat pane DONE markers as authoritative. Use raw input tools only for prompts, REPLs, editors, pagers, and TUIs—and not while a tracked command is running on that pane.

Tracked command snapshots use schemaVersion: 1 and currently move through queued, running, then completed, failed, or tracking_error. The schema also reserves cancelled, but the current tracker does not emit it. Normal tracked commands reject embedded newlines, unquoted shell comment markers (#), and unquoted background operators (&) because those forms can bypass the tracking epilogue. Set rawMode=true or noEnter=true only when you intentionally want to disable side-channel completion tracking; those records remain running.

show-buffer reads at most 65,536 bytes by default. search-buffer defaults to 40 context bytes, 50 matches, and 65,536 scanned bytes per buffer; it returns byte offsets and resume cursors when results are truncated. Literal and regex search are always available. Fuzzy matching and similarity scores require the rapidfuzz Cargo feature, which is enabled by default.

MCP resource reference

Server, pane, window, session, and client resources reflect the server's default socket. Tracked-command resources retain the effective socket recorded by their originating execute-command, including per-tool socket overrides. Resources are dynamically enumerated and filtered by the current security policy.

Only tracked-command result resources support resources/subscribe; read the resource after a notifications/resources/updated event. Other resource URIs provide on-demand snapshots.

URI Description
tmux://server/info JSON with default socket and SSH context.
tmux://pane/{paneId} Last 200 lines from a pane as plain text.
tmux://pane/{paneId}/info Pane metadata as JSON.
tmux://pane/{paneId}/tail/{lines} Tail N lines from a pane as plain text.
tmux://pane/{paneId}/tail/{lines}/ansi Tail N lines from a pane with ANSI colors.
tmux://window/{windowId}/info Window metadata as JSON.
tmux://session/{sessionId}/tree Session, window, and pane snapshot as JSON.
tmux://clients tmux clients as JSON.
tmux://command/{commandId}/result Tracked command status and output.

Remote and socket workflows

Remote SSH

Use --ssh to run tmux commands on another machine. SSH authentication must be non-interactive, such as an agent or key-based login.

tmux-mcp-rs --ssh "user@host"

Pass SSH options before the destination:

tmux-mcp-rs --ssh "-i ~/.ssh/key user@host"

The connection string is split with shell-word rules. The destination should be the last token. The server quotes the remote tmux command before sending it to SSH, and the tmux version check applies to the remote tmux when it can be reached.

Remote plus isolated socket

Create a dedicated tmux server on the remote host:

ssh user@host 'tmux -S /tmp/tmux-mcp-agent.sock -f /dev/null new-session -d -s workspace'

Start the local MCP server against that remote socket:

tmux-mcp-rs --ssh "user@host" --socket /tmp/tmux-mcp-agent.sock

Attach directly from a shell when needed:

ssh -t user@host 'tmux -S /tmp/tmux-mcp-agent.sock attach -t workspace'

Socket isolation

Use --socket or TMUX_MCP_SOCKET to keep one agent on one tmux server:

tmux-mcp-rs --socket /tmp/tmux-mcp-agent.sock
TMUX_MCP_SOCKET=/tmp/tmux-mcp-agent.sock tmux-mcp-rs

Pre-create the session when you want a human-visible workspace before the MCP client starts:

tmux -S /tmp/tmux-mcp-agent.sock -f /dev/null new-session -d -s workspace
TMUX_MCP_SOCKET=/tmp/tmux-mcp-agent.sock tmux-mcp-rs

Docker and sockets

The GHCR image includes tmux 3.x and defaults to self-contained sessions inside the container (docker exec … tmux attach). On Linux only, you can instead mount a host socket so the container MCP server drives a host tmux server—see Installation → Docker and packaging/README.md. Docker Desktop on macOS/Windows is not a reliable host-socket path; use a native install when you need easy local co-attach.

Agent workflow patterns

The integration tests cover these tmux workflows:

Pattern Tool surface
ID-first targeting list-windows, list-panes, rename-window.
Task-per-session layout create-session, create-window, split-pane, rename-pane, list tools.
Stateful shell context send-keys, capture-pane.
Continuous output pane send-keys, capture-pane.
Interactive prompt automation send-keys, capture-pane.
Interactive interrupts send-cancel, send-eof, capture-pane.
Synchronized panes broadcast set-synchronize-panes, send-keys, capture-pane.
Buffer handoff and search list-buffers, show-buffer, save-buffer, delete-buffer, search-buffer, subsearch-buffer.
Pane rearrangements split-pane, select-layout, swap-pane, break-pane, join-pane.
Metadata and zoom Rename tools, zoom-pane, resize-pane, pane/window resources.
Audit-ready context bundle execute-command, get-command-result, capture-pane.
Agent orchestration create-window, split-pane, execute-command, send-keys, capture-pane.

For agent-facing tmux workflows, see:

tmux-buffer-explorer uses tmux buffers as an external search space for large text. It is useful when an agent needs bounded search and follow-up slices instead of loading a whole buffer into context.

Development

Build the binary:

cargo build --release

Run unit tests that do not require tmux:

cargo test --lib
cargo test --test cli
cargo test --test search

Run tmux-backed integration tests:

TMUX_MCP_INTEGRATION=1 cargo test --test integration

Integration tests create isolated tmux servers using temporary sockets and clean them up afterward.

Run the same Rust checks as CI:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets

Release notes and release packaging details live in CHANGELOG.md and docs/RELEASE.md.

Notes and limitations

  • tmux owns paste buffers, while this server retains tracked command snapshots in memory. paste-text, set-buffer, append-buffer, and large retained command outputs can still consume host memory. Tracked output is line-capped by capture_max_lines, but retained strings and individual line length are not byte-capped.
  • Buffer search streams large buffers through a temp file after search.streaming_threshold_bytes, but tmux buffers themselves are not evicted by size.
  • tmux rows are parsed from tab-delimited -F output. A literal tab inside a pane title, path, or similar field can shift later fields and produce malformed or skipped rows.
  • paste-text uses bracketed paste markers. Shells and programs that do not support bracketed paste may treat embedded newlines as Enter. On macOS, the default /bin/bash 3.2 does not support modern bracketed paste behavior; zsh or a newer bash is safer for multi-line input that should not submit line by line.
  • save-buffer writes to the tmux server filesystem, and load-buffer reads from it. Both are governed by allow_capture, [security.tools], and security.allowed_buffer_paths. With the default enabled policy, callers use relative paths inside the local temp directory's tmux-mcp-buffers/ sandbox or /tmp/tmux-mcp-buffers/ over SSH; parent traversal and canonical or symlink escapes are rejected.
  • Command results are socket-bound. get-command-result must use the same effective socket as the original execute-command call.
  • Tracked command completion uses a private tmux exit buffer + wait-for signal, not forgeable pane scrollback. Optional START/DONE lines are for humans/debug only.
  • Interactive send-keys/paste-text during a tracked run on the same pane is undefined; wait for the command to finish or use another pane.
  • End-to-end SSH behavior is manually verified because CI does not provide a remote host.

Source anchors

License

MIT License. See LICENSE.