MCPconceptHome

Choose documentation 3

v0.1.2

Current version

v0.1.2
English

Early release libtmux is released, and its APIs may still change between releases. This site's structure and URLs may change too.

Edit this page on GitHub

tmux MCP, built in Ruby

tmux terminal control for AI agents, built on libtmux for Ruby and MCP Ruby SDK. This server maps tmux's object hierarchy — sessions, windows, panes — into MCP tools. It offers only the tools selected when it starts.

Install

Choose your client and configuration scope, then run the command or paste the configuration.

With RubyGems installed:

$ claude mcp add tmux -- libtmux-mcp --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.claude.json (this project)

With RubyGems installed:

$ claude mcp add tmux --scope user -- libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.claude.json (all projects)

With RubyGems installed:

$ claude mcp add tmux --scope project -- libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .mcp.json (in repo, version-controlled)

With Bundler installed:

$ claude mcp add tmux -- bundle exec libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.claude.json (this project)

With Bundler installed:

$ claude mcp add tmux --scope user -- bundle exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.claude.json (all projects)

With Bundler installed:

$ claude mcp add tmux --scope project -- bundle exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .mcp.json (in repo, version-controlled)

With RubyGems installed:

{
    "mcpServers": {
        "tmux": {
            "command": "libtmux-mcp",
            "args": ["--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: claude_desktop_config.json

With Bundler installed:

{
    "mcpServers": {
        "tmux": {
            "command": "bundle",
            "args": ["exec", "libtmux-mcp", "--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: claude_desktop_config.json

With RubyGems installed:

$ codex mcp add tmux -- libtmux-mcp --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.codex/config.toml

With RubyGems installed:

Codex's CLI doesn't support project scope yet — paste this into .codex/config.toml at the repo root.

[mcp_servers.tmux]
command = "libtmux-mcp"
args = ["--socket-name", "libtmux-docs", "--endpoint", "local"]

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .codex/config.toml (in repo)

With Bundler installed:

$ codex mcp add tmux -- bundle exec libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.codex/config.toml

With Bundler installed:

Codex's CLI doesn't support project scope yet — paste this into .codex/config.toml at the repo root.

[mcp_servers.tmux]
command = "bundle"
args = ["exec", "libtmux-mcp", "--socket-name", "libtmux-docs", "--endpoint", "local"]

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .codex/config.toml (in repo)

With RubyGems installed:

$ gemini mcp add --scope user tmux libtmux-mcp -- --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.gemini/settings.json

With RubyGems installed:

$ gemini mcp add --scope project tmux libtmux-mcp -- \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .gemini/settings.json (in repo)

With Bundler installed:

$ gemini mcp add --scope user tmux bundle -- exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.gemini/settings.json

With Bundler installed:

$ gemini mcp add --scope project tmux bundle -- exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .gemini/settings.json (in repo)

With RubyGems installed:

{
    "mcpServers": {
        "tmux": {
            "command": "libtmux-mcp",
            "args": ["--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .cursor/mcp.json (in repo)

With RubyGems installed:

{
    "mcpServers": {
        "tmux": {
            "command": "libtmux-mcp",
            "args": ["--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.cursor/mcp.json

With Bundler installed:

{
    "mcpServers": {
        "tmux": {
            "command": "bundle",
            "args": ["exec", "libtmux-mcp", "--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: .cursor/mcp.json (in repo)

With Bundler installed:

{
    "mcpServers": {
        "tmux": {
            "command": "bundle",
            "args": ["exec", "libtmux-mcp", "--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.cursor/mcp.json

With RubyGems installed:

$ grok mcp add --scope user tmux -- libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.grok/config.toml

With RubyGems installed:

$ grok mcp add --scope project tmux -- libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ./.grok/config.toml (in repo)

With Bundler installed:

$ grok mcp add --scope user tmux -- bundle exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.grok/config.toml

With Bundler installed:

$ grok mcp add --scope project tmux -- bundle exec libtmux-mcp \
    --socket-name libtmux-docs --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ./.grok/config.toml (in repo)

With RubyGems installed:

{
    "mcpServers": {
        "tmux": {
            "command": "libtmux-mcp",
            "args": ["--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.gemini/config/mcp_config.json

With Bundler installed:

{
    "mcpServers": {
        "tmux": {
            "command": "bundle",
            "args": ["exec", "libtmux-mcp", "--socket-name", "libtmux-docs", "--endpoint", "local"]
        }
    }
}

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.gemini/config/mcp_config.json

With RubyGems installed:

$ opencode mcp add tmux -- libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.config/opencode/opencode.jsonc

With Bundler installed:

$ opencode mcp add tmux -- bundle exec libtmux-mcp --socket-name libtmux-docs \
    --endpoint local

The server borrows an existing named socket. Start it first with `tmux -L libtmux-docs new-session -d`; `--endpoint` is only its public alias.

Config file: ~/.config/opencode/opencode.jsonc

libtmux-mcp exposes one existing tmux server over MCP standard input and output. Requiring libtmux/mcp starts no server; the installed libtmux-mcp executable owns the protocol transport and borrows the selected tmux daemon.

The default catalog contains tmux_capabilities and tmux_snapshot. Observation and mutation tools are opt-in, so a client cannot acquire them by calling an undisclosed name.

Start here

  • Install configures an MCP client to launch the Ruby server.
  • Tools records the actual wire schemas and per-tool policy.
  • Guides connect a client to a private tmux server.
  • Topics explain captures, cursors, references, and tool policy.
  • Examples run a client launcher or embed the MCP application.
  • Language API documents the Ruby embedding surface.

The server requires Ruby 3.3 or newer. Strong process tracking for waits and authored runs requires tmux 3.3a or newer plus native process identity support. The MCP server guide is staged from the same revision as the generated reference.

Tools

The Ruby server declares no toolsets, so its tools are listed together. Read each tool's page before enabling it.

  • tmux_capabilities Discover the fixed endpoint, server binding, enabled tools, criteria schema and limits.
  • tmux_capture Read exact-pane screen/history rows preserving LF.
  • tmux_close Destroy one exact session, window or pane.
  • tmux_create Create a session, window in an exact session, or split an exact pane using argv.
  • tmux_run Run an authored POSIX script in an explicitly enrolled idle, empty zsh 5.9 editor.
  • tmux_send Send literal UTF-8 text or named keys to one exact pane.
  • tmux_snapshot Capture ordered metadata and evaluate Ruby criteria locally.
  • tmux_wait Wait for observed literal screen text or the initial pane process exit.

Browse every tool, with its arguments, result and protocol data.

Worked example

Given this prompt, an agent connected to the Ruby server makes these calls and gets these results.

tmux window of 1 pane
%0 cat
@0

tmux_snapshot read the panes; tmux itself is unchanged.

you

List the panes on my tmux server.

agentAsk the server which tools and limits are enabled.

Result
{
"ok": true,
"data": {
"endpoint": "docs",
"server_identity": {
"generation": "2b7aa398dd617620e4e735be8ff694bc",
"pid": 2482776,
"start_time": 1793692756,
"tmux_version": "3.8a"
},
"enabled_tools": [
"tmux_capabilities",
"tmux_snapshot",
"tmux_send",
"tmux_create",
"… 4 more"
],
"criteria_schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:libtmux-ruby:where:1",
"title": "Ruby libtmux criteria version 1",
"description": "The Ruby decoder additionally enforces byte, depth and node limits, rejects duplicate keys and floating-point tokens for integer fields.",
"oneOf": [
{
"type": "object",
"required": "…",
"additionalProperties": false,
"properties": "…"
},
{
"type": "object",
"required": "…",
"additionalProperties": false,
"properties": "…"
},
{
"type": "object",
"required": "…",
"additionalProperties": false,
"properties": "…"
},
{
"type": "object",
"required": "…",
"additionalProperties": false,
"properties": "…"
},
"… 1 more"
],
"$defs": {
"session.id": {
"anyOf": "…"
},
"session.name": {
"anyOf": "…"
},
"session.created": {
"anyOf": "…"
},
"session.attached": {
"anyOf": "…"
},
"session.window_count": {
"anyOf": "…"
},
"session": {
"type": "object",
"additionalProperties": false,
"properties": "…"
},
"window.id": {
"anyOf": "…"
},
"window.name": {
"anyOf": "…"
},
"window.width": {
"anyOf": "…"
},
"window.height": {
"anyOf": "…"
},
"window.pane_count": {
"anyOf": "…"
},
"window.layout": {
"anyOf": "…"
},
"…": "30 more"
}
},
"limits": {
"max_captures": 16,
"max_capture_bytes": 8388608,
"capture_ttl_seconds": 30,
"request_timeout_seconds": 5,
"max_response_bytes": 1048576,
"max_mutation_bytes": 65536,
"max_observers": 16,
"max_enrollments": 8,
"default_enrollment_seconds": 60,
"max_enrollment_seconds": 300,
"max_script_bytes": 65536,
"default_run_output_bytes": 65536,
"…": "2 more"
},
"owns_daemon": false,
"resource_subscriptions": false,
"observation": {
"screen": "bounded_rows",
"history_continuity": "unknown",
"process_cursor": "conditional",
"requirements": [
"tmux >= 3.3a",
"64-bit Linux with peer pidfds (kernel >= 6.6)",
"same PID namespace and matching procfs",
"live pane process",
"… 3 more"
],
"wait_conditions": [
"screen_contains",
"process_exit"
]
},
"authored_run": {
"availability": "conditional",
"shell_profile": "zsh-5.9-zle",
"enrollment": "explicit_source",
"authorization": "exact_generation_at_queue_grant",
"stdin": "closed",
"persistent_shell_changes": false,
"descendant_termination": "unobserved",
"requirements": [
"tmux >= 3.3a",
"64-bit Linux with peer pidfds (kernel >= 6.6)",
"same PID namespace and matching procfs",
"live pane process",
"… 5 more"
]
}
}
}

agentList the panes.

tmux_snapshot {"entity":"pane","limit":20}
Result
{
"ok": true,
"data": {
"capture_id": "aec1a3d6e62a4ceb2c8326b8a54dcbc7",
"server_identity": {
"generation": "2b7aa398dd617620e4e735be8ff694bc",
"pid": 2482776,
"start_time": 1793692756,
"tmux_version": "3.8a"
},
"entity": "pane",
"coverage": {
"session": "complete",
"window": "complete",
"pane": "complete",
"window_link": "complete",
"client": "complete"
},
"interval": {
"clock": "monotonic_seconds",
"started": 216410.154280461,
"finished": 216410.167168026,
"reads": 5
},
"items": [
{
"kind": "pane",
"fields": {
"id": "%0",
"windowId": "@0",
"index": 0,
"pid": 2482777,
"currentCommand": "cat",
"currentPath": "/home/user/project",
"title": "host",
"active": true,
"dead": false,
"deadStatus": null,
"width": 80,
"height": 24
},
"ref": {
"generation": "2b7aa398dd617620e4e735be8ff694bc",
"kind": "pane",
"id": "%0"
}
}
],
"truncated": false
}
}

Calls and results are recorded against libtmux/libtmux-ruby at 20744401e, on a private tmux 3.8a server. The preview draws only what those results state. The agent's narration is illustrative. This page documents 46474e8a2, a different revision. The calls have the same input and output schemas there, but the results at that revision may differ from the recorded ones. Long values in a result are shortened here; the recording keeps them. The sandbox's project directory is shown as /home/user/project.

Esc

Type to search.

Reference previewOpen reference