# Migration notes

Migration and deprecation notes for libtmux-mcp are here, see {ref}`history`
for the full release log.

```{admonition} Welcome on board! 👋
1. 📌 For safety, **always** pin the package version in your install
2. 📖 Check the migration notes _(You are here)_
3. 📣 If a deprecation interrupted you - past, present, or future - voice your opinion on the [tracker].

   We want to make libtmux-mcp fun, reliable, and useful for users.

   API changes can be painful.

   If we can do something to draw the sting, we'll do it. We're taking a balanced approach. That's why these notes are here!

   (Please pin the package. 🙏)

   [tracker]: https://github.com/tmux-python/libtmux-mcp/discussions
```

## libtmux-mcp 0.1.0a21 (2026-08-30)

### `LIBTMUX_SAFETY` now crashes the server on load

`readonly`, `mutating` and `destructive` are gone. Tools belong to four
unordered toolsets named for what they do.

A server started with `LIBTMUX_SAFETY` still set raises while the module
loads, before it serves a request. Every value crashes, the former default
`mutating` included. Your MCP client reports a server that failed to start;
the message naming the replacement goes to the process's stderr, so read the
client's server log if the failure looks silent.

The variable is not ignored, and that is deliberate: one that silently
stopped working would leave you believing a surface was narrower than it is.

#### The new default names no environment variables

An unset {envvar}`LIBTMUX_TOOLSETS` selects `inspect,manage,execute` — the surface
`mutating` gave you. If you were on the old default, delete `LIBTMUX_SAFETY`
from your client config and put nothing in its place:

```json
{
  "mcpServers": {
    "tmux": {
      "command": "uvx",
      "args": ["libtmux-mcp"]
    }
  }
}
```

Name a variable only to move off that default.

#### Environment variables

| Before | After |
| --- | --- |
| `LIBTMUX_SAFETY=readonly` | `LIBTMUX_TOOLSETS=inspect` |
| `LIBTMUX_SAFETY=mutating` (the default) | nothing — the default is `inspect,manage,execute` |
| `LIBTMUX_SAFETY=destructive` | `LIBTMUX_TOOLSETS=inspect,manage,execute,teardown` |

Two variables are new. {envvar}`LIBTMUX_TOOLS` enables individual tools
regardless of toolset, and {envvar}`LIBTMUX_EXCLUDE_TOOLS` refuses them
regardless of every
enable above. An unknown name in any of the three fails startup the same way.

### Surfaces the tiers could not express

The tiers accumulated upward, so every surface was a prefix of the ladder.
The toolsets are a set, so this is now legal:

```console
$ LIBTMUX_TOOLSETS=inspect,teardown libtmux-mcp
```

An agent that can look and clean up, but not type.

### Tool names

| Before | After |
| --- | --- |
| `call_readonly_tools_batch` | {tooliconl}`call-read-tools-batch` |
| `call_mutating_tools_batch` | removed — call the tool directly |
| `call_destructive_tools_batch` | removed — call the tool directly |

A batch gives every nested call the wrapper's name, so a client rule keyed
on {tooliconl}`kill-session` never fires for a `kill_session` run inside one. That is
tolerable for reads and not for writes.

### Which toolset a tool is in

`inspect`
: Every `list_*`, `get_*`, `show_*`, `capture_*`, {toolref}`snapshot-pane`,
  {toolref}`search-panes`, {toolref}`find-pane-by-position`,
  {toolref}`display-message`, {toolref}`wait-for-text`,
  {toolref}`call-read-tools-batch`.

`manage`
: `rename_*`, `select_*`, `resize_*`, {toolref}`move-window`,
  {toolref}`swap-pane`, {toolref}`set-pane-title`, {toolref}`enter-copy-mode`,
  {toolref}`exit-copy-mode`, {toolref}`wait-for-channel`,
  {toolref}`signal-channel`, {toolref}`load-buffer`.

`execute`
: {toolref}`create-session`, {toolref}`create-window`,
  {toolref}`split-window`, {toolref}`respawn-pane`, {toolref}`run-command`,
  {toolref}`send-keys`, {toolref}`send-keys-batch`, {toolref}`paste-text`,
  {toolref}`paste-buffer`, {toolref}`pipe-pane`, {toolref}`set-option`,
  {toolref}`set-environment`.

`set_option` and `set_environment` are here rather than in `manage`
because tmux runs some stored values later: a `#(...)` job in a status
format runs when tmux draws it and repeats on the status interval, and
`default-command` decides what every future pane runs.

`teardown`
: {toolref}`kill-pane`, {toolref}`kill-window`, {toolref}`kill-session`,
  {toolref}`kill-server`, {toolref}`clear-pane`, {toolref}`delete-buffer`.

### Documentation

The safety topic is now the {ref}`trust` page. The old URL redirects.

### MCP annotations

Every tool that requests a tmux operation now explicitly advertises
`readOnlyHint: false`,
`destructiveHint: true`, `idempotentHint: false`, and `openWorldHint: true`.
An existing tmux server can use aliases and hooks to replace or extend the
operation libtmux-mcp requests, so no stronger static promise holds for every
target. The optional prompt adapter tools render text without contacting tmux
and retain their narrower hints. Use the project-owned toolsets to distinguish
the direct operation libtmux-mcp requests.

## libtmux-mcp 0.1.0a19 (2026-07-25)

### `wait_for_text` takes `patterns`, and `wait_for_content_change` is gone

{tooliconl}`wait-for-text` accepted a single `pattern` string. It now takes `patterns`, a
list — or `null` to wait for any new output at all. The schema rejects the old
argument rather than silently ignoring it, so a call written against the old
signature fails loudly with `Remove or correct the unrecognized argument(s):
pattern`.

#### Before

```json
{"tool": "wait_for_text", "arguments": {"pane_id": "%1", "pattern": "ready"}}
```

#### After

```json
{"tool": "wait_for_text", "arguments": {"pane_id": "%1", "patterns": ["ready"]}}
```

`wait_for_content_change` and its `ContentChangeResult` are removed. Pass
`patterns: null` instead — it waits for any new output and additionally
excludes pre-existing scrollback and stale paint from the change predicate,
which the old tool did not.

```json
{"tool": "wait_for_text", "arguments": {"pane_id": "%1", "patterns": null}}
```

### Waits are capped by the server

{toolref}`wait-for-text`, {toolref}`wait-for-channel`, and
{toolref}`run-command` are all bounded by
{envvar}`LIBTMUX_MCP_WAIT_MAX_SECONDS` (30 seconds by default, clamped to `[1, 120]`).
An over-large `timeout` is **clamped, not rejected** — the call succeeds and
reports the value actually enforced, so read `effective_timeout` rather than
assuming your value was honoured. A wait that previously blocked for an hour
now returns at the ceiling.

Raise the ceiling with the environment variable if your workload needs it, or
call again — the command keeps running in the pane between calls.

### `WaitForTextResult` reports an `outcome`

The result's loose booleans are replaced by one `outcome` field taking
`matched`, `any_output`, `stopped`, `alternate_screen`, or `timeout`.
`risk_band_warned` is removed; the history-limit trim signal is delivered as an
MCP warning notification instead. Clients that cannot surface notifications
should compose `tmux wait-for -S` into the command and use
{tooliconl}`wait-for-channel`,
which does not read the pane at all.

### Self-bounded tools cannot be batched

{toolref}`run-command`, {toolref}`wait-for-text`, and
{toolref}`wait-for-channel` are rejected by the
batch wrappers per operation, because batching one multiplied the wait ceiling
by the operation count. Use {tooliconl}`send-keys-batch` for command sequences, or call
the tool directly.

## libtmux-mcp 0.1.0a6 (2026-05-09)

### Recommended registration slug: `libtmux` → `tmux`

Earlier docs and install widgets recommended registering the server as
`libtmux`. From 0.1.0a6 onward the recommended slug is `tmux`, matching
the value of `serverInfo.name` returned in the MCP handshake and the
`mcp__tmux__*` tool prefix that clients namespace tool calls under.

**Existing installations continue to work.** The slug is a per-install
user choice — your client looks up the server by whatever name you
registered. Migration is optional; it only matters if you want the new
`tmux` prefix on tool calls. Claude Code's `claude mcp remove <name>`
auto-detects the registration scope (verified against
`claude --version 2.1.138`), so the commands below work whether you
originally registered at `local`, `user`, or `project` scope.

#### Before

```console
$ claude mcp add libtmux -- uvx libtmux-mcp
```

→ tools surface as `mcp__libtmux__list_panes`, `mcp__libtmux__send_keys`, …

#### After

```console
$ claude mcp remove libtmux
```

```console
$ claude mcp add tmux -- uvx libtmux-mcp
```

→ tools surface as `mcp__tmux__list_panes`, `mcp__tmux__send_keys`, …

#### What's unchanged

- PyPI package name: `libtmux-mcp`
- Python module: `libtmux_mcp`
- GitHub repository: <https://github.com/tmux-python/libtmux-mcp>
- Existing `mcp__libtmux__*` references in CLAUDE.md / AGENTS.md
  templates and agent histories continue to work if you keep the
  `libtmux` slug.
