diff --git a/docs/_ext/fastmcp_autodoc.py b/docs/_ext/fastmcp_autodoc.py
index aca3053..f6d0811 100644
--- a/docs/_ext/fastmcp_autodoc.py
+++ b/docs/_ext/fastmcp_autodoc.py
@@ -421,7 +421,7 @@ def _extract_enum_values(type_str: str) -> list[str]:
return values
-class _safety_badge_node(nodes.General, nodes.Inline, nodes.Element):
+class _safety_badge_node(nodes.General, nodes.Inline, nodes.Element): # type: ignore[misc]
"""Custom node for safety badges with ARIA attributes in HTML output."""
@@ -868,8 +868,12 @@ def _add_section_badges(
title_node += _safety_badge(tier)
-class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element):
- """Placeholder node for ``{tool}`` role, resolved at doctree-resolved."""
+class _tool_ref_placeholder(nodes.General, nodes.Inline, nodes.Element): # type: ignore[misc]
+ """Placeholder node for ``{tool}`` and ``{toolref}`` roles.
+
+ Resolved at ``doctree-resolved`` by ``_resolve_tool_refs``.
+ The ``show_badge`` attribute controls whether the safety badge is appended.
+ """
def _resolve_tool_refs(
@@ -877,7 +881,10 @@ def _resolve_tool_refs(
doctree: nodes.document,
fromdocname: str,
) -> None:
- """Resolve ``{tool}`` placeholders into links with safety badges.
+ """Resolve ``{tool}`` and ``{toolref}`` placeholders into links.
+
+ ``{tool}`` renders as ``code`` + safety badge.
+ ``{toolref}`` renders as ``code`` only (no badge).
Runs at ``doctree-resolved`` — after all labels are registered and
standard ``{ref}`` resolution is done.
@@ -888,6 +895,7 @@ def _resolve_tool_refs(
for node in list(doctree.findall(_tool_ref_placeholder)):
target = node.get("reftarget", "")
+ show_badge = node.get("show_badge", True)
label_info = domain.labels.get(target)
if label_info is None:
node.replace_self(nodes.literal("", target.replace("-", "_")))
@@ -908,10 +916,11 @@ def _resolve_tool_refs(
newnode += nodes.literal("", tool_name)
- tool_info = tool_data.get(tool_name)
- if tool_info:
- newnode += nodes.Text(" ")
- newnode += _safety_badge(tool_info.safety)
+ if show_badge:
+ tool_info = tool_data.get(tool_name)
+ if tool_info:
+ newnode += nodes.Text(" ")
+ newnode += _safety_badge(tool_info.safety)
node.replace_self(newnode)
@@ -930,7 +939,26 @@ def _tool_role(
Creates a placeholder node resolved later by ``_resolve_tool_refs``.
"""
target = text.strip().replace("_", "-")
- node = _tool_ref_placeholder(rawtext, reftarget=target)
+ node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=True)
+ return [node], []
+
+
+def _toolref_role(
+ name: str,
+ rawtext: str,
+ text: str,
+ lineno: int,
+ inliner: object,
+ options: dict[str, object] | None = None,
+ content: list[str] | None = None,
+) -> tuple[list[nodes.Node], list[nodes.system_message]]:
+ """Inline role ``:toolref:`capture-pane``` → code-linked tool name, no badge.
+
+ Like ``{tool}`` but without the safety badge. Use in dense contexts
+ (tables, inline prose) where badges would be too heavy.
+ """
+ target = text.strip().replace("_", "-")
+ node = _tool_ref_placeholder(rawtext, reftarget=target, show_badge=False)
return [node], []
@@ -958,6 +986,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.connect("doctree-resolved", _add_section_badges)
app.connect("doctree-resolved", _resolve_tool_refs)
app.add_role("tool", _tool_role)
+ app.add_role("toolref", _toolref_role)
app.add_role("badge", _badge_role)
app.add_directive("fastmcp-tool", FastMCPToolDirective)
app.add_directive("fastmcp-tool-input", FastMCPToolInputDirective)
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
index 66b9708..874e40d 100644
--- a/docs/_static/css/custom.css
+++ b/docs/_static/css/custom.css
@@ -258,6 +258,7 @@ h4:has(> .sd-badge) {
.sd-badge {
display: inline-flex !important;
align-items: center;
+ vertical-align: middle;
gap: 0.28rem;
font-size: 0.67rem;
font-weight: 650;
@@ -332,6 +333,11 @@ code.docutils + .sd-badge,
/* ── Link behavior: underline code only, on hover ───────── */
a.reference .sd-badge {
text-decoration: none;
+ vertical-align: middle;
+}
+
+a.reference:has(.sd-badge) code {
+ vertical-align: middle;
}
a.reference:has(.sd-badge) {
diff --git a/docs/conf.py b/docs/conf.py
index 2b284c5..94df75e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -95,7 +95,7 @@
},
],
"source_repository": f"{about['__repository__']}/",
- "source_branch": "master",
+ "source_branch": "main",
"source_directory": "docs/",
"announcement": "Pre-alpha. APIs may change. Feedback welcome.",
}
@@ -140,7 +140,7 @@
# sphinxext-rediraffe
rediraffe_redirects = "redirects.txt"
-rediraffe_branch = "master~1"
+rediraffe_branch = "main~1"
# sphinxext.opengraph
ogp_site_url = about["__url__"]
@@ -259,7 +259,7 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str:
fn = relpath(fn, start=pathlib.Path(libtmux_mcp.__file__).parent)
if "dev" in about["__version__"]:
- return "{}/blob/master/{}/{}/{}{}".format(
+ return "{}/blob/main/{}/{}/{}{}".format(
about["__repository__"],
"src",
about["__package_name__"],
diff --git a/docs/demo.md b/docs/demo.md
new file mode 100644
index 0000000..3b43dfc
--- /dev/null
+++ b/docs/demo.md
@@ -0,0 +1,100 @@
+---
+orphan: true
+---
+
+# Badge & Role Demo
+
+A showcase of the custom Sphinx roles and visual elements available in libtmux-mcp documentation.
+
+## Safety badges
+
+Standalone badges via `{badge}`:
+
+- {badge}`readonly` — green, read-only operations
+- {badge}`mutating` — amber, state-changing operations
+- {badge}`destructive` — red, irreversible operations
+
+## Tool references
+
+### `{tool}` — code-linked with badge
+
+{tool}`capture-pane` · {tool}`send-keys` · {tool}`search-panes` · {tool}`wait-for-text` · {tool}`kill-pane` · {tool}`create-session` · {tool}`split-window`
+
+### `{toolref}` — code-linked, no badge
+
+{toolref}`capture-pane` · {toolref}`send-keys` · {toolref}`search-panes` · {toolref}`wait-for-text` · {toolref}`kill-pane` · {toolref}`create-session` · {toolref}`split-window`
+
+### `{ref}` — plain text link
+
+{ref}`capture-pane` · {ref}`send-keys` · {ref}`search-panes` · {ref}`wait-for-text` · {ref}`kill-pane` · {ref}`create-session` · {ref}`split-window`
+
+## Badges in context
+
+### In a heading
+
+These are the actual tool headings as they render on tool pages:
+
+> `capture_pane` {badge}`readonly`
+
+> `split_window` {badge}`mutating`
+
+> `kill_session` {badge}`destructive`
+
+### In a table
+
+| Tool | Tier | Description |
+|------|------|-------------|
+| {toolref}`list-sessions` | {badge}`readonly` | List all sessions |
+| {toolref}`send-keys` | {badge}`mutating` | Send commands to a pane |
+| {toolref}`kill-pane` | {badge}`destructive` | Destroy a pane |
+
+### In prose
+
+Use {tool}`search-panes` to find text across all panes. If you know which pane, use {tool}`capture-pane` instead. After running a command with {tool}`send-keys`, always {tool}`wait-for-text` before capturing.
+
+### Dense inline (toolref, no badges)
+
+The fundamental pattern: {toolref}`send-keys` → {toolref}`wait-for-text` → {toolref}`capture-pane`. For discovery: {toolref}`list-sessions` → {toolref}`list-panes` → {toolref}`get-pane-info`.
+
+## Environment variable references
+
+{envvar}`LIBTMUX_SOCKET` · {envvar}`LIBTMUX_SAFETY` · {envvar}`LIBTMUX_SOCKET_PATH` · {envvar}`LIBTMUX_TMUX_BIN`
+
+## Glossary terms
+
+{term}`SIGINT` · {term}`SIGQUIT` · {term}`MCP` · {term}`Safety tier` · {term}`Pane` · {term}`Session`
+
+## Admonitions
+
+```{tip}
+Use {tool}`search-panes` before {tool}`capture-pane` when you don't know which pane has the output you need.
+```
+
+```{warning}
+Do not call {toolref}`capture-pane` immediately after {toolref}`send-keys` — there is a race condition. Use {toolref}`wait-for-text` between them.
+```
+
+```{note}
+All tools accept an optional `socket_name` parameter for multi-server support.
+```
+
+## Badge anatomy
+
+Each badge renders as:
+
+```html
+
+ 🔍 readonly
+
+```
+
+Features:
+- **Emoji icon** — 🔍 readonly, ✏️ mutating, 💣 destructive (native system emoji, no filters)
+- **Matte colors** — forest green, smoky amber, matte crimson with 1px border
+- **Accessible** — `role="note"` + `aria-label` for screen readers
+- **Non-selectable** — `user-select: none` so copying tool names skips badge text
+- **Context-aware sizing** — slightly larger in headings, smaller inline
+- **Sidebar compression** — badges collapse to colored dots in the right-side TOC
+- **Heading flex** — `h2/h3/h4:has(.sd-badge)` centers badge against cap-height
diff --git a/docs/glossary.md b/docs/glossary.md
index 11dea8d..df7c39c 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -33,4 +33,10 @@ Safety tier
Socket
The Unix socket used to communicate with a tmux server. Can be specified by name (`-L`) or path (`-S`).
+
+SIGINT
+ Interrupt signal (Ctrl-C). Sent via {toolref}`send-keys` with `keys: "C-c"` and `enter: false`. Most processes terminate gracefully on SIGINT.
+
+SIGQUIT
+ Quit signal (Ctrl-\\). Sent via {toolref}`send-keys` with `keys: "C-\\"` and `enter: false`. Stronger than {term}`SIGINT` — may produce a core dump on Unix. Use as an escalation when SIGINT is ignored.
```
diff --git a/docs/index.md b/docs/index.md
index 497b1b3..1bc8ff4 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -93,6 +93,7 @@ clients
:caption: Use it
tools/index
+recipes
configuration
```
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 44a162b..5a76ace 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -30,6 +30,16 @@ Here are a few things to try:
> Search all my panes for the word "error".
+## How it works
+
+When you say "run `make test` and show me the output", the agent executes a three-step pattern:
+
+1. {ref}`send-keys` — send the command to a tmux pane
+2. {ref}`wait-for-text` — wait for the shell prompt to return (command finished)
+3. {ref}`capture-pane` — read the terminal output
+
+This **send → wait → capture** sequence is the fundamental workflow. Most agent interactions with tmux follow this pattern or a variation of it.
+
## Next steps
- {ref}`concepts` — Understand the tmux hierarchy and how tools target panes
diff --git a/docs/recipes.md b/docs/recipes.md
new file mode 100644
index 0000000..c40ad61
--- /dev/null
+++ b/docs/recipes.md
@@ -0,0 +1,328 @@
+(recipes)=
+
+# Recipes
+
+Each recipe starts from a real workspace situation and traces the agent's
+reasoning through discovery, decision, and action. The goal is not to show
+tool-call sequences -- it is to show *how an agent decides* what to do with
+existing tmux state so you can write better prompts and system instructions.
+
+Every recipe uses the same structure:
+
+- **Situation** -- the developer's world before the agent acts
+- **Discover** -- what the agent inspects and why
+- **Decide** -- the judgment call that changes the plan
+- **Act** -- the minimum safe action sequence
+- **The non-obvious part** -- the lesson you would miss from reading tool docs
+ alone
+- **Prompt** -- a natural-language sentence that triggers this recipe
+
+---
+
+## Find a running dev server and test against it
+
+**Situation.** A developer manages a React project with
+[tmuxp](https://tmuxp.git-pull.com). One pane is already running
+`pnpm start` with Vite somewhere in the `react` window. They want to run
+Playwright e2e tests. The agent does not know which pane has the server,
+or what port it chose.
+
+### Discover
+
+> {toolref}`list-panes` will not help here -- it shows metadata like current
+> command and working directory, not terminal content. The dev server printed
+> its URL to the terminal minutes ago, so I need to search terminal content.
+
+The agent calls {tool}`search-panes` with `pattern: "Local:"` and
+`session_name: "myapp"`. The response comes back with pane `%5` in the `react`
+window, matched line: `Local: http://localhost:5173/`.
+
+### Decide
+
+> The server is alive and its URL is known. I do not need to start anything.
+> I just need an idle pane for running tests.
+
+The agent calls {tool}`list-panes` on the `myapp` session. Several panes show
+`pane_current_command: zsh` -- idle shells. It picks `%4` in the same window.
+
+### Act
+
+The agent calls {tool}`send-keys` in pane `%4`:
+`PLAYWRIGHT_BASE_URL=http://localhost:5173 pnpm exec playwright test`
+
+Then it calls {tool}`wait-for-text` on pane `%4` with `pattern: "passed|failed|timed out"`, `regex: true`, and `timeout: 120`. Once the
+wait resolves, it calls {tool}`capture-pane` on `%4` with `start: -80` to
+read the test results.
+
+```{tip}
+The agent's first instinct might be to *start* a Vite server. But
+{tool}`search-panes` reveals one is already running. This avoids a port
+conflict, a wasted pane, and the most common agent mistake: treating tmux
+like a blank shell.
+```
+
+### The non-obvious part
+
+{toolref}`search-panes` searches terminal *content* -- what you would see on
+screen. {toolref}`list-panes` searches *metadata* like current command and
+working directory. If the agent had used {toolref}`list-panes` to find a pane
+running `node`, it would know a process exists but not whether it is ready or
+what URL it chose.
+
+**Prompt:** "Run the Playwright tests against my dev server in the myapp
+session."
+
+---
+
+## Start a service and wait for it before running dependent work
+
+**Situation.** The developer is starting fresh in their `backend` session --
+no server running yet. They want to run integration tests, but the test
+suite needs a live API server.
+
+### Discover
+
+> First I need to know what exists in the `backend` session. If a server is
+> already running, I should reuse it instead of starting a duplicate.
+
+The agent calls {tool}`list-panes` for the `backend` session. No pane is
+running a server process. A {tool}`search-panes` call for `"listening"`
+returns no matches.
+
+### Decide
+
+> Nothing to reuse. I need a dedicated pane for the server so its output
+> stays separate from the test output.
+
+### Act
+
+The agent calls {tool}`split-window` with `session_name: "backend"` to
+create a new pane, then calls {tool}`send-keys` in that pane:
+`npm run serve`.
+
+The agent calls {tool}`wait-for-text` on the server pane with
+`pattern: "Listening on"` and `timeout: 30`. Once the wait resolves, the
+agent calls {tool}`send-keys` in the original pane:
+`npm test -- --integration`, then {tool}`wait-for-text` with
+`pattern: "passed|failed|error"` and `regex: true`, then
+{tool}`capture-pane` to read the test results.
+
+```{warning}
+Calling {toolref}`capture-pane` immediately after {toolref}`send-keys` is a
+race condition. {toolref}`send-keys` returns the moment tmux accepts the
+keystrokes, not when the command finishes. Always use {toolref}`wait-for-text`
+between them.
+```
+
+### The non-obvious part
+
+{toolref}`wait-for-text` replaces `sleep`. The server might start in 2
+seconds or 20 -- the agent adapts. The anti-pattern is polling with repeated
+{toolref}`capture-pane` calls or hardcoding a sleep duration. The MCP server
+handles the polling internally with configurable `timeout` (default 8s) and
+`interval` (default 50ms).
+
+**Prompt:** "Start the API server in my backend session and run the
+integration tests once it's ready."
+
+---
+
+## Find the failing pane without opening random terminals
+
+**Situation.** The developer kicked off multiple jobs across panes in a `ci`
+session -- linting, unit tests, integration tests, type checking. One of
+them failed, but they stepped away and do not remember which pane.
+
+### Discover
+
+> I should not capture every pane and read them all -- that is expensive and
+> slow. Instead I will search for common failure indicators across all panes
+> at once.
+
+The agent calls {tool}`search-panes` with
+`pattern: "FAIL|ERROR|error:|Traceback"`, `regex: true`, scoped to
+`session_name: "ci"`.
+
+### Decide
+
+> Two panes matched: `%3` has `FAIL: test_upload` and `%6` has
+> `error: Type 'string' is not assignable`. I will capture context from each.
+
+### Act
+
+The agent calls {tool}`capture-pane` on `%3` with `start: -60`, then on
+`%6` with `start: -60`.
+
+```{tip}
+If the error scrolled off the visible screen, use `content_start: -200` (or
+deeper) when calling {tool}`search-panes`. The `content_start` parameter
+makes search reach into scrollback history, not just the visible screen.
+```
+
+### The non-obvious part
+
+{toolref}`search-panes` checks all panes in a single call -- searching 20
+panes costs roughly the same as searching 2. An agent that instead calls
+{toolref}`list-panes` then {toolref}`capture-pane` on each one individually
+makes 20+ round trips for the same information. The `regex: true` parameter
+is required here because the `|` in the pattern is a regex alternation, not
+literal text.
+
+**Prompt:** "Check my ci session -- which jobs failed?"
+
+---
+
+## Interrupt a stuck process and recover the pane
+
+**Situation.** A long-running build is hanging. The developer wants to
+interrupt it, verify the pane is responsive, and re-run the command.
+
+### Discover
+
+> I need to send Ctrl-C. This is a tmux key name, not text -- so I must use
+> `enter: false` or tmux will send Ctrl-C followed by Enter, which could
+> confirm a prompt I did not intend to answer.
+
+The agent calls {tool}`send-keys` with `keys: "C-c"` and `enter: false` on
+the target pane.
+
+### Decide
+
+> Did the interrupt work? Some processes ignore {term}`SIGINT`. I will wait briefly
+> for a shell prompt to reappear. Developers use custom prompts, so I cannot
+> just look for `$`.
+
+The agent calls {tool}`wait-for-text` with `pattern: "[$#>%] *$"`,
+`regex: true`, and `timeout: 5`.
+
+> If the wait resolves, the shell is back. If it times out, the process
+> ignored Ctrl-C. I will escalate: try {term}`SIGQUIT` (`C-\` with `enter: false`),
+> then destroy and replace the pane only as a last resort.
+
+### Act
+
+If the wait times out, the agent sends `C-\` (also with `enter: false`). If
+that also fails, it calls {tool}`kill-pane` on the stuck pane, then
+{tool}`split-window` to create a replacement, then {tool}`send-keys` to
+re-run.
+
+```{warning}
+The `enter: false` parameter is critical. Without it, {toolref}`send-keys`
+sends Ctrl-C *then* Enter, which could confirm a "really quit?" prompt,
+submit a partially typed command, or enter a newline into a REPL.
+```
+
+### The non-obvious part
+
+Recovery is a two-step decision. Try {term}`SIGINT` first (Ctrl-C),
+verify it worked with {toolref}`wait-for-text`, escalate to {term}`SIGQUIT`
+only if needed. The escalation ladder is: interrupt, verify, escalate signal,
+destroy. Skipping
+straight to {toolref}`kill-pane` loses the pane's scrollback history and any
+partially written output that might explain *why* it hung.
+
+**Prompt:** "The build in pane %2 is stuck. Kill it and restart."
+
+---
+
+## Re-run a command without mixing old and new output
+
+**Situation.** The developer wants `pytest` re-run in tmux, but the
+candidate pane already has old test output in scrollback. They want only
+fresh results.
+
+### Discover
+
+The agent calls {tool}`list-panes` to find the pane by title, cwd, or
+current command. If more than one pane is plausible, it uses
+{tool}`capture-pane` with a small range to confirm the target.
+
+### Decide
+
+> The pane is a shell. I should clear it before running so the capture
+> afterwards contains only fresh output. If it were running a watcher or
+> long-lived process, I would not hijack it -- I would use a different pane.
+
+### Act
+
+The agent calls {tool}`clear-pane`, then {tool}`send-keys` with
+`keys: "pytest"`, then {tool}`wait-for-text` with
+`pattern: "passed|failed|error"` and `regex: true`, then
+{tool}`capture-pane` to read the fresh output.
+
+### The non-obvious part
+
+{toolref}`clear-pane` runs two tmux commands internally (`send-keys -R` then
+`clear-history`) with a brief gap between them. Calling
+{toolref}`capture-pane` immediately after {toolref}`clear-pane` may catch
+partial state. The {toolref}`wait-for-text` call after {toolref}`send-keys`
+naturally provides the needed delay, so the sequence clear-send-wait-capture
+is safe.
+
+**Prompt:** "Run `pytest` in the test pane and show me only the fresh
+output."
+
+---
+
+## Build a workspace the agent can revisit later
+
+**Situation.** The developer wants a durable project workspace -- not just a
+quick split, but a layout that later prompts can refer to by role ("the
+server pane", "the test pane").
+
+### Discover
+
+> Before creating anything, I need to check whether a session with this name
+> already exists. Creating a duplicate will fail.
+
+The agent calls {tool}`list-sessions`. No session named `myproject` exists.
+
+### Decide
+
+> Safe to create. I need three panes: editor, server, tests. I will create
+> the session, split twice, then apply a layout so tmux handles the geometry
+> instead of me calculating sizes.
+
+### Act
+
+The agent calls {tool}`create-session` with `session_name: "myproject"` and
+`start_directory: "/home/dev/myproject"`. Then {tool}`split-window` twice
+(with `direction: "right"` and `direction: "below"`), followed by
+{tool}`select-layout` with `layout: "main-vertical"`.
+
+The agent calls {tool}`set-pane-title` on each pane: `editor`, `server`,
+`tests`.
+
+The agent calls {tool}`send-keys` in the server pane: `npm run dev`, then
+{tool}`wait-for-text` for `pattern: "ready|listening|Local:"` with
+`regex: true` and `timeout: 30`.
+
+```{tip}
+If the session *does* already exist, the right move is to reuse and extend
+it, not recreate it. The {toolref}`list-sessions` check at the top is what
+makes that decision possible.
+```
+
+### The non-obvious part
+
+Titles and naming are not cosmetic. They reduce future discovery cost. When
+the agent comes back in a later conversation and the user says "restart the
+server," the agent calls {toolref}`list-panes`, finds the pane titled
+`server`, and acts -- no searching, no guessing, no capturing every pane to
+figure out which one is which. But note: pane IDs are ephemeral across tmux
+server restarts, so the agent should always re-discover by metadata (session
+name, pane title, cwd) rather than trusting remembered `%N` values.
+
+**Prompt:** "Set up a tmux workspace for myproject with editor, server, and
+test panes."
+
+---
+
+## What to read next
+
+For the principles that recur across these recipes -- discover before acting,
+wait instead of polling, content vs. metadata, prefer IDs, escalate
+gracefully -- see the {ref}`prompting guide `. For specific
+pitfalls like `enter: false` and the `send_keys`/`capture_pane` race
+condition, see {ref}`gotchas `.
+
diff --git a/docs/tools/index.md b/docs/tools/index.md
index 41b057b..334b593 100644
--- a/docs/tools/index.md
+++ b/docs/tools/index.md
@@ -2,7 +2,27 @@
# Tools
-All tools accept an optional `socket_name` parameter for multi-server support. It defaults to the `LIBTMUX_SOCKET` env var. See {ref}`configuration`.
+All tools accept an optional `socket_name` parameter for multi-server support. It defaults to the {envvar}`LIBTMUX_SOCKET` env var. See {ref}`configuration`.
+
+## Which tool do I want?
+
+**Reading terminal content?**
+- Know which pane? → {tool}`capture-pane`
+- Don't know which pane? → {tool}`search-panes`
+- Need to wait for output? → {tool}`wait-for-text`
+- Only need metadata (PID, path, size)? → {tool}`get-pane-info`
+
+**Running a command?**
+- {tool}`send-keys` — then {tool}`wait-for-text` + {tool}`capture-pane`
+
+**Creating workspace structure?**
+- New session → {tool}`create-session`
+- New window → {tool}`create-window`
+- New pane → {tool}`split-window`
+
+**Changing settings?**
+- tmux options → {tool}`show-option` / {tool}`set-option`
+- Environment vars → {ref}`show-environment` / {ref}`set-environment`
## Inspect
diff --git a/docs/tools/options.md b/docs/tools/options.md
index 2242c5b..5c3c1b8 100644
--- a/docs/tools/options.md
+++ b/docs/tools/options.md
@@ -42,6 +42,29 @@ Response:
**Side effects:** None. Readonly.
+**Example:**
+
+```json
+{
+ "tool": "show_environment",
+ "arguments": {}
+}
+```
+
+Response:
+
+```json
+{
+ "variables": {
+ "SHELL": "/bin/zsh",
+ "TERM": "xterm-256color",
+ "HOME": "/home/user",
+ "USER": "user",
+ "LANG": "C.UTF-8"
+ }
+}
+```
+
```{fastmcp-tool-input} env_tools.show_environment
```
@@ -89,5 +112,27 @@ Response:
**Side effects:** Sets the variable in the tmux server.
+**Example:**
+
+```json
+{
+ "tool": "set_environment",
+ "arguments": {
+ "name": "MY_VAR",
+ "value": "hello"
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "name": "MY_VAR",
+ "value": "hello",
+ "status": "set"
+}
+```
+
```{fastmcp-tool-input} env_tools.set_environment
```
diff --git a/docs/tools/panes.md b/docs/tools/panes.md
index d3b34e9..b927b1f 100644
--- a/docs/tools/panes.md
+++ b/docs/tools/panes.md
@@ -55,6 +55,36 @@ other metadata without reading the terminal content.
**Side effects:** None. Readonly.
+**Example:**
+
+```json
+{
+ "tool": "get_pane_info",
+ "arguments": {
+ "pane_id": "%0"
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "pane_id": "%0",
+ "pane_index": "0",
+ "pane_width": "80",
+ "pane_height": "24",
+ "pane_current_command": "zsh",
+ "pane_current_path": "/home/user/myproject",
+ "pane_pid": "12345",
+ "pane_title": "",
+ "pane_active": "1",
+ "window_id": "@0",
+ "session_id": "$0",
+ "is_caller": null
+}
+```
+
```{fastmcp-tool-input} pane_tools.get_pane_info
```
@@ -195,6 +225,37 @@ Keys sent to pane %2
**Side effects:** Changes the pane title.
+**Example:**
+
+```json
+{
+ "tool": "set_pane_title",
+ "arguments": {
+ "pane_id": "%0",
+ "title": "build"
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "pane_id": "%0",
+ "pane_index": "0",
+ "pane_width": "80",
+ "pane_height": "24",
+ "pane_current_command": "zsh",
+ "pane_current_path": "/home/user/myproject",
+ "pane_pid": "12345",
+ "pane_title": "build",
+ "pane_active": "1",
+ "window_id": "@0",
+ "session_id": "$0",
+ "is_caller": null
+}
+```
+
```{fastmcp-tool-input} pane_tools.set_pane_title
```
@@ -207,6 +268,23 @@ Keys sent to pane %2
**Side effects:** Clears the pane's visible content.
+**Example:**
+
+```json
+{
+ "tool": "clear_pane",
+ "arguments": {
+ "pane_id": "%0"
+ }
+}
+```
+
+Response (string):
+
+```text
+Pane cleared: %0
+```
+
```{fastmcp-tool-input} pane_tools.clear_pane
```
@@ -219,6 +297,37 @@ Keys sent to pane %2
**Side effects:** Changes pane size. May affect adjacent panes.
+**Example:**
+
+```json
+{
+ "tool": "resize_pane",
+ "arguments": {
+ "pane_id": "%0",
+ "height": 15
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "pane_id": "%0",
+ "pane_index": "0",
+ "pane_width": "80",
+ "pane_height": "15",
+ "pane_current_command": "zsh",
+ "pane_current_path": "/home/user/myproject",
+ "pane_pid": "12345",
+ "pane_title": "",
+ "pane_active": "1",
+ "window_id": "@0",
+ "session_id": "$0",
+ "is_caller": null
+}
+```
+
```{fastmcp-tool-input} pane_tools.resize_pane
```
@@ -234,5 +343,22 @@ without affecting sibling panes.
**Side effects:** Destroys the pane. Not reversible.
+**Example:**
+
+```json
+{
+ "tool": "kill_pane",
+ "arguments": {
+ "pane_id": "%1"
+ }
+}
+```
+
+Response (string):
+
+```text
+Pane killed: %1
+```
+
```{fastmcp-tool-input} pane_tools.kill_pane
```
diff --git a/docs/tools/sessions.md b/docs/tools/sessions.md
index 539c3cb..c01a031 100644
--- a/docs/tools/sessions.md
+++ b/docs/tools/sessions.md
@@ -51,6 +51,27 @@ or inspect server-level state before creating sessions.
**Side effects:** None. Readonly.
+**Example:**
+
+```json
+{
+ "tool": "get_server_info",
+ "arguments": {}
+}
+```
+
+Response:
+
+```json
+{
+ "is_alive": true,
+ "socket_name": null,
+ "socket_path": null,
+ "session_count": 2,
+ "version": "3.6a"
+}
+```
+
```{fastmcp-tool-input} server_tools.get_server_info
```
@@ -141,6 +162,23 @@ windows and panes in the session.
**Side effects:** Destroys the session and all its contents. Not reversible.
+**Example:**
+
+```json
+{
+ "tool": "kill_session",
+ "arguments": {
+ "session_name": "old-workspace"
+ }
+}
+```
+
+Response (string):
+
+```text
+Session killed: old-workspace
+```
+
```{fastmcp-tool-input} session_tools.kill_session
```
@@ -156,5 +194,20 @@ session, window, and pane.
**Side effects:** Destroys everything. Not reversible.
+**Example:**
+
+```json
+{
+ "tool": "kill_server",
+ "arguments": {}
+}
+```
+
+Response (string):
+
+```text
+Server killed
+```
+
```{fastmcp-tool-input} server_tools.kill_server
```
diff --git a/docs/tools/windows.md b/docs/tools/windows.md
index 9e9c71e..0723d7b 100644
--- a/docs/tools/windows.md
+++ b/docs/tools/windows.md
@@ -67,6 +67,52 @@ sending keys or capturing output.
**Side effects:** None. Readonly.
+**Example:**
+
+```json
+{
+ "tool": "list_panes",
+ "arguments": {
+ "session_name": "dev"
+ }
+}
+```
+
+Response:
+
+```json
+[
+ {
+ "pane_id": "%0",
+ "pane_index": "0",
+ "pane_width": "80",
+ "pane_height": "15",
+ "pane_current_command": "zsh",
+ "pane_current_path": "/home/user/myproject",
+ "pane_pid": "12345",
+ "pane_title": "build",
+ "pane_active": "1",
+ "window_id": "@0",
+ "session_id": "$0",
+ "is_caller": null
+ },
+ {
+ "pane_id": "%1",
+ "pane_index": "1",
+ "pane_width": "80",
+ "pane_height": "8",
+ "pane_current_command": "zsh",
+ "pane_current_path": "/home/user/myproject",
+ "pane_pid": "12400",
+ "pane_title": "",
+ "pane_active": "0",
+ "window_id": "@0",
+ "session_id": "$0",
+ "is_caller": null
+ }
+]
+```
+
```{fastmcp-tool-input} window_tools.list_panes
```
@@ -164,6 +210,35 @@ Response:
**Side effects:** Renames the window.
+**Example:**
+
+```json
+{
+ "tool": "rename_window",
+ "arguments": {
+ "session_name": "dev",
+ "new_name": "build"
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "window_id": "@0",
+ "window_name": "build",
+ "window_index": "1",
+ "session_id": "$0",
+ "session_name": "dev",
+ "pane_count": 2,
+ "window_layout": "7f9f,80x24,0,0[80x15,0,0,0,80x8,0,16,1]",
+ "window_active": "1",
+ "window_width": "80",
+ "window_height": "24"
+}
+```
+
```{fastmcp-tool-input} window_tools.rename_window
```
@@ -177,6 +252,35 @@ Response:
**Side effects:** Rearranges all panes in the window.
+**Example:**
+
+```json
+{
+ "tool": "select_layout",
+ "arguments": {
+ "session_name": "dev",
+ "layout": "even-vertical"
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "window_id": "@0",
+ "window_name": "editor",
+ "window_index": "1",
+ "session_id": "$0",
+ "session_name": "dev",
+ "pane_count": 2,
+ "window_layout": "even-vertical,80x24,0,0[80x12,0,0,0,80x11,0,13,1]",
+ "window_active": "1",
+ "window_width": "80",
+ "window_height": "24"
+}
+```
+
```{fastmcp-tool-input} window_tools.select_layout
```
@@ -189,6 +293,36 @@ Response:
**Side effects:** Changes window size.
+**Example:**
+
+```json
+{
+ "tool": "resize_window",
+ "arguments": {
+ "session_name": "dev",
+ "width": 120,
+ "height": 40
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "window_id": "@0",
+ "window_name": "editor",
+ "window_index": "1",
+ "session_id": "$0",
+ "session_name": "dev",
+ "pane_count": 2,
+ "window_layout": "baaa,120x40,0,0[120x20,0,0,0,120x19,0,21,1]",
+ "window_active": "1",
+ "window_width": "120",
+ "window_height": "40"
+}
+```
+
```{fastmcp-tool-input} window_tools.resize_window
```
@@ -203,5 +337,23 @@ Response:
**Side effects:** Destroys the window and all its panes. Not reversible.
+**Example:**
+
+```json
+{
+ "tool": "kill_window",
+ "arguments": {
+ "session_name": "dev",
+ "window_name": "old-logs"
+ }
+}
+```
+
+Response (string):
+
+```text
+Window killed: old-logs
+```
+
```{fastmcp-tool-input} window_tools.kill_window
```
diff --git a/docs/topics/gotchas.md b/docs/topics/gotchas.md
new file mode 100644
index 0000000..b33c42b
--- /dev/null
+++ b/docs/topics/gotchas.md
@@ -0,0 +1,64 @@
+(gotchas)=
+
+# Gotchas
+
+Things that will bite you if you don't know about them in advance. For symptom-based debugging, see {ref}`troubleshooting `.
+
+## Metadata vs. content
+
+{tool}`list-panes` and {tool}`list-windows` search **metadata** — names, IDs, current command. They do not search what is displayed in the terminal.
+
+To find text that is visible in terminals, use {tool}`search-panes`. To read what a specific pane shows, use {tool}`capture-pane`.
+
+This is the most common source of agent confusion. The server instructions already warn about this, but it bears repeating: if a user asks "which pane mentions error", the answer is `search_panes`, not `list_panes`.
+
+## `send_keys` sends Enter by default
+
+When you call `send_keys` with `keys: "C-c"`, it sends Ctrl-C **and then presses Enter**. For control sequences, set `enter: false`:
+
+```json
+{"tool": "send_keys", "arguments": {"keys": "C-c", "pane_id": "%0", "enter": false}}
+```
+
+The `enter` parameter defaults to `true`, which is correct for commands (`make test` + Enter) but wrong for control keys, partial input, or key sequences.
+
+## `capture_pane` after `send_keys` is a race condition
+
+`send_keys` returns immediately after sending keystrokes to tmux. It does **not** wait for the command to execute or produce output.
+
+```json
+{"tool": "send_keys", "arguments": {"keys": "pytest", "pane_id": "%0"}}
+{"tool": "capture_pane", "arguments": {"pane_id": "%0"}}
+```
+
+The capture above may return the terminal state **before** pytest runs. Use {tool}`wait-for-text` between them:
+
+```json
+{"tool": "send_keys", "arguments": {"keys": "pytest", "pane_id": "%0"}}
+{"tool": "wait_for_text", "arguments": {"pattern": "passed|failed|error", "pane_id": "%0", "regex": true}}
+{"tool": "capture_pane", "arguments": {"pane_id": "%0"}}
+```
+
+See {ref}`recipes` for the complete pattern.
+
+## Window names are not unique across sessions
+
+Two sessions can each have a window named "editor". Targeting by `window_name` alone is ambiguous — always include `session_name` or use the globally unique `window_id` (e.g., `@0`, `@1`).
+
+Pane IDs (`%0`, `%1`, etc.) are globally unique and are the preferred targeting method.
+
+## Pane IDs are globally unique but ephemeral
+
+Pane IDs like `%0`, `%5`, `%12` are unique across all sessions and windows within a tmux server. They do not reset when windows are created or destroyed.
+
+However, they reset when the tmux **server** restarts. Do not cache pane IDs across server restarts. After killing and recreating a session, re-discover pane IDs with {ref}`list-panes`.
+
+## `suppress_history` requires shell support
+
+The `suppress_history` parameter on `send_keys` prepends a space before the command, which prevents it from being saved in shell history. This only works if the shell's `HISTCONTROL` variable includes `ignorespace` (the default for bash, but not universal across all shells).
+
+## `clear_pane` is not fully atomic
+
+`clear_pane` runs two tmux commands in sequence: `send-keys -R` (reset terminal) then `clear-history` (clear scrollback). There is a brief gap between them where partial content may be visible.
+
+For most use cases this is not a problem. If you need guaranteed clean state, add a small delay before the next `capture_pane`.
diff --git a/docs/topics/index.md b/docs/topics/index.md
index bc7cb9e..ebd3569 100644
--- a/docs/topics/index.md
+++ b/docs/topics/index.md
@@ -29,6 +29,18 @@ Three-tier safety system for controlling tool access.
Symptom-based guide for common issues.
:::
+:::{grid-item-card} Gotchas
+:link: gotchas
+:link-type: doc
+Things that will bite you if you don't know about them.
+:::
+
+:::{grid-item-card} Agent Prompting
+:link: prompting
+:link-type: doc
+Write effective instructions for AI agents using tmux tools.
+:::
+
::::
```{toctree}
@@ -37,5 +49,7 @@ Symptom-based guide for common issues.
architecture
concepts
safety
+gotchas
+prompting
troubleshooting
```
diff --git a/docs/topics/prompting.md b/docs/topics/prompting.md
new file mode 100644
index 0000000..a3180de
--- /dev/null
+++ b/docs/topics/prompting.md
@@ -0,0 +1,92 @@
+(prompting)=
+
+# Agent prompting guide
+
+How to write effective instructions for AI agents using libtmux-mcp.
+
+## What the server tells your agent automatically
+
+Every MCP client receives these instructions when connecting to the libtmux-mcp server. You do not need to repeat this information — the agent already knows it.
+
+```text
+libtmux MCP server for programmatic tmux control. tmux hierarchy:
+Server > Session > Window > Pane. Use pane_id (e.g. '%1') as the
+preferred targeting method - it is globally unique within a tmux server.
+Use send_keys to execute commands and capture_pane to read output. All
+tools accept an optional socket_name parameter for multi-server support
+(defaults to LIBTMUX_SOCKET env var).
+
+IMPORTANT — metadata vs content: list_windows, list_panes, and
+list_sessions only search metadata (names, IDs, current command). To
+find text that is actually visible in terminals — when users ask what
+panes 'contain', 'mention', 'show', or 'have' — use search_panes to
+search across all pane contents, or list_panes + capture_pane on each
+pane for manual inspection.
+```
+
+The server also dynamically adds:
+- **Safety tier context**: Which tier is active and what tools are available
+- **Caller pane awareness**: If the server runs inside tmux, it tells the agent which pane is its own (via `TMUX_PANE`)
+
+## Effective prompt patterns
+
+These natural-language prompts reliably trigger the right tool sequences:
+
+| Prompt | Agent interprets as |
+|--------|-------------------|
+| "Run `pytest` in my build pane and show results" | {toolref}`send-keys` → {toolref}`wait-for-text` → {toolref}`capture-pane` |
+| "Start the dev server and wait until it's ready" | {toolref}`send-keys` → {toolref}`wait-for-text` (for "listening on") |
+| "Check if any pane has errors" | {toolref}`search-panes` with pattern "error" |
+| "Set up a workspace with editor, server, and tests" | {toolref}`create-session` → {toolref}`split-window` (x2) → {toolref}`set-pane-title` (x3) |
+| "What's running in my tmux sessions?" | {toolref}`list-sessions` → {toolref}`list-panes` → {toolref}`capture-pane` |
+| "Kill the old workspace session" | {toolref}`kill-session` (after confirming target) |
+
+## Anti-patterns to avoid
+
+| Prompt | Problem | Better version |
+|--------|---------|---------------|
+| "Run this command" | Ambiguous — agent may use its own shell instead of tmux | "Run `make test` in a tmux pane" |
+| "Check my terminal" | Which pane? Agent must discover first | "Check the pane running `npm dev`" or "Search all panes for errors" |
+| "Clean up everything" | Too broad for destructive operations | "Kill the `ci-test` session" |
+| "Show me the output" | Capture immediately? Or wait? | "Wait for the command to finish, then show me the output" |
+
+## System prompt fragments
+
+Copy these into your agent's system instructions (`CLAUDE.md`, `.cursorrules`, or MCP client config) to improve behavior:
+
+### For general tmux workflows
+
+```text
+When executing long-running commands (servers, builds, test suites),
+use tmux via the libtmux MCP server rather than running them directly.
+This keeps output accessible for later inspection. Use the pattern:
+send_keys → wait_for_text (for completion signal) → capture_pane.
+```
+
+### For safe agent behavior
+
+```text
+Before creating tmux sessions, check list_sessions to avoid duplicates.
+Always use pane_id for targeting — it is globally unique. Never run
+destructive operations (kill_session, kill_server) without confirming
+the target with the user first.
+```
+
+### For development workflows
+
+```text
+When the user asks you to run tests or start servers, use dedicated
+tmux panes. Split windows to run related processes side-by-side.
+Use wait_for_text to know when a server is ready before running tests
+that depend on it.
+```
+
+## Tool selection heuristics
+
+When an agent is unsure which tool to use, these rules help:
+
+1. **Discovery first**: Call {toolref}`list-sessions` or {toolref}`list-panes` before acting on specific targets
+2. **Prefer IDs**: Once you have a `pane_id`, use it for all subsequent calls — it never changes during the pane's lifetime
+3. **Wait, don't poll**: Use {toolref}`wait-for-text` instead of repeatedly calling {toolref}`capture-pane` in a loop
+4. **Content vs. metadata**: If looking for text *in* a terminal, use {toolref}`search-panes`. If looking for pane *properties* (name, PID, path), use {toolref}`list-panes` or {toolref}`get-pane-info`
+5. **Destructive tools are opt-in**: Never kill sessions, windows, or panes unless the user explicitly asks
diff --git a/src/libtmux_mcp/__about__.py b/src/libtmux_mcp/__about__.py
index 5bf5182..4ed7017 100644
--- a/src/libtmux_mcp/__about__.py
+++ b/src/libtmux_mcp/__about__.py
@@ -14,4 +14,4 @@
__url__ = "https://libtmux-mcp.git-pull.com"
__bug_tracker__ = "https://github.com/tmux-python/libtmux-mcp/issues"
__repository__ = "https://github.com/tmux-python/libtmux-mcp"
-__changes__ = "https://github.com/tmux-python/libtmux-mcp/blob/master/CHANGES"
+__changes__ = "https://github.com/tmux-python/libtmux-mcp/blob/main/CHANGES"
diff --git a/src/libtmux_mcp/server.py b/src/libtmux_mcp/server.py
index 2562225..340b8b5 100644
--- a/src/libtmux_mcp/server.py
+++ b/src/libtmux_mcp/server.py
@@ -63,7 +63,9 @@ def _build_instructions(safety_level: str = TAG_MUTATING) -> str:
"Available tiers: 'readonly' (read operations only), "
"'mutating' (default, read + write + send_keys), "
"'destructive' (all operations including kill commands). "
- "Set via LIBTMUX_SAFETY env var."
+ "Set via LIBTMUX_SAFETY env var. "
+ "Tools outside the active tier are hidden and will not appear in "
+ "tool listings."
)
# Agent tmux context
diff --git a/src/libtmux_mcp/tools/env_tools.py b/src/libtmux_mcp/tools/env_tools.py
index 48221e5..04431e5 100644
--- a/src/libtmux_mcp/tools/env_tools.py
+++ b/src/libtmux_mcp/tools/env_tools.py
@@ -27,6 +27,8 @@ def show_environment(
) -> EnvironmentResult:
"""Show tmux environment variables.
+ Use to inspect tmux environment variables that affect child processes.
+
Parameters
----------
session_name : str, optional
@@ -66,6 +68,9 @@ def set_environment(
) -> EnvironmentSetResult:
"""Set a tmux environment variable.
+ Use to set variables that will be inherited by new panes and windows.
+ Changes do not affect already-running processes.
+
Parameters
----------
name : str
diff --git a/src/libtmux_mcp/tools/option_tools.py b/src/libtmux_mcp/tools/option_tools.py
index 8dc23a8..acaf911 100644
--- a/src/libtmux_mcp/tools/option_tools.py
+++ b/src/libtmux_mcp/tools/option_tools.py
@@ -73,6 +73,9 @@ def show_option(
) -> OptionResult:
"""Show a tmux option value.
+ Use to check tmux configuration values such as history-limit,
+ mouse support, or status bar settings.
+
Parameters
----------
option : str
@@ -109,6 +112,9 @@ def set_option(
) -> OptionSetResult:
"""Set a tmux option value.
+ Use to change tmux behavior at runtime. Common uses: adjusting
+ history-limit, enabling mouse support, changing status bar format.
+
Parameters
----------
option : str
diff --git a/src/libtmux_mcp/tools/pane_tools.py b/src/libtmux_mcp/tools/pane_tools.py
index ce79ab1..7fb3cc7 100644
--- a/src/libtmux_mcp/tools/pane_tools.py
+++ b/src/libtmux_mcp/tools/pane_tools.py
@@ -40,6 +40,10 @@ def send_keys(
) -> str:
"""Send keys (commands or text) to a tmux pane.
+ After sending, use wait_for_text to block until the command completes,
+ or capture_pane to read the result. Do not capture_pane immediately —
+ there is a race condition.
+
Parameters
----------
keys : str
@@ -147,6 +151,8 @@ def resize_pane(
) -> PaneInfo:
"""Resize a tmux pane.
+ Use when adjusting layout for better readability or to fit content.
+
Parameters
----------
pane_id : str, optional
@@ -205,6 +211,9 @@ def kill_pane(
) -> str:
"""Kill (close) a tmux pane. Requires exact pane_id (e.g. '%5').
+ Use to clean up panes no longer needed. To remove an entire window
+ and all its panes, use kill_window instead.
+
Parameters
----------
pane_id : str
@@ -245,6 +254,8 @@ def set_pane_title(
) -> PaneInfo:
"""Set the title of a tmux pane.
+ Use titles to label panes for later identification via list_panes or get_pane_info.
+
Parameters
----------
title : str
@@ -287,6 +298,9 @@ def get_pane_info(
) -> PaneInfo:
"""Get detailed information about a tmux pane.
+ Use this for metadata (PID, path, dimensions) without reading terminal content.
+ To read what is displayed in the pane, use capture_pane instead.
+
Parameters
----------
pane_id : str, optional
@@ -326,6 +340,9 @@ def clear_pane(
) -> str:
"""Clear the contents of a tmux pane.
+ Use before send_keys + capture_pane to get a clean capture without prior output.
+ Note: this is two tmux commands with a brief gap — not fully atomic.
+
Parameters
----------
pane_id : str, optional
diff --git a/src/libtmux_mcp/tools/server_tools.py b/src/libtmux_mcp/tools/server_tools.py
index e111247..51d2deb 100644
--- a/src/libtmux_mcp/tools/server_tools.py
+++ b/src/libtmux_mcp/tools/server_tools.py
@@ -31,6 +31,9 @@ def list_sessions(
) -> list[SessionInfo]:
"""List all tmux sessions.
+ Use as the starting point for discovery — call this before targeting
+ specific sessions, windows, or panes.
+
Parameters
----------
socket_name : str, optional
@@ -61,6 +64,9 @@ def create_session(
) -> SessionInfo:
"""Create a new tmux session.
+ Check list_sessions first to avoid name conflicts. A new session
+ starts with one window and one pane.
+
Parameters
----------
session_name : str, optional
@@ -105,6 +111,10 @@ def create_session(
def kill_server(socket_name: str | None = None) -> str:
"""Kill the tmux server and all its sessions.
+ Destroys ALL sessions, windows, and panes on this server. Use kill_session
+ to remove a single session instead. Self-kill protection prevents killing
+ the server running this MCP process.
+
Parameters
----------
socket_name : str, optional
@@ -134,6 +144,9 @@ def kill_server(socket_name: str | None = None) -> str:
def get_server_info(socket_name: str | None = None) -> ServerInfo:
"""Get information about the tmux server.
+ Use to verify the tmux server is running before other operations.
+ For session-level details, use list_sessions instead.
+
Parameters
----------
socket_name : str, optional
diff --git a/src/libtmux_mcp/tools/session_tools.py b/src/libtmux_mcp/tools/session_tools.py
index 7d482b6..1fd1b30 100644
--- a/src/libtmux_mcp/tools/session_tools.py
+++ b/src/libtmux_mcp/tools/session_tools.py
@@ -81,6 +81,8 @@ def create_window(
) -> WindowInfo:
"""Create a new window in a tmux session.
+ Creates a window with one pane. Use split_window to add more panes afterward.
+
Parameters
----------
session_name : str, optional
@@ -137,6 +139,9 @@ def rename_session(
) -> SessionInfo:
"""Rename a tmux session.
+ Use when a session's purpose has changed. Existing pane_id references
+ remain valid after renaming.
+
Parameters
----------
new_name : str
@@ -167,6 +172,10 @@ def kill_session(
) -> str:
"""Kill a tmux session.
+ Destroys the session and all its windows and panes. Use kill_window
+ to remove a single window instead. Self-kill protection prevents
+ killing the session containing this MCP process.
+
Parameters
----------
session_name : str, optional
diff --git a/src/libtmux_mcp/tools/window_tools.py b/src/libtmux_mcp/tools/window_tools.py
index 7896a18..548a04a 100644
--- a/src/libtmux_mcp/tools/window_tools.py
+++ b/src/libtmux_mcp/tools/window_tools.py
@@ -111,6 +111,9 @@ def split_window(
) -> PaneInfo:
"""Split a tmux window to create a new pane.
+ Creates a new pane by splitting an existing one. Use direction to choose
+ above/below/left/right. Returns the new pane's info including its pane_id.
+
Parameters
----------
pane_id : str, optional
@@ -188,6 +191,9 @@ def rename_window(
) -> WindowInfo:
"""Rename a tmux window.
+ Use when a window's purpose has changed. Existing window_id references
+ remain valid after renaming.
+
Parameters
----------
new_name : str
@@ -227,6 +233,10 @@ def kill_window(
) -> str:
"""Kill (close) a tmux window. Requires exact window_id (e.g. '@3').
+ Destroys the window and all its panes. Use kill_pane to remove a single
+ pane instead. Self-kill protection prevents killing the window containing
+ this MCP process.
+
Parameters
----------
window_id : str
@@ -272,6 +282,9 @@ def select_layout(
) -> WindowInfo:
"""Set the layout of a tmux window.
+ Choose from: even-horizontal, even-vertical, main-horizontal,
+ main-vertical, or tiled. Rearranges all panes in the window.
+
Parameters
----------
layout : str
@@ -319,6 +332,8 @@ def resize_window(
) -> WindowInfo:
"""Resize a tmux window.
+ Use to adjust the window dimensions. This affects all panes within the window.
+
Parameters
----------
window_id : str, optional