tmux

io.github.libtmuxPublished by GitHub account libtmuxv0.0.1-alpha.4Updated Oct 24, 2026

Drive one tmux server: sessions, windows, panes, and the output they produce.

VerifiedSTDIODesktop onlyCloud & InfrastructureDeveloper Tools

Overview

AI-generated overview

Lets an assistant drive a tmux server: create and manage sessions, windows and panes, run commands, and watch pane output.

What you can do
Serves one tmux server to MCP clients, exposing sessions, windows, panes and their output. Tools cover inspection, management, command execution and teardown, including run_shell_command, which returns exit status and output, wait_for_text, and capture_since for incremental pane reads. Reads are bounded and a batch tool groups up to sixteen inspect operations. It also reports whether a pane is the one the server itself runs in.
When to use it
Worth adding when an agent must wait for, watch, or inspect terminal output rather than shell out once, for example waiting for a dev server to print its port, a test run to finish, or a deploy log to settle. For a single send-keys it is unnecessary overhead.
Requirements
Runs locally as a subprocess over stdio. Needs Go 1.26 or newer to install, and tmux 3.2a or newer on PATH. No accounts, API keys or headers are declared. Socket, binary and configuration are chosen at startup by flags or environment variables such as LIBTMUX_SOCKET, LIBTMUX_SOCKET_PATH, LIBTMUX_TMUX_BIN and LIBTMUX_TMUX_CONFIG.
Before you install
Alpha software with an unsettled API; pin an exact version. Tools run with the tmux user's authority, so a client with send_keys can run anything that user can in another pane. Writes to the pane the server itself runs in ask for confirmation through MCP elicitation and are refused if the client lacks that capability. Toolset and tool filters shape the interface but are not an OS sandbox. LIBTMUX_AUDIT can record every call.

Installation

In SourceWeft

  1. Open tmux in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

mcp

Serve one tmux server to Model Context Protocol clients, built on the tmux module and the Go MCP SDK.

[Go Reference] [libtmux.org]

console
$ go install github.com/libtmux/libtmux-go/mcp/cmd/libtmux-mcp@latest

Releases are numbered 0.1.N, and each release increments N. The API is not settled: any release may change or remove exported identifiers, and the changelog marks each break. Pin an exact version to avoid one.

Contents — Quick start · Connect another client · Choose which tmux it drives · See an agent work · Run work and wait for it · The tools · Limiting what a client can do · Settings · The pane it runs in · Known limits · Troubleshooting · An agent-shaped program · Embedding it · Packages · What it leaves out · Support and status · See also

Quick start

You need Go 1.26 or newer and tmux 3.3a or newer on PATH. The install command above puts libtmux-mcp in GOBIN when that is set, and in $(go env GOPATH)/bin otherwise.

An MCP client launches it as a subprocess and speaks to it over stdin and stdout. Closing the client ends the MCP process and its owned connections; tmux sessions remain running. If your shell cannot find libtmux-mcp, add that directory to PATH.

The Python server installs a command of the same name. Run command -v libtmux-mcp to see which one comes first, and libtmux-mcp -version to name this one.

claude mcp add: tell Claude Code about it

console
$ claude mcp add tmux -- libtmux-mcp

Claude Code starts each server it lists, to check that it connects:

console
$ claude mcp list

libtmux-mcp -doctor: check what it reaches

console
$ libtmux-mcp -doctor

It answers without a client in the way, and prints something like this:

text
libtmux-mcp doctor  tmux:    3.6 (requires 3.3a or newer)  socket:  /tmp/tmux-1000/default (from tmux-choice)  holds:   nothing — no tmux server is running on that socket           (not a fault; tmux starts one when something asks it to)  caller:  not running inside a tmux pane

Then ask the client for something only tmux can do, such as "create a tmux session called demo and tell me what panes it has".

Connect another client

Each client takes the same command and the same flags. Connect an MCP client on libtmux.org covers startup and shutdown.

codex mcp add: Codex CLI

console
$ codex mcp add tmux -- libtmux-mcp

gemini mcp add: Gemini CLI

console
$ gemini mcp add tmux libtmux-mcp

claude_desktop_config.json: Claude Desktop

Claude Desktop starts its servers without your shell's PATH, so give it the path command -v libtmux-mcp prints. Flags go in args and the environment in env:

json
{  "mcpServers": {    "tmux": {      "command": "/path/to/libtmux-mcp",      "args": ["-socket-name", "my-application"],      "env": {"LIBTMUX_TOOLSETS": "inspect,manage,execute"}    }  }}

Choose which tmux it drives

The choice is made once, when the server starts. A client cannot change it afterwards. Settings lists the other flags, and the contract has the precedence rules.

-socket-name: pick a socket by name

With no flag and neither LIBTMUX_SOCKET_PATH nor LIBTMUX_SOCKET set, the server passes tmux no socket, so tmux picks the server. Started inside tmux, that is the server you are in. Started elsewhere, it is tmux's default socket, which holds the sessions you already have, and an agent given it can change them. A named socket keeps an agent apart from them:

console
$ claude mcp add tmux -- libtmux-mcp -socket-name my-application

Tools target the selected socket and still run with your user's authority; tool selection is not confinement.

Once it has made a session, watch it work from another terminal:

console
$ tmux -L my-application attach

To drive the tmux you use every day from outside it, name that socket. tmux calls its usual one default:

console
$ claude mcp add tmux -- libtmux-mcp -socket-name default

A workspace that tmux-workspace load -L project built lives on the socket project. To let a client read it and change nothing:

console
$ claude mcp add tmux \    -e LIBTMUX_TOOLSETS=inspect \    -- libtmux-mcp -socket-name project

-socket-path and -binary: an exact socket and tmux

-socket-path names the socket by its absolute path, and -binary names the tmux executable. -doctor reports what a choice selects:

console
$ libtmux-mcp \    -doctor \    -socket-path /tmp/my-application.sock

The environment makes the same choices when no flag is given, which is how a client's configuration sets them. LIBTMUX_SOCKET_PATH names a socket path and LIBTMUX_SOCKET a socket name, and the path wins when both are set:

console
$ LIBTMUX_SOCKET=my-application libtmux-mcp -doctor

See an agent work

These are the calls examples/agent-workflow makes to run work in a pane beside its own, and what came back. Cursor tokens are left out, and each reply shows only the fields that answer the call:

text
→ split_window {"direction":"right","pane_id":"%0","percent":40}← {"paneId":"%1"}→ paste_text {"enter":true,"pane_id":"%1","text":"if sleep 1; then printf 'agent-workflow-%s\\n' ready; else printf 'agent-workflow-%s\\n' failed; fi"}← {"bytes":97}→ wait_for_text {"pane_id":"%1","patterns":["agent-workflow-ready"],"stop":["agent-workflow-failed"],"timeout":30}← {"found":true,"matched":"agent-workflow-ready","outcome":"matched"}→ run_shell_command {"command":"printf 'bounded work\\n'","pane_id":"%1","timeout":30}← {"exit_status":0,"output":["bounded work"],"timed_out":false}

The example prints these with -trace, and its page has the command. The agent drives tmux directly, so nothing is copy-pasted out of a terminal, and waiting for a server to come up is one call.

Run work and wait for it

For one tmux send-keys, shelling out is shorter. The server earns its keep once the agent has to wait, watch, inspect, or leave your terminal alone.

A dev server printing its port, a test run finishing, or a deploy log settling turns a shell-out into a polling loop. That loop reads the shell's echo of its own command and reports success before anything happened.

split_window: make room beside your pane

split_window adds a pane and returns its id. A new pane is given time to start, so run_shell_command can follow at once.

The trace splits %0 to the right with "percent":40 and gets back {"paneId":"%1"}.

wait_for_text: wait for what a pane prints

wait_for_text watches a pane and returns when a pattern appears, or when a stop pattern appears first. capture_since returns what a pane wrote since the last look.

The trace waits for agent-workflow-ready and names agent-workflow-failed as the stop pattern. It gets "outcome":"matched".

A wait longer than the ceiling is shortened, and the reply says so. The ceiling is LIBTMUX_MCP_WAIT_MAX_SECONDS.

If the pane goes away mid-wait, the reply's outcome is paneDied and lines holds what it wrote first. If only the output stream ends and the pane is alive, it is streamLost. The contract has the rest.

run_shell_command: run a command and read its exit status

run_shell_command waits for the command and returns its exit status and output, without reading the screen at all. send_keys and paste_text send input and return.

The trace runs a command that prints "bounded work" and gets back "exit_status":0.

run_shell_command refuses a pane linked with others by synchronize-panes, because one exit status cannot describe several panes.

Keep reads small

Other parts of the design exist because an agent's context and its turn are the scarce resources.

  • Reads are bounded. Captures keep the newest lines asked for and say what they left out. Searches have fixed limits, which the contract lists.
  • One call can make several reads. call_read_tools_batch answers a list of inspect calls with one row each.
  • Checking is not capturing. The listing tools report what exists without reading what any pane shows. snapshot_pane returns a pane's metadata and content together.

The tools

The tools come in toolsets. A toolset is a named group of tools, and it is what an operator grants or withholds:

  • inspect reads tmux and what panes show, and changes nothing.
  • manage rearranges what exists: it renames, resizes, selects, and moves.
  • execute creates sessions, windows, and panes, and sends input to them.
  • teardown kills sessions, windows, and panes, and clears scrollback.

TOOLS.md has every tool's arguments and what it returns, and the documentation site gives each tool the page linked above. That reference is for searching, which is why it is not here.

Four behaviours matter before you grant execute:

  • Input goes where tmux sends it. With synchronize-panes on, tmux types into every pane of the window. send_keys reports the linked panes before it sends.
  • A new pane is given time to start. Input tools wait for its shell to reach the prompt, so split_window then run_shell_command works.
  • Copy mode is left to the person. Reads never take over an attached view, and get_pane_info reports when a pane is in a mode.
  • A pane someone is viewing is refused. The input tools do not type into a pane a tmux client is showing, unless you launch the server with LIBTMUX_ALLOW_ATTENDED_INPUT.

The contract has the rest of each.

Limiting what a client can do

LIBTMUX_TOOLSETS: choose the toolsets

It takes toolset names separated by commas, in any order. Without it the server grants inspect, manage, and execute.

  • teardown is never in that default. Ask for it by name.
  • A value that is set and empty grants no toolset, and there is no none keyword.

To see the effect without a client, list what the server offers with one toolset:

console
$ LIBTMUX_TOOLSETS=inspect libtmux-mcp -tools

A client gets the same selection from the environment it starts the server with:

console
$ claude mcp add tmux \    -e LIBTMUX_TOOLSETS=inspect \    -- libtmux-mcp -socket-name default

LIBTMUX_TOOLS and LIBTMUX_EXCLUDE_TOOLS: name exact tools

LIBTMUX_TOOLS adds tools by name to what the toolsets grant, and LIBTMUX_EXCLUDE_TOOLS then removes tools by name. Excluding a tool also removes it from what call_read_tools_batch may call.

This lists the default tools without send_keys and send_keys_batch:

console
$ LIBTMUX_EXCLUDE_TOOLS=send_keys,send_keys_batch libtmux-mcp -tools

A name that matches no tool, or an empty item in either list, stops the server at startup, before it opens tmux.

Choosing tools shapes what a client is offered. Every call runs with your user's authority. The contract covers the variables from earlier releases.

LIBTMUX_ALLOW_ATTENDED_INPUT: allow input to a pane someone is viewing

Every input tool refuses a pane that a tmux client is viewing, so an agent does not type into the window a person is looking at. That refusal is the default. To let the agent type into the window you are viewing, launch the server with LIBTMUX_ALLOW_ATTENDED_INPUT=1:

console
$ claude mcp add tmux \    -e LIBTMUX_ALLOW_ATTENDED_INPUT=1 \    -- libtmux-mcp -socket-name default

The setting lifts that one refusal. A dead pane, a pane with input disabled, a pane in a mode, and a synchronized pane that has such a member are still refused. The only accepted value is 1. Any other value, including an empty one, stops startup with a message that names the variable.

The server reads it once, at startup. tmux://capabilities reports it as allowAttendedInput, and libtmux-mcp -doctor prints it.

libtmux-mcp -tools: see what a client would be offered

console
$ libtmux-mcp -tools

It prints the tools the server would advertise: each one's toolset, which processes it can reach, and a sentence on what it does. Give it the flags and environment the client gives the server, because they decide the listing.

tmux://capabilities: what each tool says about itself

Each tool describes what it reaches and changes, under _meta["com.git-pull.libtmux-mcp/capability"]. The server's one resource, tmux://capabilities, reports the same descriptions beside the selection in force. The contract lists them.

Settings

Flags choose the tmux server once, at startup:

FlagMeaning
-socket-pathan explicit socket path; when set, it wins over everything else
-socket-namea tmux socket name; when set, it wins over both socket variables below
-binarythe tmux executable; empty uses LIBTMUX_TMUX_BIN, then finds tmux on PATH

Other flags answer a question and exit, which is what a config entry that will not start needs:

FlagAnswers
-versionwhich build this is
-toolsthe tools and toolsets a client would see
-doctorwhich socket it reaches, what is on it, whether it is running inside that tmux itself, and whether input may reach a pane a client is viewing

The environment sets the rest:

VariableDoes
LIBTMUX_TOOLSETSselects any unordered subset of the toolsets
LIBTMUX_TOOLSincludes exact tool names after toolset expansion
LIBTMUX_EXCLUDE_TOOLSremoves exact names after every inclusion
LIBTMUX_ALLOW_ATTENDED_INPUT1 lets the input tools type into a pane a tmux client is viewing; unset refuses it
LIBTMUX_SOCKET_PATHselects the socket path when no socket flag selects one; wins over LIBTMUX_SOCKET
LIBTMUX_SOCKETnames the tmux socket when no socket flag and no LIBTMUX_SOCKET_PATH select one
LIBTMUX_TMUX_CONFIGselects a nonempty absolute tmux configuration path
LIBTMUX_TMUX_BINselects the tmux executable when -binary is empty
LIBTMUX_MCP_WAIT_MAX_SECONDSthe longest any one wait may run; 300 by default
LIBTMUX_AUDITstderr, or a path, to record every call
  • A flag wins over its variable, and everything is fixed when the server starts. The names match the Python server's.
  • The tmux executable is resolved once. A bad path fails at startup. A tmux server that is not running is fine, because tmux starts one on demand.
  • A long wait is shortened. The reply says so in effectiveTimeoutSeconds and timeoutClamped.

The contract has the full text.

The pane it runs in

isCaller: knowing its own pane

Every pane the server reports carries isCaller: true for the pane it runs in, false for any other, and null when the server is not in a pane at all. get_server_info answers the same question directly.

The server reads TMUX_PANE and TMUX first. When they say nothing, it finds the pane whose process is one of its own ancestors.

Asking before it types into your terminal

A write to the pane with isCaller true asks you first, through MCP's elicitation request: a question the client puts to you on the server's behalf.

  • Declining fails the call, and a client that cannot ask is refused.
  • A batch asks the same way, and declining stops the batch there.
  • With synchronize-panes on, the question covers every pane the input would reach.

The contract has the rest.

Known limits

  • Some tools require a newer tmux. Startup and ordinary tools support 3.3a, including capture_since and run_shell_command. Non-empty paste_text requires 3.4 before staging any buffer; wait_for_text polls with capture-pane below 3.7 and follows control output from 3.7. Restricted tools return an explicit version error on older supported daemons. The runtime uses subprocess commands below 3.7 and a retained control connection from 3.7.
  • Every tool is annotated as destructive. list_sessions included, each carries destructiveHint=true and no readOnlyHint. Classify by tmuxEffects in each tool's _meta.
  • tools/list is large. More than half of it is the _meta description. A client that wants a smaller reply can ignore _meta.
  • Some tools spell results differently. run_shell_command, send_keys, paste_text, and clear_pane_scrollback return fields such as exit_status; the rest return fields such as paneId.
  • An empty TMUX counts as set. Input tools refuse when TMUX or TMUX_PANE is set to nothing. Unset them instead.

The contract gives the reason for each, and PARITY.md covers the result spellings.

Troubleshooting

Ask the server first. -doctor answers most of what follows without a client in the way:

console
$ libtmux-mcp -doctor -socket-name default
  • The client shows no tmux tools. The server never started. Run the command from your client's config by hand.
  • Tools are missing. LIBTMUX_TOOLSETS, LIBTMUX_TOOLS, or LIBTMUX_EXCLUDE_TOOLS shaped the listing. -tools prints what a client receives.
  • It sees none of your sessions. With no socket flag and neither LIBTMUX_SOCKET_PATH nor LIBTMUX_SOCKET, tmux picks the server: the one TMUX names, otherwise its default socket, which may not hold your sessions. -doctor names the socket and what chose it. Point the server at yours with -socket-name or -socket-path.
  • It reaches the wrong tmux. A client's environment is not your shell's, so TMUX_TMPDIR from your profile may be unset for it.
  • Sessions you can see are reported as nothing there. Compare the tmux version -doctor prints with tmux -V, and point -binary at the right one.
  • A command works in your shell but not through a tool. show_environment reports what a new pane would inherit.

The contract explains each.

An agent-shaped program

examples/agent-workflow is the whole loop in one file. It finds its own pane, splits one off, starts work there and waits for what it prints, runs a command and reads its exit status, then reads back what it built.

It lives in the repository, outside the module you installed, so run it from a clone:

console
$ git clone https://github.com/libtmux/libtmux-go.git
console
$ go -C libtmux-go/mcp run ./examples/agent-workflow -socket-name demo

On a socket with nothing on it, it starts a session there and prints:

text
no tmux server on that socket yet; starting onenot running inside this tmux server; using active pane %0split into %12 panes in this window while the work runsthe work printed agent-workflow-readythe command exited 0 and printed:  | bounded workthe window now holds 2 panes: %0 %1

The client and server are joined in memory, so it is one program. The tool names, schemas, and results are the ones a client sees over stdin and stdout. Clean up with tmux -L demo kill-server.

Embedding it

mcp.NewServer: serve one tmux server from your own program

mcp.Run serves a tmux server over standard input and output, which is all the libtmux-mcp command does. For another transport, build an Instance and connect it yourself.

This block comes from the package's own example test. It connects one end of the SDK's in-memory transport pair and gives the other end to a client:

go
// Given: ctx context.Context; target tmux.Server; serverTransport sdk.Transportinstance, err := tmuxmcp.NewServer(target)if err != nil {	return nil, nil, err}serverSession, err := instance.Connect(	ctx, tmuxmcp.AssumeResponseCommit(serverTransport), nil,)

This package is named mcp and so is the SDK's, so the block imports this one as tmuxmcp and the SDK as sdk. NewServer checks the target and starts nothing.

mcp.AssumeResponseCommit: say a transport writes each response at once

Standard input and output are known to deliver each response as soon as the write returns. Wrap any other transport in AssumeResponseCommit, as the block does for the in-memory one.

Close the instance when you have finished serving. The reference has the rest of what an embedder is held to, and the contract has the full text.

Packages

mcp: the server as a library

mcp.Run and mcp.NewServer serve a tmux server to a client. Reference: pkg.go.dev and libtmux.org.

libtmux-mcp: the command a client launches

It serves one tmux server over stdin and stdout. Reference: pkg.go.dev and the command README.

What it leaves out

  • A second tmux server. The server serves one tmux server for the life of the process, and a client cannot name another socket in a call.
  • Anything but tools. There is one resource, and no prompts, resource templates, completions, or background job handles.
  • Confinement. Toolsets shape what a client is offered. Every call runs with your user's authority, so a client holding send_keys can run anything you can.
  • The Python server's per-call socket. The socket is chosen once, at launch. PARITY.md lists the other differences.

Support and status

  • Go 1.26+ and tmux 3.3a or newer. The server refuses an older tmux before the MCP transport starts. CI runs tmux 3.3a and 3.8 on each push, and tmux 3.3a through 3.8 for each release.
  • Linux, including WSL, and macOS. CI runs this module's tests on Linux against the same releases. macOS is supported on a best-effort basis, and CI coverage for it is highly limited.
  • A module of its own. go get on the tmux library never pulls in the MCP SDK.
  • Tagged as mcp/vN. Pin an exact version in your own go.mod; the commands here fetch the newest.

See also

ReferenceEvery exported identifier, method, and field, one page each
docs/contracts.mdWhat the server promises beyond this page
CHANGELOG.mdWhat each release of this module changed
TOOLS.mdEvery tool's arguments and results
PARITY.mdWhere this server and the Python one differ
MIGRATING.mdSettings from earlier releases, and what replaced each
cmd/libtmux-mcpThe command a client launches
examples/agent-workflowThe program above, as a directory you can run
AGENTS.mdTesting the server through a real client, and by hand
../tmux/The library this serves
libtmux-mcp in PythonA server for the same tmux and the same clients

Source: mcp/README.md at commit 41bc2a3

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

1
  1. v0.0.1-alpha.4LatestSep 16, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts