Changelog¶
libtmux-mcp 0.1.x (unreleased)¶
Notes on upcoming releases will be added here
Documentation¶
opencode joins the install picker¶
The install widget gains an opencode panel. opencode mcp add tmux -- <command>
is non-interactive once a name and a -- command are both given, so it is a CLI
panel rather than a paste-this-JSON one — which also sidesteps opencode’s
unusual entry shape.
Development¶
mcp_swap.py covers opencode and pi
use-local, status, revert, doctor and detect now reach two more agent
CLIs.
opencode is the first config the script edits that is not plain JSON or TOML.
Its $XDG_CONFIG_HOME/opencode/opencode.jsonc is JSONC, its server map hangs
off a top-level mcp key rather than mcpServers, and one entry packs argv
into a single command array with its environment table spelled
environment. Getting the shape wrong is not a soft failure there: a scalar
command is a decode error that stops opencode starting, and an env key is
dropped without a word. Comments survive a swap, including one written directly
above the command it explains.
pi ships no MCP client — its README says so outright, and the released build
contains no MCP code. ~/.pi/agent/mcp.json is read by the third-party
pi-mcp-adapter extension, so a swap written there takes effect only once that
package is installed. detect says so rather than reporting a swap that cannot
do anything.
JSONC is edited rather than reserialized
The JSON writer rebuilds the whole document, which for a commented file would
mean deleting every comment in it. JSONC values now come from stdlib json
after comments and trailing commas are blanked in place, and writes are applied
as text splices, so every byte outside a replaced value is untouched. The
obvious dependency was measured and rejected: json-five round-trips comments,
but raises on the valid JSON string "C:\\x" and silently decodes a literal
\u0041 to "A".
Per-CLI behavior is declared, not branched
CLIInfo gained container (the key path to the server map) and dialect (the
entry shape) alongside fmt. The four cli in (...) membership tuples that
get_server, set_server, delete_server and _all_server_specs each carried
are gone. Two of them ended in a bare else that fell through to the TOML key,
so a CLI registered but forgotten in one tuple reported “no entry” instead of
failing; the other two raised AssertionError, which the caller did not catch.
A non-mapping at a container key now raises a RuntimeError naming the path for
every CLI, not just Claude. scripts/README.md’s extension guide described
three branch sites when there were four; it now describes the fields instead.
libtmux-mcp 0.1.0a20 (2026-08-09)¶
libtmux-mcp 0.1.0a20 changes no tool behavior. scripts/mcp_swap.py gains use-local --pr N, which points installed agent CLIs at a pull request without a checkout and verifies the server before it rewrites any configuration, and its edits now preserve unrelated config text, file permissions, and symlink targets. ruff’s curated default rule set is enabled behind a ruff>=0.16.0 floor, taking the project from 351 enabled rules to 565 and fixing what that surfaced, and the CI workflow actions move to their current majors. In the documentation, the dataclass identifying an MCP caller describes each of its fields instead of reaching the API reference as “Alias for field number 0”.
Documentation¶
Caller identity fields are described (#105)¶
The dataclass identifying an MCP caller now says what each field holds. They previously reached the rendered API reference as “Alias for field number 0” or as a bare name carrying only its type.
Development¶
Safer pull-request testing with mcp_swap.py
use-local --pr N points installed agent CLIs at a pull request without a
checkout and verifies the server before changing their configuration. Swaps
preserve unrelated config text, file permissions, and symlink targets, while
retaining the original recovery data across failed or concurrent updates.
(#115)
CI actions updated to current majors¶
Workflow actions moved to their current major releases: actions/checkout v7,
actions/cache v6, astral-sh/setup-uv v9.0.0, codecov/codecov-action v7,
and dorny/paths-filter v4. Workflow behavior is unchanged, though setup-uv no
longer prunes the uv cache, so the first run after this repopulates it.
Minimum ruff>=0.16.0 (was unpinned)
The dev and lint dependency groups now require ruff 0.16.0 or newer, so
contributors and CI see the same diagnostics rather than whichever ruff each
machine resolved. From this release on, ruff format also formats Python code
blocks inside Markdown, so uv run ruff format . reaches the README and the
docs/ tree. Neither the lint nor the format change rewrites anything here.
(#113)
ruff’s curated default rule set is now enabled
[tool.ruff.lint] layers this project’s linters on with extend-select instead
of select. An explicit select replaces ruff’s default set rather than adding
to it, so the project had been silently opting out of every rule it did not name
itself; enabled rules go from 351 to 565. The findings that surfaced are fixed —
re.DOTALL spelled out in place of re.S, str.removesuffix and min() in
scripts/mcp_swap.py, the server instruction blob built as an f-string, and the
wrapped marker fixtures parenthesized so a dropped comma can no longer pass for
a deliberate join. scripts/mcp_swap.py is now executable and its shebang points
at uv run --script, so running it directly honours the PEP 723 metadata instead
of failing to import tomlkit under a bare python3. Two rules are scoped
ignores with their reasons recorded in
pyproject.toml: exec in docs/conf.py, which is how Sphinx reads version
metadata, and the except Exception boundaries in the error middleware, the
batch runners, the best-effort tmux probes, and mcp_swap’s config rollback.
(#113)
libtmux-mcp 0.1.0a19 (2026-07-25)¶
libtmux-mcp 0.1.0a19 makes wait_for_text see the output it was asked to watch for, and puts a ceiling under every wait. The tool anchored one row below the cursor’s position at entry, which on a quiescent pane is exactly where the next line lands, so the case it exists for — output you did not author, a daemon printing a single ready line — could not match at all. Waits are now bounded by a server ceiling, cancellable without orphaning their tmux child, and report an outcome that distinguishes a command that never ran from output that did not match from a pager owning the pane. The wait API changes shape in the process: pattern becomes patterns, stop markers end a wait early, and wait_for_content_change is removed in favour of wait_for_text(patterns=null).
Breaking changes¶
wait_for_text takes a list of patterns, and no wait outruns the
server
The single pattern string becomes patterns, a list — or null to wait for
any new output at all. A new stop list carries failure markers, and a hit ends
the wait immediately. Every wait tool is now capped by
LIBTMUX_MCP_WAIT_MAX_SECONDS (30 s by default): an over-large timeout is
clamped rather than rejected, and the result reports the value actually
enforced. The cap is not scoped to wait_for_text —
run_command and wait_for_channel previously honoured a
caller timeout verbatim and now enforce the same limit, so a call that used to
block for an hour returns at the ceiling. (#100)
# Before
wait_for_text(pane_id="%1", pattern="ready", timeout=600)
# After — patterns is a list, stop bails out early, timeout is clamped
wait_for_text(pane_id="%1", patterns=["ready"], stop=["error"], timeout=600)
wait_for_content_change and ContentChangeResult are removed
Use wait_for_text(patterns=null), which waits for any new output and
additionally excludes pre-existing scrollback and stale paint from the change
predicate. (#100)
WaitForTextResult reports an outcome, not loose
flags
One outcome field states how the wait ended — it matched, it saw any new
output, it hit a stop marker, the pane was on the alternate screen, or it timed
out. risk_band_warned is gone; the history-limit trim signal is delivered only
as an MCP warning notification. Clients that cannot surface notifications should
compose tmux wait-for -S into the command and use
wait_for_channel, which does not read the pane at all. (#100)
Self-bounded tools can no longer be nested in a batch wrapper
run_command, wait_for_text and
wait_for_channel each block under the wait ceiling, and the batch
wrappers run their operations serially with no aggregate deadline — batching one
multiplied that ceiling by the operation count. All three wrappers now reject
all three tools per operation. (#100)
What’s new¶
A wait explains itself
Alongside outcome, the result names which entry fired, whether anything new
was written, whether the match was stale text already on screen when the wait
began, and a bounded tail of the rows it was watching. Every field is observed
rather than inferred, so a timeout tells you which of “the command never ran”,
“output arrived and did not match”, and “a pager owned the pane” actually
happened. Progress notifications carry the remaining budget rather than a
constant string. (#100)
Pagers and editors are reported instead of matched on paint
Inside less, an editor, or any other full-screen program, a pane capture
returns rows the program has already painted, which a naive wait reported as new
output. The wait now says the pane is on the alternate screen so the agent reads
it instead of trusting a false match, and resumes ordinary matching when the
program exits. (#100)
Fixes¶
wait_for_text sees the row the cursor was on
The wait anchored one row BELOW the cursor’s position at entry, and on a
quiescent pane that is exactly where the next line of output lands — the cursor
sits at the end of the prompt. So the case the tool is reserved for, output you
did not author such as a daemon printing a single ready line, could not match
at all: the wait burned its whole budget and then reported that the pane had
been silent. Measured before the fix, a bare READY_MARK missed at 5.02 s with
the marker provably on screen, while the same marker written with a leading
newline matched in 1.017 s.
The anchor now lands on the entry row and that row’s pre-existing content is suppressed by value rather than by index, so carriage-return spinners and single-line status updates become matchable too. A stale match still does not end the wait — it keeps waiting for a fresh occurrence, so re-running a command whose output looks identical continues to work. (#100)
Waits no longer hang, leak, or claim a result they did not get
Four failures with one theme — a wait that reports something other than what
happened. An unresponsive tmux could leave the process, and Ctrl-C, hanging
indefinitely, because waits reached tmux through worker threads that cannot be
cancelled. Cancelling a call returned immediately but left the tmux child
running for the rest of its timeout — measured at 13 s of orphan for a
15-second wait cancelled at 2 s. Retrying a wait restarted its budget, so a
bounded wait could still run unbounded. And wait_for_channel
reported success when the tmux server shut down without ever signalling,
because tmux wait-for exits 0 for both — it now re-probes the server before
claiming a signal. (#100)
pipe_pane writes to the path you asked for
output_path reached tmux’s format expander before /bin/sh, and only the
shell layer was escaped. Both of tmux’s expansions were live, so #Session.log
lost its #S to the session name and 100%done.log became 10025one.log via
strftime — the log landed on a different file while the tool reported back the
path requested. The path is now escaped for both.
Failures name the thing that actually failed
A missing dependency was reported as “libtmux-mcp requires fastmcp” whichever module was missing — close to the one cause it cannot be, since fastmcp is a hard dependency. A pane, window, or session that could not be resolved raised “Could not find object” without naming the target the caller passed. And the unknown-argument hint stopped naming the connected client after an SDK field rename. All three now say which thing failed.
The interrupt recipe no longer reports success as failure
The interrupt_gracefully prompt told agents to treat a ^C echo as proof that
a program had trapped SIGINT and kept running. The terminal echoes ^C whenever
SIGINT is delivered, dead process or not, so the marker fired on the success
path — and the recipe’s next step is SIGQUIT and then killing the pane. It now
brackets the interrupt with get_pane_info and trusts
pane_current_command, the one signal that discriminates.
Documentation¶
New topic: Waiting
Which wait to reach for, how wait_for_text decides something is
new, how to read each result field, and what a wrong search pattern actually
costs — one bounded call whose tail usually already contains the text you
should have searched for. It also records the designs that were measured and
rejected, so they are not re-litigated from intuition: MCP background tasks, an
event-driven pipe-pane, a deferred resume handle, and removing the tool.
The wait ceiling’s rationale was wrong
The ceiling was justified as stopping a bad wait from stalling the shared MCP
connection. There is no such stall: the wait tools await throughout and FastMCP
serves the connection concurrently — measured, a tool awaiting 6 s served 58
interleaved calls at a 3.4 ms median, against exactly one at 6014 ms for a
control that blocked the event loop. The ceiling is unchanged and still right;
it bounds the agent’s turn, which MCP gives it no way to abandon mid-call.
LIBTMUX_MCP_WAIT_MAX_SECONDS is now documented in Configuration.
Development¶
mcp_swap.py keeps the pre-swap config on a repeat swap
Swapping a CLI layer that was already swapped and not yet reverted used to take
a fresh backup of the swapped config, so revert restored an already-swapped
config and the only pristine copy was lost. A re-swap now keeps the first
backup, backup files are never overwritten, and doctor no longer calls
untracked backups “safe to delete”.
mcp_swap.py doctor and use-local --env
A read-only doctor subcommand reports the effective MCP-swap environment —
which server name each CLI points at, un-reverted swaps, orphaned or missing
backups, and auth-overriding environment variables. use-local --env injects
environment entries such as an isolated LIBTMUX_SOCKET at swap time instead of
requiring a hand-edit post-edit.
libtmux-mcp 0.1.0a18 (2026-07-12)¶
libtmux-mcp 0.1.0a18 raises the libtmux floor to 0.62.0 and tightens how the server handles lookups that cannot succeed. A readonly call carrying a stale or ambiguous id now fails on the first attempt instead of paying a second tmux round-trip and a backoff window to fail identically, and an ambiguous target — a window shared between sessions, matched by name or index — now comes back with a recovery hint that says to target it by id instead of an unexplained error. The floor bump is what makes both fixes possible: libtmux 0.62.0 folds its query errors into the libtmux.exc.LibTmuxException hierarchy the server keys on.
Dependencies¶
Minimum libtmux>=0.62.0 (was >=0.61.0). Picks up libtmux 0.62.0, which
re-parents libtmux.exc.MultipleObjectsReturned and
libtmux.exc.ObjectDoesNotExist under libtmux.exc.LibTmuxException.
The retry and error-mapping fixes below rely on that hierarchy. (#98)
Fixes¶
A stale id fails once instead of twice
Readonly tools retried on any libtmux.exc.LibTmuxException, the right
trigger for the transient socket errors libtmux wraps but wrong for the rest of
that family — a pane that does not exist will not exist on the second look, and
an ambiguous match does not become unambiguous during a backoff window. A lookup
that a retry cannot change now fails on the first attempt, without the redundant
tmux round-trip and its backoff delay. (#98)
An ambiguous target says how to disambiguate it
A window shared between sessions — through a grouped session or link-window —
is listed once per session that holds it, so a name or index can match more than
one row and the lookup raises libtmux.exc.MultipleObjectsReturned. That
reached the agent with no recovery hint. It now says to target the object by id
instead. (#98)
libtmux-mcp 0.1.0a17 (2026-07-11)¶
libtmux-mcp 0.1.0a17 makes best-effort shell-history suppression the default for run_command — calls that omit suppress_history now inherit the server default, and setting LIBTMUX_SUPPRESS_HISTORY to 0 restores the previous behavior. Spawned shells gain the same hygiene: create_session, create_window, split_window, and respawn_pane accept suppress_persistent_history=true for best-effort no-disk history controls. create_window and split_window also accept per-process environment mappings, and the MCP install picker now covers Grok CLI and Antigravity.
Breaking changes¶
History suppression now defaults on for run commands
MCP clients now see the effective server default for suppress_history on run_command, and calls that omit the argument inherit it. The default requests best-effort history suppression. To run multiline input while suppression is enabled, pass suppress_history=false; to retain the previous omitted-call behavior, set LIBTMUX_SUPPRESS_HISTORY to 0 and restart the server. Direct Python calls remain unchanged. See Configuration for details. (#91)
What’s new¶
History controls for spawned shells
create_session, create_window, split_window, and respawn_pane now let callers opt into best-effort no-disk shell-history controls with suppress_persistent_history=true. Session controls reach the initial and future panes, while the other tools limit the setting to the process launched by that call.
History suppression reduces accidental persistence, but it is not secret transport: shells can override the controls, and terminal output and other observation surfaces remain visible. See History suppression for shell behavior and Safety tiers for the remaining boundaries. (#91)
Per-process environments for windows and panes
create_window and split_window now accept per-process environment mappings or JSON object strings, so callers can configure new windows and panes without changing the tmux session environment. respawn_pane now accepts the same JSON object form for its existing environment input. Values can still surface in host process inspection and child environments; use credential references, not literal credentials. See Safety tiers for details. (#91)
Documentation¶
Grok CLI and Antigravity in the MCP install picker
The MCP client picker now covers Grok CLI and Antigravity (Google’s agy) alongside Claude, Codex, Gemini, and Cursor. Grok registers through its own grok mcp add verb (~/.grok/config.toml); Antigravity has no such verb, so its mcpServers snippet is pasted into ~/.gemini/config/mcp_config.json. (#89)
libtmux-mcp 0.1.0a16 (2026-07-04)¶
libtmux-mcp 0.1.0a16 raises the libtmux floor to 0.61.0, picking up libtmux 0.61.0’s hardening of the tmux 3.7 patch line. The server runs cleanly against tmux 3.7a and 3.7b, and tmux 3.2a-3.6 keep working unchanged. The bump requires no server code or test changes.
Dependencies¶
Minimum libtmux>=0.61.0 (was >=0.60.0). Picks up libtmux 0.61.0’s hardening of the tmux 3.7 patch line, so the server runs cleanly against tmux 3.7a and 3.7b. Every 3.7-only surface stays version-gated, so tmux 3.2a-3.6 keep working unchanged, and the bump requires no server code or test changes. (#86)
libtmux-mcp 0.1.0a15 (2026-06-28)¶
libtmux-mcp 0.1.0a15 raises the libtmux floor to 0.60.0, which completes tmux 3.7 feature parity at the library layer the server wraps — floating panes, typed tmux 3.7 options, new pane format variables, and new command flags. Every 3.7-only surface is version-gated, so tmux 3.2a-3.6 keep working unchanged. The dev and test dependency groups also move to pytest 9.1.0.
Dependencies¶
Minimum libtmux>=0.60.0 (was >=0.58.0). Picks up libtmux 0.59.0’s initial tmux 3.7 support and 0.60.0’s completion of tmux 3.7 feature parity — floating panes, typed tmux 3.7 server, session, window, and pane options, new pane format variables, and new command flags. Every 3.7-only surface is version-gated, so tmux 3.2a-3.6 keep working unchanged, and the bump requires no server code or test changes. (#85)
Development¶
Minimum pytest>=9.1.0
The dev and test dependency groups now require pytest 9.1.0 or newer. (#83)
libtmux-mcp 0.1.0a14 (2026-06-14)¶
libtmux-mcp 0.1.0a14 adds tier-aware tool batching. call_readonly_tools_batch, call_mutating_tools_batch, and call_destructive_tools_batch run an ordered list of existing MCP tools in a single call and return a per-operation result for each, preserving every nested tool’s own structured output. Each wrapper caps the safety tier of the calls it will make — regardless of the server’s LIBTMUX_SAFETY tier — and on_error selects stop-at-first-failure or continue-and-report handling. Aggregate results stay within the server’s response limit.
What’s new¶
Tier-aware tool batching
call_readonly_tools_batch, call_mutating_tools_batch, and call_destructive_tools_batch run an ordered list of existing MCP tools in a single call and return a per-operation result for each, preserving every nested tool’s own structured output. Each wrapper caps the safety tier of the calls it will make — the readonly wrapper refuses mutating or destructive operations, and the mutating wrapper refuses destructive ones — regardless of the server’s LIBTMUX_SAFETY tier. Nested calls keep their normal schema validation, middleware, and safety checks, and on_error selects stop-at-first-failure or continue-and-report handling. Large aggregate results stay within the server’s response limit — oversized nested payloads are dropped (with the truncation flagged in the result), and very large operation lists are rejected rather than allowed to overflow it. (#79)
libtmux-mcp 0.1.0a13 (2026-06-13)¶
libtmux-mcp 0.1.0a13 adds send_keys_batch for sending an ordered batch of raw key/text operations to tmux panes in a single call, with per-operation results, stop-or-continue error handling, and an optional timeout that bounds both the batch and each send. Argument-validation failures also stop echoing the rejected input into the server’s logs and tool error results, so a secret-bearing argument can no longer surface there.
What’s new¶
Ordered raw input with send_keys_batch
send_keys_batch sends several raw-input operations in order and returns per-operation success or failure metadata. Choose stop-at-first-failure or continue-and-report handling, and set an optional timeout to bound a long batch and each underlying send. It is intentionally scoped to keystrokes and text input for TUIs or persistent shells; authored command completion stays with run_command, and repeated observation stays with capture_since. (#49, #61)
Fixes¶
Argument-validation failures no longer echo rejected input
When a tool call fails argument-schema validation, the error result and the server’s invalid-argument log record now omit the rejected input values, so a secret-bearing argument can no longer surface in logs or error text. (#78)
libtmux-mcp 0.1.0a12 (2026-06-13)¶
libtmux-mcp 0.1.0a12 hardens the MCP server’s read-only and safety surface and adds a one-call run_command tool. Read-only tools can no longer trigger tmux format-job shell evaluation, an invalid safety tier fails closed instead of exposing write tools, and large successful results keep their structured payload. Panes and windows also gain liveness and active-pane metadata, and the package ships a py.typed marker. The fastmcp floor rises to 3.4.2 to pick up its explicit starlette>=1.0.1 floor (CVE-2026-48710).
Dependencies¶
Minimum fastmcp>=3.4.2 (was >=3.4.0). Picks up fastmcp 3.4.1’s explicit starlette>=1.0.1 floor, so installs can no longer resolve to a Starlette version affected by CVE-2026-48710 — previously this was constrained only transitively through mcp. (#77)
What’s new¶
One-call command completion with run_command
run_command runs a shell command in a pane, waits for it to finish, and returns the exit status, timeout state, and tail-preserved output in a single call — no manual send_keys + wait_for_channel + capture_pane sequence. The command runs in a subshell so its state changes don’t leak into later calls, and suppress_history keeps secret-bearing commands out of shell history where the shell ignores space-prefixed input. (#73)
Richer, typed pane and window metadata
snapshot_pane now reports pane_pid, pane_dead, and alternate_on for liveness and alternate-screen decisions, and window results carry active_pane_id for reliable follow-up targeting. The package also ships a py.typed marker so downstream type checkers see its inline annotations. (#75)
Fixes¶
Read-only tools no longer evaluate tmux #() format jobs
search_panes and display_message are advertised as read-only, but tmux #(...) formats schedule shell jobs during expansion. Both now reject or route around #() so a read-only call can never spawn a shell. (#68, #69)
Invalid LIBTMUX_SAFETY fails closed
An unrecognized LIBTMUX_SAFETY value now falls back to readonly instead of mutating, so a typo in the safety tier can no longer expose write tools the operator meant to hide. (#71)
Large structured results keep their structured payload
The global response backstop was truncating big successful results into text-only responses before tool-level caps ran, dropping the structured metadata schema-bearing tools depend on. It now matches FastMCP’s 1 MB default, leaving per-tool line caps to handle terminal truncation. (#70)
clear_pane clears scrollback reliably
clear_pane now uses libtmux’s single-call reset path; the previous two-call sequence could leave scrollback intact. Its annotations also disclose that it is destructive and non-idempotent. (#74)
Stdio transport pinned at startup
The server runs with an explicit stdio transport so an inherited FastMCP transport environment can’t change its startup surface and break stdio clients, and --help / --version resolve locally without starting the server. (#72)
libtmux-mcp 0.1.0a11 (2026-06-06)¶
libtmux-mcp 0.1.0a11 redesigns how tool failures reach agents. Error messages now arrive exactly as raised — no more Internal error: mangling — with structured detail and recovery hints that tell agents what to do next, from stale pane ids to stray arguments leaked by client schedulers. Expected, agent-correctable failures log at WARNING so ERROR records always mean an operator should look. The fastmcp floor rises to 3.4.0 to build on its error-result and log-level support.
Dependencies¶
Minimum fastmcp>=3.4.0 (was >=3.2.4). Enables the error-result and log-level improvements below; the bump alone also restores resource titles on the tmux:// hierarchy and brings MCP-compliant telemetry span attributes.
What’s new¶
Tool errors arrive clean, with structured detail and recovery hints
Failed tool calls now return their message exactly as raised — previously every failure was prefixed with Internal error:. Error results carry a structured _meta payload (error_type, expected, suggestion), and not-found errors point at list_sessions / list_windows / list_panes to resolve stale or guessed ids. (#64)
Unknown tool arguments get a do-this-next hint
When a call includes arguments a tool doesn’t accept, the error result now names exactly which argument(s) to remove or fix. When the stray key is wait_for_previous — a scheduling flag Gemini CLI can leak into batched tool calls — the hint says so and names the connected client, so agents recover in one read instead of re-deriving the problem from a validation traceback. (#64)
Expected tool failures log at WARNING, not ERROR
Agent-correctable failures — unknown ids, invalid arguments, safety-tier denials, transient tmux errors — now log at WARNING in the server’s log stream. A missing tmux binary and unexpected exceptions stay at ERROR, so ERROR records are always worth attention. (#64)
libtmux-mcp 0.1.0a10 (2026-05-24)¶
What’s new¶
Incremental pane observation with capture_since
capture_since gives agents a cursor-based way to observe a pane without re-reading the same terminal output on every turn. The first call returns the current visible screen and an opaque cursor; later calls return only rows written or rewritten after that cursor while tmux still retains the needed history. If scrollback was cleared or trimmed, the result sets lines_missed=True, returns a conservative current visible capture, and issues a fresh cursor. Malformed cursors, cross-pane replay, pane death, and pane respawn fail clearly instead of silently switching processes. (#60)
libtmux-mcp 0.1.0a9 (2026-05-24)¶
libtmux-mcp 0.1.0a9 tightens pane polling correctness for agents waiting on terminal output. Search and wait tools now handle wrapped content, history-limit risk reporting, and pane lifecycle changes with clearer results instead of silent false positives.
Fixes¶
search_panes matches wrap-spanning slow-path text
search_panes now joins tmux-wrapped visual rows when it uses the captured-content slow path, so long build, test, or log lines can match across the pane width. Plain fast-path searches still use tmux’s native visual-row search; pass regex=True or a content range when exact text may span a wrap boundary. (#55)
wait_for_text reports trim-risk warnings in results
wait_for_text now returns risk_band_warned=True when polling enters tmux’s history-limit trim-risk band. Clients that do not surface MCP warning notifications can still detect that matching was best-effort and switch to wait_for_channel for deterministic command completion. (#54)
wait_for_content_change fails on pane lifecycle changes
wait_for_content_change now raises a tool error when the watched pane dies or is respawned during the wait. Those lifecycle changes invalidate the entry content baseline, so callers no longer receive a misleading changed=True result for a different pane process. (#53)
libtmux-mcp 0.1.0a8 (2026-05-23)¶
libtmux-mcp 0.1.0a8 bumps libtmux to 0.58.0, fixing session and window listing on systems whose locale is not UTF-8.
Dependencies¶
Minimum libtmux>=0.58.0 (was >=0.56.0). Picks up the 0.57.x command-coverage and client-awareness releases and the 0.58.0 non-UTF-8 locale fix — session and window listing could silently return empty results when the system locale was not UTF-8.
What’s new¶
Installer picker now covers config scope and file locations
The MCP install widget on the front page, Quickstart, and MCP Clients now lets you pick a config scope per LLM client alongside the install method — local / user / project for Claude Code, user / project for Codex and Gemini, project / global for Cursor. JSON-only clients (Cursor, Claude Desktop) also show the destination config-file path next to the snippet so the paste target is never a guess. Your scope choice is remembered per-client, so switching between clients restores each one’s last selection.
Installer picker can apply or bypass dependency cooldowns
A new Configure cooldowns control on the install widget lets you tack a cooldown onto the snippet without leaving the page. Pick a delay in days (uv’s --exclude-newer, pip’s --uploaded-prior-to, pipx’s --pip-args) to wait out community vetting before pulling a fresh release, or pick Bypass any global cooldown to skip a ~/.config/uv/uv.toml cutoff and grab the latest libtmux-mcp anyway. The setting persists across pages, and the embedded What are cooldowns? expander links to the Datadog Security Labs writeup and cooldowns.dev if you want the supply-chain context. (#31)
Documentation¶
MCP install widget ships working cooldown snippets
The install widget on the front page, Quickstart, and MCP Clients now emits a runnable snippet in every method × cooldown cell. uvx days panels apply the cooldown to transitive dependencies while exempting libtmux-mcp itself, so a fresh release stays installable. pipx and pip days panels fall back to the bare install command — neither tool exposes a per-package cooldown override today — and their cooldown note redirects readers to the uvx tab when they want true cooldown enforcement. (#58)
libtmux-mcp 0.1.0a7 (2026-05-16)¶
Breaking changes¶
wait_for_text waits for new output, not stale scrollback
wait_for_text now matches lines written after the call begins. The previous behaviour returned found=True on the first poll whenever the pattern already lived in the pane, so agents synchronising on command output got the wrong result. For the synchronous “is the pattern in the pane right now?” case, call search_panes instead.
Baseline-loss events surface as ToolError: pane respawn, pane death, clear-history, and any other event that drops history below the entry baseline. Pane resize that pulls lines back from history into the visible region is exempted — the anchor stays valid.
Trim during heavy output near history-limit can’t be reliably detected from polling alone. When polling approaches that limit, the tool emits a notifications/message warning so MCP clients can decide whether to keep waiting, retry, or switch to wait_for_channel. For deterministic command completion, compose tmux wait-for -S <channel> into the shell command and call wait_for_channel. (#45)
wait_for_text drops content_start / content_end
The new baseline anchor follows the pane’s grid position automatically, so the manual capture-range parameters have no remaining purpose. Drop them from call sites. (#45)
# Before
wait_for_text(pattern="OK", content_start=-100)
# After
wait_for_text(pattern="OK")
Wait result models drop timed_out
WaitForTextResult and ContentChangeResult drop the timed_out field. It was mechanically the boolean negation of the primary result (not found / not changed) and carried no information beyond that. Callers should switch to not result.found / not result.changed. (#47)
# Before
result = wait_for_text(pattern="OK")
if result.timed_out:
...
# After
result = wait_for_text(pattern="OK")
if not result.found:
...
Dependencies¶
Minimum libtmux>=0.56.0 (was >=0.55.1). Picks up libtmux 0.56’s typed wrappers for the tmux commands the server invokes — the MCP now uses libtmux’s public API instead of raw command-line escapes for pane lifecycle, scrollback, and session navigation. (#46)
Fixes¶
wait_for_channel recipe no longer exits the parent shell
The run_and_wait prompt template previously appended exit $__mcp_status to its shell payload to preserve the command’s exit status. In an interactive shell that exits the shell itself, destroying single-pane sessions. The recipe now signals completion via tmux wait-for -S without exiting, and the equivalent example in Wait for channel is similarly fixed. Exit-status preservation in interactive shells is documented as out-of-scope; agents that need it should inspect the captured output for command-specific success markers. (#47)
wait_for_text matches patterns across visually-wrapped lines
Long patterns like "Build failed: module not found" that tmux wraps at the pane’s column width are now matched against the joined logical line. Previously the wrap split the pattern across two captured rows and neither row matched. The joined line is returned in matched_lines and can exceed the pane width. (#45)
wait_for_text rejects misused pattern / interval / timeout
Empty pattern, interval below 10 ms, and non-positive timeout each raise ToolError at entry. Previously they silently matched every line, spun the tmux server in a tight loop, or completed a surprise single probe. (#45)
Documentation¶
Wait family is re-framed around wait_for_channel as the deterministic primitive
The send_keys docstring, server system instructions, wait_for_text docstring, and the user-facing quickstart, gotchas, prompting, troubleshooting, recipes, and send-keys topics now point agents at wait_for_channel with composed tmux wait-for -S for command completion. wait_for_text and wait_for_content_change are reframed as the fallbacks for output the agent does not author. The run_and_wait recipe shows the canonical safe-completion pattern. (#45)
libtmux-mcp 0.1.0a6 (2026-05-09)¶
libtmux-mcp 0.1.0a6 is the activation and registration cleanup release. It makes the server much easier for MCP clients to discover from ordinary “pane”, “window”, and “session” prompts, standardizes new setup docs around the tmux registration slug, and adds migration guidance for existing libtmux registrations. Existing installs keep working; the release changes defaults and documentation so new installs line up with the tool prefix users actually see.
What’s new¶
Bare tmux prompts now find the server
Agents no longer need the word “tmux” in every prompt before this server becomes relevant. The server instructions now name the positive cases users naturally write, such as “split this pane”, “current window”, and “this session”, while steering clients away from unrelated browser tabs, editor splits, tiling-window-manager panes, and notebook cells. The discovery anchors most likely to answer those prompts - list_panes, list_windows, and snapshot_pane - also carry the preload hint used by clients that keep a small always-available schema set. See Agent prompting guide for the user-facing activation contract. (#37)
New installs are documented as tmux
The recommended registration slug is now tmux, matching the MCP serverInfo.name and the mcp__tmux__* tool-call prefix surfaced by clients. The package name, import name, and existing user-created libtmux registrations are unchanged; only the documented install target moves to the clearer slug. See Migration notes for the walkthrough. (#40)
Documentation¶
Hierarchy-noun tool titles now include a tmux qualifier in catalog-style displays, so entries like “List tmux Sessions” stay understandable when a client shows tools from several MCP servers together. Tmux-specific verbs such as send_keys, pipe_pane, snapshot_pane, capture_pane, and paste_text keep their shorter names because the verb already identifies the tmux operation. (#38)
The Agent prompting guide guide gains an activation and discovery section with positive triggers, anti-triggers, a client-level alwaysLoad example, and a project-instructions fragment for teams that want stronger bias toward tmux when users say “this pane” or “current window”. (#39)
libtmux-mcp 0.1.0a5 (2026-05-06)¶
libtmux-mcp 0.1.0a5 makes layout-relative pane targeting a first-class workflow. Agents can ask for “the bottom-right pane” directly, reason from typed geometry returned on pane models, and avoid parsing tmux format strings for common layout questions. The release also polishes the documentation site after the theme migration and extends the local MCP swap helper so Claude users can choose project-local or user-global config.
What’s new¶
Layout-relative pane targeting via find_pane_by_position
find_pane_by_position resolves a window corner - top-left, top-right, bottom-left, or bottom-right - to a single PaneInfo. This replaces the old workaround of calling display_message with #{pane_at_bottom} / #{pane_at_right} and parsing raw string output. Single-pane and otherwise ambiguous layouts resolve deterministically, so the common “use the only pane” case does not require an extra branch in the agent workflow. (#34)
Pane responses now carry geometry
PaneInfo, PaneContentMatch, and PaneSnapshot now include window-relative coordinates, edge predicates, and the pane TTY. That means an agent can inspect returned data to decide whether a pane is at the left edge, bottom edge, or a specific corner instead of making another tmux query. The older pane_width and pane_height fields intentionally keep their existing string typing in this alpha; changing those fields is left for a later breaking-change window. (#34)
Documentation¶
The docs site moves onto gp-furo-theme with sphinx-vite-builder handling the theme asset pipeline, so the rendered site follows the same gp-sphinx docs stack used by the rest of the project documentation. (#33)
The install widget and above-the-fold typography no longer shift after first paint. Saved install-widget choices now render in the correct panel immediately, and the fonts and logo used on the landing page are loaded early enough to avoid visible swaps. (#36)
The new corner-finder appears anywhere a reader is likely to look for targeting guidance: the homepage tool chain, tools index, pane tools page, prompting guide, recipes, and example responses. Existing display_message guidance now points layout-relative pane selection at find_pane_by_position instead of teaching agents to expand pane-edge format variables by hand. (#34)
Development¶
scripts/mcp_swap.py gains --scope {user,project} for Claude. Project scope keeps the safe local override used while working in this checkout; user scope intentionally changes the fallback used from other projects. Codex, Cursor, and Gemini have only a global layer, so the flag is accepted there without creating a fake per-project mode. (#35)
libtmux-mcp 0.1.0a4 (2026-05-02)¶
libtmux-mcp 0.1.0a4 adds pane recovery and closes the last gap in the core hierarchy’s single-object metadata reads. Agents can restart a stuck pane without losing its identity, fetch known sessions or windows directly, and use a bundled development script to point local MCP clients at a checkout. The respawn_pane parameter name settles on shell before the stable API, replacing the earlier alpha spelling shell_command.
What’s new¶
Restart wedged panes in place with respawn_pane
respawn_pane restarts the process in an existing tmux pane while preserving the pane_id and the window layout. That is the right recovery primitive for long-running agent sessions: kill_pane followed by split_window creates a new pane, invalidates stored handles, and can disturb the surrounding layout. The final alpha API uses shell for the replacement command to match split_window and libtmux’s upstream respawn naming. (#27)
Direct metadata reads for sessions and windows
get_session_info and get_window_info complete the server/session/window/pane metadata pattern alongside get_server_info and get_pane_info. Callers that already have an ID or a scoped index no longer need to call a list tool and filter the response. Buffers, hooks, options, and environment values keep their existing show/load/read tools rather than gaining redundant get_*_info wrappers. (#27)
Documentation¶
Safety tiers now documents respawn_pane as a mutating recovery tool with real process side effects, including the kill=true default and the visibility tradeoffs of passing command or environment data through tmux. The same update refreshes socket-guard notes for macOS, tightens guidance around display_message and pipe_pane, and adds dedicated tool pages for the new recovery and metadata tools. (#27)
Development¶
scripts/mcp_swap.py lands as local dogfooding infrastructure for Claude, Codex, Cursor, and Gemini. It detects installed clients, shows the current libtmux-mcp registration, rewrites config to run this checkout through uv, and reverts from backups. The just mcp-use-local and just mcp-revert recipes wrap that flow for branch QA. (#27)
libtmux-mcp 0.1.0a3 (2026-04-19)¶
libtmux-mcp 0.1.0a3 is a focused correctness release after the FastMCP alignment work. It fixes caller identity across multiple tmux sockets, moves channel waits off the FastMCP event loop, and raises the libtmux floor to pick up upstream pytest-fixture cleanup. The result is less surprising metadata in multi-server setups and fewer stalled clients during long waits.
Dependencies¶
Minimum libtmux>=0.55.1 (was >=0.55.0) for tmux-python/libtmux#661, which fixes pytest-plugin socket cleanup under /tmp/tmux-<uid>/.
Fixes¶
Pane caller identity is socket-aware
The is_caller pane annotation no longer treats pane %0 on every tmux server as the current agent pane just because the pane IDs match. The annotation now compares the caller’s tmux socket as well, matching the self-protection checks used around destructive tools. This fixes PaneInfo, PaneSnapshot, and PaneContentMatch responses in multi-socket environments. (#22, fixes #19)
Channel waits no longer stall the MCP server
wait_for_channel and signal_channel now run their blocking tmux subprocess work outside the event loop. Other tool calls and client keepalives can continue while a wait is pending, matching the async behavior already used by wait_for_text and wait_for_content_change. (#21, fixes #18)
libtmux-mcp 0.1.0a2 (2026-04-19)¶
libtmux-mcp 0.1.0a2 is the FastMCP alignment release. It expands the server from a basic tmux wrapper into a broader agent automation surface with discovery, waits, buffers, hooks, pane/window navigation, prompt recipes, middleware, and bounded terminal output. The release includes one response-shape breaking change: search_panes now returns a paginated SearchPanesResult instead of a bare list. (#15)
Breaking changes¶
search_panes returns SearchPanesResult
search_panes now returns a result object with matches and pagination metadata instead of the earlier list[PaneContentMatch] shape. Existing callers should iterate over .matches; callers that search large tmux workspaces can also inspect truncated, truncated_panes, total_panes_matched, offset, and limit.
# Before
for match in search_panes(...):
...
# After
for match in search_panes(...).matches:
...
Minimum fastmcp>=3.2.4 (was >=3.1.0). The newer FastMCP release is required for ReadonlyRetryMiddleware and per-parameter input-schema descriptions.
What’s new¶
Discovery and synchronization tools
list_servers discovers live tmux servers, including explicitly provided socket paths. The wait family adds content waits (wait_for_text, wait_for_content_change) and tmux channel synchronization (wait_for_channel, signal_channel) so agents can wait for explicit milestones instead of sleeping or repeatedly capturing panes. (#15)
Buffers, hooks, and richer pane/window control
Agent-namespaced buffer tools - load_buffer, paste_buffer, show_buffer, and delete_buffer - support staged multi-line input without overloading keystroke simulation. Read-only hook tools (show_hook, show_hooks) expose tmux hook state without adding write-hooks that survive process death. The pane and window surface grows with snapshot_pane, pipe_pane, display_message, paste_text, select_pane, swap_pane, select_window, move_window, enter_copy_mode, and exit_copy_mode. (#15)
Prompt recipes for common agent workflows
Four MCP prompts ship for repeatable tmux work: run_and_wait, diagnose_failing_pane, build_dev_workspace, and interrupt_gracefully. Clients that only expose tools can opt into tool-shaped prompt access with LIBTMUX_MCP_PROMPTS_AS_TOOLS=1. See Prompts for the prompt catalog. (#15)
Middleware for safer long-running automation
The middleware stack now includes AuditMiddleware for digest-redacted argument summaries, SafetyMiddleware for safety-tier visibility, ReadonlyRetryMiddleware for transient readonly libtmux failures, and TailPreservingResponseLimitingMiddleware for oversized tool output. Timing and error-handling middleware round out the request path. (#15)
Tail-preserving bounded output
capture_pane, snapshot_pane, and show_buffer accept max_lines and report whether older lines were trimmed. Truncation keeps the newest terminal output, which is usually the active prompt or most recent command result. Pass max_lines=None when a caller intentionally wants the full scrollback or buffer. (#15)
Better schemas and startup failures
Tool input schemas now include parameter descriptions extracted from docstrings, so clients can explain arguments without separate docs lookups. Startup also fails with a clear RuntimeError when tmux is not available on PATH, and active_pane_id tells callers which pane was created by create_session. (#15)
Fixes¶
search_panesneutralizes tmux format-string injection in the regex fast path.The macOS self-kill guard resolves the live tmux socket before falling back to
TMUX_TMPDIRreconstruction.The
build_dev_workspaceprompt uses real tool parameter names, avoids waiting for prompts after screen-grabbing commands, and replaces a Linux-specific log default withlog_command.ReadonlyRetryMiddlewarelogs retry warnings underlibtmux_mcp.retry.
Documentation¶
The tools documentation gains category pages for Buffer tools, Hook tools, and Tools. The pane tools docs explain the SearchPanesResult migration, while Safety tiers documents the audit log, socket caveats, pipe_pane, and set_environment. (#15)
libtmux-mcp 0.1.0a1 (2026-04-13)¶
libtmux-mcp 0.1.0a1 expands the tmux control surface around the workflows agents actually perform after they find a pane. It adds richer screen snapshots, waits, navigation, pane/window movement, copy-mode entry points, live logging, and bracketed paste support. The docs also adopt the gp-sphinx visual stack so the generated API and tool pages become easier to scan. (#11)
What’s new¶
Screen snapshots and waits
snapshot_pane returns terminal content together with cursor position, copy-mode state, and scroll position in a single read. wait_for_content_change lets an agent wait for any visible update when it does not know the exact text to expect. PaneSnapshot and ContentChangeResult carry those responses in typed models. (#11)
Pane and window navigation
select_pane, swap_pane, select_window, and move_window let agents navigate within tmux and rearrange panes or windows without asking a user to manually focus the target. Directional window selection is scoped to the requested session, and pane selection handles next/previous relative navigation through concrete pane IDs. (#11)
Terminal I/O helpers
pipe_pane logs live pane output to a file, display_message exposes tmux format expansion as a read-only metadata escape hatch, enter_copy_mode and exit_copy_mode control copy-mode state, and paste_text uses tmux buffers for multi-line input where raw keystroke simulation is fragile. (#11)
Documentation¶
The API docs adopt the gp-sphinx documentation stack: card-style API layouts, safety and scope badges, MyST cross-reference roles, improved CLI-doc section scoping, IBM Plex typography, and FastMCP-aware tool rendering. The docs dependency stack is then refreshed to gp-sphinx 0.0.1a8. (#10, #14)
libtmux-mcp 0.1.0a0 (2026-03-22)¶
libtmux-mcp 0.1.0a0 is the first standalone alpha release, extracted from libtmux’s MCP branch. It packages libtmux-backed tmux control as an MCP server with typed tools, hierarchy resources, safety tiers, and socket-aware server selection. The release establishes the base contract that later alphas expand: inspect tmux state, make scoped mutations, and keep destructive operations behind an explicit safety tier.
What’s new¶
Standalone MCP server for tmux
The package exposes tmux sessions, windows, panes, options, and environment operations through MCP tools backed by libtmux. It includes the console entry point, FastMCP server wiring, and the first set of Pydantic response models for tool output.
Browsable tmux hierarchy
tmux:// resources let MCP clients browse the tmux hierarchy from server to sessions, windows, and panes. Tool resolvers accept the normal tmux identifiers so callers can move between resource browsing and concrete tool calls.
Safety and socket isolation
The initial server ships with readonly, mutating, and destructive safety tiers, controlled by LIBTMUX_SAFETY. Socket selection through LIBTMUX_SOCKET and LIBTMUX_SOCKET_PATH lets the same process target isolated tmux servers, and TMUX_PANE awareness marks the pane that belongs to the calling agent.
Typed implementation baseline
Server caching, resolver helpers, standardized error handling, and strict mypy coverage provide the internal foundation for later tool families. All initial tool responses are typed through Pydantic models rather than ad hoc dictionaries.